From beeee893f6bd4062bcad19c2dd0f5b686b33649c Mon Sep 17 00:00:00 2001 From: abhishek Date: Thu, 24 Sep 2026 11:42:02 +0530 Subject: [PATCH] changes --- src/api/client.ts | 35 ++++++++++++++++++++++++++++++++++- src/auth/token.ts | 21 +++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/api/client.ts b/src/api/client.ts index cc6cea7..e944cbd 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -6,9 +6,38 @@ * changes. Nothing else in the app calls `fetch`. */ -import { authHeader } from '@/auth/token'; +import { authHeader, forgetSession, readSessionToken } from '@/auth/token'; import type { FiestaEnvelope } from './types'; +/** + * A 401 on a call we authenticated means the session is over. + * + * Twelve hours after signing in, or the moment the signing key is rotated under + * an open tab, every request starts coming back 401. Without this the console + * keeps sending the dead token and each page renders its own error — which a + * shopkeeper reads as "my data has gone", not as "sign in again". The screen + * fills with failures and nothing tells them the one thing that would fix it. + * + * Only when a token was actually SENT. A 401 on an anonymous call is the + * server declining to serve a stranger, not a session ending — and the + * sign-in probe deliberately posts with no password to read a 401 back, so + * reacting to that one would clear the session at the login screen and make + * signing in impossible. + * + * `location.reload()` rather than a router push: the session is held in React + * state that this module cannot reach, and a reload is the one move guaranteed + * to land on the sign-in screen from anywhere in the app. It happens once, + * because the storage is cleared first — the reloaded app has no token, so the + * next 401 cannot loop. + */ +function endDeadSession(path: string): void { + if (!readSessionToken()) return; + forgetSession(); + // eslint-disable-next-line no-console + console.warn(`[nearle] session rejected on ${path}; signing out`); + if (typeof window !== 'undefined') window.location.reload(); +} + /** * Where Fiesta is. * @@ -182,6 +211,10 @@ async function request(path: string, options: RequestOptions = {}): Promise { const token = readSessionToken(); return token ? { Authorization: `Bearer ${token}` } : {}; } + +/** + * Drop this tab's session, because the server says the token is no longer good. + * + * A session expires after twelve hours, and the signing key can be rotated + * under a tab that is still open. Without this the console goes on sending a + * dead token and every page shows an error — which reads to the person as "my + * data has gone", not as "sign in again". + * + * Lives here rather than in `session.ts` for the same reason the reader does: + * the HTTP client is what learns a session is dead, and it cannot import the + * module that calls it. + */ +export function forgetSession(): void { + try { + sessionStorage.removeItem(SESSION_STORAGE_KEY); + } catch { + // Storage throws where site data is blocked. Nothing to do: the next read + // returns undefined either way, which is the state this wanted. + } +}