better-call 1.4.0 → 1.4.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/dist/cookies.cjs CHANGED
@@ -1,12 +1,14 @@
1
1
  const require_crypto = require("./crypto.cjs");
2
2
  const require_utils = require("./utils.cjs");
3
3
  //#region src/cookies.ts
4
+ const COOKIE_PREFIXES = {
5
+ secure: "__Secure-",
6
+ host: "__Host-"
7
+ };
4
8
  const getCookieKey = (key, prefix) => {
5
- let finalKey = key;
6
- if (prefix) if (prefix === "secure") finalKey = "__Secure-" + key;
7
- else if (prefix === "host") finalKey = "__Host-" + key;
8
- else return;
9
- return finalKey;
9
+ if (!prefix) return key;
10
+ if (prefix !== "secure" && prefix !== "host") return void 0;
11
+ return COOKIE_PREFIXES[prefix] + key;
10
12
  };
11
13
  /**
12
14
  * Parse an HTTP Cookie header string and returning an object of all cookie
@@ -39,43 +41,47 @@ function parseCookies(str) {
39
41
  }
40
42
  return cookies;
41
43
  }
42
- const _serialize = (key, value, opt = {}) => {
43
- let cookie;
44
- if (opt?.prefix === "secure") cookie = `${`__Secure-${key}`}=${value}`;
45
- else if (opt?.prefix === "host") cookie = `${`__Host-${key}`}=${value}`;
46
- else cookie = `${key}=${value}`;
47
- if (key.startsWith("__Secure-") && !opt.secure) opt.secure = true;
48
- if (key.startsWith("__Host-")) {
49
- if (!opt.secure) opt.secure = true;
50
- if (opt.path !== "/") opt.path = "/";
51
- if (opt.domain) opt.domain = void 0;
52
- }
53
- if (opt && typeof opt.maxAge === "number" && opt.maxAge >= 0) {
54
- if (opt.maxAge > 3456e4) throw new Error("Cookies Max-Age SHOULD NOT be greater than 400 days (34560000 seconds) in duration.");
55
- cookie += `; Max-Age=${Math.floor(opt.maxAge)}`;
56
- }
57
- if (opt.domain && opt.prefix !== "host") cookie += `; Domain=${opt.domain}`;
58
- if (opt.path) cookie += `; Path=${opt.path}`;
59
- if (opt.expires) {
60
- if (opt.expires.getTime() - Date.now() > 3456e7) throw new Error("Cookies Expires SHOULD NOT be greater than 400 days (34560000 seconds) in the future.");
61
- cookie += `; Expires=${opt.expires.toUTCString()}`;
44
+ function resolveCookie(name, value, options = {}) {
45
+ const resolvedName = getCookieKey(name, options.prefix) ?? name;
46
+ const resolvedOptions = { ...options };
47
+ const lowerName = resolvedName.toLowerCase();
48
+ if (lowerName.startsWith("__host-")) {
49
+ resolvedOptions.secure = true;
50
+ resolvedOptions.path = "/";
51
+ resolvedOptions.domain = void 0;
52
+ } else if (lowerName.startsWith("__secure-")) resolvedOptions.secure = true;
53
+ if (resolvedOptions.partitioned === true || resolvedOptions.sameSite?.toLowerCase() === "none") resolvedOptions.secure = true;
54
+ return {
55
+ name: resolvedName,
56
+ value,
57
+ options: resolvedOptions
58
+ };
59
+ }
60
+ function serializeCookieValue({ name, value, options }) {
61
+ let cookie = `${name}=${value}`;
62
+ if (typeof options.maxAge === "number" && options.maxAge >= 0) {
63
+ if (options.maxAge > 3456e4) throw new Error("Cookies Max-Age SHOULD NOT be greater than 400 days (34560000 seconds) in duration.");
64
+ cookie += `; Max-Age=${Math.floor(options.maxAge)}`;
62
65
  }
63
- if (opt.httpOnly) cookie += "; HttpOnly";
64
- if (opt.secure) cookie += "; Secure";
65
- if (opt.sameSite) cookie += `; SameSite=${opt.sameSite.charAt(0).toUpperCase() + opt.sameSite.slice(1)}`;
66
- if (opt.partitioned) {
67
- if (!opt.secure) opt.secure = true;
68
- cookie += "; Partitioned";
66
+ if (options.domain) cookie += `; Domain=${options.domain}`;
67
+ if (options.path) cookie += `; Path=${options.path}`;
68
+ if (options.expires) {
69
+ if (options.expires.getTime() - Date.now() > 3456e7) throw new Error("Cookies Expires SHOULD NOT be greater than 400 days (34560000 seconds) in the future.");
70
+ cookie += `; Expires=${options.expires.toUTCString()}`;
69
71
  }
72
+ if (options.httpOnly) cookie += "; HttpOnly";
73
+ if (options.secure) cookie += "; Secure";
74
+ if (options.sameSite) cookie += `; SameSite=${options.sameSite.charAt(0).toUpperCase() + options.sameSite.slice(1)}`;
75
+ if (options.partitioned) cookie += "; Partitioned";
70
76
  return cookie;
71
- };
77
+ }
72
78
  const serializeCookie = (key, value, opt) => {
73
79
  value = encodeURIComponent(value);
74
- return _serialize(key, value, opt);
80
+ return serializeCookieValue(resolveCookie(key, value, opt));
75
81
  };
76
82
  const serializeSignedCookie = async (key, value, secret, opt) => {
77
83
  value = await require_crypto.signCookieValue(value, secret);
78
- return _serialize(key, value, opt);
84
+ return serializeCookieValue(resolveCookie(key, value, opt));
79
85
  };
80
86
  //#endregion
81
87
  exports.getCookieKey = getCookieKey;
@@ -1 +1 @@
1
- {"version":3,"file":"cookies.cjs","names":["tryDecode","signCookieValue"],"sources":["../src/cookies.ts"],"sourcesContent":["import { signCookieValue } from \"./crypto\";\nimport { tryDecode } from \"./utils\";\n\nexport type CookiePrefixOptions = \"host\" | \"secure\";\n\nexport type CookieOptions = {\n\t/**\n\t * Domain of the cookie\n\t *\n\t * The Domain attribute specifies which server can receive a cookie. If specified, cookies are\n\t * available on the specified server and its subdomains. If the it is not\n\t * specified, the cookies are available on the server that sets it but not on\n\t * its subdomains.\n\t *\n\t * @example\n\t * `domain: \"example.com\"`\n\t */\n\tdomain?: string;\n\t/**\n\t * A lifetime of a cookie. Permanent cookies are deleted after the date specified in the\n\t * Expires attribute:\n\t *\n\t * Expires has been available for longer than Max-Age, however Max-Age is less error-prone, and\n\t * takes precedence when both are set. The rationale behind this is that when you set an\n\t * Expires date and time, they're relative to the client the cookie is being set on. If the\n\t * server is set to a different time, this could cause errors\n\t */\n\texpires?: Date;\n\t/**\n\t * Forbids JavaScript from accessing the cookie, for example, through the Document.cookie\n\t * property. Note that a cookie that has been created with HttpOnly will still be sent with\n\t * JavaScript-initiated requests, for example, when calling XMLHttpRequest.send() or fetch().\n\t * This mitigates attacks against cross-site scripting\n\t */\n\thttpOnly?: boolean;\n\t/**\n\t * Indicates the number of seconds until the cookie expires. A zero or negative number will\n\t * expire the cookie immediately. If both Expires and Max-Age are set, Max-Age has precedence.\n\t *\n\t * @example 604800 - 7 days\n\t */\n\tmaxAge?: number;\n\t/**\n\t * Indicates the path that must exist in the requested URL for the browser to send the Cookie\n\t * header.\n\t *\n\t * @example\n\t * \"/docs\"\n\t * // -> the request paths /docs, /docs/, /docs/Web/, and /docs/Web/HTTP will all match. the request paths /, /fr/docs will not match.\n\t */\n\tpath?: string;\n\t/**\n\t * Indicates that the cookie is sent to the server only when a request is made with the https:\n\t * scheme (except on localhost), and therefore, is more resistant to man-in-the-middle attacks.\n\t */\n\tsecure?: boolean;\n\t/**\n\t * Controls whether or not a cookie is sent with cross-site requests, providing some protection\n\t * against cross-site request forgery attacks (CSRF).\n\t *\n\t * Strict - Means that the browser sends the cookie only for same-site requests, that is,\n\t * requests originating from the same site that set the cookie. If a request originates from a\n\t * different domain or scheme (even with the same domain), no cookies with the SameSite=Strict\n\t * attribute are sent.\n\t *\n\t * Lax - Means that the cookie is not sent on cross-site requests, such as on requests to load\n\t * images or frames, but is sent when a user is navigating to the origin site from an external\n\t * site (for example, when following a link). This is the default behavior if the SameSite\n\t * attribute is not specified.\n\t *\n\t * None - Means that the browser sends the cookie with both cross-site and same-site requests.\n\t * The Secure attribute must also be set when setting this value.\n\t */\n\tsameSite?: \"Strict\" | \"Lax\" | \"None\" | \"strict\" | \"lax\" | \"none\";\n\t/**\n\t * Indicates that the cookie should be stored using partitioned storage. Note that if this is\n\t * set, the Secure directive must also be set.\n\t *\n\t * @see https://developer.mozilla.org/en-US/docs/Web/Privacy/Privacy_sandbox/Partitioned_cookies\n\t */\n\tpartitioned?: boolean;\n\t/**\n\t * Cooke Prefix\n\t *\n\t * - secure: `__Secure-` -> `__Secure-cookie-name`\n\t * - host: `__Host-` -> `__Host-cookie-name`\n\t *\n\t * `secure` must be set to true to use prefixes\n\t */\n\tprefix?: CookiePrefixOptions;\n};\n\nexport const getCookieKey = (key: string, prefix?: CookiePrefixOptions) => {\n\tlet finalKey = key;\n\tif (prefix) {\n\t\tif (prefix === \"secure\") {\n\t\t\tfinalKey = \"__Secure-\" + key;\n\t\t} else if (prefix === \"host\") {\n\t\t\tfinalKey = \"__Host-\" + key;\n\t\t} else {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\treturn finalKey;\n};\n\n/**\n * Parse an HTTP Cookie header string and returning an object of all cookie\n * name-value pairs.\n *\n * Inspired by https://github.com/unjs/cookie-es/blob/main/src/cookie/parse.ts\n *\n * @param str the string representing a `Cookie` header value\n */\nexport function parseCookies(str: string) {\n\tif (typeof str !== \"string\") {\n\t\tthrow new TypeError(\"argument str must be a string\");\n\t}\n\n\tconst cookies: Map<string, string> = new Map();\n\n\tlet index = 0;\n\twhile (index < str.length) {\n\t\tconst eqIdx = str.indexOf(\"=\", index);\n\n\t\tif (eqIdx === -1) {\n\t\t\tbreak;\n\t\t}\n\n\t\tlet endIdx = str.indexOf(\";\", index);\n\n\t\tif (endIdx === -1) {\n\t\t\tendIdx = str.length;\n\t\t} else if (endIdx < eqIdx) {\n\t\t\tindex = str.lastIndexOf(\";\", eqIdx - 1) + 1;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst key = str.slice(index, eqIdx).trim();\n\t\tif (!cookies.has(key)) {\n\t\t\tlet val = str.slice(eqIdx + 1, endIdx).trim();\n\t\t\tif (val.codePointAt(0) === 0x22) {\n\t\t\t\tval = val.slice(1, -1);\n\t\t\t}\n\t\t\tcookies.set(key, tryDecode(val));\n\t\t}\n\n\t\tindex = endIdx + 1;\n\t}\n\n\treturn cookies;\n}\n\nconst _serialize = (key: string, value: string, opt: CookieOptions = {}) => {\n\tlet cookie: string;\n\n\tif (opt?.prefix === \"secure\") {\n\t\tcookie = `${`__Secure-${key}`}=${value}`;\n\t} else if (opt?.prefix === \"host\") {\n\t\tcookie = `${`__Host-${key}`}=${value}`;\n\t} else {\n\t\tcookie = `${key}=${value}`;\n\t}\n\n\tif (key.startsWith(\"__Secure-\") && !opt.secure) {\n\t\topt.secure = true;\n\t}\n\n\tif (key.startsWith(\"__Host-\")) {\n\t\tif (!opt.secure) {\n\t\t\topt.secure = true;\n\t\t}\n\n\t\tif (opt.path !== \"/\") {\n\t\t\topt.path = \"/\";\n\t\t}\n\n\t\tif (opt.domain) {\n\t\t\topt.domain = undefined;\n\t\t}\n\t}\n\n\tif (opt && typeof opt.maxAge === \"number\" && opt.maxAge >= 0) {\n\t\tif (opt.maxAge > 34560000) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Cookies Max-Age SHOULD NOT be greater than 400 days (34560000 seconds) in duration.\",\n\t\t\t);\n\t\t}\n\t\tcookie += `; Max-Age=${Math.floor(opt.maxAge)}`;\n\t}\n\n\tif (opt.domain && opt.prefix !== \"host\") {\n\t\tcookie += `; Domain=${opt.domain}`;\n\t}\n\n\tif (opt.path) {\n\t\tcookie += `; Path=${opt.path}`;\n\t}\n\n\tif (opt.expires) {\n\t\tif (opt.expires.getTime() - Date.now() > 34560000_000) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Cookies Expires SHOULD NOT be greater than 400 days (34560000 seconds) in the future.\",\n\t\t\t);\n\t\t}\n\t\tcookie += `; Expires=${opt.expires.toUTCString()}`;\n\t}\n\n\tif (opt.httpOnly) {\n\t\tcookie += \"; HttpOnly\";\n\t}\n\n\tif (opt.secure) {\n\t\tcookie += \"; Secure\";\n\t}\n\n\tif (opt.sameSite) {\n\t\tcookie += `; SameSite=${opt.sameSite.charAt(0).toUpperCase() + opt.sameSite.slice(1)}`;\n\t}\n\n\tif (opt.partitioned) {\n\t\tif (!opt.secure) {\n\t\t\topt.secure = true;\n\t\t}\n\t\tcookie += \"; Partitioned\";\n\t}\n\n\treturn cookie;\n};\n\nexport const serializeCookie = (\n\tkey: string,\n\tvalue: string,\n\topt?: CookieOptions,\n) => {\n\tvalue = encodeURIComponent(value);\n\treturn _serialize(key, value, opt);\n};\n\nexport const serializeSignedCookie = async (\n\tkey: string,\n\tvalue: string,\n\tsecret: string,\n\topt?: CookieOptions,\n) => {\n\tvalue = await signCookieValue(value, secret);\n\treturn _serialize(key, value, opt);\n};\n"],"mappings":";;;AA4FA,MAAa,gBAAgB,KAAa,WAAiC;CAC1E,IAAI,WAAW;CACf,IAAI,QACH,IAAI,WAAW,UACd,WAAW,cAAc;MACnB,IAAI,WAAW,QACrB,WAAW,YAAY;MAEvB;CAGF,OAAO;AACR;;;;;;;;;AAUA,SAAgB,aAAa,KAAa;CACzC,IAAI,OAAO,QAAQ,UAClB,MAAM,IAAI,UAAU,+BAA+B;CAGpD,MAAM,0BAA+B,IAAI,IAAI;CAE7C,IAAI,QAAQ;CACZ,OAAO,QAAQ,IAAI,QAAQ;EAC1B,MAAM,QAAQ,IAAI,QAAQ,KAAK,KAAK;EAEpC,IAAI,UAAU,IACb;EAGD,IAAI,SAAS,IAAI,QAAQ,KAAK,KAAK;EAEnC,IAAI,WAAW,IACd,SAAS,IAAI;OACP,IAAI,SAAS,OAAO;GAC1B,QAAQ,IAAI,YAAY,KAAK,QAAQ,CAAC,IAAI;GAC1C;EACD;EAEA,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC,KAAK;EACzC,IAAI,CAAC,QAAQ,IAAI,GAAG,GAAG;GACtB,IAAI,MAAM,IAAI,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,KAAK;GAC5C,IAAI,IAAI,YAAY,CAAC,MAAM,IAC1B,MAAM,IAAI,MAAM,GAAG,EAAE;GAEtB,QAAQ,IAAI,KAAKA,cAAAA,UAAU,GAAG,CAAC;EAChC;EAEA,QAAQ,SAAS;CAClB;CAEA,OAAO;AACR;AAEA,MAAM,cAAc,KAAa,OAAe,MAAqB,CAAC,MAAM;CAC3E,IAAI;CAEJ,IAAI,KAAK,WAAW,UACnB,SAAS,GAAG,YAAY,MAAM,GAAG;MAC3B,IAAI,KAAK,WAAW,QAC1B,SAAS,GAAG,UAAU,MAAM,GAAG;MAE/B,SAAS,GAAG,IAAI,GAAG;CAGpB,IAAI,IAAI,WAAW,WAAW,KAAK,CAAC,IAAI,QACvC,IAAI,SAAS;CAGd,IAAI,IAAI,WAAW,SAAS,GAAG;EAC9B,IAAI,CAAC,IAAI,QACR,IAAI,SAAS;EAGd,IAAI,IAAI,SAAS,KAChB,IAAI,OAAO;EAGZ,IAAI,IAAI,QACP,IAAI,SAAS,KAAA;CAEf;CAEA,IAAI,OAAO,OAAO,IAAI,WAAW,YAAY,IAAI,UAAU,GAAG;EAC7D,IAAI,IAAI,SAAS,QAChB,MAAM,IAAI,MACT,qFACD;EAED,UAAU,aAAa,KAAK,MAAM,IAAI,MAAM;CAC7C;CAEA,IAAI,IAAI,UAAU,IAAI,WAAW,QAChC,UAAU,YAAY,IAAI;CAG3B,IAAI,IAAI,MACP,UAAU,UAAU,IAAI;CAGzB,IAAI,IAAI,SAAS;EAChB,IAAI,IAAI,QAAQ,QAAQ,IAAI,KAAK,IAAI,IAAI,QACxC,MAAM,IAAI,MACT,uFACD;EAED,UAAU,aAAa,IAAI,QAAQ,YAAY;CAChD;CAEA,IAAI,IAAI,UACP,UAAU;CAGX,IAAI,IAAI,QACP,UAAU;CAGX,IAAI,IAAI,UACP,UAAU,cAAc,IAAI,SAAS,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,IAAI,SAAS,MAAM,CAAC;CAGpF,IAAI,IAAI,aAAa;EACpB,IAAI,CAAC,IAAI,QACR,IAAI,SAAS;EAEd,UAAU;CACX;CAEA,OAAO;AACR;AAEA,MAAa,mBACZ,KACA,OACA,QACI;CACJ,QAAQ,mBAAmB,KAAK;CAChC,OAAO,WAAW,KAAK,OAAO,GAAG;AAClC;AAEA,MAAa,wBAAwB,OACpC,KACA,OACA,QACA,QACI;CACJ,QAAQ,MAAMC,eAAAA,gBAAgB,OAAO,MAAM;CAC3C,OAAO,WAAW,KAAK,OAAO,GAAG;AAClC"}
1
+ {"version":3,"file":"cookies.cjs","names":["tryDecode","signCookieValue"],"sources":["../src/cookies.ts"],"sourcesContent":["import { signCookieValue } from \"./crypto\";\nimport { tryDecode } from \"./utils\";\n\nexport type CookiePrefixOptions = \"host\" | \"secure\";\n\nconst COOKIE_PREFIXES = {\n\tsecure: \"__Secure-\",\n\thost: \"__Host-\",\n} as const satisfies Record<CookiePrefixOptions, string>;\n\nexport type CookieOptions = {\n\t/**\n\t * Domain of the cookie\n\t *\n\t * The Domain attribute specifies which server can receive a cookie. If specified, cookies are\n\t * available on the specified server and its subdomains. If the it is not\n\t * specified, the cookies are available on the server that sets it but not on\n\t * its subdomains.\n\t *\n\t * @example\n\t * `domain: \"example.com\"`\n\t */\n\tdomain?: string;\n\t/**\n\t * A lifetime of a cookie. Permanent cookies are deleted after the date specified in the\n\t * Expires attribute:\n\t *\n\t * Expires has been available for longer than Max-Age, however Max-Age is less error-prone, and\n\t * takes precedence when both are set. The rationale behind this is that when you set an\n\t * Expires date and time, they're relative to the client the cookie is being set on. If the\n\t * server is set to a different time, this could cause errors\n\t */\n\texpires?: Date;\n\t/**\n\t * Forbids JavaScript from accessing the cookie, for example, through the Document.cookie\n\t * property. Note that a cookie that has been created with HttpOnly will still be sent with\n\t * JavaScript-initiated requests, for example, when calling XMLHttpRequest.send() or fetch().\n\t * This mitigates attacks against cross-site scripting\n\t */\n\thttpOnly?: boolean;\n\t/**\n\t * Indicates the number of seconds until the cookie expires. A zero or negative number will\n\t * expire the cookie immediately. If both Expires and Max-Age are set, Max-Age has precedence.\n\t *\n\t * @example 604800 - 7 days\n\t */\n\tmaxAge?: number;\n\t/**\n\t * Indicates the path that must exist in the requested URL for the browser to send the Cookie\n\t * header.\n\t *\n\t * @example\n\t * \"/docs\"\n\t * // -> the request paths /docs, /docs/, /docs/Web/, and /docs/Web/HTTP will all match. the request paths /, /fr/docs will not match.\n\t */\n\tpath?: string;\n\t/**\n\t * Indicates that the cookie is sent to the server only when a request is made with the https:\n\t * scheme (except on localhost), and therefore, is more resistant to man-in-the-middle attacks.\n\t */\n\tsecure?: boolean;\n\t/**\n\t * Controls whether or not a cookie is sent with cross-site requests, providing some protection\n\t * against cross-site request forgery attacks (CSRF).\n\t *\n\t * Strict - Means that the browser sends the cookie only for same-site requests, that is,\n\t * requests originating from the same site that set the cookie. If a request originates from a\n\t * different domain or scheme (even with the same domain), no cookies with the SameSite=Strict\n\t * attribute are sent.\n\t *\n\t * Lax - Means that the cookie is not sent on cross-site requests, such as on requests to load\n\t * images or frames, but is sent when a user is navigating to the origin site from an external\n\t * site (for example, when following a link). This is the default behavior if the SameSite\n\t * attribute is not specified.\n\t *\n\t * None - Means that the browser sends the cookie with both cross-site and same-site requests.\n\t * The Secure attribute must also be set when setting this value.\n\t */\n\tsameSite?: \"Strict\" | \"Lax\" | \"None\" | \"strict\" | \"lax\" | \"none\";\n\t/**\n\t * Indicates that the cookie should be stored using partitioned storage. Note that if this is\n\t * set, the Secure directive must also be set.\n\t *\n\t * @see https://developer.mozilla.org/en-US/docs/Web/Privacy/Privacy_sandbox/Partitioned_cookies\n\t */\n\tpartitioned?: boolean;\n\t/**\n\t * Cooke Prefix\n\t *\n\t * - secure: `__Secure-` -> `__Secure-cookie-name`\n\t * - host: `__Host-` -> `__Host-cookie-name`\n\t *\n\t * Required Secure, Path, and Domain attributes are applied automatically.\n\t */\n\tprefix?: CookiePrefixOptions;\n};\n\ntype Cookie = {\n\tname: string;\n\tvalue: string;\n\toptions: CookieOptions;\n};\n\nexport const getCookieKey = (key: string, prefix?: CookiePrefixOptions) => {\n\tif (!prefix) return key;\n\tif (prefix !== \"secure\" && prefix !== \"host\") return undefined;\n\treturn COOKIE_PREFIXES[prefix] + key;\n};\n\n/**\n * Parse an HTTP Cookie header string and returning an object of all cookie\n * name-value pairs.\n *\n * Inspired by https://github.com/unjs/cookie-es/blob/main/src/cookie/parse.ts\n *\n * @param str the string representing a `Cookie` header value\n */\nexport function parseCookies(str: string) {\n\tif (typeof str !== \"string\") {\n\t\tthrow new TypeError(\"argument str must be a string\");\n\t}\n\n\tconst cookies: Map<string, string> = new Map();\n\n\tlet index = 0;\n\twhile (index < str.length) {\n\t\tconst eqIdx = str.indexOf(\"=\", index);\n\n\t\tif (eqIdx === -1) {\n\t\t\tbreak;\n\t\t}\n\n\t\tlet endIdx = str.indexOf(\";\", index);\n\n\t\tif (endIdx === -1) {\n\t\t\tendIdx = str.length;\n\t\t} else if (endIdx < eqIdx) {\n\t\t\tindex = str.lastIndexOf(\";\", eqIdx - 1) + 1;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst key = str.slice(index, eqIdx).trim();\n\t\tif (!cookies.has(key)) {\n\t\t\tlet val = str.slice(eqIdx + 1, endIdx).trim();\n\t\t\tif (val.codePointAt(0) === 0x22) {\n\t\t\t\tval = val.slice(1, -1);\n\t\t\t}\n\t\t\tcookies.set(key, tryDecode(val));\n\t\t}\n\n\t\tindex = endIdx + 1;\n\t}\n\n\treturn cookies;\n}\n\nfunction resolveCookie(\n\tname: string,\n\tvalue: string,\n\toptions: CookieOptions = {},\n): Cookie {\n\tconst resolvedName = getCookieKey(name, options.prefix) ?? name;\n\tconst resolvedOptions = { ...options };\n\n\tconst lowerName = resolvedName.toLowerCase();\n\tif (lowerName.startsWith(\"__host-\")) {\n\t\tresolvedOptions.secure = true;\n\t\tresolvedOptions.path = \"/\";\n\t\tresolvedOptions.domain = undefined;\n\t} else if (lowerName.startsWith(\"__secure-\")) {\n\t\tresolvedOptions.secure = true;\n\t}\n\tif (\n\t\tresolvedOptions.partitioned === true ||\n\t\tresolvedOptions.sameSite?.toLowerCase() === \"none\"\n\t) {\n\t\tresolvedOptions.secure = true;\n\t}\n\n\treturn { name: resolvedName, value, options: resolvedOptions };\n}\n\nfunction serializeCookieValue({ name, value, options }: Cookie) {\n\tlet cookie = `${name}=${value}`;\n\tif (typeof options.maxAge === \"number\" && options.maxAge >= 0) {\n\t\tif (options.maxAge > 34560000) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Cookies Max-Age SHOULD NOT be greater than 400 days (34560000 seconds) in duration.\",\n\t\t\t);\n\t\t}\n\t\tcookie += `; Max-Age=${Math.floor(options.maxAge)}`;\n\t}\n\n\tif (options.domain) {\n\t\tcookie += `; Domain=${options.domain}`;\n\t}\n\n\tif (options.path) {\n\t\tcookie += `; Path=${options.path}`;\n\t}\n\n\tif (options.expires) {\n\t\tif (options.expires.getTime() - Date.now() > 34560000_000) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Cookies Expires SHOULD NOT be greater than 400 days (34560000 seconds) in the future.\",\n\t\t\t);\n\t\t}\n\t\tcookie += `; Expires=${options.expires.toUTCString()}`;\n\t}\n\n\tif (options.httpOnly) {\n\t\tcookie += \"; HttpOnly\";\n\t}\n\n\tif (options.secure) {\n\t\tcookie += \"; Secure\";\n\t}\n\n\tif (options.sameSite) {\n\t\tcookie += `; SameSite=${options.sameSite.charAt(0).toUpperCase() + options.sameSite.slice(1)}`;\n\t}\n\n\tif (options.partitioned) {\n\t\tcookie += \"; Partitioned\";\n\t}\n\n\treturn cookie;\n}\n\nexport const serializeCookie = (\n\tkey: string,\n\tvalue: string,\n\topt?: CookieOptions,\n) => {\n\tvalue = encodeURIComponent(value);\n\treturn serializeCookieValue(resolveCookie(key, value, opt));\n};\n\nexport const serializeSignedCookie = async (\n\tkey: string,\n\tvalue: string,\n\tsecret: string,\n\topt?: CookieOptions,\n) => {\n\tvalue = await signCookieValue(value, secret);\n\treturn serializeCookieValue(resolveCookie(key, value, opt));\n};\n"],"mappings":"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"}
@@ -82,7 +82,7 @@ type CookieOptions = {
82
82
  * - secure: `__Secure-` -> `__Secure-cookie-name`
83
83
  * - host: `__Host-` -> `__Host-cookie-name`
84
84
  *
85
- * `secure` must be set to true to use prefixes
85
+ * Required Secure, Path, and Domain attributes are applied automatically.
86
86
  */
87
87
  prefix?: CookiePrefixOptions;
88
88
  };
@@ -82,7 +82,7 @@ type CookieOptions = {
82
82
  * - secure: `__Secure-` -> `__Secure-cookie-name`
83
83
  * - host: `__Host-` -> `__Host-cookie-name`
84
84
  *
85
- * `secure` must be set to true to use prefixes
85
+ * Required Secure, Path, and Domain attributes are applied automatically.
86
86
  */
87
87
  prefix?: CookiePrefixOptions;
88
88
  };
package/dist/cookies.mjs CHANGED
@@ -1,12 +1,14 @@
1
1
  import { signCookieValue } from "./crypto.mjs";
2
2
  import { tryDecode } from "./utils.mjs";
3
3
  //#region src/cookies.ts
4
+ const COOKIE_PREFIXES = {
5
+ secure: "__Secure-",
6
+ host: "__Host-"
7
+ };
4
8
  const getCookieKey = (key, prefix) => {
5
- let finalKey = key;
6
- if (prefix) if (prefix === "secure") finalKey = "__Secure-" + key;
7
- else if (prefix === "host") finalKey = "__Host-" + key;
8
- else return;
9
- return finalKey;
9
+ if (!prefix) return key;
10
+ if (prefix !== "secure" && prefix !== "host") return void 0;
11
+ return COOKIE_PREFIXES[prefix] + key;
10
12
  };
11
13
  /**
12
14
  * Parse an HTTP Cookie header string and returning an object of all cookie
@@ -39,43 +41,47 @@ function parseCookies(str) {
39
41
  }
40
42
  return cookies;
41
43
  }
42
- const _serialize = (key, value, opt = {}) => {
43
- let cookie;
44
- if (opt?.prefix === "secure") cookie = `${`__Secure-${key}`}=${value}`;
45
- else if (opt?.prefix === "host") cookie = `${`__Host-${key}`}=${value}`;
46
- else cookie = `${key}=${value}`;
47
- if (key.startsWith("__Secure-") && !opt.secure) opt.secure = true;
48
- if (key.startsWith("__Host-")) {
49
- if (!opt.secure) opt.secure = true;
50
- if (opt.path !== "/") opt.path = "/";
51
- if (opt.domain) opt.domain = void 0;
52
- }
53
- if (opt && typeof opt.maxAge === "number" && opt.maxAge >= 0) {
54
- if (opt.maxAge > 3456e4) throw new Error("Cookies Max-Age SHOULD NOT be greater than 400 days (34560000 seconds) in duration.");
55
- cookie += `; Max-Age=${Math.floor(opt.maxAge)}`;
56
- }
57
- if (opt.domain && opt.prefix !== "host") cookie += `; Domain=${opt.domain}`;
58
- if (opt.path) cookie += `; Path=${opt.path}`;
59
- if (opt.expires) {
60
- if (opt.expires.getTime() - Date.now() > 3456e7) throw new Error("Cookies Expires SHOULD NOT be greater than 400 days (34560000 seconds) in the future.");
61
- cookie += `; Expires=${opt.expires.toUTCString()}`;
44
+ function resolveCookie(name, value, options = {}) {
45
+ const resolvedName = getCookieKey(name, options.prefix) ?? name;
46
+ const resolvedOptions = { ...options };
47
+ const lowerName = resolvedName.toLowerCase();
48
+ if (lowerName.startsWith("__host-")) {
49
+ resolvedOptions.secure = true;
50
+ resolvedOptions.path = "/";
51
+ resolvedOptions.domain = void 0;
52
+ } else if (lowerName.startsWith("__secure-")) resolvedOptions.secure = true;
53
+ if (resolvedOptions.partitioned === true || resolvedOptions.sameSite?.toLowerCase() === "none") resolvedOptions.secure = true;
54
+ return {
55
+ name: resolvedName,
56
+ value,
57
+ options: resolvedOptions
58
+ };
59
+ }
60
+ function serializeCookieValue({ name, value, options }) {
61
+ let cookie = `${name}=${value}`;
62
+ if (typeof options.maxAge === "number" && options.maxAge >= 0) {
63
+ if (options.maxAge > 3456e4) throw new Error("Cookies Max-Age SHOULD NOT be greater than 400 days (34560000 seconds) in duration.");
64
+ cookie += `; Max-Age=${Math.floor(options.maxAge)}`;
62
65
  }
63
- if (opt.httpOnly) cookie += "; HttpOnly";
64
- if (opt.secure) cookie += "; Secure";
65
- if (opt.sameSite) cookie += `; SameSite=${opt.sameSite.charAt(0).toUpperCase() + opt.sameSite.slice(1)}`;
66
- if (opt.partitioned) {
67
- if (!opt.secure) opt.secure = true;
68
- cookie += "; Partitioned";
66
+ if (options.domain) cookie += `; Domain=${options.domain}`;
67
+ if (options.path) cookie += `; Path=${options.path}`;
68
+ if (options.expires) {
69
+ if (options.expires.getTime() - Date.now() > 3456e7) throw new Error("Cookies Expires SHOULD NOT be greater than 400 days (34560000 seconds) in the future.");
70
+ cookie += `; Expires=${options.expires.toUTCString()}`;
69
71
  }
72
+ if (options.httpOnly) cookie += "; HttpOnly";
73
+ if (options.secure) cookie += "; Secure";
74
+ if (options.sameSite) cookie += `; SameSite=${options.sameSite.charAt(0).toUpperCase() + options.sameSite.slice(1)}`;
75
+ if (options.partitioned) cookie += "; Partitioned";
70
76
  return cookie;
71
- };
77
+ }
72
78
  const serializeCookie = (key, value, opt) => {
73
79
  value = encodeURIComponent(value);
74
- return _serialize(key, value, opt);
80
+ return serializeCookieValue(resolveCookie(key, value, opt));
75
81
  };
76
82
  const serializeSignedCookie = async (key, value, secret, opt) => {
77
83
  value = await signCookieValue(value, secret);
78
- return _serialize(key, value, opt);
84
+ return serializeCookieValue(resolveCookie(key, value, opt));
79
85
  };
80
86
  //#endregion
81
87
  export { getCookieKey, parseCookies, serializeCookie, serializeSignedCookie };
@@ -1 +1 @@
1
- {"version":3,"file":"cookies.mjs","names":[],"sources":["../src/cookies.ts"],"sourcesContent":["import { signCookieValue } from \"./crypto\";\nimport { tryDecode } from \"./utils\";\n\nexport type CookiePrefixOptions = \"host\" | \"secure\";\n\nexport type CookieOptions = {\n\t/**\n\t * Domain of the cookie\n\t *\n\t * The Domain attribute specifies which server can receive a cookie. If specified, cookies are\n\t * available on the specified server and its subdomains. If the it is not\n\t * specified, the cookies are available on the server that sets it but not on\n\t * its subdomains.\n\t *\n\t * @example\n\t * `domain: \"example.com\"`\n\t */\n\tdomain?: string;\n\t/**\n\t * A lifetime of a cookie. Permanent cookies are deleted after the date specified in the\n\t * Expires attribute:\n\t *\n\t * Expires has been available for longer than Max-Age, however Max-Age is less error-prone, and\n\t * takes precedence when both are set. The rationale behind this is that when you set an\n\t * Expires date and time, they're relative to the client the cookie is being set on. If the\n\t * server is set to a different time, this could cause errors\n\t */\n\texpires?: Date;\n\t/**\n\t * Forbids JavaScript from accessing the cookie, for example, through the Document.cookie\n\t * property. Note that a cookie that has been created with HttpOnly will still be sent with\n\t * JavaScript-initiated requests, for example, when calling XMLHttpRequest.send() or fetch().\n\t * This mitigates attacks against cross-site scripting\n\t */\n\thttpOnly?: boolean;\n\t/**\n\t * Indicates the number of seconds until the cookie expires. A zero or negative number will\n\t * expire the cookie immediately. If both Expires and Max-Age are set, Max-Age has precedence.\n\t *\n\t * @example 604800 - 7 days\n\t */\n\tmaxAge?: number;\n\t/**\n\t * Indicates the path that must exist in the requested URL for the browser to send the Cookie\n\t * header.\n\t *\n\t * @example\n\t * \"/docs\"\n\t * // -> the request paths /docs, /docs/, /docs/Web/, and /docs/Web/HTTP will all match. the request paths /, /fr/docs will not match.\n\t */\n\tpath?: string;\n\t/**\n\t * Indicates that the cookie is sent to the server only when a request is made with the https:\n\t * scheme (except on localhost), and therefore, is more resistant to man-in-the-middle attacks.\n\t */\n\tsecure?: boolean;\n\t/**\n\t * Controls whether or not a cookie is sent with cross-site requests, providing some protection\n\t * against cross-site request forgery attacks (CSRF).\n\t *\n\t * Strict - Means that the browser sends the cookie only for same-site requests, that is,\n\t * requests originating from the same site that set the cookie. If a request originates from a\n\t * different domain or scheme (even with the same domain), no cookies with the SameSite=Strict\n\t * attribute are sent.\n\t *\n\t * Lax - Means that the cookie is not sent on cross-site requests, such as on requests to load\n\t * images or frames, but is sent when a user is navigating to the origin site from an external\n\t * site (for example, when following a link). This is the default behavior if the SameSite\n\t * attribute is not specified.\n\t *\n\t * None - Means that the browser sends the cookie with both cross-site and same-site requests.\n\t * The Secure attribute must also be set when setting this value.\n\t */\n\tsameSite?: \"Strict\" | \"Lax\" | \"None\" | \"strict\" | \"lax\" | \"none\";\n\t/**\n\t * Indicates that the cookie should be stored using partitioned storage. Note that if this is\n\t * set, the Secure directive must also be set.\n\t *\n\t * @see https://developer.mozilla.org/en-US/docs/Web/Privacy/Privacy_sandbox/Partitioned_cookies\n\t */\n\tpartitioned?: boolean;\n\t/**\n\t * Cooke Prefix\n\t *\n\t * - secure: `__Secure-` -> `__Secure-cookie-name`\n\t * - host: `__Host-` -> `__Host-cookie-name`\n\t *\n\t * `secure` must be set to true to use prefixes\n\t */\n\tprefix?: CookiePrefixOptions;\n};\n\nexport const getCookieKey = (key: string, prefix?: CookiePrefixOptions) => {\n\tlet finalKey = key;\n\tif (prefix) {\n\t\tif (prefix === \"secure\") {\n\t\t\tfinalKey = \"__Secure-\" + key;\n\t\t} else if (prefix === \"host\") {\n\t\t\tfinalKey = \"__Host-\" + key;\n\t\t} else {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\treturn finalKey;\n};\n\n/**\n * Parse an HTTP Cookie header string and returning an object of all cookie\n * name-value pairs.\n *\n * Inspired by https://github.com/unjs/cookie-es/blob/main/src/cookie/parse.ts\n *\n * @param str the string representing a `Cookie` header value\n */\nexport function parseCookies(str: string) {\n\tif (typeof str !== \"string\") {\n\t\tthrow new TypeError(\"argument str must be a string\");\n\t}\n\n\tconst cookies: Map<string, string> = new Map();\n\n\tlet index = 0;\n\twhile (index < str.length) {\n\t\tconst eqIdx = str.indexOf(\"=\", index);\n\n\t\tif (eqIdx === -1) {\n\t\t\tbreak;\n\t\t}\n\n\t\tlet endIdx = str.indexOf(\";\", index);\n\n\t\tif (endIdx === -1) {\n\t\t\tendIdx = str.length;\n\t\t} else if (endIdx < eqIdx) {\n\t\t\tindex = str.lastIndexOf(\";\", eqIdx - 1) + 1;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst key = str.slice(index, eqIdx).trim();\n\t\tif (!cookies.has(key)) {\n\t\t\tlet val = str.slice(eqIdx + 1, endIdx).trim();\n\t\t\tif (val.codePointAt(0) === 0x22) {\n\t\t\t\tval = val.slice(1, -1);\n\t\t\t}\n\t\t\tcookies.set(key, tryDecode(val));\n\t\t}\n\n\t\tindex = endIdx + 1;\n\t}\n\n\treturn cookies;\n}\n\nconst _serialize = (key: string, value: string, opt: CookieOptions = {}) => {\n\tlet cookie: string;\n\n\tif (opt?.prefix === \"secure\") {\n\t\tcookie = `${`__Secure-${key}`}=${value}`;\n\t} else if (opt?.prefix === \"host\") {\n\t\tcookie = `${`__Host-${key}`}=${value}`;\n\t} else {\n\t\tcookie = `${key}=${value}`;\n\t}\n\n\tif (key.startsWith(\"__Secure-\") && !opt.secure) {\n\t\topt.secure = true;\n\t}\n\n\tif (key.startsWith(\"__Host-\")) {\n\t\tif (!opt.secure) {\n\t\t\topt.secure = true;\n\t\t}\n\n\t\tif (opt.path !== \"/\") {\n\t\t\topt.path = \"/\";\n\t\t}\n\n\t\tif (opt.domain) {\n\t\t\topt.domain = undefined;\n\t\t}\n\t}\n\n\tif (opt && typeof opt.maxAge === \"number\" && opt.maxAge >= 0) {\n\t\tif (opt.maxAge > 34560000) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Cookies Max-Age SHOULD NOT be greater than 400 days (34560000 seconds) in duration.\",\n\t\t\t);\n\t\t}\n\t\tcookie += `; Max-Age=${Math.floor(opt.maxAge)}`;\n\t}\n\n\tif (opt.domain && opt.prefix !== \"host\") {\n\t\tcookie += `; Domain=${opt.domain}`;\n\t}\n\n\tif (opt.path) {\n\t\tcookie += `; Path=${opt.path}`;\n\t}\n\n\tif (opt.expires) {\n\t\tif (opt.expires.getTime() - Date.now() > 34560000_000) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Cookies Expires SHOULD NOT be greater than 400 days (34560000 seconds) in the future.\",\n\t\t\t);\n\t\t}\n\t\tcookie += `; Expires=${opt.expires.toUTCString()}`;\n\t}\n\n\tif (opt.httpOnly) {\n\t\tcookie += \"; HttpOnly\";\n\t}\n\n\tif (opt.secure) {\n\t\tcookie += \"; Secure\";\n\t}\n\n\tif (opt.sameSite) {\n\t\tcookie += `; SameSite=${opt.sameSite.charAt(0).toUpperCase() + opt.sameSite.slice(1)}`;\n\t}\n\n\tif (opt.partitioned) {\n\t\tif (!opt.secure) {\n\t\t\topt.secure = true;\n\t\t}\n\t\tcookie += \"; Partitioned\";\n\t}\n\n\treturn cookie;\n};\n\nexport const serializeCookie = (\n\tkey: string,\n\tvalue: string,\n\topt?: CookieOptions,\n) => {\n\tvalue = encodeURIComponent(value);\n\treturn _serialize(key, value, opt);\n};\n\nexport const serializeSignedCookie = async (\n\tkey: string,\n\tvalue: string,\n\tsecret: string,\n\topt?: CookieOptions,\n) => {\n\tvalue = await signCookieValue(value, secret);\n\treturn _serialize(key, value, opt);\n};\n"],"mappings":";;;AA4FA,MAAa,gBAAgB,KAAa,WAAiC;CAC1E,IAAI,WAAW;CACf,IAAI,QACH,IAAI,WAAW,UACd,WAAW,cAAc;MACnB,IAAI,WAAW,QACrB,WAAW,YAAY;MAEvB;CAGF,OAAO;AACR;;;;;;;;;AAUA,SAAgB,aAAa,KAAa;CACzC,IAAI,OAAO,QAAQ,UAClB,MAAM,IAAI,UAAU,+BAA+B;CAGpD,MAAM,0BAA+B,IAAI,IAAI;CAE7C,IAAI,QAAQ;CACZ,OAAO,QAAQ,IAAI,QAAQ;EAC1B,MAAM,QAAQ,IAAI,QAAQ,KAAK,KAAK;EAEpC,IAAI,UAAU,IACb;EAGD,IAAI,SAAS,IAAI,QAAQ,KAAK,KAAK;EAEnC,IAAI,WAAW,IACd,SAAS,IAAI;OACP,IAAI,SAAS,OAAO;GAC1B,QAAQ,IAAI,YAAY,KAAK,QAAQ,CAAC,IAAI;GAC1C;EACD;EAEA,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC,KAAK;EACzC,IAAI,CAAC,QAAQ,IAAI,GAAG,GAAG;GACtB,IAAI,MAAM,IAAI,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,KAAK;GAC5C,IAAI,IAAI,YAAY,CAAC,MAAM,IAC1B,MAAM,IAAI,MAAM,GAAG,EAAE;GAEtB,QAAQ,IAAI,KAAK,UAAU,GAAG,CAAC;EAChC;EAEA,QAAQ,SAAS;CAClB;CAEA,OAAO;AACR;AAEA,MAAM,cAAc,KAAa,OAAe,MAAqB,CAAC,MAAM;CAC3E,IAAI;CAEJ,IAAI,KAAK,WAAW,UACnB,SAAS,GAAG,YAAY,MAAM,GAAG;MAC3B,IAAI,KAAK,WAAW,QAC1B,SAAS,GAAG,UAAU,MAAM,GAAG;MAE/B,SAAS,GAAG,IAAI,GAAG;CAGpB,IAAI,IAAI,WAAW,WAAW,KAAK,CAAC,IAAI,QACvC,IAAI,SAAS;CAGd,IAAI,IAAI,WAAW,SAAS,GAAG;EAC9B,IAAI,CAAC,IAAI,QACR,IAAI,SAAS;EAGd,IAAI,IAAI,SAAS,KAChB,IAAI,OAAO;EAGZ,IAAI,IAAI,QACP,IAAI,SAAS,KAAA;CAEf;CAEA,IAAI,OAAO,OAAO,IAAI,WAAW,YAAY,IAAI,UAAU,GAAG;EAC7D,IAAI,IAAI,SAAS,QAChB,MAAM,IAAI,MACT,qFACD;EAED,UAAU,aAAa,KAAK,MAAM,IAAI,MAAM;CAC7C;CAEA,IAAI,IAAI,UAAU,IAAI,WAAW,QAChC,UAAU,YAAY,IAAI;CAG3B,IAAI,IAAI,MACP,UAAU,UAAU,IAAI;CAGzB,IAAI,IAAI,SAAS;EAChB,IAAI,IAAI,QAAQ,QAAQ,IAAI,KAAK,IAAI,IAAI,QACxC,MAAM,IAAI,MACT,uFACD;EAED,UAAU,aAAa,IAAI,QAAQ,YAAY;CAChD;CAEA,IAAI,IAAI,UACP,UAAU;CAGX,IAAI,IAAI,QACP,UAAU;CAGX,IAAI,IAAI,UACP,UAAU,cAAc,IAAI,SAAS,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,IAAI,SAAS,MAAM,CAAC;CAGpF,IAAI,IAAI,aAAa;EACpB,IAAI,CAAC,IAAI,QACR,IAAI,SAAS;EAEd,UAAU;CACX;CAEA,OAAO;AACR;AAEA,MAAa,mBACZ,KACA,OACA,QACI;CACJ,QAAQ,mBAAmB,KAAK;CAChC,OAAO,WAAW,KAAK,OAAO,GAAG;AAClC;AAEA,MAAa,wBAAwB,OACpC,KACA,OACA,QACA,QACI;CACJ,QAAQ,MAAM,gBAAgB,OAAO,MAAM;CAC3C,OAAO,WAAW,KAAK,OAAO,GAAG;AAClC"}
1
+ {"version":3,"file":"cookies.mjs","names":[],"sources":["../src/cookies.ts"],"sourcesContent":["import { signCookieValue } from \"./crypto\";\nimport { tryDecode } from \"./utils\";\n\nexport type CookiePrefixOptions = \"host\" | \"secure\";\n\nconst COOKIE_PREFIXES = {\n\tsecure: \"__Secure-\",\n\thost: \"__Host-\",\n} as const satisfies Record<CookiePrefixOptions, string>;\n\nexport type CookieOptions = {\n\t/**\n\t * Domain of the cookie\n\t *\n\t * The Domain attribute specifies which server can receive a cookie. If specified, cookies are\n\t * available on the specified server and its subdomains. If the it is not\n\t * specified, the cookies are available on the server that sets it but not on\n\t * its subdomains.\n\t *\n\t * @example\n\t * `domain: \"example.com\"`\n\t */\n\tdomain?: string;\n\t/**\n\t * A lifetime of a cookie. Permanent cookies are deleted after the date specified in the\n\t * Expires attribute:\n\t *\n\t * Expires has been available for longer than Max-Age, however Max-Age is less error-prone, and\n\t * takes precedence when both are set. The rationale behind this is that when you set an\n\t * Expires date and time, they're relative to the client the cookie is being set on. If the\n\t * server is set to a different time, this could cause errors\n\t */\n\texpires?: Date;\n\t/**\n\t * Forbids JavaScript from accessing the cookie, for example, through the Document.cookie\n\t * property. Note that a cookie that has been created with HttpOnly will still be sent with\n\t * JavaScript-initiated requests, for example, when calling XMLHttpRequest.send() or fetch().\n\t * This mitigates attacks against cross-site scripting\n\t */\n\thttpOnly?: boolean;\n\t/**\n\t * Indicates the number of seconds until the cookie expires. A zero or negative number will\n\t * expire the cookie immediately. If both Expires and Max-Age are set, Max-Age has precedence.\n\t *\n\t * @example 604800 - 7 days\n\t */\n\tmaxAge?: number;\n\t/**\n\t * Indicates the path that must exist in the requested URL for the browser to send the Cookie\n\t * header.\n\t *\n\t * @example\n\t * \"/docs\"\n\t * // -> the request paths /docs, /docs/, /docs/Web/, and /docs/Web/HTTP will all match. the request paths /, /fr/docs will not match.\n\t */\n\tpath?: string;\n\t/**\n\t * Indicates that the cookie is sent to the server only when a request is made with the https:\n\t * scheme (except on localhost), and therefore, is more resistant to man-in-the-middle attacks.\n\t */\n\tsecure?: boolean;\n\t/**\n\t * Controls whether or not a cookie is sent with cross-site requests, providing some protection\n\t * against cross-site request forgery attacks (CSRF).\n\t *\n\t * Strict - Means that the browser sends the cookie only for same-site requests, that is,\n\t * requests originating from the same site that set the cookie. If a request originates from a\n\t * different domain or scheme (even with the same domain), no cookies with the SameSite=Strict\n\t * attribute are sent.\n\t *\n\t * Lax - Means that the cookie is not sent on cross-site requests, such as on requests to load\n\t * images or frames, but is sent when a user is navigating to the origin site from an external\n\t * site (for example, when following a link). This is the default behavior if the SameSite\n\t * attribute is not specified.\n\t *\n\t * None - Means that the browser sends the cookie with both cross-site and same-site requests.\n\t * The Secure attribute must also be set when setting this value.\n\t */\n\tsameSite?: \"Strict\" | \"Lax\" | \"None\" | \"strict\" | \"lax\" | \"none\";\n\t/**\n\t * Indicates that the cookie should be stored using partitioned storage. Note that if this is\n\t * set, the Secure directive must also be set.\n\t *\n\t * @see https://developer.mozilla.org/en-US/docs/Web/Privacy/Privacy_sandbox/Partitioned_cookies\n\t */\n\tpartitioned?: boolean;\n\t/**\n\t * Cooke Prefix\n\t *\n\t * - secure: `__Secure-` -> `__Secure-cookie-name`\n\t * - host: `__Host-` -> `__Host-cookie-name`\n\t *\n\t * Required Secure, Path, and Domain attributes are applied automatically.\n\t */\n\tprefix?: CookiePrefixOptions;\n};\n\ntype Cookie = {\n\tname: string;\n\tvalue: string;\n\toptions: CookieOptions;\n};\n\nexport const getCookieKey = (key: string, prefix?: CookiePrefixOptions) => {\n\tif (!prefix) return key;\n\tif (prefix !== \"secure\" && prefix !== \"host\") return undefined;\n\treturn COOKIE_PREFIXES[prefix] + key;\n};\n\n/**\n * Parse an HTTP Cookie header string and returning an object of all cookie\n * name-value pairs.\n *\n * Inspired by https://github.com/unjs/cookie-es/blob/main/src/cookie/parse.ts\n *\n * @param str the string representing a `Cookie` header value\n */\nexport function parseCookies(str: string) {\n\tif (typeof str !== \"string\") {\n\t\tthrow new TypeError(\"argument str must be a string\");\n\t}\n\n\tconst cookies: Map<string, string> = new Map();\n\n\tlet index = 0;\n\twhile (index < str.length) {\n\t\tconst eqIdx = str.indexOf(\"=\", index);\n\n\t\tif (eqIdx === -1) {\n\t\t\tbreak;\n\t\t}\n\n\t\tlet endIdx = str.indexOf(\";\", index);\n\n\t\tif (endIdx === -1) {\n\t\t\tendIdx = str.length;\n\t\t} else if (endIdx < eqIdx) {\n\t\t\tindex = str.lastIndexOf(\";\", eqIdx - 1) + 1;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst key = str.slice(index, eqIdx).trim();\n\t\tif (!cookies.has(key)) {\n\t\t\tlet val = str.slice(eqIdx + 1, endIdx).trim();\n\t\t\tif (val.codePointAt(0) === 0x22) {\n\t\t\t\tval = val.slice(1, -1);\n\t\t\t}\n\t\t\tcookies.set(key, tryDecode(val));\n\t\t}\n\n\t\tindex = endIdx + 1;\n\t}\n\n\treturn cookies;\n}\n\nfunction resolveCookie(\n\tname: string,\n\tvalue: string,\n\toptions: CookieOptions = {},\n): Cookie {\n\tconst resolvedName = getCookieKey(name, options.prefix) ?? name;\n\tconst resolvedOptions = { ...options };\n\n\tconst lowerName = resolvedName.toLowerCase();\n\tif (lowerName.startsWith(\"__host-\")) {\n\t\tresolvedOptions.secure = true;\n\t\tresolvedOptions.path = \"/\";\n\t\tresolvedOptions.domain = undefined;\n\t} else if (lowerName.startsWith(\"__secure-\")) {\n\t\tresolvedOptions.secure = true;\n\t}\n\tif (\n\t\tresolvedOptions.partitioned === true ||\n\t\tresolvedOptions.sameSite?.toLowerCase() === \"none\"\n\t) {\n\t\tresolvedOptions.secure = true;\n\t}\n\n\treturn { name: resolvedName, value, options: resolvedOptions };\n}\n\nfunction serializeCookieValue({ name, value, options }: Cookie) {\n\tlet cookie = `${name}=${value}`;\n\tif (typeof options.maxAge === \"number\" && options.maxAge >= 0) {\n\t\tif (options.maxAge > 34560000) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Cookies Max-Age SHOULD NOT be greater than 400 days (34560000 seconds) in duration.\",\n\t\t\t);\n\t\t}\n\t\tcookie += `; Max-Age=${Math.floor(options.maxAge)}`;\n\t}\n\n\tif (options.domain) {\n\t\tcookie += `; Domain=${options.domain}`;\n\t}\n\n\tif (options.path) {\n\t\tcookie += `; Path=${options.path}`;\n\t}\n\n\tif (options.expires) {\n\t\tif (options.expires.getTime() - Date.now() > 34560000_000) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Cookies Expires SHOULD NOT be greater than 400 days (34560000 seconds) in the future.\",\n\t\t\t);\n\t\t}\n\t\tcookie += `; Expires=${options.expires.toUTCString()}`;\n\t}\n\n\tif (options.httpOnly) {\n\t\tcookie += \"; HttpOnly\";\n\t}\n\n\tif (options.secure) {\n\t\tcookie += \"; Secure\";\n\t}\n\n\tif (options.sameSite) {\n\t\tcookie += `; SameSite=${options.sameSite.charAt(0).toUpperCase() + options.sameSite.slice(1)}`;\n\t}\n\n\tif (options.partitioned) {\n\t\tcookie += \"; Partitioned\";\n\t}\n\n\treturn cookie;\n}\n\nexport const serializeCookie = (\n\tkey: string,\n\tvalue: string,\n\topt?: CookieOptions,\n) => {\n\tvalue = encodeURIComponent(value);\n\treturn serializeCookieValue(resolveCookie(key, value, opt));\n};\n\nexport const serializeSignedCookie = async (\n\tkey: string,\n\tvalue: string,\n\tsecret: string,\n\topt?: CookieOptions,\n) => {\n\tvalue = await signCookieValue(value, secret);\n\treturn serializeCookieValue(resolveCookie(key, value, opt));\n};\n"],"mappings":";;;AAKA,MAAM,kBAAkB;CACvB,QAAQ;CACR,MAAM;AACP;AA+FA,MAAa,gBAAgB,KAAa,WAAiC;CAC1E,IAAI,CAAC,QAAQ,OAAO;CACpB,IAAI,WAAW,YAAY,WAAW,QAAQ,OAAO,KAAA;CACrD,OAAO,gBAAgB,UAAU;AAClC;;;;;;;;;AAUA,SAAgB,aAAa,KAAa;CACzC,IAAI,OAAO,QAAQ,UAClB,MAAM,IAAI,UAAU,+BAA+B;CAGpD,MAAM,0BAA+B,IAAI,IAAI;CAE7C,IAAI,QAAQ;CACZ,OAAO,QAAQ,IAAI,QAAQ;EAC1B,MAAM,QAAQ,IAAI,QAAQ,KAAK,KAAK;EAEpC,IAAI,UAAU,IACb;EAGD,IAAI,SAAS,IAAI,QAAQ,KAAK,KAAK;EAEnC,IAAI,WAAW,IACd,SAAS,IAAI;OACP,IAAI,SAAS,OAAO;GAC1B,QAAQ,IAAI,YAAY,KAAK,QAAQ,CAAC,IAAI;GAC1C;EACD;EAEA,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC,KAAK;EACzC,IAAI,CAAC,QAAQ,IAAI,GAAG,GAAG;GACtB,IAAI,MAAM,IAAI,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,KAAK;GAC5C,IAAI,IAAI,YAAY,CAAC,MAAM,IAC1B,MAAM,IAAI,MAAM,GAAG,EAAE;GAEtB,QAAQ,IAAI,KAAK,UAAU,GAAG,CAAC;EAChC;EAEA,QAAQ,SAAS;CAClB;CAEA,OAAO;AACR;AAEA,SAAS,cACR,MACA,OACA,UAAyB,CAAC,GACjB;CACT,MAAM,eAAe,aAAa,MAAM,QAAQ,MAAM,KAAK;CAC3D,MAAM,kBAAkB,EAAE,GAAG,QAAQ;CAErC,MAAM,YAAY,aAAa,YAAY;CAC3C,IAAI,UAAU,WAAW,SAAS,GAAG;EACpC,gBAAgB,SAAS;EACzB,gBAAgB,OAAO;EACvB,gBAAgB,SAAS,KAAA;CAC1B,OAAO,IAAI,UAAU,WAAW,WAAW,GAC1C,gBAAgB,SAAS;CAE1B,IACC,gBAAgB,gBAAgB,QAChC,gBAAgB,UAAU,YAAY,MAAM,QAE5C,gBAAgB,SAAS;CAG1B,OAAO;EAAE,MAAM;EAAc;EAAO,SAAS;CAAgB;AAC9D;AAEA,SAAS,qBAAqB,EAAE,MAAM,OAAO,WAAmB;CAC/D,IAAI,SAAS,GAAG,KAAK,GAAG;CACxB,IAAI,OAAO,QAAQ,WAAW,YAAY,QAAQ,UAAU,GAAG;EAC9D,IAAI,QAAQ,SAAS,QACpB,MAAM,IAAI,MACT,qFACD;EAED,UAAU,aAAa,KAAK,MAAM,QAAQ,MAAM;CACjD;CAEA,IAAI,QAAQ,QACX,UAAU,YAAY,QAAQ;CAG/B,IAAI,QAAQ,MACX,UAAU,UAAU,QAAQ;CAG7B,IAAI,QAAQ,SAAS;EACpB,IAAI,QAAQ,QAAQ,QAAQ,IAAI,KAAK,IAAI,IAAI,QAC5C,MAAM,IAAI,MACT,uFACD;EAED,UAAU,aAAa,QAAQ,QAAQ,YAAY;CACpD;CAEA,IAAI,QAAQ,UACX,UAAU;CAGX,IAAI,QAAQ,QACX,UAAU;CAGX,IAAI,QAAQ,UACX,UAAU,cAAc,QAAQ,SAAS,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,QAAQ,SAAS,MAAM,CAAC;CAG5F,IAAI,QAAQ,aACX,UAAU;CAGX,OAAO;AACR;AAEA,MAAa,mBACZ,KACA,OACA,QACI;CACJ,QAAQ,mBAAmB,KAAK;CAChC,OAAO,qBAAqB,cAAc,KAAK,OAAO,GAAG,CAAC;AAC3D;AAEA,MAAa,wBAAwB,OACpC,KACA,OACA,QACA,QACI;CACJ,QAAQ,MAAM,gBAAgB,OAAO,MAAM;CAC3C,OAAO,qBAAqB,cAAc,KAAK,OAAO,GAAG,CAAC;AAC3D"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-call",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",