var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
import { extend } from './utils';
import { DISTINCT_ID, ENABLE_PERSON_PROCESSING, INITIAL_PERSON_INFO, SESSION_ID, SESSION_RECORDING_IS_SAMPLED, } from './constants';
import { isNull, isUndefined } from './utils/type-utils';
import { logger } from './utils/logger';
import { window, document } from './utils/globals';
import { uuidv7 } from './uuidv7';
var Y1970 = 'Thu, 01 Jan 1970 00:00:00 GMT';
// we store the discovered subdomain in memory because it might be read multiple times
var firstNonPublicSubDomain = '';
// helper to allow tests to clear this "cache"
export var resetSubDomainCache = function () {
firstNonPublicSubDomain = '';
};
/**
* Browsers don't offer a way to check if something is a public suffix
* e.g. `.com.au`, `.io`, `.org.uk`
*
* But they do reject cookies set on public suffixes
* Setting a cookie on `.co.uk` would mean it was sent for every `.co.uk` site visited
*
* So, we can use this to check if a domain is a public suffix
* by trying to set a cookie on a subdomain of the provided hostname
* until the browser accepts it
*
* inspired by https://github.com/AngusFu/browser-root-domain
*/
export function seekFirstNonPublicSubDomain(hostname, cookieJar) {
if (cookieJar === void 0) { cookieJar = document; }
if (firstNonPublicSubDomain) {
return firstNonPublicSubDomain;
}
if (!cookieJar) {
return '';
}
if (['localhost', '127.0.0.1'].includes(hostname))
return '';
var list = hostname.split('.');
var len = Math.min(list.length, 8); // paranoia - we know this number should be small
var key = 'dmn_chk_' + uuidv7();
var R = new RegExp('(^|;)\\s*' + key + '=1');
while (!firstNonPublicSubDomain && len--) {
var candidate = list.slice(len).join('.');
var candidateCookieValue = key + '=1;domain=.' + candidate;
// try to set cookie
cookieJar.cookie = candidateCookieValue;
if (R.test(cookieJar.cookie)) {
// the cookie was accepted by the browser, remove the test cookie
cookieJar.cookie = candidateCookieValue + ';expires=' + Y1970;
firstNonPublicSubDomain = candidate;
}
}
return firstNonPublicSubDomain;
}
var DOMAIN_MATCH_REGEX = /[a-z0-9][a-z0-9-]+\.[a-z]{2,}$/i;
var originalCookieDomainFn = function (hostname) {
var matches = hostname.match(DOMAIN_MATCH_REGEX);
return matches ? matches[0] : '';
};
export function chooseCookieDomain(hostname, cross_subdomain) {
if (cross_subdomain) {
// NOTE: Could we use this for cross domain tracking?
var matchedSubDomain = seekFirstNonPublicSubDomain(hostname);
if (!matchedSubDomain) {
var originalMatch = originalCookieDomainFn(hostname);
if (originalMatch !== matchedSubDomain) {
logger.info('Warning: cookie subdomain discovery mismatch', originalMatch, matchedSubDomain);
}
matchedSubDomain = originalMatch;
}
return matchedSubDomain ? '; domain=.' + matchedSubDomain : '';
}
return '';
}
// Methods partially borrowed from quirksmode.org/js/cookies.html
export var cookieStore = {
is_supported: function () { return !!document; },
error: function (msg) {
logger.error('cookieStore error: ' + msg);
},
get: function (name) {
if (!document) {
return;
}
try {
var nameEQ = name + '=';
var ca = document.cookie.split(';').filter(function (x) { return x.length; });
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1, c.length);
}
if (c.indexOf(nameEQ) === 0) {
return decodeURIComponent(c.substring(nameEQ.length, c.length));
}
}
}
catch (_a) { }
return null;
},
parse: function (name) {
var cookie;
try {
cookie = JSON.parse(cookieStore.get(name)) || {};
}
catch (_a) {
// noop
}
return cookie;
},
set: function (name, value, days, cross_subdomain, is_secure) {
if (!document) {
return;
}
try {
var expires = '', secure = '';
var cdomain = chooseCookieDomain(document.location.hostname, cross_subdomain);
if (days) {
var date = new Date();
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
expires = '; expires=' + date.toUTCString();
}
if (is_secure) {
secure = '; secure';
}
var new_cookie_val = name +
'=' +
encodeURIComponent(JSON.stringify(value)) +
expires +
'; SameSite=Lax; path=/' +
cdomain +
secure;
// 4096 bytes is the size at which some browsers (e.g. firefox) will not store a cookie, warn slightly before that
if (new_cookie_val.length > 4096 * 0.9) {
logger.warn('cookieStore warning: large cookie, len=' + new_cookie_val.length);
}
document.cookie = new_cookie_val;
return new_cookie_val;
}
catch (_a) {
return;
}
},
remove: function (name, cross_subdomain) {
try {
cookieStore.set(name, '', -1, cross_subdomain);
}
catch (_a) {
return;
}
},
};
var _localStorage_supported = null;
export var localStore = {
is_supported: function () {
if (!isNull(_localStorage_supported)) {
return _localStorage_supported;
}
var supported = true;
if (!isUndefined(window)) {
try {
var key = '__mplssupport__', val = 'xyz';
localStore.set(key, val);
if (localStore.get(key) !== '"xyz"') {
supported = false;
}
localStore.remove(key);
}
catch (_a) {
supported = false;
}
}
else {
supported = false;
}
if (!supported) {
logger.error('localStorage unsupported; falling back to cookie store');
}
_localStorage_supported = supported;
return supported;
},
error: function (msg) {
logger.error('localStorage error: ' + msg);
},
get: function (name) {
try {
return window === null || window === void 0 ? void 0 : window.localStorage.getItem(name);
}
catch (err) {
localStore.error(err);
}
return null;
},
parse: function (name) {
try {
return JSON.parse(localStore.get(name)) || {};
}
catch (_a) {
// noop
}
return null;
},
set: function (name, value) {
try {
window === null || window === void 0 ? void 0 : window.localStorage.setItem(name, JSON.stringify(value));
}
catch (err) {
localStore.error(err);
}
},
remove: function (name) {
try {
window === null || window === void 0 ? void 0 : window.localStorage.removeItem(name);
}
catch (err) {
localStore.error(err);
}
},
};
// Use localstorage for most data but still use cookie for COOKIE_PERSISTED_PROPERTIES
// This solves issues with cookies having too much data in them causing headers too large
// Also makes sure we don't have to send a ton of data to the server
var COOKIE_PERSISTED_PROPERTIES = [
DISTINCT_ID,
SESSION_ID,
SESSION_RECORDING_IS_SAMPLED,
ENABLE_PERSON_PROCESSING,
INITIAL_PERSON_INFO,
];
export var localPlusCookieStore = __assign(__assign({}, localStore), { parse: function (name) {
try {
var cookieProperties = {};
try {
// See if there's a cookie stored with data.
cookieProperties = cookieStore.parse(name) || {};
}
catch (_a) { }
var value = extend(cookieProperties, JSON.parse(localStore.get(name) || '{}'));
localStore.set(name, value);
return value;
}
catch (_b) {
// noop
}
return null;
}, set: function (name, value, days, cross_subdomain, is_secure, debug) {
try {
localStore.set(name, value, undefined, undefined, debug);
var cookiePersistedProperties_1 = {};
COOKIE_PERSISTED_PROPERTIES.forEach(function (key) {
if (value[key]) {
cookiePersistedProperties_1[key] = value[key];
}
});
if (Object.keys(cookiePersistedProperties_1).length) {
cookieStore.set(name, cookiePersistedProperties_1, days, cross_subdomain, is_secure, debug);
}
}
catch (err) {
localStore.error(err);
}
}, remove: function (name, cross_subdomain) {
try {
window === null || window === void 0 ? void 0 : window.localStorage.removeItem(name);
cookieStore.remove(name, cross_subdomain);
}
catch (err) {
localStore.error(err);
}
} });
var memoryStorage = {};
// Storage that only lasts the length of the pageview if we don't want to use cookies
export var memoryStore = {
is_supported: function () {
return true;
},
error: function (msg) {
logger.error('memoryStorage error: ' + msg);
},
get: function (name) {
return memoryStorage[name] || null;
},
parse: function (name) {
return memoryStorage[name] || null;
},
set: function (name, value) {
memoryStorage[name] = value;
},
remove: function (name) {
delete memoryStorage[name];
},
};
var sessionStorageSupported = null;
export var resetSessionStorageSupported = function () {
sessionStorageSupported = null;
};
// Storage that only lasts the length of a tab/window. Survives page refreshes
export var sessionStore = {
is_supported: function () {
if (!isNull(sessionStorageSupported)) {
return sessionStorageSupported;
}
sessionStorageSupported = true;
if (!isUndefined(window)) {
try {
var key = '__support__', val = 'xyz';
sessionStore.set(key, val);
if (sessionStore.get(key) !== '"xyz"') {
sessionStorageSupported = false;
}
sessionStore.remove(key);
}
catch (_a) {
sessionStorageSupported = false;
}
}
else {
sessionStorageSupported = false;
}
return sessionStorageSupported;
},
error: function (msg) {
logger.error('sessionStorage error: ', msg);
},
get: function (name) {
try {
return window === null || window === void 0 ? void 0 : window.sessionStorage.getItem(name);
}
catch (err) {
sessionStore.error(err);
}
return null;
},
parse: function (name) {
try {
return JSON.parse(sessionStore.get(name)) || null;
}
catch (_a) {
// noop
}
return null;
},
set: function (name, value) {
try {
window === null || window === void 0 ? void 0 : window.sessionStorage.setItem(name, JSON.stringify(value));
}
catch (err) {
sessionStore.error(err);
}
},
remove: function (name) {
try {
window === null || window === void 0 ? void 0 : window.sessionStorage.removeItem(name);
}
catch (err) {
sessionStore.error(err);
}
},
};
//# sourceMappingURL=storage.js.map