feat(auth): detect session expiry live and offer re-login in place
Every response already funnels through runtimeFetch, so a classifier there spots 401s, confirms them against /auth/session (a proxied provider 401 must not read as a logout), and flips a small auth-session store. The web and hosted surfaces show a frosted banner under the header whose Log in button hands off to the session gate's existing unlock flow; sends are paused while expired, the session-load error screen explains the auth case and retries itself after login, and returning to a long-idle window revalidates once via visibility/focus. Native mobile feeds the same signal into its connection re-probe instead of showing the banner; VS Code is exempt.
This commit is contained in:
@@ -12,6 +12,7 @@ import { SettingsView } from '@/components/views/SettingsView';
|
||||
import { AppLinkConfirmDialog } from '@/components/chat/AppLinkConfirmDialog';
|
||||
import { ErrorBoundary } from '@/components/ui/ErrorBoundary';
|
||||
import { RuntimeAPIProvider } from '@/contexts/RuntimeAPIProvider';
|
||||
import { useAuthSessionStore } from '@/lib/runtime-auth-expiry';
|
||||
import { registerRuntimeAPIs } from '@/contexts/runtimeAPIRegistry';
|
||||
import { TooltipProvider } from '@/components/ui/tooltip';
|
||||
import { Toaster } from '@/components/ui/sonner';
|
||||
@@ -772,6 +773,23 @@ export function MobileApp({ apis }: MobileAppProps) {
|
||||
};
|
||||
}, [isNativeMobileApp, handleNativeResume]);
|
||||
|
||||
// A confirmed mid-session auth expiry (classified centrally from live 401
|
||||
// traffic) runs the same seq-guarded re-probe the resume path uses: it ends
|
||||
// in needs-login → the native welcome screen with the auth-expired notice.
|
||||
// The shared web banner never renders on native (the session gate is not
|
||||
// mounted here), so this is the only surface reacting to the signal.
|
||||
React.useEffect(() => {
|
||||
if (!isNativeMobileApp) return;
|
||||
return useAuthSessionStore.subscribe((store, previous) => {
|
||||
if (store.state === 'expired' && previous.state !== 'expired') {
|
||||
handleNativeResume();
|
||||
// The probe ladder owns the outcome from here; the shared store goes
|
||||
// back to 'ok' so a later expiry can signal again.
|
||||
useAuthSessionStore.getState().markAuthenticated();
|
||||
}
|
||||
});
|
||||
}, [isNativeMobileApp, handleNativeResume]);
|
||||
|
||||
React.useEffect(() => {
|
||||
registerRuntimeAPIs(apis);
|
||||
return () => registerRuntimeAPIs(null);
|
||||
|
||||
Reference in New Issue
Block a user