posthog-js
Version:
Posthog-js allows you to automatically capture usage and send events to PostHog.
4,391 lines • 211 kB
JavaScript
"use strict";
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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
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.");
};
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 __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PostHog = exports.configRenames = exports.defaultConfig = void 0;
exports.init_from_snippet = init_from_snippet;
exports.init_as_module = init_as_module;
var config_1 = __importDefault(require("./config"));
var consent_1 = require("./consent");
var constants_1 = require("./constants");
var autocapture_utils_1 = require("@posthog/browser-common/utils/autocapture-utils");
var dead_clicks_autocapture_1 = require("./extensions/dead-clicks-autocapture");
var segment_integration_1 = require("./extensions/segment-integration");
var sentry_integration_1 = require("./extensions/sentry-integration");
var page_view_1 = require("./page-view");
var posthog_persistence_1 = require("./posthog-persistence");
var posthog_surveys_types_1 = require("./posthog-surveys-types");
var posthog_product_tours_types_1 = require("./posthog-product-tours-types");
var rate_limiter_1 = require("./rate-limiter");
var remote_config_1 = require("./remote-config");
var request_1 = require("./request");
var request_queue_1 = require("./request-queue");
var retry_queue_1 = require("./retry-queue");
var scroll_manager_1 = require("./scroll-manager");
var session_props_1 = require("./session-props");
var sessionid_1 = require("./sessionid");
var storage_1 = require("./storage");
var types_1 = require("./types");
var general_utils_1 = require("@posthog/browser-common/utils/general-utils");
var blocked_uas_1 = require("@posthog/browser-common/utils/blocked-uas");
var device_model_utils_1 = require("@posthog/browser-common/utils/device-model-utils");
var event_utils_1 = require("@posthog/browser-common/utils/event-utils");
var globals_1 = require("@posthog/browser-common/utils/globals");
var globals_2 = require("./utils/globals");
var logger_1 = require("@posthog/browser-common/utils/logger");
var property_utils_1 = require("@posthog/browser-common/utils/property-utils");
var request_router_1 = require("./utils/request-router");
var simple_event_emitter_1 = require("@posthog/browser-common/utils/simple-event-emitter");
var survey_utils_1 = require("./utils/survey-utils");
var core_1 = require("@posthog/core");
var uuidv7_1 = require("@posthog/browser-common/utils/uuidv7");
var external_integration_1 = require("./extensions/external-integration");
var browser_client_1 = require("./extensions/browser-client");
var instances = {};
// Tracks re-entrant calls to _execute_array. Used to detect when a third-party
// Proxy (e.g., TikTok's in-app browser) wraps window.posthog and converts method
// calls into push() calls, which would otherwise cause infinite recursion.
var _executeArrayDepth = 0;
var __NOOP = function () { };
var CONSENT_COOKIELESS_WARN = 'Consent opt in/out is not valid with cookieless_mode="always" and will be ignored';
var RESET_CONSENT_WARN = 'reset() cleared the stored consent, and capturing is now off because of `opt_out_capturing_by_default`. ' +
'Call opt_in_capturing() again, and prefer calling reset() before opting in rather than after.';
var SURVEYS_NOT_AVAILABLE = 'Surveys module not available';
var SANITIZE_DEPRECATED = 'sanitize_properties is deprecated. Use before_send instead';
var DENYLIST_INVALID = 'Invalid value for property_denylist config: ';
// Transport-level keys the browser SDK carries inside event properties (unlike other SDKs,
// where they live outside `properties`). They are out of scope of the minimal
// $feature_flag_called allowlist and must survive minimization for ingestion to work.
var FLAG_CALLED_TRANSPORT_PROPERTY_KEYS = ['token', 'distinct_id', constants_1.COOKIELESS_MODE_FLAG_PROPERTY];
var PRIMARY_INSTANCE_NAME = 'posthog';
/*
* Dynamic... constants? Is that an oxymoron?
*/
// http://hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/
// https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#withCredentials
// IE<10 does not support cross-origin XHR's but script tags
// with defer won't block window.onload; ENQUEUE_REQUESTS
// should only be true for Opera<12
var ENQUEUE_REQUESTS = !request_1.SUPPORTS_REQUEST && (globals_1.userAgent === null || globals_1.userAgent === void 0 ? void 0 : globals_1.userAgent.indexOf('MSIE')) === -1 && (globals_1.userAgent === null || globals_1.userAgent === void 0 ? void 0 : globals_1.userAgent.indexOf('Mozilla')) === -1;
var getSessionRecordingDefaults = function (defaults) {
var sessionRecording = {};
if (!defaults || defaults === 'unset') {
return sessionRecording;
}
if (defaults >= '2025-11-30') {
sessionRecording.strictMinimumDuration = true;
}
if (defaults >= '2026-05-30') {
sessionRecording.canvasCapture = { resolutionScale: 0.6 };
}
if (defaults >= '2026-06-25') {
sessionRecording.streamNetworkBody = true;
}
if (defaults >= '2026-08-30') {
sessionRecording.captureJsonLd = true;
}
return sessionRecording;
};
var defaultsThatVaryByConfig = function (defaults) { return ({
rageclick: defaults && defaults >= '2026-05-30'
? { content_ignorelist: autocapture_utils_1.DEFAULT_CONTENT_IGNORELIST_WITH_STEPPERS, ignore_text_selection: true }
: defaults && defaults >= '2025-11-30'
? { content_ignorelist: true }
: true,
capture_pageview: defaults && defaults >= '2025-05-24' ? 'history_change' : true,
session_recording: getSessionRecordingDefaults(defaults),
external_scripts_inject_target: defaults && defaults >= '2026-01-30' ? 'head' : 'body',
internal_or_test_user_hostname: defaults && defaults >= '2026-01-30' ? /^(localhost|127\.0\.0\.1)$/ : undefined,
persistence_save_debounce_ms: defaults && defaults >= '2026-05-30' ? 250 : 0,
split_storage: !!(defaults && defaults >= '2026-05-30'),
detect_google_search_app: !!(defaults && defaults >= '2026-05-30'),
disable_capture_url_hashes: !!(defaults && defaults >= '2026-06-25'),
cookieWinsOnConflict: !!(defaults && defaults !== 'unset' && defaults >= '2026-08-29'),
}); };
// NOTE: Remember to update `types.ts` when changing a default value
// to guarantee documentation is up to date, make sure to also update our website docs
// NOTE²: This shouldn't ever change because we try very hard to be backwards-compatible
var defaultConfig = function (defaults) {
var _a;
return (__assign({ api_host: 'https://us.i.posthog.com', flags_api_host: null, ui_host: null, asset_host: null, token: '', autocapture: true, cross_subdomain_cookie: (0, general_utils_1.isCrossDomainCookie)(globals_1.document === null || globals_1.document === void 0 ? void 0 : globals_1.document.location), persistence: 'localStorage+cookie', persistence_name: '', cookie_persisted_properties: [], loaded: __NOOP, save_campaign_params: true, custom_campaign_params: [], custom_blocked_useragents: [], save_referrer: true, capture_pageleave: 'if_capture_pageview', defaults: defaults !== null && defaults !== void 0 ? defaults : 'unset', __preview_deferred_init_extensions: false, __preview_external_dependency_versioned_paths: false, __preview_cookie_wins_on_conflict: false, debug: (globals_1.location && (0, core_1.isString)(globals_1.location === null || globals_1.location === void 0 ? void 0 : globals_1.location.search) && globals_1.location.search.indexOf('__posthog_debug=true') !== -1) || false, cookie_expiration: 365, upgrade: false, disable_session_recording: false, disable_persistence: false, disable_web_experiments: true, disable_surveys: false, disable_surveys_automatic_display: false, disable_conversations: false, disable_product_tours: false, disableDeviceModel: false, disable_external_dependency_loading: false, strict_script_versioning: 'fallback', enable_recording_console_log: undefined, secure_cookie: ((_a = globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.location) === null || _a === void 0 ? void 0 : _a.protocol) === 'https:', ip: false, opt_out_capturing_by_default: false, opt_out_persistence_by_default: false, opt_out_useragent_filter: false, opt_out_capturing_persistence_type: 'localStorage', consent_persistence_name: null, opt_out_capturing_cookie_prefix: null, opt_in_site_apps: false, property_denylist: [], respect_dnt: false, sanitize_properties: null, request_headers: {}, request_batching: true, properties_string_max_length: 65535, mask_all_element_attributes: false, mask_all_text: false, mask_personal_data_properties: false, custom_personal_data_properties: [], advanced_disable_flags: false, advanced_disable_decide: false, advanced_disable_feature_flags: false, advanced_disable_feature_flags_on_first_load: false, advanced_only_evaluate_survey_feature_flags: false, advanced_feature_flags_dedup_per_session: false, advanced_enable_surveys: false, advanced_disable_toolbar_metrics: false, feature_flag_request_timeout_ms: 3000, surveys_request_timeout_ms: constants_1.SURVEYS_REQUEST_TIMEOUT_MS, on_request_error: function (res) {
var error = 'Bad HTTP status: ' + res.statusCode + ' ' + res.text;
logger_1.logger.error(error);
}, get_device_id: function (uuid) { return uuid; }, capture_performance: undefined, name: 'posthog', bootstrap: {}, disable_compression: false, session_idle_timeout_seconds: 30 * 60, person_profiles: constants_1.PERSON_PROFILES_IDENTIFIED_ONLY, before_send: undefined, get_current_url: undefined, request_queue_config: { flush_interval_ms: request_queue_1.DEFAULT_FLUSH_INTERVAL_MS }, error_tracking: {},
// Used for internal testing
_onCapture: __NOOP }, defaultsThatVaryByConfig(defaults)));
};
exports.defaultConfig = defaultConfig;
var CONFIG_RENAMES = [
['process_person', 'person_profiles'],
['xhr_headers', 'request_headers'],
['cookie_name', 'persistence_name'],
['disable_cookie', 'disable_persistence'],
['__preview_disable_beacon', 'disable_beacon'],
['store_google', 'save_campaign_params'],
['verbose', 'debug'],
['__preview_cookie_wins_on_conflict', 'cookieWinsOnConflict'],
];
var configRenames = function (origConfig) {
var e_1, _a;
var renames = {};
try {
for (var CONFIG_RENAMES_1 = __values(CONFIG_RENAMES), CONFIG_RENAMES_1_1 = CONFIG_RENAMES_1.next(); !CONFIG_RENAMES_1_1.done; CONFIG_RENAMES_1_1 = CONFIG_RENAMES_1.next()) {
var _b = __read(CONFIG_RENAMES_1_1.value, 2), oldKey = _b[0], newKey = _b[1];
if (!(0, core_1.isUndefined)(origConfig[oldKey])) {
;
renames[newKey] = origConfig[oldKey];
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (CONFIG_RENAMES_1_1 && !CONFIG_RENAMES_1_1.done && (_a = CONFIG_RENAMES_1.return)) _a.call(CONFIG_RENAMES_1);
}
finally { if (e_1) throw e_1.error; }
}
// on_xhr_error is not present, as the type is different to on_request_error
// the original config takes priority over the renames
var newConfig = (0, general_utils_1.extend)({}, renames, origConfig);
// Maintain backwards compatibility for the deprecated preview option while
// letting the supported options take precedence when they are provided.
var previewExternalDependencyVersionedPaths = origConfig.__preview_external_dependency_versioned_paths;
if (!(0, core_1.isUndefined)(previewExternalDependencyVersionedPaths)) {
if ((0, core_1.isUndefined)(origConfig.strict_script_versioning)) {
newConfig.strict_script_versioning = !!previewExternalDependencyVersionedPaths;
}
if ((0, core_1.isString)(previewExternalDependencyVersionedPaths) && (0, core_1.isUndefined)(origConfig.asset_host)) {
newConfig.asset_host = previewExternalDependencyVersionedPaths;
}
}
// merge property_blacklist into property_denylist
if ((0, core_1.isArray)(origConfig.property_blacklist)) {
if ((0, core_1.isUndefined)(origConfig.property_denylist)) {
newConfig.property_denylist = origConfig.property_blacklist;
}
else if ((0, core_1.isArray)(origConfig.property_denylist)) {
newConfig.property_denylist = __spreadArray(__spreadArray([], __read(origConfig.property_blacklist), false), __read(origConfig.property_denylist), false);
}
else {
logger_1.logger.error(DENYLIST_INVALID + origConfig.property_denylist);
}
}
return newConfig;
};
exports.configRenames = configRenames;
var DeprecatedWebPerformanceObserver = /** @class */ (function () {
function DeprecatedWebPerformanceObserver() {
this.__forceAllowLocalhost = false;
}
Object.defineProperty(DeprecatedWebPerformanceObserver.prototype, "_forceAllowLocalhost", {
get: function () {
return this.__forceAllowLocalhost;
},
set: function (value) {
logger_1.logger.error('WebPerformanceObserver is deprecated and has no impact on network capture. Use `_forceAllowLocalhostNetworkCapture` on `posthog.sessionRecording`');
this.__forceAllowLocalhost = value;
},
enumerable: false,
configurable: true
});
return DeprecatedWebPerformanceObserver;
}());
/**
*
* This is the SDK reference for the PostHog JavaScript Web SDK.
* You can learn more about example usage in the
* [JavaScript Web SDK documentation](/docs/libraries/js).
* You can also follow [framework specific guides](/docs/frameworks)
* to integrate PostHog into your project.
*
* This SDK is designed for browser environments.
* Use the PostHog [Node.js SDK](/docs/libraries/node) for server-side usage.
*
* @constructor
*/
var PostHog = /** @class */ (function () {
/**
* Creates an uninitialized PostHog instance.
*
* @remarks
* Most browser applications should use the default exported singleton and call `posthog.init()`.
* Construct a new instance only when you need to manage a separate SDK instance manually.
*
* @example
* ```js
* const instance = new PostHog()
* instance.init('<ph_project_api_key>', { api_host: 'https://us.i.posthog.com' })
* ```
*/
function PostHog() {
var _this = this;
var _a;
this.webPerformance = new DeprecatedWebPerformanceObserver();
this._personProcessingSetOncePropertiesSent = false;
this.version = config_1.default.LIB_VERSION;
// Keys registered via register_for_session — cleared when the PostHog session rotates
this._sessionRegisteredPropKeys = new Set();
this._sessionRegisteredPropertiesStorageKey = '';
this._internalEventEmitter = new simple_event_emitter_1.SimpleEventEmitter();
this._extensions = [];
this._extensionEventPropertyProducers = [];
this._hasStableInitialDistinctId = false;
this._hasWarnedAboutVolatileIdentity = false;
/** @deprecated - deprecated in 1.241.0, use `calculateEventProperties` instead */
this._calculate_event_properties = this.calculateEventProperties.bind(this);
this.config = (0, exports.defaultConfig)();
this.SentryIntegration = sentry_integration_1.SentryIntegration;
this.sentryIntegration = function (options) { return (0, sentry_integration_1.sentryIntegration)(_this, options); };
this.__request_queue = [];
this.__loaded = false;
this.analyticsDefaultEndpoint = '/e/';
this._initialPageviewCaptured = false;
this._visibilityStateListener = null;
this._initialPersonProfilesConfig = null;
this._cachedPersonProperties = null;
this.scrollManager = new scroll_manager_1.ScrollManager(this);
this.pageViewManager = new page_view_1.PageViewManager(this);
this.rateLimiter = new rate_limiter_1.RateLimiter(this);
this.requestRouter = new request_router_1.RequestRouter(this);
this.consent = new consent_1.ConsentManager(this);
this.externalIntegrations = new external_integration_1.ExternalIntegrations(this);
// Eagerly construct extensions from default classes so they're available before init().
// For the slim bundle, these remain undefined until _initExtensions sets them from config.
var ext = (_a = PostHog.__defaultExtensionClasses) !== null && _a !== void 0 ? _a : {};
this.featureFlags = ext.featureFlags && new ext.featureFlags(this);
this.toolbar = ext.toolbar && new ext.toolbar(this);
this.surveys = ext.surveys && new ext.surveys(this);
this.conversations = ext.conversations && new ext.conversations(this);
this.logs = ext.logs && new ext.logs(this);
this.metrics = ext.metrics && new ext.metrics(this);
this.experiments = ext.experiments && new ext.experiments(this);
this.exceptions = ext.exceptions && new ext.exceptions(this);
// NOTE: See the property definition for deprecation notice
this.people = {
set: function (prop, to, callback) {
var _a;
var setProps = (0, core_1.isString)(prop) ? (_a = {}, _a[prop] = to, _a) : prop;
_this.setPersonProperties(setProps);
callback === null || callback === void 0 ? void 0 : callback({});
},
set_once: function (prop, to, callback) {
var _a;
var setProps = (0, core_1.isString)(prop) ? (_a = {}, _a[prop] = to, _a) : prop;
_this.setPersonProperties(undefined, setProps);
callback === null || callback === void 0 ? void 0 : callback({});
},
};
this.on('eventCaptured', function (data) { return logger_1.logger.info("send \"".concat(data === null || data === void 0 ? void 0 : data.event, "\""), data); });
}
PostHog.prototype._removeExtension = function (extension) {
if (!extension) {
return;
}
var idx = this._extensions.indexOf(extension);
if (idx !== -1) {
this._extensions.splice(idx, 1);
}
};
PostHog.prototype._replaceExtension = function (oldExt, newExt) {
var _a;
this._removeExtension(oldExt);
this._extensions.push(newExt);
(_a = newExt.initialize) === null || _a === void 0 ? void 0 : _a.call(newExt);
return newExt;
};
PostHog.prototype._inCookielessMode = function () {
return (this.config.cookieless_mode === constants_1.COOKIELESS_ALWAYS ||
(this.config.cookieless_mode === constants_1.COOKIELESS_ON_REJECT && this.consent.isRejected()));
};
// memory, sessionStorage, and disable_persistence all drop durable identity: the distinct ID lives in
// memory for a single page, so each load mints a fresh one that identify() then merges onto the person,
// eventually pushing it past the distinct-ID display limit and hiding its events from person pages and the
// session tab. Warn once, when person processing is first requested, unless a stable ID is supplied.
// Cookieless mode registers a stable sentinel instead of a new uuid, so it is excluded.
PostHog.prototype._warnIfVolatileIdentityWithoutStableId = function () {
if (this._hasWarnedAboutVolatileIdentity) {
return;
}
// The Segment integration owns identity and supplies its stable user/anonymous ID before events load.
if (this.config.segment) {
return;
}
if (this._inCookielessMode()) {
return;
}
var volatileIdentityPersistence = this.config.persistence === 'memory' || this.config.persistence === 'sessionStorage';
if (!volatileIdentityPersistence && !this.config.disable_persistence) {
return;
}
// Only an ID supplied at init is guaranteed to be restored on the next load. setIdentity() and
// set_config({ bootstrap }) mutate these config fields at runtime without replacing the current distinct ID.
if (this._hasStableInitialDistinctId) {
return;
}
var cause;
var lifetime;
var fix;
if (volatileIdentityPersistence) {
cause = "persistence is set to '".concat(this.config.persistence, "'");
// sessionStorage survives same-tab reloads and navigation, so it mints a fresh ID per
// tab/window; memory is dropped on every load.
lifetime =
this.config.persistence === 'memory' ? 'on every page load' : 'for every new browser tab or window';
fix =
"Either set persistence to 'localStorage+cookie', or keep this persistence and pass a stable ID through bootstrap.distinctID.";
}
else {
cause = 'persistence is disabled (disable_persistence is true)';
lifetime = 'on every page load';
fix =
'Either set disable_persistence to false, or keep persistence disabled and pass a stable ID through bootstrap.distinctID.';
}
this._hasWarnedAboutVolatileIdentity = true;
// Unlike logger.warn(), this warning must be visible with the normal debug:false configuration.
// eslint-disable-next-line no-console
console.warn('[PostHog.js]', "".concat(cause, " but no bootstrap.distinctID was provided. ") +
"PostHog will mint a new distinct ID ".concat(lifetime, ", so calling identify() merges a new ") +
'ID onto the person each time. A person can then pass the distinct-ID limit and its events stop ' +
"appearing on person pages and the session tab. ".concat(fix));
};
/**
* Replace a stale cookieless sentinel distinct_id with an anonymous device id.
*
* Consent lives in shared cookie/localStorage, but the in-memory distinct_id does not. A tab
* that started in cookieless mode holds the `$posthog_cookieless` sentinel as its distinct_id;
* when consent is flipped to opted-in in another tab, this tab stops being cookieless
* (`_inCookielessMode()` becomes false) while the sentinel remains in memory. If that sentinel
* then escapes into a real event — or into `identify()` as `$anon_distinct_id` — the plugin
* server never hashes it and every affected browser collapses onto a single
* `$posthog_cookieless` person.
*/
PostHog.prototype._healCookielessSentinelDistinctId = function () {
if (this._inCookielessMode() || this.get_distinct_id() !== constants_1.COOKIELESS_SENTINEL_VALUE) {
return;
}
var persistence = this.persistence;
if (!persistence) {
return;
}
if (!this._is_persistence_disabled()) {
// The tab that handled the shared consent change already persisted its replacement
// identity. Reload it before re-enabling this stale tab's persistence so we neither
// split one browser across device IDs nor overwrite the shared identity with stale state.
persistence.load(true);
}
var currentDistinctId = this.get_distinct_id();
if (!currentDistinctId || currentDistinctId === constants_1.COOKIELESS_SENTINEL_VALUE) {
var uuid = this.config.get_device_id((0, uuidv7_1.uuidv7)());
this.register({
distinct_id: uuid,
$device_id: uuid,
});
// distinct id == $device_id is a proxy for an anonymous user
persistence.set_property(constants_1.USER_STATE, constants_1.USER_STATE_ANONYMOUS);
}
// A cross-tab consent change bypasses opt_in_capturing() in this tab, so reconcile its
// persistence state now that the shared consent store says persistence is allowed.
this._sync_opt_out_with_persistence();
};
Object.defineProperty(PostHog.prototype, "decideEndpointWasHit", {
// Legacy property to support existing usage - this isn't technically correct but it's what it has always been - a proxy for flags being loaded
/** @deprecated Use `flagsEndpointWasHit` instead. We migrated to using a new feature flag endpoint and the new method is more semantically accurate */
get: function () {
var _a, _b;
return (_b = (_a = this.featureFlags) === null || _a === void 0 ? void 0 : _a.hasLoadedFlags) !== null && _b !== void 0 ? _b : false;
},
enumerable: false,
configurable: true
});
Object.defineProperty(PostHog.prototype, "flagsEndpointWasHit", {
/**
* Whether feature flags have been initialized for this instance.
*
* @remarks
* Despite the name, this is a proxy for processed feature flag values. It may be true for
* bootstrapped flags before a network request completes.
*
* @returns True once the SDK has processed feature flag values.
*/
get: function () {
var _a, _b;
return (_b = (_a = this.featureFlags) === null || _a === void 0 ? void 0 : _a.hasLoadedFlags) !== null && _b !== void 0 ? _b : false;
},
enumerable: false,
configurable: true
});
// Initialization methods
/**
* Initializes a new instance of the PostHog capturing object.
*
* @remarks
* All new instances are added to the main posthog object as sub properties (such as
* `posthog.library_name`) and also returned by this function. [Learn more about configuration options](https://posthog.com/docs/libraries/js/config)
*
* @example
* ```js
* // basic initialization
* posthog.init('<ph_project_api_key>', {
* api_host: '<ph_client_api_host>'
* })
* ```
*
* @example
* ```js
* // multiple instances
* posthog.init('<ph_project_api_key>', {}, 'project1')
* posthog.init('<ph_project_api_key>', {}, 'project2')
* ```
*
* @public
*
* @param token - Your PostHog API token
* @param config - A dictionary of config options to override
* @param name - The name for the new posthog instance that you want created
*
* {@label Initialization}
*
* @returns The newly initialized PostHog instance
*/
PostHog.prototype.init = function (token, config, name) {
var _a;
if (!name || name === PRIMARY_INSTANCE_NAME) {
// This means we are initializing the primary instance (i.e. this)
return this._init(token, config, name);
}
else {
var namedPosthog = (_a = instances[name]) !== null && _a !== void 0 ? _a : new PostHog();
namedPosthog._init(token, config, name);
instances[name] = namedPosthog;
instances[PRIMARY_INSTANCE_NAME][name] = namedPosthog;
return namedPosthog;
}
};
// posthog._init(token:string, config:object, name:string)
//
// This function sets up the current instance of the posthog
// library. The difference between this method and the init(...)
// method is this one initializes the actual instance, whereas the
// init(...) method sets up a new library and calls _init on it.
//
// Note that there are operations that can be asynchronous, so we
// accept a callback that is called when all the asynchronous work
// is done. Note that we do not use promises because we want to be
// IE11 compatible. We could use polyfills, which would make the
// code a bit cleaner, but will add some overhead.
//
PostHog.prototype._init = function (token, config, name) {
var _this = this;
var _a, _b, _c, _d;
if (config === void 0) { config = {}; }
var normalizedToken = (0, core_1.isString)(token) ? token.trim() : '';
if (!normalizedToken) {
logger_1.logger.critical('PostHog was initialized without a token. This likely indicates a misconfiguration. Please check the first argument passed to posthog.init()');
return this;
}
if (this.__loaded) {
// need to be able to log before having processed debug config
if (normalizedToken !== ((_a = this.config) === null || _a === void 0 ? void 0 : _a.token)) {
// A second init() with a different project token often means that someone is trying to send
// events to a second project without giving that instance a name.
// eslint-disable-next-line no-console
console.warn('[PostHog.js]', "You have already initialized PostHog with a different project token! Re-initializing is a no-op, so events will keep going to the project this instance was initialized with. To capture into a second project, load PostHog once, then initialize a named instance after the SDK has loaded, e.g. posthog.init('".concat(normalizedToken, "', { ... }, 'project2')"));
}
else {
// eslint-disable-next-line no-console
console.warn('[PostHog.js]', 'You have already initialized PostHog! Re-initializing is a no-op');
}
return this;
}
this.__loaded = true;
this.config = (0, exports.defaultConfig)(config.defaults); // fully overwritten by set_config right below
config.debug = this._checkLocalStorageForDebug(config.debug);
this._originalUserConfig = config; // Store original user config for migration
this._triggered_notifs = [];
if (config.person_profiles) {
this._initialPersonProfilesConfig = config.person_profiles;
}
else if (config.process_person) {
this._initialPersonProfilesConfig = config.process_person;
}
var baseConfig = (0, exports.defaultConfig)(config.defaults);
var userConfig = (0, exports.configRenames)(config);
var mergedConfig = (0, general_utils_1.extend)({}, baseConfig, userConfig, {
name: name,
token: normalizedToken,
});
// a partial user-supplied rageclick object should keep the date-gated defaults
// (e.g. content_ignorelist, ignore_text_selection) rather than replacing them wholesale
if ((0, core_1.isObject)(baseConfig.rageclick) && (0, core_1.isObject)(userConfig.rageclick)) {
mergedConfig.rageclick = (0, general_utils_1.extend)({}, baseConfig.rageclick, userConfig.rageclick);
}
// likewise a partial user-supplied session_recording keeps the date-gated top-level
// defaults (e.g. strictMinimumDuration, canvasCapture) it doesn't set. this is a shallow
// one-level merge: a user-supplied nested object (e.g. their own canvasCapture) still
// replaces the default one wholesale rather than merging into it.
if ((0, core_1.isObject)(baseConfig.session_recording) && (0, core_1.isObject)(userConfig.session_recording)) {
mergedConfig.session_recording = (0, general_utils_1.extend)({}, baseConfig.session_recording, userConfig.session_recording);
}
this.set_config(mergedConfig);
if (this.config.on_xhr_error) {
logger_1.logger.error('on_xhr_error is deprecated. Use on_request_error instead');
}
this.compression = config.disable_compression ? undefined : types_1.Compression.GZipJS;
var persistenceDisabled = this._is_persistence_disabled();
this.persistence = new posthog_persistence_1.PostHogPersistence(this.config, persistenceDisabled);
this.sessionPersistence =
this.config.persistence === 'sessionStorage' || this.config.persistence === 'memory'
? this.persistence
: // sessionStorage sibling shares the primary's storage name; it must not own/clean the split group entries
new posthog_persistence_1.PostHogPersistence(__assign(__assign({}, this.config), { persistence: 'sessionStorage' }), persistenceDisabled, false);
var persistenceName = this.config.persistence_name || this.config.token;
this._sessionRegisteredPropertiesStorageKey = 'ph_' + persistenceName + '_session_registered_properties';
if (this.config.persistence !== 'memory' && !persistenceDisabled && storage_1.sessionStore._is_supported()) {
var sessionRegisteredPropKeys = storage_1.sessionStore._parse(this._sessionRegisteredPropertiesStorageKey);
if ((0, core_1.isArray)(sessionRegisteredPropKeys)) {
sessionRegisteredPropKeys.forEach(function (key) {
if ((0, core_1.isString)(key)) {
_this._sessionRegisteredPropKeys.add(key);
}
});
}
}
else {
storage_1.sessionStore._remove(this._sessionRegisteredPropertiesStorageKey);
}
// should I store the initial person profiles config in persistence?
var initialPersistenceProps = __assign({}, this.persistence.props);
var initialSessionProps = __assign({}, this.sessionPersistence.props);
this.register({ $initialization_time: new Date().toISOString() });
this._requestQueue = new request_queue_1.RequestQueue(function (req) { return _this._send_retriable_request(req); }, this.config.request_queue_config);
this._retryQueue = new retry_queue_1.RetryQueue(this);
this.__request_queue = [];
var startInCookielessMode = this._inCookielessMode();
if (!startInCookielessMode) {
this.sessionManager = new sessionid_1.SessionIdManager(this);
this.sessionPropsManager = new session_props_1.SessionPropsManager(this, this.sessionManager, this.persistence);
// Clear user-registered session properties when the current PostHog session is replaced.
// Browser sessionStorage can outlive multiple PostHog sessions in the same tab.
this.sessionManager.onSessionId(function (_sessionId, _windowId, changeReason) {
if ((changeReason === null || changeReason === void 0 ? void 0 : changeReason.activityTimeout) ||
(changeReason === null || changeReason === void 0 ? void 0 : changeReason.sessionPastMaximumLength) ||
(changeReason === null || changeReason === void 0 ? void 0 : changeReason.crossTabAdoption)) {
_this._clearSessionRegisteredProps();
}
});
}
this._enrollFeatureFlags();
// Conditionally defer extension initialization based on config
if (this.config.__preview_deferred_init_extensions) {
// EXPERIMENTAL: Defer non-critical extension initialization to next tick
// This reduces main thread blocking during init
// while keeping critical path (persistence, sessions, capture) synchronous
logger_1.logger.info('Deferring extension initialization to improve startup performance');
setTimeout(function () {
_this._initExtensions(startInCookielessMode);
}, 0);
}
else {
// Legacy synchronous initialization (default for now)
logger_1.logger.info('Initializing extensions synchronously');
this._initExtensions(startInCookielessMode);
}
// if any instance on the page has debug = true, we set the
// global debug to be true
config_1.default.DEBUG = config_1.default.DEBUG || this.config.debug;
if (config_1.default.DEBUG) {
logger_1.logger.info('Starting in debug mode', {
this: this,
config: config,
thisC: __assign({}, this.config),
p: initialPersistenceProps,
s: initialSessionProps,
});
}
// When identity_distinct_id is provided at init time, use it as the
// bootstrap distinct ID so the Person record is created from the first event.
if (this.config.identity_distinct_id && !((_b = config.bootstrap) === null || _b === void 0 ? void 0 : _b.distinctID)) {
config.bootstrap = __assign(__assign({}, config.bootstrap), { distinctID: this.config.identity_distinct_id, isIdentifiedID: true });
}
var initialDistinctId = (_c = config.bootstrap) === null || _c === void 0 ? void 0 : _c.distinctID;
this._hasStableInitialDistinctId = !!initialDistinctId && !(0, core_1.isEmptyString)(initialDistinctId);
// isUndefined doesn't provide typehint here so wouldn't reduce bundle as we'd need to assign
// eslint-disable-next-line posthog-js/no-direct-undefined-check
if (((_d = config.bootstrap) === null || _d === void 0 ? void 0 : _d.distinctID) !== undefined) {
var bootstrapDistinctId = config.bootstrap.distinctID;
var existingDistinctId = this.get_distinct_id();
var existingUserState = this.persistence.get_property(constants_1.USER_STATE);
if (config.bootstrap.isIdentifiedID &&
existingDistinctId != null &&
existingDistinctId !== bootstrapDistinctId &&
existingUserState === constants_1.USER_STATE_ANONYMOUS) {
// The server bootstrapped identity for an identified user, but local persistence
// still has an anonymous ID from a previous session. Calling identify() merges
// the anonymous user into the identified user, ensuring consistent identity
// for feature flag evaluation and preventing duplicate $feature_flag_called events.
//
// Note: this runs during _init(), before _loaded() enables the request queue.
// The $identify event is enqueued and flushed once the queue starts. The
// reloadFeatureFlags() call inside identify() sets _reloadDebouncer, so the
// subsequent ensureFlagsLoaded() from _onRemoteConfig is a no-op (no double request).
this.identify(bootstrapDistinctId);
}
else if (config.bootstrap.isIdentifiedID &&
existingDistinctId != null &&
existingDistinctId !== bootstrapDistinctId &&
existingUserState === constants_1.USER_STATE_IDENTIFIED) {
// The existing user is already identified with a different ID. Silently
// switching identities without an $identify event would corrupt analytics.
// Preserve the existing identity and log a warning.
logger_1.logger.warn('Bootstrap distinctID differs from an already-identified user. ' +
'The existing identity is preserved. Call reset() before reinitializing ' +
'if you intend to switch users.');
}
else {
var uuid = this.config.get_device_id((0, uuidv7_1.uuidv7)());
var deviceID = config.bootstrap.isIdentifiedID ? uuid : bootstrapDistinctId;
this.persistence.set_property(constants_1.USER_STATE, config.bootstrap.isIdentifiedID ? constants_1.USER_STATE_IDENTIFIED : constants_1.USER_STATE_ANONYMOUS);
this.register({
distinct_id: bootstrapDistinctId,
$device_id: deviceID,
});
}
}
if (startInCookielessMode) {
this.register_once({
distinct_id: constants_1.COOKIELESS_SENTINEL_VALUE,
$device_id: null,
}, '');
}
else if (!this.get_distinct_id()) {
// There is no need to set the distinct id
// or the device id if something was already stored
// in the persistence
var uuid = this.config.get_device_id((0, uuidv7_1.uuidv7)());
this.register_once({
distinct_id: uuid,
$device_id: uuid,
}, '');
// distinct id == $device_id is a proxy for anonymous user
this.persistence.set_property(constants_1.USER_STATE, constants_1.USER_STATE_ANONYMOUS);
}
// Set up event handler for pageleave
// Use `onpagehide` if available, see https://calendar.perfplanet.com/2020/beaconing-in-practice/#beaconing-reliability-avoiding-abandons
//
// Not making it passive to try and force the browser to handle this before the page is unloaded
(0, general_utils_1.addEventListener)(globals_1.window, 'onpagehide' in self ? 'pagehide' : 'unload', this._handle_unload.bind(this), {
passive: false,
});
// We want to avoid promises for IE11 compatibility, so we use callbacks here
if (config.segment) {
(0, segment_integration_1.setupSegmentIntegration)(this, function () { return _this._loaded(); });
}
else {
this._loaded();
}
if ((0, core_1.isFunction)(this.config._onCapture) && this.config._onCapture !== __NOOP) {
logger_1.logger.warn('onCapture is deprecated. Please use `before_send` instead');
this.on('eventCaptured', function (data) { return _this.config._onCapture(data.event, data); });
}
if (this.config.ip) {
logger_1.logger.warn('The `ip` config option has NO EFFECT AT ALL and has been deprecated. Use a custom transformation or "Discard IP data" project setting instead. See https://posthog.com/tutorials/web-redact-properties#hiding-customer-ip-address for more information.');
}
// Not awaited — the first event may miss $device_model, which is fine for a stable per-device dimension.
if (!this.config.disableDeviceModel) {
(0, device_model_utils_1.getDeviceModel)()
.then(function (model) {
var _a;
if (model) {
_this.register((_a = {}, _a[constants_1.DEVICE_MODEL] = model, _a));
}
})
.catch(__NOOP);
}
return this;
};
PostHog.prototype._isSharedExtension = function (extension) {
var sharedExtension = extension;
return (0, core_1.isString)(sharedExtension.name) && (0, core_1.isFunction)(sharedExtension.setup);
};
PostHog.prototype._enrollExtension = function (extension, initTasks) {
var _this = this;
if (this._isSharedExtension(extension)) {
initTasks.push(function () {
return void _this._getBrowserClientAdapter()
.add(extension)
.catch(function () { var _a; return (_a = extension.dispose) === null || _a === void 0 ? void 0 : _a.call(extension); })
.catch(function (error) {
logger_1.logger.error("Failed to dispose browser extension \"".concat(extension.name, "\""), error);
});
});
}
else {
this._extensions.push(extension);
}
};
PostHog.prototype._enrollFeatureFlags = function () {
var _this = this;
var _a, _b, _c, _d, _e, _f;
var FeatureFlagsClass = (_b = (_a = this.config.__extensionClasses) === null || _a === void 0 ? void 0 : _a.featureFlags) !== null && _b !== void 0 ? _b : (_c = PostHog.__defaultExtensionClasses) === null || _c === void 0 ? void 0 : _c.featureFlags;
if (!FeatureFlagsClass) {
return;
}
if (!this.featureFlags || !(this.featureFlags instanceof FeatureFlagsClass)) {
(_d = this._featureFlagsReloadingUnsubscribe) === null || _d === void 0 ? void 0 : _d.call(this);
this._featureFlagsReloadingUnsubscribe = undefined;
this.featureFlags = new FeatureFlagsClass(this);
}
if ((0, core_1.isFunction)(this.featureFlags.onReloading) && (0, core_1.isFunction)(this.featureFlags.setup)) {
if (!this._featureFlagsReloadingUnsubscribe) {
this._featureFlagsReloadingUnsubscribe = this.featureFlags.onReloading(function () {
_this._internalEventEmitter.emit('featureFlagsReloading', true);
});
void this._getBrowserClientAdapter().add(this.featureFlags);
}
}
else {
(_f = (_e = this.featureFlags).initialize) === null || _f === void 0 ? void 0 : _f.call(_e);
}
};
PostHog.prototype._initExtensions = function (startInCookielessMode) {
var _this = this;
var _a, _b, _c, _d, _e, _f, _g;
// we don't support IE11 anymore, so performance.now is safe
// eslint-disable-next-line compat/compat
var initStartTime = performance.now();
var ext = __assign(__assign({}, PostHog.__defaultExtensionClasses), this.config.__extensionClasses);
var initTasks = [];
// Due to name mangling, we can't easily iterate and assign these extensions
// The assignment needs to also be mangled. Thus, the loop is unrolled.
if (ext.exceptions) {
this._extensions.push((this.exceptions = (_a = this.exceptions) !== null && _a !== void 0 ? _a : new ext.exceptions(this)));
}
if (ext.historyAutocapture) {
this._extensions.push((this.historyAutocapture = new ext.historyAutocapture(this)));
}
if (ext.tracingHeaders) {
this._extensions.push((this.tracingHeaders = new ext.tracingHeaders(this)));
}
if (ext.siteApps) {
this._extensions.push((this.siteApps = new ext.siteApps(this)));
}
if (ext.sessionRecording && !startInCookielessMode) {
this._extensions.push((this.sessionRecording = new ext.sessionRecording(this)));
}
if (!this.config.disable_scroll_properties) {
initTasks.push(function () {
_this.scrollManager.startMeasuringScrollPosition();
});
}
if (ext.autocapture) {
this._enrollExtension((this.autocapture = new ext.autocapture(this)), initTasks);
}
if (ext.surveys) {
this._enrollExtension((this.surveys = (_b = this.surveys) !== null && _b !== void 0 ? _b : new ext.surveys(this)), initTasks);
}
if (ext.logs) {
this._enrollExtension((this.logs = (_c = this.logs) !== null && _c !== void 0 ? _c : new ext.logs(this)), initTasks);
}
if (ext.metrics) {
this._extensions.push((this.metrics = (_d = this.metrics) !== null && _d !== void 0 ? _d : new ext.metrics(this)));
}
if (ext.conversations) {
this._extensions.push((this.conversations = (_e = this.conversations) !== null && _e !== void 0 ? _e : new ext.conversations(this)));
}
if (ext.productTours) {
this._extensions.push((this.productTours = new ext.productTours(this)));
}
if (ext.heatmaps) {
this._extensions.push((this.heatmaps = new ext.heatmaps(this)));
}
if (ext.webVitalsAutocapture) {
this._extensions.push((this.webVitalsAutocapture = new ext.webVitalsAutocapture(this)));
}
if (ext.exceptionObserver) {
this._extensions.push((this.exceptionObserver = new ext.exceptionObserver(this)));
}
if (ext.deadClicksAutocapture) {
this._extensions.push((this.deadClicksAutocapture = new ext.deadClicksAutocapture(this, dead_clicks_autocapture_1.isDeadClicksEnabledForAutocapture)));
}
if (ext.toolbar) {
this._extensions.push((this.toolbar = (_f = this.toolbar) !== null && _f !== void 0 ? _f : new ext.toolbar(this)));
}
if (ext.experiments) {
this._extensions.push((this.experiments = (_g = this.experiments) !== null && _g !== void 0 ? _g : new ext.experiments(this)));
}
this._extensions.forEach(function (extension) {
if (!extension.initialize)
return;
initTasks.push(function () {
var _a;
(_a = extension.initialize) === null || _a === void 0 ? void 0 : _a.call(extension);
});
});
// Replay any pending remote config that arrived before extensions were ready
initTasks.push(function () {
if (_this._pendingRemoteConfig) {
var result_1 = _this._pendingRemoteConfig;
_this._pendingRemoteConfig = undefined;
_this._extensions.forEach(function (ext) { var _a; return (_a = ext.onRemoteConfig) === null || _a === void 0 ? void 0 : _a.call(ext, result_1); });
}
});
// Process tasks with time-slicing to avoid blocking
this._processInitTaskQueue(initTasks, initStartTime);
};
PostHog.prototype._processInitTaskQueue = function (queue, initStartTime) {
var _a;
var _this = this;
var TIME_BUDGET_MS = 30; // Respect frame budget (~60fps = 16ms, but we're already deferred)
while (queue.length > 0) {
// Only time-slice if deferred init is enabled, otherwise run synchronously
if (this.config.__preview_deferred_init_extensions) {
// we don't support IE11 anymore, so performance.now is safe
var elapsed = performance.now() - initStartTime;
// Check if we've exceeded our time budget
if (elapsed >= TIME_BUDGET_MS && queue.length > 0) {
// Yield to browser, then continue processing
setTimeout(function () {
_this._processInitTaskQueue(queue, initStartTime);
}, 0);
return;
}
}
// Process next task
var task = queue.shift();
if (task) {
try {
task();
}
catch (error) {
logger_1.logger.error('Error initializing extension:', error);
}
}
}
// All tasks complete - record timing for both sync and deferred modes
// we don't support IE11 anymore, so performance.now is safe
// eslint-disable-next-line compat/compat
var taskInitTiming = Math.round(performance.now() - initStartTime);
this.register_for_session((_a = {},
_a[constants_1.SDK_DEBUG_EXTENSIONS_INIT_METHOD] = this.config.__preview_deferred_init_extensions
? 'deferred'
: 'synchronous',
_a[constants_1.SDK_DEBUG_EXTENSIONS_INIT_TIME_MS] = taskInitTiming,
_a));
if (this.config.__preview_deferred_init_extensions) {
logger_1.logger.info("PostHog extensions initialized (".concat(taskInitTiming, "ms)"));
}
};
PostHog.prototype._onRemoteConfig = function (result) {
var _this = this;
var _a, _b;
if (!(globals_1.document && globals_1.document.body)) {
logger_1.logger.info('document not ready yet, trying again in 500 milliseconds...');
setTimeout(function () {
_this._onRemoteConfig(result);
}, 500);
return;
}
// Store config in case extensions aren't initialized yet (only needed for deferred init)
if (this.config.__preview_deferred_init_extensions) {
this._pendingRemoteConfig = result;
}
// Cache the latest remote config result so extensions that are created later
// (e.g. sessionRecording after opt_in_capturing from cookieless mode) can
// replay it and pick up server-side settings like recording enable flags.
// Storing the result (not just a config) means a replayed failure is
// distinguishable from a successful empty config.
this._lastRemoteConfig = result;
this.compression = undefined;
if (result.ok) {
var config = result.config;
if (config.supportedCompression && !this.config.disable_compression) {
this.compression = (0, core_1.includes)(config['supportedCompression'], types_1.Compression.GZipJS)
? types_1.Compression.GZipJS
: (0, core_1.includes)(config['supportedCompression'], types_1.Compression.Base64)
? types_1.Compression.Base64
: undefined;
}
if ((_a = config.analytics) === null || _a === void 0 ? void 0 : _a.endpoint) {
this.analyticsDefaultEndpoint = config.analytics.endpoint;
}
}
// Runs on failure too: the person_profiles default must be applied even when
// the remote config could not be fetched.
this.set_config({
person_profiles: this._initialPersonProfilesConfig
? this._initialPersonProfilesConfig
: constants_1.PERSON_PROFILES_IDENTIFIED_ONLY,
});
(_b = this._browserClientAdapter) === null || _b === void 0 ? void 0 : _b.handleRemoteConfig(result);
// Every legacy extension receives the canonical result and handles failures itself.
this._extensions.forEach(function (ext) { var _a; return (_a = ext.onRemoteConfig) === null || _a === void 0 ? void 0 : _a.call(ext, result); });
};
PostHog.prototype._loaded = function () {
var _this = this;
try {
this.config.loaded(this);
}
catch (err) {
logger_1.logger.critical('`loaded` function failed', err);
}
this._start_queue_if_opted_in();
// Check if current hostname matches internal_or_test_user_hostname pattern and mark as test user before any events
if (this.config.internal_or_test_user_hostname && (globals_1.location === null || globals_1.location === void 0 ? void 0 : globals_1.location.hostname)) {
var hostname = globals_1.location.hostname;
var pattern = this.config.internal_or_test_user_hostname;
var matches = typeof pattern === 'string' ? hostname === pattern : pattern.test(hostname);
if (matches) {
this.setInternalOrTestUser();
}
}
// this happens after "loaded" so a user can call identify or any other things before the pageview fires
if (this.config.capture_pageview) {
// NOTE: We want to fire this on the next tick as the previous implementation had this side effect
// and some clients may rely on it
setTimeout(function () {
if (_this.consent.isOptedIn() || _this._inCookielessMode()) {
_this._captureInitialPageview();
}
}, 1);
}
this._remoteConfigLoader = new remote_config_1.RemoteConfigLoader(this);
this._remoteConfigLoader.load();
};
PostHog.prototype._start_queue_if_opted_in = function () {
var _a;
if (this.is_capturing()) {
if (this.config.request_batching) {
(_a = this._requestQueue) === null || _a === void 0 ? void 0 : _a.enable();
}
}
};
PostHog.prototype._dom_loaded = function () {
var _this = this;
if (this.is_capturing()) {
(0, general_utils_1.eachArray)(this.__request_queue, function (item) { return _this._send_retriable_request(item); });
}
this.__request_queue = [];
this._start_queue_if_opted_in();
};
PostHog.prototype._handle_unload = function () {
var _a, _b, _c, _d, _e, _f;
// Optional-call the method, not just the receiver: after a deploy a cached older
// lazy-loaded surveys chunk can yield an instance whose prototype lacks handlePageUnload,
// and `this.surveys?.handlePageUnload()` would still throw "handlePageUnload is not a function".
(_b = (_a = this.surveys) === null || _a === void 0 ? void 0 : _a.handlePageUnload) === null || _b === void 0 ? void 0 : _b.call(_a);
// Metrics are pre-aggregated client-side and don't ride the request
// queue, so the drain must not depend on `request_batching`.
void ((_c = this.metrics) === null || _c === void 0 ? void 0 : _c.flush('sendBeacon'));
if (!this.config.request_batching) {
if (this._shouldCapturePageleave()) {
this.capture(constants_1.EVENT_PAGELEAVE, null, { transport: 'sendBeacon' });
}
return;
}
if (this._shouldCapturePageleave()) {
this.capture(constants_1.EVENT_PAGELEAVE);
}
(_d = this.logs) === null || _d === void 0 ? void 0 : _d.flushLogs('sendBeacon');
(_e = this._requestQueue) === null || _e === void 0 ? void 0 : _e.unload();
(_f = this._retryQueue) === null || _f === void 0 ? void 0 : _f.unload();
};
PostHog.prototype._send_request = function (options) {
var _this = this;
var _a, _b, _c;
if (!this.__loaded) {
if (options.fireCallbackOnDrop) {
(_a = options.callback) === null || _a === void 0 ? void 0 : _a.call(options, { statusCode: 0 });
}
return;
}
if (ENQUEUE_REQUESTS) {
this.__request_queue.push(options);
return;
}
if (this.rateLimiter.isServerRateLimited(options.batchKey)) {
if (options.fireCallbackOnDrop) {
(_b = options.callback) === null || _b === void 0 ? void 0 : _b.call(options, { statusCode: 429 });
}
return;
}
options.transport = options.transport || this.config.api_transport;
options.headers = __assign(__assign({}, this.config.request_headers), options.headers);
options.compression =
options.compression === 'best-available'
? ((_c = this.compression) !== null && _c !== void 0 ? _c : options.compressionFallback)
: options.compression;
var disableBeacon = (0, core_1.isUndefined)(this.config.disable_beacon)
? this.config.__preview_disable_beacon
: this.config.disable_beacon;
if (disableBeacon) {
options.disableTransport = ['sendBeacon'];
}
// Specially useful if you're doing SSR with NextJS
// Users must be careful when tweaking `cache` because they might get out-of-date feature flags
options.fetchOptions = options.fetchOptions || this.config.fetch_options;
(0, request_1.request)(__assign(__assign({}, options), { callback: function (response) {
var _a, _b, _c;
_this.rateLimiter.checkForLimiting(response);
if (response.statusCode >= 400) {
(_b = (_a = _this.config).on_request_error) === null || _b === void 0 ? void 0 : _b.call(_a, response);
}
(_c = options.callback) === null || _c === void 0 ? void 0 : _c.call(options, response);
} }));
};
PostHog.prototype._send_retriable_request = function (options) {
if (this._retryQueue) {
this._retryQueue.retriableRequest(options);
}
else {
this._send_request(options);
}
};
/**
* _execute_array() deals with processing any posthog function
* calls that were called before the PostHog library were loaded
* (and are thus stored in an array so they can be called later)
*
* Note: we fire off all the posthog function calls && user defined
* functions BEFORE we fire off posthog capturing calls. This is so
* identify/register/set_config calls can properly modify early
* capturing calls.
*
* @param {Array} array
*/
PostHog.prototype._execute_array = function (array) {
var _this = this;
_executeArrayDepth++;
try {
var fn_name_1;
var alias_calls_1 = [];
var other_calls_1 = [];
var capturing_calls_1 = [];
(0, general_utils_1.eachArray)(array, function (item) {
if (item) {
fn_name_1 = item[0];
if ((0, core_1.isArray)(fn_name_1)) {
capturing_calls_1.push(item); // chained call e.g. posthog.get_group().set()
}
else if ((0, core_1.isFunction)(item)) {
try {
;
item.call(_this);
}
catch (e) {
logger_1.logger.error('Error executing queued PostHog call', item, e);
}
}
else if ((0, core_1.isArray)(item) && fn_name_1 === 'alias') {
alias_calls_1.push(item);
}
else if ((0, core_1.isArray)(item) &&
fn_name_1.indexOf('capture') !== -1 &&
(0, core_1.isFunction)(_this[fn_name_1])) {
capturing_calls_1.push(item);
}
else {
other_calls_1.push(item);
}
}
});
var execute = function (calls, thisArg) {
(0, general_utils_1.eachArray)(calls, function (item) {
try {
if ((0, core_1.isArray)(item[0])) {
// chained call
var caller_1 = thisArg;
(0, general_utils_1.each)(item, function (call) {
caller_1 = caller_1[call[0]].apply(caller_1, call.slice(1));
});
}
else {
thisArg[item[0]].apply(thisArg, item.slice(1));
}
}
catch (e) {
logger_1.logger.error('Error executing queued PostHog call', item, e);
}
});
};
execute(alias_calls_1, this);
execute(other_calls_1, this);
execute(capturing_calls_1, this);
}
finally {
_executeArrayDepth--;
}
};
/**
* push() keeps the standard async-array-push
* behavior around after the lib is loaded.
* This is only useful for external integrations that
* do not wish to rely on our convenience methods
* (created in the snippet).
*
* @example
* ```js
* posthog.push(['register', { a: 'b' }]);
* ```
*
* @param {SnippetArrayItem} item A `[function_name, ...args]` array to be executed.
*/
PostHog.prototype.push = function (item) {
if (_executeArrayDepth > 0 && (0, core_1.isArray)(item) && (0, core_1.isString)(item[0])) {
// push() is being called while _execute_array is already running.
// This happens when a third-party Proxy (e.g., TikTok's in-app browser)
// wraps window.posthog and converts method calls into push() calls,
// creating an infinite loop: _execute_array -> this[method] -> Proxy ->
// push -> _execute_array -> ...
// Dispatch directly from the prototype to break the cycle.
var fn = PostHog.prototype[item[0]];
if ((0, core_1.isFunction)(fn)) {
fn.apply(this, item.slice(1));
}
return;
}
this._execute_array([item]);
};
/**
* Captures an event with optional properties and configuration.
*
* @remarks
* You can capture arbitrary object-like values as events. [Learn about capture best practices](/docs/product-analytics/capture-events)
*
* @example
* ```js
* // basic event capture
* posthog.capture('cta-button-clicked', {
* button_name: 'Get Started',
* page: 'homepage'
* })
* ```
*
* {@label Capture}
*
* @public
*
* @param event_name - The name of the event (e.g., 'Sign Up', 'Button Click', 'Purchase')
* @param properties - Properties to include with the event describing the user or event details
* @param options - Optional configuration for the capture request
*
* @returns The capture result containing event data, or undefined if capture failed
*/
PostHog.prototype.capture = function (event_name, properties, options) {
var _a, _b, _c;
var _d;
// While developing, a developer might purposefully _not_ call init(),
// in this case, we would like capture to be a noop.
if (!this.__loaded || !this.persistence || !this.sessionPersistence || !this._requestQueue) {
logger_1.logger.uninitializedWarning('posthog.capture');
return;
}
if (!this.is_capturing()) {
return;
}
// typing doesn't prevent interesting data
if ((0, core_1.isUndefined)(event_name) || !(0, core_1.isString)(event_name)) {
logger_1.logger.error('No event name provided to posthog.capture');
return;
}
this._healCookielessSentinelDistinctId();
var isBot = !this.config.opt_out_useragent_filter && this._is_bot();
var shouldDropBotEvent = isBot && !this.config.__preview_capture_bot_pageviews;
// We drop bot events unless the preview flag to send bot pageviews is enabled
// or the user has explicitly opted out of useragent filtering
if (shouldDropBotEvent) {
return;
}
var clientRateLimitContext = !(options === null || options === void 0 ? void 0 : options.skip_client_rate_limiting)
? this.rateLimiter.clientRateLimitContext()
: undefined;
if (clientRateLimitContext === null || clientRateLimitContext === void 0 ? void 0 : clientRateLimitContext.isRateLimited) {
logger_1.logger.critical('This capture call is ignored due to client rate limiting.');
return;
}
if ((properties === null || properties === void 0 ? void 0 : properties.$current_url) && !(0, core_1.isString)(properties === null || properties === void 0 ? void 0 : properties.$current_url)) {
logger_1.logger.error('Invalid `$current_url` property provided to `posthog.capture`. Input must be a string. Ignoring provided value.');
properties === null || properties === void 0 ? true : delete properties.$current_url;
}
if (event_name === '$exception' && !(options === null || options === void 0 ? void 0 : options._originatedFromCaptureException)) {
logger_1.logger.warn("Using `posthog.capture('$exception')` is unreliable because it does not attach required metadata. Use `posthog.captureException(error)` instead, which attaches required metadata automatically.");
}
// update persistence
this.sessionPersistence.update_search_keyword();
// The initial campaign/referrer props need to be stored in the regular persistence, as they are there to mimic
// the person-initial props. The non-initial versions are stored in the sessionPersistence, as they are sent
// with every event and used by the session table to create session-initial props.
if (this.config.save_campaign_params) {
this.sessionPersistence.update_campaign_params();
}
if (this.config.save_referrer) {
this.sessionPersistence.update_referrer_info();
}
if (this.config.save_campaign_params || this.config.save_referrer) {
this.persistence.set_initial_person_info();
}
var systemTime = new Date();
var timestamp = (options === null || options === void 0 ? void 0 : options.timestamp) || systemTime;
// codeql[js/insecure-randomness] Event UUIDs are identifiers for deduplication, not secrets.
var uuid = (0, core_1.getEventUuid)(options === null || options === void 0 ? void 0 : options.uuid, uuidv7_1.uuidv7);
var data = {
uuid: uuid,
event: event_name,
properties: this.calculateEventProperties(event_name, properties || {}, timestamp, uuid),
};
// Route pageviews to $bot_pageview when bot detected and preview flag enabled
if (event_name === constants_1.EVENT_PAGEVIEW && this.config.__preview_capture_bot_pageviews && isBot) {
data.event = '$bot_pageview';
// While it's obvious that a $bot_pageview is (likely) from a bot, we explicitly set $browser_type
// to make it easy to filter and test bot pageviews in the product
data.properties.$browser_type = 'bot';
}
if (clientRateLimitContext) {
data.properties['$lib_rate_limit_remaining_tokens'] = clientRateLimitContext.remainingTokens;
}
// When the server gates this project into minimal $feature_flag_called events and the
// evaluated flag is not linked to an experiment, the fully merged properties are rebuilt
// from the strict allowlist below (after the last SDK-added property) so super properties
// and the context envelope are structurally excluded. Any missing signal falls back to
// the full event.
var shouldSendMinimalFlagCalledEvent = event_name === '$feature_flag_called' &&
data.properties['$feature_flag_has_experiment'] === false &&
this.get_property(constants_1.PERSISTENCE_MINIMAL_FLAG_CALLED_EVENTS) === true;
var setProperties = options === null || options === void 0 ? void 0 : options.$set;
if (setProperties && !shouldSendMinimalFlagCalledEvent) {
data.$set = options === null || options === void 0 ? void 0 : options.$set;
}
var unsetProperties = options === null || options === void 0 ? void 0 : options.$unset;
if (unsetProperties) {
data.$unset = unsetProperties;
}
// $groupidentify doesn't process person $set_once on the server, so don't mark
// initial person props as sent. This ensures they're included with subsequent
// $identify calls.
var markSetOnceAsSent = event_name !== constants_1.EVENT_GROUPIDENTIFY;
// $identify should always include initial props because it creates/merges persons
// and may be processed before earlier anonymous events on the server
var forceIncludeInitialProps = event_name === constants_1.EVENT_IDENTIFY;
// Minimal flag-called events must not carry $set_once. Skipping the calculation (rather
// than dropping its result) avoids marking the initial person props as sent, so they
// still go out with the next full event.
var setOnceProperties = shouldSendMinimalFlagCalledEvent
? undefined
: this._calculate_set_once_properties(options === null || options === void 0 ? void 0 : options.$set_once, markSetOnceAsSent, forceIncludeInitialProps);
if (setOnceProperties) {
data.$set_once = setOnceProperties;
}
if (!(options === null || options === void 0 ? void 0 : options._noTruncate)) {
data = (0, general_utils_1._copyAndTruncateStrings)(data, this.config.properties_string_max_length);
}
data.timestamp = timestamp;
if (!(0, core_1.isUndefined)(options === null || options === void 0 ? void 0 : options.timestamp)) {
data.properties['$event_time_override_provided'] = true;
data.properties['$event_time_override_system_time'] = systemTime;
}
// before_send runs after this filter and may deliberately re-add stripped properties;
// the SDK itself must not enrich beyond allowlisted keys past this point.
if (shouldSendMinimalFlagCalledEvent) {
data.properties = (0, core_1.minimizeFlagCalledEventProperties)(data.properties, FLAG_CALLED_TRANSPORT_PROPERTY_KEYS);
}
if (event_name === posthog_surveys_types_1.SurveyEventName.DISMISSED || event_name === posthog_surveys_types_1.SurveyEventName.SENT) {
var surveyId = properties === null || properties === void 0 ? void 0 : properties[posthog_surveys_types_1.SurveyEventProperties.SURVEY_ID];
var surveyIteration = properties === null || properties === void 0 ? void 0 : properties[posthog_surveys_types_1.SurveyEventProperties.SURVEY_ITERATION];
(0, survey_utils_1.setSurveySeenOnLocalStorage)({ id: surveyId, current_iteration: surveyIteration });
data.$set = __assign(__assign({}, data.$set), (_a = {}, _a[(0, survey_utils_1.getSurveyInteractionProperty)({ id: surveyId, current_iteration: surveyIteration }, event_name === posthog_surveys_types_1.SurveyEventName.SENT ? 'responded' : 'dismissed')] = true, _a));
}
else if (event_name === posthog_surveys_types_1.SurveyEventName.SHOWN) {
data.$set = __assign(__assign({}, data.$set), (_b = {}, _b[posthog_surveys_types_1.SurveyEventProperties.SURVEY_LAST_SEEN_DATE] = new Date().toISOString(), _b));
}
if (event_name === posthog_product_tours_types_1.ProductTourEventName.SHOWN) {
var tourType = properties === null || properties === void 0 ? void 0 : properties[posthog_product_tours_types_1.ProductTourEventProperties.TOUR_TYPE];
if (tourType) {
data.$set = __assign(__assign({}, data.$set), (_c = {}, _c["".concat(posthog_product_tours_types_1.ProductTourEventProperties.TOUR_LAST_SEEN_DATE, "/").concat(tourType)] = new Date().toISOString(), _c));
}
}
// Top-level $set overriding values from the one from properties is taken from the plugin-server normalizeEvent
// This doesn't handle $set_once, because posthog-people doesn't either
var finalSet = __assign(__assign({}, data.properties['$set']), data['$set']);
if (!(0, core_1.isEmptyObject)(finalSet)) {
this.setPersonPropertiesForFlags(finalSet);
}
if (!(0, core_1.isNullish)(this.config.before_send)) {
var beforeSendResult = this._runBeforeSend(data);
if (!beforeSendResult) {
return;
}
else {
data = beforeSendResult;
// codeql[js/insecure-randomness] Event UUIDs are identifiers for deduplication, not secrets.
data.uuid = (0, core_1.getEventUuid)(data.uuid, uuidv7_1.uuidv7);
}
}
this._internalEventEmitter.emit('eventCaptured', data);
var url = (_d = options === null || options === void 0 ? void 0 : options._url) !== null && _d !== void 0 ? _d : this.requestRouter.endpointFor('api', this.analyticsDefaultEndpoint);
var isSessionRecording = (options === null || options === void 0 ? void 0 : options._batchKey) === 'recordings' || /\/s\/(?:\?|$)/.test(url);
var requestOptions = {
method: 'POST',
url: url,
data: data,
compression: 'best-available',
timestampMode: isSessionRecording ? 'body' : 'capture-body',
batchKey: options === null || options === void 0 ? void 0 : options._batchKey,
transport: options === null || options === void 0 ? void 0 : options.transport,
};
if (this.config.request_batching && (!options || (options === null || options === void 0 ? void 0 : options._batchKey)) && !(options === null || options === void 0 ? void 0 : options.send_instantly)) {
this._requestQueue.enqueue(requestOptions);
}
else {
this._send_retriable_request(requestOptions);
}
return data;
};
PostHog.prototype._addCaptureHook = function (callback) {
return this.on('eventCaptured', function (data) { return callback(data.event, data); });
};
PostHog.prototype.getExtension = function (name) {
var _a;
return (_a = this._browserClientAdapter) === null || _a === void 0 ? void 0 : _a.getExtension(name);
};
PostHog.prototype._getBrowserClientAdapter = function () {
var _a;
return ((_a = this._browserClientAdapter) !== null && _a !== void 0 ? _a : (this._browserClientAdapter = new browser_client_1.BrowserClientAdapter(this)));
};
PostHog.prototype._registerExtensionEventProperties = function (producer) {
var _this = this;
this._extensionEventPropertyProducers.push(producer);
var active = true;
return function () {
if (!active) {
return;
}
active = false;
var index = _this._extensionEventPropertyProducers.indexOf(producer);
if (index !== -1) {
_this._extensionEventPropertyProducers.splice(index, 1);
}
};
};
PostHog.prototype._processCookieIdentityChange = function (reloadFeatureFlags) {
var _a, _b, _c;
if (reloadFeatureFlags === void 0) { reloadFeatureFlags = true; }
if (!((_a = this.persistence) === null || _a === void 0 ? void 0 : _a.consumeCookieIdentityChange())) {
return false;
}
this._cachedPersonProperties = null;
if (this.persistence.get_property(constants_1.USER_STATE) === constants_1.USER_STATE_ANONYMOUS) {
// Persistent event properties, including groups, are cleared while
// reconciling the reset snapshot. Clear the separate session store
// here before assembling an event under the anonymous identity.
(_b = this.sessionPersistence) === null || _b === void 0 ? void 0 : _b.clear();
this._sessionRegisteredPropKeys.clear();
this._persistSessionRegisteredPropKeys();
}
(_c = this.featureFlags) === null || _c === void 0 ? void 0 : _c.reset();
if (reloadFeatureFlags) {
this.reloadFeatureFlags();
}
return true;
};
/**
* This method is used internally to calculate the event properties before sending it to PostHog. It can also be
* used by integrations (e.g. Segment) to enrich events with PostHog properties before sending them to Segment,
* which is required for some PostHog products to work correctly. (e.g. to have a correct $session_id property).
*
* @param {String} eventName The name of the event. This can be anything the user does - 'Button Click', 'Sign Up', '$pageview', etc.
* @param {Object} eventProperties The properties to include with the event.
* @param {Date} [timestamp] The timestamp of the event, e.g. for calculating time on page. If not set, it'll automatically be set to the current time.
* @param {String} [uuid] The uuid of the event, e.g. for storing the $pageview ID.
* @param {Boolean} [readOnly] Set this if you do not intend to actually send the event, and therefore do not want to update internal state e.g. session timeout
*
* @internal
*/
PostHog.prototype.calculateEventProperties = function (eventName, eventProperties, timestamp, uuid, readOnly) {
var e_2, _a;
var _b;
timestamp = timestamp || new Date();
if (!this.persistence || !this.sessionPersistence) {
return eventProperties;
}
// Cookies do not emit cross-origin storage events. Reconcile before
// reading any event properties so already-open sibling subdomains pick
// up identify/reset changes, including for replay snapshot events.
this.persistence.syncCookieProperties();
this._processCookieIdentityChange();
// set defaults
var startTimestamp = readOnly ? undefined : this.persistence.remove_event_timer(eventName);
var properties = __assign({}, eventProperties);
properties['token'] = this.config.token;
properties['$config_defaults'] = this.config.defaults;
if (this._inCookielessMode()) {
// Set a flag to tell the plugin server to use cookieless server hash mode
properties[constants_1.COOKIELESS_MODE_FLAG_PROPERTY] = true;
}
if (eventName === '$snapshot') {
var persistenceProps = __assign(__assign({}, this.persistence.properties()), this.sessionPersistence.properties());
properties['distinct_id'] = persistenceProps.distinct_id;
if (
// we spotted one customer that was managing to send `false` for ~9k events a day
!((0, core_1.isString)(properties['distinct_id']) || (0, core_1.isNumber)(properties['distinct_id'])) ||
(0, core_1.isEmptyString)(properties['distinct_id'])) {
logger_1.logger.error('Invalid distinct_id for replay event. This indicates a bug in your implementation');
}
return properties;
}
var infoProperties = (0, event_utils_1.getEventProperties)(this.config.mask_personal_data_properties, this.config.custom_personal_data_properties, this.config.detect_google_search_app, this.config.disable_capture_url_hashes);
if (this.sessionManager) {
var _c = this.sessionManager.checkAndGetSessionAndWindowId(readOnly, timestamp.getTime(), true), sessionId = _c.sessionId, windowId = _c.windowId;
properties['$session_id'] = sessionId;
properties['$window_id'] = windowId;
}
if (this.sessionPropsManager) {
(0, general_utils_1.extend)(properties, this.sessionPropsManager.getSessionProps());
}
try {
if (this.sessionRecording) {
(0, general_utils_1.extend)(properties, this.sessionRecording.sdkDebugProperties);
}
properties['$sdk_debug_retry_queue_size'] = (_b = this._retryQueue) === null || _b === void 0 ? void 0 : _b.length;
}
catch (e) {
properties['$sdk_debug_error_capturing_properties'] = String(e);
}
if (this.requestRouter.region === request_router_1.RequestRouterRegion.CUSTOM) {
properties['$lib_custom_api_host'] = this.config.api_host;
}
var pageviewProperties;
if (eventName === constants_1.EVENT_PAGEVIEW && !readOnly) {
pageviewProperties = this.pageViewManager.doPageView(timestamp, uuid);
}
else if (eventName === constants_1.EVENT_PAGELEAVE && !readOnly) {
pageviewProperties = this.pageViewManager.doPageLeave(timestamp);
}
else {
pageviewProperties = this.pageViewManager.doEvent();
}
properties = (0, general_utils_1.extend)(properties, pageviewProperties);
if (eventName === constants_1.EVENT_PAGEVIEW && globals_1.document) {
properties['title'] = globals_1.document.title;
}
// set $duration if time_event was previously called for this event
if (!(0, core_1.isUndefined)(startTimestamp)) {
var duration_in_ms = timestamp.getTime() - startTimestamp;
properties['$duration'] = parseFloat((duration_in_ms / 1000).toFixed(3));
}
// this is only added when this.config.opt_out_useragent_filter is true,
// or it would always add "browser"
if (globals_1.userAgent && this.config.opt_out_useragent_filter) {
properties['$browser_type'] = this._is_bot() ? 'bot' : 'browser';
}
// note: extend writes to the first object, so lets make sure we
// don't write to the persistence properties object and info
// properties object by passing in a new object
// $referrer / $referring_domain are written to the sessionPersistence from document.referrer
// on every capture, and sessionPersistence is merged after the regular persistence below, so
// they normally win. When the user has explicitly set one of these via posthog.register()
// (which writes to the regular persistence), let that value win instead. Resolve the
// precedence per property so a single registered key does not suppress the other, and a
// stale session value cannot shadow the registered one. Without this an SPA or iframe
// reports document.referrer (the iframe's own origin) rather than the registered value.
var persistenceProperties = this.persistence.properties();
var sessionPersistenceProperties = this.sessionPersistence.properties();
(0, general_utils_1.each)(['$referrer', '$referring_domain'], function (referrerKey) {
if (referrerKey in persistenceProperties) {
delete sessionPersistenceProperties[referrerKey];
}
});
var dynamicProperties = {};
if (this._extensionEventPropertyProducers.length > 0) {
try {
for (var _d = __values(this._extensionEventPropertyProducers.slice()), _e = _d.next(); !_e.done; _e = _d.next()) {
var producer = _e.value;
try {
(0, general_utils_1.extend)(dynamicProperties, producer());
}
catch (error) {
logger_1.logger.error('Failed to produce browser extension event properties', error);
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (_e && !_e.done && (_a = _d.return)) _a.call(_d);
}
finally { if (e_2) throw e_2.error; }
}
}
// update properties with pageview info and super-properties
properties = (0, general_utils_1.extend)({}, infoProperties, persistenceProperties, sessionPersistenceProperties, __assign(__assign({}, dynamicProperties), properties));
properties['$is_identified'] = this._isIdentified();
if ((0, core_1.isArray)(this.config.property_denylist)) {
(0, general_utils_1.each)(this.config.property_denylist, function (denylisted_prop) {
delete properties[denylisted_prop];
});
}
else {
logger_1.logger.error(DENYLIST_INVALID +
this.config.property_denylist +
' or property_blacklist config: ' +
this.config.property_blacklist);
}
var sanitize_properties = this.config.sanitize_properties;
if (sanitize_properties) {
logger_1.logger.error(SANITIZE_DEPRECATED);
properties = sanitize_properties(properties, eventName);
}
// add person processing flag as very last step, so it cannot be overridden
var hasPersonProcessing = this._hasPersonProcessing();
properties['$process_person_profile'] = hasPersonProcessing;
// if the event has person processing, ensure that all future events will too, even if the setting changes
if (hasPersonProcessing && !readOnly) {
this._requirePersonProcessing('_calculate_event_properties');
}
return properties;
};
/**
* Add additional set_once properties to the event when creating a person profile. This allows us to create the
* profile with mostly-accurate properties, despite earlier events not setting them. We do this by storing them in
* persistence.
* @param dataSetOnce
* @param markAsSent - if true, marks the properties as sent so they won't be included in future events.
* Set to false for events like $groupidentify where the server doesn't process person props.
* @param forceIncludeInitialProps - if true, include initial person props even if they've already been sent.
* Used for $identify which creates/merges persons and may be processed out of order.
*/
PostHog.prototype._calculate_set_once_properties = function (dataSetOnce, markAsSent, forceIncludeInitialProps) {
var _a;
if (markAsSent === void 0) { markAsSent = true; }
if (forceIncludeInitialProps === void 0) { forceIncludeInitialProps = false; }
if (!this.persistence || !this._hasPersonProcessing()) {
return dataSetOnce;
}
if (this._personProcessingSetOncePropertiesSent && !forceIncludeInitialProps) {
// We only need to send these properties once. Sending them with later events would be redundant and would
// just require extra work on the server to process them.
return dataSetOnce;
}
// if we're an identified person, send initial params with every event
var initialProps = this.persistence.get_initial_props();
var sessionProps = (_a = this.sessionPropsManager) === null || _a === void 0 ? void 0 : _a.getSetOnceProps();
var setOnceProperties = (0, general_utils_1.extend)({}, initialProps, sessionProps || {}, dataSetOnce || {});
var sanitize_properties = this.config.sanitize_properties;
if (sanitize_properties) {
logger_1.logger.error(SANITIZE_DEPRECATED);
setOnceProperties = sanitize_properties(setOnceProperties, '$set_once');
}
if (markAsSent) {
this._personProcessingSetOncePropertiesSent = true;
}
if ((0, core_1.isEmptyObject)(setOnceProperties)) {
return undefined;
}
return setOnceProperties;
};
/**
* Registers super properties that are included with all events.
*
* @remarks
* Super properties are stored in persistence and automatically added to every event you capture.
* These values will overwrite any existing super properties with the same keys.
*
* @example
* ```js
* // register a single property
* posthog.register({ plan: 'premium' })
* ```
*
* {@label Capture}
*
* @example
* ```js
* // register multiple properties
* posthog.register({
* email: 'user@example.com',
* account_type: 'business',
* signup_date: '2023-01-15'
* })
* ```
*
* @example
* ```js
* // register with custom expiration
* posthog.register({ campaign: 'summer_sale' }, 7) // expires in 7 days
* ```
*
* @public
*
* @param {Object} properties properties to store about the user
* @param {Number} [days] How many days since the user's last visit to store the super properties
*/
PostHog.prototype.register = function (properties, days) {
var _a;
(_a = this.persistence) === null || _a === void 0 ? void 0 : _a.register(properties, days);
};
/**
* Registers super properties only if they haven't been set before.
*
* @remarks
* Unlike `register()`, this method will not overwrite existing super properties.
* Use this for properties that should only be set once, like signup date or initial referrer.
*
* {@label Capture}
*
* @example
* ```js
* // register once-only properties
* posthog.register_once({
* first_login_date: new Date().toISOString(),
* initial_referrer: document.referrer
* })
* ```
*
* @example
* ```js
* // override existing value if it matches default
* posthog.register_once(
* { user_type: 'premium' },
* 'unknown' // overwrite if current value is 'unknown'
* )
* ```
*
* @public
*
* @param {Object} properties An associative array of properties to store about the user
* @param {*} [default_value] Value to override if already set in super properties (ex: 'False') Default: 'None'
* @param {Number} [days] How many days since the users last visit to store the super properties
*/
PostHog.prototype.register_once = function (properties, default_value, days) {
var _a;
(_a = this.persistence) === null || _a === void 0 ? void 0 : _a.register_once(properties, default_value, days);
};
/**
* Registers super properties for the current session only.
*
* @remarks
* Session super properties are automatically added to all events during the current browser session.
* Unlike regular super properties, these are cleared when the session ends and are stored in sessionStorage.
*
* {@label Capture}
*
* @example
* ```js
* // register session-specific properties
* posthog.register_for_session({
* current_page_type: 'checkout',
* ab_test_variant: 'control'
* })
* ```
*
* @example
* ```js
* // register properties for user flow tracking
* posthog.register_for_session({
* selected_plan: 'pro',
* completed_steps: 3,
* flow_id: 'signup_flow_v2'
* })
* ```
*
* @public
*
* @param {Object} properties An associative array of properties to store about the user
*/
PostHog.prototype.register_for_session = function (properties) {
var _this = this;
var _a, _b;
(_a = this.persistence) === null || _a === void 0 ? void 0 : _a.syncCookieProperties();
this._processCookieIdentityChange();
(_b = this.sessionPersistence) === null || _b === void 0 ? void 0 : _b.register(properties);
Object.keys(properties).forEach(function (key) { return _this._sessionRegisteredPropKeys.add(key); });
this._persistSessionRegisteredPropKeys();
};
/**
* Removes a super property from persistent storage.
*
* @remarks
* This will stop the property from being automatically included in future events.
* The property will be permanently removed from the user's profile.
*
* {@label Capture}
*
* @example
* ```js
* // remove a super property
* posthog.unregister('plan_type')
* ```
*
* @public
*
* @param {String} property The name of the super property to remove
*/
PostHog.prototype.unregister = function (property) {
var _a;
(_a = this.persistence) === null || _a === void 0 ? void 0 : _a.unregister(property);
};
/**
* Removes a session super property from the current session.
*
* @remarks
* This will stop the property from being automatically included in future events for this session.
* The property is removed from sessionStorage.
*
* {@label Capture}
*
* @example
* ```js
* // remove a session property
* posthog.unregister_for_session('current_flow')
* ```
*
* @public
*
* @param {String} property The name of the session super property to remove
*/
PostHog.prototype.unregister_for_session = function (property) {
var _a;
(_a = this.sessionPersistence) === null || _a === void 0 ? void 0 : _a.unregister(property);
this._sessionRegisteredPropKeys.delete(property);
this._persistSessionRegisteredPropKeys();
};
PostHog.prototype._register_single = function (prop, value) {
var _a;
this.register((_a = {}, _a[prop] = value, _a));
};
PostHog.prototype._clearSessionRegisteredProps = function () {
var _this = this;
this._sessionRegisteredPropKeys.forEach(function (property) {
var _a;
(_a = _this.sessionPersistence) === null || _a === void 0 ? void 0 : _a.unregister(property);
});
this._sessionRegisteredPropKeys.clear();
this._persistSessionRegisteredPropKeys();
};
PostHog.prototype._persistSessionRegisteredPropKeys = function () {
var _a;
if (!this._sessionRegisteredPropertiesStorageKey) {
return;
}
if (this.config.persistence === 'memory' ||
((_a = this.sessionPersistence) === null || _a === void 0 ? void 0 : _a._disabled) ||
!storage_1.sessionStore._is_supported()) {
storage_1.sessionStore._remove(this._sessionRegisteredPropertiesStorageKey);
return;
}
var registeredProperties = [];
this._sessionRegisteredPropKeys.forEach(function (property) { return registeredProperties.push(property); });
if (registeredProperties.length > 0) {
storage_1.sessionStore._set(this._sessionRegisteredPropertiesStorageKey, registeredProperties);
}
else {
storage_1.sessionStore._remove(this._sessionRegisteredPropertiesStorageKey);
}
};
/**
* Gets the value of a feature flag for the current user.
*
* @remarks
* Returns the feature flag value which can be a boolean, string, or undefined.
* Supports multivariate flags that can return custom string values.
*
* {@label Feature flags}
*
* @example
* ```js
* // check boolean flag
* if (posthog.getFeatureFlag('new-feature')) {
* // show new feature
* }
* ```
*
* @example
* ```js
* // check multivariate flag
* const variant = posthog.getFeatureFlag('button-color')
* if (variant === 'red') {
* // show red button
* }
* ```
*
* @public
*
* @param {string} key Key of the feature flag.
* @param {FeatureFlagOptions} [options] Optional lookup settings. If `{ send_event: false }`, we won't send a `$feature_flag_called` event to PostHog. If `{ fresh: true }`, we won't return cached values from localStorage - only values loaded from the server.
* @returns {boolean | string | undefined} The feature flag value, or undefined if the flag is unavailable.
*/
PostHog.prototype.getFeatureFlag = function (key, options) {
var _a;
return (_a = this.featureFlags) === null || _a === void 0 ? void 0 : _a.getFeatureFlag(key, options);
};
/**
* Get feature flag payload value matching key for user (supports multivariate flags).
*
* {@label Feature flags}
*
* @example
* ```js
* const betaFeature = posthog.getFeatureFlagResult('beta-feature')
* if (betaFeature?.variant === 'some-value') {
* const someValue = betaFeature?.payload
* // do something
* }
* ```
*
* @public
*
* @deprecated Use `getFeatureFlagResult()` instead
*
* @param {string} key Key of the feature flag.
* @returns {JsonType} The feature flag payload.
*/
PostHog.prototype.getFeatureFlagPayload = function (key) {
var _a;
return (_a = this.featureFlags) === null || _a === void 0 ? void 0 : _a.getFeatureFlagPayload(key);
};
/**
* Get a feature flag evaluation result including both the flag value and payload.
*
* By default, this method emits the `$feature_flag_called` event.
*
* {@label Feature flags}
*
* @example
* ```js
* const result = posthog.getFeatureFlagResult('my-flag')
* if (result?.enabled) {
* console.log('Flag is enabled with payload:', result.payload)
* }
* ```
*
* @example
* ```js
* // multivariate flag
* const result = posthog.getFeatureFlagResult('button-color')
* if (result?.variant === 'red') {
* showRedButton(result.payload)
* }
* ```
*
* @public
*
* @param {string} key Key of the feature flag.
* @param {Object} [options] Options for the feature flag lookup.
* @param {boolean} [options.send_event=true] If false, won't send the $feature_flag_called event.
* @param {boolean} [options.fresh=false] If true, won't return cached values from localStorage - only values loaded from the server.
* @returns {FeatureFlagResult | undefined} The feature flag result including key, enabled, variant, and payload.
*/
PostHog.prototype.getFeatureFlagResult = function (key, options) {
var _a;
return (_a = this.featureFlags) === null || _a === void 0 ? void 0 : _a.getFeatureFlagResult(key, options);
};
/**
* Returns all currently cached feature flags as `FeatureFlagResult`s. This is a synchronous read of
* the flags from the last load (no network request); call `reloadFeatureFlags()` first to refresh.
* Unlike `getFeatureFlag()`, it does not send a `$feature_flag_called` event.
*
* @returns {FeatureFlagResult[]} All loaded flags, or an empty array if none are loaded.
*/
PostHog.prototype.getAllFeatureFlags = function () {
var _a, _b;
return (_b = (_a = this.featureFlags) === null || _a === void 0 ? void 0 : _a.getAllFeatureFlags()) !== null && _b !== void 0 ? _b : [];
};
PostHog.prototype.isFeatureEnabled = function (key, options) {
var _a, _b;
return (_b = (_a = this.featureFlags) === null || _a === void 0 ? void 0 : _a.isFeatureEnabled(key, options)) !== null && _b !== void 0 ? _b : options === null || options === void 0 ? void 0 : options.defaultValue;
};
/**
* Feature flag values are cached. If something has changed with your user and you'd like to refetch their flag values, call this method.
*
* {@label Feature flags}
*
* @example
* ```js
* posthog.reloadFeatureFlags()
* ```
*
* @public
*/
PostHog.prototype.reloadFeatureFlags = function () {
var _a;
(_a = this.featureFlags) === null || _a === void 0 ? void 0 : _a.reloadFeatureFlags();
};
/**
* Manually update feature flag values without making a network request.
*
* This is useful when you have feature flag values from an external source
* (e.g., server-side evaluation, edge middleware) and want to inject them
* into the client SDK.
*
* {@label Feature flags}
*
* @example
* ```js
* // Replace all flags with server-evaluated values
* posthog.updateFlags({
* 'my-flag': true,
* 'my-experiment': 'variant-a'
* })
*
* // Merge with existing flags (update only specified flags)
* posthog.updateFlags(
* { 'my-flag': true },
* undefined,
* { merge: true }
* )
*
* // With payloads
* posthog.updateFlags(
* { 'my-flag': true },
* { 'my-flag': { some: 'data' } }
* )
* ```
*
* @param flags - An object mapping flag keys to their values (boolean or string variant)
* @param payloads - Optional object mapping flag keys to their JSON payloads
* @param options - Optional settings. Use `{ merge: true }` to merge with existing flags instead of replacing.
* @public
*/
PostHog.prototype.updateFlags = function (flags, payloads, options) {
var _a;
(_a = this.featureFlags) === null || _a === void 0 ? void 0 : _a.updateFlags(flags, payloads, options);
};
/**
* Opt the user in or out of an early access feature. [Learn more in the docs](/docs/feature-flags/early-access-feature-management#option-2-custom-implementation)
*
* {@label Feature flags}
*
* @example
* ```js
* const toggleBeta = (betaKey) => {
* if (activeBetas.some(
* beta => beta.flagKey === betaKey
* )) {
* posthog.updateEarlyAccessFeatureEnrollment(
* betaKey,
* false
* )
* setActiveBetas(
* prevActiveBetas => prevActiveBetas.filter(
* item => item.flagKey !== betaKey
* )
* );
* return
* }
*
* posthog.updateEarlyAccessFeatureEnrollment(
* betaKey,
* true
* )
* setInactiveBetas(
* prevInactiveBetas => prevInactiveBetas.filter(
* item => item.flagKey !== betaKey
* )
* );
* }
*
* const registerInterest = (featureKey) => {
* posthog.updateEarlyAccessFeatureEnrollment(
* featureKey,
* true
* )
* // Update UI to show user has registered
* }
* ```
*
* @public
*
* @param {String} key The key of the feature flag to update.
* @param {Boolean} isEnrolled Whether the user is enrolled in the feature.
* @param {String} [stage] The stage of the feature flag to update.
*/
PostHog.prototype.updateEarlyAccessFeatureEnrollment = function (key, isEnrolled, stage) {
var _a;
(_a = this.featureFlags) === null || _a === void 0 ? void 0 : _a.updateEarlyAccessFeatureEnrollment(key, isEnrolled, stage);
};
/**
* Get the list of early access features. To check enrollment status, use `isFeatureEnabled`. [Learn more in the docs](/docs/feature-flags/early-access-feature-management#option-2-custom-implementation)
*
* {@label Feature flags}
*
* @example
* ```js
* const posthog = usePostHog()
* const activeFlags = useActiveFeatureFlags()
*
* const [activeBetas, setActiveBetas] = useState([])
* const [inactiveBetas, setInactiveBetas] = useState([])
* const [comingSoonFeatures, setComingSoonFeatures] = useState([])
*
* useEffect(() => {
* posthog.getEarlyAccessFeatures((features) => {
* // Filter features by stage
* const betaFeatures = features.filter(feature => feature.stage === 'beta')
* const conceptFeatures = features.filter(feature => feature.stage === 'concept')
*
* setComingSoonFeatures(conceptFeatures)
*
* if (!activeFlags || activeFlags.length === 0) {
* setInactiveBetas(betaFeatures)
* return
* }
*
* const activeBetas = betaFeatures.filter(
* beta => activeFlags.includes(beta.flagKey)
* );
* const inactiveBetas = betaFeatures.filter(
* beta => !activeFlags.includes(beta.flagKey)
* );
* setActiveBetas(activeBetas)
* setInactiveBetas(inactiveBetas)
* }, true, ['concept', 'beta'])
* }, [activeFlags])
* ```
*
* @public
*
* @param {Function} callback The callback function will be called when the early access features are loaded.
* @param {Boolean} [force_reload] Whether to force a reload of the early access features.
* @param {String[]} [stages] The stages of the early access features to load.
*/
PostHog.prototype.getEarlyAccessFeatures = function (callback, force_reload, stages) {
var _a;
if (force_reload === void 0) { force_reload = false; }
return (_a = this.featureFlags) === null || _a === void 0 ? void 0 : _a.getEarlyAccessFeatures(callback, force_reload, stages);
};
/**
* Exposes a set of events that PostHog will emit.
* e.g. `eventCaptured` is emitted immediately before trying to send an event
*
* Unlike `onFeatureFlags` and `onSessionId` these are not called when the
* listener is registered, the first callback will be the next event
* _after_ registering a listener
*
* Available events:
* - `eventCaptured`: Emitted immediately before trying to send an event
* - `featureFlagsReloading`: Emitted when feature flags are being reloaded (e.g. after `identify()`, `group()`, or `reloadFeatureFlags()`)
*
* {@label Capture}
*
* @example
* ```js
* posthog.on('eventCaptured', (event) => {
* console.log(event)
* })
* ```
*
* @example
* ```js
* // Track when feature flags are reloading to show a loading state
* posthog.on('featureFlagsReloading', () => {
* console.log('Feature flags are being reloaded...')
* })
* ```
*
* @public
*
* @param {String} event The event to listen for.
* @param {Function} cb The callback function to call when the event is emitted.
* @returns {Function} A function that can be called to unsubscribe the listener.
*/
PostHog.prototype.on = function (event, cb) {
return this._internalEventEmitter.on(event, cb);
};
/**
* Register an event listener that runs when feature flags become available or when they change.
* If there are flags, the listener is called immediately in addition to being called on future changes.
* Note that this is not called only when we fetch feature flags from the server, but also when they change in the browser.
*
* {@label Feature flags}
*
* @public
*
* @example
* ```js
* posthog.onFeatureFlags(function(featureFlags, featureFlagsVariants, { errorsLoading }) {
* // do something
* })
* ```
*
* @param callback - The callback function will be called once the feature flags are ready or when they are updated.
* It'll return a list of feature flags enabled for the user, the variants,
* and also a context object indicating whether we succeeded to fetch the flags or not.
* @returns A function that can be called to unsubscribe the listener. Used by `useEffect` when the component unmounts.
*/
PostHog.prototype.onFeatureFlags = function (callback) {
if (!this.featureFlags) {
callback([], {}, { errorsLoading: true });
return function () { };
}
return this.featureFlags.onFeatureFlags(callback);
};
/**
* Register an event listener that runs when surveys are loaded.
*
* Callback parameters:
* - surveys: Survey[]: An array containing all survey objects fetched from PostHog using the getSurveys method
* - context: { isLoaded: boolean, error?: string }: An object indicating if the surveys were loaded successfully
*
* {@label Surveys}
*
* @example
* ```js
* posthog.onSurveysLoaded((surveys, context) => { // do something })
* ```
*
* @param {SurveyCallback} callback The callback function will be called when surveys are loaded or updated.
* @returns A function that can be called to unsubscribe the listener.
*/
PostHog.prototype.onSurveysLoaded = function (callback) {
if (!this.surveys) {
callback([], { isLoaded: false, error: SURVEYS_NOT_AVAILABLE });
return function () { };
}
return this.surveys.onSurveysLoaded(callback);
};
/**
* Register an event listener that runs whenever the session id or window id change.
* If there is already a session id, the listener is called immediately in addition to being called on future changes.
*
* Can be used, for example, to sync the PostHog session id with a backend session.
*
* {@label Identification}
*
* @public
*
* @example
* ```js
* posthog.onSessionId(function(sessionId, windowId) { // do something })
* ```
*
* @param {Function} [callback] The callback function will be called once a session id is present or when it or the window id are updated.
* @returns {Function} A function that can be called to unsubscribe the listener. E.g. Used by `useEffect` when the component unmounts.
*/
PostHog.prototype.onSessionId = function (callback) {
var _a, _b;
return (_b = (_a = this.sessionManager) === null || _a === void 0 ? void 0 : _a.onSessionId(callback)) !== null && _b !== void 0 ? _b : (function () { });
};
/**
* Get list of all surveys.
*
* {@label Surveys}
*
* @example
* ```js
* function callback(surveys, context) {
* // do something
* }
*
* posthog.getSurveys(callback, false)
* ```
*
* @public
*
* @param {SurveyCallback} callback Function that receives the array of surveys.
* @param {boolean} [forceReload] Optional boolean to force an API call for updated surveys.
*/
PostHog.prototype.getSurveys = function (callback, forceReload) {
if (forceReload === void 0) { forceReload = false; }
this.surveys
? this.surveys.getSurveys(callback, forceReload)
: callback([], { isLoaded: false, error: SURVEYS_NOT_AVAILABLE });
};
/**
* Get surveys that should be enabled for the current user. See [fetching surveys documentation](/docs/surveys/implementing-custom-surveys#fetching-surveys-manually) for more details.
*
* {@label Surveys}
*
* @example
* ```js
* posthog.getActiveMatchingSurveys((surveys) => {
* // do something
* })
* ```
*
* @public
*
* @param {SurveyCallback} callback The callback function will be called when the surveys are loaded or updated.
* @param {boolean} [forceReload] Whether to force a reload of the surveys.
*/
PostHog.prototype.getActiveMatchingSurveys = function (callback, forceReload) {
if (forceReload === void 0) { forceReload = false; }
this.surveys
? this.surveys.getActiveMatchingSurveys(callback, forceReload)
: callback([], { isLoaded: false, error: SURVEYS_NOT_AVAILABLE });
};
/**
* Although we recommend using popover surveys and display conditions,
* if you want to show surveys programmatically without setting up all
* the extra logic needed for API surveys, you can render surveys
* programmatically with the renderSurvey method.
*
* This takes a survey ID and an HTML selector to render an unstyled survey.
*
* {@label Surveys}
*
* @example
* ```js
* posthog.renderSurvey(coolSurveyID, '#survey-container')
* ```
*
* @deprecated Use displaySurvey instead - it's more complete and also supports popover surveys.
*
* @public
*
* @param {String} surveyId The ID of the survey to render.
* @param {String} selector The selector of the HTML element to render the survey on.
*/
PostHog.prototype.renderSurvey = function (surveyId, selector) {
var _a;
(_a = this.surveys) === null || _a === void 0 ? void 0 : _a.renderSurvey(surveyId, selector);
};
/**
* Display a survey programmatically as either a popover or inline element.
*
* @param {string} surveyId The survey ID to display.
* @param {DisplaySurveyOptions} [options] Display configuration. Defaults to a popover that respects dashboard conditions and delays.
*
* @example
* ```js
* // Display as popover (respects all conditions defined in the dashboard)
* posthog.displaySurvey('survey-id-123')
* ```
*
* @example
* ```js
* // Display inline in a specific element
* posthog.displaySurvey('survey-id-123', {
* displayType: DisplaySurveyType.Inline,
* ignoreConditions: false,
* ignoreDelay: false,
* selector: '#survey-container'
* })
* ```
*
* @example
* ```js
* // Force display ignoring conditions and delays
* posthog.displaySurvey('survey-id-123', {
* displayType: DisplaySurveyType.Popover,
* ignoreConditions: true,
* ignoreDelay: true
* })
* ```
*
* {@label Surveys}
*/
PostHog.prototype.displaySurvey = function (surveyId, options) {
var _a;
if (options === void 0) { options = survey_utils_1.DEFAULT_DISPLAY_SURVEY_OPTIONS; }
(_a = this.surveys) === null || _a === void 0 ? void 0 : _a.displaySurvey(surveyId, options);
};
/**
* Cancels a pending survey that is waiting to be displayed (e.g., due to a popup delay).
*
* {@label Surveys}
*
* @param {string} surveyId The survey ID whose pending display should be cancelled.
*/
PostHog.prototype.cancelPendingSurvey = function (surveyId) {
var _a;
(_a = this.surveys) === null || _a === void 0 ? void 0 : _a.cancelPendingSurvey(surveyId);
};
/**
* Checks the feature flags associated with this Survey to see if the survey can be rendered.
* This method is deprecated because it's synchronous and won't return the correct result if surveys are not loaded.
* Use `canRenderSurveyAsync` instead.
*
* {@label Surveys}
*
* @public
* @deprecated
*
* @param surveyId The ID of the survey to check.
* @returns A SurveyRenderReason object indicating if the survey can be rendered.
*/
PostHog.prototype.canRenderSurvey = function (surveyId) {
var _a, _b;
return ((_b = (_a = this.surveys) === null || _a === void 0 ? void 0 : _a.canRenderSurvey(surveyId)) !== null && _b !== void 0 ? _b : {
visible: false,
disabledReason: SURVEYS_NOT_AVAILABLE,
});
};
/**
* Checks the feature flags associated with this Survey to see if the survey can be rendered.
*
* {@label Surveys}
*
* @example
* ```js
* posthog.canRenderSurveyAsync(surveyId).then((result) => {
* if (result.visible) {
* // Survey can be rendered
* console.log('Survey can be rendered')
* } else {
* // Survey cannot be rendered
* console.log('Survey cannot be rendered:', result.disabledReason)
* }
* })
* ```
*
* @public
*
* @param surveyId The ID of the survey to check.
* @param forceReload If true, the survey will be reloaded from the server, Default: false
* @returns A SurveyRenderReason object indicating if the survey can be rendered.
*/
PostHog.prototype.canRenderSurveyAsync = function (surveyId, forceReload) {
var _a, _b;
if (forceReload === void 0) { forceReload = false; }
return ((_b = (_a = this.surveys) === null || _a === void 0 ? void 0 : _a.canRenderSurveyAsync(surveyId, forceReload)) !== null && _b !== void 0 ? _b :
// eslint-disable-next-line compat/compat
Promise.resolve({ visible: false, disabledReason: SURVEYS_NOT_AVAILABLE }));
};
PostHog.prototype._validateIdentifyId = function (id) {
if (!id || (0, core_1.isEmptyString)(id)) {
logger_1.logger.critical('Unique user id has not been set in posthog.identify');
return false;
}
if (id === constants_1.COOKIELESS_SENTINEL_VALUE) {
logger_1.logger.critical("The string \"".concat(id, "\" was set in posthog.identify which indicates an error. This ID is only used as a sentinel value."));
return false;
}
if ((0, core_1.isDistinctIdStringLike)(id) || ['undefined', 'null'].includes(id.toLowerCase())) {
logger_1.logger.critical("The string \"".concat(id, "\" was set in posthog.identify which indicates an error. This ID should be unique to the user and not a hardcoded string."));
return false;
}
return true;
};
/**
* Associates a user with a unique identifier instead of an auto-generated ID.
* Learn more about [identifying users](/docs/product-analytics/identify)
*
* {@label Identification}
*
* @remarks
* By default, PostHog assigns each user a randomly generated `distinct_id`. Use this method to
* replace that ID with your own unique identifier (like a user ID from your database).
*
* @example
* ```js
* // basic identification
* posthog.identify('user_12345')
* ```
*
* @example
* ```js
* // identify with user properties
* posthog.identify('user_12345', {
* email: 'user@example.com',
* plan: 'premium'
* })
* ```
*
* @example
* ```js
* // identify with set and set_once properties
* posthog.identify('user_12345',
* { last_login: new Date() }, // updates every time
* { signup_date: new Date() } // sets only once
* )
* ```
*
* @public
*
* @param {String} [new_distinct_id] A string that uniquely identifies a user. If not provided, the distinct_id currently in the persistent store (cookie or localStorage) will be used.
* @param {Object} [userPropertiesToSet] Optional: An associative array of properties to store about the user. Note: For feature flag evaluations, if the same key is present in the userPropertiesToSetOnce,
* it will be overwritten by the value in userPropertiesToSet.
* @param {Object} [userPropertiesToSetOnce] Optional: An associative array of properties to store about the user. If property is previously set, this does not override that value.
*/
PostHog.prototype.identify = function (new_distinct_id, userPropertiesToSet, userPropertiesToSetOnce) {
var _a;
if (!this.__loaded || !this.persistence) {
return logger_1.logger.uninitializedWarning('posthog.identify');
}
if ((0, core_1.isNumber)(new_distinct_id)) {
new_distinct_id = new_distinct_id.toString();
logger_1.logger.warn('The first argument to posthog.identify was a number, but it should be a string. It has been converted to a string.');
}
if (!this._validateIdentifyId(new_distinct_id)) {
return;
}
if (!this._requirePersonProcessing('posthog.identify')) {
return;
}
this._healCookielessSentinelDistinctId();
// Adopt any sibling identity first, then make this explicit identify
// authoritative until its complete replacement cookie is published. Keep
// the pre-sync identity so adopting the requested ID still performs the
// normal feature-flag identity-change side effects in this tab.
var preSyncDistinctId = this.get_distinct_id();
var identityChangedDuringSync = this.persistence.syncCookieProperties() && this.get_distinct_id() !== preSyncDistinctId;
// Clean up the adopted identity before applying this call's explicit
// person properties. Consuming it during the $identify/$set capture would
// otherwise remove the properties that were just supplied by the caller.
var processedCookieIdentityChange = this._processCookieIdentityChange(false);
var cookieSyncSuppressionStarted = this.persistence._beginCookieSyncSuppression();
var identifyCompleted = false;
try {
var previous_distinct_id = this.get_distinct_id();
this.register({ $user_id: new_distinct_id });
if (!this.get_property(constants_1.DEVICE_ID)) {
// The persisted distinct id might not actually be a device id at all
// it might be a distinct id of the user from before
var device_id = previous_distinct_id;
this.register_once({
$had_persisted_distinct_id: true,
$device_id: device_id,
}, '');
}
// if the previous distinct id had an alias stored, then we clear it
if (new_distinct_id !== previous_distinct_id && new_distinct_id !== this.get_property(constants_1.ALIAS_ID_KEY)) {
this.unregister(constants_1.ALIAS_ID_KEY);
this.register({ distinct_id: new_distinct_id });
}
var isKnownAnonymous = (this.persistence.get_property(constants_1.USER_STATE) || constants_1.USER_STATE_ANONYMOUS) === constants_1.USER_STATE_ANONYMOUS;
var identityDidChange = new_distinct_id !== previous_distinct_id;
var shouldTransitionToIdentified = !identityDidChange && isKnownAnonymous;
// send an $identify event any time the distinct_id is changing and the old ID is an anonymous ID
// - logic on the server will determine whether or not to do anything with it.
if (identityDidChange && isKnownAnonymous) {
this.persistence.set_property(constants_1.USER_STATE, constants_1.USER_STATE_IDENTIFIED);
// Update current user properties
this.setPersonPropertiesForFlags({ $set: userPropertiesToSet || {}, $set_once: userPropertiesToSetOnce || {} }, false);
if (this.config.cookieWinsOnConflict) {
// Publish the identity transition before capture so sibling
// subdomains never observe the old cookie during the debounce window.
this.persistence._publishSuppressedCookieSnapshot();
}
this.capture(constants_1.EVENT_IDENTIFY, {
distinct_id: new_distinct_id,
$anon_distinct_id: previous_distinct_id,
}, { $set: userPropertiesToSet || {}, $set_once: userPropertiesToSetOnce || {} });
this._cachedPersonProperties = (0, property_utils_1.getPersonPropertiesHash)(new_distinct_id, userPropertiesToSet, userPropertiesToSetOnce);
// let the reload feature flag request know to send this previous distinct id
// for flag consistency
(_a = this.featureFlags) === null || _a === void 0 ? void 0 : _a.setAnonymousDistinctId(previous_distinct_id);
}
else if (shouldTransitionToIdentified) {
this.persistence.set_property(constants_1.USER_STATE, constants_1.USER_STATE_IDENTIFIED);
var setProperties = userPropertiesToSet || {};
var setOnceProperties = userPropertiesToSetOnce || {};
this.setPersonPropertiesForFlags({ $set: setProperties, $set_once: setOnceProperties }, false);
if (this.config.cookieWinsOnConflict) {
this.persistence._publishSuppressedCookieSnapshot();
}
this.capture('$set', { $set: setProperties, $set_once: setOnceProperties });
// This transition must create/update the person even when an identical property call was cached earlier.
// Cache only after capture so deduplication cannot suppress the transition event.
this._cachedPersonProperties = (0, property_utils_1.getPersonPropertiesHash)(new_distinct_id, userPropertiesToSet, userPropertiesToSetOnce);
}
else if (userPropertiesToSet || userPropertiesToSetOnce) {
// If the distinct_id is not changing, but we have user properties to set, we can check if they have changed
// and if so, send a $set event
this.setPersonProperties(userPropertiesToSet, userPropertiesToSetOnce);
}
// Reload active feature flags if the distinct ID changes. Clear stored flag calls because they belong to the
// previous identity. A same-ID transition only needs a reload when the caller supplied properties that can
// affect flag evaluation; the anonymous/identified state itself is not part of the /flags request.
if (identityDidChange || identityChangedDuringSync || processedCookieIdentityChange) {
this.reloadFeatureFlags();
if (this.featureFlags) {
this.featureFlags.resetFlagCallReported();
}
else {
this.unregister(constants_1.FLAG_CALL_REPORTED);
}
}
else if (shouldTransitionToIdentified && (userPropertiesToSet || userPropertiesToSetOnce)) {
this.reloadFeatureFlags();
}
identifyCompleted = true;
}
finally {
if (cookieSyncSuppressionStarted) {
// Do not publish a partially assembled identity when a customer
// callback or capture hook throws during identify().
this.persistence._endCookieSyncSuppression(identifyCompleted);
}
}
};
/**
* Sets properties on the person profile associated with the current `distinct_id`.
* Learn more about [identifying users](/docs/product-analytics/identify)
*
* {@label Identification}
*
* @remarks
* Updates user properties that are stored with the person profile in PostHog.
* If `person_profiles` is set to `identified_only` and no profile exists, this will create one.
*
* @example
* ```js
* // set user properties
* posthog.setPersonProperties({
* email: 'user@example.com',
* plan: 'premium'
* })
* ```
*
* @example
* ```js
* // set properties
* posthog.setPersonProperties(
* { name: 'Max Hedgehog' }, // $set properties
* { initial_url: '/blog' } // $set_once properties
* )
* ```
*
* @public
*
* @param {Object} [userPropertiesToSet] Optional: An associative array of properties to store about the user. Note: For feature flag evaluations, if the same key is present in the userPropertiesToSetOnce,
* it will be overwritten by the value in userPropertiesToSet.
* @param {Object} [userPropertiesToSetOnce] Optional: An associative array of properties to store about the user. If property is previously set, this does not override that value.
*/
PostHog.prototype.setPersonProperties = function (userPropertiesToSet, userPropertiesToSetOnce) {
if (!userPropertiesToSet && !userPropertiesToSetOnce) {
return;
}
if (!this._requirePersonProcessing('posthog.setPersonProperties')) {
return;
}
var hash = (0, property_utils_1.getPersonPropertiesHash)(this.get_distinct_id(), userPropertiesToSet, userPropertiesToSetOnce);
// if exactly this $set call has been sent before, don't send it again - determine based on hash of properties
if (this._cachedPersonProperties === hash) {
logger_1.logger.info('A duplicate setPersonProperties call was made with the same properties. It has been ignored.');
return;
}
// Update current user properties
this.setPersonPropertiesForFlags({ $set: userPropertiesToSet || {}, $set_once: userPropertiesToSetOnce || {} }, true);
this.capture('$set', { $set: userPropertiesToSet || {}, $set_once: userPropertiesToSetOnce || {} });
this._cachedPersonProperties = hash;
};
/**
* Removes properties from the person profile associated with the current `distinct_id`.
* Learn more about [identifying users](/docs/product-analytics/identify)
*
* {@label Identification}
*
* @remarks
* Deletes the given person properties from the person profile in PostHog. This is the
* counterpart to {@link setPersonProperties} — instead of hand-passing `$unset` inside a
* `capture()` call, you can remove properties with a dedicated method.
* If `person_profiles` is set to `never`, this call is ignored.
*
* @example
* ```js
* // remove a single property
* posthog.unsetPersonProperties('plan')
* ```
*
* @example
* ```js
* // remove multiple properties
* posthog.unsetPersonProperties(['plan', 'email'])
* ```
*
* @public
*
* @param {String|String[]} propertyNames The name (or names) of the person properties to remove.
*/
PostHog.prototype.unsetPersonProperties = function (propertyNames) {
var _a;
var names = ((0, core_1.isArray)(propertyNames) ? propertyNames : [propertyNames]).filter(function (name) { return (0, core_1.isString)(name) && name.length > 0; });
if (names.length === 0) {
return;
}
if (!this._requirePersonProcessing('posthog.unsetPersonProperties')) {
return;
}
// Remove the properties from the locally-stored person properties used for flag evaluation
// so flags re-evaluate without the unset values, mirroring setPersonPropertiesForFlags.
(_a = this.featureFlags) === null || _a === void 0 ? void 0 : _a.unsetPersonPropertiesForFlags(names, true);
this.capture('$set', { $unset: names });
// Invalidate the setPersonProperties dedupe cache so a subsequent set of an unset
// property is not skipped as a duplicate.
this._cachedPersonProperties = null;
};
/**
* Associates the user with a group for group-based analytics.
* Learn more about [groups](/docs/product-analytics/group-analytics)
*
* {@label Identification}
*
* @remarks
* Groups allow you to analyze users collectively (e.g., by organization, team, or account).
* This sets the group association for all subsequent events and reloads feature flags.
*
* @example
* ```js
* // associate user with an organization
* posthog.group('organization', 'org_12345', {
* name: 'Acme Corp',
* plan: 'enterprise'
* })
* ```
*
* @example
* ```js
* // associate with multiple group types
* posthog.group('organization', 'org_12345')
* posthog.group('team', 'team_67890')
* ```
*
* @public
*
* @param {String} groupType Group type (example: 'organization')
* @param {String} groupKey Group key (example: 'org::5')
* @param {Object} groupPropertiesToSet Optional properties to set for group
*/
PostHog.prototype.group = function (groupType, groupKey, groupPropertiesToSet) {
var _a, _b;
var _c;
if (!groupType || !groupKey) {
logger_1.logger.error('posthog.group requires a group type and group key');
return;
}
// Apply a sibling reset before reading or writing groups so this explicit
// mutation is newer than the adopted cookie snapshot.
(_c = this.persistence) === null || _c === void 0 ? void 0 : _c.syncCookieProperties();
this._processCookieIdentityChange();
var existingGroups = this.getGroups();
var isNewGroup = existingGroups[groupType] !== groupKey;
// if group key changes, remove stored group properties
if (isNewGroup) {
this.resetGroupPropertiesForFlags(groupType);
}
this.register({ $groups: __assign(__assign({}, existingGroups), (_a = {}, _a[groupType] = groupKey, _a)) });
// Send $groupidentify when the group is new/changed OR when properties
// are provided. Skip only when the group already exists with the same
// key and no new properties are being set.
if (isNewGroup || groupPropertiesToSet) {
var groupIdentifyProperties = {
$group_type: groupType,
$group_key: groupKey,
};
if (groupPropertiesToSet) {
groupIdentifyProperties.$group_set = groupPropertiesToSet;
}
this.capture(constants_1.EVENT_GROUPIDENTIFY, groupIdentifyProperties);
}
if (groupPropertiesToSet) {
this.setGroupPropertiesForFlags((_b = {}, _b[groupType] = groupPropertiesToSet, _b));
}
// If groups change and no properties change, reload feature flags.
// The property change reload case is handled in setGroupPropertiesForFlags.
if (isNewGroup && !groupPropertiesToSet) {
this.reloadFeatureFlags();
}
};
/**
* Resets only the group properties of the user currently logged in.
* Learn more about [groups](/docs/product-analytics/group-analytics)
*
* {@label Identification}
*
* @example
* ```js
* posthog.resetGroups()
* ```
*
* @public
*/
PostHog.prototype.resetGroups = function () {
this.register({ $groups: {} });
this.resetGroupPropertiesForFlags();
// If groups changed, reload feature flags.
this.reloadFeatureFlags();
};
/**
* Sometimes, you might want to evaluate feature flags using properties that haven't been ingested yet,
* or were set incorrectly earlier. You can do so by setting properties the flag depends on with these calls:
*
* {@label Feature flags}
*
* @example
* ```js
* // Set properties
* posthog.setPersonPropertiesForFlags({'property1': 'value', property2: 'value2'})
* ```
*
* @example
* ```js
* // Set properties without reloading
* posthog.setPersonPropertiesForFlags({'property1': 'value', property2: 'value2'}, false)
* ```
*
* @public
*
* @param {Object} properties The properties to override.
* @param {Boolean} [reloadFeatureFlags] Whether to reload feature flags.
*/
PostHog.prototype.setPersonPropertiesForFlags = function (properties, reloadFeatureFlags) {
var _a;
if (reloadFeatureFlags === void 0) { reloadFeatureFlags = true; }
(_a = this.featureFlags) === null || _a === void 0 ? void 0 : _a.setPersonPropertiesForFlags(properties, reloadFeatureFlags);
};
/**
* Resets the person properties for feature flags.
*
* {@label Feature flags}
*
* @public
*
* @example
* ```js
* posthog.resetPersonPropertiesForFlags()
* ```
*
* @example
* ```js
* // Reset properties without reloading
* posthog.resetPersonPropertiesForFlags(false)
* ```
*
* @param {Boolean} [reloadFeatureFlags=true] Whether to reload feature flags.
*/
PostHog.prototype.resetPersonPropertiesForFlags = function (reloadFeatureFlags) {
var _a;
if (reloadFeatureFlags === void 0) { reloadFeatureFlags = true; }
(_a = this.featureFlags) === null || _a === void 0 ? void 0 : _a.resetPersonPropertiesForFlags(reloadFeatureFlags);
};
/**
* Set override group properties for feature flags.
* This is used when dealing with new groups / where you don't want to wait for ingestion
* to update properties.
* Takes in an object, the key of which is the group type.
*
* {@label Feature flags}
*
* @public
*
* @example
* ```js
* // Set properties with reload
* posthog.setGroupPropertiesForFlags({'organization': { name: 'CYZ', employees: '11' } })
* ```
*
* @example
* ```js
* // Set properties without reload
* posthog.setGroupPropertiesForFlags({'organization': { name: 'CYZ', employees: '11' } }, false)
* ```
*
* @param {Object} properties The properties to override, the key of which is the group type.
* @param {Boolean} [reloadFeatureFlags] Whether to reload feature flags.
*/
PostHog.prototype.setGroupPropertiesForFlags = function (properties, reloadFeatureFlags) {
var _a;
if (reloadFeatureFlags === void 0) { reloadFeatureFlags = true; }
if (!this._requirePersonProcessing('posthog.setGroupPropertiesForFlags')) {
return;
}
(_a = this.featureFlags) === null || _a === void 0 ? void 0 : _a.setGroupPropertiesForFlags(properties, reloadFeatureFlags);
};
/**
* Resets the group properties for feature flags.
*
* {@label Feature flags}
*
* @public
*
* @example
* ```js
* posthog.resetGroupPropertiesForFlags()
* ```
*
* @param {string} [group_type] Optional group type to reset. If omitted, all group properties are reset.
*/
PostHog.prototype.resetGroupPropertiesForFlags = function (group_type) {
var _a;
(_a = this.featureFlags) === null || _a === void 0 ? void 0 : _a.resetGroupPropertiesForFlags(group_type);
};
/**
* Resets all user data and starts a fresh session.
*
* ⚠️ **Warning**: Only call this when a user logs out. Calling at the wrong time can cause split sessions.
*
* This clears:
* - Session ID and super properties
* - User identification (sets new random distinct_id)
* - Cached data and consent settings
*
* ⚠️ **Warning**: because consent is cleared, `reset()` returns the instance to the default
* consent state. With `opt_out_capturing_by_default` that default is opted out,
* so calling `reset()` *after* `opt_in_capturing()` silently stops capturing.
* Always `reset()` first, then opt in.
*
* {@label Identification}
* @example
* ```js
* // reset on user logout
* function logout() {
* posthog.reset()
* // redirect to login page
* }
* ```
*
* @example
* ```js
* // reset and generate new device ID
* posthog.reset(true) // also resets device_id
* ```
*
* @example
* ```js
* // reset with a custom anonymous ID and bootstrapped feature flags
* posthog.reset({
* bootstrap: {
* distinctID: myAnonymousID,
* isIdentifiedID: false,
* featureFlags: { 'my-flag': true },
* }
* })
* ```
*
* @example
* ```js
* // with opt_out_capturing_by_default, reset() before opting in, never after
* posthog.reset()
* posthog.opt_in_capturing()
* ```
*
* @public
*
* @param options Boolean to reset the device ID (legacy), or reset options including bootstrap values.
*/
PostHog.prototype.reset = function (options) {
var reset_device_id = (0, core_1.isBoolean)(options) ? options : options === null || options === void 0 ? void 0 : options.resetDeviceID;
var bootstrap = (0, core_1.isBoolean)(options) ? undefined : options === null || options === void 0 ? void 0 : options.bootstrap;
this._reset(reset_device_id, false, bootstrap);
};
PostHog.prototype._reset = function (reset_device_id, isConsentTransition, bootstrap) {
var _a, _b;
var _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
if (isConsentTransition === void 0) { isConsentTransition = false; }
logger_1.logger.info('reset');
if (!this.__loaded) {
return logger_1.logger.uninitializedWarning('posthog.reset');
}
var bootstrapSessionID = bootstrap === null || bootstrap === void 0 ? void 0 : bootstrap.sessionID;
this.config.bootstrap = bootstrap || ((_c = this._originalUserConfig) === null || _c === void 0 ? void 0 : _c.bootstrap) || {};
(_e = (_d = this.featureFlags) === null || _d === void 0 ? void 0 : _d.updateConfig) === null || _e === void 0 ? void 0 : _e.call(_d, this.config, this._shouldDisableFlags());
var device_id = this.get_property(constants_1.DEVICE_ID);
// $device_model describes the physical device, not the user, so preserve it across reset()
// the same way $device_id is — it is only ever re-resolved at init.
var device_model = this.get_property(constants_1.DEVICE_MODEL);
// Snapshot the session-recording remote config before clearing persistence.
// It's server-defined config (sample rate, masking, canvas, triggers, …),
// not user state, and must survive reset(). Otherwise start('session_id_changed')
// bails on the next session rotation: rrweb is torn down with no replacement
// and the new session opens with no FullSnapshot until the next periodic
// checkout (~5 min later).
var recordingRemoteConfig = this.get_property(constants_1.SESSION_RECORDING_REMOTE_CONFIG);
// Consent is user state, so reset() clears it along with the rest. But when capturing is
// opted out by default that flips capturing back off, and nothing else surfaces it: events
// are dropped with no error. Warn instead of failing silently.
var wasCapturing = this.is_capturing();
this.consent.reset();
if (!isConsentTransition && wasCapturing && !this.is_capturing()) {
// Unlike logger.warn(), this warning must be visible with the normal debug:false configuration.
// eslint-disable-next-line no-console
console.warn('[PostHog.js]', RESET_CONSENT_WARN);
}
var cookieSyncSuppressionStarted = (_g = (_f = this.persistence) === null || _f === void 0 ? void 0 : _f._beginCookieSyncSuppression) === null || _g === void 0 ? void 0 : _g.call(_f);
var resetCompleted = false;
try {
(_h = this.persistence) === null || _h === void 0 ? void 0 : _h.clear();
(_j = this.sessionPersistence) === null || _j === void 0 ? void 0 : _j.clear();
this._sessionRegisteredPropKeys.clear();
this._persistSessionRegisteredPropKeys();
if (!(0, core_1.isUndefined)(recordingRemoteConfig)) {
(_k = this.persistence) === null || _k === void 0 ? void 0 : _k.register((_a = {}, _a[constants_1.SESSION_RECORDING_REMOTE_CONFIG] = recordingRemoteConfig, _a));
}
(_l = this.surveys) === null || _l === void 0 ? void 0 : _l.reset();
(_m = this.featureFlags) === null || _m === void 0 ? void 0 : _m.reset();
(_o = this.conversations) === null || _o === void 0 ? void 0 : _o.reset();
(_p = this.logs) === null || _p === void 0 ? void 0 : _p.reset();
(_q = this.metrics) === null || _q === void 0 ? void 0 : _q.reset();
(_r = this.persistence) === null || _r === void 0 ? void 0 : _r.set_property(constants_1.USER_STATE, constants_1.USER_STATE_ANONYMOUS);
(_s = this.sessionManager) === null || _s === void 0 ? void 0 : _s.resetSessionId();
this._cachedPersonProperties = null;
if (this.config.cookieless_mode === constants_1.COOKIELESS_ALWAYS) {
this.register_once({
distinct_id: constants_1.COOKIELESS_SENTINEL_VALUE,
$device_id: null,
}, '');
}
else {
var uuid = this.config.get_device_id((0, uuidv7_1.uuidv7)());
this.register_once({
distinct_id: uuid,
$device_id: reset_device_id ? uuid : device_id,
}, '');
if (!reset_device_id && !(0, core_1.isUndefined)(device_model)) {
this.register((_b = {}, _b[constants_1.DEVICE_MODEL] = device_model, _b));
}
}
this.register({
$last_posthog_reset: new Date().toISOString(),
}, 1);
if (bootstrap) {
// isUndefined doesn't provide typehint here so wouldn't reduce bundle as we'd need to assign
// eslint-disable-next-line posthog-js/no-direct-undefined-check
if (bootstrap.distinctID !== undefined && !this._inCookielessMode()) {
(_t = this.persistence) === null || _t === void 0 ? void 0 : _t.set_property(constants_1.USER_STATE, bootstrap.isIdentifiedID ? constants_1.USER_STATE_IDENTIFIED : constants_1.USER_STATE_ANONYMOUS);
this.register({ distinct_id: bootstrap.distinctID });
}
(_u = this.featureFlags) === null || _u === void 0 ? void 0 : _u.initialize();
if (!(0, core_1.isUndefined)(bootstrapSessionID) &&
!((_v = this.sessionManager) === null || _v === void 0 ? void 0 : _v.setBootstrapSessionId(bootstrapSessionID, true))) {
var bootstrapWithoutSessionID = __assign({}, bootstrap);
delete bootstrapWithoutSessionID.sessionID;
this.config.bootstrap = bootstrapWithoutSessionID;
}
}
// Clear HMAC identity verification fields
delete this.config.identity_distinct_id;
delete this.config.identity_hash;
resetCompleted = true;
}
finally {
if (cookieSyncSuppressionStarted) {
// Publish the reset identity as one complete cookie before another
// sibling tab can resurrect the pre-reset state. Always release
// suppression, including when a customer callback throws.
(_x = (_w = this.persistence) === null || _w === void 0 ? void 0 : _w._endCookieSyncSuppression) === null || _x === void 0 ? void 0 : _x.call(_w, resetCompleted);
}
}
// Reload feature flags for the reset user, just like identify()
// does when the distinct_id changes.
this.reloadFeatureFlags();
};
/**
* Flushes any queued events and resolves once teardown is complete.
*
* @remarks
* This exists primarily for parity with the server-side
* [Node.js SDK](/docs/libraries/node), whose `shutdown()` you call once before a
* process exits. In the browser there is no process to exit, so this method
* performs synchronous best-effort extension cleanup, flushes the request
* queues, and always resolves.
*
* It is safe to call in isomorphic teardown code (for example a Nuxt/Next module
* that calls `shutdown()` on both the server and the client) so the same
* symmetric cleanup works in either environment without throwing.
*
* {@label Lifecycle}
* @example
* ```js
* // symmetric teardown that runs on both server and client
* await posthog.shutdown()
* ```
*
* @public
*
* @param {number} [_shutdownTimeoutMs] Retained for parity with the Node.js SDK; ignored in browsers.
* @returns {Promise<void>} A promise that resolves once best-effort cleanup and queue flushing complete.
*/
PostHog.prototype.shutdown = function (_shutdownTimeoutMs) {
return __awaiter(this, void 0, void 0, function () {
var _a, _b, _c, _d, _e, _f, _g, _h;
return __generator(this, function (_j) {
void _shutdownTimeoutMs;
if (!this.__loaded) {
logger_1.logger.uninitializedWarning('posthog.shutdown');
return [2 /*return*/];
}
this._getBrowserClientAdapter().dispose();
(_a = this.sessionRecording) === null || _a === void 0 ? void 0 : _a.dispose();
// Best-effort flush of anything still queued, mirroring page-unload teardown
// so no buffered events are silently dropped when teardown is explicit.
(_b = this.logs) === null || _b === void 0 ? void 0 : _b.flushLogs('sendBeacon');
void ((_c = this.metrics) === null || _c === void 0 ? void 0 : _c.flush('sendBeacon'));
(_d = this._requestQueue) === null || _d === void 0 ? void 0 : _d.unload();
(_e = this._retryQueue) === null || _e === void 0 ? void 0 : _e.unload();
try {
(_f = this.featureFlags) === null || _f === void 0 ? void 0 : _f.destroy();
}
catch (error) {
logger_1.logger.error('Error while destroying feature flags', error);
}
(_g = this.persistence) === null || _g === void 0 ? void 0 : _g.destroy();
(_h = this.sessionPersistence) === null || _h === void 0 ? void 0 : _h.destroy();
return [2 /*return*/];
});
});
};
/**
* Set HMAC-based identity verification.
*
* @remarks
* When set, products like conversations use server-verified identity
* (distinct_id + HMAC hash) instead of anonymous session identifiers.
* The hash should be computed server-side as HMAC-SHA256 of the
* distinct_id using the project's API secret.
*
* @param distinctId - The verified user distinct_id
* @param hash - HMAC-SHA256 of distinctId using the project API secret
*
* @example
* ```js
* posthog.setIdentity('user_123', 'a1b2c3d4e5f6...')
* ```
*
* @public
*/
PostHog.prototype.setIdentity = function (distinctId, hash) {
var _a;
this.config.identity_distinct_id = distinctId;
this.config.identity_hash = hash;
this.alias(distinctId);
(_a = this.conversations) === null || _a === void 0 ? void 0 : _a._onIdentityChanged();
};
/**
* Clear HMAC-based identity verification, reverting to anonymous mode.
*
* @example
* ```js
* posthog.clearIdentity()
* ```
*
* @public
*/
PostHog.prototype.clearIdentity = function () {
var _a;
delete this.config.identity_distinct_id;
delete this.config.identity_hash;
(_a = this.conversations) === null || _a === void 0 ? void 0 : _a._onIdentityCleared();
};
/**
* Returns the current distinct ID for the user.
*
* @remarks
* This is either the auto-generated ID or the ID set via `identify()`.
* The distinct ID is used to associate events with users in PostHog.
*
* {@label Identification}
*
* @example
* ```js
* // get the current user ID
* const userId = posthog.get_distinct_id()
* console.log('Current user:', userId)
* ```
*
* @example
* ```js
* // use in loaded callback
* posthog.init('token', {
* loaded: (posthog) => {
* const id = posthog.get_distinct_id()
* // use the ID
* }
* })
* ```
*
* @public
*
* @returns The current distinct ID
*/
PostHog.prototype.get_distinct_id = function () {
return this.get_property('distinct_id');
};
/**
* Returns the current groups.
*
* {@label Identification}
*
* @public
*
* @returns The current groups
*/
PostHog.prototype.getGroups = function () {
return this.get_property('$groups') || {};
};
/**
* Returns the current session_id.
*
* @remarks
* This should only be used for informative purposes.
* Any actual internal use case for the session_id should be handled by the sessionManager.
*
* @public
*
* @returns The stored session ID for the current session. This may be an empty string if the client is not yet fully initialized.
*/
PostHog.prototype.get_session_id = function () {
var _a, _b;
return (_b = (_a = this.sessionManager) === null || _a === void 0 ? void 0 : _a.checkAndGetSessionAndWindowId(true).sessionId) !== null && _b !== void 0 ? _b : '';
};
/**
* Returns the Replay url for the current session.
*
* {@label Session replay}
*
* @public
*
* @example
* ```js
* // basic usage
* posthog.get_session_replay_url()
* ```
*
* @example
* ```js
* // timestamp
* posthog.get_session_replay_url({ withTimestamp: true })
* ```
*
* @example
* ```js
* // timestamp and lookback
* posthog.get_session_replay_url({
* withTimestamp: true,
* timestampLookBack: 30 // look back 30 seconds
* })
* ```
*
* @param {Object} [options] Options for the URL.
* @param {boolean} [options.withTimestamp] Whether to include the timestamp in the URL.
* @param {number} [options.timestampLookBack] How many seconds to look back for the timestamp.
* @returns The URL for the current session replay, or an empty string if sessions are unavailable.
*/
PostHog.prototype.get_session_replay_url = function (options) {
var _a;
if (!this.sessionManager) {
return '';
}
var _b = this.sessionManager.checkAndGetSessionAndWindowId(true), sessionId = _b.sessionId, sessionStartTimestamp = _b.sessionStartTimestamp;
var url = this.requestRouter.endpointFor('ui', "/project/".concat(this.config.token, "/replay/").concat(sessionId));
if ((options === null || options === void 0 ? void 0 : options.withTimestamp) && sessionStartTimestamp) {
var LOOK_BACK = (_a = options.timestampLookBack) !== null && _a !== void 0 ? _a : 10;
if (!sessionStartTimestamp) {
return url;
}
var recordingStartTime = Math.max(Math.floor((new Date().getTime() - sessionStartTimestamp) / 1000) - LOOK_BACK, 0);
url += "?t=".concat(recordingStartTime);
}
return url;
};
/**
* Creates an alias linking two distinct user identifiers. Learn more about [identifying users](/docs/product-analytics/identify)
*
* {@label Identification}
*
* @remarks
* PostHog will use this to link two distinct_ids going forward (not retroactively).
* Call this when a user signs up to connect their anonymous session with their account.
*
*
* @example
* ```js
* // link anonymous user to account on signup
* posthog.alias('user_12345')
* ```
*
* @example
* ```js
* // explicit alias with original ID
* posthog.alias('user_12345', 'anonymous_abc123')
* ```
*
* @public
*
* @param {String} alias A unique identifier that you want to use for this user in the future.
* @param {String} [original] The current identifier being used for this user.
* @returns The `$create_alias` capture result, `-1` if alias matches the current distinct ID, `-2` if aliasing would duplicate a People user, or void if person processing is disabled.
*/
PostHog.prototype.alias = function (alias, original) {
// If the $people_distinct_id key exists in persistence, there has been a previous
// posthog.people.identify() call made for this user. It is VERY BAD to make an alias with
// this ID, as it will duplicate users.
if (alias === this.get_property(constants_1.PEOPLE_DISTINCT_ID_KEY)) {
logger_1.logger.critical('Attempting to create alias for existing People user - aborting.');
return -2;
}
if (!this._requirePersonProcessing('posthog.alias')) {
return;
}
if ((0, core_1.isUndefined)(original)) {
original = this.get_distinct_id();
}
if (alias !== original) {
this._register_single(constants_1.ALIAS_ID_KEY, alias);
return this.capture('$create_alias', { alias: alias, distinct_id: original });
}
else {
logger_1.logger.warn('alias matches current distinct_id - skipping api call.');
this.identify(alias);
return -1;
}
};
/**
* Updates the configuration of the PostHog instance.
*
* {@label Initialization}
*
* @public
*
* @param {Partial<PostHogConfig>} config A dictionary of new configuration values to update
*/
PostHog.prototype.set_config = function (config) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
var oldConfig = __assign({}, this.config);
if ((0, core_1.isObject)(config)) {
(0, general_utils_1.extend)(this.config, (0, exports.configRenames)(config));
var isPersistenceDisabled = this._is_persistence_disabled();
(_a = this.persistence) === null || _a === void 0 ? void 0 : _a.update_config(this.config, oldConfig, isPersistenceDisabled);
this.sessionPersistence =
this.config.persistence === 'sessionStorage' || this.config.persistence === 'memory'
? this.persistence
: // sessionStorage sibling shares the primary's storage name; it must not own/clean the split group entries
new posthog_persistence_1.PostHogPersistence(__assign(__assign({}, this.config), { persistence: 'sessionStorage' }), isPersistenceDisabled, false);
var debugConfigFromLocalStorage = this._checkLocalStorageForDebug(this.config.debug);
if ((0, core_1.isBoolean)(debugConfigFromLocalStorage)) {
this.config.debug = debugConfigFromLocalStorage;
}
if ((0, core_1.isBoolean)(this.config.debug)) {
if (this.config.debug) {
config_1.default.DEBUG = true;
storage_1.localStore._is_supported() && storage_1.localStore._set('ph_debug', true);
logger_1.logger.info('set_config', {
config: config,
oldConfig: oldConfig,
newConfig: __assign({}, this.config),
});
}
else {
config_1.default.DEBUG = false;
storage_1.localStore._is_supported() && storage_1.localStore._remove('ph_debug');
}
}
(_c = (_b = this.featureFlags) === null || _b === void 0 ? void 0 : _b.updateConfig) === null || _c === void 0 ? void 0 : _c.call(_b, this.config, this._shouldDisableFlags());
(_d = this.exceptionObserver) === null || _d === void 0 ? void 0 : _d.onConfigChange();
(_e = this.exceptions) === null || _e === void 0 ? void 0 : _e.onConfigChange();
(_f = this.sessionRecording) === null || _f === void 0 ? void 0 : _f.startIfEnabledOrStop();
(_g = this.tracingHeaders) === null || _g === void 0 ? void 0 : _g.startIfEnabledOrStop();
(_h = this.autocapture) === null || _h === void 0 ? void 0 : _h.startIfEnabled();
(_j = this.heatmaps) === null || _j === void 0 ? void 0 : _j.startIfEnabled();
if ('capture_pageview' in config || 'disable_capture_url_hashes' in config) {
(_k = this.historyAutocapture) === null || _k === void 0 ? void 0 : _k.startIfEnabledOrStop();
}
(_l = this.exceptionObserver) === null || _l === void 0 ? void 0 : _l.startIfEnabledOrStop();
(_m = this.deadClicksAutocapture) === null || _m === void 0 ? void 0 : _m.startIfEnabledOrStop();
(_o = this.surveys) === null || _o === void 0 ? void 0 : _o.loadIfEnabled();
this._sync_opt_out_with_persistence();
(_p = this.externalIntegrations) === null || _p === void 0 ? void 0 : _p.startIfEnabledOrStop();
}
};
/**
* @internal
* Allows wrapper SDKs (e.g. posthog-flutter, posthog-react-native) to override the
* `$lib` and `$lib_version` properties sent with every event.
*
* This is not a public API and may change without notice.
*/
PostHog.prototype._overrideSDKInfo = function (sdkName, sdkVersion) {
config_1.default.LIB_NAME = sdkName;
config_1.default.LIB_VERSION = sdkVersion;
};
/**
* turns session recording on, and updates the config option `disable_session_recording` to false
*
* {@label Session replay}
*
* @public
*
* @example
* ```js
* // Start and ignore controls
* posthog.startSessionRecording(true)
* ```
*
* @example
* ```js
* // Start and override controls
* posthog.startSessionRecording({
* // you don't have to send all of these
* sampling: true || false,
* linked_flag: true || false,
* url_trigger: true || false,
* event_trigger: true || false
* })
* ```
*
* @param override.sampling - optional boolean to override the default sampling behavior - ensures the next session recording to start will not be skipped by sampling config.
* @param override.linked_flag - optional boolean to override the default linked_flag behavior - ensures the next session recording to start will not be skipped by linked_flag config.
* @param override.url_trigger - optional boolean to override the default url_trigger behavior - ensures the next session recording to start will not be skipped by url_trigger config.
* @param override.event_trigger - optional boolean to override the default event_trigger behavior - ensures the next session recording to start will not be skipped by event_trigger config.
* @param override - optional boolean to override the default sampling behavior - ensures the next session recording to start will not be skipped by sampling or linked_flag config. `true` is shorthand for { sampling: true, linked_flag: true }
*/
PostHog.prototype.startSessionRecording = function (override) {
var _a, _b, _c, _d, _e;
var overrideAll = override === true;
var overrideConfig = {
sampling: overrideAll || !!(override === null || override === void 0 ? void 0 : override.sampling),
linked_flag: overrideAll || !!(override === null || override === void 0 ? void 0 : override.linked_flag),
url_trigger: overrideAll || !!(override === null || override === void 0 ? void 0 : override.url_trigger),
event_trigger: overrideAll || !!(override === null || override === void 0 ? void 0 : override.event_trigger),
};
if (Object.values(overrideConfig).some(Boolean)) {
// allow the session id check to rotate session id if necessary
(_a = this.sessionManager) === null || _a === void 0 ? void 0 : _a.checkAndGetSessionAndWindowId();
if (overrideConfig.sampling) {
(_b = this.sessionRecording) === null || _b === void 0 ? void 0 : _b.overrideSampling();
}
if (overrideConfig.linked_flag) {
(_c = this.sessionRecording) === null || _c === void 0 ? void 0 : _c.overrideLinkedFlag();
}
if (overrideConfig.url_trigger) {
(_d = this.sessionRecording) === null || _d === void 0 ? void 0 : _d.overrideTrigger('url');
}
if (overrideConfig.event_trigger) {
(_e = this.sessionRecording) === null || _e === void 0 ? void 0 : _e.overrideTrigger('event');
}
}
this.set_config({ disable_session_recording: false });
};
/**
* turns session recording off, and updates the config option
* disable_session_recording to true
*
* {@label Session replay}
*
* @public
*
* @example
* ```js
* // Stop session recording
* posthog.stopSessionRecording()
* ```
*/
PostHog.prototype.stopSessionRecording = function () {
this.set_config({ disable_session_recording: true });
};
/**
* returns a boolean indicating whether session recording
* is currently running
*
* {@label Session replay}
*
* @public
*
* @example
* ```js
* // Stop session recording if it's running
* if (posthog.sessionRecordingStarted()) {
* posthog.stopSessionRecording()
* }
* ```
*
* @returns Whether session recording is currently running.
*/
PostHog.prototype.sessionRecordingStarted = function () {
var _a;
return !!((_a = this.sessionRecording) === null || _a === void 0 ? void 0 : _a.started);
};
/**
* Capture a caught exception manually
*
* {@label Error tracking}
*
* @public
*
* @example
* ```js
* // Capture a caught exception
* try {
* // something that might throw
* } catch (error) {
* posthog.captureException(error)
* }
* ```
*
* @example
* ```js
* // With additional properties
* posthog.captureException(error, {
* customProperty: 'value',
* anotherProperty: ['I', 'can be a list'],
* ...
* })
* ```
*
* @param {unknown} error The error or exception-like value to capture.
* @param {Properties} [additionalProperties] Any additional properties to add to the error event.
* @returns The result of the capture, or undefined if exception capture is unavailable.
*/
PostHog.prototype.captureException = function (error, additionalProperties) {
if (!this.exceptions)
return;
var syntheticException = new Error('PostHog syntheticException');
var errorToProperties = this.exceptions.buildProperties(error, {
handled: true,
syntheticException: syntheticException,
});
return this.exceptions.sendExceptionEvent(__assign(__assign({}, errorToProperties), additionalProperties));
};
/**
* Add a breadcrumb-like step that will be attached to the next captured exception.
*
* {@label Error tracking}
*
* @public
*
* @example
* ```js
* posthog.addExceptionStep('Checkout button clicked', {
* checkout_id: 'ch_123',
* })
* ```
*
* @param {string} message The step message.
* @param {Properties} [properties] Additional context for this step.
*/
PostHog.prototype.addExceptionStep = function (message, properties) {
var _a;
(_a = this.exceptions) === null || _a === void 0 ? void 0 : _a.addExceptionStep(message, properties);
};
/**
* Capture a log entry and send it to the PostHog logs endpoint.
*
* {@label Logs}
*
* @public
*
* @example
* ```js
* posthog.captureLog({
* body: 'checkout completed',
* level: 'info',
* attributes: { order_id: 'ord_789', amount_cents: 4999 },
* })
* ```
*
* @param {CaptureLogOptions} options The log entry options
*/
PostHog.prototype.captureLog = function (options) {
var _a;
(_a = this.logs) === null || _a === void 0 ? void 0 : _a.captureLog(options);
};
Object.defineProperty(PostHog.prototype, "logger", {
/**
* Logger with convenience methods for each severity level.
*
* @example
* ```js
* posthog.logger.info('checkout completed', { order_id: 'ord_789' })
* posthog.logger.error('payment failed', { error_code: 'E001' })
* ```
*/
get: function () {
var _a, _b;
return (_b = (_a = this.logs) === null || _a === void 0 ? void 0 : _a.logger) !== null && _b !== void 0 ? _b : PostHog._noopLogger;
},
enumerable: false,
configurable: true
});
/**
* turns exception autocapture on, and updates the config option `capture_exceptions` to the provided config (or `true`)
*
* {@label Error tracking}
*
* @public
*
* @example
* ```js
* // Start with default exception autocapture rules. No-op if already enabled
* posthog.startExceptionAutocapture()
* ```
*
* @example
* ```js
* // Start and override controls
* posthog.startExceptionAutocapture({
* // you don't have to send all of these (unincluded values will use the default)
* capture_unhandled_errors: true || false,
* capture_unhandled_rejections: true || false,
* capture_console_errors: true || false
* })
* ```
*
* @param config - optional configuration option to control the exception autocapture behavior
*/
PostHog.prototype.startExceptionAutocapture = function (config) {
this.set_config({ capture_exceptions: config !== null && config !== void 0 ? config : true });
};
/**
* turns exception autocapture off by updating the config option `capture_exceptions` to `false`
*
* {@label Error tracking}
*
* @public
*
* @example
* ```js
* // Stop capturing exceptions automatically
* posthog.stopExceptionAutocapture()
* ```
*/
PostHog.prototype.stopExceptionAutocapture = function () {
this.set_config({ capture_exceptions: false });
};
/**
* returns a boolean indicating whether the [toolbar](/docs/toolbar) loaded
*
* {@label Toolbar}
*
* @public
*
* @param {ToolbarParams} params Toolbar parameters.
* @returns Whether the toolbar loaded.
*/
PostHog.prototype.loadToolbar = function (params) {
var _a, _b;
return (_b = (_a = this.toolbar) === null || _a === void 0 ? void 0 : _a.loadToolbar(params)) !== null && _b !== void 0 ? _b : false;
};
/**
* Returns the value of a super property. Returns undefined if the property doesn't exist.
*
* {@label Identification}
*
* @remarks
* get_property() can only be called after the PostHog library has finished loading.
* init() has a loaded function available to handle this automatically.
*
* @example
* ```js
* // grab value for '$user_id' after the posthog library has loaded
* posthog.init('<YOUR PROJECT TOKEN>', {
* loaded: function(posthog) {
* user_id = posthog.get_property('$user_id');
* }
* });
* ```
* @public
*
* @param {String} property_name The name of the super property you want to retrieve
* @returns The stored property value, or undefined if it is not set.
*/
PostHog.prototype.get_property = function (property_name) {
var _a;
return (_a = this.persistence) === null || _a === void 0 ? void 0 : _a.props[property_name];
};
/**
* Returns the value of the session super property named property_name. If no such
* property is set, getSessionProperty() will return the undefined value.
*
* {@label Identification}
*
* @public
*
* @remarks
* This is based on browser-level `sessionStorage`, NOT the PostHog session.
* getSessionProperty() can only be called after the PostHog library has finished loading.
* init() has a loaded function available to handle this automatically.
*
* @example
* ```js
* // grab value for 'user_id' after the posthog library has loaded
* posthog.init('YOUR PROJECT TOKEN', {
* loaded: function(posthog) {
* user_id = posthog.getSessionProperty('user_id');
* }
* });
* ```
*
* @param {String} property_name The name of the session super property you want to retrieve
* @returns The stored session property value, or undefined if it is not set.
*/
PostHog.prototype.getSessionProperty = function (property_name) {
var _a;
return (_a = this.sessionPersistence) === null || _a === void 0 ? void 0 : _a.props[property_name];
};
/**
* Returns a string representation of the PostHog instance.
*
* {@label Initialization}
*
* @internal
*/
PostHog.prototype.toString = function () {
var _a;
var name = (_a = this.config.name) !== null && _a !== void 0 ? _a : PRIMARY_INSTANCE_NAME;
if (name !== PRIMARY_INSTANCE_NAME) {
name = PRIMARY_INSTANCE_NAME + '.' + name;
}
return name;
};
PostHog.prototype._isIdentified = function () {
var _a, _b;
return (((_a = this.persistence) === null || _a === void 0 ? void 0 : _a.get_property(constants_1.USER_STATE)) === constants_1.USER_STATE_IDENTIFIED ||
((_b = this.sessionPersistence) === null || _b === void 0 ? void 0 : _b.get_property(constants_1.USER_STATE)) === constants_1.USER_STATE_IDENTIFIED);
};
PostHog.prototype._hasPersonProcessing = function () {
var _a, _b, _c, _d;
return !(this.config.person_profiles === 'never' ||
(this.config.person_profiles === constants_1.PERSON_PROFILES_IDENTIFIED_ONLY &&
!this._isIdentified() &&
(0, core_1.isEmptyObject)(this.getGroups()) &&
!((_b = (_a = this.persistence) === null || _a === void 0 ? void 0 : _a.props) === null || _b === void 0 ? void 0 : _b[constants_1.ALIAS_ID_KEY]) &&
!((_d = (_c = this.persistence) === null || _c === void 0 ? void 0 : _c.props) === null || _d === void 0 ? void 0 : _d[constants_1.ENABLE_PERSON_PROCESSING])));
};
PostHog.prototype._shouldCapturePageleave = function () {
return (this.config.capture_pageleave === true ||
(this.config.capture_pageleave === 'if_capture_pageview' && !!this.config.capture_pageview));
};
/**
* Creates a person profile for the current user, if they don't already have one and config.person_profiles is set
* to 'identified_only'. Produces a warning and does not create a profile if config.person_profiles is set to
* 'never'. Learn more about [person profiles](/docs/product-analytics/identify)
*
* {@label Identification}
*
* @public
*
* @example
* ```js
* posthog.createPersonProfile()
* ```
*/
PostHog.prototype.createPersonProfile = function () {
if (this._hasPersonProcessing()) {
// if a person profile already exists, don't send an event when we don't need to
return;
}
if (!this._requirePersonProcessing('posthog.createPersonProfile')) {
return;
}
// sent a $set event. We don't set any properties here, but attribution props will be added later
this.setPersonProperties({}, {});
};
/**
* Marks the current user as a test user by setting the `$internal_or_test_user` person property to `true`.
* This also enables person processing for the current user.
*
* This is useful for using in a cohort your internal/test filters for your posthog org.
* @see https://posthog.com/tutorials/filter-internal-users
* Create a cohort with `$internal_or_test_user` IS SET, and set your internal test filters to be NOT IN that cohort.
*
* {@label Identification}
*
* @example
* ```js
* // Manually mark as test user
* posthog.setInternalOrTestUser()
*
* // Or use internal_or_test_user_hostname config for automatic detection
* posthog.init('token', { internal_or_test_user_hostname: 'localhost' })
* ```
*
* @public
*/
PostHog.prototype.setInternalOrTestUser = function () {
if (!this._requirePersonProcessing('posthog.setInternalOrTestUser')) {
return;
}
this.setPersonProperties({ $internal_or_test_user: true });
};
/**
* Enables person processing if possible, returns true if it does so or already enabled, false otherwise
*
* @param function_name
*/
PostHog.prototype._requirePersonProcessing = function (function_name) {
if (this.config.person_profiles === 'never') {
logger_1.logger.error(function_name + ' was called, but process_person is set to "never". This call will be ignored.');
return false;
}
this._warnIfVolatileIdentityWithoutStableId();
this._register_single(constants_1.ENABLE_PERSON_PROCESSING, true);
return true;
};
PostHog.prototype._is_persistence_disabled = function () {
if (this.config.cookieless_mode === 'always') {
return true;
}
var isOptedOut = this.consent.isOptedOut();
var defaultPersistenceDisabled = this.config.opt_out_persistence_by_default || this.config.cookieless_mode === constants_1.COOKIELESS_ON_REJECT;
// TRICKY: We want a deterministic state for persistence so that a new pageload has the same persistence
return this.config.disable_persistence || (isOptedOut && !!defaultPersistenceDisabled);
};
PostHog.prototype._sync_opt_out_with_persistence = function () {
var _a, _b, _c, _d, _e;
var persistenceDisabled = this._is_persistence_disabled();
// Release what console capture is holding as soon as capturing is off, rather
// than at the next console write.
if (!this.is_capturing()) {
(_a = this.logs) === null || _a === void 0 ? void 0 : _a._onOptOut();
}
if (((_b = this.persistence) === null || _b === void 0 ? void 0 : _b._disabled) !== persistenceDisabled) {
(_c = this.persistence) === null || _c === void 0 ? void 0 : _c.set_disabled(persistenceDisabled);
}
if (((_d = this.sessionPersistence) === null || _d === void 0 ? void 0 : _d._disabled) !== persistenceDisabled) {
(_e = this.sessionPersistence) === null || _e === void 0 ? void 0 : _e.set_disabled(persistenceDisabled);
}
if (persistenceDisabled) {
this._sessionRegisteredPropKeys.clear();
this._persistSessionRegisteredPropKeys();
}
return persistenceDisabled;
};
/**
* Opts the user into data capturing and persistence.
*
* {@label Privacy}
*
* @remarks
* Enables event tracking and data persistence (cookies/localStorage) for this PostHog instance.
* By default, captures an `$opt_in` event unless disabled.
*
* @example
* ```js
* // simple opt-in
* posthog.opt_in_capturing()
* ```
*
* @example
* ```js
* // opt-in with custom event and properties
* posthog.opt_in_capturing({
* captureEventName: 'Privacy Accepted',
* captureProperties: { source: 'banner' }
* })
* ```
*
* @example
* ```js
* // opt-in without capturing event
* posthog.opt_in_capturing({
* captureEventName: false
* })
* ```
*
* @public
*
* @param {Object} [options] A dictionary of opt-in options.
* @param {EventName | null | false} [options.captureEventName] Event name to use for capturing the opt-in action. Set to `null` or `false` to skip capturing the opt-in event. Defaults to `$opt_in`.
* @param {Properties} [options.captureProperties] Set of properties to capture along with the opt-in action.
*/
PostHog.prototype.opt_in_capturing = function (options) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
if (this.config.cookieless_mode === constants_1.COOKIELESS_ALWAYS) {
logger_1.logger.warn(CONSENT_COOKIELESS_WARN);
return;
}
if (this._inCookielessMode()) {
// Identity changes must not let queued recorder work flush under the replacement identity.
(_a = this.sessionRecording) === null || _a === void 0 ? void 0 : _a.dispose({ discardBufferedEvents: true });
// If the user was being treated as rejected in on_reject mode (either explicitly opted out, or opted out by default via opt_out_capturing_by_default), then before we can start sending regular non-cookieless events
// we need to reset the instance to ensure that there is no leaking of state or data between the cookieless and regular events
this._reset(true, true);
(_b = this.sessionManager) === null || _b === void 0 ? void 0 : _b.destroy();
(_c = this.pageViewManager) === null || _c === void 0 ? void 0 : _c.destroy();
this.sessionManager = new sessionid_1.SessionIdManager(this);
this.pageViewManager = new page_view_1.PageViewManager(this);
if (this.persistence) {
this.sessionPropsManager = new session_props_1.SessionPropsManager(this, this.sessionManager, this.persistence);
}
var SessionRecordingClass = (_e = (_d = this.config.__extensionClasses) === null || _d === void 0 ? void 0 : _d.sessionRecording) !== null && _e !== void 0 ? _e : (_f = PostHog.__defaultExtensionClasses) === null || _f === void 0 ? void 0 : _f.sessionRecording;
if (SessionRecordingClass) {
this.sessionRecording = this._replaceExtension(this.sessionRecording, new SessionRecordingClass(this));
// Replay the cached remote config result so the new recorder picks up
// server-side settings (enable flag, endpoint, sampling) that arrived while
// we were still in cookieless mode and sessionRecording didn't yet exist.
if (this._lastRemoteConfig) {
(_h = (_g = this.sessionRecording) === null || _g === void 0 ? void 0 : _g.onRemoteConfig) === null || _h === void 0 ? void 0 : _h.call(_g, this._lastRemoteConfig);
}
}
}
this.consent.optInOut(true);
this._sync_opt_out_with_persistence();
// Start queue after opting in
this._start_queue_if_opted_in();
// Restart session recording if it should now be enabled
// (this handles the case where opt_out_capturing_by_default or cookieless_mode prevented it from starting)
(_j = this.sessionRecording) === null || _j === void 0 ? void 0 : _j.startIfEnabledOrStop();
// Reinitialize surveys if we're in cookieless mode and just opted in
if (this.config.cookieless_mode == constants_1.COOKIELESS_ON_REJECT) {
(_k = this.surveys) === null || _k === void 0 ? void 0 : _k.loadIfEnabled();
}
// Don't capture if captureEventName is null or false
if ((0, core_1.isUndefined)(options === null || options === void 0 ? void 0 : options.captureEventName) || (options === null || options === void 0 ? void 0 : options.captureEventName)) {
this.capture((_l = options === null || options === void 0 ? void 0 : options.captureEventName) !== null && _l !== void 0 ? _l : '$opt_in', options === null || options === void 0 ? void 0 : options.captureProperties, { send_instantly: true });
}
if (this.config.capture_pageview) {
this._captureInitialPageview();
}
};
/**
* Opts the user out of data capturing and persistence.
*
* {@label Privacy}
*
* @remarks
* Disables event tracking and data persistence (cookies/localStorage) for this PostHog instance.
* If `opt_out_persistence_by_default` is true, SDK persistence will also be disabled.
*
* @example
* ```js
* // opt user out (e.g., on privacy settings page)
* posthog.opt_out_capturing()
* ```
*
* @public
*/
PostHog.prototype.opt_out_capturing = function () {
var _a, _b;
if (this.config.cookieless_mode === constants_1.COOKIELESS_ALWAYS) {
logger_1.logger.warn(CONSENT_COOKIELESS_WARN);
return;
}
var sessionRecording = this.config.cookieless_mode === constants_1.COOKIELESS_ON_REJECT ? this.sessionRecording : undefined;
// Identity changes must not let queued recorder work flush under the cookieless identity.
sessionRecording === null || sessionRecording === void 0 ? void 0 : sessionRecording.dispose({ discardBufferedEvents: true });
if (this.config.cookieless_mode === constants_1.COOKIELESS_ON_REJECT && this.consent.isOptedIn()) {
// If the user has opted in, we need to reset the instance to ensure that there is no leaking of state or data between the cookieless and regular events
this._reset(true, true);
}
this.consent.optInOut(false);
this._sync_opt_out_with_persistence();
if (this.config.cookieless_mode === constants_1.COOKIELESS_ON_REJECT) {
// If cookieless_mode is COOKIELESS_ON_REJECT, we start capturing events in cookieless mode
this.register({
distinct_id: constants_1.COOKIELESS_SENTINEL_VALUE,
$device_id: null,
});
// Detach the recorder before sessionManager goes away so pending work cannot outlive it.
this._removeExtension(sessionRecording);
this.sessionRecording = undefined;
(_a = this.sessionManager) === null || _a === void 0 ? void 0 : _a.destroy();
(_b = this.pageViewManager) === null || _b === void 0 ? void 0 : _b.destroy();
this.sessionManager = undefined;
this.sessionPropsManager = undefined;
if (this.config.capture_pageview) {
this._captureInitialPageview();
}
// At init time, consent was PENDING so is_capturing() was false and _start_queue_if_opted_in() was a no-op.
// Now that rejection has been recorded, capturing is active — enable the queue so batched events are flushed.
this._start_queue_if_opted_in();
}
};
/**
* Checks if the user has opted into data capturing.
*
* {@label Privacy}
*
* @remarks
* Returns the current consent status for event tracking and data persistence.
*
* @example
* ```js
* if (posthog.has_opted_in_capturing()) {
* // show analytics features
* }
* ```
*
* @public
*
* @returns {boolean} current opt-in status
*/
PostHog.prototype.has_opted_in_capturing = function () {
return this.consent.isOptedIn();
};
/**
* Checks if the user has opted out of data capturing.
*
* {@label Privacy}
*
* @remarks
* Returns the current consent status for event tracking and data persistence.
*
* @example
* ```js
* if (posthog.has_opted_out_capturing()) {
* // disable analytics features
* }
* ```
*
* @public
*
* @returns {boolean} current opt-out status
*/
PostHog.prototype.has_opted_out_capturing = function () {
return this.consent.isOptedOut();
};
/**
* Returns the explicit consent status of the user.
*
* @remarks
* This can be used to check if the user has explicitly opted in or out of data capturing, or neither. This does not
* take the default config options into account, only whether the user has made an explicit choice, so this can be
* used to determine whether to show an initial cookie banner or not.
*
* @example
* ```js
* const consentStatus = posthog.get_explicit_consent_status()
* if (consentStatus === "granted") {
* // user has explicitly opted in
* } else if (consentStatus === "denied") {
* // user has explicitly opted out
* } else if (consentStatus === "pending"){
* // user has not made a choice, show consent banner
* }
* ```
*
* @public
*
* @returns The current explicit consent status.
*/
PostHog.prototype.get_explicit_consent_status = function () {
var consent = this.consent.consent;
return consent === consent_1.ConsentStatus.GRANTED ? 'granted' : consent === consent_1.ConsentStatus.DENIED ? 'denied' : 'pending';
};
/**
* Checks whether the PostHog library is currently capturing events.
*
* Usually this means that the user has not opted out of capturing, but the exact behaviour can be controlled by
* some config options.
*
* Additionally, if the cookieless_mode is set to `'on_reject'`, we will capture events in cookieless mode if the
* user has opted out or been defaulted to opt-out.
*
* {@label Privacy}
*
* @see {PostHogConfig.cookieless_mode}
* @see {PostHogConfig.opt_out_persistence_by_default}
* @see {PostHogConfig.respect_dnt}
*
* @returns {boolean} whether the posthog library is capturing events
*/
PostHog.prototype.is_capturing = function () {
if (this.config.cookieless_mode === constants_1.COOKIELESS_ALWAYS) {
return true;
}
if (this.config.cookieless_mode === constants_1.COOKIELESS_ON_REJECT) {
return this.consent.isRejected() || this.consent.isOptedIn();
}
else {
return !this.has_opted_out_capturing();
}
};
/**
* Clear the user's opt in/out status of data capturing and cookies/localstorage for this PostHog instance
*
* {@label Privacy}
*
* @public
*
*/
PostHog.prototype.clear_opt_in_out_capturing = function () {
this.consent.reset();
this._sync_opt_out_with_persistence();
};
PostHog.prototype._is_bot = function () {
if (globals_1.navigator) {
return (0, blocked_uas_1.isLikelyBot)(globals_1.navigator, this.config.custom_blocked_useragents);
}
else {
return undefined;
}
};
PostHog.prototype._captureInitialPageview = function () {
if (!globals_1.document) {
return;
}
// If page is not visible, add a listener to detect when the page becomes visible
// and trigger the pageview only then
// This is useful to avoid `prerender` calls from Chrome/Wordpress/SPAs
// that are not visible to the user
if (globals_1.document.visibilityState !== 'visible') {
if (!this._visibilityStateListener) {
this._visibilityStateListener = this._captureInitialPageview.bind(this);
(0, general_utils_1.addEventListener)(globals_1.document, constants_1.DOM_EVENT_VISIBILITYCHANGE, this._visibilityStateListener);
}
return;
}
// Extra check here to guarantee we only ever trigger a single `$pageview` event
if (!this._initialPageviewCaptured) {
this._initialPageviewCaptured = true;
this.capture(constants_1.EVENT_PAGEVIEW, { title: globals_1.document.title }, { send_instantly: true });
// After we've captured the initial pageview, we can remove the listener
if (this._visibilityStateListener) {
globals_1.document.removeEventListener(constants_1.DOM_EVENT_VISIBILITYCHANGE, this._visibilityStateListener);
this._visibilityStateListener = null;
}
}
};
/**
* Enables or disables debug mode for detailed logging.
*
* @remarks
* Debug mode logs all PostHog calls to the browser console for troubleshooting.
* Can also be enabled by adding `?__posthog_debug=true` to the URL.
*
* {@label Initialization}
*
* @example
* ```js
* // enable debug mode
* posthog.debug(true)
* ```
*
* @example
* ```js
* // disable debug mode
* posthog.debug(false)
* ```
*
* @public
*
* @param {boolean} [debug] If true, will enable debug mode.
*/
PostHog.prototype.debug = function (debug) {
if (debug === false) {
globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.console.log("You've disabled debug mode.");
this.set_config({ debug: false });
}
else {
globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.console.log("You're now in debug mode. All calls to PostHog will be logged in your console.\nYou can disable this with `posthog.debug(false)`.");
this.set_config({ debug: true });
}
};
/**
* Helper method to check if external API calls (flags/decide) should be disabled
* Handles migration from old `advanced_disable_decide` to new `advanced_disable_flags`
*/
PostHog.prototype._shouldDisableFlags = function () {
// Check if advanced_disable_flags was explicitly set in original config
var originalConfig = this._originalUserConfig || {};
if ('advanced_disable_flags' in originalConfig) {
return !!originalConfig.advanced_disable_flags;
}
// Check if advanced_disable_flags was set post-init (different from default false)
if (this.config.advanced_disable_flags !== false) {
return !!this.config.advanced_disable_flags;
}
// Check for post-init changes to advanced_disable_decide
if (this.config.advanced_disable_decide === true) {
logger_1.logger.warn("Config field 'advanced_disable_decide' is deprecated. Please use 'advanced_disable_flags' instead. " +
'The old field will be removed in a future major version.');
return true;
}
// Fall back to migration logic for original user config
return (0, general_utils_1.migrateConfigField)(originalConfig, 'advanced_disable_flags', 'advanced_disable_decide', false, logger_1.logger);
};
PostHog.prototype._runBeforeSend = function (data) {
var e_3, _a, e_4, _b;
var _c;
if ((0, core_1.isNullish)(this.config.before_send)) {
return data;
}
// Some properties are required for the event to be ingested - e.g. `token`
// carries the project api_key, and ingest rejects any event that arrives
// without it (a 401). Snapshot which of these were present before the hooks
// run, so we can tell if a hook removed one (the names are captured here, so
// a hook that mutates `data.properties` in place can't hide the removal).
var requiredProperties = Object.keys((_c = data.properties) !== null && _c !== void 0 ? _c : {}).filter(core_1.isKnownUnsafeEditableEventProperty);
var fns = (0, core_1.isArray)(this.config.before_send) ? this.config.before_send : [this.config.before_send];
var beforeSendResult = data;
try {
for (var fns_1 = __values(fns), fns_1_1 = fns_1.next(); !fns_1_1.done; fns_1_1 = fns_1.next()) {
var fn = fns_1_1.value;
try {
beforeSendResult = fn(beforeSendResult);
if ((0, core_1.isNullish)(beforeSendResult)) {
var logMessage = "Event '".concat(data.event, "' was rejected in beforeSend function");
if ((0, core_1.isKnownUnsafeEditableEvent)(data.event)) {
logger_1.logger.warn("".concat(logMessage, ". This can cause unexpected behavior."));
}
else {
logger_1.logger.info(logMessage);
}
return null;
}
if (!beforeSendResult.properties || (0, core_1.isEmptyObject)(beforeSendResult.properties)) {
logger_1.logger.warn("Event '".concat(data.event, "' has no properties after beforeSend function, this is likely an error."));
}
}
catch (e) {
logger_1.logger.error("Error in beforeSend function for event '".concat(data.event, "':"), e);
return null;
}
}
}
catch (e_3_1) { e_3 = { error: e_3_1 }; }
finally {
try {
if (fns_1_1 && !fns_1_1.done && (_a = fns_1.return)) _a.call(fns_1);
}
finally { if (e_3) throw e_3.error; }
}
try {
// If a beforeSend hook removed a property the event needs to be ingested
// (e.g. a generic token/PII scrubber matching /token/i dropping `token`),
// ingest would silently reject every such event with a 401. Drop the event
// here and warn instead - we don't restore the property, since the removal
// may have been intentional.
for (var requiredProperties_1 = __values(requiredProperties), requiredProperties_1_1 = requiredProperties_1.next(); !requiredProperties_1_1.done; requiredProperties_1_1 = requiredProperties_1.next()) {
var property = requiredProperties_1_1.value;
if (beforeSendResult.properties && (0, core_1.isNullish)(beforeSendResult.properties[property])) {
logger_1.logger.warn("Event '".concat(data.event, "' had its '").concat(property, "' property removed in a beforeSend function. This property is required for ingestion, so the event will be dropped."));
return null;
}
}
}
catch (e_4_1) { e_4 = { error: e_4_1 }; }
finally {
try {
if (requiredProperties_1_1 && !requiredProperties_1_1.done && (_b = requiredProperties_1.return)) _b.call(requiredProperties_1);
}
finally { if (e_4) throw e_4.error; }
}
return beforeSendResult;
};
/**
* Returns the current page view ID.
*
* {@label Initialization}
*
* @public
*
* @returns The current page view ID, or undefined if no page view has been captured.
*/
PostHog.prototype.getPageViewId = function () {
var _a;
return (_a = this.pageViewManager._currentPageview) === null || _a === void 0 ? void 0 : _a.pageViewId;
};
/**
* Capture written user feedback for a LLM trace. Numeric values are converted to strings.
*
* {@label LLM analytics}
*
* @public
*
* @param traceId The trace ID to capture feedback for.
* @param userFeedback The feedback to capture.
*/
PostHog.prototype.captureTraceFeedback = function (traceId, userFeedback) {
this.capture('$ai_feedback', {
$ai_trace_id: String(traceId),
$ai_feedback_text: userFeedback,
});
};
/**
* Capture a metric for a LLM trace. Numeric values are converted to strings.
*
* {@label LLM analytics}
*
* @public
*
* @param traceId The trace ID to capture the metric for.
* @param metricName The name of the metric to capture.
* @param metricValue The value of the metric to capture.
*/
PostHog.prototype.captureTraceMetric = function (traceId, metricName, metricValue) {
this.capture('$ai_metric', {
$ai_trace_id: String(traceId),
$ai_metric_name: metricName,
$ai_metric_value: String(metricValue),
});
};
PostHog.prototype._checkLocalStorageForDebug = function (debugConfig) {
var explicitlyFalse = (0, core_1.isBoolean)(debugConfig) && !debugConfig;
var isTrueInLocalStorage = storage_1.localStore._is_supported() && storage_1.localStore._get('ph_debug') === 'true';
return explicitlyFalse ? false : isTrueInLocalStorage ? true : debugConfig;
};
PostHog.__defaultExtensionClasses = {};
PostHog._noopLogger = (function () {
var noop = function () { };
return { trace: noop, debug: noop, info: noop, warn: noop, error: noop, fatal: noop };
})();
return PostHog;
}());
exports.PostHog = PostHog;
(0, general_utils_1.safewrapClass)(PostHog, ['identify']);
var add_dom_loaded_handler = function () {
// Cross browser DOM Loaded support
function dom_loaded_handler() {
// function flag since we only want to execute this once
if (dom_loaded_handler.done) {
return;
}
;
dom_loaded_handler.done = true;
ENQUEUE_REQUESTS = false;
(0, general_utils_1.each)(instances, function (inst) {
inst._dom_loaded();
});
}
if (globals_1.document === null || globals_1.document === void 0 ? void 0 : globals_1.document.addEventListener) {
if (globals_1.document.readyState === 'complete') {
// safari 4 can fire the DOMContentLoaded event before loading all
// external JS (including this file). you will see some copypasta
// on the internet that checks for 'complete' and 'loaded', but
// 'loaded' is an IE thing
dom_loaded_handler();
}
else {
(0, general_utils_1.addEventListener)(globals_1.document, 'DOMContentLoaded', dom_loaded_handler, { capture: false });
}
return;
}
// Only IE6-8 don't support `document.addEventListener` and we don't support them
// so let's simply log an error stating PostHog couldn't be initialized
// We're checking for `window` to avoid erroring out on a SSR context
if (globals_1.window) {
logger_1.logger.error("Browser doesn't support `document.addEventListener` so PostHog couldn't be initialized");
}
};
function init_from_snippet() {
config_1.default.SDK_DIST_CHANNEL = 'cdn';
var snippetPostHog = globals_2.assignableWindow['posthog'];
// The snippet stub always has an _i initialization queue, while a materialized SDK instance does not.
// Multiple snippet init() calls can insert array.js more than once, so do not let a later execution replace
// the live global instance (including an unloaded primary with loaded named instances).
if (snippetPostHog && !(0, core_1.isArray)(snippetPostHog['_i'])) {
return;
}
var posthogMain = (instances[PRIMARY_INSTANCE_NAME] = new PostHog());
if (snippetPostHog) {
/**
* The snippet uses some clever tricks to allow deferred loading of array.js (this code)
*
* window.posthog is an array which the queue of calls made before the lib is loaded
* It has methods attached to it to simulate the posthog object so for instance
*
* window.posthog.init("TOKEN", {api_host: "foo" })
* window.posthog.capture("my-event", {foo: "bar" })
*
* ... will mean that window.posthog will look like this:
* window.posthog == [
* ["my-event", {foo: "bar"}]
* ]
*
* window.posthog[_i] == [
* ["TOKEN", {api_host: "foo" }, "posthog"]
* ]
*
* If a name is given to the init function then the same as above is true but as a sub-property on the object:
*
* window.posthog.init("TOKEN", {}, "ph2")
* window.posthog.ph2.people.set({foo: "bar"})
*
* window.posthog.ph2 == []
* window.posthog.people == [
* ["set", {foo: "bar"}]
* ]
*
*/
// Call all pre-loaded init calls properly
var processedSnippetQueues_1 = [];
(0, general_utils_1.each)(snippetPostHog['_i'], function (item) {
if (item && (0, core_1.isArray)(item)) {
var instance = posthogMain.init(item[0], item[1], item[2]);
var instanceSnippet = snippetPostHog[item[2]] || snippetPostHog;
if (instance.__loaded && processedSnippetQueues_1.indexOf(instanceSnippet) === -1) {
processedSnippetQueues_1.push(instanceSnippet);
// Crunch through the people queue first - we queue this data up &
// flush on identify, so it's better to do all these operations first
instance._execute_array.call(instance.people, instanceSnippet.people);
instance._execute_array(instanceSnippet);
}
}
});
}
// Keep the snippet sentinel so another pasted snippet cannot replace live SDK methods with queue stubs.
;
posthogMain.__SV = 1;
globals_2.assignableWindow['posthog'] = posthogMain;
add_dom_loaded_handler();
}
function init_as_module() {
config_1.default.SDK_DIST_CHANNEL = 'npm';
var posthogMain = (instances[PRIMARY_INSTANCE_NAME] = new PostHog());
add_dom_loaded_handler();
return posthogMain;
}
//# sourceMappingURL=posthog-core.js.map