UNPKG

posthog-js

Version:

Posthog-js allows you to automatically capture usage and send events to PostHog.

734 lines • 32.6 kB
"use strict"; var __values = (this && this.__values) || function(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TriggerGroupMatching = exports.EventTriggerMatching = exports.LinkedFlagMatching = exports.URLTriggerMatching = exports.AlwaysActivatedTriggerMatching = exports.PendingTriggerMatching = exports.AndTriggerMatching = exports.OrTriggerMatching = exports.TRIGGER_DISABLED = exports.TRIGGER_PENDING = exports.TRIGGER_ACTIVATED = exports.RRWEB_ERROR = exports.MISSING_CONFIG = exports.AWAITING_CONFIG = exports.LAZY_LOADING = exports.PAUSED = exports.BUFFERING = exports.ACTIVE = exports.SAMPLED = exports.DISABLED = void 0; exports.nullMatchSessionRecordingStatus = nullMatchSessionRecordingStatus; exports.anyMatchSessionRecordingStatus = anyMatchSessionRecordingStatus; exports.allMatchSessionRecordingStatus = allMatchSessionRecordingStatus; exports.triggerGroupsMatchSessionRecordingStatus = triggerGroupsMatchSessionRecordingStatus; var constants_1 = require("../../../constants"); var core_1 = require("@posthog/core"); var logger_1 = require("@posthog/browser-common/utils/logger"); var url_targeting_utils_1 = require("@posthog/browser-common/utils/url-targeting-utils"); exports.DISABLED = 'disabled'; exports.SAMPLED = 'sampled'; exports.ACTIVE = 'active'; exports.BUFFERING = 'buffering'; exports.PAUSED = 'paused'; exports.LAZY_LOADING = 'lazy_loading'; exports.AWAITING_CONFIG = 'awaiting_config'; exports.MISSING_CONFIG = 'missing_config'; exports.RRWEB_ERROR = 'rrweb_error'; var TRIGGER = 'trigger'; exports.TRIGGER_ACTIVATED = TRIGGER + '_activated'; exports.TRIGGER_PENDING = TRIGGER + '_pending'; exports.TRIGGER_DISABLED = TRIGGER + '_' + exports.DISABLED; /* triggers can have one of three statuses: * - trigger_activated: the trigger met conditions to start recording * - trigger_pending: the trigger is present, but the conditions are not yet met * - trigger_disabled: the trigger is not present */ // eslint-disable-next-line @typescript-eslint/no-unused-vars var triggerStatuses = [exports.TRIGGER_ACTIVATED, exports.TRIGGER_PENDING, exports.TRIGGER_DISABLED]; function persistedTriggerStatus(instance, triggerCount, groupId, groupPrefix, fallbackPersistenceKey, sessionId) { if (triggerCount === 0) { return exports.TRIGGER_DISABLED; } // V2: Use per-group persistence key if groupId is provided var persistenceKey = groupId ? groupPrefix + groupId : fallbackPersistenceKey; var currentTriggerSession = instance === null || instance === void 0 ? void 0 : instance.get_property(persistenceKey); return currentTriggerSession === sessionId ? exports.TRIGGER_ACTIVATED : exports.TRIGGER_PENDING; } /** * Session recording starts in buffering mode while waiting for "flags response". * Once the response is received, it might be disabled, active or sampled. * When "sampled" that means a sample rate is set, and the last time the session ID rotated * the sample rate determined this session should be sent to the server. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars var sessionRecordingStatuses = [ exports.DISABLED, exports.SAMPLED, exports.ACTIVE, exports.BUFFERING, exports.PAUSED, exports.LAZY_LOADING, exports.AWAITING_CONFIG, exports.MISSING_CONFIG, exports.RRWEB_ERROR, ]; function sessionRecordingUrlTriggerMatches(url, triggers, compiledRegexCache) { return triggers.some(function (trigger) { var _a; switch (trigger.matching) { case 'regex': { var regex = (_a = compiledRegexCache === null || compiledRegexCache === void 0 ? void 0 : compiledRegexCache.get(trigger.url)) !== null && _a !== void 0 ? _a : new RegExp(trigger.url); return regex.test(url); } default: return false; } }); } function orTriggerStatus(statuses) { if (statuses.includes(exports.TRIGGER_ACTIVATED)) { return exports.TRIGGER_ACTIVATED; } if (statuses.includes(exports.TRIGGER_PENDING)) { return exports.TRIGGER_PENDING; } return exports.TRIGGER_DISABLED; } function andTriggerStatus(statuses) { var enabledStatuses = new Set(statuses); // trigger_disabled means no config enabledStatuses.delete(exports.TRIGGER_DISABLED); switch (enabledStatuses.size) { case 0: return exports.TRIGGER_DISABLED; case 1: return Array.from(enabledStatuses)[0]; default: return exports.TRIGGER_PENDING; } } var OrTriggerMatching = /** @class */ (function () { function OrTriggerMatching(_matchers) { this._matchers = _matchers; } OrTriggerMatching.prototype.triggerStatus = function (sessionId) { return orTriggerStatus(this._matchers.map(function (matcher) { return matcher.triggerStatus(sessionId); })); }; OrTriggerMatching.prototype.triggerStatusNoSideEffects = function (sessionId) { return orTriggerStatus(this._matchers.map(function (matcher) { return matcher.triggerStatusNoSideEffects(sessionId); })); }; OrTriggerMatching.prototype.stop = function () { this._matchers.forEach(function (m) { return m.stop(); }); }; return OrTriggerMatching; }()); exports.OrTriggerMatching = OrTriggerMatching; var AndTriggerMatching = /** @class */ (function () { function AndTriggerMatching(_matchers) { this._matchers = _matchers; } AndTriggerMatching.prototype.triggerStatus = function (sessionId) { return andTriggerStatus(this._matchers.map(function (matcher) { return matcher.triggerStatus(sessionId); })); }; AndTriggerMatching.prototype.triggerStatusNoSideEffects = function (sessionId) { return andTriggerStatus(this._matchers.map(function (matcher) { return matcher.triggerStatusNoSideEffects(sessionId); })); }; AndTriggerMatching.prototype.stop = function () { this._matchers.forEach(function (m) { return m.stop(); }); }; return AndTriggerMatching; }()); exports.AndTriggerMatching = AndTriggerMatching; var PendingTriggerMatching = /** @class */ (function () { function PendingTriggerMatching() { } PendingTriggerMatching.prototype.triggerStatus = function () { return this.triggerStatusNoSideEffects(); }; PendingTriggerMatching.prototype.triggerStatusNoSideEffects = function () { return exports.TRIGGER_PENDING; }; PendingTriggerMatching.prototype.stop = function () { // no-op }; return PendingTriggerMatching; }()); exports.PendingTriggerMatching = PendingTriggerMatching; var AlwaysActivatedTriggerMatching = /** @class */ (function () { function AlwaysActivatedTriggerMatching() { } AlwaysActivatedTriggerMatching.prototype.triggerStatus = function () { return this.triggerStatusNoSideEffects(); }; AlwaysActivatedTriggerMatching.prototype.triggerStatusNoSideEffects = function () { return exports.TRIGGER_ACTIVATED; }; AlwaysActivatedTriggerMatching.prototype.stop = function () { // no-op }; return AlwaysActivatedTriggerMatching; }()); exports.AlwaysActivatedTriggerMatching = AlwaysActivatedTriggerMatching; var isEagerLoadedConfig = function (x) { return 'sessionRecording' in x; }; var URLTriggerMatching = /** @class */ (function () { function URLTriggerMatching(_instance, groupId) { this._instance = _instance; this._urlTriggers = []; this._urlBlocklist = []; this._compiledTriggerRegexes = new Map(); this._compiledBlocklistRegexes = new Map(); this._lastCheckedUrl = ''; this.urlBlocked = false; this._groupId = groupId; } URLTriggerMatching.prototype.onConfig = function (config) { var _a, _b; this._urlTriggers = (isEagerLoadedConfig(config) ? (0, core_1.isObject)(config.sessionRecording) ? (_a = config.sessionRecording) === null || _a === void 0 ? void 0 : _a.urlTriggers : [] : config === null || config === void 0 ? void 0 : config.urlTriggers) || []; this._urlBlocklist = (isEagerLoadedConfig(config) ? (0, core_1.isObject)(config.sessionRecording) ? (_b = config.sessionRecording) === null || _b === void 0 ? void 0 : _b.urlBlocklist : [] : config === null || config === void 0 ? void 0 : config.urlBlocklist) || []; this._compileRegexCache(); }; /** * Compiles and caches RegExp objects from URL triggers and blocklist. * This prevents recreating RegExp objects on every rrweb event */ URLTriggerMatching.prototype._compileRegexCache = function () { var e_1, _a, e_2, _b; this._compiledTriggerRegexes.clear(); this._compiledBlocklistRegexes.clear(); try { for (var _c = __values(this._urlTriggers), _d = _c.next(); !_d.done; _d = _c.next()) { var trigger = _d.value; if (trigger.matching === 'regex' && !this._compiledTriggerRegexes.has(trigger.url)) { try { this._compiledTriggerRegexes.set(trigger.url, new RegExp(trigger.url)); } catch (e) { logger_1.logger.error('Invalid URL trigger regex pattern:', trigger.url, e); } } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (_d && !_d.done && (_a = _c.return)) _a.call(_c); } finally { if (e_1) throw e_1.error; } } try { for (var _e = __values(this._urlBlocklist), _f = _e.next(); !_f.done; _f = _e.next()) { var trigger = _f.value; if (trigger.matching === 'regex' && !this._compiledBlocklistRegexes.has(trigger.url)) { try { this._compiledBlocklistRegexes.set(trigger.url, new RegExp(trigger.url)); } catch (e) { logger_1.logger.error('Invalid URL blocklist regex pattern:', trigger.url, e); } } } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (_f && !_f.done && (_b = _e.return)) _b.call(_e); } finally { if (e_2) throw e_2.error; } } }; /** * @deprecated Use onConfig instead */ URLTriggerMatching.prototype.onRemoteConfig = function (response) { this.onConfig(response); }; URLTriggerMatching.prototype._urlTriggerStatus = function (sessionId) { return persistedTriggerStatus(this._instance, this._urlTriggers.length, this._groupId, constants_1.SESSION_RECORDING_TRIGGER_V2_GROUP_URL_PREFIX, constants_1.SESSION_RECORDING_URL_TRIGGER_ACTIVATED_SESSION, sessionId); }; URLTriggerMatching.prototype.triggerStatus = function (sessionId) { var _a; var result = this.triggerStatusNoSideEffects(sessionId); this._instance.register_for_session((_a = {}, _a[constants_1.SDK_DEBUG_REPLAY_URL_TRIGGER_STATUS] = result, _a)); return result; }; /** * Pure read of the trigger status. Unlike {@link triggerStatus} it does not write the debug * session property, so a diagnostic that polls every leg (e.g. while buffering) can read it * without adding a `register_for_session` persistence write per call. */ URLTriggerMatching.prototype.triggerStatusNoSideEffects = function (sessionId) { return this._urlTriggerStatus(sessionId); }; URLTriggerMatching.prototype.isCurrentUrlBlocked = function () { var url = (0, url_targeting_utils_1.getTargetingUrl)(this._instance); if (!url) { return false; } try { return sessionRecordingUrlTriggerMatches(url, this._urlBlocklist, this._compiledBlocklistRegexes); } catch (_a) { return true; } }; /** * Check URL blocklist and pause/resume recording accordingly * This is separate from trigger checking and is used by both V1 and V2 * * Performance optimization: Only checks when URL changes to avoid redundant regex matching */ URLTriggerMatching.prototype.checkUrlBlocklist = function (onPause, onResume) { var url = (0, url_targeting_utils_1.getTargetingUrl)(this._instance); if (!url) { return; } // Performance optimization: Skip if URL hasn't changed since last check if (url === this._lastCheckedUrl) { return; } this._lastCheckedUrl = url; // Check blocklist and call onPause/onResume // Note: DON'T set this.urlBlocked here - let the callbacks (_pauseRecording/_resumeRecording) set it var wasBlocked = this.urlBlocked; var isNowBlocked = sessionRecordingUrlTriggerMatches(url, this._urlBlocklist, this._compiledBlocklistRegexes); if (wasBlocked && isNowBlocked) { return; } if (isNowBlocked && !wasBlocked) { onPause(); } else if (!isNowBlocked && wasBlocked) { onResume(); } }; URLTriggerMatching.prototype.checkUrlTriggerConditions = function (onPause, onResume, onActivate, sessionId) { var url = (0, url_targeting_utils_1.getTargetingUrl)(this._instance); if (!url) { return; } // Performance optimization: Skip if URL hasn't changed since last check // This prevents redundant checks on every rrweb event if (url === this._lastCheckedUrl) { return; } this._lastCheckedUrl = url; // Check blocklist and call onPause/onResume // Note: DON'T set this.urlBlocked here - let the callbacks (_pauseRecording/_resumeRecording) set it var wasBlocked = this.urlBlocked; var isNowBlocked = sessionRecordingUrlTriggerMatches(url, this._urlBlocklist, this._compiledBlocklistRegexes); if (isNowBlocked && !wasBlocked) { onPause(); } else if (!isNowBlocked && wasBlocked) { onResume(); } // Check URL triggers var isActivated = this._urlTriggerStatus(sessionId) === exports.TRIGGER_ACTIVATED; var urlMatches = sessionRecordingUrlTriggerMatches(url, this._urlTriggers, this._compiledTriggerRegexes); if (!isActivated && urlMatches) { onActivate('url', url); } }; URLTriggerMatching.prototype.stop = function () { this._lastCheckedUrl = ''; }; return URLTriggerMatching; }()); exports.URLTriggerMatching = URLTriggerMatching; var LinkedFlagMatching = /** @class */ (function () { function LinkedFlagMatching(_instance) { this._instance = _instance; this.linkedFlag = null; this.linkedFlagSeen = false; this._flagListenerCleanup = function () { }; } LinkedFlagMatching.prototype.triggerStatus = function () { var _a; var result = this.triggerStatusNoSideEffects(); this._instance.register_for_session((_a = {}, _a[constants_1.SDK_DEBUG_REPLAY_LINKED_FLAG_TRIGGER_STATUS] = result, _a)); return result; }; // Pure read of the trigger status — see URLTriggerMatching.triggerStatusNoSideEffects. LinkedFlagMatching.prototype.triggerStatusNoSideEffects = function () { var result = exports.TRIGGER_PENDING; if ((0, core_1.isNullish)(this.linkedFlag)) { result = exports.TRIGGER_DISABLED; } if (this.linkedFlagSeen) { result = exports.TRIGGER_ACTIVATED; } return result; }; LinkedFlagMatching.prototype.onConfig = function (config, onStarted) { var _this = this; var _a; this.linkedFlag = (isEagerLoadedConfig(config) ? (0, core_1.isObject)(config.sessionRecording) ? (_a = config.sessionRecording) === null || _a === void 0 ? void 0 : _a.linkedFlag : null : config === null || config === void 0 ? void 0 : config.linkedFlag) || null; if (!(0, core_1.isNullish)(this.linkedFlag) && !this.linkedFlagSeen) { var linkedFlag_1 = (0, core_1.isString)(this.linkedFlag) ? this.linkedFlag : this.linkedFlag.flag; var linkedVariant_1 = (0, core_1.isString)(this.linkedFlag) ? null : this.linkedFlag.variant; this._flagListenerCleanup = this._instance.onFeatureFlags(function (_flags, variants) { var flagIsPresent = (0, core_1.isObject)(variants) && linkedFlag_1 in variants; var linkedFlagMatches = false; if (flagIsPresent) { var variantForFlagKey = variants[linkedFlag_1]; if ((0, core_1.isBoolean)(variantForFlagKey)) { linkedFlagMatches = variantForFlagKey === true; } else if (linkedVariant_1) { linkedFlagMatches = variantForFlagKey === linkedVariant_1; } else { // then this is a variant flag and we want to match any string linkedFlagMatches = !!variantForFlagKey; } } // onFeatureFlags fires on every flag reload, so only report activation on the // inactive->active transition. Firing on every reload while the flag stays truthy // re-reports linked_flag_matched and (via onStarted -> _onTriggerActivated) restarts // the full-snapshot interval each time, which can postpone periodic full snapshots // indefinitely when flags reload more often than full_snapshot_interval_millis. var isNewActivation = linkedFlagMatches && !_this.linkedFlagSeen; _this.linkedFlagSeen = linkedFlagMatches; if (isNewActivation) { onStarted(linkedFlag_1, linkedVariant_1); } }); } }; /** * @deprecated Use onConfig instead */ LinkedFlagMatching.prototype.onRemoteConfig = function (response, onStarted) { this.onConfig(response, onStarted); }; LinkedFlagMatching.prototype.stop = function () { this._flagListenerCleanup(); }; return LinkedFlagMatching; }()); exports.LinkedFlagMatching = LinkedFlagMatching; var EventTriggerMatching = /** @class */ (function () { function EventTriggerMatching(_instance, groupId) { this._instance = _instance; this._eventTriggers = []; this._groupId = groupId; } EventTriggerMatching.prototype.onConfig = function (config) { var _a; // Handle both RemoteConfig (nested) and SessionRecordingPersistedConfig (flattened) structures this._eventTriggers = (isEagerLoadedConfig(config) ? (0, core_1.isObject)(config.sessionRecording) ? (_a = config.sessionRecording) === null || _a === void 0 ? void 0 : _a.eventTriggers : [] : config === null || config === void 0 ? void 0 : config.eventTriggers) || []; }; /** * @deprecated Use onConfig instead */ EventTriggerMatching.prototype.onRemoteConfig = function (response) { this.onConfig(response); }; EventTriggerMatching.prototype._eventTriggerStatus = function (sessionId) { var triggerCount = this._eventTriggers.length; return persistedTriggerStatus(this._instance, triggerCount, this._groupId, constants_1.SESSION_RECORDING_TRIGGER_V2_GROUP_EVENT_PREFIX, constants_1.SESSION_RECORDING_EVENT_TRIGGER_ACTIVATED_SESSION, sessionId); }; EventTriggerMatching.prototype.triggerStatus = function (sessionId) { var _a; var result = this.triggerStatusNoSideEffects(sessionId); this._instance.register_for_session((_a = {}, _a[constants_1.SDK_DEBUG_REPLAY_EVENT_TRIGGER_STATUS] = result, _a)); return result; }; // Pure read of the trigger status — see URLTriggerMatching.triggerStatusNoSideEffects. EventTriggerMatching.prototype.triggerStatusNoSideEffects = function (sessionId) { return this._eventTriggerStatus(sessionId); }; EventTriggerMatching.prototype.checkEventTriggerConditions = function (eventName, onActivate, sessionId) { if (this._eventTriggers.length === 0) { return; } var isActivated = this._eventTriggerStatus(sessionId) === exports.TRIGGER_ACTIVATED; var includes = this._eventTriggers.includes(eventName); if (!isActivated && includes) { onActivate('event', eventName); } }; EventTriggerMatching.prototype.stop = function () { // no-op }; return EventTriggerMatching; }()); exports.EventTriggerMatching = EventTriggerMatching; /** * V2 Trigger Group Matching - manages a single trigger group with its own conditions */ var TriggerGroupMatching = /** @class */ (function () { function TriggerGroupMatching(_instance, group, onFlagStarted) { this._instance = _instance; this.group = group; // V2: Pass groupId to child matchers for per-group persistence this._urlTriggerMatching = new URLTriggerMatching(_instance, group.id); this._eventTriggerMatching = new EventTriggerMatching(_instance, group.id); this._linkedFlagMatching = new LinkedFlagMatching(_instance); // Check if all conditions are empty (no events, urls, or flags) var hasEvents = group.conditions.events && group.conditions.events.length > 0; var hasUrls = group.conditions.urls && group.conditions.urls.length > 0; var hasFlag = !!group.conditions.flag; if (!hasEvents && !hasUrls && !hasFlag) { // Empty conditions = trigger immediately on session start this._combinedMatching = new AlwaysActivatedTriggerMatching(); } else { // V2: Events arrive as objects {name, properties?} from the server. // Extract just the names — property filter evaluation is handled by the strategy. var eventNames = (group.conditions.events || []).map(function (e) { return e.name; }); // Convert group config to the format expected by the individual matchers var config = { urlTriggers: group.conditions.urls || [], eventTriggers: eventNames, linkedFlag: group.conditions.flag || null, urlBlocklist: [], }; this._urlTriggerMatching.onConfig(config); this._eventTriggerMatching.onConfig(config); this._linkedFlagMatching.onConfig(config, onFlagStarted); // Combine matchers based on the group's matchType var matchers = [this._eventTriggerMatching, this._urlTriggerMatching, this._linkedFlagMatching]; this._combinedMatching = group.conditions.matchType === 'any' ? new OrTriggerMatching(matchers) : new AndTriggerMatching(matchers); } } TriggerGroupMatching.prototype.triggerStatus = function (sessionId) { return this._combinedMatching.triggerStatus(sessionId); }; TriggerGroupMatching.prototype.triggerStatusNoSideEffects = function (sessionId) { return this._combinedMatching.triggerStatusNoSideEffects(sessionId); }; TriggerGroupMatching.prototype.getPendingTriggerConditions = function (sessionId) { if (this.triggerStatusNoSideEffects(sessionId) !== exports.TRIGGER_PENDING) { return []; } var legs = [ { label: 'URL condition not matched', status: this._urlTriggerMatching.triggerStatusNoSideEffects(sessionId), }, { label: 'event condition not matched', status: this._eventTriggerMatching.triggerStatusNoSideEffects(sessionId), }, { label: 'linked flag condition not matched', status: this._linkedFlagMatching.triggerStatusNoSideEffects(), }, ]; return legs.filter(function (_a) { var status = _a.status; return status === exports.TRIGGER_PENDING; }).map(function (_a) { var label = _a.label; return label; }); }; TriggerGroupMatching.prototype.checkEventTriggerConditions = function (eventName, onActivate, sessionId) { this._eventTriggerMatching.checkEventTriggerConditions(eventName, onActivate, sessionId); }; TriggerGroupMatching.prototype.checkUrlTriggerConditions = function (onPause, onResume, onActivate, sessionId) { this._urlTriggerMatching.checkUrlTriggerConditions(onPause, onResume, onActivate, sessionId); }; /** * V2: Activate this group's trigger and persist to group-specific key * This prevents cross-group contamination and survives page reloads */ TriggerGroupMatching.prototype.activateTrigger = function (triggerType, sessionId) { var _a; var _b; var persistenceKey = triggerType === 'url' ? constants_1.SESSION_RECORDING_TRIGGER_V2_GROUP_URL_PREFIX + this.group.id : constants_1.SESSION_RECORDING_TRIGGER_V2_GROUP_EVENT_PREFIX + this.group.id; (_b = this._instance.persistence) === null || _b === void 0 ? void 0 : _b.register((_a = {}, _a[persistenceKey] = sessionId, _a)); }; TriggerGroupMatching.prototype.stop = function () { this._urlTriggerMatching.stop(); this._eventTriggerMatching.stop(); this._linkedFlagMatching.stop(); }; return TriggerGroupMatching; }()); exports.TriggerGroupMatching = TriggerGroupMatching; // we need a no-op matcher before we can lazy-load the other matches, since all matchers wait on remote config anyway function nullMatchSessionRecordingStatus(triggersStatus) { if (triggersStatus.rrwebError) { return exports.RRWEB_ERROR; } if (!triggersStatus.isRecordingEnabled) { return exports.DISABLED; } return exports.BUFFERING; } function anyMatchSessionRecordingStatus(triggersStatus) { if (triggersStatus.rrwebError) { return exports.RRWEB_ERROR; } if (!triggersStatus.receivedFlags) { return exports.BUFFERING; } if (!triggersStatus.isRecordingEnabled) { return exports.DISABLED; } if (triggersStatus.urlTriggerMatching.urlBlocked) { return exports.PAUSED; } var sampledActive = triggersStatus.isSampled === true; var triggerMatches = new OrTriggerMatching([ triggersStatus.eventTriggerMatching, triggersStatus.urlTriggerMatching, triggersStatus.linkedFlagMatching, ]).triggerStatus(triggersStatus.sessionId); if (sampledActive) { return exports.SAMPLED; } if (triggerMatches === exports.TRIGGER_ACTIVATED) { return exports.ACTIVE; } if (triggerMatches === exports.TRIGGER_PENDING) { // even if sampled active is false, we should still be buffering // since a pending trigger could override it return exports.BUFFERING; } // if sampling is set and the session is already decided to not be sampled // then we should never be active if (triggersStatus.isSampled === false) { return exports.DISABLED; } return exports.ACTIVE; } function allMatchSessionRecordingStatus(triggersStatus) { if (triggersStatus.rrwebError) { return exports.RRWEB_ERROR; } if (!triggersStatus.receivedFlags) { return exports.BUFFERING; } if (!triggersStatus.isRecordingEnabled) { return exports.DISABLED; } if (triggersStatus.urlTriggerMatching.urlBlocked) { return exports.PAUSED; } var andTriggerMatch = new AndTriggerMatching([ triggersStatus.eventTriggerMatching, triggersStatus.urlTriggerMatching, triggersStatus.linkedFlagMatching, ]); var currentTriggerStatus = andTriggerMatch.triggerStatus(triggersStatus.sessionId); var hasTriggersConfigured = currentTriggerStatus !== exports.TRIGGER_DISABLED; var hasSamplingConfigured = (0, core_1.isBoolean)(triggersStatus.isSampled); if (hasTriggersConfigured && currentTriggerStatus === exports.TRIGGER_PENDING) { return exports.BUFFERING; } if (hasTriggersConfigured && currentTriggerStatus === exports.TRIGGER_DISABLED) { return exports.DISABLED; } // sampling can't ever cause buffering, it's always determined right away or not configured if (hasSamplingConfigured && !triggersStatus.isSampled) { return exports.DISABLED; } // If sampling is configured and set to true, return sampled if (triggersStatus.isSampled === true) { return exports.SAMPLED; } return exports.ACTIVE; } /** * V2 Trigger Groups Status Matcher - implements union behavior: * 1. Evaluate ALL trigger groups * 2. For each matching group, check if its sample rate hits * 3. If ANY group's sample rate hits → record session */ function triggerGroupsMatchSessionRecordingStatus(triggersStatus) { var e_3, _a; if (triggersStatus.rrwebError) { return exports.RRWEB_ERROR; } if (!triggersStatus.receivedFlags) { return exports.BUFFERING; } if (!triggersStatus.isRecordingEnabled) { return exports.DISABLED; } // Check if any URL is blocked (url blocklist is global, not per-group) if (triggersStatus.urlTriggerMatching.urlBlocked) { return exports.PAUSED; } var groupMatchers = triggersStatus.triggerGroupMatchers; var samplingResults = triggersStatus.triggerGroupSamplingResults; if (groupMatchers.length === 0) { // No V2 groups configured - should not happen, but treat as disabled return exports.DISABLED; } // Evaluate all groups to determine overall status var anyGroupPending = false; var anyGroupSampled = false; try { for (var groupMatchers_1 = __values(groupMatchers), groupMatchers_1_1 = groupMatchers_1.next(); !groupMatchers_1_1.done; groupMatchers_1_1 = groupMatchers_1.next()) { var matcher = groupMatchers_1_1.value; var groupStatus = matcher.triggerStatus(triggersStatus.sessionId); if (groupStatus === exports.TRIGGER_ACTIVATED) { // Check if this group's sample rate hit var groupId = matcher.group.id; var samplingResult = samplingResults.get(groupId); if ((0, core_1.isUndefined)(samplingResult)) { logger_1.logger.warn('[V2 Triggers] Group activated but no sampling decision found', { groupId: groupId }); } else if (samplingResult === true) { anyGroupSampled = true; } } else if (groupStatus === exports.TRIGGER_PENDING) { anyGroupPending = true; } } } catch (e_3_1) { e_3 = { error: e_3_1 }; } finally { try { if (groupMatchers_1_1 && !groupMatchers_1_1.done && (_a = groupMatchers_1.return)) _a.call(groupMatchers_1); } finally { if (e_3) throw e_3.error; } } // Union behavior: if ANY group hit its sample rate, record if (anyGroupSampled) { return exports.SAMPLED; } // If any group is pending, keep buffering if (anyGroupPending) { return exports.BUFFERING; } // All groups are either disabled, conditions not met, or sampled out return exports.DISABLED; } //# sourceMappingURL=triggerMatching.js.map