From c7da6459d21230cb8ac13b4bdf3f79c1dcdf5b09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6berl?= Date: Sun, 23 Jun 2024 19:50:37 +0200 Subject: [PATCH 1/7] feat: switch to Azure Functions v4 * remove function.json - it is not needed anymore * rename function path to "sk_render" since v4 does not allow the path to start with "__" See also: https://techcommunity.microsoft.com/t5/apps-on-azure-blog/azure-functions-version-4-of-the-node-js-programming-model-is-in/ba-p/3773541 closes #159 --- demo/func/host.json | 2 +- files/api/host.json | 2 +- files/api/package.json | 7 ++- files/entry.js | 132 +++++++++++++++++++---------------------- files/headers.js | 13 ++-- index.d.ts | 7 ++- index.js | 22 +------ package-lock.json | 49 ++++++++++++--- package.json | 2 +- 9 files changed, 126 insertions(+), 110 deletions(-) diff --git a/demo/func/host.json b/demo/func/host.json index 14992fc..dd16270 100644 --- a/demo/func/host.json +++ b/demo/func/host.json @@ -2,6 +2,6 @@ "version": "2.0", "extensionBundle": { "id": "Microsoft.Azure.Functions.ExtensionBundle", - "version": "[2.*, 3.0.0)" + "version": "[4.0.0, 5.0.0)" } } diff --git a/files/api/host.json b/files/api/host.json index 14992fc..dd16270 100644 --- a/files/api/host.json +++ b/files/api/host.json @@ -2,6 +2,6 @@ "version": "2.0", "extensionBundle": { "id": "Microsoft.Azure.Functions.ExtensionBundle", - "version": "[2.*, 3.0.0)" + "version": "[4.0.0, 5.0.0)" } } diff --git a/files/api/package.json b/files/api/package.json index 0967ef4..88851a9 100644 --- a/files/api/package.json +++ b/files/api/package.json @@ -1 +1,6 @@ -{} +{ + "main": "sk_render/index.js", + "dependencies": { + "@azure/functions": "^4" + } +} diff --git a/files/entry.js b/files/entry.js index 2ae126e..6f49bd2 100644 --- a/files/entry.js +++ b/files/entry.js @@ -6,6 +6,7 @@ import { getClientPrincipalFromHeaders, splitCookiesFromHeaders } from './headers'; +import { app } from '@azure/functions'; // replaced at build time // @ts-expect-error @@ -17,99 +18,86 @@ const server = new Server(manifest); const initialized = server.init({ env: process.env }); /** - * @typedef {import('@azure/functions').AzureFunction} AzureFunction - * @typedef {import('@azure/functions').Context} Context + * @typedef {import('@azure/functions').InvocationContext} InvocationContext * @typedef {import('@azure/functions').HttpRequest} HttpRequest + * @typedef {import('@azure/functions').HttpResponse} HttpResponse */ -/** - * @param {Context} context - */ -export async function index(context) { - const request = toRequest(context); - - if (debug) { - context.log( - 'Starting request', - context?.req?.method, - context?.req?.headers?.['x-ms-original-url'] - ); - context.log(`Original request: ${JSON.stringify(context)}`); - context.log(`Request: ${JSON.stringify(request)}`); - } - - const ipAddress = getClientIPFromHeaders(request.headers); - const clientPrincipal = getClientPrincipalFromHeaders(request.headers); - - await initialized; - const rendered = await server.respond(request, { - getClientAddress() { - return ipAddress; - }, - platform: { - clientPrincipal, - context +app.http('sk_render', { + methods: ['HEAD', 'GET', 'POST', 'DELETE', 'PUT', 'OPTIONS'], + /** + * + * @param {HttpRequest} httpRequest + * @param {InvocationContext} context + */ + handler: async (httpRequest, context) => { + if (debug) { + context.log(`Request: ${JSON.stringify(httpRequest)}`); } - }); - const response = await toResponse(rendered); + const request = toRequest(httpRequest); + + const ipAddress = getClientIPFromHeaders(request.headers); + const clientPrincipal = getClientPrincipalFromHeaders(request.headers); + + await initialized; + const rendered = await server.respond(request, { + getClientAddress() { + return ipAddress; + }, + platform: { + user: httpRequest.user, + clientPrincipal, + context + } + }); + + if (debug) { + context.log(`SK headers: ${JSON.stringify(Object.fromEntries(rendered.headers.entries()))}`); + context.log(`Response: ${JSON.stringify(rendered)}`); + } - if (debug) { - context.log(`SK headers: ${JSON.stringify(Object.fromEntries(rendered.headers.entries()))}`); - context.log(`Response: ${JSON.stringify(response)}`); + return toResponse(rendered); } - - context.res = response; -} +}); /** - * @param {Context} context + * @param {HttpRequest} httpRequest * @returns {Request} - * */ -function toRequest(context) { - const { method, headers, rawBody, body } = context.req; - // because we proxy all requests to the render function, the original URL in the request is /api/__render - // this header contains the URL the user requested - const originalUrl = headers['x-ms-original-url']; - - // SWA strips content-type headers from empty POST requests, but SK form actions require the header - // https://github.com/geoffrich/svelte-adapter-azure-swa/issues/178 - if (method === 'POST' && !body && !headers['content-type']) { - headers['content-type'] = 'application/x-www-form-urlencoded'; - } - - /** @type {RequestInit} */ - const init = { - method, - headers: new Headers(headers) - }; - - if (method !== 'GET' && method !== 'HEAD') { - init.body = Buffer.isBuffer(body) - ? body - : typeof rawBody === 'string' - ? Buffer.from(rawBody, 'utf-8') - : rawBody; - } + */ +function toRequest(httpRequest) { + const originalUrl = httpRequest.headers.get('x-ms-original-url'); + + /** @type {Record} */ + const headers = {}; + httpRequest.headers.forEach((value, key) => { + if (key !== 'x-ms-original-url') { + headers[key] = value; + } + }); - return new Request(originalUrl, init); + return new Request(originalUrl, { + method: httpRequest.method, + headers: new Headers(headers), + // @ts-ignore + body: httpRequest.body, + duplex: 'half' + }); } /** * @param {Response} rendered - * @returns {Promise>} + * @returns {Promise} */ async function toResponse(rendered) { - const { status } = rendered; - const resBody = new Uint8Array(await rendered.arrayBuffer()); - const { headers, cookies } = splitCookiesFromHeaders(rendered.headers); return { - status, - body: resBody, + status: rendered.status, + // @ts-ignore + body: rendered.body, headers, cookies, - isRaw: true + enableContentNegotiation: false }; } diff --git a/files/headers.js b/files/headers.js index b606ab6..3e7967a 100644 --- a/files/headers.js +++ b/files/headers.js @@ -1,30 +1,35 @@ import * as set_cookie_parser from 'set-cookie-parser'; +/** + * @typedef {import('@azure/functions').Cookie} Cookie + */ + /** * Splits 'set-cookie' headers into individual cookies * @param {Headers} headers * @returns {{ - * headers: Record, - * cookies: set_cookie_parser.Cookie[] + * headers: Headers, + * cookies: Cookie[] * }} */ export function splitCookiesFromHeaders(headers) { /** @type {Record} */ const resHeaders = {}; - /** @type {set_cookie_parser.Cookie[]} */ + /** @type {Cookie[]} */ const resCookies = []; headers.forEach((value, key) => { if (key === 'set-cookie') { const cookieStrings = set_cookie_parser.splitCookiesString(value); + // @ts-ignore resCookies.push(...set_cookie_parser.parse(cookieStrings)); } else { resHeaders[key] = value; } }); - return { headers: resHeaders, cookies: resCookies }; + return { headers: new Headers(resHeaders), cookies: resCookies }; } /** diff --git a/index.d.ts b/index.d.ts index dd304be..7f6c30f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,6 +1,6 @@ import { Adapter } from '@sveltejs/kit'; import { ClientPrincipal, CustomStaticWebAppConfig } from './types/swa'; -import { Context } from '@azure/functions'; +import { HttpRequestUser, InvocationContext } from '@azure/functions'; import esbuild from 'esbuild'; export * from './types/swa'; @@ -37,8 +37,11 @@ declare global { * * @see The {@link https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-node#context-object Azure function documentation} */ + context: InvocationContext; + + user: HttpRequestUser; + clientPrincipal?: ClientPrincipal; - context: Context; } } } diff --git a/index.js b/index.js index f59a34e..b9d9887 100644 --- a/index.js +++ b/index.js @@ -7,26 +7,7 @@ import esbuild from 'esbuild'; * @typedef {import('esbuild').BuildOptions} BuildOptions */ -const ssrFunctionRoute = '/api/__render'; - -const functionJson = ` -{ - "bindings": [ - { - "authLevel": "anonymous", - "type": "httpTrigger", - "direction": "in", - "name": "req", - "route": "__render" - }, - { - "type": "http", - "direction": "out", - "name": "res" - } - ] -} -`; +const ssrFunctionRoute = '/api/sk_render'; /** * Validate the static web app configuration does not override the minimum config for the adapter to work correctly. @@ -137,7 +118,6 @@ If you want to suppress this error, set allowReservedSwaRoutes to true in your a }; await esbuild.build(default_options); - writeFileSync(join(functionDir, 'function.json'), functionJson); builder.log.minor('Copying assets...'); builder.writeClient(staticDir); diff --git a/package-lock.json b/package-lock.json index a1a5582..e1d7654 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,19 @@ { "name": "svelte-adapter-azure-swa", - "version": "0.20.1", + "version": "0.21.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "svelte-adapter-azure-swa", - "version": "0.19.1", + "version": "0.21.0", "license": "MIT", "dependencies": { "esbuild": "^0.19.9", "set-cookie-parser": "^2.6.0" }, "devDependencies": { - "@azure/functions": "^1.2.3", + "@azure/functions": "^4", "@sveltejs/kit": "^2.0.4", "@types/node": "^18.19.3", "@types/set-cookie-parser": "^2.4.7", @@ -40,10 +40,18 @@ } }, "node_modules/@azure/functions": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@azure/functions/-/functions-1.2.3.tgz", - "integrity": "sha512-dZITbYPNg6ay6ngcCOjRUh1wDhlFITS0zIkqplyH5KfKEAVPooaoaye5mUFnR+WP9WdGRjlNXyl/y2tgWKHcRg==", - "dev": true + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@azure/functions/-/functions-4.5.0.tgz", + "integrity": "sha512-WNCiOHMQEZpezxgThD3o2McKEjUEljtQBvdw4X4oE5714eTw76h33kIj0660ZJGEnxYSx4dx18oAbg5kLMs9iQ==", + "dev": true, + "dependencies": { + "cookie": "^0.6.0", + "long": "^4.0.0", + "undici": "^5.13.0" + }, + "engines": { + "node": ">=18.0" + } }, "node_modules/@esbuild/aix-ppc64": { "version": "0.19.10", @@ -390,6 +398,15 @@ "node": ">=12" } }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -1220,6 +1237,12 @@ "dev": true, "peer": true }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "dev": true + }, "node_modules/loupe": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", @@ -1746,6 +1769,18 @@ "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==", "dev": true }, + "node_modules/undici": { + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", + "dev": true, + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", diff --git a/package.json b/package.json index 901ddbc..726f1e6 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "@sveltejs/kit": "^2.0.0" }, "devDependencies": { - "@azure/functions": "^1.2.3", + "@azure/functions": "^4", "@sveltejs/kit": "^2.0.4", "@types/node": "^18.19.3", "@types/set-cookie-parser": "^2.4.7", From 595addf3fa9cc6c6a94e5dd6a47ebb71fa0f40c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6berl?= Date: Tue, 25 Jun 2024 21:10:58 +0200 Subject: [PATCH 2/7] add @azure/functions to external esbuild options (if not already there) --- index.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/index.js b/index.js index b9d9887..279deaf 100644 --- a/index.js +++ b/index.js @@ -104,6 +104,11 @@ If you want to suppress this error, set allowReservedSwaRoutes to true in your a })};\n` ); + // add @azure/functions to esbuildOptions.external if not already set - this is needed by the Azure Functiions v4 runtime + if (!esbuildOptions.external?.includes('@azure/functions')) { + esbuildOptions.external = [...(esbuildOptions.external || []), '@azure/functions']; + } + /** @type {BuildOptions} */ const default_options = { entryPoints: [entry], From db3cba8ce7ad32f88ebcc0ab3787acba8ab3857d Mon Sep 17 00:00:00 2001 From: Geoff Rich <4992896+geoffrich@users.noreply.github.com> Date: Sat, 13 Jul 2024 17:29:34 -0700 Subject: [PATCH 3/7] Port over changes from #179 --- files/entry.js | 15 +++++++++++++++ package-lock.json | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/files/entry.js b/files/entry.js index 6f49bd2..230bb35 100644 --- a/files/entry.js +++ b/files/entry.js @@ -32,6 +32,11 @@ app.http('sk_render', { */ handler: async (httpRequest, context) => { if (debug) { + context.log( + 'Starting request', + httpRequest.method, + httpRequest.headers.get('x-ms-original-url') + ); context.log(`Request: ${JSON.stringify(httpRequest)}`); } @@ -68,6 +73,16 @@ app.http('sk_render', { function toRequest(httpRequest) { const originalUrl = httpRequest.headers.get('x-ms-original-url'); + // SWA strips content-type headers from empty POST requests, but SK form actions require the header + // https://github.com/geoffrich/svelte-adapter-azure-swa/issues/178 + if ( + httpRequest.method === 'POST' && + !httpRequest.body && + !httpRequest.headers.get('content-type') + ) { + httpRequest.headers.set('content-type', 'application/x-www-form-urlencoded'); + } + /** @type {Record} */ const headers = {}; httpRequest.headers.forEach((value, key) => { diff --git a/package-lock.json b/package-lock.json index e1d7654..162489f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "svelte-adapter-azure-swa", - "version": "0.21.0", + "version": "0.20.1", "license": "MIT", "dependencies": { "esbuild": "^0.19.9", From 3340de33d1cb9cb5cad7f91805d7f646655f49a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6berl?= <123199+derkoe@users.noreply.github.com> Date: Mon, 15 Jul 2024 07:36:13 +0000 Subject: [PATCH 4/7] Fix one unit test and update docs / release notes --- CHANGELOG.md | 10 ++++++++++ README.md | 2 +- test/index.test.js | 10 +++++----- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fafb334..b158bc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## [Unreleased] + +### Features + +* **Breaking:** switch to Azure Functions v4: + * the function path has changed from `/api/__render` to `/api/sk_render` (v4 does not allow routes starting with underscores) + * see also [Migrate to version 4 of the Node.js programming model for Azure Functions](https://learn.microsoft.com/en-us/azure/azure-functions/functions-node-upgrade-v4) from the Azure docs + + + ### [0.20.1](https://www.github.com/geoffrich/svelte-adapter-azure-swa/compare/v0.20.0...v0.20.1) (2024-07-13) diff --git a/README.md b/README.md index 9bc9986..c9a1d34 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ custom/ └── index.js ``` -Also note that the adapter reserves the folder prefix `sk_render` and API route prefix `__render` for Azure functions generated by the adapter. So, if you use a custom API directory, you cannot have any other folder starting with `sk_render` or functions available at the `__render` route, since these will conflict with the adapter's Azure functions. +Also note that the adapter reserves the folder prefix `sk_render` and API route prefix `sk_render` for Azure functions generated by the adapter. So, if you use a custom API directory, you cannot have any other folder starting with `sk_render` or functions available at the `sk_render` route, since these will conflict with the adapter's Azure functions. ### staticDir diff --git a/test/index.test.js b/test/index.test.js index 2155895..e0f97ad 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -21,7 +21,7 @@ describe('generateConfig', () => { const result = generateConfig({}, 'appDir'); expect(result).toStrictEqual({ navigationFallback: { - rewrite: '/api/__render' + rewrite: '/api/sk_render' }, platform: { apiRuntime: 'node:18' @@ -29,7 +29,7 @@ describe('generateConfig', () => { routes: expect.arrayContaining([ { methods: ['POST', 'PUT', 'DELETE'], - rewrite: '/api/__render', + rewrite: '/api/sk_render', route: '*' }, { @@ -79,7 +79,7 @@ describe('adapt', () => { await adapter.adapt(builder); expect(writeFileSync).toBeCalledWith( 'custom/api/sk_render/function.json', - expect.stringContaining('__render') + expect.stringContaining('sk_render') ); // we don't copy the required function files to a custom API directory expect(builder.copy).not.toBeCalledWith(expect.stringContaining('api'), 'custom/api'); @@ -116,11 +116,11 @@ describe('adapt', () => { routes: expect.arrayContaining([ { route: '/index.html', - rewrite: '/api/__render' + rewrite: '/api/sk_render' }, { route: '/', - rewrite: '/api/__render' + rewrite: '/api/sk_render' } ]) }) From 9337132adeb27f6c074717e734ac3db2439fd070 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6berl?= <123199+derkoe@users.noreply.github.com> Date: Mon, 15 Jul 2024 07:52:12 +0000 Subject: [PATCH 5/7] fix test 'writes to custom api directory' --- test/index.test.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/index.test.js b/test/index.test.js index e0f97ad..fd35ded 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -2,6 +2,7 @@ import { expect, describe, test, vi } from 'vitest'; import azureAdapter, { generateConfig } from '../index'; import { writeFileSync, existsSync } from 'fs'; import { jsonMatching, toMatchJSON } from './json'; +import esbuild from 'esbuild'; expect.extend({ jsonMatching, toMatchJSON }); @@ -77,10 +78,10 @@ describe('adapt', () => { const adapter = azureAdapter({ apiDir: 'custom/api' }); const builder = getMockBuilder(); await adapter.adapt(builder); - expect(writeFileSync).toBeCalledWith( - 'custom/api/sk_render/function.json', - expect.stringContaining('sk_render') - ); + expect(esbuild.build).toBeCalledWith(expect.objectContaining({ + outfile: 'custom/api/sk_render/index.js', + })); + // we don't copy the required function files to a custom API directory expect(builder.copy).not.toBeCalledWith(expect.stringContaining('api'), 'custom/api'); }); From 53ec98a4a6e13364ff9f9ff983d791ca1ae5297a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6berl?= <123199+derkoe@users.noreply.github.com> Date: Mon, 15 Jul 2024 08:40:21 +0000 Subject: [PATCH 6/7] convert external function to v4 --- demo/func/HelloWorld/function.json | 16 -------------- demo/func/HelloWorld/index.js | 35 +++++++++++++++++++----------- 2 files changed, 22 insertions(+), 29 deletions(-) delete mode 100644 demo/func/HelloWorld/function.json diff --git a/demo/func/HelloWorld/function.json b/demo/func/HelloWorld/function.json deleted file mode 100644 index 1c1f040..0000000 --- a/demo/func/HelloWorld/function.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "bindings": [ - { - "authLevel": "anonymous", - "type": "httpTrigger", - "direction": "in", - "name": "req", - "methods": ["get", "post"] - }, - { - "type": "http", - "direction": "out", - "name": "res" - } - ] -} diff --git a/demo/func/HelloWorld/index.js b/demo/func/HelloWorld/index.js index bd41adb..4189cc9 100644 --- a/demo/func/HelloWorld/index.js +++ b/demo/func/HelloWorld/index.js @@ -1,13 +1,22 @@ -module.exports = async function (context, req) { - context.log('JavaScript HTTP trigger function processed a request.'); - - const name = req.query.name || (req.body && req.body.name); - const responseMessage = name - ? 'Hello, ' + name + '. This HTTP triggered function executed successfully.' - : 'This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.'; - - context.res = { - // status: 200, /* Defaults to 200 */ - body: responseMessage - }; -}; +const { app } = require('@azure/functions'); + +app.http('httpTrigger1', { + methods: ['GET', 'POST'], + handler: async (req, context) => { + context.log('JavaScript HTTP trigger function processed a request.'); + + let name; + if (req.query.has('name')) { + name = req.query.get('name') + } else { + let body = await req.json(); + name = body.name; + } + + const responseMessage = name + ? 'Hello, ' + name + '. This HTTP triggered function executed successfully.' + : 'This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.'; + + return { body: responseMessage }; + } +}); From 4cbffd0faca40798d2050ff135bedd5b543b421a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6berl?= <123199+derkoe@users.noreply.github.com> Date: Mon, 15 Jul 2024 11:26:28 +0000 Subject: [PATCH 7/7] fix headers test - use fetch API --- test/headers.test.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/headers.test.js b/test/headers.test.js index 37145bc..b807e5c 100644 --- a/test/headers.test.js +++ b/test/headers.test.js @@ -18,10 +18,7 @@ describe('header processing', () => { expect(cookies).toEqual({ cookies: [], - headers: { - 'content-type': 'application/json', - location: '/' - } + headers }); }); @@ -40,7 +37,7 @@ describe('header processing', () => { const cookies = splitCookiesFromHeaders(headers); expect(cookies).toStrictEqual({ - headers: {}, + headers: new Headers(), cookies: [ { expires: new Date('1994-11-06T08:49:37.000Z'),