@remix-run/cookie 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Michael Jackson
3
+ Copyright (c) 2025 Shopify Inc.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -21,7 +21,16 @@ npm install @remix-run/cookie
21
21
  ```tsx
22
22
  import { createCookie } from '@remix-run/cookie'
23
23
 
24
- let sessionCookie = createCookie('session', { secrets: ['s3cret1'] })
24
+ let sessionCookie = createCookie('session', {
25
+ httpOnly: true,
26
+ secrets: ['s3cret1'],
27
+ secure: true,
28
+ })
29
+
30
+ cookie.name // "session"
31
+ cookie.httpOnly // true
32
+ cookie.secure // true
33
+ cookie.signed // true
25
34
 
26
35
  // Get the value of the "session" cookie from the request's `Cookie` header
27
36
  let value = await sessionCookie.parse(request.headers.get('Cookie'))
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { type Cookie, type CookieOptions, createCookie } from './lib/cookie.ts';
1
+ export { type CookieOptions, Cookie, createCookie } from './lib/cookie.ts';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,aAAa,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA"}
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export { createCookie } from "./lib/cookie.js";
1
+ export { Cookie, createCookie } from "./lib/cookie.js";
@@ -1,4 +1,33 @@
1
1
  import { type CookieProperties } from '@remix-run/headers';
2
+ export interface CookieOptions extends CookieProperties {
3
+ /**
4
+ * A function that decodes the cookie value.
5
+ *
6
+ * Defaults to `decodeURIComponent`, which decodes any URL-encoded sequences into their original
7
+ * characters.
8
+ *
9
+ * See [RFC 6265](https://tools.ietf.org/html/rfc6265#section-4.1.1) for more details.
10
+ */
11
+ decode?: (value: string) => string;
12
+ /**
13
+ * A function that encodes the cookie value.
14
+ *
15
+ * Defaults to `encodeURIComponent`, which percent-encodes all characters that are not allowed
16
+ * in a cookie value.
17
+ *
18
+ * See [RFC 6265](https://tools.ietf.org/html/rfc6265#section-4.1.1) for more details.
19
+ */
20
+ encode?: (value: string) => string;
21
+ /**
22
+ * An array of secrets that may be used to sign/unsign the value of a cookie.
23
+ *
24
+ * The array makes it easy to rotate secrets. New secrets should be added to
25
+ * the beginning of the array. `cookie.serialize()` will always use the first
26
+ * value in the array, but `cookie.parse()` may use any of them so that
27
+ * cookies that were signed with older secrets still work.
28
+ */
29
+ secrets?: string[];
30
+ }
2
31
  type SameSiteValue = 'Strict' | 'Lax' | 'None';
3
32
  /**
4
33
  * Represents a HTTP cookie.
@@ -8,37 +37,39 @@ type SameSiteValue = 'Strict' | 'Lax' | 'None';
8
37
  * Also supports cryptographic signing of the cookie value to ensure it's not tampered with, and
9
38
  * secret rotation to easily rotate secrets without breaking existing cookies.
10
39
  */
11
- export interface Cookie {
40
+ export declare class Cookie {
41
+ #private;
42
+ constructor(name: string, options?: CookieOptions);
12
43
  /**
13
44
  * The domain of the cookie.
14
45
  *
15
46
  * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#domaindomain-value)
16
47
  */
17
- readonly domain: string | undefined;
48
+ get domain(): string | undefined;
18
49
  /**
19
50
  * The expiration date of the cookie.
20
51
  *
21
52
  * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#expiresdate)
22
53
  */
23
- readonly expires: Date | undefined;
54
+ get expires(): Date | undefined;
24
55
  /**
25
56
  * True if the cookie is HTTP-only.
26
57
  *
27
58
  * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#httponly)
28
59
  */
29
- readonly httpOnly: boolean;
60
+ get httpOnly(): boolean;
30
61
  /**
31
62
  * The maximum age of the cookie in seconds.
32
63
  *
33
64
  * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie#max-agenumber)
34
65
  */
35
- readonly maxAge: number | undefined;
66
+ get maxAge(): number | undefined;
36
67
  /**
37
68
  * The name of the cookie.
38
69
  *
39
70
  * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie#cookie-namecookie-value)
40
71
  */
41
- readonly name: string;
72
+ get name(): string;
42
73
  /**
43
74
  * Extracts the value of this cookie from a `Cookie` header value.
44
75
  * @param headerValue The `Cookie` header to parse
@@ -50,25 +81,25 @@ export interface Cookie {
50
81
  *
51
82
  * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#partitioned)
52
83
  */
53
- readonly partitioned: boolean;
84
+ get partitioned(): boolean;
54
85
  /**
55
86
  * The path of the cookie. Defaults to `/`.
56
87
  *
57
88
  * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#pathpath-value)
58
89
  */
59
- readonly path: string;
90
+ get path(): string;
60
91
  /**
61
92
  * The `SameSite` attribute of the cookie. Defaults to `Lax`.
62
93
  *
63
94
  * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value)
64
95
  */
65
- readonly sameSite: SameSiteValue;
96
+ get sameSite(): SameSiteValue;
66
97
  /**
67
98
  * True if the cookie is secure (only sent over HTTPS).
68
99
  *
69
100
  * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#secure)
70
101
  */
71
- readonly secure: boolean;
102
+ get secure(): boolean;
72
103
  /**
73
104
  * Returns the value to use in a `Set-Cookie` header for this cookie.
74
105
  * @param value The value to serialize
@@ -79,36 +110,7 @@ export interface Cookie {
79
110
  /**
80
111
  * True if this cookie uses one or more secrets for verification.
81
112
  */
82
- readonly signed: boolean;
83
- }
84
- export interface CookieOptions extends CookieProperties {
85
- /**
86
- * A function that decodes the cookie value.
87
- *
88
- * Defaults to `decodeURIComponent`, which decodes any URL-encoded sequences into their original
89
- * characters.
90
- *
91
- * See [RFC 6265](https://tools.ietf.org/html/rfc6265#section-4.1.1) for more details.
92
- */
93
- decode?: (value: string) => string;
94
- /**
95
- * A function that encodes the cookie value.
96
- *
97
- * Defaults to `encodeURIComponent`, which percent-encodes all characters that are not allowed
98
- * in a cookie value.
99
- *
100
- * See [RFC 6265](https://tools.ietf.org/html/rfc6265#section-4.1.1) for more details.
101
- */
102
- encode?: (value: string) => string;
103
- /**
104
- * An array of secrets that may be used to sign/unsign the value of a cookie.
105
- *
106
- * The array makes it easy to rotate secrets. New secrets should be added to
107
- * the beginning of the array. `cookie.serialize()` will always use the first
108
- * value in the array, but `cookie.parse()` may use any of them so that
109
- * cookies that were signed with older secrets still work.
110
- */
111
- secrets?: string[];
113
+ get signed(): boolean;
112
114
  }
113
115
  /**
114
116
  * Creates a new cookie object.
@@ -1 +1 @@
1
- {"version":3,"file":"cookie.d.ts","sourceRoot":"","sources":["../../src/lib/cookie.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAA;AAI3B,KAAK,aAAa,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAA;AAE9C;;;;;;;GAOG;AACH,MAAM,WAAW,MAAM;IACrB;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,IAAI,GAAG,SAAS,CAAA;IAClC;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,KAAK,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACzD;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAA;IAChC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;IACxB;;;;;OAKG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACnE;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CACzB;AAED,MAAM,WAAW,aAAc,SAAQ,gBAAgB;IACrD;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAClC;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAClC;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,CA4E1E"}
1
+ {"version":3,"file":"cookie.d.ts","sourceRoot":"","sources":["../../src/lib/cookie.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAA;AAI3B,MAAM,WAAW,aAAc,SAAQ,gBAAgB;IACrD;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAClC;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAClC;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CACnB;AAED,KAAK,aAAa,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAA;AAG9C;;;;;;;GAOG;AACH,qBAAa,MAAM;;gBAcL,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa;IAmCjD;;;;OAIG;IACH,IAAI,MAAM,IAAI,MAAM,GAAG,SAAS,CAE/B;IAED;;;;OAIG;IACH,IAAI,OAAO,IAAI,IAAI,GAAG,SAAS,CAE9B;IAED;;;;OAIG;IACH,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED;;;;OAIG;IACH,IAAI,MAAM,IAAI,MAAM,GAAG,SAAS,CAE/B;IAED;;;;OAIG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;OAIG;IACG,KAAK,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAa/D;;;;OAIG;IACH,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED;;;;OAIG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;OAIG;IACH,IAAI,QAAQ,IAAI,aAAa,CAE5B;IAED;;;;OAIG;IACH,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED;;;;;OAKG;IACG,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC;IAkBzE;;OAEG;IACH,IAAI,MAAM,IAAI,OAAO,CAEpB;CACF;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,CAE1E"}
@@ -1,5 +1,164 @@
1
1
  import { Cookie as CookieHeader, SetCookie as SetCookieHeader, } from '@remix-run/headers';
2
2
  import { sign, unsign } from "./cookie-signing.js";
3
+ /**
4
+ * Represents a HTTP cookie.
5
+ *
6
+ * Supports parsing and serializing the cookie to/from `Cookie` and `Set-Cookie` headers.
7
+ *
8
+ * Also supports cryptographic signing of the cookie value to ensure it's not tampered with, and
9
+ * secret rotation to easily rotate secrets without breaking existing cookies.
10
+ */
11
+ export class Cookie {
12
+ #name;
13
+ #decode;
14
+ #encode;
15
+ #secrets;
16
+ #domain;
17
+ #expires;
18
+ #httpOnly;
19
+ #maxAge;
20
+ #partitioned;
21
+ #path;
22
+ #sameSite;
23
+ #secure;
24
+ constructor(name, options) {
25
+ let { decode = decodeURIComponent, encode = encodeURIComponent, secrets = [], domain, expires, httpOnly, maxAge, path = '/', partitioned, secure, sameSite = 'Lax', } = options ?? {};
26
+ if (partitioned === true) {
27
+ // Partitioned cookies must be set with Secure
28
+ // See https://developer.mozilla.org/en-US/docs/Web/Privacy/Guides/Privacy_sandbox/Partitioned_cookies
29
+ secure = true;
30
+ }
31
+ this.#name = name;
32
+ this.#decode = decode;
33
+ this.#encode = encode;
34
+ this.#secrets = secrets;
35
+ this.#domain = domain;
36
+ this.#expires = expires;
37
+ this.#httpOnly = httpOnly;
38
+ this.#maxAge = maxAge;
39
+ this.#partitioned = partitioned;
40
+ this.#path = path;
41
+ this.#sameSite = sameSite;
42
+ this.#secure = secure;
43
+ }
44
+ /**
45
+ * The domain of the cookie.
46
+ *
47
+ * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#domaindomain-value)
48
+ */
49
+ get domain() {
50
+ return this.#domain;
51
+ }
52
+ /**
53
+ * The expiration date of the cookie.
54
+ *
55
+ * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#expiresdate)
56
+ */
57
+ get expires() {
58
+ return this.#expires;
59
+ }
60
+ /**
61
+ * True if the cookie is HTTP-only.
62
+ *
63
+ * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#httponly)
64
+ */
65
+ get httpOnly() {
66
+ return this.#httpOnly ?? false;
67
+ }
68
+ /**
69
+ * The maximum age of the cookie in seconds.
70
+ *
71
+ * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie#max-agenumber)
72
+ */
73
+ get maxAge() {
74
+ return this.#maxAge;
75
+ }
76
+ /**
77
+ * The name of the cookie.
78
+ *
79
+ * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie#cookie-namecookie-value)
80
+ */
81
+ get name() {
82
+ return this.#name;
83
+ }
84
+ /**
85
+ * Extracts the value of this cookie from a `Cookie` header value.
86
+ * @param headerValue The `Cookie` header to parse
87
+ * @returns The value of this cookie, or `null` if it's not present
88
+ */
89
+ async parse(headerValue) {
90
+ if (!headerValue)
91
+ return null;
92
+ let header = new CookieHeader(headerValue);
93
+ if (!header.has(this.#name))
94
+ return null;
95
+ let value = header.get(this.#name);
96
+ if (value === '')
97
+ return '';
98
+ let decoded = await decodeCookieValue(value, this.#secrets, this.#decode);
99
+ return decoded;
100
+ }
101
+ /**
102
+ * True if the cookie is partitioned.
103
+ *
104
+ * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#partitioned)
105
+ */
106
+ get partitioned() {
107
+ return this.#partitioned ?? false;
108
+ }
109
+ /**
110
+ * The path of the cookie. Defaults to `/`.
111
+ *
112
+ * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#pathpath-value)
113
+ */
114
+ get path() {
115
+ return this.#path;
116
+ }
117
+ /**
118
+ * The `SameSite` attribute of the cookie. Defaults to `Lax`.
119
+ *
120
+ * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value)
121
+ */
122
+ get sameSite() {
123
+ return this.#sameSite;
124
+ }
125
+ /**
126
+ * True if the cookie is secure (only sent over HTTPS).
127
+ *
128
+ * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#secure)
129
+ */
130
+ get secure() {
131
+ return this.#secure ?? false;
132
+ }
133
+ /**
134
+ * Returns the value to use in a `Set-Cookie` header for this cookie.
135
+ * @param value The value to serialize
136
+ * @param props (optional) Additional properties to use when serializing the cookie
137
+ * @returns The `Set-Cookie` header for this cookie
138
+ */
139
+ async serialize(value, props) {
140
+ let header = new SetCookieHeader({
141
+ name: this.#name,
142
+ value: value === '' ? '' : await encodeCookieValue(value, this.#secrets, this.#encode),
143
+ domain: this.#domain,
144
+ expires: this.#expires,
145
+ httpOnly: this.#httpOnly,
146
+ maxAge: this.#maxAge,
147
+ partitioned: this.#partitioned,
148
+ path: this.#path,
149
+ sameSite: this.#sameSite,
150
+ secure: this.#secure,
151
+ ...props,
152
+ });
153
+ return header.toString();
154
+ }
155
+ /**
156
+ * True if this cookie uses one or more secrets for verification.
157
+ */
158
+ get signed() {
159
+ return this.#secrets.length > 0;
160
+ }
161
+ }
3
162
  /**
4
163
  * Creates a new cookie object.
5
164
  * @param name The name of the cookie
@@ -7,67 +166,7 @@ import { sign, unsign } from "./cookie-signing.js";
7
166
  * @returns A cookie object
8
167
  */
9
168
  export function createCookie(name, options) {
10
- let { decode = decodeURIComponent, encode = encodeURIComponent, secrets = [], domain, expires, httpOnly, maxAge, path = '/', partitioned, secure, sameSite = 'Lax', } = options ?? {};
11
- return {
12
- get domain() {
13
- return domain;
14
- },
15
- get expires() {
16
- return expires;
17
- },
18
- get httpOnly() {
19
- return httpOnly ?? false;
20
- },
21
- get maxAge() {
22
- return maxAge;
23
- },
24
- get name() {
25
- return name;
26
- },
27
- async parse(headerValue) {
28
- if (!headerValue)
29
- return null;
30
- let header = new CookieHeader(headerValue);
31
- if (!header.has(name))
32
- return null;
33
- let value = header.get(name);
34
- if (value === '')
35
- return '';
36
- let decoded = await decodeCookieValue(value, secrets, decode);
37
- return decoded;
38
- },
39
- get partitioned() {
40
- return partitioned ?? false;
41
- },
42
- get path() {
43
- return path;
44
- },
45
- get sameSite() {
46
- return sameSite;
47
- },
48
- get secure() {
49
- return secure ?? false;
50
- },
51
- async serialize(value, props) {
52
- let header = new SetCookieHeader({
53
- name: name,
54
- value: value === '' ? '' : await encodeCookieValue(value, secrets, encode),
55
- domain,
56
- expires,
57
- httpOnly,
58
- maxAge,
59
- partitioned,
60
- path,
61
- sameSite,
62
- secure,
63
- ...props,
64
- });
65
- return header.toString();
66
- },
67
- get signed() {
68
- return secrets.length > 0;
69
- },
70
- };
169
+ return new Cookie(name, options);
71
170
  }
72
171
  async function decodeCookieValue(value, secrets, decode) {
73
172
  if (secrets.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remix-run/cookie",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "A toolkit for working with cookies in JavaScript",
5
5
  "author": "Michael Jackson <mjijackson@gmail.com>",
6
6
  "license": "MIT",
@@ -29,7 +29,7 @@
29
29
  "@types/node": "^24.6.0"
30
30
  },
31
31
  "peerDependencies": {
32
- "@remix-run/headers": "^0.16.0"
32
+ "@remix-run/headers": "^0.17.1"
33
33
  },
34
34
  "keywords": [
35
35
  "http",
package/src/index.ts CHANGED
@@ -1 +1 @@
1
- export { type Cookie, type CookieOptions, createCookie } from './lib/cookie.ts'
1
+ export { type CookieOptions, Cookie, createCookie } from './lib/cookie.ts'
package/src/lib/cookie.ts CHANGED
@@ -6,7 +6,38 @@ import {
6
6
 
7
7
  import { sign, unsign } from './cookie-signing.ts'
8
8
 
9
+ export interface CookieOptions extends CookieProperties {
10
+ /**
11
+ * A function that decodes the cookie value.
12
+ *
13
+ * Defaults to `decodeURIComponent`, which decodes any URL-encoded sequences into their original
14
+ * characters.
15
+ *
16
+ * See [RFC 6265](https://tools.ietf.org/html/rfc6265#section-4.1.1) for more details.
17
+ */
18
+ decode?: (value: string) => string
19
+ /**
20
+ * A function that encodes the cookie value.
21
+ *
22
+ * Defaults to `encodeURIComponent`, which percent-encodes all characters that are not allowed
23
+ * in a cookie value.
24
+ *
25
+ * See [RFC 6265](https://tools.ietf.org/html/rfc6265#section-4.1.1) for more details.
26
+ */
27
+ encode?: (value: string) => string
28
+ /**
29
+ * An array of secrets that may be used to sign/unsign the value of a cookie.
30
+ *
31
+ * The array makes it easy to rotate secrets. New secrets should be added to
32
+ * the beginning of the array. `cookie.serialize()` will always use the first
33
+ * value in the array, but `cookie.parse()` may use any of them so that
34
+ * cookies that were signed with older secrets still work.
35
+ */
36
+ secrets?: string[]
37
+ }
38
+
9
39
  type SameSiteValue = 'Strict' | 'Lax' | 'None'
40
+ type Coder = (value: string) => string
10
41
 
11
42
  /**
12
43
  * Represents a HTTP cookie.
@@ -16,108 +47,184 @@ type SameSiteValue = 'Strict' | 'Lax' | 'None'
16
47
  * Also supports cryptographic signing of the cookie value to ensure it's not tampered with, and
17
48
  * secret rotation to easily rotate secrets without breaking existing cookies.
18
49
  */
19
- export interface Cookie {
50
+ export class Cookie {
51
+ #name: string
52
+ #decode: Coder
53
+ #encode: Coder
54
+ #secrets: string[]
55
+ #domain: string | undefined
56
+ #expires: Date | undefined
57
+ #httpOnly: boolean | undefined
58
+ #maxAge: number | undefined
59
+ #partitioned: boolean | undefined
60
+ #path: string
61
+ #sameSite: SameSiteValue
62
+ #secure: boolean | undefined
63
+
64
+ constructor(name: string, options?: CookieOptions) {
65
+ let {
66
+ decode = decodeURIComponent,
67
+ encode = encodeURIComponent,
68
+ secrets = [],
69
+ domain,
70
+ expires,
71
+ httpOnly,
72
+ maxAge,
73
+ path = '/',
74
+ partitioned,
75
+ secure,
76
+ sameSite = 'Lax',
77
+ } = options ?? {}
78
+
79
+ if (partitioned === true) {
80
+ // Partitioned cookies must be set with Secure
81
+ // See https://developer.mozilla.org/en-US/docs/Web/Privacy/Guides/Privacy_sandbox/Partitioned_cookies
82
+ secure = true
83
+ }
84
+
85
+ this.#name = name
86
+ this.#decode = decode
87
+ this.#encode = encode
88
+ this.#secrets = secrets
89
+ this.#domain = domain
90
+ this.#expires = expires
91
+ this.#httpOnly = httpOnly
92
+ this.#maxAge = maxAge
93
+ this.#partitioned = partitioned
94
+ this.#path = path
95
+ this.#sameSite = sameSite
96
+ this.#secure = secure
97
+ }
98
+
20
99
  /**
21
100
  * The domain of the cookie.
22
101
  *
23
102
  * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#domaindomain-value)
24
103
  */
25
- readonly domain: string | undefined
104
+ get domain(): string | undefined {
105
+ return this.#domain
106
+ }
107
+
26
108
  /**
27
109
  * The expiration date of the cookie.
28
110
  *
29
111
  * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#expiresdate)
30
112
  */
31
- readonly expires: Date | undefined
113
+ get expires(): Date | undefined {
114
+ return this.#expires
115
+ }
116
+
32
117
  /**
33
118
  * True if the cookie is HTTP-only.
34
119
  *
35
120
  * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#httponly)
36
121
  */
37
- readonly httpOnly: boolean
122
+ get httpOnly(): boolean {
123
+ return this.#httpOnly ?? false
124
+ }
125
+
38
126
  /**
39
127
  * The maximum age of the cookie in seconds.
40
128
  *
41
129
  * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie#max-agenumber)
42
130
  */
43
- readonly maxAge: number | undefined
131
+ get maxAge(): number | undefined {
132
+ return this.#maxAge
133
+ }
134
+
44
135
  /**
45
136
  * The name of the cookie.
46
137
  *
47
138
  * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie#cookie-namecookie-value)
48
139
  */
49
- readonly name: string
140
+ get name(): string {
141
+ return this.#name
142
+ }
143
+
50
144
  /**
51
145
  * Extracts the value of this cookie from a `Cookie` header value.
52
146
  * @param headerValue The `Cookie` header to parse
53
147
  * @returns The value of this cookie, or `null` if it's not present
54
148
  */
55
- parse(headerValue: string | null): Promise<string | null>
149
+ async parse(headerValue: string | null): Promise<string | null> {
150
+ if (!headerValue) return null
151
+
152
+ let header = new CookieHeader(headerValue)
153
+ if (!header.has(this.#name)) return null
154
+
155
+ let value = header.get(this.#name)!
156
+ if (value === '') return ''
157
+
158
+ let decoded = await decodeCookieValue(value, this.#secrets, this.#decode)
159
+ return decoded
160
+ }
161
+
56
162
  /**
57
163
  * True if the cookie is partitioned.
58
164
  *
59
165
  * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#partitioned)
60
166
  */
61
- readonly partitioned: boolean
167
+ get partitioned(): boolean {
168
+ return this.#partitioned ?? false
169
+ }
170
+
62
171
  /**
63
172
  * The path of the cookie. Defaults to `/`.
64
173
  *
65
174
  * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#pathpath-value)
66
175
  */
67
- readonly path: string
176
+ get path(): string {
177
+ return this.#path
178
+ }
179
+
68
180
  /**
69
181
  * The `SameSite` attribute of the cookie. Defaults to `Lax`.
70
182
  *
71
183
  * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value)
72
184
  */
73
- readonly sameSite: SameSiteValue
185
+ get sameSite(): SameSiteValue {
186
+ return this.#sameSite
187
+ }
188
+
74
189
  /**
75
190
  * True if the cookie is secure (only sent over HTTPS).
76
191
  *
77
192
  * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#secure)
78
193
  */
79
- readonly secure: boolean
194
+ get secure(): boolean {
195
+ return this.#secure ?? false
196
+ }
197
+
80
198
  /**
81
199
  * Returns the value to use in a `Set-Cookie` header for this cookie.
82
200
  * @param value The value to serialize
83
201
  * @param props (optional) Additional properties to use when serializing the cookie
84
202
  * @returns The `Set-Cookie` header for this cookie
85
203
  */
86
- serialize(value: string, props?: CookieProperties): Promise<string>
87
- /**
88
- * True if this cookie uses one or more secrets for verification.
89
- */
90
- readonly signed: boolean
91
- }
204
+ async serialize(value: string, props?: CookieProperties): Promise<string> {
205
+ let header = new SetCookieHeader({
206
+ name: this.#name,
207
+ value: value === '' ? '' : await encodeCookieValue(value, this.#secrets, this.#encode),
208
+ domain: this.#domain,
209
+ expires: this.#expires,
210
+ httpOnly: this.#httpOnly,
211
+ maxAge: this.#maxAge,
212
+ partitioned: this.#partitioned,
213
+ path: this.#path,
214
+ sameSite: this.#sameSite,
215
+ secure: this.#secure,
216
+ ...props,
217
+ })
218
+
219
+ return header.toString()
220
+ }
92
221
 
93
- export interface CookieOptions extends CookieProperties {
94
- /**
95
- * A function that decodes the cookie value.
96
- *
97
- * Defaults to `decodeURIComponent`, which decodes any URL-encoded sequences into their original
98
- * characters.
99
- *
100
- * See [RFC 6265](https://tools.ietf.org/html/rfc6265#section-4.1.1) for more details.
101
- */
102
- decode?: (value: string) => string
103
- /**
104
- * A function that encodes the cookie value.
105
- *
106
- * Defaults to `encodeURIComponent`, which percent-encodes all characters that are not allowed
107
- * in a cookie value.
108
- *
109
- * See [RFC 6265](https://tools.ietf.org/html/rfc6265#section-4.1.1) for more details.
110
- */
111
- encode?: (value: string) => string
112
222
  /**
113
- * An array of secrets that may be used to sign/unsign the value of a cookie.
114
- *
115
- * The array makes it easy to rotate secrets. New secrets should be added to
116
- * the beginning of the array. `cookie.serialize()` will always use the first
117
- * value in the array, but `cookie.parse()` may use any of them so that
118
- * cookies that were signed with older secrets still work.
223
+ * True if this cookie uses one or more secrets for verification.
119
224
  */
120
- secrets?: string[]
225
+ get signed(): boolean {
226
+ return this.#secrets.length > 0
227
+ }
121
228
  }
122
229
 
123
230
  /**
@@ -127,85 +234,9 @@ export interface CookieOptions extends CookieProperties {
127
234
  * @returns A cookie object
128
235
  */
129
236
  export function createCookie(name: string, options?: CookieOptions): Cookie {
130
- let {
131
- decode = decodeURIComponent,
132
- encode = encodeURIComponent,
133
- secrets = [],
134
- domain,
135
- expires,
136
- httpOnly,
137
- maxAge,
138
- path = '/',
139
- partitioned,
140
- secure,
141
- sameSite = 'Lax',
142
- } = options ?? {}
143
-
144
- return {
145
- get domain() {
146
- return domain
147
- },
148
- get expires() {
149
- return expires
150
- },
151
- get httpOnly() {
152
- return httpOnly ?? false
153
- },
154
- get maxAge() {
155
- return maxAge
156
- },
157
- get name() {
158
- return name
159
- },
160
- async parse(headerValue: string | null): Promise<string | null> {
161
- if (!headerValue) return null
162
-
163
- let header = new CookieHeader(headerValue)
164
- if (!header.has(name)) return null
165
-
166
- let value = header.get(name)!
167
- if (value === '') return ''
168
-
169
- let decoded = await decodeCookieValue(value, secrets, decode)
170
- return decoded
171
- },
172
- get partitioned() {
173
- return partitioned ?? false
174
- },
175
- get path() {
176
- return path
177
- },
178
- get sameSite() {
179
- return sameSite
180
- },
181
- get secure() {
182
- return secure ?? false
183
- },
184
- async serialize(value: string, props?: CookieProperties): Promise<string> {
185
- let header = new SetCookieHeader({
186
- name: name,
187
- value: value === '' ? '' : await encodeCookieValue(value, secrets, encode),
188
- domain,
189
- expires,
190
- httpOnly,
191
- maxAge,
192
- partitioned,
193
- path,
194
- sameSite,
195
- secure,
196
- ...props,
197
- })
198
-
199
- return header.toString()
200
- },
201
- get signed() {
202
- return secrets.length > 0
203
- },
204
- }
237
+ return new Cookie(name, options)
205
238
  }
206
239
 
207
- type Coder = (value: string) => string
208
-
209
240
  async function decodeCookieValue(
210
241
  value: string,
211
242
  secrets: string[],