// `session.ts` is the thin layer that loads or creates the device // session at app startup. The keypair half is always materialised // (loaded from the [KeyStore], or freshly generated when the slot is // empty); the device-session-id half is read out of the [Cache] and // is `null` until Phase 7's confirm-email-code handler stores it via // [setDeviceSessionId]. A `null` deviceSessionId is the marker that // the user must run the email-code login flow. // // Phase 7 wires the production caller (a layout-level loader that // runs `loadDeviceSession` on first render and forwards the result // into the Svelte session store). Phase 6 ships the persistence // primitives this loader needs. import type { Cache, DeviceKeypair, KeyStore } from "../platform/store/index"; export const SESSION_NAMESPACE = "session"; export const SESSION_ID_KEY = "device-session-id"; export interface DeviceSession { keypair: DeviceKeypair; deviceSessionId: string | null; } /** * loadDeviceSession returns the device session for the current * device. The keypair is loaded from `keyStore`; if the slot is * empty, a fresh non-exportable Ed25519 keypair is generated and * persisted. The returned `deviceSessionId` is `null` until Phase 7 * registers the public key with the gateway and persists the * resulting id via [setDeviceSessionId]. */ export async function loadDeviceSession( keyStore: KeyStore, cache: Cache, ): Promise { const existing = await keyStore.load(); const keypair = existing ?? (await keyStore.generate()); const stored = await cache.get(SESSION_NAMESPACE, SESSION_ID_KEY); return { keypair, deviceSessionId: stored ?? null }; } /** * setDeviceSessionId persists the device-session id returned by the * gateway's confirm-email-code response so subsequent app starts can * resume the session without re-login. */ export async function setDeviceSessionId( cache: Cache, deviceSessionId: string, ): Promise { await cache.put(SESSION_NAMESPACE, SESSION_ID_KEY, deviceSessionId); } /** * clearDeviceSession wipes both the device keypair and the stored * device-session id. The next [loadDeviceSession] call will generate * a fresh keypair and report `deviceSessionId: null`, forcing * re-login. Used by user-driven logout and by gateway-driven * revocation paths. */ export async function clearDeviceSession( keyStore: KeyStore, cache: Cache, ): Promise { await Promise.all([ keyStore.clear(), cache.delete(SESSION_NAMESPACE, SESSION_ID_KEY), ]); }