Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105import { browser } from "$app/environment";import { isDid, type Did } from "@atcute/lexicons/syntax";
// atcute owns oauth sessions; this stores metadata/order and active-account cookies.
export const CURRENT_DID_KEY = "tangled.currentDid";export const CURRENT_HANDLE_KEY = "tangled.currentHandle";const ACCOUNTS_KEY = "tangled.accounts";
// appview account cap parityexport const MAX_ACCOUNTS = 20;
export interface AuthAccount { did: Did; handle: string; // unix seconds; appview parity addedAt: number;}
const isAccount = (value: unknown): value is AuthAccount => !!value && typeof value === "object" && isDid((value as AuthAccount).did) && typeof (value as AuthAccount).handle === "string";
export const loadAccounts = (): AuthAccount[] => { if (!browser) return []; try { const raw = localStorage.getItem(ACCOUNTS_KEY); if (!raw) return []; const parsed = JSON.parse(raw) as unknown; if (!Array.isArray(parsed)) return []; return parsed.filter(isAccount).map((account) => ({ did: account.did, handle: account.handle, addedAt: typeof account.addedAt === "number" ? account.addedAt : 0 })); } catch { return []; }};
export const saveAccounts = (accounts: readonly AuthAccount[]): void => { if (!browser) return; localStorage.setItem(ACCOUNTS_KEY, JSON.stringify(accounts));};
// stored sessions are authoritative, metadata only supplies order and handleexport const reconcileAccounts = ( stored: readonly Did[], meta: readonly AuthAccount[]): AuthAccount[] => { const storedSet = new Set(stored); const known = new Set(meta.map((account) => account.did)); const ordered = meta.filter((account) => storedSet.has(account.did)); for (const did of stored) { if (!known.has(did)) { ordered.push({ did, handle: did, addedAt: Math.floor(Date.now() / 1000) }); } } return ordered;};
// dedupe by did, preserve insertion order, and keep the original addedAt.export const upsertAccount = ( accounts: readonly AuthAccount[], account: AuthAccount): AuthAccount[] => { const index = accounts.findIndex((existing) => existing.did === account.did); if (index >= 0) { const next = accounts.slice(); next[index] = { ...account, addedAt: accounts[index].addedAt }; return next; } if (accounts.length >= MAX_ACCOUNTS) return accounts.slice(); return [...accounts, account];};
export const dropAccount = (accounts: readonly AuthAccount[], did: Did): AuthAccount[] => accounts.filter((account) => account.did !== did);
export const persistActive = (did: string, handle: string): void => { if (!browser) return; localStorage.setItem(CURRENT_DID_KEY, did); localStorage.setItem(CURRENT_HANDLE_KEY, handle); const secure = location.protocol === "https:" ? "; secure" : ""; const attrs = `; path=/; max-age=31536000; samesite=lax${secure}`; document.cookie = `${CURRENT_DID_KEY}=${encodeURIComponent(did)}${attrs}`; document.cookie = `${CURRENT_HANDLE_KEY}=${encodeURIComponent(handle)}${attrs}`;};
export const clearActive = (): void => { if (!browser) return; localStorage.removeItem(CURRENT_DID_KEY); localStorage.removeItem(CURRENT_HANDLE_KEY); document.cookie = `${CURRENT_DID_KEY}=; path=/; max-age=0; samesite=lax`; document.cookie = `${CURRENT_HANDLE_KEY}=; path=/; max-age=0; samesite=lax`;};
export const readActiveDid = (): Did | null => { if (!browser) return null; const value = localStorage.getItem(CURRENT_DID_KEY); return isDid(value) ? value : null;};