import { credentials } from './authStorage'; /** * WHEN ANOTHER TAB SIGNS OUT, THIS ONE IS NEVER TOLD. * * A front desk keeps several tabs open. The idle timer is per-tab and in-memory - it watches document * events on its own tab - so whichever tab goes quiet first is the one that signs everybody out. It calls * logout(), which clears the shared localStorage keys and then navigates ITSELF back to the portal page. * * Every other tab keeps rendering. Its React state still says signed in, so the nav, the clinician's name * and any open patient dialog stay on the monitor with the session already gone underneath them. Nothing * in the app listened for `storage`, so there was no path by which those tabs could find out. */ // It watches the credential record: the committed epoch is the session and its retirement is the event. A wakeup // also arrives when someone signs in again elsewhere, so it is only the prompt to look; the epoch read back answers. /** Whether the shared session is gone right now, as the last committed read says. */ export const sessionTokenIsGone = (): boolean => // Storage that cannot be read cannot tell us the session ended, and claiming it did would cover a working // screen. Silence is the safe direction here, unlike everywhere else in this file. credentials().current().state === 'signed-out'; /** The committed epoch of the shared session, or null when there is none. */ export const storedSessionId = (): string | null => credentials().epoch(); /** A view signed in under a session that predates ids is judged by its token alone. */ export const UNNUMBERED_SESSION = ''; // The session this tab is ending itself. Its own sweep and the moments before its reload are not another tab's. let endingHere: string | null | undefined; /** Called by logout with the epoch it ends: this tab's own sign-out never covers this tab. */ export const noteSessionEndingHere = (epoch: string | null = storedSessionId()): void => { endingHere = epoch ?? UNNUMBERED_SESSION; }; /** Check the displayed session on storage changes and tab return; a different session cannot restore this view. */ export function watchForSessionEndedElsewhere( onEnded: () => void, viewSession: () => string | undefined, ): () => void { if (typeof window === 'undefined') return () => undefined; const check = () => { const shown = viewSession(); if (shown === undefined || shown === endingHere) return; // Nothing read yet, or nothing readable, says nothing about whether the session ended. const state = credentials().current().state; if (state === 'loading' || state === 'unavailable') return; if (sessionTokenIsGone() || (shown !== UNNUMBERED_SESSION && storedSessionId() !== shown)) onEnded(); }; const onVisible = () => { if (document.visibilityState === 'visible') check(); }; const stopRecord = credentials().onChange(check); window.addEventListener('pageshow', check); window.addEventListener('focus', check); document.addEventListener('visibilitychange', onVisible); check(); return () => { stopRecord(); window.removeEventListener('pageshow', check); window.removeEventListener('focus', check); document.removeEventListener('visibilitychange', onVisible); }; }