diff --git a/src/session.spec.ts b/src/session.spec.ts index 86246af..2b7ef4b 100644 --- a/src/session.spec.ts +++ b/src/session.spec.ts @@ -323,6 +323,41 @@ describe('session', () => { }); }); + it('should pass through non-JSON responses with just the cookie added', async () => { + // Set up a custom loader that returns HTML + const htmlContent = '

Hello World!

'; + const customLoader = jest.fn().mockReturnValue( + new Response(htmlContent, { + headers: { + 'Content-Type': 'text/html', + 'X-Custom-Header': 'test-value', + }, + }), + ); + + // Call authkitLoader with the HTML-returning loader + const result = await authkitLoader(createLoaderArgs(createMockRequest()), customLoader); + + // Verify we got back a Response, not a DataWithResponseInit + assertIsResponse(result); + + // Check that the response body wasn't modified + const resultText = await result.clone().text(); + expect(resultText).toBe(htmlContent); + + // Check that original headers were preserved + expect(result.headers.get('Content-Type')).toBe('text/html'); + expect(result.headers.get('X-Custom-Header')).toBe('test-value'); + + // Check that session cookie was added + expect(result.headers.get('Set-Cookie')).toBe('session-cookie'); + + // Verify that the JSON parsing method was not called + const jsonSpy = jest.spyOn(Response.prototype, 'json'); + expect(jsonSpy).not.toHaveBeenCalled(); + jsonSpy.mockRestore(); + }); + it('should return authorized data with session claims', async () => { const { data } = await authkitLoader(createLoaderArgs(createMockRequest())); @@ -371,7 +406,7 @@ describe('session', () => { const { data, init } = await authkitLoader(createLoaderArgs(createMockRequest()), customLoader); expect(getHeaderValue(init?.headers, 'Custom-Header')).toBe('test-header'); - expect(getHeaderValue(init?.headers, 'Content-Type')).toBe('application/json; charset=utf-8'); + expect(getHeaderValue(init?.headers, 'Content-Type')).toBe('application/json'); expect(data).toEqual( expect.objectContaining({ diff --git a/src/session.ts b/src/session.ts index c21fa2b..e134b52 100644 --- a/src/session.ts +++ b/src/session.ts @@ -14,7 +14,7 @@ import { sealData, unsealData } from 'iron-session'; import { createRemoteJWKSet, decodeJwt, jwtVerify } from 'jose'; import { getConfig } from './config.js'; import { configureSessionStorage, getSessionStorage } from './sessionStorage.js'; -import { isResponse, isRedirect } from './utils.js'; +import { isResponse, isRedirect, isJsonResponse } from './utils.js'; // must be a type since this is a subtype of response // interfaces must conform to the types they extend @@ -309,15 +309,17 @@ async function handleAuthLoader( } const newResponse = new Response(loaderResult.body, loaderResult); - const responseData = await newResponse.json(); - // Set the content type in case the user returned a Response instead of the - // json helper method - newResponse.headers.set('Content-Type', 'application/json; charset=utf-8'); if (session) { newResponse.headers.append('Set-Cookie', session.headers['Set-Cookie']); } + if (!isJsonResponse(newResponse)) { + return newResponse; + } + + const responseData = await newResponse.json(); + return data({ ...responseData, ...auth }, newResponse); } diff --git a/src/utils.ts b/src/utils.ts index a869cbe..29b742d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -37,6 +37,17 @@ export function isResponse(response: unknown): response is Response { return response instanceof Response; } +/** + * Returns true if the response is a JSON response. + * This is determined by checking if the Content-Type header includes 'application/json'. + * @param res - The response to check. + * @returns True if the response is a JSON response. + */ +export function isJsonResponse(res: Response): boolean { + const contentType = res.headers.get('Content-Type')?.toLowerCase(); + return !!contentType?.includes('application/json'); +} + /** * Checks if the data is a DataWithResponseInit object. * @param data - The data to check.