UNPKG

4.5 kBJavaScriptView Raw
1/**
2 * react-router v8.3.0
3 *
4 * Copyright (c) Remix Software Inc.
5 *
6 * This source code is licensed under the MIT license found in the
7 * LICENSE.md file in the root directory of this source tree.
8 *
9 * @license MIT
10 */
11import { warnOnce } from "./warnings.js";
12import { sign, unsign } from "./crypto.js";
13import { parse, serialize } from "cookie-es";
14//#region lib/server-runtime/cookies.ts
15/**
16* Creates a logical container for managing a browser cookie from the server.
17*
18* @public
19* @category Utils
20* @mode framework
21* @mode data
22* @param name The name of the cookie.
23* @param cookieOptions Options for parsing and serializing the cookie.
24* @returns A {@link Cookie} object for parsing and serializing the cookie.
25*/
26const createCookie = (name, cookieOptions = {}) => {
27 let { secrets = [], ...options } = {
28 path: "/",
29 sameSite: "lax",
30 ...cookieOptions
31 };
32 warnOnceAboutExpiresCookie(name, options.expires);
33 return {
34 get name() {
35 return name;
36 },
37 get isSigned() {
38 return secrets.length > 0;
39 },
40 get expires() {
41 return typeof options.maxAge !== "undefined" ? new Date(Date.now() + options.maxAge * 1e3) : options.expires;
42 },
43 async parse(cookieHeader, parseOptions) {
44 if (!cookieHeader) return null;
45 let cookies = parse(cookieHeader, {
46 ...options,
47 ...parseOptions
48 });
49 if (name in cookies) {
50 let value = cookies[name];
51 if (typeof value === "string" && value !== "") return await decodeCookieValue(value, secrets);
52 else return "";
53 } else return null;
54 },
55 async serialize(value, serializeOptions) {
56 return serialize(name, value === "" ? "" : await encodeCookieValue(value, secrets), {
57 ...options,
58 ...serializeOptions
59 });
60 }
61 };
62};
63/**
64* Returns `true` if a value is a React Router {@link Cookie} object.
65*
66* @public
67* @category Utils
68* @mode framework
69* @mode data
70* @param object The value to check.
71* @returns `true` if the value is a React Router {@link Cookie} object;
72* otherwise, `false`.
73*/
74const isCookie = (object) => {
75 return object != null && typeof object.name === "string" && typeof object.isSigned === "boolean" && typeof object.parse === "function" && typeof object.serialize === "function";
76};
77async function encodeCookieValue(value, secrets) {
78 let encoded = encodeData(value);
79 if (secrets.length > 0) encoded = await sign(encoded, secrets[0]);
80 return encoded;
81}
82async function decodeCookieValue(value, secrets) {
83 if (secrets.length > 0) {
84 for (let secret of secrets) {
85 let unsignedValue = await unsign(value, secret);
86 if (unsignedValue !== false) return decodeData(unsignedValue);
87 }
88 return null;
89 }
90 return decodeData(value);
91}
92function encodeData(value) {
93 return btoa(myUnescape(encodeURIComponent(JSON.stringify(value))));
94}
95function decodeData(value) {
96 try {
97 return JSON.parse(decodeURIComponent(myEscape(atob(value))));
98 } catch (e) {
99 return {};
100 }
101}
102function myEscape(value) {
103 let str = value.toString();
104 let result = "";
105 let index = 0;
106 let chr, code;
107 while (index < str.length) {
108 chr = str.charAt(index++);
109 if (/[\w*+\-./@]/.exec(chr)) result += chr;
110 else {
111 code = chr.charCodeAt(0);
112 if (code < 256) result += "%" + hex(code, 2);
113 else result += "%u" + hex(code, 4).toUpperCase();
114 }
115 }
116 return result;
117}
118function hex(code, length) {
119 let result = code.toString(16);
120 while (result.length < length) result = "0" + result;
121 return result;
122}
123function myUnescape(value) {
124 let str = value.toString();
125 let result = "";
126 let index = 0;
127 let chr, part;
128 while (index < str.length) {
129 chr = str.charAt(index++);
130 if (chr === "%") if (str.charAt(index) === "u") {
131 part = str.slice(index + 1, index + 5);
132 if (/^[\da-f]{4}$/i.exec(part)) {
133 result += String.fromCharCode(parseInt(part, 16));
134 index += 5;
135 continue;
136 }
137 } else {
138 part = str.slice(index, index + 2);
139 if (/^[\da-f]{2}$/i.exec(part)) {
140 result += String.fromCharCode(parseInt(part, 16));
141 index += 2;
142 continue;
143 }
144 }
145 result += chr;
146 }
147 return result;
148}
149function warnOnceAboutExpiresCookie(name, expires) {
150 warnOnce(!expires, `The "${name}" cookie has an "expires" property set. This will cause the expires value to not be updated when the session is committed. Instead, you should set the expires value when serializing the cookie. You can use \`commitSession(session, { expires })\` if using a session storage object, or \`cookie.serialize("value", { expires })\` if you're using the cookie directly.`);
151}
152//#endregion
153export { createCookie, isCookie };