Files
spa/src/auth/cross-tab-sync.ts
julian d3ccdded23 feat: task 1.8 logout flow + cross-tab sync + strict tsconfig
- src/auth/logout.ts: performLogout({ queryClient, navigate? })
  orchestration. Calls store.logout(), clears query cache, navigates
  to /login. Server-call failures swallowed; local state still flips
  to anonymous.
- src/auth/cross-tab-sync.ts: startCrossTabSync(). Subscribes to the
  auth store and bumps trm-auth-version in localStorage on status
  transitions; listens for storage events and re-runs initialize()
  when another tab bumps. Idempotent via module-level guard. No-ops
  if localStorage is unavailable.
- src/ui/components/logout-button.tsx: <LogoutButton> wraps shadcn
  Button. Pass-through props but locks onClick / disabled / children.
  Local isLoggingOut for "Signing out..." indicator + double-click
  guard.
- src/auth/bootstrap.tsx: <AuthBootstrap> now calls startCrossTabSync()
  alongside the existing init steps. Single mount point.
- src/auth/index.ts: re-export performLogout + startCrossTabSync.
- src/routes/_authed/index.tsx: replaced the inline sign-out button
  with <LogoutButton />. The existing useEffect on 'anonymous' status
  handles navigation when cross-tab sync fires.

Bonus: tsconfig.app.json now has "strict": true. TanStack Router emits
a conditional-type error ("strictNullChecks must be enabled") in
editors when strict is off; tsc -b was lenient. Caught in 1.7's
review. Typecheck remained green after enabling.
2026-05-02 18:46:35 +02:00

45 lines
1.4 KiB
TypeScript

import { useAuthStore } from './store';
/**
* Cross-tab auth synchronisation via the `storage` event.
*
* When a user logs out (or in) in tab A, other tabs need to pick up the
* new state without the user clicking around. The storage event fires on
* every other tab when localStorage changes — bumping a version key in
* tab A causes tabs B, C, ... to re-run the auth probe and converge.
*
* Pattern is one-way: this tab writes the version key on its own auth
* transitions; storage events trigger `initialize()` to re-read state from
* Directus. The actual auth state lives in cookies (server-issued), not in
* localStorage — the version key is just a "something changed, re-check"
* signal.
*
* Idempotent: safe to call multiple times (HMR-friendly).
*/
const VERSION_KEY = 'trm-auth-version';
let started = false;
export function startCrossTabSync(): void {
if (started) return;
started = true;
let lastStatus: string | null = null;
useAuthStore.subscribe((state) => {
if (state.status === lastStatus) return;
lastStatus = state.status;
try {
localStorage.setItem(VERSION_KEY, String(Date.now()));
} catch {
// localStorage may be unavailable (private browsing, quota). Sync
// silently no-ops; auth still works in this tab, just not cross-tab.
}
});
window.addEventListener('storage', (ev) => {
if (ev.key !== VERSION_KEY) return;
void useAuthStore.getState().initialize();
});
}