@remix-run/cookie 0.4.1 → 0.5.1

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
@@ -57,7 +57,7 @@ let sessionCookie = createCookie('session', {
57
57
  secrets: ['secret1'],
58
58
  })
59
59
 
60
- console.log(sessionCookie.isSigned) // true
60
+ console.log(sessionCookie.signed) // true
61
61
 
62
62
  let response = new Response('Hello, world!', {
63
63
  headers: {
@@ -86,7 +86,7 @@ let response = new Response('Hello, world!', {
86
86
 
87
87
  ### Custom Encoding
88
88
 
89
- By default, the library will use `encodeURIComponent` and `decodeURIComponent` to encode and decode the cookie value. This is suitable for most use cases, but you can provide your own functions to customize the encoding and decoding of the cookie value.
89
+ By default, [`encodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) and [`decodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent) are used to encode and decode the cookie value. This is suitable for most use cases, but you can provide your own functions to customize the encoding and decoding of the cookie value.
90
90
 
91
91
  ```tsx
92
92
  let sessionCookie = createCookie('session', {
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";
@@ -27,7 +27,7 @@ export async function unsign(cookie, secret) {
27
27
  return false;
28
28
  }
29
29
  }
30
- async function createKey(secret, usages) {
30
+ function createKey(secret, usages) {
31
31
  return crypto.subtle.importKey('raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, usages);
32
32
  }
33
33
  function byteStringToArray(byteString) {
@@ -1,4 +1,36 @@
1
1
  import { type CookieProperties } from '@remix-run/headers';
2
+ /**
3
+ * Options for creating a cookie.
4
+ */
5
+ export interface CookieOptions extends CookieProperties {
6
+ /**
7
+ * A function that decodes the cookie value. Decodes any URL-encoded sequences into their
8
+ * original characters.
9
+ *
10
+ * See [RFC 6265](https://tools.ietf.org/html/rfc6265#section-4.1.1) for more details.
11
+ *
12
+ * @default decodeURIComponent
13
+ */
14
+ decode?: (value: string) => string;
15
+ /**
16
+ * A function that encodes the cookie value. Percent-encodes all characters that are not allowed
17
+ * in a cookie value.
18
+ *
19
+ * See [RFC 6265](https://tools.ietf.org/html/rfc6265#section-4.1.1) for more details.
20
+ *
21
+ * @default encodeURIComponent
22
+ */
23
+ encode?: (value: string) => string;
24
+ /**
25
+ * An array of secrets that may be used to sign/unsign the value of a cookie.
26
+ *
27
+ * The array makes it easy to rotate secrets. New secrets should be added to
28
+ * the beginning of the array. `cookie.serialize()` will always use the first
29
+ * value in the array, but `cookie.parse()` may use any of them so that
30
+ * cookies that were signed with older secrets still work.
31
+ */
32
+ secrets?: string[];
33
+ }
2
34
  type SameSiteValue = 'Strict' | 'Lax' | 'None';
3
35
  /**
4
36
  * Represents a HTTP cookie.
@@ -8,39 +40,48 @@ type SameSiteValue = 'Strict' | 'Lax' | 'None';
8
40
  * Also supports cryptographic signing of the cookie value to ensure it's not tampered with, and
9
41
  * secret rotation to easily rotate secrets without breaking existing cookies.
10
42
  */
11
- export interface Cookie {
43
+ export declare class Cookie implements CookieProperties {
44
+ #private;
45
+ /**
46
+ * @param name The name of the cookie
47
+ * @param options Options for the cookie
48
+ */
49
+ constructor(name: string, options?: CookieOptions);
12
50
  /**
13
51
  * The domain of the cookie.
14
52
  *
15
53
  * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#domaindomain-value)
16
54
  */
17
- readonly domain: string | undefined;
55
+ get domain(): string | undefined;
18
56
  /**
19
57
  * The expiration date of the cookie.
20
58
  *
21
59
  * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#expiresdate)
22
60
  */
23
- readonly expires: Date | undefined;
61
+ get expires(): Date | undefined;
24
62
  /**
25
63
  * True if the cookie is HTTP-only.
26
64
  *
27
65
  * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#httponly)
66
+ *
67
+ * @default false
28
68
  */
29
- readonly httpOnly: boolean;
69
+ get httpOnly(): boolean;
30
70
  /**
31
71
  * The maximum age of the cookie in seconds.
32
72
  *
33
73
  * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie#max-agenumber)
34
74
  */
35
- readonly maxAge: number | undefined;
75
+ get maxAge(): number | undefined;
36
76
  /**
37
77
  * The name of the cookie.
38
78
  *
39
79
  * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie#cookie-namecookie-value)
40
80
  */
41
- readonly name: string;
81
+ get name(): string;
42
82
  /**
43
83
  * Extracts the value of this cookie from a `Cookie` header value.
84
+ *
44
85
  * @param headerValue The `Cookie` header to parse
45
86
  * @returns The value of this cookie, or `null` if it's not present
46
87
  */
@@ -49,72 +90,53 @@ export interface Cookie {
49
90
  * True if the cookie is partitioned.
50
91
  *
51
92
  * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#partitioned)
93
+ *
94
+ * @default false
52
95
  */
53
- readonly partitioned: boolean;
96
+ get partitioned(): boolean;
54
97
  /**
55
- * The path of the cookie. Defaults to `/`.
98
+ * The path of the cookie.
56
99
  *
57
100
  * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#pathpath-value)
101
+ *
102
+ * @default '/'
58
103
  */
59
- readonly path: string;
104
+ get path(): string;
60
105
  /**
61
- * The `SameSite` attribute of the cookie. Defaults to `Lax`.
106
+ * The `SameSite` attribute of the cookie.
62
107
  *
63
108
  * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value)
109
+ *
110
+ * @default 'Lax'
64
111
  */
65
- readonly sameSite: SameSiteValue;
112
+ get sameSite(): SameSiteValue;
66
113
  /**
67
114
  * True if the cookie is secure (only sent over HTTPS).
68
115
  *
69
116
  * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#secure)
117
+ *
118
+ * @default false
70
119
  */
71
- readonly secure: boolean;
120
+ get secure(): boolean;
72
121
  /**
73
122
  * Returns the value to use in a `Set-Cookie` header for this cookie.
123
+ *
74
124
  * @param value The value to serialize
75
- * @param props (optional) Additional properties to use when serializing the cookie
76
- * @returns The `Set-Cookie` header for this cookie
125
+ * @param props Additional properties to use when serializing the cookie
126
+ * @returns The `Set-Cookie` header value for this cookie
77
127
  */
78
128
  serialize(value: string, props?: CookieProperties): Promise<string>;
79
129
  /**
80
130
  * True if this cookie uses one or more secrets for verification.
81
131
  */
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[];
132
+ get signed(): boolean;
112
133
  }
113
134
  /**
114
135
  * Creates a new cookie object.
136
+ *
115
137
  * @param name The name of the cookie
116
- * @param options (optional) Additional options for the cookie
117
- * @returns A cookie object
138
+ * @param options Options for the cookie
139
+ * @returns A new `Cookie` object
118
140
  */
119
141
  export declare function createCookie(name: string, options?: CookieOptions): Cookie;
120
142
  export {};
@@ -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,CAkF1E"}
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;;GAEG;AACH,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,MAAO,YAAW,gBAAgB;;IAc7C;;;OAGG;IACH,YAAY,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,EAiChD;IAED;;;;OAIG;IACH,IAAI,MAAM,IAAI,MAAM,GAAG,SAAS,CAE/B;IAED;;;;OAIG;IACH,IAAI,OAAO,IAAI,IAAI,GAAG,SAAS,CAE9B;IAED;;;;;;OAMG;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;;;;;OAKG;IACG,KAAK,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAW9D;IAED;;;;;;OAMG;IACH,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED;;;;;;OAMG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;;;OAMG;IACH,IAAI,QAAQ,IAAI,aAAa,CAE5B;IAED;;;;;;OAMG;IACH,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED;;;;;;OAMG;IACG,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgBxE;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,OAAO,CAEpB;CACF;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,CAE1E"}
@@ -1,78 +1,189 @@
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
+ /**
25
+ * @param name The name of the cookie
26
+ * @param options Options for the cookie
27
+ */
28
+ constructor(name, options) {
29
+ let { decode = decodeURIComponent, encode = encodeURIComponent, secrets = [], domain, expires, httpOnly, maxAge, path = '/', partitioned, secure, sameSite = 'Lax', } = options ?? {};
30
+ if (partitioned === true) {
31
+ // Partitioned cookies must be set with Secure
32
+ // See https://developer.mozilla.org/en-US/docs/Web/Privacy/Guides/Privacy_sandbox/Partitioned_cookies
33
+ secure = true;
34
+ }
35
+ this.#name = name;
36
+ this.#decode = decode;
37
+ this.#encode = encode;
38
+ this.#secrets = secrets;
39
+ this.#domain = domain;
40
+ this.#expires = expires;
41
+ this.#httpOnly = httpOnly;
42
+ this.#maxAge = maxAge;
43
+ this.#partitioned = partitioned;
44
+ this.#path = path;
45
+ this.#sameSite = sameSite;
46
+ this.#secure = secure;
47
+ }
48
+ /**
49
+ * The domain of the cookie.
50
+ *
51
+ * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#domaindomain-value)
52
+ */
53
+ get domain() {
54
+ return this.#domain;
55
+ }
56
+ /**
57
+ * The expiration date of the cookie.
58
+ *
59
+ * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#expiresdate)
60
+ */
61
+ get expires() {
62
+ return this.#expires;
63
+ }
64
+ /**
65
+ * True if the cookie is HTTP-only.
66
+ *
67
+ * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#httponly)
68
+ *
69
+ * @default false
70
+ */
71
+ get httpOnly() {
72
+ return this.#httpOnly ?? false;
73
+ }
74
+ /**
75
+ * The maximum age of the cookie in seconds.
76
+ *
77
+ * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie#max-agenumber)
78
+ */
79
+ get maxAge() {
80
+ return this.#maxAge;
81
+ }
82
+ /**
83
+ * The name of the cookie.
84
+ *
85
+ * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie#cookie-namecookie-value)
86
+ */
87
+ get name() {
88
+ return this.#name;
89
+ }
90
+ /**
91
+ * Extracts the value of this cookie from a `Cookie` header value.
92
+ *
93
+ * @param headerValue The `Cookie` header to parse
94
+ * @returns The value of this cookie, or `null` if it's not present
95
+ */
96
+ async parse(headerValue) {
97
+ if (!headerValue)
98
+ return null;
99
+ let header = new CookieHeader(headerValue);
100
+ if (!header.has(this.#name))
101
+ return null;
102
+ let value = header.get(this.#name);
103
+ if (value === '')
104
+ return '';
105
+ let decoded = await decodeCookieValue(value, this.#secrets, this.#decode);
106
+ return decoded;
107
+ }
108
+ /**
109
+ * True if the cookie is partitioned.
110
+ *
111
+ * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#partitioned)
112
+ *
113
+ * @default false
114
+ */
115
+ get partitioned() {
116
+ return this.#partitioned ?? false;
117
+ }
118
+ /**
119
+ * The path of the cookie.
120
+ *
121
+ * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#pathpath-value)
122
+ *
123
+ * @default '/'
124
+ */
125
+ get path() {
126
+ return this.#path;
127
+ }
128
+ /**
129
+ * The `SameSite` attribute of the cookie.
130
+ *
131
+ * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value)
132
+ *
133
+ * @default 'Lax'
134
+ */
135
+ get sameSite() {
136
+ return this.#sameSite;
137
+ }
138
+ /**
139
+ * True if the cookie is secure (only sent over HTTPS).
140
+ *
141
+ * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#secure)
142
+ *
143
+ * @default false
144
+ */
145
+ get secure() {
146
+ return this.#secure ?? false;
147
+ }
148
+ /**
149
+ * Returns the value to use in a `Set-Cookie` header for this cookie.
150
+ *
151
+ * @param value The value to serialize
152
+ * @param props Additional properties to use when serializing the cookie
153
+ * @returns The `Set-Cookie` header value for this cookie
154
+ */
155
+ async serialize(value, props) {
156
+ let header = new SetCookieHeader({
157
+ name: this.#name,
158
+ value: value === '' ? '' : await encodeCookieValue(value, this.#secrets, this.#encode),
159
+ domain: this.#domain,
160
+ expires: this.#expires,
161
+ httpOnly: this.#httpOnly,
162
+ maxAge: this.#maxAge,
163
+ partitioned: this.#partitioned,
164
+ path: this.#path,
165
+ sameSite: this.#sameSite,
166
+ secure: this.#secure,
167
+ ...props,
168
+ });
169
+ return header.toString();
170
+ }
171
+ /**
172
+ * True if this cookie uses one or more secrets for verification.
173
+ */
174
+ get signed() {
175
+ return this.#secrets.length > 0;
176
+ }
177
+ }
3
178
  /**
4
179
  * Creates a new cookie object.
180
+ *
5
181
  * @param name The name of the cookie
6
- * @param options (optional) Additional options for the cookie
7
- * @returns A cookie object
182
+ * @param options Options for the cookie
183
+ * @returns A new `Cookie` object
8
184
  */
9
185
  export function createCookie(name, options) {
10
- let { decode = decodeURIComponent, encode = encodeURIComponent, secrets = [], domain, expires, httpOnly, maxAge, path = '/', partitioned, secure, sameSite = 'Lax', } = options ?? {};
11
- if (partitioned === true) {
12
- // Partitioned cookies must be set with Secure
13
- // See https://developer.mozilla.org/en-US/docs/Web/Privacy/Guides/Privacy_sandbox/Partitioned_cookies
14
- secure = true;
15
- }
16
- return {
17
- get domain() {
18
- return domain;
19
- },
20
- get expires() {
21
- return expires;
22
- },
23
- get httpOnly() {
24
- return httpOnly ?? false;
25
- },
26
- get maxAge() {
27
- return maxAge;
28
- },
29
- get name() {
30
- return name;
31
- },
32
- async parse(headerValue) {
33
- if (!headerValue)
34
- return null;
35
- let header = new CookieHeader(headerValue);
36
- if (!header.has(name))
37
- return null;
38
- let value = header.get(name);
39
- if (value === '')
40
- return '';
41
- let decoded = await decodeCookieValue(value, secrets, decode);
42
- return decoded;
43
- },
44
- get partitioned() {
45
- return partitioned ?? false;
46
- },
47
- get path() {
48
- return path;
49
- },
50
- get sameSite() {
51
- return sameSite;
52
- },
53
- get secure() {
54
- return secure ?? false;
55
- },
56
- async serialize(value, props) {
57
- let header = new SetCookieHeader({
58
- name: name,
59
- value: value === '' ? '' : await encodeCookieValue(value, secrets, encode),
60
- domain,
61
- expires,
62
- httpOnly,
63
- maxAge,
64
- partitioned,
65
- path,
66
- sameSite,
67
- secure,
68
- ...props,
69
- });
70
- return header.toString();
71
- },
72
- get signed() {
73
- return secrets.length > 0;
74
- },
75
- };
186
+ return new Cookie(name, options);
76
187
  }
77
188
  async function decodeCookieValue(value, secrets, decode) {
78
189
  if (secrets.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remix-run/cookie",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "description": "A toolkit for working with cookies in JavaScript",
5
5
  "author": "Michael Jackson <mjijackson@gmail.com>",
6
6
  "license": "MIT",
@@ -26,10 +26,11 @@
26
26
  "./package.json": "./package.json"
27
27
  },
28
28
  "devDependencies": {
29
- "@types/node": "^24.6.0"
29
+ "@types/node": "^24.6.0",
30
+ "@typescript/native-preview": "7.0.0-dev.20251125.1"
30
31
  },
31
- "peerDependencies": {
32
- "@remix-run/headers": "^0.17.0"
32
+ "dependencies": {
33
+ "@remix-run/headers": "^0.19.0"
33
34
  },
34
35
  "keywords": [
35
36
  "http",
@@ -39,9 +40,9 @@
39
40
  "set-cookie"
40
41
  ],
41
42
  "scripts": {
42
- "build": "tsc -p tsconfig.build.json",
43
+ "build": "tsgo -p tsconfig.build.json",
43
44
  "clean": "git clean -fdX",
44
- "test": "node --disable-warning=ExperimentalWarning --test './src/**/*.test.ts'",
45
- "typecheck": "tsc --noEmit"
45
+ "test": "node --disable-warning=ExperimentalWarning --test",
46
+ "typecheck": "tsgo --noEmit"
46
47
  }
47
48
  }
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'
@@ -34,7 +34,7 @@ export async function unsign(cookie: string, secret: string): Promise<string | f
34
34
  }
35
35
  }
36
36
 
37
- async function createKey(secret: string, usages: CryptoKey['usages']): Promise<CryptoKey> {
37
+ function createKey(secret: string, usages: CryptoKey['usages']): Promise<CryptoKey> {
38
38
  return crypto.subtle.importKey(
39
39
  'raw',
40
40
  encoder.encode(secret),
package/src/lib/cookie.ts CHANGED
@@ -6,7 +6,41 @@ import {
6
6
 
7
7
  import { sign, unsign } from './cookie-signing.ts'
8
8
 
9
+ /**
10
+ * Options for creating a cookie.
11
+ */
12
+ export interface CookieOptions extends CookieProperties {
13
+ /**
14
+ * A function that decodes the cookie value. Decodes any URL-encoded sequences into their
15
+ * original characters.
16
+ *
17
+ * See [RFC 6265](https://tools.ietf.org/html/rfc6265#section-4.1.1) for more details.
18
+ *
19
+ * @default decodeURIComponent
20
+ */
21
+ decode?: (value: string) => string
22
+ /**
23
+ * A function that encodes the cookie value. Percent-encodes all characters that are not allowed
24
+ * in a cookie value.
25
+ *
26
+ * See [RFC 6265](https://tools.ietf.org/html/rfc6265#section-4.1.1) for more details.
27
+ *
28
+ * @default encodeURIComponent
29
+ */
30
+ encode?: (value: string) => string
31
+ /**
32
+ * An array of secrets that may be used to sign/unsign the value of a cookie.
33
+ *
34
+ * The array makes it easy to rotate secrets. New secrets should be added to
35
+ * the beginning of the array. `cookie.serialize()` will always use the first
36
+ * value in the array, but `cookie.parse()` may use any of them so that
37
+ * cookies that were signed with older secrets still work.
38
+ */
39
+ secrets?: string[]
40
+ }
41
+
9
42
  type SameSiteValue = 'Strict' | 'Lax' | 'None'
43
+ type Coder = (value: string) => string
10
44
 
11
45
  /**
12
46
  * Represents a HTTP cookie.
@@ -16,202 +50,213 @@ type SameSiteValue = 'Strict' | 'Lax' | 'None'
16
50
  * Also supports cryptographic signing of the cookie value to ensure it's not tampered with, and
17
51
  * secret rotation to easily rotate secrets without breaking existing cookies.
18
52
  */
19
- export interface Cookie {
53
+ export class Cookie implements CookieProperties {
54
+ #name: string
55
+ #decode: Coder
56
+ #encode: Coder
57
+ #secrets: string[]
58
+ #domain: string | undefined
59
+ #expires: Date | undefined
60
+ #httpOnly: boolean | undefined
61
+ #maxAge: number | undefined
62
+ #partitioned: boolean | undefined
63
+ #path: string
64
+ #sameSite: SameSiteValue
65
+ #secure: boolean | undefined
66
+
67
+ /**
68
+ * @param name The name of the cookie
69
+ * @param options Options for the cookie
70
+ */
71
+ constructor(name: string, options?: CookieOptions) {
72
+ let {
73
+ decode = decodeURIComponent,
74
+ encode = encodeURIComponent,
75
+ secrets = [],
76
+ domain,
77
+ expires,
78
+ httpOnly,
79
+ maxAge,
80
+ path = '/',
81
+ partitioned,
82
+ secure,
83
+ sameSite = 'Lax',
84
+ } = options ?? {}
85
+
86
+ if (partitioned === true) {
87
+ // Partitioned cookies must be set with Secure
88
+ // See https://developer.mozilla.org/en-US/docs/Web/Privacy/Guides/Privacy_sandbox/Partitioned_cookies
89
+ secure = true
90
+ }
91
+
92
+ this.#name = name
93
+ this.#decode = decode
94
+ this.#encode = encode
95
+ this.#secrets = secrets
96
+ this.#domain = domain
97
+ this.#expires = expires
98
+ this.#httpOnly = httpOnly
99
+ this.#maxAge = maxAge
100
+ this.#partitioned = partitioned
101
+ this.#path = path
102
+ this.#sameSite = sameSite
103
+ this.#secure = secure
104
+ }
105
+
20
106
  /**
21
107
  * The domain of the cookie.
22
108
  *
23
109
  * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#domaindomain-value)
24
110
  */
25
- readonly domain: string | undefined
111
+ get domain(): string | undefined {
112
+ return this.#domain
113
+ }
114
+
26
115
  /**
27
116
  * The expiration date of the cookie.
28
117
  *
29
118
  * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#expiresdate)
30
119
  */
31
- readonly expires: Date | undefined
120
+ get expires(): Date | undefined {
121
+ return this.#expires
122
+ }
123
+
32
124
  /**
33
125
  * True if the cookie is HTTP-only.
34
126
  *
35
127
  * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#httponly)
128
+ *
129
+ * @default false
36
130
  */
37
- readonly httpOnly: boolean
131
+ get httpOnly(): boolean {
132
+ return this.#httpOnly ?? false
133
+ }
134
+
38
135
  /**
39
136
  * The maximum age of the cookie in seconds.
40
137
  *
41
138
  * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie#max-agenumber)
42
139
  */
43
- readonly maxAge: number | undefined
140
+ get maxAge(): number | undefined {
141
+ return this.#maxAge
142
+ }
143
+
44
144
  /**
45
145
  * The name of the cookie.
46
146
  *
47
147
  * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie#cookie-namecookie-value)
48
148
  */
49
- readonly name: string
149
+ get name(): string {
150
+ return this.#name
151
+ }
152
+
50
153
  /**
51
154
  * Extracts the value of this cookie from a `Cookie` header value.
155
+ *
52
156
  * @param headerValue The `Cookie` header to parse
53
157
  * @returns The value of this cookie, or `null` if it's not present
54
158
  */
55
- parse(headerValue: string | null): Promise<string | null>
159
+ async parse(headerValue: string | null): Promise<string | null> {
160
+ if (!headerValue) return null
161
+
162
+ let header = new CookieHeader(headerValue)
163
+ if (!header.has(this.#name)) return null
164
+
165
+ let value = header.get(this.#name)!
166
+ if (value === '') return ''
167
+
168
+ let decoded = await decodeCookieValue(value, this.#secrets, this.#decode)
169
+ return decoded
170
+ }
171
+
56
172
  /**
57
173
  * True if the cookie is partitioned.
58
174
  *
59
175
  * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#partitioned)
176
+ *
177
+ * @default false
60
178
  */
61
- readonly partitioned: boolean
179
+ get partitioned(): boolean {
180
+ return this.#partitioned ?? false
181
+ }
182
+
62
183
  /**
63
- * The path of the cookie. Defaults to `/`.
184
+ * The path of the cookie.
64
185
  *
65
186
  * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#pathpath-value)
187
+ *
188
+ * @default '/'
66
189
  */
67
- readonly path: string
190
+ get path(): string {
191
+ return this.#path
192
+ }
193
+
68
194
  /**
69
- * The `SameSite` attribute of the cookie. Defaults to `Lax`.
195
+ * The `SameSite` attribute of the cookie.
70
196
  *
71
197
  * [MDN Reference](https://developer.mozilla.org/en-US/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value)
198
+ *
199
+ * @default 'Lax'
72
200
  */
73
- readonly sameSite: SameSiteValue
201
+ get sameSite(): SameSiteValue {
202
+ return this.#sameSite
203
+ }
204
+
74
205
  /**
75
206
  * True if the cookie is secure (only sent over HTTPS).
76
207
  *
77
208
  * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#secure)
209
+ *
210
+ * @default false
78
211
  */
79
- readonly secure: boolean
212
+ get secure(): boolean {
213
+ return this.#secure ?? false
214
+ }
215
+
80
216
  /**
81
217
  * Returns the value to use in a `Set-Cookie` header for this cookie.
218
+ *
82
219
  * @param value The value to serialize
83
- * @param props (optional) Additional properties to use when serializing the cookie
84
- * @returns The `Set-Cookie` header for this cookie
85
- */
86
- serialize(value: string, props?: CookieProperties): Promise<string>
87
- /**
88
- * True if this cookie uses one or more secrets for verification.
220
+ * @param props Additional properties to use when serializing the cookie
221
+ * @returns The `Set-Cookie` header value for this cookie
89
222
  */
90
- readonly signed: boolean
91
- }
223
+ async serialize(value: string, props?: CookieProperties): Promise<string> {
224
+ let header = new SetCookieHeader({
225
+ name: this.#name,
226
+ value: value === '' ? '' : await encodeCookieValue(value, this.#secrets, this.#encode),
227
+ domain: this.#domain,
228
+ expires: this.#expires,
229
+ httpOnly: this.#httpOnly,
230
+ maxAge: this.#maxAge,
231
+ partitioned: this.#partitioned,
232
+ path: this.#path,
233
+ sameSite: this.#sameSite,
234
+ secure: this.#secure,
235
+ ...props,
236
+ })
237
+
238
+ return header.toString()
239
+ }
92
240
 
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
241
  /**
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.
242
+ * True if this cookie uses one or more secrets for verification.
119
243
  */
120
- secrets?: string[]
244
+ get signed(): boolean {
245
+ return this.#secrets.length > 0
246
+ }
121
247
  }
122
248
 
123
249
  /**
124
250
  * Creates a new cookie object.
251
+ *
125
252
  * @param name The name of the cookie
126
- * @param options (optional) Additional options for the cookie
127
- * @returns A cookie object
253
+ * @param options Options for the cookie
254
+ * @returns A new `Cookie` object
128
255
  */
129
256
  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
- if (partitioned === true) {
145
- // Partitioned cookies must be set with Secure
146
- // See https://developer.mozilla.org/en-US/docs/Web/Privacy/Guides/Privacy_sandbox/Partitioned_cookies
147
- secure = true
148
- }
149
-
150
- return {
151
- get domain() {
152
- return domain
153
- },
154
- get expires() {
155
- return expires
156
- },
157
- get httpOnly() {
158
- return httpOnly ?? false
159
- },
160
- get maxAge() {
161
- return maxAge
162
- },
163
- get name() {
164
- return name
165
- },
166
- async parse(headerValue: string | null): Promise<string | null> {
167
- if (!headerValue) return null
168
-
169
- let header = new CookieHeader(headerValue)
170
- if (!header.has(name)) return null
171
-
172
- let value = header.get(name)!
173
- if (value === '') return ''
174
-
175
- let decoded = await decodeCookieValue(value, secrets, decode)
176
- return decoded
177
- },
178
- get partitioned() {
179
- return partitioned ?? false
180
- },
181
- get path() {
182
- return path
183
- },
184
- get sameSite() {
185
- return sameSite
186
- },
187
- get secure() {
188
- return secure ?? false
189
- },
190
- async serialize(value: string, props?: CookieProperties): Promise<string> {
191
- let header = new SetCookieHeader({
192
- name: name,
193
- value: value === '' ? '' : await encodeCookieValue(value, secrets, encode),
194
- domain,
195
- expires,
196
- httpOnly,
197
- maxAge,
198
- partitioned,
199
- path,
200
- sameSite,
201
- secure,
202
- ...props,
203
- })
204
-
205
- return header.toString()
206
- },
207
- get signed() {
208
- return secrets.length > 0
209
- },
210
- }
257
+ return new Cookie(name, options)
211
258
  }
212
259
 
213
- type Coder = (value: string) => string
214
-
215
260
  async function decodeCookieValue(
216
261
  value: string,
217
262
  secrets: string[],