import { randomBytes } from "node:crypto"; // Server-side session store. The browser never sees any of this; it only holds // the opaque id (see cookie.ts). Keyed by a random 256-bit session id. // // NOTE: this is in-memory and per-process. It is fine for now but does not // survive a restart and is not shared across instances. A durable store // (e.g. Redis) is a later concern, tracked outside this task. export interface SessionPerson { userId: string; name: string; email: string; pictureUrl: string | null; /** The person's id on the feed (feed GET /api/v1/auth/me data.id); null when unknown. */ feedUserId?: string | null; } export interface SessionData { profileAccessToken: string; /** Epoch milliseconds when the profile access token expires, or null if unknown. */ profileAccessTokenExpiresAt: number | null; profileRefreshToken: string; feedAccessToken: string; /** Epoch milliseconds when the feed access token expires, or null if unknown. */ feedAccessTokenExpiresAt: number | null; person: SessionPerson; createdAt: number; } // Survive Next.js dev HMR by stashing the map on globalThis. const globalRef = globalThis as typeof globalThis & { __mfbSessionStore?: Map; }; const store: Map = globalRef.__mfbSessionStore ?? new Map(); globalRef.__mfbSessionStore = store; export function createSession(data: SessionData): string { const id = randomBytes(32).toString("hex"); store.set(id, data); return id; } export function getSession(id: string): SessionData | null { return store.get(id) ?? null; } /** * Replace the data for an existing session. Used after a token refresh to * persist the new tokens. A no-op when the session is already gone, so a * refresh cannot resurrect a signed-out session. */ export function updateSession(id: string, data: SessionData): void { if (store.has(id)) store.set(id, data); } export function deleteSession(id: string): void { store.delete(id); }