fix(auth): auth pages reload when a session appears in another tab
Fixes #69 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
@@ -0,0 +1,33 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { getFallbackSession, subscribeSessionChanges } from '../state/sessions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* #69 — `useSessionSync` (the full cross-tab session sync, with in-place token
|
||||||
|
* rotation handling) is mounted only inside the authenticated `ClientRoot`
|
||||||
|
* shell, so a tab parked on the login/register/OIDC-callback pages never
|
||||||
|
* learns that another tab just signed in. Left alone, that tab keeps showing
|
||||||
|
* the login form and, if submitted, starts a second device/session for the
|
||||||
|
* same account.
|
||||||
|
*
|
||||||
|
* This is a deliberately lighter watcher for the unauthenticated routes: it
|
||||||
|
* only cares about a session APPEARING where there was none before (a fresh
|
||||||
|
* login completed in another tab) and reloads so this tab picks up the
|
||||||
|
* authenticated shell instead. It does not need `useSessionSync`'s
|
||||||
|
* rotation/relogin handling since there is no running client here to keep in
|
||||||
|
* sync — do not import/duplicate that logic, just reload.
|
||||||
|
*/
|
||||||
|
export function useSessionAppeared(): void {
|
||||||
|
useEffect(() => {
|
||||||
|
// If we already have a session, there's nothing to watch for here — the
|
||||||
|
// authenticated shell's own useSessionSync takes over once mounted.
|
||||||
|
if (getFallbackSession()) return undefined;
|
||||||
|
|
||||||
|
const unsubscribe = subscribeSessionChanges((session) => {
|
||||||
|
if (session) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return unsubscribe;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ import { settingsAtom } from '../state/settings';
|
|||||||
import { isWithinTimeWindow } from '../utils/timeWindow';
|
import { isWithinTimeWindow } from '../utils/timeWindow';
|
||||||
import { LotusToastContainer } from '../features/toast/LotusToastContainer';
|
import { LotusToastContainer } from '../features/toast/LotusToastContainer';
|
||||||
import { useTauriNotificationBadge } from '../hooks/useTauriNotificationBadge';
|
import { useTauriNotificationBadge } from '../hooks/useTauriNotificationBadge';
|
||||||
|
import { useSessionAppeared } from '../hooks/useSessionAppeared';
|
||||||
import { useTauriWindowChrome } from '../hooks/useTauriWindowChrome';
|
import { useTauriWindowChrome } from '../hooks/useTauriWindowChrome';
|
||||||
import { isTauri } from '../hooks/useTauri';
|
import { isTauri } from '../hooks/useTauri';
|
||||||
import { TitleBar } from '../features/desktop/TitleBar';
|
import { TitleBar } from '../features/desktop/TitleBar';
|
||||||
@@ -165,6 +166,12 @@ const queryClient = new QueryClient();
|
|||||||
function App() {
|
function App() {
|
||||||
const screenSize = useScreenSize();
|
const screenSize = useScreenSize();
|
||||||
useCompositionEndTracking();
|
useCompositionEndTracking();
|
||||||
|
// #69 — cross-tab session sync (useSessionSync) only mounts inside the
|
||||||
|
// authenticated ClientRoot shell, so a tab still on the unauthenticated
|
||||||
|
// routes (login/register/OIDC callback) never learns that another tab just
|
||||||
|
// signed in. This lighter watcher covers that gap by reloading once a
|
||||||
|
// session appears where there was none.
|
||||||
|
useSessionAppeared();
|
||||||
|
|
||||||
const portalContainer = document.getElementById('portalContainer') ?? undefined;
|
const portalContainer = document.getElementById('portalContainer') ?? undefined;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user