From 63d579fd3cd4c6223c0d0d4344a4d70b380718a0 Mon Sep 17 00:00:00 2001 From: PhuocOng <122703392+PhuocOng@users.noreply.github.com> Date: Sun, 13 Sep 2026 16:00:36 -0700 Subject: [PATCH] fix(cors): preserve network error responses --- src/cors.test.ts | 29 +++++++++++++++++++++++++++++ src/cors.ts | 7 ++++--- src/with-supabase.test.ts | 18 ++++++++++++++++++ 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/cors.test.ts b/src/cors.test.ts index 4c9a694..bf12211 100644 --- a/src/cors.test.ts +++ b/src/cors.test.ts @@ -64,6 +64,35 @@ describe('isCorsDisabled', () => { }) describe('addCorsHeaders', () => { + it.each([ + undefined, + 'default', + { headers: { 'Access-Control-Allow-Origin': 'https://example.com' } }, + ] as const)( + 'preserves network error responses with CORS config %j', + (config) => { + const response = Response.error() + const result = addCorsHeaders(response, config) + + expect(result).toBe(response) + expect(result.type).toBe('error') + expect(result.status).toBe(0) + expect(result.headers.get('Access-Control-Allow-Origin')).toBeNull() + }, + ) + + it.each([400, 500])( + 'still adds CORS headers to HTTP %i responses', + async (status) => { + const response = new Response('upstream failure', { status }) + const result = addCorsHeaders(response) + + expect(result.status).toBe(status) + expect(result.headers.get('Access-Control-Allow-Origin')).toBe('*') + expect(await result.text()).toBe('upstream failure') + }, + ) + it('adds default CORS headers to response', () => { const response = new Response('ok') const result = addCorsHeaders(response, true) diff --git a/src/cors.ts b/src/cors.ts index d733fd0..ef98b03 100644 --- a/src/cors.ts +++ b/src/cors.ts @@ -57,11 +57,12 @@ export function buildCorsHeaders(config?: CorsConfig): Record { * Returns a new `Response` with CORS headers appended. * * Creates a clone of the original response and sets each CORS header on it. - * If CORS is disabled (`'disabled'` or the deprecated `false`), returns the original response unchanged. + * If CORS is disabled (`'disabled'` or the deprecated `false`), or the response + * represents a network error, returns the original response unchanged. * * @param response - The original response to augment. * @param config - The CORS configuration. - * @returns A new `Response` with CORS headers set, or the original response if CORS is disabled. + * @returns A new `Response` with CORS headers set, or the original response if CORS is disabled or it represents a network error. * * @internal */ @@ -69,7 +70,7 @@ export function addCorsHeaders( response: Response, config?: CorsConfig, ): Response { - if (isCorsDisabled(config)) return response + if (isCorsDisabled(config) || response.type === 'error') return response const corsHeaders = buildCorsHeaders(config) const newResponse = new Response(response.body, response) diff --git a/src/with-supabase.test.ts b/src/with-supabase.test.ts index 93ba5a6..5b05e47 100644 --- a/src/with-supabase.test.ts +++ b/src/with-supabase.test.ts @@ -215,6 +215,24 @@ describe('withSupabase', () => { }) }) + it.each(['handler', 'pipeline'] as const)( + 'preserves network error responses in %s composition', + async (composition) => { + const response = Response.error() + const config = { auth: 'none', env: baseEnv } as const + const next = async () => response + const handler = + composition === 'pipeline' + ? pipeline([withSupabase(config)], next) + : withSupabase(config, next) + + const result = await handler(new Request('http://localhost')) + + expect(result).toBe(response) + expect(result.type).toBe('error') + }, + ) + it('adds CORS headers to success response', async () => { const handler = withSupabase({ auth: 'none', env: baseEnv }, async () => Response.json({ ok: true }),