UNPKG

@remix-run/server-runtime

Version:
174 lines (166 loc) 5.12 kB
/** * @remix-run/server-runtime v2.16.8 * * Copyright (c) Remix Software Inc. * * This source code is licensed under the MIT license found in the * LICENSE.md file in the root directory of this source tree. * * @license MIT */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var cookie = require('cookie'); var warnings = require('./warnings.js'); /** * A HTTP cookie. * * A Cookie is a logical container for metadata about a HTTP cookie; its name * and options. But it doesn't contain a value. Instead, it has `parse()` and * `serialize()` methods that allow a single instance to be reused for * parsing/encoding multiple different values. * * @see https://remix.run/utils/cookies#cookie-api */ /** * Creates a logical container for managing a browser cookie from the server. * * @see https://remix.run/utils/cookies#createcookie */ const createCookieFactory = ({ sign, unsign }) => (name, cookieOptions = {}) => { let { secrets = [], ...options } = { path: "/", sameSite: "lax", ...cookieOptions }; warnOnceAboutExpiresCookie(name, options.expires); return { get name() { return name; }, get isSigned() { return secrets.length > 0; }, get expires() { // Max-Age takes precedence over Expires return typeof options.maxAge !== "undefined" ? new Date(Date.now() + options.maxAge * 1000) : options.expires; }, async parse(cookieHeader, parseOptions) { if (!cookieHeader) return null; let cookies = cookie.parse(cookieHeader, { ...options, ...parseOptions }); return name in cookies ? cookies[name] === "" ? "" : await decodeCookieValue(unsign, cookies[name], secrets) : null; }, async serialize(value, serializeOptions) { return cookie.serialize(name, value === "" ? "" : await encodeCookieValue(sign, value, secrets), { ...options, ...serializeOptions }); } }; }; /** * Returns true if an object is a Remix cookie container. * * @see https://remix.run/utils/cookies#iscookie */ const isCookie = object => { return object != null && typeof object.name === "string" && typeof object.isSigned === "boolean" && typeof object.parse === "function" && typeof object.serialize === "function"; }; async function encodeCookieValue(sign, value, secrets) { let encoded = encodeData(value); if (secrets.length > 0) { encoded = await sign(encoded, secrets[0]); } return encoded; } async function decodeCookieValue(unsign, value, secrets) { if (secrets.length > 0) { for (let secret of secrets) { let unsignedValue = await unsign(value, secret); if (unsignedValue !== false) { return decodeData(unsignedValue); } } return null; } return decodeData(value); } function encodeData(value) { return btoa(myUnescape(encodeURIComponent(JSON.stringify(value)))); } function decodeData(value) { try { return JSON.parse(decodeURIComponent(myEscape(atob(value)))); } catch (error) { return {}; } } // See: https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/es.escape.js function myEscape(value) { let str = value.toString(); let result = ""; let index = 0; let chr, code; while (index < str.length) { chr = str.charAt(index++); if (/[\w*+\-./@]/.exec(chr)) { result += chr; } else { code = chr.charCodeAt(0); if (code < 256) { result += "%" + hex(code, 2); } else { result += "%u" + hex(code, 4).toUpperCase(); } } } return result; } function hex(code, length) { let result = code.toString(16); while (result.length < length) result = "0" + result; return result; } // See: https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/es.unescape.js function myUnescape(value) { let str = value.toString(); let result = ""; let index = 0; let chr, part; while (index < str.length) { chr = str.charAt(index++); if (chr === "%") { if (str.charAt(index) === "u") { part = str.slice(index + 1, index + 5); if (/^[\da-f]{4}$/i.exec(part)) { result += String.fromCharCode(parseInt(part, 16)); index += 5; continue; } } else { part = str.slice(index, index + 2); if (/^[\da-f]{2}$/i.exec(part)) { result += String.fromCharCode(parseInt(part, 16)); index += 2; continue; } } } result += chr; } return result; } function warnOnceAboutExpiresCookie(name, expires) { warnings.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.`); } exports.createCookieFactory = createCookieFactory; exports.isCookie = isCookie;