import { getCsrfToken, setAuthUserKey } from "./auth"; export const SESSION_INVALIDATED_EVENT = "session-invalidated"; const SESSION_SIGNAL_KEY = "authSessionContext"; let bound: { id: string; context: string; csrf: string | null } | null = null; let invalidated = false; const requests = new AbortController(); export function isSessionInvalidated() { return invalidated; } export function sessionRequestSignal() { return requests.signal; } export function getSessionContext() { return invalidated ? null : bound?.context ?? null; } export function invalidateSession() { if (invalidated) return; invalidated = true; requests.abort(); window.dispatchEvent(new Event(SESSION_INVALIDATED_EVENT)); } // A page never adopts a second session. Reloading disposes old forms, promises, caches, // dialogs and retry callbacks together; none can obtain the next account's write context. export function bindSession(id: string, context: string) { if (invalidated) return false; if (!id || !context || (bound && (bound.id !== id || bound.context !== context))) { invalidateSession(); return false; } bound ??= { id, context, csrf: getCsrfToken() }; setAuthUserKey(id, false); try { window.localStorage.setItem(SESSION_SIGNAL_KEY, context); } catch { /* Cookie/focus checks still apply. */ } return true; } export function checkSessionCookie() { if (bound && bound.csrf !== getCsrfToken()) invalidateSession(); } window.addEventListener("storage", event => { if (!bound) return; if (event.key === null || (event.key === SESSION_SIGNAL_KEY && event.newValue !== bound.context) || (event.key === "authUserKey" && event.newValue !== bound.id)) invalidateSession(); }); window.addEventListener("auth-user-changed", checkSessionCookie); window.addEventListener("focus", checkSessionCookie); window.addEventListener("pageshow", event => { // A BFCache snapshot can contain private state and outstanding callbacks from an old session. if (event.persisted && bound) invalidateSession(); else checkSessionCookie(); }); window.addEventListener("visibilitychange", checkSessionCookie); // Fallback when cookies change through another client without a storage event. window.setInterval(checkSessionCookie, 1000);