Repository: geoffrich/svelte-adapter-azure-swa (js)

Issue:
Multiple Set-Cookie not working in hook.js/ts in the Build. Only setting the first cookie.
**Sveltekit version: 1.0.0.next.405
Azure-SWA-adapter-version: ^0.10.0**

In hooks.js if we are sending multiple cookies in response header like this:
```
response.headers.append('Set-Cookie', cookie1);
response.headers.append('Set-Cookie', cookie2);
response.headers.append('Set-Cookie', cookie3);
```
The response header during dev environment looks like this:
```
Set-cookie : cookie1
Set-cookie : cookie2
Set-cookie : cookie3
```
and it sets all cookies. 

But when testing the build either with SWA CLI or deployed app. The response header looks like this:

`Set-Cookie: Cookie1; Cookie2; Cookie3`
This only takes Cookie1 into account and leave the rest.  

I have deployed my app on Vercel with Serverless Function  using Vercel adapter and on Azure with Nodejs adapter. On both , the `Set-Cookie` behave as expected and set all the cookies and response header also looks like this:
```
Set-cookie : cookie1
Set-cookie : cookie2
Set-cookie : cookie3
```

Pull request description:
Fixes #63 Multiple Set-Cookie not working
This fixes #63, according to local tests with the SWA cli. SvelteKit returns cookies combined together in a single header, so they have to be split apart and parsed into a structure SWA/Azure understands. I haven't tested this in Azure, but if it doesn't work there it's because of the problems mentioned here: https://github.com/Azure/static-web-apps/issues/278

Patch:
diff --git a/files/entry.js b/files/entry.js
index f143dd5..43422a5 100644
--- a/files/entry.js
+++ b/files/entry.js
@@ -1,6 +1,7 @@
 import { installPolyfills } from '@sveltejs/kit/node/polyfills';
 import { Server } from 'SERVER';
 import { manifest } from 'MANIFEST';
+import { splitCookiesFromHeaders } from './headers';
 
 // replaced at build time
 const debug = DEBUG;
@@ -67,16 +68,13 @@ async function toResponse(rendered) {
 	const { status } = rendered;
 	const resBody = new Uint8Array(await rendered.arrayBuffer());
 
-	/** @type {Record<string, string>} */
-	const resHeaders = {};
-	rendered.headers.forEach((value, key) => {
-		resHeaders[key] = value;
-	});
+	const { headers, cookies } = splitCookiesFromHeaders(rendered.headers);
 
 	return {
 		status,
 		body: resBody,
-		headers: resHeaders,
+		headers,
+		cookies,
 		isRaw: true
 	};
 }
diff --git a/files/headers.js b/files/headers.js
new file mode 100644
index 0000000..e94a139
--- /dev/null
+++ b/files/headers.js
@@ -0,0 +1,28 @@
+import * as set_cookie_parser from 'set-cookie-parser';
+
+/**
+ * Splits 'set-cookie' headers into individual cookies
+ * @param {Headers} headers
+ * @returns {{
+ *   headers: Record<string, string>,
+ *   cookies: set_cookie_parser.Cookie[]
+ * }}
+ */
+export function splitCookiesFromHeaders(headers) {
+	/** @type {Record<string, string>} */
+	const resHeaders = {};
+
+	/** @type {set_cookie_parser.Cookie[]} */
+	const resCookies = [];
+
+	headers.forEach((value, key) => {
+		if (key === 'set-cookie') {
+			const cookieStrings = set_cookie_parser.splitCookiesString(value);
+			resCookies.push(...set_cookie_parser.parse(cookieStrings));
+		} else {
+			resHeaders[key] = value;
+		}
+	});
+
+	return { headers: resHeaders, cookies: resCookies };
+}
diff --git a/package-lock.json b/package-lock.json
index 093d35e..1ce0ecb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,12 +9,14 @@
 			"version": "0.10.0",
 			"license": "MIT",
 			"dependencies": {
-				"esbuild": "0.15.7"
+				"esbuild": "^0.15.7",
+				"set-cookie-parser": "^2.5.1"
 			},
 			"devDependencies": {
 				"@azure/functions": "^1.2.3",
 				"@sveltejs/kit": "^1.0.0-next.395",
 				"@types/node": "^17.0.5",
+				"@types/set-cookie-parser": "^2.4.2",
 				"@vitest/coverage-c8": "^0.23.4",
 				"prettier": "^2.4.1",
 				"vitest": "^0.23.4"
@@ -188,6 +190,15 @@
 			"integrity": "sha512-w3mrvNXLeDYV1GKTZorGJQivK6XLCoGwpnyJFbJVK/aTBQUxOCaa/GlFAAN3OTDFcb7h5tiFG+YXCO2By+riZw==",
 			"dev": true
 		},
+		"node_modules/@types/set-cookie-parser": {
+			"version": "2.4.2",
+			"resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.2.tgz",
+			"integrity": "sha512-fBZgytwhYAUkj/jC/FAV4RQ5EerRup1YQsXQCh8rZfiHkc4UahC192oH0smGwsXol3cL3A5oETuAHeQHmhXM4w==",
+			"dev": true,
+			"dependencies": {
+				"@types/node": "*"
+			}
+		},
 		"node_modules/@vitest/coverage-c8": {
 			"version": "0.23.4",
 			"resolved": "https://registry.npmjs.org/@vitest/coverage-c8/-/coverage-c8-0.23.4.tgz",
@@ -1475,8 +1486,7 @@
 		"node_modules/set-cookie-parser": {
 			"version": "2.5.1",
 			"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.5.1.tgz",
-			"integrity": "sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ==",
-			"dev": true
+			"integrity": "sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ=="
 		},
 		"node_modules/shebang-command": {
 			"version": "2.0.0",
@@ -2327,6 +2337,15 @@
 			"integrity": "sha512-w3mrvNXLeDYV1GKTZorGJQivK6XLCoGwpnyJFbJVK/aTBQUxOCaa/GlFAAN3OTDFcb7h5tiFG+YXCO2By+riZw==",
 			"dev": true
 		},
+		"@types/set-cookie-parser": {
+			"version": "2.4.2",
+			"resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.2.tgz",
+			"integrity": "sha512-fBZgytwhYAUkj/jC/FAV4RQ5EerRup1YQsXQCh8rZfiHkc4UahC192oH0smGwsXol3cL3A5oETuAHeQHmhXM4w==",
+			"dev": true,
+			"requires": {
+				"@types/node": "*"
+			}
+		},
 		"@vitest/coverage-c8": {
 			"version": "0.23.4",
 			"resolved": "https://registry.npmjs.org/@vitest/coverage-c8/-/coverage-c8-0.23.4.tgz",
@@ -3134,8 +3153,7 @@
 		"set-cookie-parser": {
 			"version": "2.5.1",
 			"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.5.1.tgz",
-			"integrity": "sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ==",
-			"dev": true
+			"integrity": "sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ=="
 		},
 		"shebang-command": {
 			"version": "2.0.0",
diff --git a/package.json b/package.json
index 1350f79..248b061 100644
--- a/package.json
+++ b/package.json
@@ -36,12 +36,14 @@
 		"@azure/functions": "^1.2.3",
 		"@sveltejs/kit": "^1.0.0-next.395",
 		"@types/node": "^17.0.5",
+		"@types/set-cookie-parser": "^2.4.2",
 		"@vitest/coverage-c8": "^0.23.4",
 		"prettier": "^2.4.1",
 		"vitest": "^0.23.4"
 	},
 	"dependencies": {
-		"esbuild": "^0.15.7"
+		"esbuild": "^0.15.7",
+		"set-cookie-parser": "^2.5.1"
 	},
 	"files": [
 		"files",


Test patch:
diff --git a/test/headers.test.js b/test/headers.test.js
new file mode 100644
index 0000000..7140601
--- /dev/null
+++ b/test/headers.test.js
@@ -0,0 +1,59 @@
+import { installPolyfills } from '@sveltejs/kit/node/polyfills';
+import { expect, describe, test } from 'vitest';
+import { splitCookiesFromHeaders } from '../files/headers';
+
+installPolyfills();
+
+describe('header processing', () => {
+	test('no cookies', () => {
+		const headers = new Headers();
+		headers.append('Location', '/');
+		headers.append('Content-Type', 'application/json');
+
+		const cookies = splitCookiesFromHeaders(headers);
+
+		expect(cookies).toEqual({
+			cookies: [],
+			headers: {
+				'content-type': 'application/json',
+				location: '/'
+			}
+		});
+	});
+
+	test('multiple cookies', () => {
+		const headers = new Headers();
+
+		// https://httpwg.org/specs/rfc7231.html#http.date
+		const exp1 = 'Sun, 06 Nov 1994 08:49:37 GMT';
+		const exp2 = 'Sunday, 06-Nov-94 08:49:37 GMT';
+		const exp3 = 'Sun Nov  6 08:49:37 1994 GMT';
+
+		headers.append('Set-Cookie', `key1=val1; Expires=${exp1}`);
+		headers.append('Set-Cookie', `key2=val2; Expires=${exp2}`);
+		headers.append('Set-Cookie', `key3=val3; Expires=${exp3}`);
+
+		const cookies = splitCookiesFromHeaders(headers);
+
+		expect(cookies).toStrictEqual({
+			headers: {},
+			cookies: [
+				{
+					expires: new Date('1994-11-06T08:49:37.000Z'),
+					name: 'key1',
+					value: 'val1'
+				},
+				{
+					expires: new Date('1994-11-06T08:49:37.000Z'),
+					name: 'key2',
+					value: 'val2'
+				},
+				{
+					expires: new Date('1994-11-06T08:49:37.000Z'),
+					name: 'key3',
+					value: 'val3'
+				}
+			]
+		});
+	});
+});