415 lines
16 KiB
JavaScript
415 lines
16 KiB
JavaScript
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
typeof define === 'function' && define.amd ? define(factory) :
|
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.UniversalCookie = factory());
|
|
})(this, (function () { 'use strict';
|
|
|
|
var dist = {};
|
|
|
|
var hasRequiredDist;
|
|
|
|
function requireDist () {
|
|
if (hasRequiredDist) return dist;
|
|
hasRequiredDist = 1;
|
|
Object.defineProperty(dist, "__esModule", { value: true });
|
|
dist.parse = parse;
|
|
dist.serialize = serialize;
|
|
/**
|
|
* RegExp to match cookie-name in RFC 6265 sec 4.1.1
|
|
* This refers out to the obsoleted definition of token in RFC 2616 sec 2.2
|
|
* which has been replaced by the token definition in RFC 7230 appendix B.
|
|
*
|
|
* cookie-name = token
|
|
* token = 1*tchar
|
|
* tchar = "!" / "#" / "$" / "%" / "&" / "'" /
|
|
* "*" / "+" / "-" / "." / "^" / "_" /
|
|
* "`" / "|" / "~" / DIGIT / ALPHA
|
|
*
|
|
* Note: Allowing more characters - https://github.com/jshttp/cookie/issues/191
|
|
* Allow same range as cookie value, except `=`, which delimits end of name.
|
|
*/
|
|
const cookieNameRegExp = /^[\u0021-\u003A\u003C\u003E-\u007E]+$/;
|
|
/**
|
|
* RegExp to match cookie-value in RFC 6265 sec 4.1.1
|
|
*
|
|
* cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
|
|
* cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
|
|
* ; US-ASCII characters excluding CTLs,
|
|
* ; whitespace DQUOTE, comma, semicolon,
|
|
* ; and backslash
|
|
*
|
|
* Allowing more characters: https://github.com/jshttp/cookie/issues/191
|
|
* Comma, backslash, and DQUOTE are not part of the parsing algorithm.
|
|
*/
|
|
const cookieValueRegExp = /^[\u0021-\u003A\u003C-\u007E]*$/;
|
|
/**
|
|
* RegExp to match domain-value in RFC 6265 sec 4.1.1
|
|
*
|
|
* domain-value = <subdomain>
|
|
* ; defined in [RFC1034], Section 3.5, as
|
|
* ; enhanced by [RFC1123], Section 2.1
|
|
* <subdomain> = <label> | <subdomain> "." <label>
|
|
* <label> = <let-dig> [ [ <ldh-str> ] <let-dig> ]
|
|
* Labels must be 63 characters or less.
|
|
* 'let-dig' not 'letter' in the first char, per RFC1123
|
|
* <ldh-str> = <let-dig-hyp> | <let-dig-hyp> <ldh-str>
|
|
* <let-dig-hyp> = <let-dig> | "-"
|
|
* <let-dig> = <letter> | <digit>
|
|
* <letter> = any one of the 52 alphabetic characters A through Z in
|
|
* upper case and a through z in lower case
|
|
* <digit> = any one of the ten digits 0 through 9
|
|
*
|
|
* Keep support for leading dot: https://github.com/jshttp/cookie/issues/173
|
|
*
|
|
* > (Note that a leading %x2E ("."), if present, is ignored even though that
|
|
* character is not permitted, but a trailing %x2E ("."), if present, will
|
|
* cause the user agent to ignore the attribute.)
|
|
*/
|
|
const domainValueRegExp = /^([.]?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)([.][a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i;
|
|
/**
|
|
* RegExp to match path-value in RFC 6265 sec 4.1.1
|
|
*
|
|
* path-value = <any CHAR except CTLs or ";">
|
|
* CHAR = %x01-7F
|
|
* ; defined in RFC 5234 appendix B.1
|
|
*/
|
|
const pathValueRegExp = /^[\u0020-\u003A\u003D-\u007E]*$/;
|
|
const __toString = Object.prototype.toString;
|
|
const NullObject = /* @__PURE__ */ (() => {
|
|
const C = function () { };
|
|
C.prototype = Object.create(null);
|
|
return C;
|
|
})();
|
|
/**
|
|
* Parse a cookie header.
|
|
*
|
|
* Parse the given cookie header string into an object
|
|
* The object has the various cookies as keys(names) => values
|
|
*/
|
|
function parse(str, options) {
|
|
const obj = new NullObject();
|
|
const len = str.length;
|
|
// RFC 6265 sec 4.1.1, RFC 2616 2.2 defines a cookie name consists of one char minimum, plus '='.
|
|
if (len < 2)
|
|
return obj;
|
|
const dec = options?.decode || decode;
|
|
let index = 0;
|
|
do {
|
|
const eqIdx = str.indexOf("=", index);
|
|
if (eqIdx === -1)
|
|
break; // No more cookie pairs.
|
|
const colonIdx = str.indexOf(";", index);
|
|
const endIdx = colonIdx === -1 ? len : colonIdx;
|
|
if (eqIdx > endIdx) {
|
|
// backtrack on prior semicolon
|
|
index = str.lastIndexOf(";", eqIdx - 1) + 1;
|
|
continue;
|
|
}
|
|
const keyStartIdx = startIndex(str, index, eqIdx);
|
|
const keyEndIdx = endIndex(str, eqIdx, keyStartIdx);
|
|
const key = str.slice(keyStartIdx, keyEndIdx);
|
|
// only assign once
|
|
if (obj[key] === undefined) {
|
|
let valStartIdx = startIndex(str, eqIdx + 1, endIdx);
|
|
let valEndIdx = endIndex(str, endIdx, valStartIdx);
|
|
const value = dec(str.slice(valStartIdx, valEndIdx));
|
|
obj[key] = value;
|
|
}
|
|
index = endIdx + 1;
|
|
} while (index < len);
|
|
return obj;
|
|
}
|
|
function startIndex(str, index, max) {
|
|
do {
|
|
const code = str.charCodeAt(index);
|
|
if (code !== 0x20 /* */ && code !== 0x09 /* \t */)
|
|
return index;
|
|
} while (++index < max);
|
|
return max;
|
|
}
|
|
function endIndex(str, index, min) {
|
|
while (index > min) {
|
|
const code = str.charCodeAt(--index);
|
|
if (code !== 0x20 /* */ && code !== 0x09 /* \t */)
|
|
return index + 1;
|
|
}
|
|
return min;
|
|
}
|
|
/**
|
|
* Serialize data into a cookie header.
|
|
*
|
|
* Serialize a name value pair into a cookie string suitable for
|
|
* http headers. An optional options object specifies cookie parameters.
|
|
*
|
|
* serialize('foo', 'bar', { httpOnly: true })
|
|
* => "foo=bar; httpOnly"
|
|
*/
|
|
function serialize(name, val, options) {
|
|
const enc = options?.encode || encodeURIComponent;
|
|
if (!cookieNameRegExp.test(name)) {
|
|
throw new TypeError(`argument name is invalid: ${name}`);
|
|
}
|
|
const value = enc(val);
|
|
if (!cookieValueRegExp.test(value)) {
|
|
throw new TypeError(`argument val is invalid: ${val}`);
|
|
}
|
|
let str = name + "=" + value;
|
|
if (!options)
|
|
return str;
|
|
if (options.maxAge !== undefined) {
|
|
if (!Number.isInteger(options.maxAge)) {
|
|
throw new TypeError(`option maxAge is invalid: ${options.maxAge}`);
|
|
}
|
|
str += "; Max-Age=" + options.maxAge;
|
|
}
|
|
if (options.domain) {
|
|
if (!domainValueRegExp.test(options.domain)) {
|
|
throw new TypeError(`option domain is invalid: ${options.domain}`);
|
|
}
|
|
str += "; Domain=" + options.domain;
|
|
}
|
|
if (options.path) {
|
|
if (!pathValueRegExp.test(options.path)) {
|
|
throw new TypeError(`option path is invalid: ${options.path}`);
|
|
}
|
|
str += "; Path=" + options.path;
|
|
}
|
|
if (options.expires) {
|
|
if (!isDate(options.expires) ||
|
|
!Number.isFinite(options.expires.valueOf())) {
|
|
throw new TypeError(`option expires is invalid: ${options.expires}`);
|
|
}
|
|
str += "; Expires=" + options.expires.toUTCString();
|
|
}
|
|
if (options.httpOnly) {
|
|
str += "; HttpOnly";
|
|
}
|
|
if (options.secure) {
|
|
str += "; Secure";
|
|
}
|
|
if (options.partitioned) {
|
|
str += "; Partitioned";
|
|
}
|
|
if (options.priority) {
|
|
const priority = typeof options.priority === "string"
|
|
? options.priority.toLowerCase()
|
|
: undefined;
|
|
switch (priority) {
|
|
case "low":
|
|
str += "; Priority=Low";
|
|
break;
|
|
case "medium":
|
|
str += "; Priority=Medium";
|
|
break;
|
|
case "high":
|
|
str += "; Priority=High";
|
|
break;
|
|
default:
|
|
throw new TypeError(`option priority is invalid: ${options.priority}`);
|
|
}
|
|
}
|
|
if (options.sameSite) {
|
|
const sameSite = typeof options.sameSite === "string"
|
|
? options.sameSite.toLowerCase()
|
|
: options.sameSite;
|
|
switch (sameSite) {
|
|
case true:
|
|
case "strict":
|
|
str += "; SameSite=Strict";
|
|
break;
|
|
case "lax":
|
|
str += "; SameSite=Lax";
|
|
break;
|
|
case "none":
|
|
str += "; SameSite=None";
|
|
break;
|
|
default:
|
|
throw new TypeError(`option sameSite is invalid: ${options.sameSite}`);
|
|
}
|
|
}
|
|
return str;
|
|
}
|
|
/**
|
|
* URL-decode string value. Optimized to skip native call when no %.
|
|
*/
|
|
function decode(str) {
|
|
if (str.indexOf("%") === -1)
|
|
return str;
|
|
try {
|
|
return decodeURIComponent(str);
|
|
}
|
|
catch (e) {
|
|
return str;
|
|
}
|
|
}
|
|
/**
|
|
* Determine if value is a Date.
|
|
*/
|
|
function isDate(val) {
|
|
return __toString.call(val) === "[object Date]";
|
|
}
|
|
|
|
return dist;
|
|
}
|
|
|
|
var distExports = requireDist();
|
|
|
|
function hasDocumentCookie() {
|
|
const testingValue = typeof global === 'undefined'
|
|
? undefined
|
|
: global.TEST_HAS_DOCUMENT_COOKIE;
|
|
if (typeof testingValue === 'boolean') {
|
|
return testingValue;
|
|
}
|
|
// Can we get/set cookies on document.cookie?
|
|
return typeof document === 'object' && typeof document.cookie === 'string';
|
|
}
|
|
function parseCookies(cookies) {
|
|
if (typeof cookies === 'string') {
|
|
return distExports.parse(cookies);
|
|
}
|
|
else if (typeof cookies === 'object' && cookies !== null) {
|
|
return cookies;
|
|
}
|
|
else {
|
|
return {};
|
|
}
|
|
}
|
|
function readCookie(value, options = {}) {
|
|
const cleanValue = cleanupCookieValue(value);
|
|
if (!options.doNotParse) {
|
|
try {
|
|
return JSON.parse(cleanValue);
|
|
}
|
|
catch (e) {
|
|
// At least we tried
|
|
}
|
|
}
|
|
// Ignore clean value if we failed the deserialization
|
|
// It is not relevant anymore to trim those values
|
|
return value;
|
|
}
|
|
function cleanupCookieValue(value) {
|
|
// express prepend j: before serializing a cookie
|
|
if (value && value[0] === 'j' && value[1] === ':') {
|
|
return value.substr(2);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
class Cookies {
|
|
constructor(cookies, defaultSetOptions = {}) {
|
|
this.changeListeners = [];
|
|
this.HAS_DOCUMENT_COOKIE = false;
|
|
this.update = () => {
|
|
if (!this.HAS_DOCUMENT_COOKIE) {
|
|
return;
|
|
}
|
|
const previousCookies = this.cookies;
|
|
this.cookies = distExports.parse(document.cookie);
|
|
this._checkChanges(previousCookies);
|
|
};
|
|
const domCookies = typeof document === 'undefined' ? '' : document.cookie;
|
|
this.cookies = parseCookies(cookies || domCookies);
|
|
this.defaultSetOptions = defaultSetOptions;
|
|
this.HAS_DOCUMENT_COOKIE = hasDocumentCookie();
|
|
}
|
|
_emitChange(params) {
|
|
for (let i = 0; i < this.changeListeners.length; ++i) {
|
|
this.changeListeners[i](params);
|
|
}
|
|
}
|
|
_checkChanges(previousCookies) {
|
|
const names = new Set(Object.keys(previousCookies).concat(Object.keys(this.cookies)));
|
|
names.forEach((name) => {
|
|
if (previousCookies[name] !== this.cookies[name]) {
|
|
this._emitChange({
|
|
name,
|
|
value: readCookie(this.cookies[name]),
|
|
});
|
|
}
|
|
});
|
|
}
|
|
_startPolling() {
|
|
this.pollingInterval = setInterval(this.update, 300);
|
|
}
|
|
_stopPolling() {
|
|
if (this.pollingInterval) {
|
|
clearInterval(this.pollingInterval);
|
|
}
|
|
}
|
|
get(name, options = {}) {
|
|
if (!options.doNotUpdate) {
|
|
this.update();
|
|
}
|
|
return readCookie(this.cookies[name], options);
|
|
}
|
|
getAll(options = {}) {
|
|
if (!options.doNotUpdate) {
|
|
this.update();
|
|
}
|
|
const result = {};
|
|
for (let name in this.cookies) {
|
|
result[name] = readCookie(this.cookies[name], options);
|
|
}
|
|
return result;
|
|
}
|
|
set(name, value, options) {
|
|
if (options) {
|
|
options = Object.assign(Object.assign({}, this.defaultSetOptions), options);
|
|
}
|
|
else {
|
|
options = this.defaultSetOptions;
|
|
}
|
|
const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
|
|
this.cookies = Object.assign(Object.assign({}, this.cookies), { [name]: stringValue });
|
|
if (this.HAS_DOCUMENT_COOKIE) {
|
|
document.cookie = distExports.serialize(name, stringValue, options);
|
|
}
|
|
this._emitChange({ name, value, options });
|
|
}
|
|
remove(name, options) {
|
|
const finalOptions = (options = Object.assign(Object.assign(Object.assign({}, this.defaultSetOptions), options), { expires: new Date(1970, 1, 1, 0, 0, 1), maxAge: 0 }));
|
|
this.cookies = Object.assign({}, this.cookies);
|
|
delete this.cookies[name];
|
|
if (this.HAS_DOCUMENT_COOKIE) {
|
|
document.cookie = distExports.serialize(name, '', finalOptions);
|
|
}
|
|
this._emitChange({ name, value: undefined, options });
|
|
}
|
|
addChangeListener(callback) {
|
|
this.changeListeners.push(callback);
|
|
if (this.HAS_DOCUMENT_COOKIE && this.changeListeners.length === 1) {
|
|
if (typeof window === 'object' && 'cookieStore' in window) {
|
|
window.cookieStore.addEventListener('change', this.update);
|
|
}
|
|
else {
|
|
this._startPolling();
|
|
}
|
|
}
|
|
}
|
|
removeChangeListener(callback) {
|
|
const idx = this.changeListeners.indexOf(callback);
|
|
if (idx >= 0) {
|
|
this.changeListeners.splice(idx, 1);
|
|
}
|
|
if (this.HAS_DOCUMENT_COOKIE && this.changeListeners.length === 0) {
|
|
if (typeof window === 'object' && 'cookieStore' in window) {
|
|
window.cookieStore.removeEventListener('change', this.update);
|
|
}
|
|
else {
|
|
this._stopPolling();
|
|
}
|
|
}
|
|
}
|
|
removeAllChangeListeners() {
|
|
while (this.changeListeners.length > 0) {
|
|
this.removeChangeListener(this.changeListeners[0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Cookies;
|
|
|
|
}));
|