From d02bbdf93c0a921daacc1a086a2727bb3341ab51 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 18 Sep 2026 02:11:18 +0000 Subject: [PATCH] Deduplicate the expired-session handling in the API client request() and requestMultipart() repeated the identical clear-auth, toast, and SPA-navigate-to-login block verbatim, including its comment. Extract it into dropExpiredSession() so the logic (and the reasoning for it) exists in one place. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01VGNJ41qzCnHvQ2VYDaZw2X --- frontend/src/api/client.ts | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 4f1affff..7d51a4fd 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -113,6 +113,19 @@ async function handleUnauthorized(): Promise { return refreshPromise } +// Shared by `request` and `requestMultipart`: the refresh failed (or there was +// no session to refresh), so drop it and send the user back to sign in. +function dropExpiredSession(): void { + useAuthStore.getState().clearAuth() + toast('Your session has expired. Please sign in again.', 'error') + // SPA navigation, not window.location: a hard document load forces a full + // reload from the local WebView origin (capacitor://localhost on iOS, + // https://localhost on Android), which re-bootstraps the whole app and is + // unreliable for an absolute path under the capacitor:// scheme. Imported + // lazily to avoid pulling the route tree into this low-level module. + void import('../router').then(({ router }) => router.navigate({ to: '/login' })) +} + async function request(path: string, options: RequestOptions = {}, isRetry = false): Promise { const { body, headers, ...rest } = options let token = useAuthStore.getState().accessToken @@ -145,14 +158,7 @@ async function request(path: string, options: RequestOptions = {}, isRetry = if (refreshed) { return request(path, options, true) } - useAuthStore.getState().clearAuth() - toast('Your session has expired. Please sign in again.', 'error') - // SPA navigation, not window.location: a hard document load forces a full - // reload from the local WebView origin (capacitor://localhost on iOS, - // https://localhost on Android), which re-bootstraps the whole app and is - // unreliable for an absolute path under the capacitor:// scheme. Imported - // lazily to avoid pulling the route tree into this low-level module. - void import('../router').then(({ router }) => router.navigate({ to: '/login' })) + dropExpiredSession() throw new Error('Session expired') } @@ -190,14 +196,7 @@ async function requestMultipart(path: string, formData: FormData, isRetry = f if (refreshed) { return requestMultipart(path, formData, true) } - useAuthStore.getState().clearAuth() - toast('Your session has expired. Please sign in again.', 'error') - // SPA navigation, not window.location: a hard document load forces a full - // reload from the local WebView origin (capacitor://localhost on iOS, - // https://localhost on Android), which re-bootstraps the whole app and is - // unreliable for an absolute path under the capacitor:// scheme. Imported - // lazily to avoid pulling the route tree into this low-level module. - void import('../router').then(({ router }) => router.navigate({ to: '/login' })) + dropExpiredSession() throw new Error('Session expired') }