fix(sessions): atomic session blob + cross-tab sync (N97 partial)
Session now persists as ONE atomic cinny_session_v1 JSON write (blob-wins read, transparent migration from the ~10 legacy keys, dual-write kept one release for rollback). subscribeSessionChanges + useSessionSync reload a tab whose session was changed/removed by another tab (logout/login/token rotation). OIDC refresher already routes through setFallbackSession, so rotations stay atomic. Tests 7→22. Full token-protection redesign remains tracked in LOTUS_BUGS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import { useEffect } from 'react';
|
||||
import { getFallbackSession, subscribeSessionChanges } from '../state/sessions';
|
||||
|
||||
/**
|
||||
* Keep this tab in sync with session changes performed in other tabs/windows.
|
||||
*
|
||||
* The coordinator mounts this once inside the authenticated client shell.
|
||||
* `storage` events fire only in tabs that did NOT perform the write, so the
|
||||
* callback here always represents an out-of-tab change.
|
||||
*
|
||||
* Default action is the safest one for auth-critical state — a full reload:
|
||||
* - session REMOVED elsewhere (logout / localStorage.clear()) → the access
|
||||
* token disappears, so we reload; the router bounces to auth on next boot.
|
||||
* - session APPEARED or its access token CHANGED elsewhere (a fresh login or
|
||||
* a token rotation) → we reload so the client re-initialises with the new
|
||||
* credentials rather than running on a stale/revoked token.
|
||||
*
|
||||
* A change that does not alter the access token (e.g. an OIDC metadata-only
|
||||
* rewrite) is ignored, which also collapses the several storage events emitted
|
||||
* by a single dual-write into at most one reload.
|
||||
*/
|
||||
export const useSessionSync = (): void => {
|
||||
useEffect(() => {
|
||||
// Snapshot the credential this tab booted with; compare against it so we
|
||||
// only reload on a genuine credential change.
|
||||
const initialAccessToken = getFallbackSession()?.accessToken ?? null;
|
||||
|
||||
const unsubscribe = subscribeSessionChanges((session) => {
|
||||
const nextAccessToken = session?.accessToken ?? null;
|
||||
if (nextAccessToken === initialAccessToken) return;
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, []);
|
||||
};
|
||||
Reference in New Issue
Block a user