UNPKG

posthog-js

Version:

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

2,443 lines • 144 kB
"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 __read = (this && this.__read) || function (o, n) {
    var m = typeof Symbol === "function" && o[Symbol.iterator];
    if (!m) return o;
    var i = m.call(o), r, ar = [], e;
    try {
        while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
    }
    catch (error) { e = { error: error }; }
    finally {
        try {
            if (r && !r.done && (m = i["return"])) m.call(i);
        }
        finally { if (e) throw e.error; }
    }
    return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
    if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
        if (ar || !(i in from)) {
            if (!ar) ar = Array.prototype.slice.call(from, 0, i);
            ar[i] = from[i];
        }
    }
    return to.concat(ar || Array.prototype.slice.call(from));
};
var __values = (this && this.__values) || function(o) {
    var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
    if (m) return m.call(o);
    if (o && typeof o.length === "number") return {
        next: function () {
            if (o && i >= o.length) o = void 0;
            return { value: o && o[i++], done: !o };
        }
    };
    throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LazyLoadedSessionRecording = exports.SESSION_RECORDING_BATCH_KEY = exports.RECORDING_BUFFER_TIMEOUT = exports.RECORDING_MAX_EVENT_SIZE = exports.RECORDING_REMOTE_CONFIG_TTL_MS = exports.RECORDING_IDLE_THRESHOLD_MS = exports.splitBuffer = exports.SEVEN_MEGABYTES = void 0;
var rrweb_types_1 = require("../types/rrweb-types");
var config_1 = require("./config");
var triggerMatching_1 = require("./triggerMatching");
var sessionrecording_utils_1 = require("./sessionrecording-utils");
var sessionrecording_utils_2 = require("./sessionrecording-utils");
Object.defineProperty(exports, "SEVEN_MEGABYTES", { enumerable: true, get: function () { return sessionrecording_utils_2.SEVEN_MEGABYTES; } });
Object.defineProperty(exports, "splitBuffer", { enumerable: true, get: function () { return sessionrecording_utils_2.splitBuffer; } });
var fflate_1 = require("fflate");
var globals_1 = require("@posthog/browser-common/utils/globals");
var globals_2 = require("../../../utils/globals");
var general_utils_1 = require("@posthog/browser-common/utils/general-utils");
var mutation_throttler_1 = require("./mutation-throttler");
var logger_1 = require("@posthog/browser-common/utils/logger");
var core_1 = require("@posthog/core");
var constants_1 = require("../../../constants");
var request_utils_1 = require("@posthog/browser-common/utils/request-utils");
var config_2 = __importDefault(require("../../../config"));
var flushed_size_tracker_1 = require("./flushed-size-tracker");
var recording_strategies_1 = require("./recording-strategies");
var event_utils_1 = require("@posthog/browser-common/utils/event-utils");
var json_ld_1 = require("./json-ld");
var BASE_ENDPOINT = '/s/';
var DEFAULT_CANVAS_QUALITY = 0.4;
var DEFAULT_CANVAS_FPS = 4;
var MAX_CANVAS_FPS = 12;
var MAX_CANVAS_QUALITY = 1;
// lower bound for session_recording.canvasCapture.resolutionScale, so a misconfiguration can't
// capture at a degenerate resolution.
var MIN_CANVAS_SCALE = 0.1;
var TWO_SECONDS = 2000;
var ONE_KB = 1024;
var ONE_MINUTE = 1000 * 60;
var FIVE_MINUTES = ONE_MINUTE * 5;
var ONE_HOUR = ONE_MINUTE * 60;
var MIN_TRIGGER_PENDING_BUFFER_INTERVAL_MILLIS = 1000;
var MAX_TRIGGER_PENDING_BUFFER_INTERVAL_MILLIS = ONE_HOUR;
// A full snapshot runs as one uninterruptible task, so anything at this scale is a
// visible freeze - no rendering, scrolling, or cursor movement - and worth a warning.
var SLOW_FULL_SNAPSHOT_THRESHOLD_MS = 500;
function roundOrUndefined(value) {
    return (0, core_1.isUndefined)(value) ? undefined : Math.round(value);
}
/**
 * Extracts the network_timing value from a capturePerformance config.
 * Returns `true`/`false` if explicitly set, or `undefined` if not specified.
 */
function networkTimingFromConfig(config) {
    return (0, core_1.isObject)(config) ? config.network_timing : config;
}
exports.RECORDING_IDLE_THRESHOLD_MS = FIVE_MINUTES;
exports.RECORDING_REMOTE_CONFIG_TTL_MS = ONE_HOUR;
exports.RECORDING_MAX_EVENT_SIZE = ONE_KB * ONE_KB * 0.9; // ~1mb (with some wiggle room)
exports.RECORDING_BUFFER_TIMEOUT = 2000; // 2 seconds
exports.SESSION_RECORDING_BATCH_KEY = 'recordings';
var LOGGER_PREFIX = '[SessionRecording]';
var logger = (0, logger_1.createLogger)(LOGGER_PREFIX);
var ACTIVE_SOURCES = [
    rrweb_types_1.IncrementalSource.MouseMove,
    rrweb_types_1.IncrementalSource.MouseInteraction,
    rrweb_types_1.IncrementalSource.Scroll,
    rrweb_types_1.IncrementalSource.ViewportResize,
    rrweb_types_1.IncrementalSource.Input,
    rrweb_types_1.IncrementalSource.TouchMove,
    rrweb_types_1.IncrementalSource.MediaInteraction,
    rrweb_types_1.IncrementalSource.Drag,
];
var newQueuedEvent = function (rrwebMethod) { return ({
    rrwebMethod: rrwebMethod,
    enqueuedAt: Date.now(),
    attempt: 1,
}); };
function getRRWeb() {
    var _a;
    return (_a = globals_2.assignableWindow === null || globals_2.assignableWindow === void 0 ? void 0 : globals_2.assignableWindow.__PosthogExtensions__) === null || _a === void 0 ? void 0 : _a.rrweb;
}
function getRRWebRecord() {
    var _a;
    return (_a = getRRWeb()) === null || _a === void 0 ? void 0 : _a.record;
}
function gzipStringToString(serializedData) {
    return (0, fflate_1.strFromU8)((0, fflate_1.gzipSync)((0, fflate_1.strToU8)(serializedData)), true);
}
function serializeForCompression(data) {
    try {
        // fast path: plain native stringify, since a replacer callback is expensive on
        // large snapshots and circular event data is rare
        return JSON.stringify(data);
    }
    catch (_a) {
        // circular event data (e.g. a leaked instance graph) degrades gracefully to
        // '[Circular]' markers instead of throwing, the same two-step approach as jsonStringify
        return JSON.stringify(data, (0, sessionrecording_utils_1.circularReferenceReplacer)());
    }
}
function gzipToString(data) {
    return gzipStringToString(serializeForCompression(data));
}
function gzipToStringAsync(data) {
    return __awaiter(this, void 0, void 0, function () {
        var serializedData, compressed, _a, _b;
        return __generator(this, function (_c) {
            switch (_c.label) {
                case 0:
                    serializedData = serializeForCompression(data);
                    return [4 /*yield*/, (0, core_1.gzipCompress)(serializedData, config_2.default.DEBUG, { rethrow: true })];
                case 1:
                    compressed = _c.sent();
                    _a = fflate_1.strFromU8;
                    _b = Uint8Array.bind;
                    return [4 /*yield*/, compressed.arrayBuffer()];
                case 2: return [2 /*return*/, _a.apply(void 0, [new (_b.apply(Uint8Array, [void 0, _c.sent()]))(), true])];
            }
        });
    });
}
var _gzippedEmptyArray;
var _gzippedEmptyArrayPromise;
var _isNativeAsyncSessionRecordingGzipSupported = typeof globalThis !== 'undefined' && (0, core_1.isGzipSupported)();
var _nativeAsyncSessionRecordingGzipDisabled = false;
function gzipField(data) {
    if ((0, core_1.isArray)(data) && data.length === 0) {
        _gzippedEmptyArray = _gzippedEmptyArray !== null && _gzippedEmptyArray !== void 0 ? _gzippedEmptyArray : gzipToString([]);
        return _gzippedEmptyArray;
    }
    return gzipToString(data);
}
function gzipFieldAsync(data) {
    return __awaiter(this, void 0, void 0, function () {
        return __generator(this, function (_a) {
            if ((0, core_1.isArray)(data) && data.length === 0) {
                if (_gzippedEmptyArray) {
                    return [2 /*return*/, _gzippedEmptyArray];
                }
                _gzippedEmptyArrayPromise =
                    _gzippedEmptyArrayPromise !== null && _gzippedEmptyArrayPromise !== void 0 ? _gzippedEmptyArrayPromise : gzipToStringAsync([])
                        .then(function (compressed) {
                        _gzippedEmptyArray = compressed;
                        return compressed;
                    })
                        .catch(function (error) {
                        _gzippedEmptyArray = undefined;
                        _gzippedEmptyArrayPromise = undefined;
                        throw error;
                    });
                return [2 /*return*/, _gzippedEmptyArrayPromise];
            }
            return [2 /*return*/, gzipToStringAsync(data)];
        });
    });
}
function shouldCompressEvent(event) {
    return (event.type === rrweb_types_1.EventType.FullSnapshot ||
        (event.type === rrweb_types_1.EventType.IncrementalSnapshot &&
            (event.data.source === rrweb_types_1.IncrementalSource.Mutation ||
                event.data.source === rrweb_types_1.IncrementalSource.StyleSheetRule)));
}
function shouldUseNativeAsyncSessionRecordingGzip(event) {
    return (_isNativeAsyncSessionRecordingGzipSupported &&
        !_nativeAsyncSessionRecordingGzipDisabled &&
        shouldCompressEvent(event));
}
function compressedResult(event) {
    return { event: event, size: (0, sessionrecording_utils_1.estimateCompressedEventSize)(event) };
}
function buildCompressedFullSnapshotEvent(event, data) {
    return __assign(__assign({}, event), { data: data, cv: '2024-10' });
}
function buildCompressedIncrementalEvent(event, fields) {
    // reshapes rrweb incremental `data` into its compressed string-field variant — the
    // compiler cannot relate the incoming union member to the matching compressed member
    // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
    return __assign(__assign({}, event), { cv: '2024-10', data: __assign(__assign({}, event.data), fields) });
}
/**
 * rrweb's packer takes an event and returns a string or the reverse on `unpack`.
 * but we want to be able to inspect metadata during ingestion.
 * and don't want to compress the entire event,
 * so we have a custom packer that only compresses part of some events
 *
 * returns the compressed event and its estimated JSON size,
 * avoiding a redundant JSON.stringify for size estimation
 */
function compressEventSync(event) {
    try {
        if (event.type === rrweb_types_1.EventType.FullSnapshot) {
            return compressedResult(buildCompressedFullSnapshotEvent(event, gzipToString(event.data)));
        }
        if (event.type === rrweb_types_1.EventType.IncrementalSnapshot && event.data.source === rrweb_types_1.IncrementalSource.Mutation) {
            return compressedResult(buildCompressedIncrementalEvent(event, {
                texts: gzipField(event.data.texts),
                attributes: gzipField(event.data.attributes),
                removes: gzipField(event.data.removes),
                adds: gzipField(event.data.adds),
            }));
        }
        if (event.type === rrweb_types_1.EventType.IncrementalSnapshot && event.data.source === rrweb_types_1.IncrementalSource.StyleSheetRule) {
            return compressedResult(buildCompressedIncrementalEvent(event, {
                adds: event.data.adds ? gzipToString(event.data.adds) : undefined,
                removes: event.data.removes ? gzipToString(event.data.removes) : undefined,
            }));
        }
    }
    catch (e) {
        logger.error('could not compress event - will use uncompressed event', e);
    }
    return { event: event, size: (0, sessionrecording_utils_1.estimateSize)(event) };
}
function compressEventAsync(event) {
    return __awaiter(this, void 0, void 0, function () {
        var _a, _b, _c, _d, texts, attributes, removes, adds, _e, adds, removes, e_1;
        return __generator(this, function (_f) {
            switch (_f.label) {
                case 0:
                    _f.trys.push([0, 7, , 8]);
                    if (!(event.type === rrweb_types_1.EventType.FullSnapshot)) return [3 /*break*/, 2];
                    _a = compressedResult;
                    _b = buildCompressedFullSnapshotEvent;
                    _c = [event];
                    return [4 /*yield*/, gzipToStringAsync(event.data)];
                case 1: return [2 /*return*/, _a.apply(void 0, [_b.apply(void 0, _c.concat([_f.sent()]))])];
                case 2:
                    if (!(event.type === rrweb_types_1.EventType.IncrementalSnapshot && event.data.source === rrweb_types_1.IncrementalSource.Mutation)) return [3 /*break*/, 4];
                    return [4 /*yield*/, Promise.all([
                            gzipFieldAsync(event.data.texts),
                            gzipFieldAsync(event.data.attributes),
                            gzipFieldAsync(event.data.removes),
                            gzipFieldAsync(event.data.adds),
                        ])];
                case 3:
                    _d = __read.apply(void 0, [_f.sent(), 4]), texts = _d[0], attributes = _d[1], removes = _d[2], adds = _d[3];
                    return [2 /*return*/, compressedResult(buildCompressedIncrementalEvent(event, { texts: texts, attributes: attributes, removes: removes, adds: adds }))];
                case 4:
                    if (!(event.type === rrweb_types_1.EventType.IncrementalSnapshot && event.data.source === rrweb_types_1.IncrementalSource.StyleSheetRule)) return [3 /*break*/, 6];
                    return [4 /*yield*/, Promise.all([
                            event.data.adds ? gzipToStringAsync(event.data.adds) : undefined,
                            event.data.removes ? gzipToStringAsync(event.data.removes) : undefined,
                        ])];
                case 5:
                    _e = __read.apply(void 0, [_f.sent(), 2]), adds = _e[0], removes = _e[1];
                    return [2 /*return*/, compressedResult(buildCompressedIncrementalEvent(event, { adds: adds, removes: removes }))];
                case 6: return [3 /*break*/, 8];
                case 7:
                    e_1 = _f.sent();
                    if ((0, core_1.isNativeAsyncGzipError)(e_1)) {
                        _nativeAsyncSessionRecordingGzipDisabled = true;
                    }
                    logger.error('could not compress event asynchronously - trying synchronous compression', e_1);
                    return [2 /*return*/, compressEventSync(event)];
                case 8: return [2 /*return*/, { event: event, size: (0, sessionrecording_utils_1.estimateSize)(event) }];
            }
        });
    });
}
function isCustomEvent(e, tag) {
    return e.type === rrweb_types_1.EventType.Custom && e.data.tag === tag;
}
function isSessionIdleEvent(e) {
    return isCustomEvent(e, 'sessionIdle');
}
function getSessionIdlePayload(e) {
    return isSessionIdleEvent(e) ? e.data.payload : null;
}
function isSessionEndingEvent(e) {
    return isCustomEvent(e, '$session_ending');
}
function getSessionEndingPayload(e) {
    return isSessionEndingEvent(e) ? e.data.payload : null;
}
function isSessionStartingEvent(e) {
    return isCustomEvent(e, '$session_starting');
}
function getSessionStartingPayload(e) {
    return isSessionStartingEvent(e) ? e.data.payload : null;
}
function isAllowedWhenIdle(e) {
    return isSessionIdleEvent(e) || isSessionEndingEvent(e) || isSessionStartingEvent(e);
}
// dropping these desyncs the player's mirror, styles, or viewport in ways only a
// fresh full snapshot repairs; canvas is excluded because a snapshot cannot restore it
function isMirrorDesyncingWhenDropped(e) {
    if (e.type === rrweb_types_1.EventType.FullSnapshot || e.type === rrweb_types_1.EventType.Meta) {
        return true;
    }
    return e.type === rrweb_types_1.EventType.IncrementalSnapshot && e.data.source !== rrweb_types_1.IncrementalSource.CanvasMutation;
}
/** When we put the recording into a paused state, we add a custom event.
 *  However, in the paused state, events are dropped and never make it to the buffer,
 *  so we need to manually let this one through */
function isRecordingPausedEvent(e) {
    return e.type === rrweb_types_1.EventType.Custom && e.data.tag === 'recording paused';
}
var LazyLoadedSessionRecording = /** @class */ (function () {
    function LazyLoadedSessionRecording(_instance, documentWasEverVisible) {
        var _this = this;
        this._instance = _instance;
        this._endpoint = BASE_ENDPOINT;
        /**
         * Util to help developers working on this feature manually override
         */
        this._forceAllowLocalhostNetworkCapture = false;
        this._stopRrweb = undefined;
        this._jsonLdCaptureReady = false;
        this._lastActivityTimestamp = Date.now();
        this._isActivatingTrigger = false;
        /**
         * and a queue - that contains rrweb events that we want to send to rrweb, but rrweb wasn't able to accept them yet
         */
        this._queuedRRWebEvents = [];
        this._isIdle = 'unknown';
        // events rrweb observed while confirmed idle are dropped, not buffered, so the
        // player's mirror no longer matches the live DOM; when > 0 only a fresh full
        // snapshot stops later incrementals referencing state the player never saw.
        // Cleared only when a full snapshot actually passes the idle gate, so a failed
        // wake heal retries on the next wake instead of losing the signal
        this._eventsDroppedWhileIdle = 0;
        // attribute mutations the throttler dropped across this session, reported on captured
        // events so the drop path is measurable in our own data
        this._throttledMutationsDropped = 0;
        this._oversizedMutationsDropped = 0;
        this._oversizedMutationBytesDropped = 0;
        // true while the current epoch has had no user interaction; a held epoch is
        // discarded (not shipped) by stop or a subsequent rotation
        this._holdFlushUntilInteraction = false;
        // fresh-start holds ship on a clean unload (passive visits are captured, matching
        // pre-hold behavior); rotation-born holds don't — that unload ship was the incident
        this._heldEpochShipsOnUnload = false;
        // set when a held buffer hit the size cap and was dropped to bound memory; a release
        // then takes a fresh full snapshot so the recording resumes playable
        this._heldBufferOverflowed = false;
        // a release while the recorder is stopped (e.g. an override during startSessionRecording's
        // restart) must survive the next start(), whose fresh-start hold would otherwise swallow it
        this._suppressNextFreshStartHold = false;
        this._rrwebError = false;
        this._rrwebStartAttempted = false;
        this._maxDepthExceeded = false;
        // only log a snapshot cost tracking error once per recorder instance
        this._snapshotCostErrorLogged = false;
        // only warn once per recorder instance that client-side masking is shadowing the project setting
        this._hasWarnedClientMaskingOverride = false;
        // only warn once per recorder instance that maskAttributeFn is ignored
        this._hasWarnedExclusiveAttributeMasking = false;
        this._maskRegionsFnFailed = false;
        // only log once per recorder instance so a repeatedly-throwing callback doesn't flood the console
        this._hasLoggedRecorderCallbackError = false;
        this._fullSnapshotTimestamps = [];
        // the session a FullSnapshot was last captured for, read by _ensureFullSnapshotForSession and by
        // the marker-only flush guard (unlike _fullSnapshotTimestamps, which records emit-time debug
        // telemetry). Capture-time, not ship-time: a buffer cleared before it flushed leaves this set.
        this._lastFullSnapshotSessionId = undefined;
        this._fullSnapshotHealAttemptedFor = undefined;
        this._pendingCompressionEvents = [];
        this._queuedCompressionEvents = 0;
        this._compressionQueueGeneration = 0;
        this._isStoppingAfterCompression = false;
        this._removePageViewCaptureHook = undefined;
        this._removeEventTriggerCaptureHook = undefined;
        this._onSessionIdListener = undefined;
        this._onSessionIdleResetForcedListener = undefined;
        this._forceIdleSessionIdListener = undefined;
        this._onSessionIdCallback = function (sessionId, windowId, changeReason) {
            var _a, _b, _c, _d, _e;
            if (!changeReason)
                return;
            // Skip if session hasn't actually changed (callback might fire multiple times)
            if (sessionId === _this._sessionId && windowId === _this._windowId) {
                return;
            }
            var wasLikelyReset = changeReason.noSessionId;
            var shouldLinkSessions = !wasLikelyReset && (changeReason.activityTimeout || changeReason.sessionPastMaximumLength);
            // Capture old IDs before start() updates them
            var oldSessionId = _this._sessionId;
            var oldWindowId = _this._windowId;
            if (shouldLinkSessions) {
                _this._tryAddCustomEvent('$session_ending', {
                    currentSessionId: oldSessionId,
                    currentWindowId: oldWindowId,
                    nextSessionId: sessionId,
                    nextWindowId: windowId,
                    changeReason: changeReason,
                    // we'll need to correct the time of this if it's captured when idle
                    // so we don't extend reported session time with a debug event
                    lastActivityTimestamp: _this._lastActivityTimestamp,
                    flushed_size: (_a = _this._flushedSizeTracker) === null || _a === void 0 ? void 0 : _a.currentTrackedSize(oldSessionId),
                });
            }
            // Reset first full snapshot timestamp for the new session
            (_b = _this._instance.persistence) === null || _b === void 0 ? void 0 : _b.unregister(constants_1.SESSION_RECORDING_FIRST_FULL_SNAPSHOT_TIMESTAMP);
            _this._maxDepthExceeded = false;
            (_d = (_c = getRRWeb()) === null || _c === void 0 ? void 0 : _c.resetMaxDepthState) === null || _d === void 0 ? void 0 : _d.call(_c);
            _this._tryAddCustomEvent('$session_id_change', { sessionId: sessionId, windowId: windowId, changeReason: changeReason });
            _this._clearConditionalRecordingPersistence();
            // The $session_id_change emit above can synchronously re-enter
            // _updateWindowAndSessionIds (rrweb delivers addCustomEvent through emit), which
            // adopts the new ids and restarts the recorder itself. Restart here only when the
            // ids are still stale — confirmed idle, stopped, or states where that emit never
            // reached the session check (blocked URL, queued emit) — so a rotation restarts
            // exactly once.
            if ((_this._isIdle !== false || !_this.isStarted) &&
                (_this._sessionId !== sessionId || _this._windowId !== windowId)) {
                // rotation while not confirmed-active: hold the new epoch's buffer until the
                // user actually interacts, so idle tabs don't ship one recording per rotation.
                // gated on a real session-id change (a windowId-only restart is not rotation-born)
                // and read before _isIdle is overwritten with 'unknown'
                var holdNextEpoch = _this._sessionId !== sessionId && _this._isIdle !== false;
                _this._isIdle = 'unknown';
                _this._restartForSessionIdChange(holdNextEpoch);
            }
            if (shouldLinkSessions) {
                _this._tryAddCustomEvent('$session_starting', {
                    previousSessionId: oldSessionId,
                    previousWindowId: oldWindowId,
                    nextSessionId: sessionId,
                    nextWindowId: windowId,
                    changeReason: changeReason,
                    // we'll need to correct the time of this if it's captured when idle
                    // so we don't extend reported session time with a debug event
                    lastActivityTimestamp: _this._lastActivityTimestamp,
                });
            }
            // always re-decide for the new session — guarding on the persisted config here would skip
            // V2 trigger groups and post-reset() sessions, leaving them undecided (= ACTIVE, unsampled)
            (_e = _this._strategy) === null || _e === void 0 ? void 0 : _e.makeSamplingDecisions(sessionId);
        };
        this._hasPassedMinimumDuration = function () {
            var _a;
            var persistedSessionId = (_a = _this._instance.persistence) === null || _a === void 0 ? void 0 : _a.props[constants_1.SESSION_RECORDING_PAST_MINIMUM_DURATION];
            return persistedSessionId === _this._sessionId;
        };
        this._getBufferDuration = function () {
            var _a, _b;
            if (_this._buffer.data.length === 0) {
                return null;
            }
            var firstTimestamp = (_a = _this._buffer.data[0]) === null || _a === void 0 ? void 0 : _a.timestamp;
            var lastTimestamp = (_b = _this._buffer.data[_this._buffer.data.length - 1]) === null || _b === void 0 ? void 0 : _b.timestamp;
            if (!(0, core_1.isNumber)(firstTimestamp) || !(0, core_1.isNumber)(lastTimestamp)) {
                return null;
            }
            return lastTimestamp - firstTimestamp;
        };
        this._isBelowMinimumDuration = function () {
            var _a;
            var _b, _c, _d;
            var minimumDuration = _this._minimumDuration;
            if (!(0, core_1.isNumber)(minimumDuration)) {
                return false;
            }
            var strictMode = (_c = (_b = _this._instance.config.session_recording) === null || _b === void 0 ? void 0 : _b.strictMinimumDuration) !== null && _c !== void 0 ? _c : false;
            if (!strictMode) {
                var sessionDuration = _this._sessionDuration;
                var isPositiveSessionDuration = (0, core_1.isNumber)(sessionDuration) && sessionDuration >= 0;
                return isPositiveSessionDuration && sessionDuration < minimumDuration;
            }
            if (_this._hasPassedMinimumDuration()) {
                return false;
            }
            var bufferDuration = _this._getBufferDuration();
            if ((0, core_1.isNull)(bufferDuration)) {
                return true;
            }
            if (bufferDuration >= minimumDuration) {
                (_d = _this._instance.persistence) === null || _d === void 0 ? void 0 : _d.register((_a = {},
                    _a[constants_1.SESSION_RECORDING_PAST_MINIMUM_DURATION] = _this._sessionId,
                    _a));
                return false;
            }
            return true;
        };
        this._onBeforeUnload = function () {
            // If still buffering (waiting for triggers), discard the buffer
            if (_this.status === triggerMatching_1.BUFFERING) {
                _this._clearBuffer();
                return;
            }
            // a clean unload releases a fresh-start hold for passive visits (reading, video),
            // but only if the document was ever visible. Rotation-born holds stay held, and an
            // overflowed hold has nothing playable left to ship.
            if (_this._holdFlushUntilInteraction &&
                _this._heldEpochShipsOnUnload &&
                _this._documentWasEverVisible &&
                !_this._heldBufferOverflowed) {
                _this._holdFlushUntilInteraction = false;
            }
            // beforeunload cannot wait for async CompressionStream work. Synchronously
            // compress any queued events so sendBeacon can include them in this flush.
            _this._drainCompressionQueueSync();
            _this._flushBuffer();
        };
        // pagehide fires after beforeunload, i.e. after the flush above has emptied the
        // buffer - but also after rrweb's own pagehide listener has synchronously emitted
        // any remaining deferred stylesheet mutations. Repeat the drain+flush so those
        // events ship instead of dying in a buffer nothing will ever flush again.
        this._onPageHide = function () {
            _this._onBeforeUnload();
        };
        this._onOffline = function () {
            _this._tryAddCustomEvent('browser offline', {});
        };
        this._onOnline = function () {
            _this._tryAddCustomEvent('browser online', {});
        };
        this._onVisibilityChange = function () {
            if (globals_1.document === null || globals_1.document === void 0 ? void 0 : globals_1.document.visibilityState) {
                if (globals_1.document.visibilityState === 'visible') {
                    _this._documentWasEverVisible = true;
                }
                var label = 'window ' + globals_1.document.visibilityState;
                _this._tryAddCustomEvent(label, {});
            }
        };
        // An older core does not provide visibility history. Preserve its unload behavior
        // rather than treating a currently hidden document as one that was never visible.
        this._documentWasEverVisible = documentWasEverVisible !== null && documentWasEverVisible !== void 0 ? documentWasEverVisible : true;
        // we know there's a sessionManager, so don't need to start without a session id
        var _a = this._sessionManager.checkAndGetSessionAndWindowId(), sessionId = _a.sessionId, windowId = _a.windowId;
        this._sessionId = sessionId;
        this._windowId = windowId;
        this._linkedFlagMatching = new triggerMatching_1.LinkedFlagMatching(this._instance);
        this._urlTriggerMatching = new triggerMatching_1.URLTriggerMatching(this._instance);
        this._eventTriggerMatching = new triggerMatching_1.EventTriggerMatching(this._instance);
        this._buffer = this._clearBuffer();
        if (this._sessionIdleThresholdMilliseconds >= this._sessionManager.sessionTimeoutMs) {
            logger.warn("session_idle_threshold_ms (".concat(this._sessionIdleThresholdMilliseconds, ") is greater than the session timeout (").concat(this._sessionManager.sessionTimeoutMs, "). Session will never be detected as idle"));
        }
        this._flushedSizeTracker = new flushed_size_tracker_1.FlushedSizeTracker(this._instance);
    }
    Object.defineProperty(LazyLoadedSessionRecording.prototype, "sessionId", {
        get: function () {
            return this._sessionId;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(LazyLoadedSessionRecording.prototype, "_sessionManager", {
        get: function () {
            if (!this._instance.sessionManager) {
                throw new Error(LOGGER_PREFIX + ' must be started with a valid sessionManager.');
            }
            return this._instance.sessionManager;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(LazyLoadedSessionRecording.prototype, "_sessionIdleThresholdMilliseconds", {
        get: function () {
            return this._instance.config.session_recording.session_idle_threshold_ms || exports.RECORDING_IDLE_THRESHOLD_MS;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(LazyLoadedSessionRecording.prototype, "_isSampled", {
        get: function () {
            var currentValue = this._instance.get_property(constants_1.SESSION_RECORDING_IS_SAMPLED);
            // anything but this session's own tagged decision decodes to null, forcing a fresh decision
            return (0, recording_strategies_1.decodeSamplingDecision)(currentValue, this.sessionId);
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(LazyLoadedSessionRecording.prototype, "_sampleRate", {
        get: function () {
            var _a;
            var rate = (_a = this._remoteConfig) === null || _a === void 0 ? void 0 : _a.sampleRate;
            return (0, core_1.isNumber)(rate) ? rate : null;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(LazyLoadedSessionRecording.prototype, "_minimumDuration", {
        get: function () {
            var _a, _b;
            return (_b = (_a = this._strategy) === null || _a === void 0 ? void 0 : _a.getMinimumDuration(this.sessionId)) !== null && _b !== void 0 ? _b : null;
        },
        enumerable: false,
        configurable: true
    });
    LazyLoadedSessionRecording.prototype.setDocumentWasEverVisible = function (documentWasEverVisible) {
        if (documentWasEverVisible) {
            this._documentWasEverVisible = true;
        }
    };
    Object.defineProperty(LazyLoadedSessionRecording.prototype, "_masking", {
        get: function () {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j;
            var masking_server_side = (_a = this._remoteConfig) === null || _a === void 0 ? void 0 : _a.masking;
            var masking_client_side = {
                maskAllInputs: (_b = this._instance.config.session_recording) === null || _b === void 0 ? void 0 : _b.maskAllInputs,
                maskTextSelector: (_c = this._instance.config.session_recording) === null || _c === void 0 ? void 0 : _c.maskTextSelector,
                blockSelector: (_d = this._instance.config.session_recording) === null || _d === void 0 ? void 0 : _d.blockSelector,
                maskAllElementAttributes: (_e = this._instance.config.session_recording) === null || _e === void 0 ? void 0 : _e.maskAllElementAttributes,
            };
            var maskAllInputs = (_f = masking_client_side === null || masking_client_side === void 0 ? void 0 : masking_client_side.maskAllInputs) !== null && _f !== void 0 ? _f : masking_server_side === null || masking_server_side === void 0 ? void 0 : masking_server_side.maskAllInputs;
            var maskTextSelector = (_g = masking_client_side === null || masking_client_side === void 0 ? void 0 : masking_client_side.maskTextSelector) !== null && _g !== void 0 ? _g : masking_server_side === null || masking_server_side === void 0 ? void 0 : masking_server_side.maskTextSelector;
            var blockSelector = (_h = masking_client_side === null || masking_client_side === void 0 ? void 0 : masking_client_side.blockSelector) !== null && _h !== void 0 ? _h : masking_server_side === null || masking_server_side === void 0 ? void 0 : masking_server_side.blockSelector;
            var maskAllElementAttributes = (_j = masking_client_side === null || masking_client_side === void 0 ? void 0 : masking_client_side.maskAllElementAttributes) !== null && _j !== void 0 ? _j : masking_server_side === null || masking_server_side === void 0 ? void 0 : masking_server_side.maskAllElementAttributes;
            return !(0, core_1.isUndefined)(maskAllInputs) ||
                !(0, core_1.isUndefined)(maskTextSelector) ||
                !(0, core_1.isUndefined)(blockSelector) ||
                !(0, core_1.isUndefined)(maskAllElementAttributes)
                ? {
                    maskAllInputs: maskAllInputs !== null && maskAllInputs !== void 0 ? maskAllInputs : true,
                    maskTextSelector: maskTextSelector,
                    blockSelector: blockSelector,
                    maskAllElementAttributes: maskAllElementAttributes,
                }
                : undefined;
        },
        enumerable: false,
        configurable: true
    });
    /**
     * Client-side masking config in `posthog.init` intentionally wins over the "Privacy and masking"
     * project setting (the remote config). That precedence is by-design, but silent: a developer who
     * sets e.g. `maskTextSelector: '*'` locally can be surprised that their dashboard setting has no
     * effect. Warn (once per recorder) when the two diverge so the override is self-explaining.
     */
    LazyLoadedSessionRecording.prototype._warnIfClientMaskingShadowsServer = function () {
        var _a;
        if (this._hasWarnedClientMaskingOverride) {
            return;
        }
        var masking_server_side = (_a = this._remoteConfig) === null || _a === void 0 ? void 0 : _a.masking;
        if ((0, core_1.isNullish)(masking_server_side)) {
            return;
        }
        var clientConfig = this._instance.config.session_recording;
        var divergentFields = ['maskAllInputs', 'maskTextSelector', 'blockSelector', 'maskAllElementAttributes'].filter(function (field) {
            var clientValue = clientConfig === null || clientConfig === void 0 ? void 0 : clientConfig[field];
            var serverValue = masking_server_side[field];
            // a client value that is set and disagrees with the project's value shadows it — an unset
            // server value counts as a divergence too, since the client is then masking something the
            // project setting did not ask for (e.g. maskTextSelector: '*' vs an unset project selector)
            return !(0, core_1.isUndefined)(clientValue) && clientValue !== serverValue;
        });
        if (divergentFields.length === 0) {
            return;
        }
        this._hasWarnedClientMaskingOverride = true;
        logger.warn('Session recording masking is configured both in `posthog.init` and in your project settings, and they differ. ' +
            'The `session_recording` options in `posthog.init` take precedence, so the project ("Privacy and masking") setting is ignored for: ' +
            divergentFields.join(', ') +
            '. Remove these masking options from `posthog.init` to use the project setting instead. ' +
            'See https://posthog.com/docs/session-replay/privacy', {
            client: {
                maskAllInputs: clientConfig === null || clientConfig === void 0 ? void 0 : clientConfig.maskAllInputs,
                maskTextSelector: clientConfig === null || clientConfig === void 0 ? void 0 : clientConfig.maskTextSelector,
                blockSelector: clientConfig === null || clientConfig === void 0 ? void 0 : clientConfig.blockSelector,
                maskAllElementAttributes: clientConfig === null || clientConfig === void 0 ? void 0 : clientConfig.maskAllElementAttributes,
            },
            project: masking_server_side,
        });
    };
    Object.defineProperty(LazyLoadedSessionRecording.prototype, "_canvasResolutionScale", {
        // (0,1] fraction of the canvas display size to capture frames at, from
        // session_recording.canvasCapture.resolutionScale. clamped to [MIN_CANVAS_SCALE, 1]; defaults
        // to 1 (full resolution) so capture only drops below full resolution when explicitly
        // configured. replay upscales the frame back to its display size.
        get: function () {
            var _a;
            return (0, core_1.clampToRange)((_a = this._instance.config.session_recording.canvasCapture) === null || _a === void 0 ? void 0 : _a.resolutionScale, MIN_CANVAS_SCALE, 1, (0, logger_1.createLogger)('canvas recording resolution scale'), 1);
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(LazyLoadedSessionRecording.prototype, "_canvasRecording", {
        get: function () {
            var _a, _b, _c, _d, _e, _f, _g;
            var canvasRecording_client_side = this._instance.config.session_recording.captureCanvas;
            var canvasRecording_server_side = (_a = this._remoteConfig) === null || _a === void 0 ? void 0 : _a.canvasRecording;
            var enabled = (_c = (_b = canvasRecording_client_side === null || canvasRecording_client_side === void 0 ? void 0 : canvasRecording_client_side.recordCanvas) !== null && _b !== void 0 ? _b : canvasRecording_server_side === null || canvasRecording_server_side === void 0 ? void 0 : canvasRecording_server_side.enabled) !== null && _c !== void 0 ? _c : false;
            var fps = (_e = (_d = canvasRecording_client_side === null || canvasRecording_client_side === void 0 ? void 0 : canvasRecording_client_side.canvasFps) !== null && _d !== void 0 ? _d : canvasRecording_server_side === null || canvasRecording_server_side === void 0 ? void 0 : canvasRecording_server_side.fps) !== null && _e !== void 0 ? _e : DEFAULT_CANVAS_FPS;
            var quality = (_g = (_f = canvasRecording_client_side === null || canvasRecording_client_side === void 0 ? void 0 : canvasRecording_client_side.canvasQuality) !== null && _f !== void 0 ? _f : canvasRecording_server_side === null || canvasRecording_server_side === void 0 ? void 0 : canvasRecording_server_side.quality) !== null && _g !== void 0 ? _g : DEFAULT_CANVAS_QUALITY;
            if (typeof quality === 'string') {
                var parsed = parseFloat(quality);
                quality = isNaN(parsed) ? 0.4 : parsed;
            }
            return {
                enabled: enabled,
                fps: (0, core_1.clampToRange)(fps, 0, MAX_CANVAS_FPS, (0, logger_1.createLogger)('canvas recording fps'), DEFAULT_CANVAS_FPS),
                quality: (0, core_1.clampToRange)(quality, 0, MAX_CANVAS_QUALITY, (0, logger_1.createLogger)('canvas recording quality'), DEFAULT_CANVAS_QUALITY),
            };
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(LazyLoadedSessionRecording.prototype, "_isConsoleLogCaptureEnabled", {
        get: function () {
            var _a;
            var enabled_server_side = !!((_a = this._remoteConfig) === null || _a === void 0 ? void 0 : _a.consoleLogRecordingEnabled);
            var enabled_client_side = this._instance.config.enable_recording_console_log;
            return enabled_client_side !== null && enabled_client_side !== void 0 ? enabled_client_side : enabled_server_side;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(LazyLoadedSessionRecording.prototype, "_networkPayloadCapture", {
        // network payload capture config has three parts
        // each can be configured server side or client side
        get: function () {
            var _a, _b, _c;
            var networkPayloadCapture_server_side = (_a = this._remoteConfig) === null || _a === void 0 ? void 0 : _a.networkPayloadCapture;
            var networkPayloadCapture_client_side = {
                recordHeaders: (_b = this._instance.config.session_recording) === null || _b === void 0 ? void 0 : _b.recordHeaders,
                recordBody: (_c = this._instance.config.session_recording) === null || _c === void 0 ? void 0 : _c.recordBody,
            };
            var headersEnabled = (networkPayloadCapture_client_side === null || networkPayloadCapture_client_side === void 0 ? void 0 : networkPayloadCapture_client_side.recordHeaders) || (networkPayloadCapture_server_side === null || networkPayloadCapture_server_side === void 0 ? void 0 : networkPayloadCapture_server_side.recordHeaders);
            var bodyEnabled = (networkPayloadCapture_client_side === null || networkPayloadCapture_client_side === void 0 ? void 0 : networkPayloadCapture_client_side.recordBody) || (networkPayloadCapture_server_side === null || networkPayloadCapture_server_side === void 0 ? void 0 : networkPayloadCapture_server_side.recordBody);
            var clientNetworkTiming = networkTimingFromConfig(this._instance.config.capture_performance);
            var serverNetworkTiming = networkTimingFromConfig(networkPayloadCapture_server_side === null || networkPayloadCapture_server_side === void 0 ? void 0 : networkPayloadCapture_server_side.capturePerformance);
            var networkTimingEnabled = !!((0, core_1.isBoolean)(clientNetworkTiming) ? clientNetworkTiming : serverNetworkTiming);
            return headersEnabled || bodyEnabled || networkTimingEnabled
                ? { recordHeaders: headersEnabled, recordBody: bodyEnabled, recordPerformance: networkTimingEnabled }
                : undefined;
        },
        enumerable: false,
        configurable: true
    });
    LazyLoadedSessionRecording.prototype._gatherRRWebPlugins = function () {
        var _a, _b, _c, _d;
        var plugins = [];
        var recordConsolePlugin = (_b = (_a = globals_2.assignableWindow.__PosthogExtensions__) === null || _a === void 0 ? void 0 : _a.rrwebPlugins) === null || _b === void 0 ? void 0 : _b.getRecordConsolePlugin;
        if (recordConsolePlugin && this._isConsoleLogCaptureEnabled) {
            plugins.push(recordConsolePlugin());
        }
        var networkPlugin = (_d = (_c = globals_2.assignableWindow.__PosthogExtensions__) === null || _c === void 0 ? void 0 : _c.rrwebPlugins) === null || _d === void 0 ? void 0 : _d.getRecordNetworkPlugin;
        if (!!this._networkPayloadCapture && (0, core_1.isFunction)(networkPlugin)) {
            var canRecordNetwork = !(0, request_utils_1.isLocalhost)() || this._forceAllowLocalhostNetworkCapture;
            if (canRecordNetwork) {
                // The unversioned recorder can run with older cores, so this router method must be feature-detected.
                var isIngestionEndpoint = (0, core_1.isFunction)(this._instance.requestRouter.isIngestionEndpoint)
                    ? this._instance.requestRouter.isIngestionEndpoint.bind(this._instance.requestRouter)
                    : undefined;
                plugins.push(networkPlugin((0, config_1.buildNetworkRequestOptions)(this._instance.config, this._networkPayloadCapture, isIngestionEndpoint)));
            }
            else {
                logger.info('NetworkCapture not started because we are on localhost.');
            }
        }
        return plugins;
    };
    LazyLoadedSessionRecording.prototype._stripUrlHash = function (url) {
        return this._instance.config.disable_capture_url_hashes ? (0, core_1.stripUrlHash)(url) : url;
    };
    LazyLoadedSessionRecording.prototype._maskReplayUrl = function (url, forceStripHash) {
        if (forceStripHash === void 0) { forceStripHash = false; }
        var href = forceStripHash ? (0, core_1.stripUrlHash)(url) : this._stripUrlHash(url);
        var paramsToMask = this._instance.config.mask_personal_data_properties
            ? __spreadArray(__spreadArray([], __read(event_utils_1.PERSONAL_DATA_CAMPAIGN_PARAMS), false), __read((this._instance.config.custom_personal_data_properties || [])), false) : [];
        return this._maskUrl((0, request_utils_1.maskQueryParams)(href, paramsToMask, event_utils_1.MASKED));
    };
    LazyLoadedSessionRecording.prototype._maskUrl = function (url) {
        var _a;
        var userSessionRecordingOptions = this._instance.config.session_recording;
        // userSessionRecordingOptions.maskNetworkRequestFn is deprecated, fallback to it
        if (userSessionRecordingOptions.maskCapturedNetworkRequestFn) {
            var result = userSessionRecordingOptions.maskCapturedNetworkRequestFn({
                name: url,
            });
            // CapturedNetworkRequest uses 'name' for URL, but also check 'url' for compatibility
            return (_a = result === null || result === void 0 ? void 0 : result.name) !== null && _a !== void 0 ? _a : result === null || result === void 0 ? void 0 : result.url;
        }
        if (userSessionRecordingOptions.maskNetworkRequestFn) {
            var result = userSessionRecordingOptions.maskNetworkRequestFn({
                url: url,
            });
            return result === null || result === void 0 ? void 0 : result.url;
        }
        return url;
    };
    LazyLoadedSessionRecording.prototype._tryRRWebMethod = function (queuedRRWebEvent) {
        try {
            queuedRRWebEvent.rrwebMethod();
            return true;
        }
        catch (e) {
            // Sometimes a race can occur where the recorder is not fully started yet
            if (this._queuedRRWebEvents.length < 10) {
                this._queuedRRWebEvents.push({
                    enqueuedAt: queuedRRWebEvent.enqueuedAt || Date.now(),
                    attempt: queuedRRWebEvent.attempt + 1,
                    rrwebMethod: queuedRRWebEvent.rrwebMethod,
                });
            }
            else {
                logger.warn('could not emit queued rrweb event.', e, queuedRRWebEvent);
            }
            return false;
        }
    };
    LazyLoadedSessionRecording.prototype._tryAddCustomEvent = function (tag, payload) {
        return this._tryRRWebMethod(newQueuedEvent(function () { return getRRWebRecord().addCustomEvent(tag, payload); }));
    };
    LazyLoadedSessionRecording.prototype._canCaptureJsonLd = function () {
        var _a;
        return (this._jsonLdCaptureReady &&
            ((_a = this._instance.config.session_recording) === null || _a === void 0 ? void 0 : _a.captureJsonLd) === true &&
            !this._urlTriggerMatching.urlBlocked &&
            this._isIdle !== true);
    };
    LazyLoadedSessionRecording.prototype._tryAddJsonLdEvent = function (jsonLd) {
        if (!this._canCaptureJsonLd()) {
            return false;
        }
        try {
            var rrwebRecord = getRRWebRecord();
            if (!rrwebRecord) {
                return false;
            }
            rrwebRecord.addCustomEvent(json_ld_1.JSON_LD_EVENT_TAG, jsonLd);
            return this._canCaptureJsonLd();
        }
        catch (_a) {
            return false;
        }
    };
    LazyLoadedSessionRecording.prototype._scheduleJsonLdScan = function (force) {
        var _this = this;
        if (force === void 0) { force = false; }
        // Run the scan after the current rrweb event updates the JSON-LD capture state.
        // eslint-disable-next-line compat/compat
        Promise.resolve().then(function () { var _a; return (_a = _this._jsonLdCapture) === null || _a === void 0 ? void 0 : _a.scan(force); });
    };
    LazyLoadedSessionRecording.prototype._pageViewFallBack = function () {
        try {
            if (this._instance.config.capture_pageview || !globals_1.window) {
                return;
            }
            // Preserve the previous normalization behavior for this fallback (e.g. https://test.com -> https://test.com/)
            // while still applying query masking. This path was already hashless before disable_capture_url_hashes.
            // eslint-disable-next-line compat/compat
            var url = new URL(globals_1.window.location.href);
            var currentUrl = this._maskReplayUrl(url.origin + url.pathname + url.search);
            if (this._lastHref !== currentUrl) {
                this._lastHref = currentUrl;
                this._tryAddCustomEvent('$url_changed', { href: currentUrl });
            }
        }
        catch (_a) {
            // If URL processing fails, don't capture anything
        }
    };
    LazyLoadedSessionRecording.prototype._processQueuedEvents = function () {
        var _this = this;
        if (this._queuedRRWebEvents.length) {
            // if rrweb isn't ready to accept events earlier, then we queued them up.
            // now that `emit` has been called rrweb should be ready to accept them.
            // so, before we process this event, we try our queued events _once_ each
            // we don't want to risk queuing more things and never exiting this loop!
            // if they fail here, they'll be pushed into a new queue
            // and tried on the next loop.
            // there is a risk of this queue growing in an uncontrolled manner.
            // so its length is limited elsewhere
            // for now this is to help us ensure we can capture events that happen
            // and try to identify more about when it is failing
            var itemsToProcess = __spreadArray([], __read(this._queuedRRWebEvents), false);
            this._queuedRRWebEvents = [];
            itemsToProcess.forEach(function (queuedRRWebEvent) {
                if (Date.now() - queuedRRWebEvent.enqueuedAt <= TWO_SECONDS) {
                    _this._tryRRWebMethod(queuedRRWebEvent);
                }
            });
        }
    };
    LazyLoadedSessionRecording.prototype._tryTakeFullSnapshot = function () {
        return this._tryRRWebMethod(newQueuedEvent(function () { return getRRWebRecord().takeFullSnapshot(); }));
    };
    Object.defineProperty(LazyLoadedSessionRecording.prototype, "_fullSnapshotIntervalMillis", {
        get: function () {
            var _a, _b, _c, _d;
            if (((_a = this._strategy) === null || _a === void 0 ? void 0 : _a.hasPendingTriggers(this.sessionId)) && !['sampled', 'active'].includes(this.status)) {
                var configuredInterval = (_b = this._instance.config.session_recording) === null || _b === void 0 ? void 0 : _b.trigger_pending_buffer_interval_millis;
                return (0, core_1.isNumber)(configuredInterval) &&
                    Number.isFinite(configuredInterval) &&
                    configuredInterval >= MIN_TRIGGER_PENDING_BUFFER_INTERVAL_MILLIS &&
                    configuredInterval <= MAX_TRIGGER_PENDING_BUFFER_INTERVAL_MILLIS
                    ? configuredInterval
                    : ONE_MINUTE;
            }
            return (_d = (_c = this._instance.config.session_recording) === null || _c === void 0 ? void 0 : _c.full_snapshot_interval_millis) !== null && _d !== void 0 ? _d : FIVE_MINUTES;
        },
        enumerable: false,
        configurable: true
    });
    LazyLoadedSessionRecording.prototype._scheduleFullSnapshot = function () {
        var _this = this;
        if (this._fullSnapshotTimer) {
            clearInterval(this._fullSnapshotTimer);
        }
        // we don't schedule snapshots while idle
        if (this._isIdle === true) {
            return;
        }
        var interval = this._fullSnapshotIntervalMillis;
        if (!interval) {
            return;
        }
        this._fullSnapshotTimer = setInterval(function () {
            _this._tryTakeFullSnapshot();
        }, interval);
    };
    LazyLoadedSessionRecording.prototype._onTriggerActivated = function (triggerType) {
        // V2 event-trigger matches release the interaction hold here (V1's release lives in
        // _activateTrigger). URL and linked-flag activations never release — they fire
        // without a human present.
        if (triggerType === 'event') {
            this._releaseHoldAndFlush();
        }
        if (this._urlTriggerMatching.urlBlocked || !['sampled', 'active'].includes(this.status)) {
            return;
        }
        this._scheduleFullSnapshot();
    };
    LazyLoadedSessionRecording.prototype._pauseRecording = function () {
        var _a;
        // we check _urlBlocked not status, since more than one thing can affect status
        if (this._urlTriggerMatching.urlBlocked) {
            return;
        }
        // we can't flush the buffer here since someone might be starting on a blocked page.
        // and we need to be sure that we don't record that page,
        // so we might not get the below custom event, but events will report the paused status.
        // which will allow debugging of sessions that start on blocked pages
        this._urlTriggerMatching.urlBlocked = true;
        (_a = this._jsonLdCapture) === null || _a === void 0 ? void 0 : _a.scan();
        // Clear the snapshot timer since we don't want new snapshots while paused
        clearInterval(this._fullSnapshotTimer);
        logger.info('recording paused due to URL blocker');
        this._tryAddCustomEvent('recording paused', { reason: 'url blocker' });
    };
    LazyLoadedSessionRecording.prototype._resumeRecording = function () {
        var _a;
        // we check _urlBlocked not status, since more than one thing can affect status
        if (!this._urlTriggerMatching.urlBlocked) {
            return;
        }
        // Baseline JSON-LD while capture is suppressed so blocked-page mutations cannot emit after resume.
        (_a = this._jsonLdCapture) === null || _a === void 0 ? void 0 : _a.scan();
        this._urlTriggerMatching.urlBlocked = false;
        this._tryTakeFullSnapshot();
        this._scheduleJsonLdScan();
        this._scheduleFullSnapshot();
        this._tryAddCustomEvent('recording resumed', { reason: 'left blocked url' });
        logger.info('recording resumed');
    };
    LazyLoadedSessionRecording.prototype._activateTrigger = function (triggerType, matchDetail) {
        var _a, _b;
        var _c, _d, _e;
        // Event triggers are explicit "this session matters" evidence and release the
        // interaction hold; URL triggers only scope where recording is allowed and never do.
        // Must run before the pending-trigger short-circuit below: with triggerMatchType
        // 'any' a URL trigger may already have satisfied the combined status, making this
        // activation a no-op — the hold release must still happen.
        if (triggerType === 'event') {
            this._releaseHoldAndFlush();
        }
        // V1 only: V2 uses per-group activation and never calls this method
        // Prevent re-entry: if we're already activating a trigger, skip to avoid infinite recursion
        // This can happen when _reportStarted emits custom events that match the trigger condition
        if (this._isActivatingTrigger) {
            return;
        }
        if (!((_c = this._strategy) === null || _c === void 0 ? void 0 : _c.hasPendingTriggers(this.sessionId))) {
            return;
        }
        this._isActivatingTrigger = true;
        try {
            // V1: Write trigger activation to persistence
            // (V2 handles this per-group via TriggerGroupMatching.activateTrigger)
            var persistenceKey = triggerType === 'url'
                ? constants_1.SESSION_RECORDING_URL_TRIGGER_ACTIVATED_SESSION
                : constants_1.SESSION_RECORDING_EVENT_TRIGGER_ACTIVATED_SESSION;
            (_d = this._instance.persistence) === null || _d === void 0 ? void 0 : _d.register((_a = {},
                _a[persistenceKey] = this.sessionId,
                _a));
            (_e = this._strategy) === null || _e === void 0 ? void 0 : _e.updateActiveTriggers(this.sessionId);
            this._onTriggerActivated();
            this._flushBuffer();
            this._reportStarted((triggerType + '_trigger_matched'), (_b = {},
                _b[triggerType === 'url' ? 'matchedUrl' : 'matchedEvent'] = matchDetail,
                _b));
        }
        finally {
            this._isActivatingTrigger = false;
        }
    };
    Object.defineProperty(LazyLoadedSessionRecording.prototype, "isStarted", {
        get: function () {
            return !!this._stopRrweb;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(LazyLoadedSessionRecording.prototype, "_remoteConfig", {
        get: function () {
            var _a, _b;
            var persistedConfig = this._instance.get_property(constants_1.SESSION_RECORDING_REMOTE_CONFIG);
            if (!persistedConfig) {
                return undefined;
            }
            var parsedConfig;
            try {
                parsedConfig = (0, core_1.isObject)(persistedConfig) ? persistedConfig : JSON.parse(persistedConfig);
            }
            catch (e) {
                // Do not unregister here: the SDK only registers structured configs, and this read path should
                // ignore corrupt legacy/external values without mutating persistence.
                logger.warn('persisted remote config for session recording is invalid and will be ignored', e);
                return undefined;
            }
            // Only check TTL if recording hasn't started yet
            // Once started, trust the config until a hard page load
            if (!this.isStarted) {
                // default to now so that configs persisted by older SDK versions
                // (which never set cache_timestamp) are treated as fresh
                var cacheTimestamp = (_a = parsedConfig.cache_timestamp) !== null && _a !== void 0 ? _a : Date.now();
                if (Date.now() - cacheTimestamp > exports.RECORDING_REMOTE_CONFIG_TTL_MS) {
                    logger.info('persisted remote config for session recording is stale and will be ignored', {
                        cacheTimestamp: cacheTimestamp,
                        persistedConfig: persistedConfig,
                    });
                    (_b = this._instance.persistence) === null || _b === void 0 ? void 0 : _b.unregister(constants_1.SESSION_RECORDING_REMOTE_CONFIG);
                    return undefined;
                }
            }
            return parsedConfig;
        },
        enumerable: false,
        configurable: true
    });
    LazyLoadedSessionRecording.prototype._checkOverride = function (key, overrideFunction, clearOverride) {
        var overrideFlag = this._instance.get_property(key);
        if (overrideFlag) {
            overrideFunction();
            // Clean up the override flag after applying it
            clearOverride();
        }
    };
    LazyLoadedSessionRecording.prototype.start = function (startReason) {
        var _this = this;
        var _a, _b;
        var config = this._remoteConfig;
        if (!config) {
            logger.info('remote config must be stored in persistence before recording can start');
            return;
        }
        // any epoch that starts without confirmed user activity is held until there's
        // evidence someone cares (interaction, event trigger, or explicit override) —
        // a tab nobody touches (prefetch, background load) must not ship a billable
        // recording. Rotation-born epochs re-set this in _restartForSessionIdChange
        // after this returns. Guarded on isStarted so a re-entrant start() on a live
        // held recorder (e.g. a remote-config refresh) cannot release a hold that only
        // interaction evidence should release.
        if (!this.isStarted) {
            this._holdFlushUntilInteraction = this._isIdle !== false && !this._suppressNextFreshStartHold;
            this._suppressNextFreshStartHold = false;
            this._heldEpochShipsOnUnload = this._holdFlushUntilInteraction;
            this._heldBufferOverflowed = false;
        }
        // Invalidate any in-flight async cleanup queued by a prior stop(). On a session-id
        // rotation, _updateWindowAndSessionIds calls stop() then start() synchronously; if
        // stop() took the _stopAfterCompressionQueueDrains path (non-empty compression queue),
        // its async cleanup would later call _teardown() and silently tear down THIS new
        // recorder once the drain promise resolves. Bumping the generation here causes that
        // pending cleanup to bail at its generation check, and resetting the rest of the
        // compression-stop state means a future stop() of this new recorder is not mistaken
        // for the still-in-progress old one (which would make it a silent no-op).
        // Guarded on the stop-in-progress flag because start() is also called re-entrantly
        // on a live recorder (e.g. opt-in flows) where the queue holds the current session's
        // events and must survive.
        // Discard the buffer too — the bailed-out cleanup would have cleared it. Otherwise the
        // prior session's snapshots flush under the old session id with the new distinct_id,
        // mis-attributing the recording (#3822).
        if (this._isStoppingAfterCompression) {
            this._invalidateCompressionQueue();
            this._clearBuffer();
        }
        // We want to ensure the sessionManager is reset if necessary on loading the recorder
        var _c = this._sessionManager.checkAndGetSessionAndWindowId(), sessionId = _c.sessionId, windowId = _c.windowId;
        this._sessionId = sessionId;
        this._windowId = windowId;
        // Reset first full snapshot tracking for the new session
        (_a = this._instance.persistence) === null || _a === void 0 ? void 0 : _a.unregister(constants_1.SESSION_RECORDING_FIRST_FULL_SNAPSHOT_TIMESTAMP);
        if (config === null || config === void 0 ? void 0 : config.endpoint) {
            this._endpoint = config === null || config === void 0 ? void 0 : config.endpoint;
        }
        // Initialize the appropriate strategy based on config version
        var isV2 = (config === null || config === void 0 ? void 0 : config.version) === 2 && (config === null || config === void 0 ? void 0 : config.triggerGroups) && config.triggerGroups.length > 0;
        if (isV2) {
            this._strategy = new recording_strategies_1.V2TriggerGroupStrategy(this._instance, this._urlTriggerMatching, this._reportStarted.bind(this), this._tryAddCustomEvent.bind(this), this._onTriggerActivated.bind(this));
        }
        else {
            this._strategy = new recording_strategies_1.V1RecordingStrategy(this._instance, this._urlTriggerMatching, this._eventTriggerMatching, this._linkedFlagMatching, this._reportStarted.bind(this), this._tryTakeFullSnapshot.bind(this), this._onTriggerActivated.bind(this));
        }
        // Let the strategy configure itself
        this._strategy.onRemoteConfig(config);
        // Setup event trigger listeners via strategy
        (_b = this._removeEventTriggerCaptureHook) === null || _b === void 0 ? void 0 : _b.call(this);
        this._removeEventTriggerCaptureHook = this._strategy.setupEventTriggerListeners(this._instance.on.bind(this._instance, 'eventCaptured'), this.sessionId, function (triggerType, matchDetail) { return _this._activateTrigger(triggerType, matchDetail); });
        this._checkOverride(constants_1.SESSION_RECORDING_OVERRIDE_SAMPLING, function () {
            _this.overrideSampling();
        }, function () { var _a; return (_a = _this._instance.persistence) === null || _a === void 0 ? void 0 : _a.unregister(constants_1.SESSION_RECORDING_OVERRIDE_SAMPLING); });
        this._checkOverride(constants_1.SESSION_RECORDING_OVERRIDE_LINKED_FLAG, function () {
            _this.overrideLinkedFlag();
        }, function () { var _a; return (_a = _this._instance.persistence) === null || _a === void 0 ? void 0 : _a.unregister(constants_1.SESSION_RECORDING_OVERRIDE_LINKED_FLAG); });
        this._checkOverride(constants_1.SESSION_RECORDING_OVERRIDE_EVENT_TRIGGER, function () {
            _this.overrideTrigger('event');
        }, function () { var _a; return (_a = _this._instance.persistence) === null || _a === void 0 ? void 0 : _a.unregister(constants_1.SESSION_RECORDING_OVERRIDE_EVENT_TRIGGER); });
        this._checkOverride(constants_1.SESSION_RECORDING_OVERRIDE_URL_TRIGGER, function () {
            _this.overrideTrigger('url');
        }, function () { var _a; return (_a = _this._instance.persistence) === null || _a === void 0 ? void 0 : _a.unregister(constants_1.SESSION_RECORDING_OVERRIDE_URL_TRIGGER); });
        // Let strategy make sampling decisions
        this._strategy.makeSamplingDecisions(this.sessionId);
        this._startRecorder();
        if (this._rrwebError) {
            return;
        }
        // calling addEventListener multiple times is safe and will not add duplicates
        (0, general_utils_1.addEventListener)(globals_1.window, 'beforeunload', this._onBeforeUnload);
        // registered after _startRecorder so rrweb's own pagehide listener runs first:
        // it synchronously flushes deferred stylesheet mutations, and this later
        // listener then ships them (beforeunload has already fired, so nothing else will)
        (0, general_utils_1.addEventListener)(globals_1.window, 'pagehide', this._onPageHide);
        (0, general_utils_1.addEventListener)(globals_1.window, 'offline', this._onOffline);
        (0, general_utils_1.addEventListener)(globals_1.window, 'online', this._onOnline);
        (0, general_utils_1.addEventListener)(globals_1.document, 'visibilitychange', this._onVisibilityChange);
        if (!this._onSessionIdListener && (0, core_1.isFunction)(this._sessionManager.onSessionId)) {
            this._onSessionIdListener = this._sessionManager.onSessionId(this._onSessionIdCallback);
        }
        // NB: SessionIdManager.on was only added in posthog-js 1.268.6. This recorder chunk is loaded
        // from the CDN and can run against an older bundled core that has no `on` method, so guard the
        // call to degrade gracefully (recording still starts, it just skips the forced-idle-reset listener)
        // rather than throwing a TypeError during start().
        if (!this._onSessionIdleResetForcedListener && (0, core_1.isFunction)(this._sessionManager.on)) {
            this._onSessionIdleResetForcedListener = this._sessionManager.on('forcedIdleReset', function () {
                // a session was forced to reset due to idle timeout and lack of activity
                _this._clearConditionalRecordingPersistence();
                _this._isIdle = 'unknown';
                _this.stop();
                // then we want a session id listener to restart the recording when a new session starts
                _this._forceIdleSessionIdListener = _this._sessionManager.onSessionId(function (sessionId, windowId, changeReason) {
                    var _a;
                    // this should first unregister itself
                    (_a = _this._forceIdleSessionIdListener) === null || _a === void 0 ? void 0 : _a.call(_this);
                    _this._forceIdleSessionIdListener = undefined;
                    _this._onSessionIdCallback(sessionId, windowId, changeReason);
                });
            });
        }
        else if (!(0, core_1.isFunction)(this._sessionManager.on)) {
            logger.warn('bundled core has no SessionIdManager.on (requires posthog-js >= 1.268.6); ' +
                'recording will start but skip forced-idle-reset handling');
        }
        if ((0, core_1.isNullish)(this._removePageViewCaptureHook)) {
            // :TRICKY: rrweb does not capture navigation within SPA-s, so hook into our $pageview events to get access to all events.
            //   Dropping the initial event is fine (it's always captured by rrweb).
            this._removePageViewCaptureHook = this._instance.on('eventCaptured', function (event) {
                // If anything could go wrong here,
                // it has the potential to block the main loop,
                // so we catch all errors.
                try {
                    if (event.event === '$pageview') {
                        var href = (event === null || event === void 0 ? void 0 : event.properties.$current_url)
                            ? _this._maskReplayUrl(event.properties.$current_url)
                            : '';
                        if (!href) {
                            return;
                        }
                        _this._tryAddCustomEvent('$pageview', { href: href });
                    }
                }
                catch (e) {
                    logger.error('Could not add $pageview to rrweb session', e);
                }
            });
        }
        if (this.status === triggerMatching_1.ACTIVE) {
            this._reportStarted(startReason || 'recording_initialized');
        }
    };
    LazyLoadedSessionRecording.prototype._teardown = function () {
        var _a, _b, _c, _d, _e, _f;
        globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.removeEventListener('beforeunload', this._onBeforeUnload);
        globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.removeEventListener('pagehide', this._onPageHide);
        globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.removeEventListener('offline', this._onOffline);
        globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.removeEventListener('online', this._onOnline);
        globals_1.document === null || globals_1.document === void 0 ? void 0 : globals_1.document.removeEventListener('visibilitychange', this._onVisibilityChange);
        clearInterval(this._fullSnapshotTimer);
        this._clearFlushBufferTimer();
        (_a = this._removePageViewCaptureHook) === null || _a === void 0 ? void 0 : _a.call(this);
        this._removePageViewCaptureHook = undefined;
        (_b = this._removeEventTriggerCaptureHook) === null || _b === void 0 ? void 0 : _b.call(this);
        this._removeEventTriggerCaptureHook = undefined;
        (_c = this._onSessionIdListener) === null || _c === void 0 ? void 0 : _c.call(this);
        this._onSessionIdListener = undefined;
        (_d = this._onSessionIdleResetForcedListener) === null || _d === void 0 ? void 0 : _d.call(this);
        this._onSessionIdleResetForcedListener = undefined;
        (_e = this._forceIdleSessionIdListener) === null || _e === void 0 ? void 0 : _e.call(this);
        this._forceIdleSessionIdListener = undefined;
        (_f = this._strategy) === null || _f === void 0 ? void 0 : _f.stop();
        this._stopRecordingProducers();
        this._invalidateCompressionQueue();
    };
    // Invalidate any in-flight async compression work so it does not capture events
    // after stop()/discard() has cleared the buffer or after a future restart.
    LazyLoadedSessionRecording.prototype._invalidateCompressionQueue = function () {
        this._compressionQueueGeneration += 1;
        this._pendingCompressionEvents = [];
        this._queuedCompressionEvents = 0;
        this._compressionQueue = undefined;
        this._isStoppingAfterCompression = false;
    };
    LazyLoadedSessionRecording.prototype._stopRecordingProducers = function () {
        var _a, _b, _c;
        (_a = this._mutationThrottler) === null || _a === void 0 ? void 0 : _a.stop();
        // Clear any queued rrweb events to prevent memory leaks from closures
        this._queuedRRWebEvents = [];
        (_b = this._jsonLdCapture) === null || _b === void 0 ? void 0 : _b.stop();
        this._jsonLdCapture = undefined;
        this._jsonLdCaptureReady = false;
        (_c = this._stopRrweb) === null || _c === void 0 ? void 0 : _c.call(this);
        this._stopRrweb = undefined;
    };
    LazyLoadedSessionRecording.prototype._stopAfterCompressionQueueDrains = function () {
        var _this = this;
        if (!this._compressionQueue || this._queuedCompressionEvents === 0) {
            return false;
        }
        if (this._isStoppingAfterCompression) {
            return true;
        }
        this._isStoppingAfterCompression = true;
        var generation = this._compressionQueueGeneration;
        this._clearFlushBufferTimer();
        // Stop rrweb synchronously so it cannot keep producing events while we wait
        // for the compression queue to drain and flush the already queued events.
        this._stopRecordingProducers();
        this._compressionQueue
            .catch(function () { return undefined; })
            .then(function () {
            if (generation !== _this._compressionQueueGeneration) {
                return;
            }
            _this._isStoppingAfterCompression = false;
            _this._flushBuffer();
            _this._clearBuffer();
            _this._teardown();
            logger.info('stopped');
        })
            .catch(function () {
            // Keep stop() best-effort. Compression errors are handled per event,
            // but never let an unexpected queue failure block teardown.
            _this._isStoppingAfterCompression = false;
            _this._teardown();
            logger.info('stopped');
        });
        return true;
    };
    LazyLoadedSessionRecording.prototype.stop = function () {
        // Stop rrweb first: its teardown synchronously flushes any deferred
        // stylesheet mutations through the emit path, and they must reach the
        // buffer (or the compression queue, checked next) before the final
        // flush below, or they are cleared unshipped.
        this._stopRecordingProducers();
        if (this._stopAfterCompressionQueueDrains()) {
            return;
        }
        // for a held (interaction-less) epoch the flush is suppressed, so this
        // flush-then-clear discards it — correct for rotation restarts and opt-out alike
        this._flushBuffer();
        this._clearBuffer();
        this._teardown();
        logger.info('stopped');
    };
    // ordering matters: the hold is set after stop() (so the stop discards or ships the
    // old epoch per its own flag) and after start() (whose fresh-start reset would
    // otherwise clobber it); no flush can run synchronously in between
    LazyLoadedSessionRecording.prototype._restartForSessionIdChange = function (holdNextEpoch) {
        var _a, _b;
        this.stop();
        // cost metrics are per-session; reset them only after the old recorder has
        // stopped (its teardown flush still records deferred stylesheet work, which
        // belongs to the old session) and before the new one takes its first snapshot
        this._slowestFullSnapshot = undefined;
        this._lastSeenSnapshotCost = undefined;
        // the throttler drop counts are per-session too, so the new session starts at zero
        this._throttledMutationsDropped = 0;
        this._oversizedMutationsDropped = 0;
        this._oversizedMutationBytesDropped = 0;
        (_b = (_a = getRRWeb()) === null || _a === void 0 ? void 0 : _a.resetSnapshotCostState) === null || _b === void 0 ? void 0 : _b.call(_a);
        this.start('session_id_changed');
        this._holdFlushUntilInteraction = holdNextEpoch;
        this._heldEpochShipsOnUnload = false;
    };
    // releases run on evidence someone cares about the session: a user interaction, an event
    // trigger match, or an explicit override. Scheduling instead of flushing synchronously keeps
    // the release batched on the normal cadence and re-passes _flushBuffer's gates.
    LazyLoadedSessionRecording.prototype._releaseHoldAndFlush = function () {
        if (!this.isStarted) {
            this._suppressNextFreshStartHold = true;
        }
        if (!this._holdFlushUntilInteraction) {
            return;
        }
        this._holdFlushUntilInteraction = false;
        this._heldEpochShipsOnUnload = false;
        if (this._heldBufferOverflowed) {
            this._heldBufferOverflowed = false;
            this._tryTakeFullSnapshot();
        }
        this._scheduleFlushBuffer();
    };
    LazyLoadedSessionRecording.prototype.discard = function (_a) {
        var _b = _a === void 0 ? {} : _a, _c = _b.discardProducerEvents, discardProducerEvents = _c === void 0 ? false : _c;
        if (discardProducerEvents) {
            // rrweb teardown can synchronously emit deferred stylesheet mutations.
            // Clear first so those emissions cannot flush existing data, then clear them below too.
            this._clearBuffer();
            this._stopRecordingProducers();
        }
        this._clearBuffer();
        this._teardown();
        logger.info('discarded');
    };
    LazyLoadedSessionRecording.prototype._captureProcessedEvent = function (event, eventToSend, size, targetSessionId, targetWindowId) {
        var properties = {
            $snapshot_bytes: size,
            $snapshot_data: eventToSend,
            $session_id: targetSessionId,
            $window_id: targetWindowId,
        };
        if (this.status === triggerMatching_1.DISABLED) {
            this._clearBuffer();
            return;
        }
        this._ensureFullSnapshotForSession(event, targetSessionId);
        this._captureSnapshotBuffered(properties);
    };
    // snapshot cost tracking is telemetry: an error in it must be visible in debug
    // logs, but must never break recording. Logged once per recorder lifetime so
    // the FullSnapshot hot path cannot spam the console.
    LazyLoadedSessionRecording.prototype._safeTrackFullSnapshotCost = function () {
        try {
            this._trackFullSnapshotCost();
        }
        catch (e) {
            if (!this._snapshotCostErrorLogged) {
                this._snapshotCostErrorLogged = true;
                logger.error('could not track full snapshot cost', e);
            }
        }
    };
    LazyLoadedSessionRecording.prototype._trackFullSnapshotCost = function () {
        var _a, _b;
        var cost = (_b = (_a = getRRWeb()) === null || _a === void 0 ? void 0 : _a.getLastSnapshotCost) === null || _b === void 0 ? void 0 : _b.call(_a);
        if (!cost || cost === this._lastSeenSnapshotCost) {
            return;
        }
        this._lastSeenSnapshotCost = cost;
        if (!this._slowestFullSnapshot || cost.durationMs > this._slowestFullSnapshot.durationMs) {
            this._slowestFullSnapshot = cost;
        }
        if (cost.durationMs >= SLOW_FULL_SNAPSHOT_THRESHOLD_MS) {
            logger.warn('full snapshot was slow, the page may have been unresponsive while it ran', {
                durationMs: Math.round(cost.durationMs),
                stylesheetMs: Math.round(cost.stylesheetMs),
                nodeCount: cost.nodeCount,
                cssRuleCount: cost.cssRuleCount,
                nonDeferrableCssRuleCount: cost.nonDeferrableCssRuleCount,
                deferredStylesheetCount: cost.deferredStylesheetCount,
            });
        }
    };
    // A session whose incrementals ship before any FullSnapshot is unplayable until the next periodic snapshot, so request one from rrweb (once per session id, to avoid loops if taking one keeps failing).
    LazyLoadedSessionRecording.prototype._ensureFullSnapshotForSession = function (event, targetSessionId) {
        if (event.type === rrweb_types_1.EventType.FullSnapshot) {
            this._lastFullSnapshotSessionId = targetSessionId;
            return;
        }
        if (event.type !== rrweb_types_1.EventType.IncrementalSnapshot) {
            return;
        }
        if (
        // deliberately conservative: only heal after this recorder shipped a FullSnapshot to another session (the rotation signature), since on a fresh start rrweb's init snapshot is always ordered ahead of any incremental
        (0, core_1.isUndefined)(this._lastFullSnapshotSessionId) ||
            this._lastFullSnapshotSessionId === targetSessionId ||
            this._fullSnapshotHealAttemptedFor === targetSessionId) {
            return;
        }
        this._fullSnapshotHealAttemptedFor = targetSessionId;
        logger.info('incremental snapshot for a session with no full snapshot - requesting one', {
            sessionId: targetSessionId,
        });
        this._tryTakeFullSnapshot();
    };
    LazyLoadedSessionRecording.prototype._finishQueuedCompressionEvent = function (queuedEvent) {
        if (queuedEvent.counted && queuedEvent.generation === this._compressionQueueGeneration) {
            this._queuedCompressionEvents = Math.max(0, this._queuedCompressionEvents - 1);
        }
        queuedEvent.counted = false;
        this._pendingCompressionEvents = this._pendingCompressionEvents.filter(function (x) { return x !== queuedEvent; });
    };
    LazyLoadedSessionRecording.prototype._captureQueuedCompressionEvent = function (queuedEvent, eventToSend, size) {
        if (queuedEvent.processed || queuedEvent.generation !== this._compressionQueueGeneration) {
            return;
        }
        queuedEvent.processed = true;
        this._captureProcessedEvent(queuedEvent.event, eventToSend, size, queuedEvent.targetSessionId, queuedEvent.targetWindowId);
    };
    LazyLoadedSessionRecording.prototype._processQueuedCompressionEventSync = function (queuedEvent) {
        try {
            var _a = queuedEvent.compressionEnabled
                ? compressEventSync(queuedEvent.event)
                : { event: queuedEvent.event, size: (0, sessionrecording_utils_1.estimateSize)(queuedEvent.event) }, eventToSend = _a.event, size = _a.size;
            this._captureQueuedCompressionEvent(queuedEvent, eventToSend, size);
        }
        finally {
            this._finishQueuedCompressionEvent(queuedEvent);
        }
    };
    LazyLoadedSessionRecording.prototype._drainCompressionQueueSync = function () {
        var _this = this;
        var queuedEvents = __spreadArray([], __read(this._pendingCompressionEvents), false);
        queuedEvents.forEach(function (queuedEvent) {
            _this._processQueuedCompressionEventSync(queuedEvent);
        });
    };
    LazyLoadedSessionRecording.prototype._enqueueCompression = function (event, compressionEnabled, targetSessionId, targetWindowId) {
        var _this = this;
        var queuedEvent = {
            event: event,
            compressionEnabled: compressionEnabled,
            targetSessionId: targetSessionId,
            targetWindowId: targetWindowId,
            generation: this._compressionQueueGeneration,
            processed: false,
            counted: true,
        };
        this._pendingCompressionEvents.push(queuedEvent);
        this._queuedCompressionEvents += 1;
        var processEvent = function () { return __awaiter(_this, void 0, void 0, function () {
            var eventToSend, size, result, _a, _b, e_2;
            return __generator(this, function (_c) {
                switch (_c.label) {
                    case 0:
                        _c.trys.push([0, , 9, 10]);
                        if (queuedEvent.processed) {
                            return [2 /*return*/];
                        }
                        eventToSend = void 0;
                        size = void 0;
                        _c.label = 1;
                    case 1:
                        _c.trys.push([1, 7, , 8]);
                        if (!compressionEnabled) return [3 /*break*/, 5];
                        if (!shouldUseNativeAsyncSessionRecordingGzip(event)) return [3 /*break*/, 3];
                        return [4 /*yield*/, compressEventAsync(event)];
                    case 2:
                        _b = _c.sent();
                        return [3 /*break*/, 4];
                    case 3:
                        _b = compressEventSync(event);
                        _c.label = 4;
                    case 4:
                        _a = _b;
                        return [3 /*break*/, 6];
                    case 5:
                        _a = { event: event, size: (0, sessionrecording_utils_1.estimateSize)(event) };
                        _c.label = 6;
                    case 6:
                        result = _a;
                        eventToSend = result.event;
                        size = result.size;
                        return [3 /*break*/, 8];
                    case 7:
                        e_2 = _c.sent();
                        // a compression failure must never reject the queue promise chain, since the
                        // rejection would surface as an unhandled rejection and drop the event
                        logger.error('could not process queued compression event - will use uncompressed event', e_2);
                        eventToSend = event;
                        size = (0, sessionrecording_utils_1.estimateSize)(event);
                        return [3 /*break*/, 8];
                    case 8:
                        this._captureQueuedCompressionEvent(queuedEvent, eventToSend, size);
                        return [3 /*break*/, 10];
                    case 9:
                        this._finishQueuedCompressionEvent(queuedEvent);
                        return [7 /*endfinally*/];
                    case 10: return [2 /*return*/];
                }
            });
        }); };
        this._compressionQueue = this._compressionQueue
            ? this._compressionQueue.catch(function () { return undefined; }).then(processEvent)
            : processEvent();
    };
    LazyLoadedSessionRecording.prototype.onRRwebEmit = function (rawEvent) {
        var _a;
        var _this = this;
        var _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
        // late event after sessionManager teardown (e.g. cookieless opt-out) — _sessionManager would throw
        if (!this._instance.sessionManager) {
            return;
        }
        this._processQueuedEvents();
        if (!rawEvent || !(0, core_1.isObject)(rawEvent)) {
            return;
        }
        if (rawEvent.type === rrweb_types_1.EventType.Meta) {
            var href = this._maskReplayUrl(rawEvent.data.href);
            this._lastHref = href;
            if (!href) {
                return;
            }
            rawEvent.data.href = href;
        }
        else {
            this._pageViewFallBack();
        }
        // Check if the URL matches any trigger patterns - delegate to strategy
        (_b = this._strategy) === null || _b === void 0 ? void 0 : _b.checkUrlTriggers(this.sessionId, function () { return _this._pauseRecording(); }, function () { return _this._resumeRecording(); }, function (triggerType, matchDetail) { return _this._activateTrigger(triggerType, matchDetail); });
        // always have to check if the URL is blocked really early,
        // or you risk getting stuck in a loop
        if (this._urlTriggerMatching.urlBlocked && !isRecordingPausedEvent(rawEvent)) {
            return;
        }
        // we're processing a full snapshot, so we should reset the timer
        if (rawEvent.type === rrweb_types_1.EventType.FullSnapshot) {
            if ((_d = (_c = getRRWeb()) === null || _c === void 0 ? void 0 : _c.wasMaxDepthReached) === null || _d === void 0 ? void 0 : _d.call(_c)) {
                this._maxDepthExceeded = true;
            }
            // the FullSnapshot event is emitted partway through rrweb's takeFullSnapshot,
            // whose cost window only closes after the post-snapshot buffer drain and
            // adoptedStyleSheets work; the sync read below picks up any snapshot we have
            // not seen yet, and the microtask (which runs as soon as the synchronous
            // snapshot task finishes, before any newer snapshot can replace the globals)
            // picks up the one in progress right now
            this._safeTrackFullSnapshotCost();
            Promise.resolve().then(function () { return _this._safeTrackFullSnapshotCost(); });
            this._scheduleFullSnapshot();
            // Full snapshots reset rrweb's node IDs, so clear any logged node tracking
            (_e = this._mutationThrottler) === null || _e === void 0 ? void 0 : _e.reset();
            // Track the timestamp of the first full snapshot for this session
            // This helps us detect session rotation issues where incremental snapshots
            // are sent before the full snapshot
            (_f = this._instance.persistence) === null || _f === void 0 ? void 0 : _f.register_once((_a = {},
                _a[constants_1.SESSION_RECORDING_FIRST_FULL_SNAPSHOT_TIMESTAMP] = rawEvent.timestamp,
                _a), undefined);
        }
        // Clear the buffer if waiting for a trigger and only keep data from after the current full snapshot
        var jsonLdRemovedFromPendingBuffer = rawEvent.type === rrweb_types_1.EventType.FullSnapshot && ((_g = this._strategy) === null || _g === void 0 ? void 0 : _g.hasPendingTriggers(this.sessionId));
        if (jsonLdRemovedFromPendingBuffer) {
            this._clearBufferBeforeMostRecentMeta();
        }
        var throttledEvent = this._mutationThrottler ? this._mutationThrottler.throttleMutations(rawEvent) : rawEvent;
        if (!throttledEvent) {
            // when awake, a throttled attribute's later updates get through once the bucket
            // refills; while idle those are dropped too, so only the wake heal restores them
            if (this._isIdle === true) {
                this._eventsDroppedWhileIdle++;
            }
            return;
        }
        // TODO: Re-add ensureMaxMessageSize once we are confident in it
        var event = (0, sessionrecording_utils_1.truncateLargeConsoleLogs)(throttledEvent);
        // Session lifecycle events ($session_ending, $session_starting, sessionIdle) carry their
        // target session ID in the payload. We must extract this BEFORE _updateWindowAndSessionIds
        // runs, because that method triggers checkAndGetSessionAndWindowId() which would update
        // this._sessionId. This is critical for $session_ending which must go to the OLD session,
        // not the new one, and for $session_starting which must go to the NEW session.
        var sessionEndingPayload = getSessionEndingPayload(event);
        var sessionStartingPayload = getSessionStartingPayload(event);
        var sessionIdlePayload = getSessionIdlePayload(event);
        if (sessionEndingPayload || sessionStartingPayload) {
            // Backdate $session_ending to the old session's real end so a late-detected idle
            // period doesn't artificially extend it. $session_starting must NOT be backdated:
            // it ships to the NEW session, and stamping it with the old session's last activity
            // drags the new recording's start back by the whole idle gap
            if (sessionEndingPayload === null || sessionEndingPayload === void 0 ? void 0 : sessionEndingPayload.lastActivityTimestamp) {
                event.timestamp = sessionEndingPayload.lastActivityTimestamp;
            }
        }
        else if (!(sessionIdlePayload === null || sessionIdlePayload === void 0 ? void 0 : sessionIdlePayload.sessionId)) {
            // sessionIdle markers with a pinned session id skip the session check: they are only
            // emitted from inside _updateWindowAndSessionIds, whose caller runs the check itself
            // right after, and running it here first can adopt a pending rotation before the
            // marker is captured — attributing a marker backdated by the idle gap to the new
            // session, which drags the new recording's start back and makes its prefix unplayable
            this._updateWindowAndSessionIds(event);
        }
        if (rawEvent.type === rrweb_types_1.EventType.FullSnapshot) {
            this._fullSnapshotTimestamps.push([this._sessionId, rawEvent.timestamp]);
            if (this._fullSnapshotTimestamps.length > 6) {
                this._fullSnapshotTimestamps = this._fullSnapshotTimestamps.slice(-6);
            }
        }
        // Route lifecycle events using their payload IDs:
        // - $session_ending uses currentSessionId (the old session it's ending)
        // - $session_starting uses nextSessionId (the new session it's starting)
        // - sessionIdle uses the session that went idle (backdated markers must not follow a rotation)
        // - All other events use the current session ID
        var targetSessionId = (_k = (_j = (_h = sessionEndingPayload === null || sessionEndingPayload === void 0 ? void 0 : sessionEndingPayload.currentSessionId) !== null && _h !== void 0 ? _h : sessionStartingPayload === null || sessionStartingPayload === void 0 ? void 0 : sessionStartingPayload.nextSessionId) !== null && _j !== void 0 ? _j : sessionIdlePayload === null || sessionIdlePayload === void 0 ? void 0 : sessionIdlePayload.sessionId) !== null && _k !== void 0 ? _k : this._sessionId;
        var targetWindowId = (_o = (_m = (_l = sessionEndingPayload === null || sessionEndingPayload === void 0 ? void 0 : sessionEndingPayload.currentWindowId) !== null && _l !== void 0 ? _l : sessionStartingPayload === null || sessionStartingPayload === void 0 ? void 0 : sessionStartingPayload.nextWindowId) !== null && _m !== void 0 ? _m : sessionIdlePayload === null || sessionIdlePayload === void 0 ? void 0 : sessionIdlePayload.windowId) !== null && _o !== void 0 ? _o : this._windowId;
        // When in an idle state we keep recording but don't capture the events,
        // we don't want to return early if idle is 'unknown'
        if (this._isIdle === true && !isAllowedWhenIdle(event)) {
            if (isMirrorDesyncingWhenDropped(event)) {
                this._eventsDroppedWhileIdle++;
            }
            return;
        }
        if (event.type === rrweb_types_1.EventType.FullSnapshot) {
            // this snapshot re-syncs the player's mirror, so idle-era drops are healed
            this._eventsDroppedWhileIdle = 0;
        }
        if (sessionIdlePayload) {
            // session idle events have a timestamp when rrweb sees them
            // which can artificially lengthen a session
            // we know when we detected it based on the payload and can correct the timestamp
            event.timestamp = sessionIdlePayload.lastActivityTimestamp + sessionIdlePayload.threshold;
        }
        var jsonLdCaptureWasReady = this._jsonLdCaptureReady;
        this._jsonLdCaptureReady = true;
        if (jsonLdRemovedFromPendingBuffer) {
            this._scheduleJsonLdScan(true);
        }
        else if (!jsonLdCaptureWasReady) {
            this._scheduleJsonLdScan();
        }
        var compressionEnabled = (_p = this._instance.config.session_recording.compress_events) !== null && _p !== void 0 ? _p : true;
        if (this._queuedCompressionEvents > 0 ||
            (compressionEnabled && shouldUseNativeAsyncSessionRecordingGzip(event))) {
            this._enqueueCompression(event, compressionEnabled, targetSessionId, targetWindowId);
            return;
        }
        var _q = compressionEnabled
            ? compressEventSync(event)
            : { event: event, size: (0, sessionrecording_utils_1.estimateSize)(event) }, eventToSend = _q.event, size = _q.size;
        this._captureProcessedEvent(event, eventToSend, size, targetSessionId, targetWindowId);
    };
    Object.defineProperty(LazyLoadedSessionRecording.prototype, "status", {
        get: function () {
            if (!this._strategy) {
                return triggerMatching_1.DISABLED;
            }
            var context = {
                instance: this._instance,
                sessionId: this.sessionId,
                isSampled: this._isSampled,
                rrwebError: this._rrwebError,
                urlTriggerMatching: this._urlTriggerMatching,
                eventTriggerMatching: this._eventTriggerMatching,
                linkedFlagMatching: this._linkedFlagMatching,
                remoteConfig: this._remoteConfig,
            };
            return this._strategy.getStatus(context);
        },
        enumerable: false,
        configurable: true
    });
    LazyLoadedSessionRecording.prototype.log = function (message, level) {
        var _a;
        if (level === void 0) { level = 'log'; }
        (_a = this._instance.sessionRecording) === null || _a === void 0 ? void 0 : _a.onRRwebEmit({
            type: 6,
            data: {
                plugin: 'rrweb/console@1',
                payload: {
                    level: level,
                    trace: [],
                    // Even though it is a string, we stringify it as that's what rrweb expects
                    payload: [JSON.stringify(message)],
                },
            },
            timestamp: Date.now(),
        });
    };
    LazyLoadedSessionRecording.prototype.overrideLinkedFlag = function () {
        this._linkedFlagMatching.linkedFlagSeen = true;
        this._tryTakeFullSnapshot();
        this._releaseHoldAndFlush();
        this._reportStarted('linked_flag_overridden');
    };
    /**
     * this ignores the sampling config and (if other conditions are met) causes capture to start
     *
     * It is not usual to call this directly,
     * instead call `posthog.startSessionRecording({sampling: true})`
     * */
    LazyLoadedSessionRecording.prototype.overrideSampling = function () {
        var _a;
        var _b;
        (_b = this._instance.persistence) === null || _b === void 0 ? void 0 : _b.register((_a = {},
            // short-circuits the `makeSamplingDecision` function in the session recording module
            _a[constants_1.SESSION_RECORDING_IS_SAMPLED] = this.sessionId,
            _a[constants_1.SESSION_RECORDING_SAMPLE_RATE] = null,
            _a));
        this._tryTakeFullSnapshot();
        this._releaseHoldAndFlush();
        this._reportStarted('sampling_overridden');
    };
    /**
     * this ignores the URL/Event trigger config and (if other conditions are met) causes capture to start
     *
     * It is not usual to call this directly,
     * instead call `posthog.startSessionRecording({trigger: 'url' | 'event'})`
     * */
    LazyLoadedSessionRecording.prototype.overrideTrigger = function (triggerType) {
        this._activateTrigger(triggerType);
        // unlike an organic URL trigger match, an explicit override releases the hold for both trigger types
        this._releaseHoldAndFlush();
    };
    LazyLoadedSessionRecording.prototype._currentMaskedHostname = function () {
        var _a;
        try {
            var href = (_a = globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.location) === null || _a === void 0 ? void 0 : _a.href;
            if (!href) {
                return undefined;
            }
            var maskedUrl = this._maskReplayUrl(href);
            if (!maskedUrl) {
                return undefined;
            }
            // not convertToURL: it resolves invalid input (e.g. a masking fn returning "REDACTED")
            // against the current page and would return the real hostname we're trying to mask.
            // new URL throws instead, so bad input falls through to the catch and we omit the property.
            // eslint-disable-next-line compat/compat
            return new URL(maskedUrl).hostname || undefined;
        }
        catch (_b) {
            return undefined;
        }
    };
    LazyLoadedSessionRecording.prototype._clearFlushBufferTimer = function () {
        if (this._flushBufferTimer) {
            clearTimeout(this._flushBufferTimer);
            this._flushBufferTimer = undefined;
        }
    };
    LazyLoadedSessionRecording.prototype._scheduleFlushBuffer = function () {
        var _this = this;
        if (!this._flushBufferTimer) {
            this._flushBufferTimer = setTimeout(function () {
                _this._flushBuffer();
            }, exports.RECORDING_BUFFER_TIMEOUT);
        }
    };
    // Custom events are lifecycle markers (sessionIdle, $session_id_change, ...). A buffer holding
    // nothing else has no content to render, so shipping it as a session's first block opens a
    // recording that bills the customer and plays back as nothing. Once the session has a
    // FullSnapshot, later marker-only blocks append to real content and are fine.
    // $session_starting and $session_ending are exempt: they only exist in this stream, and
    // dropping them would break the linking chain through a session that recorded nothing.
    LazyLoadedSessionRecording.prototype._wouldOpenRecordingWithMarkersOnly = function () {
        if (this._lastFullSnapshotSessionId === this._buffer.sessionId || this._buffer.data.length === 0) {
            return false;
        }
        return this._buffer.data.every(function (event) {
            return (event === null || event === void 0 ? void 0 : event.type) === rrweb_types_1.EventType.Custom && !isSessionEndingEvent(event) && !isSessionStartingEvent(event);
        });
    };
    /**
     * Names the trigger conditions that are keeping the session in BUFFERING, so diagnostics can
     * identify which leg failed instead of only reporting an unexplained "buffering". A URL trigger
     * that never matches under the default AND matching otherwise looks like a session that is about
     * to record.
     */
    LazyLoadedSessionRecording.prototype._describePendingTriggerConditions = function () {
        var _a, _b;
        return (_b = (_a = this._strategy) === null || _a === void 0 ? void 0 : _a.getPendingTriggerConditions(this.sessionId)) !== null && _b !== void 0 ? _b : [];
    };
    LazyLoadedSessionRecording.prototype._maybeLogBufferingReason = function (status) {
        var _a, _b, _c;
        if (status !== triggerMatching_1.BUFFERING) {
            if (!(0, core_1.isUndefined)(this._lastLoggedBufferingReason)) {
                this._instance.register_for_session((_a = {}, _a[constants_1.SDK_DEBUG_REPLAY_PENDING_TRIGGER_CONDITIONS] = [], _a));
            }
            this._lastLoggedBufferingReason = undefined;
            return;
        }
        var pending = this._describePendingTriggerConditions();
        if (pending.length === 0) {
            if (!(0, core_1.isUndefined)(this._lastLoggedBufferingReason)) {
                this._instance.register_for_session((_b = {}, _b[constants_1.SDK_DEBUG_REPLAY_PENDING_TRIGGER_CONDITIONS] = [], _b));
            }
            this._lastLoggedBufferingReason = undefined;
            return;
        }
        var reason = pending.join(', ');
        var reasonKey = "".concat(this.sessionId, ":").concat(reason);
        // only report on change so the flush cadence doesn't repeat diagnostics while buffering
        if (reasonKey === this._lastLoggedBufferingReason) {
            return;
        }
        this._lastLoggedBufferingReason = reasonKey;
        this._instance.register_for_session((_c = {}, _c[constants_1.SDK_DEBUG_REPLAY_PENDING_TRIGGER_CONDITIONS] = pending, _c));
        logger.info("buffering: ".concat(reason));
    };
    LazyLoadedSessionRecording.prototype._flushBuffer = function () {
        var _this = this;
        var _a, _b;
        // cleared before the re-entrant reads below, so a flush they schedule survives this call
        this._clearFlushBufferTimer();
        // hold the buffer rather than ship a billable recording for an epoch nobody touched
        if (this._holdFlushUntilInteraction) {
            return this._buffer;
        }
        // keep the markers rather than open a recording with them: the next capture schedules another
        // flush, so they ship alongside the content that follows, and go with the page if none does
        if (this._wouldOpenRecordingWithMarkersOnly()) {
            // unplayable either way, so a flush that finds them past the cap drops them rather than
            // holding them. Only a flush checks this, so it bounds the common case, not every case
            return this._buffer.size > exports.RECORDING_MAX_EVENT_SIZE ? this._clearBuffer() : this._buffer;
        }
        // the reads below consult the session manager, which can synchronously adopt a pending
        // session rotation and re-enter this recorder, swapping this._buffer for the new epoch's;
        // that pass flushes or holds it itself, so ship only the buffer these checks validated
        var validatedBuffer = this._buffer;
        // never flush while a sampling decision is missing (e.g. wiped by posthog.reset()) — an
        // undecided session reads as ACTIVE and would leak a batch it then decides not to record
        (_a = this._strategy) === null || _a === void 0 ? void 0 : _a.ensureSamplingDecision(this.sessionId);
        var isBelowMinimumDuration = this._isBelowMinimumDuration();
        var status = this.status;
        // run on every flush, not just the buffering branch: when the session goes active this
        // clears the saved reason, so a later session that buffers for the same condition still logs
        this._maybeLogBufferingReason(status);
        if (status === triggerMatching_1.BUFFERING || status === triggerMatching_1.PAUSED || status === triggerMatching_1.DISABLED || isBelowMinimumDuration) {
            this._scheduleFlushBuffer();
            return this._buffer;
        }
        if (this._buffer !== validatedBuffer) {
            return this._buffer;
        }
        if (validatedBuffer.data.length > 0) {
            var snapshotHostname_1 = this._currentMaskedHostname();
            var snapshotEvents = (0, sessionrecording_utils_1.splitBuffer)(validatedBuffer);
            snapshotEvents.forEach(function (snapshotBuffer) {
                var _a;
                (_a = _this._flushedSizeTracker) === null || _a === void 0 ? void 0 : _a.trackSize(snapshotBuffer.sessionId, snapshotBuffer.size);
                _this._captureSnapshot({
                    $snapshot_bytes: snapshotBuffer.size,
                    $snapshot_data: snapshotBuffer.data,
                    $session_id: snapshotBuffer.sessionId,
                    $window_id: snapshotBuffer.windowId,
                    $lib: config_2.default.LIB_NAME,
                    $lib_version: config_2.default.LIB_VERSION,
                    $snapshot_host: snapshotHostname_1,
                });
            });
            // Notify strategy that initial flush is complete (performance optimization)
            (_b = this._strategy) === null || _b === void 0 ? void 0 : _b.onFlushComplete();
        }
        // buffer is empty, we clear it in case the session id has changed
        return this._clearBuffer();
    };
    LazyLoadedSessionRecording.prototype._captureSnapshotBuffered = function (properties) {
        var _a;
        var additionalBytes = 2 + (((_a = this._buffer) === null || _a === void 0 ? void 0 : _a.data.length) || 0); // 2 bytes for the array brackets and 1 byte for each comma
        // Extract target session ID from properties to ensure we flush when session changes
        // This is critical for lifecycle events ($session_ending, $session_starting) which may
        // have different target session IDs than this._sessionId
        var targetSessionId = properties.$session_id;
        // A session-id mismatch must flush and rebind even while idle, or an idle rotation appends the new session's Meta and FullSnapshot to the old session's buffer and ships them under the old session id.
        var sessionChanged = this._buffer.sessionId !== targetSessionId;
        if (sessionChanged ||
            // we never want to flush a healthy same-session buffer while confirmed idle, but
            // 'unknown' still captures so its buffer must respect the size cap or it grows unbounded
            (this._isIdle !== true &&
                !this._holdFlushUntilInteraction &&
                this._buffer.size + properties.$snapshot_bytes + additionalBytes > exports.RECORDING_MAX_EVENT_SIZE)) {
            var sessionBeforeFlush = this._sessionId;
            this._buffer = this._flushBuffer();
            // a rotation adopted re-entrantly during that flush owns this._buffer now; clearing it
            // or relabeling it with this event's pre-rotation ids would mis-attribute the new
            // epoch, so drop the stale event instead
            if (this._sessionId !== sessionBeforeFlush) {
                return;
            }
            // A suppressed flush (e.g. buffering, paused, held, below minimum duration) returns the buffer un-drained, and relabeling the prior session's events would mis-attribute them, so discard them instead.
            if (sessionChanged && this._buffer.data.length > 0) {
                this._buffer = this._clearBuffer();
            }
            // After flushing, update buffer to use the new target session/window IDs
            this._buffer.sessionId = targetSessionId;
            this._buffer.windowId = properties.$window_id;
        }
        // a held buffer can't ship at the cap, so bound memory instead: drop the epoch's data
        // and stop collecting; a release takes a fresh full snapshot to resume playable
        if (this._holdFlushUntilInteraction &&
            (this._heldBufferOverflowed ||
                this._buffer.size + properties.$snapshot_bytes + additionalBytes > exports.RECORDING_MAX_EVENT_SIZE)) {
            if (!this._heldBufferOverflowed) {
                this._heldBufferOverflowed = true;
                this._buffer = this._clearBuffer();
            }
            return;
        }
        this._buffer.size += properties.$snapshot_bytes;
        this._buffer.data.push(properties.$snapshot_data);
        this._buffer.sizes.push(properties.$snapshot_bytes);
        // Schedule the flush unless confirmed idle or held — a held epoch can't ship anyway
        // (scheduling would just churn a no-op timer every cycle) and every release path
        // schedules its own flush.
        if (this._isIdle !== true && !this._holdFlushUntilInteraction) {
            this._scheduleFlushBuffer();
        }
    };
    LazyLoadedSessionRecording.prototype._captureSnapshot = function (properties) {
        // :TRICKY: Make sure we batch these requests, use a custom endpoint and don't truncate the strings.
        this._instance.capture('$snapshot', properties, {
            _url: this._instance.requestRouter.endpointFor('api', this._endpoint),
            _noTruncate: true,
            _batchKey: exports.SESSION_RECORDING_BATCH_KEY,
            skip_client_rate_limiting: true,
        });
    };
    Object.defineProperty(LazyLoadedSessionRecording.prototype, "_sessionDuration", {
        get: function () {
            var _a, _b;
            var mostRecentSnapshot = (_a = this._buffer) === null || _a === void 0 ? void 0 : _a.data[((_b = this._buffer) === null || _b === void 0 ? void 0 : _b.data.length) - 1];
            var sessionStartTimestamp = this._sessionManager.checkAndGetSessionAndWindowId(true).sessionStartTimestamp;
            return mostRecentSnapshot ? mostRecentSnapshot.timestamp - sessionStartTimestamp : null;
        },
        enumerable: false,
        configurable: true
    });
    LazyLoadedSessionRecording.prototype._clearBufferBeforeMostRecentMeta = function () {
        if (!this._buffer || this._buffer.data.length === 0) {
            return this._clearBuffer();
        }
        // Find the last meta event index by iterating backwards
        var lastMetaIndex = -1;
        for (var i = this._buffer.data.length - 1; i >= 0; i--) {
            if (this._buffer.data[i].type === rrweb_types_1.EventType.Meta) {
                lastMetaIndex = i;
                break;
            }
        }
        if (lastMetaIndex >= 0) {
            this._buffer.data = this._buffer.data.slice(lastMetaIndex);
            this._buffer.sizes = this._buffer.sizes.slice(lastMetaIndex);
            this._buffer.size = this._buffer.sizes.reduce(function (a, b) { return a + b; }, 0);
            return this._buffer;
        }
        else {
            return this._clearBuffer();
        }
    };
    LazyLoadedSessionRecording.prototype._clearBuffer = function () {
        this._buffer = {
            size: 0,
            data: [],
            sizes: [],
            sessionId: this._sessionId,
            windowId: this._windowId,
        };
        return this._buffer;
    };
    LazyLoadedSessionRecording.prototype._reportStarted = function (startReason, tagPayload) {
        var _a;
        this._instance.register_for_session((_a = {},
            _a[constants_1.SESSION_RECORDING_START_REASON] = startReason,
            _a));
        logger.info(startReason.replace('_', ' '), tagPayload);
        if (startReason !== 'session_id_changed') {
            this._tryAddCustomEvent('$recording_started', __assign({ reason: startReason }, tagPayload));
        }
    };
    LazyLoadedSessionRecording.prototype._isInteractiveEvent = function (event) {
        var _a;
        return (event.type === sessionrecording_utils_1.INCREMENTAL_SNAPSHOT_EVENT_TYPE &&
            ACTIVE_SOURCES.indexOf((_a = event.data) === null || _a === void 0 ? void 0 : _a.source) !== -1);
    };
    LazyLoadedSessionRecording.prototype._updateWindowAndSessionIds = function (event) {
        // Some recording events are triggered by non-user events (e.g. "X minutes ago" text updating on the screen).
        // We don't want to extend the session or trigger a new session in these cases. These events are designated by event
        // type -> incremental update, and source -> mutation.
        var _a;
        var isUserInteraction = this._isInteractiveEvent(event);
        if (!isUserInteraction && this._isIdle !== true) {
            // We check if the lastActivityTimestamp is old enough to go idle
            var timeSinceLastActivity = event.timestamp - this._lastActivityTimestamp;
            if (timeSinceLastActivity > this._sessionIdleThresholdMilliseconds) {
                // we mark as idle right away,
                // or else we get multiple idle events
                // if there are lots of non-user activity events being emitted
                this._isIdle = true;
                // don't take full snapshots while idle
                clearInterval(this._fullSnapshotTimer);
                this._tryAddCustomEvent('sessionIdle', {
                    eventTimestamp: event.timestamp,
                    lastActivityTimestamp: this._lastActivityTimestamp,
                    threshold: this._sessionIdleThresholdMilliseconds,
                    bufferLength: this._buffer.data.length,
                    bufferSize: this._buffer.size,
                    // pin the marker to the session that actually went idle: it is restamped to
                    // lastActivityTimestamp + threshold, and if a pending rotation is adopted
                    // before it is captured (e.g. waking from a long-suspended tab), routing it
                    // by the live session id would backdate the rotation-born session's start
                    // by the whole idle gap
                    sessionId: this._sessionId,
                    windowId: this._windowId,
                });
                // proactively flush the buffer in case the session is idle for a long time
                this._flushBuffer();
            }
        }
        var returningFromIdle = false;
        if (isUserInteraction) {
            this._lastActivityTimestamp = event.timestamp;
            if (this._isIdle) {
                var idleWasUnknown = this._isIdle === 'unknown';
                // Remove the idle state
                this._isIdle = false;
                // if the idle state was unknown, we don't want to add an event, since we're just in bootup
                // whereas if it was true, we know we've been idle for a while, and we can mark ourselves as returning from idle
                if (!idleWasUnknown) {
                    this._tryAddCustomEvent('sessionNoLongerIdle', {
                        reason: 'user activity',
                        type: event.type,
                    });
                    returningFromIdle = true;
                }
            }
        }
        // The session check runs in every idle state (it only reads in-memory persistence
        // props, so it is cheap). While 'unknown' the recorder still captures, so it must keep
        // checking or its events are stamped with a stale session id. While confirmed idle,
        // the readOnly check below still enforces the 24-hour session cap
        // (sessionPastMaximumLength rotates even on readOnly calls) and hears cross-tab
        // rotations. Bailing here is how idle tabs used to accrete multi-day recordings that
        // blew straight through SESSION_LENGTH_LIMIT under one session id.
        // We only want to extend the session if it is an interactive event.
        var _b = this._sessionManager.checkAndGetSessionAndWindowId(!isUserInteraction, event.timestamp), windowId = _b.windowId, sessionId = _b.sessionId;
        var sessionIdChanged = this._sessionId !== sessionId;
        var windowIdChanged = this._windowId !== windowId;
        this._windowId = windowId;
        this._sessionId = sessionId;
        if (sessionIdChanged || windowIdChanged) {
            var holdNextEpoch = sessionIdChanged && this._isIdle !== false;
            if (holdNextEpoch) {
                // same idle bookkeeping as the session-manager callback path: a rotation born
                // without interaction starts its epoch in the 'unknown' state (this path can
                // adopt a rotation with _isIdle === true now that confirmed-idle emits still
                // run the session check for the 24-hour cap)
                this._isIdle = 'unknown';
            }
            this._restartForSessionIdChange(holdNextEpoch);
        }
        else {
            if (isUserInteraction) {
                // first interaction within a held session ships the buffer, playable from t=0
                this._releaseHoldAndFlush();
            }
            if (returningFromIdle) {
                // a trigger-pending buffer ships on activation, so it needs the heal as
                // much as a live recording; only sampled-out and disabled states skip it
                var bufferCanShip = ['sampled', 'active'].includes(this.status) || ((_a = this._strategy) === null || _a === void 0 ? void 0 : _a.hasPendingTriggers(this.sessionId));
                // a snapshot from _releaseHoldAndFlush above has already cleared the
                // counter by the time this check runs, so we never take two in one tick
                if (this._eventsDroppedWhileIdle > 0 && bufferCanShip) {
                    this._tryTakeFullSnapshot();
                }
                this._scheduleJsonLdScan();
                this._scheduleFullSnapshot();
            }
        }
    };
    LazyLoadedSessionRecording.prototype._clearConditionalRecordingPersistence = function () {
        var _a;
        (_a = this._strategy) === null || _a === void 0 ? void 0 : _a.clearConditionalRecordingPersistence();
    };
    Object.defineProperty(LazyLoadedSessionRecording.prototype, "sdkDebugProperties", {
        get: function () {
            var _a;
            var _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
            var sessionStartTimestamp = this._sessionManager.checkAndGetSessionAndWindowId(true).sessionStartTimestamp;
            // deferred sheets that never made it back into the recording (see rrweb-snapshot),
            // undefined on recorder chunks that predate the counters
            var deferredStylesheetStats = (_c = (_b = getRRWeb()) === null || _b === void 0 ? void 0 : _b.getDeferredStylesheetStats) === null || _c === void 0 ? void 0 : _c.call(_b);
            return _a = {
                    $recording_status: this.status,
                    $sdk_debug_replay_internal_buffer_length: this._buffer.data.length,
                    $sdk_debug_replay_internal_buffer_size: this._buffer.size,
                    $sdk_debug_current_session_duration: this._sessionDuration,
                    $sdk_debug_session_start: sessionStartTimestamp,
                    $sdk_debug_replay_flushed_size: (_d = this._flushedSizeTracker) === null || _d === void 0 ? void 0 : _d.currentTrackedSize(this.sessionId),
                    $sdk_debug_replay_full_snapshots: this._fullSnapshotTimestamps,
                    $snapshot_max_depth_exceeded: this._maxDepthExceeded,
                    // slowest, not most recent: a full snapshot is one uninterruptible task, so
                    // the worst one is the freeze a user would actually have noticed
                    $sdk_debug_replay_slowest_full_snapshot_ms: roundOrUndefined((_e = this._slowestFullSnapshot) === null || _e === void 0 ? void 0 : _e.durationMs),
                    $sdk_debug_replay_slowest_full_snapshot_stylesheet_ms: roundOrUndefined((_f = this._slowestFullSnapshot) === null || _f === void 0 ? void 0 : _f.stylesheetMs),
                    $sdk_debug_replay_slowest_full_snapshot_nodes: (_g = this._slowestFullSnapshot) === null || _g === void 0 ? void 0 : _g.nodeCount,
                    $sdk_debug_replay_slowest_full_snapshot_css_rules: (_h = this._slowestFullSnapshot) === null || _h === void 0 ? void 0 : _h.cssRuleCount,
                    // never-deferrable sources (CSSOM-only <style>, adoptedStyleSheets) split out,
                    // so a customer can tell which bucket dominates their page
                    $sdk_debug_replay_slowest_full_snapshot_css_rules_non_deferrable: (_j = this._slowestFullSnapshot) === null || _j === void 0 ? void 0 : _j.nonDeferrableCssRuleCount,
                    // cumulative across the session, unlike the slowest-snapshot properties: a fast
                    // first snapshot's deferrals must not vanish because a slower one deferred none
                    $sdk_debug_replay_deferred_stylesheets: deferredStylesheetStats === null || deferredStylesheetStats === void 0 ? void 0 : deferredStylesheetStats.deferredCount,
                    $sdk_debug_replay_deferred_stylesheets_failed: deferredStylesheetStats === null || deferredStylesheetStats === void 0 ? void 0 : deferredStylesheetStats.failedCount,
                    $sdk_debug_replay_deferred_stylesheets_abandoned: deferredStylesheetStats === null || deferredStylesheetStats === void 0 ? void 0 : deferredStylesheetStats.abandonedCount,
                    $sdk_debug_replay_deferred_stylesheet_ms: roundOrUndefined(deferredStylesheetStats === null || deferredStylesheetStats === void 0 ? void 0 : deferredStylesheetStats.totalMs),
                    $sdk_debug_replay_deferred_stylesheet_slowest_slice_ms: roundOrUndefined(deferredStylesheetStats === null || deferredStylesheetStats === void 0 ? void 0 : deferredStylesheetStats.slowestSliceMs),
                    $sdk_debug_replay_slowest_mutation_batch_ms: roundOrUndefined((_m = (_l = (_k = getRRWeb()) === null || _k === void 0 ? void 0 : _k.getMutationCost) === null || _l === void 0 ? void 0 : _l.call(_k)) === null || _m === void 0 ? void 0 : _m.slowestBatchMs),
                    // duration samples dropped because they straddled a tab suspension or blew
                    // the plausibility cap (see rrweb-snapshot snapshot-cost.ts): the duration
                    // gauges above stay alert-safe, and the discard itself stays observable
                    $sdk_debug_replay_discarded_duration_samples: (_p = (_o = getRRWeb()) === null || _o === void 0 ? void 0 : _o.getDiscardedDurationSamples) === null || _p === void 0 ? void 0 : _p.call(_o),
                    // cumulative across the session: attribute mutations the throttler dropped, each
                    // of which risks the player showing DOM that already left the live page
                    $sdk_debug_replay_throttled_mutations_dropped: this._throttledMutationsDropped,
                    $sdk_debug_replay_oversized_mutations_dropped: this._oversizedMutationsDropped,
                    $sdk_debug_replay_oversized_mutation_bytes_dropped: this._oversizedMutationBytesDropped,
                    $sdk_debug_replay_rrweb_error: this._rrwebError
                },
                _a[constants_1.SDK_DEBUG_REPLAY_RRWEB_ATTACHED] = !!this._stopRrweb,
                _a[constants_1.SDK_DEBUG_REPLAY_RRWEB_START_ATTEMPTED] = this._rrwebStartAttempted,
                _a;
        },
        enumerable: false,
        configurable: true
    });
    LazyLoadedSessionRecording.prototype._startRecorder = function () {
        var e_3, _a;
        var _this = this;
        var _b, _c, _d, _e, _f;
        if (this._stopRrweb) {
            return;
        }
        this._rrwebStartAttempted = true;
        // rrweb config info: https://github.com/rrweb-io/rrweb/blob/7d5d0033258d6c29599fb08412202d9a2c7b9413/src/record/index.ts#L28
        var sessionRecordingOptions = {
            // a limited set of the rrweb config options that we expose to our users.
            // see https://github.com/rrweb-io/rrweb/blob/master/guide.md
            blockClass: 'ph-no-capture',
            blockSelector: undefined,
            ignoreClass: 'ph-ignore-input',
            maskTextClass: 'ph-mask',
            maskTextSelector: undefined,
            maskTextFn: undefined,
            maskAllInputs: true,
            maskInputOptions: { password: true },
            maskInputFn: undefined,
            maskAllElementAttributes: false,
            maskAttributeFn: undefined,
            slimDOMOptions: {},
            collectFonts: false,
            inlineStylesheet: true,
            // inlining every CSSRule of every sheet is the dominant cost of a full
            // snapshot on CSS-heavy pages, and it runs as one uninterruptible task.
            // We cap it here (rrweb itself stays unbounded) so the budget only turns
            // on when this config surface ships alongside the recorder; the overflow
            // is inlined across idle callbacks. The cap is in rules, not elapsed
            // time, so a single enormous sheet can't slip through, and ~10k rules
            // (a couple hundred ms in Chrome, well above Bootstrap's 2-3k) keeps it
            // inert for ordinary sites. Users can raise it, or set 0 to disable.
            inlineStylesheetBudgetRules: 10000,
            recordCrossOriginIframes: false,
            sampling: undefined,
            attributeFilter: undefined,
        };
        // only allows user to set our allowlisted options
        var userSessionRecordingOptions = this._instance.config.session_recording;
        try {
            for (var _g = __values(Object.entries(userSessionRecordingOptions || {})), _h = _g.next(); !_h.done; _h = _g.next()) {
                var _j = __read(_h.value, 2), key = _j[0], value = _j[1];
                if (key in sessionRecordingOptions) {
                    if (key === 'maskInputOptions') {
                        // ensure password config is set if not included
                        sessionRecordingOptions.maskInputOptions = __assign({ password: true }, value);
                    }
                    else if (key === 'sampling') {
                        // only allow a narrow subset of rrweb's sampling options;
                        // canvas sampling is owned by the canvas recording config
                        var userSampling = (value !== null && value !== void 0 ? value : {});
                        var sampling = {};
                        if (!(0, core_1.isUndefined)(userSampling.mousemove)) {
                            sampling.mousemove = userSampling.mousemove;
                        }
                        if (!(0, core_1.isUndefined)(userSampling.mouseInteraction)) {
                            sampling.mouseInteraction = userSampling.mouseInteraction;
                        }
                        sessionRecordingOptions.sampling = sampling;
                    }
                    else {
                        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
                        // @ts-ignore
                        sessionRecordingOptions[key] = value;
                    }
                }
            }
        }
        catch (e_3_1) { e_3 = { error: e_3_1 }; }
        finally {
            try {
                if (_h && !_h.done && (_a = _g.return)) _a.call(_g);
            }
            finally { if (e_3) throw e_3.error; }
        }
        if ((userSessionRecordingOptions === null || userSessionRecordingOptions === void 0 ? void 0 : userSessionRecordingOptions.captureJsonLd) === true &&
            sessionRecordingOptions.slimDOMOptions !== true &&
            sessionRecordingOptions.slimDOMOptions !== 'all') {
            sessionRecordingOptions.slimDOMOptions = __assign(__assign({}, sessionRecordingOptions.slimDOMOptions), { script: true });
        }
        if (this._canvasRecording && this._canvasRecording.enabled) {
            sessionRecordingOptions.recordCanvas = true;
            // canvas fps is owned by the canvas recording config; merge so that
            // user-provided sampling (e.g. mousemove) survives
            sessionRecordingOptions.sampling = __assign(__assign({}, sessionRecordingOptions.sampling), { canvas: this._canvasRecording.fps });
            sessionRecordingOptions.dataURLOptions = { type: 'image/webp', quality: this._canvasRecording.quality };
            sessionRecordingOptions.canvasResolutionScale = this._canvasResolutionScale;
            sessionRecordingOptions.canvasMasking = {
                // read live like regionsFn below: when true, rrweb skips canvas pixels
                // in DOM full snapshots, and it is re-evaluated at each snapshot so a
                // provider registered after recording started (via set_config) is
                // honored without a recorder restart
                configured: function () { var _a, _b; return (0, core_1.isFunction)((_b = (_a = _this._instance.config.session_recording) === null || _a === void 0 ? void 0 : _a.canvasCapture) === null || _b === void 0 ? void 0 : _b.maskRegionsFn); },
                // read live so a provider registered after recording started (e.g. by
                // a plugin that boots later than posthog-js) takes effect immediately.
                // undefined must stay distinct from null, or every canvas without a
                // provider would fail closed
                regionsFn: function (canvas) {
                    var _a, _b, _c;
                    var fn = (_b = (_a = _this._instance.config.session_recording) === null || _a === void 0 ? void 0 : _a.canvasCapture) === null || _b === void 0 ? void 0 : _b.maskRegionsFn;
                    if (!(0, core_1.isFunction)(fn)) {
                        return undefined;
                    }
                    try {
                        return (_c = fn(canvas)) !== null && _c !== void 0 ? _c : null;
                    }
                    catch (e) {
                        if (!_this._maskRegionsFnFailed) {
                            _this._maskRegionsFnFailed = true;
                            logger.warn('canvasCapture.maskRegionsFn threw, canvas frames will be skipped', e);
                        }
                        return null;
                    }
                },
            };
        }
        if (this._masking) {
            sessionRecordingOptions.maskAllInputs = (_b = this._masking.maskAllInputs) !== null && _b !== void 0 ? _b : true;
            sessionRecordingOptions.maskTextSelector = (_c = this._masking.maskTextSelector) !== null && _c !== void 0 ? _c : undefined;
            sessionRecordingOptions.blockSelector = (_d = this._masking.blockSelector) !== null && _d !== void 0 ? _d : undefined;
            sessionRecordingOptions.maskAllElementAttributes = (_e = this._masking.maskAllElementAttributes) !== null && _e !== void 0 ? _e : false;
        }
        if (sessionRecordingOptions.maskAllElementAttributes && sessionRecordingOptions.maskAttributeFn) {
            // mutually exclusive, and the coarse option fails closed so a callback
            // cannot accidentally unmask what it hides
            sessionRecordingOptions.maskAttributeFn = undefined;
            if (!this._hasWarnedExclusiveAttributeMasking) {
                this._hasWarnedExclusiveAttributeMasking = true;
                logger.warn('`maskAllElementAttributes` and `maskAttributeFn` are mutually exclusive, and `maskAttributeFn` is being ignored. ' +
                    'Remove one of the two. Note that `maskAllElementAttributes` can also come from your project\'s "Privacy and masking" settings. ' +
                    'See https://posthog.com/docs/session-replay/privacy');
            }
        }
        this._warnIfClientMaskingShadowsServer();
        var rrwebRecord = getRRWebRecord();
        if (!rrwebRecord) {
            logger.error('_startRecorder was called but rrwebRecord is not available. This indicates something has gone wrong.');
            return;
        }
        this._mutationThrottler =
            (_f = this._mutationThrottler) !== null && _f !== void 0 ? _f : new mutation_throttler_1.MutationThrottler(rrwebRecord, {
                refillRate: this._instance.config.session_recording.__mutationThrottlerRefillRate,
                bucketSize: this._instance.config.session_recording.__mutationThrottlerBucketSize,
                onBlockedNode: function (id, node) {
                    var message = "Too many mutations on node '".concat(id, "'. Rate limiting. This could be due to SVG animations or something similar");
                    logger.info(message, {
                        node: node,
                    });
                    _this.log(LOGGER_PREFIX + ' ' + message, 'warn');
                },
                onDroppedAttributeMutations: function (count) { return (_this._throttledMutationsDropped += count); },
                bytesRefillRate: this._instance.config.session_recording.__mutationBytesRefillRate,
                bytesBucketSize: this._instance.config.session_recording.__mutationBytesBucketSize,
                resyncIntervalMs: this._fullSnapshotIntervalMillis,
                onDroppedOversizedMutation: function (bytes) {
                    if (_this._oversizedMutationsDropped === 0) {
                        _this.log(LOGGER_PREFIX +
                            ' Dropped an oversized DOM mutation to keep the recording playable. The recording will resync with a full snapshot.', 'warn');
                    }
                    _this._oversizedMutationsDropped += 1;
                    _this._oversizedMutationBytesDropped += bytes;
                },
                requestFullSnapshot: function () { return _this._tryTakeFullSnapshot(); },
            });
        var activePlugins = this._gatherRRWebPlugins();
        this._stopRrweb = rrwebRecord(__assign({ emit: function (event) {
                _this.onRRwebEmit(event);
            }, plugins: activePlugins, errorHandler: function (error, context) {
                // A host API patch shares its callback boundary with the native
                // operation. Preserve the application's exception semantics when
                // rrweb cannot reliably distinguish where that error originated.
                if (context !== 'rrweb') {
                    return false;
                }
                if (!_this._hasLoggedRecorderCallbackError) {
                    _this._hasLoggedRecorderCallbackError = true;
                    logger.error('rrweb internal error - recording will continue but may be incomplete', error);
                }
                return true;
            } }, sessionRecordingOptions));
        if (!this._stopRrweb) {
            this._rrwebError = true;
            logger.error('rrweb failed to start - Loss of recording data is possible. Check the browser console for rrweb errors.');
            return;
        }
        this._rrwebError = false;
        if ((userSessionRecordingOptions === null || userSessionRecordingOptions === void 0 ? void 0 : userSessionRecordingOptions.captureJsonLd) === true &&
            globals_1.document &&
            (globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.MutationObserver) &&
            !this._jsonLdCapture) {
            this._jsonLdCapture = (0, json_ld_1.startJsonLdCapture)(globals_1.document, globals_1.window.MutationObserver, {
                blockClass: sessionRecordingOptions.blockClass,
                blockSelector: sessionRecordingOptions.blockSelector,
                maskTextClass: sessionRecordingOptions.maskTextClass,
                maskTextSelector: sessionRecordingOptions.maskTextSelector,
                getCaptureState: function () {
                    var _a;
                    return ((_a = _this._instance.config.session_recording) === null || _a === void 0 ? void 0 : _a.captureJsonLd) !== true ||
                        _this._urlTriggerMatching.urlBlocked ||
                        _this._urlTriggerMatching.isCurrentUrlBlocked()
                        ? null
                        : _this._canCaptureJsonLd();
                },
                emit: function (jsonLd) { return _this._tryAddJsonLdEvent(jsonLd); },
            });
            if (this._urlTriggerMatching.isCurrentUrlBlocked()) {
                this._jsonLdCapture.scan();
            }
        }
        // We reset the last activity timestamp, resetting the idle timer
        this._lastActivityTimestamp = Date.now();
        // stay unknown if we're not sure if we're idle or not
        this._isIdle = (0, core_1.isBoolean)(this._isIdle) ? this._isIdle : 'unknown';
        this.tryAddCustomEvent('$remote_config_received', this._remoteConfig);
        this._tryAddCustomEvent('$session_options', {
            sessionRecordingOptions: sessionRecordingOptions,
            activePlugins: activePlugins.map(function (p) { return p === null || p === void 0 ? void 0 : p.name; }),
        });
        this._tryAddCustomEvent('$posthog_config', {
            config: this._instance.config,
        });
    };
    LazyLoadedSessionRecording.prototype.tryAddCustomEvent = function (tag, payload) {
        return this._tryAddCustomEvent(tag, payload);
    };
    return LazyLoadedSessionRecording;
}());
exports.LazyLoadedSessionRecording = LazyLoadedSessionRecording;
//# sourceMappingURL=lazy-loaded-session-recorder.js.map