/** * Vest sessions relayed by the extension. * * Vest logs a person in through Google/Privy in the browser, and refreshes * the session with a device key the browser will not export, so this * machine cannot log in by itself. Instead the AutoFirmer extension watches * the signed-in Vest tab: whenever the Vest app renews its own ~15-minute * access token, the extension posts it here (POST /api/vest/session). The * Vest client (lib/vest-class.ts) trades with the latest one for its wallet * and mints per-account tokens from it. * * Kept in memory only — never in the DB, the logs, a report or a backup. A * restart forgets them; the Vest tab's next renewal (or the extension's * next tick) relays a fresh one within minutes. */ export interface VestSession { accessToken: string; /** Epoch ms the access token stops working. */ expiresAt: number; /** The wallet the Vest login belongs to, lower-case. */ primaryAddr: string; userId: string; receivedAt: number; } /** What the dashboard and the extension may see of a session: never the token. */ export interface VestSessionInfo { primaryAddr: string; userId: string; expiresAt: number; receivedAt: number; } const g = globalThis as typeof globalThis & { __vestSessions?: Map }; function store(): Map { return (g.__vestSessions ??= new Map()); } /** Shortest life a relayed token must have left to be taken. */ const MIN_REMAINING_MS = 30_000; /** Longest a relayed token may claim to live — a Vest access token is * ~15 minutes; anything far beyond that is not one. */ const MAX_REMAINING_MS = 6 * 60 * 60_000; /** Take a relayed session. Returns why it was refused, or null. A token for * a wallet only ever replaces one that expires sooner. */ export function putVestSession(input: { accessToken: unknown; expiresAt: unknown; primaryAddr: unknown; userId: unknown }, now = Date.now()): string | null { const token = typeof input.accessToken === 'string' ? input.accessToken.trim() : ''; const expiresAt = Number(input.expiresAt); const addr = typeof input.primaryAddr === 'string' ? input.primaryAddr.trim().toLowerCase() : ''; if (!/^[\w-]+\.[\w-]+\.[\w-]+$/.test(token) || token.length > 8192) return 'accessToken is not a JWT'; if (!/^0x[0-9a-f]{40}$/.test(addr)) return 'primaryAddr is not a wallet address'; if (!Number.isFinite(expiresAt) || expiresAt - now < MIN_REMAINING_MS) return 'token already expired'; if (expiresAt - now > MAX_REMAINING_MS) return 'expiresAt is implausibly far away'; const prev = store().get(addr); if (prev && prev.expiresAt > expiresAt && prev.accessToken !== token) return null; // keep the fresher one store().set(addr, { accessToken: token, expiresAt, primaryAddr: addr, userId: typeof input.userId === 'string' ? input.userId : '', receivedAt: now, }); return null; } /** The live session for a wallet, or null when none has been relayed or it * is about to expire. */ export function vestSessionFor(primaryAddr: string, now = Date.now(), minRemainingMs = MIN_REMAINING_MS): VestSession | null { const s = store().get(primaryAddr.trim().toLowerCase()); return s && s.expiresAt - now >= minRemainingMs ? s : null; } /** Drop a wallet's session — the gateway refused it. */ export function dropVestSession(primaryAddr: string, token: string): void { const key = primaryAddr.trim().toLowerCase(); if (store().get(key)?.accessToken === token) store().delete(key); } /** Every relayed session, newest first, without tokens. */ export function listVestSessions(): VestSessionInfo[] { return [...store().values()] .sort((a, b) => b.receivedAt - a.receivedAt) .map(({ primaryAddr, userId, expiresAt, receivedAt }) => ({ primaryAddr, userId, expiresAt, receivedAt })); }