Files
frontend-helper/src/lib/session-store.ts
T

62 lines
2.0 KiB
TypeScript

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<string, SessionData>;
};
const store: Map<string, SessionData> =
globalRef.__mfbSessionStore ?? new Map<string, SessionData>();
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);
}