From f46b37192faa5037639093a892c44a3535227884 Mon Sep 17 00:00:00 2001 From: saopig1 <4x7sw862st@gmail.com> Date: Wed, 27 May 2026 14:34:56 +0800 Subject: [PATCH] fix(web): break infinite recursion in apiFetch by capturing nativeFetch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit apiFetch called window.fetch which installApiClient had reassigned to call apiFetch — every request blew the stack. Capture the native fetch at module load (before any wrap) and use it inside apiFetch. Symptom: first-run / login redirect never fires because the router guard hangs on session.refresh(). Co-Authored-By: Claude Opus 4.7 (1M context) --- web/src/api/http.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/web/src/api/http.ts b/web/src/api/http.ts index 920c02b..e196051 100644 --- a/web/src/api/http.ts +++ b/web/src/api/http.ts @@ -2,11 +2,14 @@ import router from '../router/index.js'; import { useSession } from '../composables/useSession.js'; let installed = false; +const nativeFetch: typeof window.fetch = window.fetch.bind(window); /** * Wraps fetch so every call: * - sends cookies (`credentials: 'same-origin'`) * - on 401 from /api/*: clear local session, redirect to /login + * + * Always uses the captured native fetch, never the (possibly wrapped) global. */ export function apiFetch(input: RequestInfo | URL, init: RequestInit = {}): Promise { const merged: RequestInit = { @@ -14,7 +17,7 @@ export function apiFetch(input: RequestInfo | URL, init: RequestInit = {}): Prom ...init, headers: { ...(init.headers ?? {}) }, }; - return fetch(input, merged).then(async (res) => { + return nativeFetch(input, merged).then(async (res) => { if (res.status === 401 && shouldTriggerRefresh(input)) { const session = useSession(); await session.refresh(); @@ -39,10 +42,8 @@ function shouldTriggerRefresh(input: RequestInfo | URL): boolean { export function installApiClient(): void { if (installed) return; installed = true; - const original = window.fetch.bind(window); window.fetch = ((input: RequestInfo | URL, init?: RequestInit) => { return apiFetch(input, init ?? {}); }) as typeof window.fetch; - // Keep original accessible if anything needs to bypass - (window as unknown as { __originalFetch?: typeof fetch }).__originalFetch = original; + (window as unknown as { __originalFetch?: typeof fetch }).__originalFetch = nativeFetch; }