| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 | |
| 6 | |
| 7 | |
| 8 | |
| 9 | |
| 10 |
|
| 11 | import { warnOnce } from "./warnings.js";
|
| 12 | import { sign, unsign } from "./crypto.js";
|
| 13 | import { parse, serialize } from "cookie-es";
|
| 14 |
|
| 15 | |
| 16 | |
| 17 |
|
| 18 | const createCookie = (name, cookieOptions = {}) => {
|
| 19 | let { secrets = [], ...options } = {
|
| 20 | path: "/",
|
| 21 | sameSite: "lax",
|
| 22 | ...cookieOptions
|
| 23 | };
|
| 24 | warnOnceAboutExpiresCookie(name, options.expires);
|
| 25 | return {
|
| 26 | get name() {
|
| 27 | return name;
|
| 28 | },
|
| 29 | get isSigned() {
|
| 30 | return secrets.length > 0;
|
| 31 | },
|
| 32 | get expires() {
|
| 33 | return typeof options.maxAge !== "undefined" ? new Date(Date.now() + options.maxAge * 1e3) : options.expires;
|
| 34 | },
|
| 35 | async parse(cookieHeader, parseOptions) {
|
| 36 | if (!cookieHeader) return null;
|
| 37 | let cookies = parse(cookieHeader, {
|
| 38 | ...options,
|
| 39 | ...parseOptions
|
| 40 | });
|
| 41 | if (name in cookies) {
|
| 42 | let value = cookies[name];
|
| 43 | if (typeof value === "string" && value !== "") return await decodeCookieValue(value, secrets);
|
| 44 | else return "";
|
| 45 | } else return null;
|
| 46 | },
|
| 47 | async serialize(value, serializeOptions) {
|
| 48 | return serialize(name, value === "" ? "" : await encodeCookieValue(value, secrets), {
|
| 49 | ...options,
|
| 50 | ...serializeOptions
|
| 51 | });
|
| 52 | }
|
| 53 | };
|
| 54 | };
|
| 55 | |
| 56 | |
| 57 | |
| 58 | |
| 59 |
|
| 60 | const isCookie = (object) => {
|
| 61 | return object != null && typeof object.name === "string" && typeof object.isSigned === "boolean" && typeof object.parse === "function" && typeof object.serialize === "function";
|
| 62 | };
|
| 63 | async function encodeCookieValue(value, secrets) {
|
| 64 | let encoded = encodeData(value);
|
| 65 | if (secrets.length > 0) encoded = await sign(encoded, secrets[0]);
|
| 66 | return encoded;
|
| 67 | }
|
| 68 | async function decodeCookieValue(value, secrets) {
|
| 69 | if (secrets.length > 0) {
|
| 70 | for (let secret of secrets) {
|
| 71 | let unsignedValue = await unsign(value, secret);
|
| 72 | if (unsignedValue !== false) return decodeData(unsignedValue);
|
| 73 | }
|
| 74 | return null;
|
| 75 | }
|
| 76 | return decodeData(value);
|
| 77 | }
|
| 78 | function encodeData(value) {
|
| 79 | return btoa(myUnescape(encodeURIComponent(JSON.stringify(value))));
|
| 80 | }
|
| 81 | function decodeData(value) {
|
| 82 | try {
|
| 83 | return JSON.parse(decodeURIComponent(myEscape(atob(value))));
|
| 84 | } catch (e) {
|
| 85 | return {};
|
| 86 | }
|
| 87 | }
|
| 88 | function myEscape(value) {
|
| 89 | let str = value.toString();
|
| 90 | let result = "";
|
| 91 | let index = 0;
|
| 92 | let chr, code;
|
| 93 | while (index < str.length) {
|
| 94 | chr = str.charAt(index++);
|
| 95 | if (/[\w*+\-./@]/.exec(chr)) result += chr;
|
| 96 | else {
|
| 97 | code = chr.charCodeAt(0);
|
| 98 | if (code < 256) result += "%" + hex(code, 2);
|
| 99 | else result += "%u" + hex(code, 4).toUpperCase();
|
| 100 | }
|
| 101 | }
|
| 102 | return result;
|
| 103 | }
|
| 104 | function hex(code, length) {
|
| 105 | let result = code.toString(16);
|
| 106 | while (result.length < length) result = "0" + result;
|
| 107 | return result;
|
| 108 | }
|
| 109 | function myUnescape(value) {
|
| 110 | let str = value.toString();
|
| 111 | let result = "";
|
| 112 | let index = 0;
|
| 113 | let chr, part;
|
| 114 | while (index < str.length) {
|
| 115 | chr = str.charAt(index++);
|
| 116 | if (chr === "%") if (str.charAt(index) === "u") {
|
| 117 | part = str.slice(index + 1, index + 5);
|
| 118 | if (/^[\da-f]{4}$/i.exec(part)) {
|
| 119 | result += String.fromCharCode(parseInt(part, 16));
|
| 120 | index += 5;
|
| 121 | continue;
|
| 122 | }
|
| 123 | } else {
|
| 124 | part = str.slice(index, index + 2);
|
| 125 | if (/^[\da-f]{2}$/i.exec(part)) {
|
| 126 | result += String.fromCharCode(parseInt(part, 16));
|
| 127 | index += 2;
|
| 128 | continue;
|
| 129 | }
|
| 130 | }
|
| 131 | result += chr;
|
| 132 | }
|
| 133 | return result;
|
| 134 | }
|
| 135 | function warnOnceAboutExpiresCookie(name, expires) {
|
| 136 | 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.`);
|
| 137 | }
|
| 138 |
|
| 139 | export { createCookie, isCookie };
|