// Undercooked — the tab identity (PLAN A16; carried over from the // phase-01 shell, where these helpers lived in `app.js`). // // Identity is a 24-byte random token per browser tab (the browser's // `crypto.getRandomValues` is the same entropy the server's // `secrets.token_urlsafe(24)` uses, and the only option a browser has), // held in `sessionStorage` so a reload re-joins instead of re-minting. // The last room code is remembered the same way, so a reload can // rejoin fast. Nothing here is personal: no names, no PII (PLAN §11). const TOKEN_KEY = "uc.token"; const LAST_CODE_KEY = "uc.lastCode"; const CODE_LENGTH = 5; /** Base64url without padding, so the token is safe in a query string or header. */ function base64url(bytes) { let binary = ""; for (const byte of bytes) binary += String.fromCharCode(byte); return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); } /** A random, unguessable id for this browser tab (identity, not an account). */ export function randomToken() { const bytes = new Uint8Array(24); globalThis.crypto.getRandomValues(bytes); return base64url(bytes); } function read(storage, key) { try { return storage.getItem(key); } catch { return null; // private browsing / storage disabled: stay functional for this tab only } } function write(storage, key, value) { try { storage.setItem(key, value); } catch { /* nothing to persist to; the in-memory value still works */ } } /** The tab's session token, created once per tab and kept in sessionStorage. */ export function getToken(storage = globalThis.sessionStorage) { const existing = read(storage, TOKEN_KEY); if (typeof existing === "string" && existing.length > 0) return existing; const token = randomToken(); write(storage, TOKEN_KEY, token); return token; } /** Uppercase what the user typed and keep only characters a code can contain. */ export function normalizeCode(value) { return String(value ?? "") .toUpperCase() .replace(/[^0-9A-Z]/g, "") .slice(0, CODE_LENGTH); } export function lastCode(storage = globalThis.sessionStorage) { return read(storage, LAST_CODE_KEY) || ""; } export function rememberCode(code, storage = globalThis.sessionStorage) { write(storage, LAST_CODE_KEY, normalizeCode(code)); } export { CODE_LENGTH };