From 4302449b61a000845c2f345131ef85b34e0103d2 Mon Sep 17 00:00:00 2001 From: HitanshiThakar Date: Wed, 3 Jun 2026 00:50:16 +0530 Subject: [PATCH 1/2] fix(frontend): clear request timeout on failed requests --- frontend/src/api.ts | 50 +++++++++++++++----------- frontend/testing/unit/api.auth.test.ts | 43 +++++++++++++++++++++- 2 files changed, 72 insertions(+), 21 deletions(-) diff --git a/frontend/src/api.ts b/frontend/src/api.ts index b3cb9fcf..3971ae64 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -115,28 +115,38 @@ async function request(path: string, init?: RequestInit): Promise { const apiKey = getApiKey() const authHeaders: Record = apiKey ? { 'X-Api-Key': apiKey } : {} - const response = await fetch(`${API_BASE}${path}`, { - ...init, - headers: { - ...authHeaders, - ...(init?.headers as Record | undefined), - }, - signal: controller.signal, - }) - window.clearTimeout(timeoutId) - - if (response.status === 401) { - // Notify the app so it can show the API-key setup UI without every - // caller needing to handle auth independently. - window.dispatchEvent(new CustomEvent(AUTH_REQUIRED_EVENT)) - throw new Error('AUTH_REQUIRED') - } - - if (!response.ok) { - throw new Error(`Request failed: ${response.status}`) + try { + const response = await fetch(`${API_BASE}${path}`, { + ...init, + headers: { + ...authHeaders, + ...(init?.headers as Record | undefined), + }, + signal: controller.signal, + }) + + if (response.status === 401) { + // Notify the app so it can show the API-key setup UI without every + // caller needing to handle auth independently. + window.dispatchEvent(new CustomEvent(AUTH_REQUIRED_EVENT)) + throw new Error('AUTH_REQUIRED') + } + + if (!response.ok) { + throw new Error(`Request failed: ${response.status}`) + } + return response.json() + } finally { + window.clearTimeout(timeoutId) } - return response.json() } + + // if (!response.ok) { + // throw new Error(`Request failed: ${response.status}`) + // } + // return response.json() + // } + export function getHealth() { return request('/health') diff --git a/frontend/testing/unit/api.auth.test.ts b/frontend/testing/unit/api.auth.test.ts index 2ef7352f..dc96372f 100644 --- a/frontend/testing/unit/api.auth.test.ts +++ b/frontend/testing/unit/api.auth.test.ts @@ -30,7 +30,7 @@ function mockResponse(status: number, body: unknown = {}) { ok: status >= 200 && status < 300, status, json: () => Promise.resolve(body), - } as Response) + }) } // --------------------------------------------------------------------------- @@ -155,6 +155,47 @@ describe('request() 401 handling', () => { }) }) +// --------------------------------------------------------------------------- +// request() — timeout cleanup +// --------------------------------------------------------------------------- + +describe('request() timeout cleanup', () => { + afterEach(() => { + vi.useRealTimers() + vi.restoreAllMocks() + vi.unstubAllGlobals() + }) + + it('clears the timeout when fetch rejects', async () => { + const timeoutId = 1 as unknown as ReturnType +vi.spyOn(window, 'setTimeout').mockImplementation(() => timeoutId) + const clearTimeoutSpy = vi.spyOn(window, 'clearTimeout') + vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('fetch failed'))) + + await expect(listPlugins()).rejects.toThrow('fetch failed') + expect(clearTimeoutSpy).toHaveBeenCalledWith(timeoutId) + }) + + it('clears the timeout when request is aborted', async () => { + vi.useFakeTimers() + const clearTimeoutSpy = vi.spyOn(window, 'clearTimeout') + + vi.stubGlobal('fetch', vi.fn().mockImplementation((_, init) => { + const signal = (init as any)?.signal + return new Promise((_resolve, reject) => { + signal?.addEventListener('abort', () => { + reject(new DOMException('Aborted', 'AbortError')) + }) + }) + })) + + const promise = listPlugins() + vi.runAllTimers() + await expect(promise).rejects.toThrow('Aborted') + expect(clearTimeoutSpy).toHaveBeenCalled() + }) +}) + // --------------------------------------------------------------------------- // request() — successful authenticated request // --------------------------------------------------------------------------- From 74ed641b26eefee525686975e06de2fd78098ffd Mon Sep 17 00:00:00 2001 From: HitanshiThakar Date: Wed, 3 Jun 2026 01:36:01 +0530 Subject: [PATCH 2/2] fix: remove trailing whitespace --- frontend/src/api.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 3971ae64..d5ea3db0 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -140,13 +140,6 @@ async function request(path: string, init?: RequestInit): Promise { window.clearTimeout(timeoutId) } } - - // if (!response.ok) { - // throw new Error(`Request failed: ${response.status}`) - // } - // return response.json() - // } - export function getHealth() { return request('/health')