/** * Where the session token is kept, and how the HTTP client reaches it. * * A module of its own, holding nothing but the storage key and a reader, * because the two files that need it cannot import each other: `session.ts` * calls the API to sign in, and `client.ts` needs the token to make that same * API call authorised. Anything shared between them has to sit underneath both. * * It imports nothing, on purpose — that is what keeps it free of the cycle. */ /** * The single owner of this key. * * `session.ts` writes the blob and `client.ts` reads one field out of it, and a * second copy of the string is how those two quietly stop agreeing after a * rename. */ export const SESSION_STORAGE_KEY = 'nearle.session.v1'; /** * The signed session on this tab, if there is one. * * Reads storage on every call rather than caching. Sign-in and sign-out both * happen while the app is running, and a cached token would keep authorising * requests for a user who has just left — or send nothing for one who has just * arrived, until a reload. * * Returns undefined for every failure, including a throw. `sessionStorage` * raises rather than returning null in a browser with site data blocked, and a * console that cannot read a token should make an unauthenticated request and * be refused by the server, not fail to render. */ export function readSessionToken(): string | undefined { try { const raw = sessionStorage.getItem(SESSION_STORAGE_KEY); if (!raw) return undefined; const parsed: unknown = JSON.parse(raw); if (typeof parsed !== 'object' || parsed === null) return undefined; const token = (parsed as { token?: unknown }).token; return typeof token === 'string' && token !== '' ? token : undefined; } catch { return undefined; } } /** * The `Authorization` header for a request, or nothing at all. * * Nothing, rather than an empty or `Bearer null` header, when there is no * session. A header that is present but meaningless is worse than an absent * one: `middleware.WebAuth` refuses a token that does not verify whatever the * enforcement flag says, so sending rubbish would turn every anonymous call * into a 401 — including the ones that are still meant to work while the * rollout is in progress. */ export function authHeader(): Record { const token = readSessionToken(); return token ? { Authorization: `Bearer ${token}` } : {}; }