posthog-js
Version:
Posthog-js allows you to automatically capture usage and send events to PostHog.
1,720 lines • 90.1 kB
JavaScript
"use strict";
/* eslint camelcase: "off" */
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);
};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
var __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PostHogPersistence = void 0;
var general_utils_1 = require("@posthog/browser-common/utils/general-utils");
var storage_1 = require("./storage");
var globals_1 = require("@posthog/browser-common/utils/globals");
var constants_1 = require("./constants");
var persistence_key_policy_1 = require("./persistence-key-policy");
// The "freshness" key each split group stamps when its server payload is
// (re)loaded. A group entry carrying an older timestamp than the main blob is a
// stale orphan (a gate-off / older-SDK tab wrote a fresher payload back to main)
// and must not win on load. Groups without an entry here have no freshness
// signal, so the group entry wins by default (the migrated-forward home).
var VOLATILE_FINGERPRINT_PLACEHOLDER = '__volatile__';
// Both freshness keys are volatile (their on-disk value lags the last content
// change). That is safe: when only the stamp moved, both the group entry and the
// main blob hold identical content, so whichever side wins still produces the
// same values. A mixed-fleet race at most causes one extra /flags cycle.
var GROUP_FRESHNESS_KEY = {
flags: constants_1.PERSISTENCE_FEATURE_FLAG_EVALUATED_AT,
surveys: constants_1.SURVEYS_LOADED_AT,
};
var core_1 = require("@posthog/core");
var event_utils_1 = require("@posthog/browser-common/utils/event-utils");
var logger_1 = require("@posthog/browser-common/utils/logger");
var core_2 = require("@posthog/core");
var CASE_INSENSITIVE_PERSISTENCE_TYPES = [
'cookie',
'localstorage',
'localstorage+cookie',
'sessionstorage',
'memory',
];
var getCookieIdentityChangePendingName = function (name) { return "".concat(name, "_cookie_identity_change_pending"); };
var parseName = function (config) {
var token = '';
if (config['token']) {
token = config['token'].replace(/\+/g, 'PL').replace(/\//g, 'SL').replace(/=/g, 'EQ');
}
if (config['persistence_name']) {
return 'ph_' + config['persistence_name'];
}
else {
return 'ph_' + token + '_posthog';
}
};
// Fingerprint slot for the main persistence entry. Group entries (`flags`)
// use their group name as the slot. See `_writeEntry`.
var MAIN_STORAGE_SLOT = 'main';
// Feature flag evaluation state is shared by every same-origin tab. Keep these
// keys synchronized so a stale tab cannot overwrite an enrollment or cached
// flag update when it next writes the persistence blob.
var CROSS_TAB_FEATURE_FLAG_KEYS = [
constants_1.ENABLED_FEATURE_FLAGS,
constants_1.PERSISTENCE_ACTIVE_FEATURE_FLAGS,
constants_1.PERSISTENCE_FEATURE_FLAG_DETAILS,
constants_1.PERSISTENCE_FEATURE_FLAG_PAYLOADS,
constants_1.PERSISTENCE_FEATURE_FLAG_REQUEST_ID,
constants_1.PERSISTENCE_FEATURE_FLAG_EVALUATED_AT,
constants_1.PERSISTENCE_MINIMAL_FLAG_CALLED_EVENTS,
constants_1.STORED_PERSON_PROPERTIES_KEY,
];
var isCrossTabFeatureFlagKey = function (key) {
return CROSS_TAB_FEATURE_FLAG_KEYS.indexOf(key) !== -1;
};
var isStorageValueEqual = function (left, right) {
try {
return JSON.stringify(left) === JSON.stringify(right);
}
catch (_a) {
return left === right;
}
};
var parseStorageValue = function (value) {
if (!value) {
return {};
}
var parsed = JSON.parse(value);
return (0, core_2.isObject)(parsed) ? parsed : {};
};
var isArrayContentsEqual = function (arr1, arr2) {
if (arr1.length !== arr2.length) {
return false;
}
var sortedArr1 = __spreadArray([], __read(arr1), false).sort();
var sortedArr2 = __spreadArray([], __read(arr2), false).sort();
return sortedArr1.every(function (item, index) { return item === sortedArr2[index]; });
};
var clearStaleEventProperties = function (props, cookieProperties) {
(0, general_utils_1.each)(props, function (_value, key) {
var policy = (0, persistence_key_policy_1.getPersistenceKeyPolicy)(key);
if ((!policy || policy.exposure === 'event') && !Object.prototype.hasOwnProperty.call(cookieProperties, key)) {
delete props[key];
}
});
};
/**
* PostHog Persistence Object
* @constructor
*/
var PostHogPersistence = /** @class */ (function () {
/**
* @param {PostHogConfig} config initial PostHog configuration
* @param {boolean=} isDisabled should persistence be disabled (e.g. because of consent management)
*/
function PostHogPersistence(config, isDisabled, ownsSplitStorage) {
if (ownsSplitStorage === void 0) { ownsSplitStorage = true; }
var _this = this;
// Per-storage-entry write bookkeeping, keyed by slot (`main` plus each group
// name): no-op-rejection fingerprint, per-group dirty flag, and on-disk
// materialization. Reset wholesale on remove()/clear() so a save after
// remove always lands. See `SlotWriteState`.
this._slotState = {};
// Whether the resolved storage backend can host the split (localStorage /
// localStorage+cookie). Set by `_buildStorage`.
this._splitStorageEligible = false;
// Whether flag config is stored in their own entries this session:
// backend-eligible AND `split_storage` enabled.
// Re-resolved on every `update_config` (backend rebuild or a runtime flag flip).
this._splitStorage = false;
// Identity changes can be adopted by background persistence writes before
// capture runs. Keep the transition pending until core performs the flag and
// person-cache side effects associated with the new identity.
this._cookieIdentityChangePending = false;
// A local reset or storage migration owns the next cookie snapshot. Ignore
// sibling writes until the complete replacement has been published.
this._cookieSyncSuppressed = false;
// Nested feature-flag entries changed locally and waiting for a durable
// write. Sibling updates to other flags can still be merged while pending.
this._pendingCrossTabFeatureFlagChanges = new Map();
this._storageMigrationInProgress = false;
this._localIdentityChangePending = false;
this._crossTabFeatureFlagIdentityMismatch = false;
this._crossTabFeatureFlagHandlers = new Set();
this._config = config;
this._ownsSplitStorage = ownsSplitStorage;
this.props = {};
this._campaign_params_url = undefined;
this._name = parseName(config);
this._storage = this._buildStorage(config);
this._splitStorage = this._resolveSplitStorage(config);
this.load();
// Preserve only values for which load() selected a fresher source than
// the current storage entry. Ordinary loaded values remain mergeable.
this._markLoadedCrossTabFeatureFlagChangesPending();
if (config.debug) {
logger_1.logger.info('Persistence loaded', config['persistence'], __assign({}, this.props));
}
this.update_config(config, config, isDisabled);
this.save();
// Install unload flush listeners unconditionally. They are a no-op
// when no debounced write is pending (see `flush()`), so it is safe
// to install even when `persistence_save_debounce_ms` is 0 at
// construction. Crucially this also handles `posthog.set_config({
// persistence_save_debounce_ms: 250 })` enabling debounce later —
// we'd otherwise miss the listener install and lose pending writes
// on close.
if (globals_1.window) {
var flush = function () { return _this.flush(); };
(0, general_utils_1.addEventListener)(globals_1.window, 'beforeunload', flush, { capture: false });
(0, general_utils_1.addEventListener)(globals_1.window, 'pagehide', flush, { capture: false });
this._onStorage = function (event) {
if (!_this._splitStorageEligible ||
(event.storageArea && event.storageArea !== (globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.localStorage)) ||
!event.key) {
return;
}
if (event.key === _this._name) {
_this._syncCrossTabFeatureFlagProperties(event.key, MAIN_STORAGE_SLOT);
return;
}
if (_this._splitStorage) {
var group = persistence_key_policy_1.PERSISTENCE_STORAGE_GROUPS.find(function (group) { return event.key === _this._groupEntryName(group); });
if (group) {
_this._syncCrossTabFeatureFlagProperties(event.key, group);
}
}
};
(0, general_utils_1.addEventListener)(globals_1.window, 'storage', this._onStorage);
}
}
PostHogPersistence.prototype.markCrossTabFeatureFlagChanges = function (changes) {
var _this = this;
Object.entries(changes).forEach(function (_a) {
var _b = __read(_a, 2), key = _b[0], properties = _b[1];
var existingPendingChanges = _this._pendingCrossTabFeatureFlagChanges.get(key);
if (!isCrossTabFeatureFlagKey(key) || existingPendingChanges === true) {
return;
}
if (properties === true) {
_this._setCrossTabFeatureFlagChangesPending(key, true);
return;
}
var pendingChanges = new Set(existingPendingChanges || []);
properties.forEach(function (property) { return pendingChanges.add(property); });
if (pendingChanges.size) {
_this._setCrossTabFeatureFlagChangesPending(key, pendingChanges);
}
});
};
PostHogPersistence.prototype.onCrossTabFeatureFlagChange = function (handler) {
var _this = this;
this._crossTabFeatureFlagHandlers.add(handler);
return function () { return _this._crossTabFeatureFlagHandlers.delete(handler); };
};
PostHogPersistence.prototype.destroy = function () {
if (this._onStorage && globals_1.window) {
globals_1.window.removeEventListener('storage', this._onStorage);
this._onStorage = undefined;
}
this._crossTabFeatureFlagHandlers.clear();
};
PostHogPersistence.prototype._syncCrossTabFeatureFlagProperties = function (storageKey, slot, notify) {
if (notify === void 0) { notify = true; }
if (this._disabled) {
return false;
}
var nextEntry;
var storageValue;
try {
// A queued storage event can be older than the value currently on disk.
// Always reconcile against the latest snapshot instead of event.newValue.
storageValue = storage_1.localStore._get(storageKey);
if ((0, core_1.isNull)(storageValue)) {
var state = this._slotWriteState(slot);
state.storageValue = null;
if (slot !== MAIN_STORAGE_SLOT) {
state.persisted = false;
}
return false;
}
nextEntry = parseStorageValue(storageValue);
}
catch (_a) {
return false;
}
var identityEntry = slot === MAIN_STORAGE_SLOT ? nextEntry : storage_1.localStore._parse(this._name);
if (identityEntry && this._hasCrossTabFeatureFlagIdentityMismatch(identityEntry)) {
return false;
}
var changed = this._mergeCrossTabFeatureFlagProperties(nextEntry, slot, notify);
if (slot !== MAIN_STORAGE_SLOT ||
!this._splitStorage ||
isStorageValueEqual(this._partitionProps().main, nextEntry)) {
this._rememberCrossTabStorageFingerprint(nextEntry, slot, true, storageValue);
}
return changed;
};
PostHogPersistence.prototype._hasCrossTabFeatureFlagIdentityMismatch = function (entry) {
var localDistinctId = this.props[constants_1.DISTINCT_ID];
var persistedDistinctId = entry[constants_1.DISTINCT_ID];
return (!(0, core_1.isUndefined)(localDistinctId) &&
!(0, core_1.isUndefined)(persistedDistinctId) &&
localDistinctId !== persistedDistinctId);
};
PostHogPersistence.prototype._rememberCrossTabStorageFingerprint = function (entry, slot, materialized, storageValue) {
var state = this._slotWriteState(slot);
state.storageValue = storageValue;
if (slot !== MAIN_STORAGE_SLOT) {
state.persisted = materialized;
}
try {
state.fingerprint = this._entryFingerprint(entry, slot);
}
catch (_a) {
state.fingerprint = undefined;
}
};
PostHogPersistence.prototype._mergeCrossTabFeatureFlagProperties = function (nextEntry, slot, notify) {
var _this = this;
var changed = false;
CROSS_TAB_FEATURE_FLAG_KEYS.forEach(function (prop) {
var _a;
var group = (_a = (0, persistence_key_policy_1.getPersistenceKeyPolicy)(prop)) === null || _a === void 0 ? void 0 : _a.storageGroup;
if ((slot === MAIN_STORAGE_SLOT && _this._splitStorage && group) ||
(slot !== MAIN_STORAGE_SLOT && group !== slot)) {
return;
}
var hasNextValue = prop in nextEntry;
var nextValue = _this._mergePendingCrossTabFeatureFlagChanges(prop, hasNextValue ? nextEntry[prop] : undefined);
var keepKey = _this._pendingCrossTabFeatureFlagChanges.has(prop) ? prop in _this.props : hasNextValue;
if (keepKey === prop in _this.props && isStorageValueEqual(nextValue, _this.props[prop])) {
return;
}
if (keepKey) {
_this._setProp(prop, nextValue, false);
}
else {
_this._deleteProp(prop, false);
}
changed = true;
});
if (changed && notify) {
this._crossTabFeatureFlagHandlers.forEach(function (handler) { return handler(); });
}
return changed;
};
PostHogPersistence.prototype._reconcileCrossTabFeatureFlagPropertiesBeforeWrite = function () {
var _this = this;
// localStorage has no atomic read/merge/write operation. This reconciliation
// closes normal event and debounce windows, but two tabs that flush at the
// same instant can still race and leave the last complete blob on disk.
// Solving that requires a separate cross-tab locking or versioning protocol.
if (!this._splitStorageEligible) {
return false;
}
try {
this._crossTabFeatureFlagIdentityMismatch = false;
var mainValue = storage_1.localStore._get(this._name);
var mainState = this._slotWriteState(MAIN_STORAGE_SLOT);
var mainEntry_1;
var mainChanged_1 = false;
var changed_1 = false;
if (mainValue !== mainState.storageValue) {
if ((0, core_1.isNull)(mainValue)) {
// Removing persistence disables or resets durable state; it is
// not an authoritative empty feature-flag evaluation.
mainState.storageValue = null;
}
else {
mainEntry_1 = parseStorageValue(mainValue);
this._crossTabFeatureFlagIdentityMismatch =
!this._localIdentityChangePending && this._hasCrossTabFeatureFlagIdentityMismatch(mainEntry_1);
if (this._crossTabFeatureFlagIdentityMismatch) {
return false;
}
changed_1 = this._mergeCrossTabFeatureFlagProperties(mainEntry_1, MAIN_STORAGE_SLOT, false);
if (!this._splitStorage || isStorageValueEqual(this._partitionProps().main, mainEntry_1)) {
this._rememberCrossTabStorageFingerprint(mainEntry_1, MAIN_STORAGE_SLOT, true, mainValue);
}
else {
mainState.storageValue = mainValue;
}
mainChanged_1 = true;
}
}
if (this._splitStorage) {
persistence_key_policy_1.PERSISTENCE_STORAGE_GROUPS.forEach(function (group) {
var groupValue = storage_1.localStore._get(_this._groupEntryName(group));
var groupState = _this._slotWriteState(group);
var mainFallback = mainEntry_1 || {};
var mainCarriesGroup = mainChanged_1 &&
CROSS_TAB_FEATURE_FLAG_KEYS.some(function (key) { var _a; return ((_a = (0, persistence_key_policy_1.getPersistenceKeyPolicy)(key)) === null || _a === void 0 ? void 0 : _a.storageGroup) === group && key in mainFallback; });
if (groupValue === groupState.storageValue && !mainCarriesGroup) {
return;
}
if ((0, core_1.isNull)(groupValue)) {
groupState.storageValue = null;
groupState.persisted = false;
// Before the first split write, grouped keys can still live
// in the main blob. Use them as the migration fallback.
if (mainCarriesGroup) {
changed_1 = _this._mergeCrossTabFeatureFlagProperties(mainFallback, group, false) || changed_1;
}
return;
}
var groupEntry = parseStorageValue(groupValue);
changed_1 = _this._mergeCrossTabFeatureFlagProperties(groupEntry, group, false) || changed_1;
_this._rememberCrossTabStorageFingerprint(groupEntry, group, true, groupValue);
});
}
return changed_1;
}
catch (_a) {
return false;
}
};
PostHogPersistence.prototype._setCrossTabFeatureFlagChangesPending = function (key, changes) {
this._pendingCrossTabFeatureFlagChanges.set(key, changes);
var policy = (0, persistence_key_policy_1.getPersistenceKeyPolicy)(key);
if (!(policy === null || policy === void 0 ? void 0 : policy.volatile)) {
var group = this._splitStorage ? policy === null || policy === void 0 ? void 0 : policy.storageGroup : undefined;
this._slotWriteState(group || MAIN_STORAGE_SLOT).fingerprint = undefined;
this._markGroupDirty(key);
}
};
PostHogPersistence.prototype._markAllCrossTabFeatureFlagChangesPending = function () {
var _this = this;
// remove() either resets slot bookkeeping or deliberately retains group
// fingerprints. Do not invalidate retained groups just for the transaction.
CROSS_TAB_FEATURE_FLAG_KEYS.forEach(function (key) { return _this._pendingCrossTabFeatureFlagChanges.set(key, true); });
};
PostHogPersistence.prototype._markLoadedCrossTabFeatureFlagChangesPending = function () {
var _this = this;
if (!this._splitStorageEligible) {
return;
}
try {
var mainValue = storage_1.localStore._get(this._name);
var mainEntry_2 = parseStorageValue(mainValue);
this._slotWriteState(MAIN_STORAGE_SLOT).storageValue = mainValue;
CROSS_TAB_FEATURE_FLAG_KEYS.forEach(function (key) {
var _a;
var group = _this._splitStorage ? (_a = (0, persistence_key_policy_1.getPersistenceKeyPolicy)(key)) === null || _a === void 0 ? void 0 : _a.storageGroup : undefined;
var groupValue = group ? storage_1.localStore._get(_this._groupEntryName(group)) : null;
if (group) {
var groupState = _this._slotWriteState(group);
groupState.storageValue = groupValue;
groupState.persisted = !(0, core_1.isNull)(groupValue);
}
var storedEntry = group && !(0, core_1.isNull)(groupValue) ? parseStorageValue(groupValue) : mainEntry_2;
if (key in _this.props) {
_this._markPendingCrossTabFeatureFlagChanges(key, storedEntry[key], _this.props[key]);
}
else if (key in storedEntry) {
_this._setCrossTabFeatureFlagChangesPending(key, true);
}
});
}
catch (_a) { }
};
PostHogPersistence.prototype._mergePendingCrossTabFeatureFlagChanges = function (key, nextValue) {
var pendingChanges = this._pendingCrossTabFeatureFlagChanges.get(key);
if (!pendingChanges) {
return nextValue;
}
if (pendingChanges === true) {
return this.props[key];
}
if (key === constants_1.PERSISTENCE_ACTIVE_FEATURE_FLAGS) {
var merged_1 = new Set((0, core_1.isArray)(nextValue) ? nextValue : []);
var local_1 = new Set((0, core_1.isArray)(this.props[key]) ? this.props[key] : []);
pendingChanges.forEach(function (flag) { return (local_1.has(flag) ? merged_1.add(flag) : merged_1.delete(flag)); });
return Array.from(merged_1);
}
var merged = (0, core_2.isObject)(nextValue) ? __assign({}, nextValue) : {};
var local = (0, core_2.isObject)(this.props[key]) ? this.props[key] : {};
pendingChanges.forEach(function (property) {
if (property in local) {
merged[property] = local[property];
}
else {
delete merged[property];
}
});
return merged;
};
PostHogPersistence.prototype._markPendingCrossTabFeatureFlagChanges = function (key, previousValue, nextValue) {
var _a;
if (!isCrossTabFeatureFlagKey(key)) {
return;
}
var existingPendingChanges = this._pendingCrossTabFeatureFlagChanges.get(key);
if (existingPendingChanges === true) {
return;
}
// Recompute against the latest durable value instead of accumulating
// mutations. A local false -> true -> false sequence is no longer pending
// when storage still contains false, so a later sibling update can win.
var durableValue = previousValue;
if (this._splitStorageEligible) {
try {
var group = this._splitStorage ? (_a = (0, persistence_key_policy_1.getPersistenceKeyPolicy)(key)) === null || _a === void 0 ? void 0 : _a.storageGroup : undefined;
var storageKey = group ? this._groupEntryName(group) : this._name;
durableValue = parseStorageValue(storage_1.localStore._get(storageKey))[key];
}
catch (_b) { }
}
var pendingChanges = new Set(existingPendingChanges || []);
if (key === constants_1.PERSISTENCE_ACTIVE_FEATURE_FLAGS) {
if ((!(0, core_1.isUndefined)(previousValue) && !(0, core_1.isArray)(previousValue)) ||
(!(0, core_1.isUndefined)(durableValue) && !(0, core_1.isArray)(durableValue)) ||
!(0, core_1.isArray)(nextValue)) {
this._setCrossTabFeatureFlagChangesPending(key, true);
return;
}
var previous_1 = new Set(previousValue || []);
var durable_1 = new Set(durableValue || []);
var next_1 = new Set(nextValue);
new Set(__spreadArray(__spreadArray([], __read(previous_1), false), __read(next_1), false)).forEach(function (flag) {
if (previous_1.has(flag) !== next_1.has(flag)) {
pendingChanges.add(flag);
}
});
pendingChanges.forEach(function (flag) {
if (durable_1.has(flag) === next_1.has(flag)) {
pendingChanges.delete(flag);
}
});
}
else if ((0, core_2.isObject)(nextValue)) {
if ((!(0, core_1.isUndefined)(previousValue) && !(0, core_2.isObject)(previousValue)) ||
((0, core_1.isUndefined)(previousValue) && (0, core_2.isEmptyObject)(nextValue) && (0, core_1.isUndefined)(durableValue))) {
this._setCrossTabFeatureFlagChangesPending(key, true);
return;
}
var previous_2 = (0, core_2.isObject)(previousValue) ? previousValue : {};
var durable_2 = (0, core_2.isObject)(durableValue) ? durableValue : {};
new Set(__spreadArray(__spreadArray([], __read(Object.keys(previous_2)), false), __read(Object.keys(nextValue)), false)).forEach(function (property) {
if (property in previous_2 !== property in nextValue ||
!isStorageValueEqual(previous_2[property], nextValue[property])) {
pendingChanges.add(property);
}
});
pendingChanges.forEach(function (property) {
if (property in durable_2 === property in nextValue &&
isStorageValueEqual(durable_2[property], nextValue[property])) {
pendingChanges.delete(property);
}
});
}
else {
if (!isStorageValueEqual(durableValue, nextValue)) {
this._setCrossTabFeatureFlagChangesPending(key, true);
}
else {
this._pendingCrossTabFeatureFlagChanges.delete(key);
}
return;
}
if (pendingChanges.size) {
this._setCrossTabFeatureFlagChangesPending(key, pendingChanges);
}
else {
this._pendingCrossTabFeatureFlagChanges.delete(key);
}
};
PostHogPersistence.prototype._saveDebounceMs = function () {
var _a;
var value = (_a = this._config) === null || _a === void 0 ? void 0 : _a.persistence_save_debounce_ms;
return (0, core_1.isNumber)(value) && value > 0 ? value : 0;
};
PostHogPersistence.prototype._rememberCurrentCookieProperties = function (props) {
if (!this._config.cookieWinsOnConflict || this._config.persistence.toLowerCase() !== 'localstorage+cookie') {
return;
}
if (props) {
// Remember exactly what this write attempted instead of rereading the
// shared cookie. A sibling can update the cookie immediately after our
// write; recording that later value here would hide an unseen update.
try {
var cookieProperties = (0, storage_1.getCookiePersistedProperties)(props, this._config.cookie_persisted_properties || []);
var customCookieProperties = this._config.cookie_persisted_properties || [];
var metadata = (0, storage_1.getCookiePersistedPropertiesMetadata)(cookieProperties, customCookieProperties);
var expectedFingerprint = JSON.stringify(cookieProperties) + '|' + JSON.stringify(metadata);
// `localStorage+cookie._set` reports localStorage success even if
// its best-effort cookie mirror fails. Advance the observed
// fingerprint only when the shared cookie actually contains this
// exact snapshot. A different value may be either the old cookie
// or a sibling write, and must remain eligible for reconciliation.
var currentCookieValue = storage_1.cookieStore._get(this._name) || undefined;
if (currentCookieValue &&
(0, storage_1.getCookiePropertiesFingerprint)(this._name, currentCookieValue) === expectedFingerprint) {
this._lastSeenCookiePropertiesFingerprint = expectedFingerprint;
this._lastSeenMainCookieValue = currentCookieValue;
}
}
catch (_a) { }
return;
}
try {
var cookieValue = storage_1.cookieStore._get(this._name) || undefined;
this._lastSeenCookiePropertiesFingerprint = cookieValue
? (0, storage_1.getCookiePropertiesFingerprint)(this._name, cookieValue)
: undefined;
this._lastSeenMainCookieValue = cookieValue;
}
catch (_b) { }
};
/**
* Adopt shared-cookie changes made by a sibling subdomain without replacing
* localStorage-only or pending in-memory properties. Returns true when a new
* non-empty cookie snapshot was observed.
*/
PostHogPersistence.prototype.syncCookieProperties = function () {
return this._syncCookieProperties(this._config);
};
PostHogPersistence.prototype._syncCookieProperties = function (config, ignoreDisabled) {
var _this = this;
if (ignoreDisabled === void 0) { ignoreDisabled = false; }
if ((this._disabled && !ignoreDisabled) ||
this._cookieSyncSuppressed ||
!config.cookieWinsOnConflict ||
config.persistence.toLowerCase() !== 'localstorage+cookie') {
return false;
}
var cookieValue;
try {
cookieValue = storage_1.cookieStore._get(this._name) || undefined;
}
catch (_a) { }
if (!cookieValue || cookieValue === this._lastSeenMainCookieValue) {
// Ignore sidecar-only changes. Metadata is meaningful only together
// with a new main snapshot, and treating it as a sibling identity
// update could roll back a local write when the main mirror failed.
return false;
}
var cookieFingerprint = (0, storage_1.getCookiePropertiesFingerprint)(this._name, cookieValue);
var cookieProperties;
try {
cookieProperties = (0, storage_1.getSafeCookieProperties)(JSON.parse(cookieValue));
}
catch (_b) {
return false;
}
// If the main cookie changed while its metadata was read, retry on the
// next synchronization rather than applying the old values while marking
// the new snapshot as observed.
if ((storage_1.cookieStore._get(this._name) || undefined) !== cookieValue) {
return false;
}
this._lastSeenCookiePropertiesFingerprint = cookieFingerprint;
this._lastSeenMainCookieValue = cookieValue;
var metadataState = (0, storage_1.getCookiePersistedPropertiesMetadataState)(this._name, cookieValue);
var authoritativeCookieProperties = __spreadArray(__spreadArray([], __read(storage_1.COOKIE_PERSISTED_PROPERTIES), false), __read(metadataState.properties), false);
var invalidCookieProperties = {};
Object.keys(cookieProperties).forEach(function (key) {
var value = cookieProperties[key];
if ((0, core_1.isUndefined)(value) ||
(0, core_1.isNull)(value) ||
value === '' ||
(key === constants_1.USER_STATE && value !== constants_1.USER_STATE_ANONYMOUS && value !== constants_1.USER_STATE_IDENTIFIED)) {
invalidCookieProperties[key] = true;
delete cookieProperties[key];
}
});
if ((0, core_2.isEmptyObject)(cookieProperties)) {
return false;
}
var hasValidCookieIdentity = constants_1.DISTINCT_ID in cookieProperties ||
cookieProperties[constants_1.USER_STATE] === constants_1.USER_STATE_ANONYMOUS ||
cookieProperties[constants_1.USER_STATE] === constants_1.USER_STATE_IDENTIFIED;
var previousProps = this.props;
var previousDistinctId = previousProps[constants_1.DISTINCT_ID];
var previousUserState = previousProps[constants_1.USER_STATE];
var nextProps = (0, general_utils_1.extend)({}, previousProps);
var cookiePersistedProperties = __spreadArray(__spreadArray([], __read(storage_1.COOKIE_PERSISTED_PROPERTIES), false), __read((config.cookie_persisted_properties || [])), false);
cookiePersistedProperties.forEach(function (key) {
if (authoritativeCookieProperties.indexOf(key) !== -1 &&
!(key in cookieProperties) &&
!invalidCookieProperties[key] &&
(hasValidCookieIdentity || (key !== constants_1.DISTINCT_ID && key !== constants_1.USER_STATE))) {
var localValue = nextProps[key];
var legacyFalsyBuiltIn = !metadataState.isValid &&
storage_1.COOKIE_PERSISTED_PROPERTIES.indexOf(key) !== -1 &&
(localValue === false || localValue === 0);
if (!legacyFalsyBuiltIn) {
delete nextProps[key];
}
}
});
this.props = (0, general_utils_1.extend)(nextProps, cookieProperties);
CROSS_TAB_FEATURE_FLAG_KEYS.forEach(function (key) {
var hadPreviousValue = key in previousProps;
var hasNextValue = key in _this.props;
if (hadPreviousValue !== hasNextValue || !isStorageValueEqual(previousProps[key], _this.props[key])) {
if (hasNextValue) {
_this._markPendingCrossTabFeatureFlagChanges(key, previousProps[key], _this.props[key]);
}
else {
_this._setCrossTabFeatureFlagChangesPending(key, true);
}
}
});
// Older writers can omit $user_state entirely. Once that authoritative
// omission removes a prior identified state, represent it explicitly as
// anonymous so reset cleanup (including $groups) runs consistently.
if (hasValidCookieIdentity && !(constants_1.USER_STATE in cookieProperties) && !(constants_1.USER_STATE in this.props)) {
this._setProp(constants_1.USER_STATE, constants_1.USER_STATE_ANONYMOUS);
}
var nextDistinctId = this.props[constants_1.DISTINCT_ID];
var nextUserState = this.props[constants_1.USER_STATE];
if (hasValidCookieIdentity && (nextDistinctId !== previousDistinctId || nextUserState !== previousUserState)) {
this._localIdentityChangePending = true;
this._cookieIdentityChangePending = true;
storage_1.sessionStore._set(getCookieIdentityChangePendingName(this._name), true);
this._deleteProp(constants_1.STORED_PERSON_PROPERTIES_KEY);
this._deleteProp(constants_1.PERSISTENCE_ACTIVE_FEATURE_FLAGS);
this._deleteProp(constants_1.ENABLED_FEATURE_FLAGS);
this._deleteProp(constants_1.PERSISTENCE_FEATURE_FLAG_DETAILS);
this._deleteProp(constants_1.PERSISTENCE_FEATURE_FLAG_PAYLOADS);
this._deleteProp(constants_1.PERSISTENCE_FEATURE_FLAG_REQUEST_ID);
this._deleteProp(constants_1.PERSISTENCE_FEATURE_FLAG_EVALUATED_AT);
this._deleteProp(constants_1.PERSISTENCE_FEATURE_FLAG_ERRORS);
this._deleteProp(constants_1.FLAG_CALL_REPORTED);
var siblingReset = nextUserState === constants_1.USER_STATE_ANONYMOUS &&
(previousUserState === constants_1.USER_STATE_IDENTIFIED ||
cookieProperties[constants_1.USER_STATE] === constants_1.USER_STATE_ANONYMOUS ||
(!(0, core_1.isUndefined)(previousDistinctId) && nextDistinctId !== previousDistinctId));
if (siblingReset) {
// reset() clears event-visible persistence. Mirror that for a
// sibling reset without discarding hidden SDK state or values
// that the new shared-cookie snapshot explicitly carries.
clearStaleEventProperties(this.props, cookieProperties);
this._deleteProp(constants_1.STORED_GROUP_PROPERTIES_KEY);
}
// `$user_id` is localStorage-only, but it is bound to the current
// identity. Never carry the previous logged-in user across a sibling
// identify/reset adopted from the shared cookie.
if (nextUserState === constants_1.USER_STATE_IDENTIFIED) {
this.props.$user_id = nextDistinctId;
}
else {
delete this.props.$user_id;
}
this._deleteProp(constants_1.ALIAS_ID_KEY);
}
return true;
};
PostHogPersistence.prototype.consumeCookieIdentityChange = function () {
var pendingName = getCookieIdentityChangePendingName(this._name);
var changed = this._cookieIdentityChangePending || !!storage_1.sessionStore._get(pendingName);
this._cookieIdentityChangePending = false;
if (changed) {
storage_1.sessionStore._remove(pendingName);
}
return changed;
};
PostHogPersistence.prototype._beginCookieSyncSuppression = function (ignoreDisabled) {
if (ignoreDisabled === void 0) { ignoreDisabled = false; }
if (!this._cookieSyncSuppressed &&
(!this._disabled || ignoreDisabled) &&
this._config.cookieWinsOnConflict &&
this._config.persistence.toLowerCase() === 'localstorage+cookie') {
this._cookieSyncSuppressed = true;
return true;
}
return false;
};
PostHogPersistence.prototype._publishSuppressedCookieSnapshot = function () {
if (!this._cookieSyncSuppressed) {
return;
}
if (!(0, core_1.isUndefined)(this._pendingSaveTimer)) {
clearTimeout(this._pendingSaveTimer);
this._pendingSaveTimer = undefined;
}
// Force the complete local snapshot through without reconciling a
// stale sibling write observed during this authoritative transaction.
delete this._slotState[MAIN_STORAGE_SLOT];
this._writeNow(true);
};
PostHogPersistence.prototype._endCookieSyncSuppression = function (publish) {
if (publish === void 0) { publish = true; }
if (!this._cookieSyncSuppressed) {
return;
}
try {
if (publish) {
this._publishSuppressedCookieSnapshot();
}
else if (!(0, core_1.isUndefined)(this._pendingSaveTimer)) {
// A failed identify/reset may have scheduled saves before it
// threw. Do not let one run after suppression is released and
// publish the incomplete transaction asynchronously.
clearTimeout(this._pendingSaveTimer);
this._pendingSaveTimer = undefined;
}
}
finally {
this._cookieSyncSuppressed = false;
}
};
/**
* Returns whether persistence is disabled. Only available in SDKs > 1.257.1. Do not use on extensions, otherwise
* it'll break backwards compatibility for any version before 1.257.1.
*/
PostHogPersistence.prototype.isDisabled = function () {
return !!this._disabled;
};
PostHogPersistence.prototype._buildStorage = function (config) {
if (CASE_INSENSITIVE_PERSISTENCE_TYPES.indexOf(config['persistence'].toLowerCase()) === -1) {
logger_1.logger.critical('Unknown persistence type ' + config['persistence'] + '; falling back to localStorage+cookie');
config['persistence'] = 'localStorage+cookie';
}
// Create this before hand to avoid creating it multiple times
// Creating it inside each individual condition below is too complicated and will break backwards compatibility
// so create it once for this specific config and use it if necessary
var localPlusCookieStore = (0, storage_1.createLocalPlusCookieStore)(config['cookie_persisted_properties'] || [], config.cookieWinsOnConflict);
var store;
// The flag split is only meaningful on a localStorage-backed
// store: it is the one that broadcasts large cross-tab `storage` events.
// cookie can't hold the cluster, memory/sessionStorage don't broadcast.
var splitEligible = false;
// We handle storage type in a case-insensitive way for backwards compatibility
var storage_type = config['persistence'].toLowerCase();
if (storage_type === 'localstorage' && storage_1.localStore._is_supported()) {
store = storage_1.localStore;
splitEligible = true;
}
else if (storage_type === 'localstorage+cookie' && localPlusCookieStore._is_supported()) {
store = localPlusCookieStore;
splitEligible = true;
}
else if (storage_type === 'sessionstorage' && storage_1.sessionStore._is_supported()) {
store = storage_1.sessionStore;
}
else if (storage_type === 'memory') {
store = storage_1.memoryStore;
}
else if (storage_type === 'cookie' && storage_1.cookieStore._is_supported()) {
store = storage_1.cookieStore;
}
else if (localPlusCookieStore._is_supported()) {
// selected storage type wasn't supported, fallback to 'localstorage+cookie' if possible
store = localPlusCookieStore;
splitEligible = true;
}
else if (storage_1.cookieStore._is_supported()) {
store = storage_1.cookieStore;
}
else {
// Neither web storage nor cookies are available -- e.g. a page served from a
// `data:` URL, where Chrome disables both. Falling back to cookieStore here left
// every read and write silently failing, so degrade to in-memory persistence.
store = storage_1.memoryStore;
}
this._splitStorageEligible = splitEligible;
return store;
};
PostHogPersistence.prototype._groupEntryName = function (group) {
return "".concat(this._name, "__").concat(group);
};
// The split is on only when the resolved backend can host it (localStorage /
// localStorage+cookie, set by `_buildStorage` into `_splitStorageEligible`)
// AND the config opts in. Resolved here so the constructor and the runtime
// `update_config` toggle can never disagree about whether the split is active.
PostHogPersistence.prototype._resolveSplitStorage = function (config) {
return this._splitStorageEligible && !!config['split_storage'];
};
PostHogPersistence.prototype.properties = function () {
var p = {};
(0, general_utils_1.each)(this.props, function (v, k) {
var _a;
var policy = (0, persistence_key_policy_1.getPersistenceKeyPolicy)(k);
if (!policy || policy.exposure === 'event') {
if ((_a = policy === null || policy === void 0 ? void 0 : policy.shouldSkipFromEventProperties) === null || _a === void 0 ? void 0 : _a.call(policy, v)) {
return;
}
// Unknown keys are treated as user-defined super properties and remain event-visible.
p[k] = v;
}
});
return p;
};
/**
* Reload persisted properties from storage.
*
* @param allowDisabled - Read while this instance is disabled when shared consent changed in another tab
* and the in-memory disabled state has not yet been reconciled.
*/
PostHogPersistence.prototype.load = function (allowDisabled) {
var _this = this;
var _a, _b;
if (allowDisabled === void 0) { allowDisabled = false; }
if (this._disabled && !allowDisabled) {
return;
}
var reconcileCookieIdentity = this._config.cookieWinsOnConflict && this._config.persistence.toLowerCase() === 'localstorage+cookie';
var localEntryBeforeMerge = reconcileCookieIdentity ? storage_1.localStore._parse(this._name) : null;
var cookieEntryBeforeMerge = {};
if (reconcileCookieIdentity) {
try {
cookieEntryBeforeMerge = (0, storage_1.getSafeCookieProperties)(storage_1.cookieStore._parse(this._name));
(0, general_utils_1.each)(cookieEntryBeforeMerge, function (value, key) {
if ((0, core_1.isUndefined)(value) || (0, core_1.isNull)(value) || value === '') {
delete cookieEntryBeforeMerge[key];
}
});
}
catch (_c) { }
}
var entry = this._storage._parse(this._name);
if (entry) {
this.props = (0, general_utils_1.extend)({}, entry);
}
if (this._splitStorage) {
this._loadGroupEntries();
}
if (reconcileCookieIdentity && entry) {
var previousDistinctId = localEntryBeforeMerge === null || localEntryBeforeMerge === void 0 ? void 0 : localEntryBeforeMerge[constants_1.DISTINCT_ID];
var previousUserState = (_a = localEntryBeforeMerge === null || localEntryBeforeMerge === void 0 ? void 0 : localEntryBeforeMerge[constants_1.USER_STATE]) !== null && _a !== void 0 ? _a : constants_1.USER_STATE_ANONYMOUS;
var nextDistinctId = entry[constants_1.DISTINCT_ID];
var nextUserState = (_b = entry[constants_1.USER_STATE]) !== null && _b !== void 0 ? _b : constants_1.USER_STATE_ANONYMOUS;
if (nextDistinctId !== previousDistinctId || nextUserState !== previousUserState) {
this._cookieIdentityChangePending = true;
storage_1.sessionStore._set(getCookieIdentityChangePendingName(this._name), true);
var nextProps_1 = (0, general_utils_1.extend)({}, this.props);
storage_1.COOKIE_IDENTITY_BOUND_LOCAL_PROPERTIES.forEach(function (key) { return delete nextProps_1[key]; });
var siblingReset = nextUserState === constants_1.USER_STATE_ANONYMOUS &&
(previousUserState === constants_1.USER_STATE_IDENTIFIED ||
cookieEntryBeforeMerge[constants_1.USER_STATE] === constants_1.USER_STATE_ANONYMOUS ||
(!(0, core_1.isUndefined)(previousDistinctId) && nextDistinctId !== previousDistinctId));
if (siblingReset) {
clearStaleEventProperties(nextProps_1, cookieEntryBeforeMerge);
delete nextProps_1[constants_1.STORED_GROUP_PROPERTIES_KEY];
}
this.props = nextProps_1;
// Split entries are loaded after the main blob, so remove the
// previous identity's grouped flag state from both memory and its
// localStorage slot before a future load can restore it again.
var affectedGroups_1 = new Set();
storage_1.COOKIE_IDENTITY_BOUND_LOCAL_PROPERTIES.forEach(function (key) {
var _a;
var group = (_a = (0, persistence_key_policy_1.getPersistenceKeyPolicy)(key)) === null || _a === void 0 ? void 0 : _a.storageGroup;
if (group) {
affectedGroups_1.add(group);
}
});
affectedGroups_1.forEach(function (group) {
var groupProps = {};
(0, general_utils_1.each)(_this.props, function (value, key) {
var _a;
if (((_a = (0, persistence_key_policy_1.getPersistenceKeyPolicy)(key)) === null || _a === void 0 ? void 0 : _a.storageGroup) === group) {
groupProps[key] = value;
}
});
if ((0, core_2.isEmptyObject)(groupProps)) {
storage_1.localStore._remove(_this._groupEntryName(group));
_this._slotState[group] = {};
}
else if (storage_1.localStore._set(_this._groupEntryName(group), groupProps)) {
_this._slotState[group] = {
persisted: true,
fingerprint: _this._entryFingerprint(groupProps, group),
};
}
});
}
}
// A write can durably adopt the new main-cookie identity before core has
// cleared the separate session persistence. Keep that cleanup pending
// across reloads in this browser tab.
if (storage_1.sessionStore._get(getCookieIdentityChangePendingName(this._name))) {
this._cookieIdentityChangePending = true;
}
// `_parse()` may have read a different cookie than a reread here if a
// sibling writes concurrently. Let the first synchronization compare the
// current shared cookie with the loaded props instead of marking an
// arbitrary later snapshot as observed.
};
// Merge each group entry over `props`, which already holds the main blob.
// On a first upgrade the main blob may still carry the old flag
// values; a present group entry wins, so we resolve
// `props[key] = group[key] ?? main[key]` in a single pass (the "check the
// old key once" migration). The first `save()` then strips the keys from
// the main blob. Group entries are localStorage-only (read via `localStore`
// directly, never the cookie or the localPlusCookie re-write-on-parse path).
PostHogPersistence.prototype._loadGroupEntries = function () {
var e_1, _a;
try {
for (var PERSISTENCE_STORAGE_GROUPS_1 = __values(persistence_key_policy_1.PERSISTENCE_STORAGE_GROUPS), PERSISTENCE_STORAGE_GROUPS_1_1 = PERSISTENCE_STORAGE_GROUPS_1.next(); !PERSISTENCE_STORAGE_GROUPS_1_1.done; PERSISTENCE_STORAGE_GROUPS_1_1 = PERSISTENCE_STORAGE_GROUPS_1.next()) {
var group = PERSISTENCE_STORAGE_GROUPS_1_1.value;
// `localStore._parse` returns `{}` (not null) for a missing key, so
// gate on real content: an empty/absent entry is not "persisted" and
// must not be tracked, or `_writeNowSplit` would re-create it as `{}`.
var groupEntry = storage_1.localStore._parse(this._groupEntryName(group));
if (groupEntry && !(0, core_2.isEmptyObject)(groupEntry)) {
var state = this._slotWriteState(group);
state.persisted = true;
// Seed the no-op fingerprint with the snapshot we just read, so the
// first frequent main-blob save at startup (before fresh flags
// return from the network) recognises an unchanged flag entry and
// neither re-serializes nor re-broadcasts it to every open tab.
// Only safe when the main blob carries no key for this group: a
// leftover (partial migration, or a stale tab that wrote a flag key
// back to main) makes the partitioned payload differ from what is on
// disk, so the entry must still be written. Leaving it unseeded then
// lets the first save's fingerprint check write the merged payload
// through — completing the migration / healing the orphan. The
// `_writeEntry` group fast-path skips on `!dirty && fingerprint set`,
// which would otherwise short-circuit that write before the
// fingerprint is even compared.
if (!this._mainCarriesGroupKey(group)) {
state.fingerprint = this._entryFingerprint(groupEntry, group);
}
// The group entry is normally the migrated-forward home and wins
// over the main blob. The exception: a group that stamps a
// freshness timestamp (flags: $feature_flag_evaluated_at) lets us
// detect when a gate-off / older-SDK tab wrote a fresher payload
// back into the main blob — then we keep the main blob and let the
// next save heal the group entry. With no timestamp on either side
// (migration leftover) the group wins.
if (!this._groupEntryIsStale(group, groupEntry)) {
(0, general_utils_1.extend)(this.props, groupEntry);
}
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (PERSISTENCE_STORAGE_GROUPS_1_1 && !PERSISTENCE_STORAGE_GROUPS_1_1.done && (_a = PERSISTENCE_STORAGE_GROUPS_1.return)) _a.call(PERSISTENCE_STORAGE_GROUPS_1);
}
finally { if (e_1) throw e_1.error; }
}
};
// True when the already-loaded main blob still holds a key belonging to this
// group — a migration leftover the next save must fold into the group entry.
// Checked before the group entry is merged in, so it sees only the main blob's
// own keys (sibling groups carry a different storageGroup and never match).
PostHogPersistence.prototype._mainCarriesGroupKey = function (group) {
return Object.keys(this.props).some(function (key) { var _a; return ((_a = (0, persistence_key_policy_1.getPersistenceKeyPolicy)(key)) === null || _a === void 0 ? void 0 : _a.storageGroup) === group; });
};
PostHogPersistence.prototype._groupEntryIsStale = function (group, groupEntry) {
var freshnessKey = GROUP_FRESHNESS_KEY[group];
if (!freshnessKey) {
return false;
}
var groupLoadedAt = groupEntry[freshnessKey];
var mainLoadedAt = this.props[freshnessKey];
return (0, core_1.isNumber)(groupLoadedAt) && (0, core_1.isNumber)(mainLoadedAt) && mainLoadedAt > groupLoadedAt;
};
/**
* Refresh a single key from on-disk storage into `this.props` without
* touching the rest. Used by `SessionIdManager` on the cross-tab idle
* path so we can pick up a sibling tab's SESSION_ID write without
* either:
* - flushing our own (potentially stale) whole-props blob to storage
* via `flush()`, which would clobber the sibling's write, or
* - replacing all of `props` via `load()`, which would discard any
* in-memory writes that haven't yet been debounced to storage.
*/
PostHogPersistence.prototype.refreshKey = function (prop) {
var _a;
if (this._disabled) {
return;
}
var group = this._splitStorage ? (_a = (0, persistence_key_policy_1.getPersistenceKeyPolicy)(prop)) === null || _a === void 0 ? void 0 : _a.storageGroup : undefined;
var entry = group ? storage_1.localStore._parse(this._groupEntryName(group)) : this._storage._parse(this._name);
if (entry && prop in entry) {
this._setProp(prop, entry[prop]);
return;
}
// A grouped key that has not migrated yet still lives in the main blob;
// check there once before concluding a sibling removed it.
if (group) {
var mainEntry = this._storage._parse(this._name);
if (mainEntry && prop in mainEntry) {
this._setProp(prop, mainEntry[prop]);
return;
}
}
this._deleteProp(prop);
};
/**
* NOTE: Saving frequently causes issues with Recordings and Consent Management Platform (CMP) tools which
* observe cookie changes, and modify their UI, often causing infinite loops.
* As such callers of this should ideally check that the data has changed beforehand
*/
PostHogPersistence.prototype.save = function () {
var _this = this;
if (this._disabled) {
return;
}
var debounce = this._saveDebounceMs();
if (debounce <= 0) {
this._writeNow();
return;
}
// Coalesce: if a flush is already scheduled, the latest `props`
// will be picked up when the timer fires. Otherwise schedule one.
if (!(0, core_1.isUndefined)(this._pendingSaveTimer)) {
return;
}
this._pendingSaveTimer = setTimeout(function () {
_this._pendingSaveTimer = undefined;
_this._writeNow();
}, debounce);
};
/**
* Force any pending debounced save to land in storage immediately.
* No-op when there is no pending timer — crucially, this means the
* `beforeunload` / `pagehide` listeners installed in the constructor
* cannot accidentally resurrect a storage entry that `remove()` or
* `clear()` just deleted. Without this guard, the listener would
* call `_writeNow()` and write the in-memory `props` (now `{}`) back
* to storage, breaking `posthog.reset()` / opt-out flows.
*/
PostHogPersistence.prototype.flush = function () {
if ((0, core_1.isUndefined)(this._pendingSaveTimer)) {
return;
}
clearTimeout(this._pendingSaveTimer);
this._pendingSaveTimer = undefined;
this._writeNow();
};
PostHogPersistence.prototype._writeNow = function (forceSuppressedSnapshot) {
if (forceSuppressedSnapshot === void 0) { forceSuppressedSnapshot = false; }
if (this._disabled || (this._cookieSyncSuppressed && !forceSuppressedSnapshot)) {
return;
}
// Reconcile immediately before writing as well as before capture. This
// closes the debounce window where a sibling identify/reset could
// otherwise be overwritten by this tab's pending stale whole-blob save.
// A transaction ending suppression owns its complete snapshot and must
// not adopt a sibling write that arrived while it was in progress.
if (!forceSuppressedSnapshot) {
this.syncCookieProperties();
}
var shouldReconcileCrossTabProperties = !forceSuppressedSnapshot && !this._storageMigrationInProgress;
if (!shouldReconcileCrossTabProperties) {
this._crossTabFeatureFlagIdentityMismatch = false;
}
var crossTabPropertiesChanged = shouldReconcileCrossTabProperties
? this._reconcileCrossTabFeatureFlagPropertiesBeforeWrite()
: false;
if (this._crossTabFeatureFlagIdentityMismatch) {
if (this._config.debug) {
logger_1.logger.warn('skipping persistence write because storage belongs to a different distinct ID');
}
return;
}
if (this._splitStorage) {
this._writeNowSplit();
if (crossTabPropertiesChanged) {
this._crossTabFeatureFlagHandlers.forEach(function (handler) { return handler(); });
}
return;
}
var writeResult = this._writeEntry(this._storage, this._name, this.props, MAIN_STORAGE_SLOT);
if (writeResult === 'written') {
this._rememberCurrentCookieProperties(this.props);
}
if (writeResult !== 'failed') {
this._pendingCrossTabFeatureFlagChanges.clear();
this._localIdentityChangePending = false;
}
if (crossTabPropertiesChanged) {
this._crossTabFeatureFlagHandlers.forEach(function (handler) { return handler(); });
}
};
// Partition `props` by storage group and write each entry independently:
// the main blob without the grouped keys (stripping them completes the
// migration), plus one entry per group holding only its keys. Per-entry
// fingerprints mean a main-blob change does not rewrite the rarely-changing
// flag entries, and vice-versa — which is the whole bandwidth win.
// Group entries go to `localStore` directly so they never hit the 4 KB
// cookie or the localPlusCookie re-write-on-parse path. INVARIANT: keep group
// entries on `localStore` — `_entryFingerprint` omits the cookie options from
// group fingerprints precisely because cookies can never carry a group entry;
// routing one to a cookie store would make a cookie-option change silently
// skip a needed rewrite.
PostHogPersistence.prototype._writeNowSplit = function () {
var e_2, _a;
var _this = this;
var _b;
var _c = this._partitionProps(), main = _c.main, groups = _c.groups;
var mainWriteResult = this._writeEntry(this._storage, this._name, main, MAIN_STORAGE_SLOT);
if (mainWriteResult === 'written') {
this._rememberCurrentCookieProperties(main);
}
if (mainWriteResult !== 'failed') {
this._localIdentityChangePending = false;
CROSS_TAB_FEATURE_FLAG_KEYS.forEach(function (key) {
var _a;
if (!((_a = (0, persistence_key_policy_1.getPersistenceKeyPolicy)(key)) === null || _a === void 0 ? void 0 : _a.storageGroup)) {
_this._pendingCrossTabFeatureFlagChanges.delete(key);
}
});
}
var _loop_1 = function (group) {
var groupProps = groups[group];
// Don't materialize an entry just to hold `{}`: skip a group that is
// empty and has never been persisted. Once a group has held content
// we keep writing it (even when empty) so a later clear actually lands.
if ((0, core_2.isEmptyObject)(groupProps) && !((_b = this_1._slotState[group]) === null || _b === void 0 ? void 0 : _b.persisted)) {
CROSS_TAB_FEATURE_FLAG_KEYS.forEach(function (key) {
var _a;
if (((_a = (0, persistence_key_policy_1.getPersistenceKeyPolicy)(key)) === null || _a === void 0 ? void 0 : _a.storageGroup) === group) {
_this._pendingCrossTabFeatureFlagChanges.delete(key);
}
});
return "continue";
}
// `_writeEntry` marks the slot `persisted` (on `_slotState`) only
// after a confirmed-successful `_set`, so a failed (e.g. quota) write
// does not falsely mark the group as materialized on disk.
var groupWriteResult = this_1._writeEntry(storage_1.localStore, this_1._groupEntryName(group), groupProps, group);
if (groupWriteResult !== 'failed') {
CROSS_TAB_FEATURE_FLAG_KEYS.forEach(function (key) {
var policy = (0, persistence_key_policy_1.getPersistenceKeyPolicy)(key);
if ((policy === null || policy === void 0 ? void 0 : policy.storageGroup) === group && (groupWriteResult === 'written' || !policy.volatile)) {
_this._pendingCrossTabFeatureFlagChanges.delete(key);
}
});
}
};
var this_1 = this;
try {
for (var PERSISTENCE_STORAGE_GROUPS_2 = __values(persistence_key_policy_1.PERSISTENCE_STORAGE_GROUPS), PERSISTENCE_STORAGE_GROUPS_2_1 = PERSISTENCE_STORAGE_GROUPS_2.next(); !PERSISTENCE_STORAGE_GROUPS_2_1.done; PERSISTENCE_STORAGE_GROUPS_2_1 = PERSISTENCE_STORAGE_GROUPS_2.next()) {
var group = PERSISTENCE_STORAGE_GROUPS_2_1.value;
_loop_1(group);
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (PERSISTENCE_STORAGE_GROUPS_2_1 && !PERSISTENCE_STORAGE_GROUPS_2_1.done && (_a = PERSISTENCE_STORAGE_GROUPS_2.return)) _a.call(PERSISTENCE_STORAGE_GROUPS_2);
}
finally { if (e_2) throw e_2.error; }
}
};
PostHogPersistence.prototype._partitionProps = function () {
var main = {};
var groups = { flags: {}, surveys: {} };
(0, general_utils_1.each)(this.props, function (value, key) {
var _a;
var group = (_a = (0, persistence_key_policy_1.getPersistenceKeyPolicy)(key)) === null || _a === void 0 ? void 0 : _a.storageGroup;
if (group) {
groups[group][key] = value;
}
else {
main[key] = value;
}
});
return { main: main, groups: groups };
};
// The no-op-rejection snapshot for an entry. The main entry can live in a
// cookie, so its fingerprint also covers the cookie options (expire_days,
// cross_subdomain, secure): a `set_config({ cookie_expiration })` must force
// a rewrite even when props are unchanged, otherwise the cookie keeps its old
// `Expires` header until some other prop changes. Group entries are
// localStorage-only — cookie options never reach them, so excluding those
// keeps a group fingerprint a pure function of its payload. That lets `load()`
// seed it before the cookie options are even resolved, and keeps it stable
// across the cookie-option setters that run during construction, so an
// unchanged flag entry is neither re-serialized nor re-broadcast.
PostHogPersistence.prototype._entryFingerprint = function (props, slot) {
if (slot === MAIN_STORAGE_SLOT) {
return JSON.stringify(props) + '|' + this._expire_days + '|' + this._cross_subdomain + '|' + this._secure;
}
// Volatile keys count by presence only: a write triggered by a real
// content change records a fingerprint that stays valid while the
// volatile values keep moving between writes, but adding or deleting a
// volatile key still changes the fingerprint so the entry writes through.
var stable = {};
(0, general_utils_1.each)(props, function (value, key) {
var _a;
stable[key] = ((_a = (0, persistence_key_policy_1.getPersistenceKeyPolicy)(key)) === null || _a === void 0 ? void 0 : _a.volatile) ? VOLATILE_FINGERPRINT_PLACEHOLDER : value;
});
return JSON.stringify(stable);
};
// No-op rejection: skip the write when nothing that affects this entry has
// changed since the last successful write. Callers spam `save()` after every
// property change, and many of those changes leave the storage payload
// unchanged. Writing identical bytes to localStorage still fires a cross-tab
// `storage` event where Chrome allocates the payload buffer in mojo IPC even
// though no listener reacts.
//
// JSON.stringify can throw on BigInt / circular refs. We let the
// underlying storage layer keep its existing try/catch behaviour
// (log and drop) by falling through on serialization errors.
PostHogPersistence.prototype._writeEntry = function (storage, name, props, slot) {
var state = this._slotWriteState(slot);
// Fast path for group slots (localStorage-only): when nothing in the
// group changed since its last successful write, skip the JSON.stringify
// of the large flag payload entirely. The main slot is excluded —
// it is small, changes on nearly every write, and carries cookie options
// in its fingerprint, so it always serializes.
if (slot !== MAIN_STORAGE_SLOT && !state.dirty && !(0, core_1.isUndefined)(state.fingerprint)) {
return 'skipped';
}
var fingerprint;
try {
fingerprint = this._entryFingerprint(props, slot);
if (fingerprint === state.fingerprint) {
state.dirty = false;
return 'skipped';
}
}
catch (_a) {
// serialization failed (BigInt / circular ref); fall through to
// storage._set, which handles the error itself, but don't cache an
// un-fingerprinted write.
fingerprint = undefined;
}
// Record the fingerprint (and clear the dirty flag, mark persisted) only
// after a confirmed-successful durable write: localStorage / sessionStorage
// swallow quota errors, so caching ahead of a failed write would skip
// every future retry and silently lose the entry.
if (storage._set(name, props, this._expire_days, this._cross_subdomain, this._secure, this._config.debug)) {
state.dirty = false;
if (slot !== MAIN_STORAGE_SLOT) {
// The group entry has now actually landed on disk — only here is
// it correct to record it as persisted (gates the empty-entry
// skip in `_writeNowSplit`).
state.persisted = true;
}
if (!(0, core_1.isUndefined)(fingerprint)) {
state.fingerprint = fingerprint;
}
if (this._splitStorageEligible) {
state.storageValue = storage_1.localStore._get(name);
}
return 'written';
}
else if (this._config.debug) {
// The durable write did not land (e.g. localStorage quota). The slot
// stays dirty / un-fingerprinted so the next save retries it; surface
// it under debug so a repeated failure on a group entry — which would
// otherwise silently strand the flag cache — is visible.
logger_1.logger.warn("failed to persist storage entry \"".concat(name, "\"; will retry on next save"));
}
return 'failed';
};
// `keepGroupEntries` is set by the cookie-option setters (set_secure /
// set_cross_subdomain). A cookie-scope change has to clear the cookie-backed
// main entry, but the group entries are localStorage-only and entirely
// scope-independent, so deleting and rewriting them would be the exact
// per-page-load flag-blob churn the split exists to remove (these setters fire
// once each on every construction, transitioning the in-memory option from
// undefined to its configured value). Opt-out / reset (set_disabled / clear)
// pass nothing and wipe everything.
//
// INVARIANT for `keepGroupEntries: true`: the caller must not also mutate
// `props`. We keep the on-disk group entries AND their retained fingerprint;
// that is only safe while `props` still matches what is on disk. The cookie
// setters satisfy this (they touch only `_secure` / `_cross_subdomain`). A
// future caller that clears or rewrites `props` while keeping the entries
// would leave the retained fingerprint describing stale on-disk content and
// skip the corrective write.
PostHogPersistence.prototype.remove = function (_a) {
var e_3, _b;
var _c = _a === void 0 ? {} : _a, _d = _c.keepGroupEntries, keepGroupEntries = _d === void 0 ? false : _d;
// Any write following this removal owns its complete feature-flag
// snapshot and must not adopt the entry that was just removed.
this._markAllCrossTabFeatureFlagChangesPending();
// Cancel any pending debounced write — the storage entry is going
// away so there is nothing useful to flush.
if (!(0, core_1.isUndefined)(this._pendingSaveTimer)) {
clearTimeout(this._pendingSaveTimer);
this._pendingSaveTimer = undefined;
}
// remove both domain and subdomain cookies
this._storage._remove(this._name, false);
this._storage._remove(this._name, true);
// Wipe the group entries too — even when the split is currently off — so
// a default flip-flop or version downgrade cannot strand an orphaned
// flag entry that would leak across users on reset()/opt-out.
// Only the owning instance does this: the sessionStorage sibling
// posthog-core spins up shares this instance's storage name, so it must
// not delete the localStorage owner's entries. localStorage-only.
if (!keepGroupEntries && this._ownsSplitStorage) {
try {
for (var PERSISTENCE_STORAGE_GROUPS_3 = __values(persistence_key_policy_1.PERSISTENCE_STORAGE_GROUPS), PERSISTENCE_STORAGE_GROUPS_3_1 = PERSISTENCE_STORAGE_GROUPS_3.next(); !PERSISTENCE_STORAGE_GROUPS_3_1.done; PERSISTENCE_STORAGE_GROUPS_3_1 = PERSISTENCE_STORAGE_GROUPS_3.next()) {
var group = PERSISTENCE_STORAGE_GROUPS_3_1.value;
storage_1.localStore._remove(this._groupEntryName(group));
}
}
catch (e_3_1) { e_3 = { error: e_3_1 }; }
finally {
try {
if (PERSISTENCE_STORAGE_GROUPS_3_1 && !PERSISTENCE_STORAGE_GROUPS_3_1.done && (_b = PERSISTENCE_STORAGE_GROUPS_3.return)) _b.call(PERSISTENCE_STORAGE_GROUPS_3);
}
finally { if (e_3) throw e_3.error; }
}
}
// The main entry is gone, so its bookkeeping must reset for the next
// save to write through. When the group entries are kept, so is their
// fingerprint/persisted state — that is what lets the following save
// recognise them as unchanged and skip the rewrite.
if (keepGroupEntries) {
delete this._slotState[MAIN_STORAGE_SLOT];
}
else {
this._slotState = {};
}
this._lastSeenCookiePropertiesFingerprint = undefined;
this._lastSeenMainCookieValue = undefined;
};
// removes the storage entry and deletes all loaded data
// forced name for tests
PostHogPersistence.prototype.clear = function () {
this.remove();
this.props = {};
};
/**
* @param {Object} props
* @param {*=} default_value
* @param {number=} days
*/
PostHogPersistence.prototype.register_once = function (props, default_value, days) {
var _this = this;
if ((0, core_2.isObject)(props)) {
// Explicit local mutations are newer than any unobserved sibling
// snapshot. Reconcile first so the caller's values win afterward.
this.syncCookieProperties();
if ((0, core_1.isUndefined)(default_value)) {
default_value = 'None';
}
this._expire_days = (0, core_1.isUndefined)(days) ? this._default_expiry : days;
var hasChanges_1 = false;
(0, general_utils_1.each)(props, function (val, prop) {
if (!_this.props.hasOwnProperty(prop) || _this.props[prop] === default_value) {
_this._setProp(prop, val);
hasChanges_1 = true;
}
});
if (hasChanges_1) {
this.save();
return true;
}
}
return false;
};
/**
* @param {Object} props
* @param {number=} days
*/
PostHogPersistence.prototype.register = function (props, days) {
var _this = this;
if ((0, core_2.isObject)(props)) {
this.syncCookieProperties();
this._expire_days = (0, core_1.isUndefined)(days) ? this._default_expiry : days;
var hasChanges_2 = false;
(0, general_utils_1.each)(props, function (val, prop) {
if (props.hasOwnProperty(prop) && (_this.props[prop] !== val || (0, core_2.isObject)(val) || (0, core_1.isArray)(val))) {
_this._setProp(prop, val);
hasChanges_2 = true;
}
});
if (hasChanges_2) {
this.save();
return true;
}
}
return false;
};
PostHogPersistence.prototype.unregister = function (propOrProps) {
var e_4, _a;
this.syncCookieProperties();
var props = typeof propOrProps === 'string' ? [propOrProps] : propOrProps;
var hasChanges = false;
try {
for (var props_1 = __values(props), props_1_1 = props_1.next(); !props_1_1.done; props_1_1 = props_1.next()) {
var prop = props_1_1.value;
if (prop in this.props) {
this._deleteProp(prop);
hasChanges = true;
}
}
}
catch (e_4_1) { e_4 = { error: e_4_1 }; }
finally {
try {
if (props_1_1 && !props_1_1.done && (_a = props_1.return)) _a.call(props_1);
}
finally { if (e_4) throw e_4.error; }
}
if (hasChanges) {
this.save();
}
};
PostHogPersistence.prototype.update_campaign_params = function () {
var currentUrl = globals_1.document === null || globals_1.document === void 0 ? void 0 : globals_1.document.URL;
if (currentUrl === this._campaign_params_url) {
return;
}
var campaignParams = (0, event_utils_1.getCampaignParams)(this._config.custom_campaign_params, this._config.mask_personal_data_properties, this._config.custom_personal_data_properties);
// only save campaign params if there were any
if (!(0, core_2.isEmptyObject)((0, general_utils_1.stripEmptyProperties)(campaignParams))) {
this.register(campaignParams);
}
this._campaign_params_url = currentUrl;
};
PostHogPersistence.prototype.update_search_keyword = function () {
this.register((0, event_utils_1.getSearchInfo)());
};
PostHogPersistence.prototype.update_referrer_info = function () {
this.register_once((0, event_utils_1.getReferrerInfo)(), undefined);
};
PostHogPersistence.prototype.set_initial_person_info = function () {
var _a;
if (this.props[constants_1.INITIAL_CAMPAIGN_PARAMS] || this.props[constants_1.INITIAL_REFERRER_INFO]) {
// the user has initial properties stored the previous way, don't save them again
return;
}
this.register_once((_a = {},
_a[constants_1.INITIAL_PERSON_INFO] = (0, event_utils_1.getPersonInfo)(this._config.mask_personal_data_properties, this._config.custom_personal_data_properties, this._config.disable_capture_url_hashes),
_a), undefined);
};
PostHogPersistence.prototype.get_initial_props = function () {
var _this = this;
var p = {};
// this section isn't written to anymore, but we should keep reading from it for backwards compatibility
// for a while
(0, general_utils_1.each)([constants_1.INITIAL_REFERRER_INFO, constants_1.INITIAL_CAMPAIGN_PARAMS], function (key) {
var initialReferrerInfo = _this.props[key];
if (initialReferrerInfo) {
(0, general_utils_1.each)(initialReferrerInfo, function (v, k) {
p['$initial_' + (0, core_2.stripLeadingDollar)(k)] = v;
});
}
});
var initialPersonInfo = this.props[constants_1.INITIAL_PERSON_INFO];
if (initialPersonInfo) {
var initialPersonProps = (0, event_utils_1.getInitialPersonPropsFromInfo)(initialPersonInfo, this._config.disable_capture_url_hashes);
(0, general_utils_1.extend)(p, initialPersonProps);
}
return p;
};
// safely fills the passed in object with stored properties,
// does not override any properties defined in both
// returns the passed in object
PostHogPersistence.prototype.safe_merge = function (props) {
(0, general_utils_1.each)(this.props, function (val, prop) {
if (!(prop in props)) {
props[prop] = val;
}
});
return props;
};
PostHogPersistence.prototype.update_config = function (config, oldConfig, isDisabled) {
var persistenceModeChanged = config.persistence !== oldConfig.persistence;
var cookiePersistedPropertiesChanged = !isArrayContentsEqual(config.cookie_persisted_properties || [], oldConfig.cookie_persisted_properties || []);
var persistenceChanged = persistenceModeChanged || cookiePersistedPropertiesChanged;
var cookiePrecedenceChanged = config.cookieWinsOnConflict !== oldConfig.cookieWinsOnConflict;
var disabled = config['disable_persistence'] || !!isDisabled;
var reEnabling = !!this._disabled && !disabled;
// Reconcile through the old routing before replacing it. PostHog mutates
// its config object before calling this method, so use the old snapshot
// explicitly rather than relying on this._config. A configuration change
// that re-enables persistence may read the shared cookie before writes resume.
if (!disabled) {
this._syncCookieProperties(oldConfig, reEnabling);
}
this._config = config;
// A newly enabled precedence policy or cookie-backed key set must apply
// to the current cookie even if the old-policy sync just fingerprinted it.
if (!disabled && (persistenceModeChanged || cookiePersistedPropertiesChanged || cookiePrecedenceChanged)) {
this._lastSeenCookiePropertiesFingerprint = undefined;
this._lastSeenMainCookieValue = undefined;
// Newly added cookie-backed keys are still local-only in the current
// cookie. Keep the old key set authoritative until migration writes
// the first snapshot under the new configuration.
this._syncCookieProperties(__assign(__assign({}, config), { cookie_persisted_properties: oldConfig.cookie_persisted_properties }), reEnabling);
}
// `_buildStorage` re-resolves both the backend and `_splitStorageEligible`,
// so on a persistence change build the new store first, then derive the
// split flag from the fresh eligibility. The new backend may no longer be
// split-eligible (e.g. localStorage -> memory).
var newStore = persistenceChanged || cookiePrecedenceChanged ? this._buildStorage(config) : this._storage;
var wantSplit = this._resolveSplitStorage(config);
var storageMigration = persistenceChanged || wantSplit !== this._splitStorage;
var cookieOptionsChanged = config['cross_subdomain_cookie'] !== this._cross_subdomain || config['secure_cookie'] !== this._secure;
var cookieSyncSuppressionStarted = !disabled && (storageMigration || cookieOptionsChanged) && this._beginCookieSyncSuppression(reEnabling);
// _buildStorage has already resolved the new backend eligibility, while
// this._storage still points at the old backend until migration clears it.
// Keep that authoritative migration snapshot intact until the swap.
this._storageMigrationInProgress = storageMigration;
try {
this._default_expiry = this._expire_days = config['cookie_expiration'];
this.set_disabled(disabled);
this.set_cross_subdomain(config['cross_subdomain_cookie']);
this.set_secure(config['secure_cookie']);
// Migrate when the backend changed or the split routing flipped at
// runtime, e.g. set_config({ split_storage: true }). Either way we
// clear the old layout and re-save in the new routing.
if (storageMigration) {
var props = this.props;
this.clear();
this._storage = newStore;
this._splitStorage = wantSplit;
this.props = props;
this.save();
}
else if (cookiePrecedenceChanged) {
this._storage = newStore;
if (!disabled) {
// Persist the reconciled snapshot immediately. In particular,
// when precedence is disabled, a later reload must not let the
// stale localStorage value become authoritative again.
delete this._slotState[MAIN_STORAGE_SLOT];
this._writeNow();
}
}
}
finally {
this._storageMigrationInProgress = false;
if (cookieSyncSuppressionStarted) {
// Cookie option and storage migrations clear the shared cookie.
// Restore one complete authoritative snapshot before another
// subdomain can initialize.
this._endCookieSyncSuppression();
}
}
};
PostHogPersistence.prototype.set_disabled = function (disabled) {
this._disabled = disabled;
if (this._disabled) {
this.remove();
}
else {
this.save();
}
};
PostHogPersistence.prototype.set_cross_subdomain = function (cross_subdomain) {
if (cross_subdomain !== this._cross_subdomain) {
this._cross_subdomain = cross_subdomain;
this.remove({ keepGroupEntries: true });
this.save();
}
};
PostHogPersistence.prototype.set_secure = function (secure) {
if (secure !== this._secure) {
this._secure = secure;
this.remove({ keepGroupEntries: true });
this.save();
}
};
PostHogPersistence.prototype.set_event_timer = function (event_name, timestamp) {
var timers = this.props[constants_1.EVENT_TIMERS_KEY] || {};
timers[event_name] = timestamp;
this._setProp(constants_1.EVENT_TIMERS_KEY, timers);
this.save();
};
PostHogPersistence.prototype.remove_event_timer = function (event_name) {
var timers = this.props[constants_1.EVENT_TIMERS_KEY] || {};
var timestamp = timers[event_name];
if (!(0, core_1.isUndefined)(timestamp)) {
delete timers[event_name];
this._setProp(constants_1.EVENT_TIMERS_KEY, timers);
this.save();
}
return timestamp;
};
PostHogPersistence.prototype.get_property = function (prop) {
return this.props[prop];
};
PostHogPersistence.prototype.set_property = function (prop, to) {
this._setProp(prop, to);
this.save();
};
PostHogPersistence.prototype._setProp = function (prop, to, trackLocalChange) {
var _a;
if (trackLocalChange === void 0) { trackLocalChange = true; }
var previousValue = this.props[prop];
this.props[prop] = to;
if (!trackLocalChange) {
return;
}
if ((prop === constants_1.DISTINCT_ID || prop === constants_1.USER_STATE) && previousValue !== to) {
this._localIdentityChangePending = true;
}
this._markPendingCrossTabFeatureFlagChanges(prop, previousValue, to);
// A volatile value change never dirties its group — it changes on every
// remote load and would otherwise force a rewrite of the large entry per
// load. Deletions still dirty (see _deleteProp): presence is part of the
// fingerprint, the moving value is not.
if (!((_a = (0, persistence_key_policy_1.getPersistenceKeyPolicy)(prop)) === null || _a === void 0 ? void 0 : _a.volatile)) {
this._markGroupDirty(prop);
}
};
PostHogPersistence.prototype._deleteProp = function (prop, trackLocalChange) {
if (trackLocalChange === void 0) { trackLocalChange = true; }
delete this.props[prop];
if (!trackLocalChange) {
return;
}
if (isCrossTabFeatureFlagKey(prop)) {
this._setCrossTabFeatureFlagChangesPending(prop, true);
}
this._markGroupDirty(prop);
};
// Mark the prop's storage group dirty so its entry is re-serialized on the
// next write. Props with no group live in the main blob, which always writes.
PostHogPersistence.prototype._markGroupDirty = function (prop) {
var _a;
var group = (_a = (0, persistence_key_policy_1.getPersistenceKeyPolicy)(prop)) === null || _a === void 0 ? void 0 : _a.storageGroup;
if (group) {
this._slotWriteState(group).dirty = true;
}
};
// The write-bookkeeping record for a slot, created on first access.
PostHogPersistence.prototype._slotWriteState = function (slot) {
return this._slotState[slot] || (this._slotState[slot] = {});
};
return PostHogPersistence;
}());
exports.PostHogPersistence = PostHogPersistence;
//# sourceMappingURL=posthog-persistence.js.map