@remix-run/server-runtime
Version:
Server runtime for Remix
174 lines (166 loc) • 5.12 kB
JavaScript
/**
* @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
*/
;
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;