diff --git a/.changeset/fuzzy-jeans-tickle.md b/.changeset/fuzzy-jeans-tickle.md new file mode 100644 index 0000000000..3925411c4a --- /dev/null +++ b/.changeset/fuzzy-jeans-tickle.md @@ -0,0 +1,5 @@ +--- +'@shopify/hydrogen': patch +--- + +Adds ability to add multiple cookies in one response diff --git a/packages/hydrogen/src/entry-server.tsx b/packages/hydrogen/src/entry-server.tsx index 442f36025d..831d5cdecb 100644 --- a/packages/hydrogen/src/entry-server.tsx +++ b/packages/hydrogen/src/entry-server.tsx @@ -475,14 +475,20 @@ async function stream( } if (await isStreamingSupported()) { - return new Response(transform.readable, responseOptions); + return new Response(transform.readable, { + ...responseOptions, + headers: getHeaders(responseOptions.headers), + }); } const bufferedBody = await bufferReadableStream( transform.readable.getReader() ); - return new Response(bufferedBody, responseOptions); + return new Response(bufferedBody, { + ...responseOptions, + headers: getHeaders(responseOptions.headers), + }); } else if (response) { const {pipe} = ssrRenderToPipeableStream(AppSSR, { nonce, @@ -818,9 +824,22 @@ function getResponseOptions( error?: Error ) { const responseInit = {} as ResponseOptions; + // @ts-ignore responseInit.headers = Object.fromEntries(headers.entries()); + // @ts-ignore + const rawHeaders = headers.raw(); + // Warning! Headers.raw is non-standard and might disappear in undici or newer versions of node-fetch + // See: https://github.com/whatwg/fetch/issues/973 + const setCookieKey = Object.keys(rawHeaders).find( + (key) => key.toLowerCase() === 'set-cookie' + ); + + if (setCookieKey) { + responseInit.headers['set-cookie'] = rawHeaders[setCookieKey]; + } + if (error) { responseInit.status = 500; } else { @@ -911,3 +930,22 @@ function postRequestTasks( logQueryTimings(type, request); request.savePreloadQueries(); } + +function getHeaders(rawHeaders: Record> = {}) { + const headers = new Headers(); + + for (const [key, values] of Object.entries(rawHeaders)) { + // values doesn't have an array prototype, so instanceof doesn't work. + // Check for .splice instead + // @ts-ignore + if (values?.splice) { + for (const value of values) { + headers.append(key, value as string); + } + } else { + headers.append(key, values as string); + } + } + + return headers; +} diff --git a/packages/playground/server-components/tests/e2e-test-cases.ts b/packages/playground/server-components/tests/e2e-test-cases.ts index 4330cc29c5..8e7cad259a 100644 --- a/packages/playground/server-components/tests/e2e-test-cases.ts +++ b/packages/playground/server-components/tests/e2e-test-cases.ts @@ -184,11 +184,11 @@ export default async function testCases({ expect(response.status).toEqual(201); // statusText cannot be modified in workers expect(response.statusText).toEqual(isWorker ? 'Created' : 'hey'); - expect(response.headers.get('Accept-Encoding')).toBe('deflate, gzip'); - expect(response.headers.get('Set-Cookie')).toBe( - 'hello=world, hello2=world2' - ); + expect(response.headers.raw()['set-cookie']).toEqual([ + 'hello=world', + 'hello2=world2', + ]); }); it('uses the provided custom body', async () => {