UNPKG

posthog-js

Version:

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

2,666 lines • 156 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.PENDING_BUFFER_STORAGE_SUFFIX = exports.SESSION_RECORDING_BATCH_KEY = exports.RECORDING_BUFFER_TIMEOUT = exports.RECORDING_MAX_EVENT_SIZE = exports.RECORDING_IDLE_THRESHOLD_MS = exports.splitBuffer = exports.SEVEN_MEGABYTES = exports.RECORDING_REMOTE_CONFIG_TTL_MS = void 0;
var constants_1 = require("../../../constants");
var constants_2 = require("../../../constants");
Object.defineProperty(exports, "RECORDING_REMOTE_CONFIG_TTL_MS", { enumerable: true, get: function () { return constants_2.RECORDING_REMOTE_CONFIG_TTL_MS; } });
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_3 = require("../../../constants");
var storage_1 = require("../../../storage");
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;
/**
 * 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_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';
// snapshots a timing gate still held when the page unloaded are parked under this key for the
// next page in the same tab. sessionStorage dies with the tab, so a visit that really ended
// there still honours the gate that held them
exports.PENDING_BUFFER_STORAGE_SUFFIX = '_replay_pending_buffer';
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 (e) {
        // data past the engine's maximum string length is not something the replacer can
        // shorten, and the retry would build the string up to that limit all over again
        // before failing the same way - on unload that stall is paid before the final flush
        if (e instanceof RangeError) {
            throw e;
        }
        // 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
    // oxlint-disable-next-line typescript/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.warn('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.warn('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;
        this._throttledMutationsDropped = 0;
        this._oversizedMutationsDropped = 0;
        this._oversizedMutationBytesDropped = 0;
        // events dropped because their JSON is longer than the engine's maximum string length. The
        // page keeps running and the recording loses them silently, so the count has to ship
        this._unstringifiableEventsDropped = 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 stopped collecting to bound memory; a
        // release ships what was held and takes a fresh full snapshot to resume 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;
        // 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;
        // the session a FullSnapshot was last captured for, read by _ensureFullSnapshotForSession and by
        // the marker-only flush guard. Capture-time, not ship-time: a buffer cleared before it flushed leaves this set.
        this._lastFullSnapshotSessionId = undefined;
        this._fullSnapshotHealAttemptedFor = undefined;
        // a parked buffer is read once per recorder, so a re-entrant start() cannot replay it twice
        this._pendingBufferRestored = false;
        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_3.SESSION_RECORDING_FIRST_FULL_SNAPSHOT_TIMESTAMP);
            (_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);
        };
        // 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
        this._isRestartingForSessionIdChange = false;
        this._hasPassedMinimumDuration = function () {
            var _a;
            var persistedSessionId = (_a = _this._instance.persistence) === null || _a === void 0 ? void 0 : _a.props[constants_3.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_3.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. An
            // overflowed hold takes its recovery snapshot here, not in _releaseHoldAndFlush:
            // that path early-returns once the hold is cleared, so a cancelled navigation
            // would never heal the gap between cap and the resumed recording.
            if (_this._holdFlushUntilInteraction && _this._heldEpochShipsOnUnload && _this._documentWasEverVisible) {
                _this._setFlushHold(undefined);
                if (_this._heldBufferOverflowed) {
                    _this._heldBufferOverflowed = false;
                    _this._tryTakeFullSnapshot();
                }
            }
            // beforeunload cannot wait for async CompressionStream work. Synchronously
            // compress any queued events so sendBeacon can include them in this flush.
            _this._drainCompressionQueueSync();
            _this._flushBuffer(true);
        };
        // 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, sessionStartTimestamp = _a.sessionStartTimestamp;
        this._sessionId = sessionId;
        this._windowId = windowId;
        this._sessionStartTimestamp = sessionStartTimestamp;
        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();
        // A shared persistence_name also shares session/window IDs, but must not share replay
        // data across project tokens. Encode the pair without ambiguous separators and use a
        // distinct key shape: legacy parked buffers have no token and cannot be safely restored.
        var persistenceName = this._instance.config.persistence_name || this._instance.config.token;
        this._pendingBufferStorageKey =
            'ph' + exports.PENDING_BUFFER_STORAGE_SUFFIX + '_' + JSON.stringify([persistenceName, this._instance.config.token]);
        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_3.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,
            };
            // an explicit client `false` wins, as in buildNetworkRequestOptions. Otherwise a remote `true` still
            // installs the plugin, and its performance observer records request urls even with network timing off
            var headersEnabled = networkPayloadCapture_client_side.recordHeaders !== false &&
                (networkPayloadCapture_client_side.recordHeaders || (networkPayloadCapture_server_side === null || networkPayloadCapture_server_side === void 0 ? void 0 : networkPayloadCapture_server_side.recordHeaders));
            var bodyEnabled = networkPayloadCapture_client_side.recordBody !== false &&
                (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(__assign(__assign({}, (0, config_1.buildNetworkRequestOptions)(this._instance.config, this._networkPayloadCapture, isIngestionEndpoint)), { 
                    // Only new cores default this private option to true. Old cores loading the
                    // unversioned recorder lack it and must retain legacy fetch delegation.
                    captureFetchSynchronously: this._instance.config.__preview_replay_sync_fetch === true })));
            }
            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.
        // oxlint-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.
            // oxlint-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_3.SESSION_RECORDING_URL_TRIGGER_ACTIVATED_SESSION
                : constants_3.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;
            var persistedConfig = this._instance.get_property(constants_3.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
            // A rotation restart is briefly not-started between stop() and start(); it keeps that trust
            if (!this.isStarted && !this._isRestartingForSessionIdChange) {
                // 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 > constants_1.RECORDING_REMOTE_CONFIG_TTL_MS) {
                    logger.info('persisted remote config for session recording is stale and will be ignored', {
                        cacheTimestamp: cacheTimestamp,
                        persistedConfig: persistedConfig,
                    });
                    // Core needs the persisted config to reach its refresh path when recording restarts.
                    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) {
            var holdFreshStart = this._isIdle !== false && !this._suppressNextFreshStartHold;
            this._setFlushHold(holdFreshStart ? 'no_interaction_since_recording_started' : undefined);
            this._suppressNextFreshStartHold = false;
            this._heldEpochShipsOnUnload = holdFreshStart;
            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, sessionStartTimestamp = _c.sessionStartTimestamp;
        this._sessionId = sessionId;
        this._windowId = windowId;
        this._sessionStartTimestamp = sessionStartTimestamp;
        // pick up snapshots a timing gate held when the previous page in this tab unloaded
        this._restorePendingBuffer();
        // Reset first full snapshot tracking for the new session
        (_a = this._instance.persistence) === null || _a === void 0 ? void 0 : _a.unregister(constants_3.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_3.SESSION_RECORDING_OVERRIDE_SAMPLING, function () {
            _this.overrideSampling();
        }, function () { var _a; return (_a = _this._instance.persistence) === null || _a === void 0 ? void 0 : _a.unregister(constants_3.SESSION_RECORDING_OVERRIDE_SAMPLING); });
        this._checkOverride(constants_3.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_3.SESSION_RECORDING_OVERRIDE_LINKED_FLAG); });
        this._checkOverride(constants_3.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_3.SESSION_RECORDING_OVERRIDE_EVENT_TRIGGER); });
        this._checkOverride(constants_3.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_3.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;
        // rrweb's stop closure must neither abort the teardown that follows nor leave
        // _stopRrweb set, which would keep isStarted reporting a stopped recorder
        try {
            (_c = this._stopRrweb) === null || _c === void 0 ? void 0 : _c.call(this);
        }
        catch (e) {
            logger.warn('could not stop rrweb', e);
        }
        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;
        // The internal hold still suppresses the final flush, but it is no longer an actionable
        // diagnostic once stop() has returned to the caller.
        this._flushHoldReason = undefined;
        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._releaseHoldAfterStop();
            _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._releaseHoldAfterStop();
            _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();
        // a rotation's synchronous start() would invalidate a deferred drain, destroying the old session's tail
        if (this._isRestartingForSessionIdChange) {
            this._drainCompressionQueueSync();
        }
        else 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._releaseHoldAfterStop();
        this._teardown();
        logger.info('stopped');
    };
    // Release the internal hold once the epoch's buffer is gone. Must run AFTER the
    // flush-then-clear above: releasing it first would let the suppressed flush ship the held
    // buffer. The async stop path clears the externally reported reason immediately while keeping
    // this internal protection until compression drains. Without this cleanup a stopped or
    // discarded recorder keeps reporting a stale $sdk_debug_replay_flush_hold_reason on later
    // captured events, because the sdkDebugProperties getter still runs after stop() (the recorder
    // is torn down, not dropped).
    // During a rotation restart, preserveLogDedup keeps _lastLoggedFlushHold: start() sets a
    // transient fresh-start hold that _restartForSessionIdChange immediately overwrites with the
    // real rotation reason. Explicit stops clear the key so a later held epoch logs again.
    LazyLoadedSessionRecording.prototype._releaseHoldAfterStop = function () {
        this._setFlushHold(undefined, { preserveLogDedup: this._isRestartingForSessionIdChange });
        this._heldEpochShipsOnUnload = false;
    };
    LazyLoadedSessionRecording.prototype.flushBeforeIdentityReset = function () {
        // a deferred stop has already torn rrweb down but still holds the tail for a later flush
        if (!this.isStarted && !this._isStoppingAfterCompression) {
            return;
        }
        this._drainCompressionQueueSync();
        this._flushBuffer();
    };
    LazyLoadedSessionRecording.prototype._restartForSessionIdChange = function (holdNextEpoch) {
        var _a, _b;
        this._isRestartingForSessionIdChange = true;
        // the new epoch's idle clock starts now, before teardown or the restart snapshot can emit
        this._lastActivityTimestamp = Date.now();
        try {
            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._lastSeenSnapshotCost = undefined;
            // the drop counts are per-session too, so the new session starts at zero
            this._unstringifiableEventsDropped = 0;
            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');
        }
        finally {
            this._isRestartingForSessionIdChange = false;
        }
        this._setFlushHold(holdNextEpoch ? 'no_interaction_since_session_rotated' : undefined);
        this._heldEpochShipsOnUnload = false;
    };
    // Keep the hold and its reported reason synchronized during normal recording transitions.
    // Terminal paths can temporarily keep only the internal hold to prevent teardown-time flushes.
    // Nothing else announces a held epoch - no flush is scheduled while held, so the log has to
    // happen here rather than on a flush that may never run.
    LazyLoadedSessionRecording.prototype._setFlushHold = function (reason, _a) {
        var _b = _a === void 0 ? {} : _a, _c = _b.preserveLogDedup, preserveLogDedup = _c === void 0 ? false : _c;
        this._holdFlushUntilInteraction = !(0, core_1.isUndefined)(reason);
        this._flushHoldReason = reason;
        if ((0, core_1.isUndefined)(reason)) {
            // a stop/discard preserves the dedup key so a rotation restart's transient
            // fresh-start hold is not logged; a release clears it so a later hold re-logs
            if (!preserveLogDedup) {
                this._lastLoggedFlushHold = undefined;
            }
            return;
        }
        // Inside a rotation restart, start() sets a transient fresh-start hold that
        // _restartForSessionIdChange overwrites with the real rotation reason on its next line
        // (with this flag already cleared). Defer the log and dedup bookkeeping to that
        // authoritative call, so a held rotation logs once, under the reason that actually sticks,
        // instead of also emitting a mislabelled fresh-start line for the same epoch.
        if (this._isRestartingForSessionIdChange) {
            return;
        }
        var holdKey = "".concat(this.sessionId, ":").concat(reason);
        if (holdKey === this._lastLoggedFlushHold) {
            return;
        }
        this._lastLoggedFlushHold = holdKey;
        logger.info("holding buffer: ".concat(reason, ". nothing is uploaded until the user interacts with the page"));
    };
    // 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._setFlushHold(undefined);
        this._heldEpochShipsOnUnload = false;
        if (this._heldBufferOverflowed) {
            this._heldBufferOverflowed = false;
            // the overflowed hold retained its data, so the release ships it; a fresh full
            // snapshot bridges the gap between the cap and this release
            this._tryTakeFullSnapshot();
        }
        this._scheduleFlushBuffer();
    };
    LazyLoadedSessionRecording.prototype.discard = function (_a) {
        var _b = _a === void 0 ? {} : _a, _c = _b.discardProducerEvents, discardProducerEvents = _c === void 0 ? false : _c;
        // Reuse the internal hold as a synchronous discard guard. rrweb teardown can emit enough
        // data to hit the size cap, but discard must never flush it.
        this._holdFlushUntilInteraction = true;
        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();
        }
        // rrweb can synchronously emit deferred stylesheet mutations while _teardown() stops it,
        // so clear on both sides of it: the first clear stops those emissions flushing this epoch's
        // data at the size cap, the second drops the emissions themselves. The hold is released
        // only afterwards — _teardown() clears the flush timer before it stops rrweb, so a flush
        // an unheld emission schedules outlives teardown and ships a discarded epoch.
        this._clearBuffer();
        this._teardown();
        this._clearBuffer();
        this._releaseHoldAfterStop();
        logger.info('discarded');
    };
    LazyLoadedSessionRecording.prototype._captureProcessedEvent = function (event, eventToSend, size, targetSessionId, targetWindowId) {
        // the request encoder stringifies the whole batch, and the request queue merges every
        // queued recording chunk into one request, so buffering an event that cannot be
        // stringified would take every chunk queued alongside it down too. Drop only this event.
        if (size === sessionrecording_utils_1.UNSTRINGIFIABLE_EVENT_SIZE) {
            this._unstringifiableEventsDropped += 1;
            logger.warn('could not stringify event - dropping it to keep the rest of the recording');
            return;
        }
        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 (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) {
        var _a;
        try {
            var eventToSend = queuedEvent.event;
            var size = void 0;
            if (queuedEvent.compressionEnabled) {
                try {
                    ;
                    (_a = compressEventSync(queuedEvent.event), eventToSend = _a.event, size = _a.size);
                }
                catch (e) {
                    logger.warn('could not process queued compression event - will use uncompressed event', e);
                }
            }
            // only size the raw event when compression did not already report a size: this drain
            // runs on unload, and a discarded estimate costs a full stringify of every event
            if ((0, core_1.isUndefined)(size)) {
                size = (0, sessionrecording_utils_1.estimateSize)(queuedEvent.event);
            }
            try {
                this._captureQueuedCompressionEvent(queuedEvent, eventToSend, size);
            }
            catch (e) {
                // the async path swallows this too, a throw here would abort the rotation restart
                logger.warn('could not capture queued compression event', e);
            }
        }
        finally {
            this._finishQueuedCompressionEvent(queuedEvent);
        }
    };
    LazyLoadedSessionRecording.prototype._drainCompressionQueueSync = function () {
        var _this = this;
        var queuedEvents = __spreadArray([], __read(this._pendingCompressionEvents), false);
        queuedEvents.forEach(function (queuedEvent) {
            try {
                _this._processQueuedCompressionEventSync(queuedEvent);
            }
            catch (e) {
                // this drain runs on unload: a throw here would skip the remaining
                // queued events and the final flush, truncating the recording
                logger.warn('could not drain queued compression event', e);
            }
        });
    };
    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.warn('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;
        // 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) {
            // 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
            (_c = this._mutationThrottler) === null || _c === void 0 ? void 0 : _c.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
            (_d = this._instance.persistence) === null || _d === void 0 ? void 0 : _d.register_once((_a = {},
                _a[constants_3.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 && ((_e = this._strategy) === null || _e === void 0 ? void 0 : _e.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);
        }
        // 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 = (_h = (_g = (_f = sessionEndingPayload === null || sessionEndingPayload === void 0 ? void 0 : sessionEndingPayload.currentSessionId) !== null && _f !== void 0 ? _f : sessionStartingPayload === null || sessionStartingPayload === void 0 ? void 0 : sessionStartingPayload.nextSessionId) !== null && _g !== void 0 ? _g : sessionIdlePayload === null || sessionIdlePayload === void 0 ? void 0 : sessionIdlePayload.sessionId) !== null && _h !== void 0 ? _h : this._sessionId;
        var targetWindowId = (_l = (_k = (_j = sessionEndingPayload === null || sessionEndingPayload === void 0 ? void 0 : sessionEndingPayload.currentWindowId) !== null && _j !== void 0 ? _j : sessionStartingPayload === null || sessionStartingPayload === void 0 ? void 0 : sessionStartingPayload.nextWindowId) !== null && _k !== void 0 ? _k : sessionIdlePayload === null || sessionIdlePayload === void 0 ? void 0 : sessionIdlePayload.windowId) !== null && _l !== void 0 ? _l : 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 = (_m = this._instance.config.session_recording.compress_events) !== null && _m !== void 0 ? _m : true;
        if (event.type === rrweb_types_1.EventType.Custom && event.data.tag === json_ld_1.JSON_LD_EVENT_TAG) {
            var href = void 0;
            try {
                href = globals_1.window ? this._maskReplayUrl(globals_1.window.location.href) : undefined;
            }
            catch (_o) {
                // A masking callback failure must not expose the original URL or interrupt recording.
            }
            event.data.href = href;
        }
        if (this._queuedCompressionEvents > 0 ||
            (compressionEnabled && shouldUseNativeAsyncSessionRecordingGzip(event))) {
            this._enqueueCompression(event, compressionEnabled, targetSessionId, targetWindowId);
            return;
        }
        var _p = compressionEnabled
            ? compressEventSync(event)
            : { event: event, size: (0, sessionrecording_utils_1.estimateSize)(event) }, eventToSend = _p.event, size = _p.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_3.SESSION_RECORDING_IS_SAMPLED] = this.sessionId,
            _a[constants_3.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.
            // oxlint-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_3.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_3.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_3.SDK_DEBUG_REPLAY_PENDING_TRIGGER_CONDITIONS] = pending, _c));
        logger.info("buffering: ".concat(reason));
    };
    LazyLoadedSessionRecording.prototype._canParkPendingBuffer = function () {
        var _a;
        // the same three conditions the sessionid manager applies to the window id, so an opt-out
        // that stops one from writing stops the other
        return (this._instance.config.persistence !== 'memory' &&
            ((_a = this._instance.persistence) === null || _a === void 0 ? void 0 : _a._disabled) !== true &&
            storage_1.sessionStore._is_supported());
    };
    /**
     * Parks the buffer for the next page in this tab. A timing gate holds the buffer for a retry
     * that an unloading page never runs, so without this the snapshots die with the page and the
     * recording opens partway through the session.
     */
    LazyLoadedSessionRecording.prototype._parkBufferForNextPage = function () {
        if (this._buffer.data.length === 0 ||
            this._buffer.size === this._lastParkedBufferSize ||
            this._buffer.size > exports.RECORDING_MAX_EVENT_SIZE ||
            !this._canParkPendingBuffer()) {
            return;
        }
        this._lastParkedBufferSize = this._buffer.size;
        storage_1.sessionStore._set(this._pendingBufferStorageKey, this._buffer);
    };
    LazyLoadedSessionRecording.prototype._restorePendingBuffer = function () {
        if (this._pendingBufferRestored || !this._canParkPendingBuffer()) {
            return;
        }
        this._pendingBufferRestored = true;
        var parked = storage_1.sessionStore._parse(this._pendingBufferStorageKey);
        storage_1.sessionStore._remove(this._pendingBufferStorageKey);
        if (!(0, core_1.isObject)(parked) ||
            !(0, core_1.isArray)(parked.data) ||
            parked.data.length === 0 ||
            !(0, core_1.isArray)(parked.sizes) ||
            parked.sizes.length !== parked.data.length ||
            !(0, core_1.isNumber)(parked.size)) {
            return;
        }
        // same epoch, same tab only: a rotation or a duplicated tab (which copies sessionStorage)
        // must not replay another epoch's snapshots
        if (parked.sessionId !== this._sessionId || parked.windowId !== this._windowId) {
            return;
        }
        if (this._buffer.data.length > 0) {
            return;
        }
        this._buffer = parked;
    };
    LazyLoadedSessionRecording.prototype._flushBuffer = function (isUnloading) {
        var _this = this;
        var _a, _b;
        if (isUnloading === void 0) { isUnloading = false; }
        // 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
            if (this._buffer.size > exports.RECORDING_MAX_EVENT_SIZE) {
                return this._clearBuffer();
            }
            if (isUnloading) {
                this._parkBufferForNextPage();
            }
            return 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) {
            // ACTIVE or SAMPLED here means the minimum duration is the only gate left: both are
            // shippable statuses that only reach this branch through isBelowMinimumDuration. Shipping
            // past it would open a recording the customer configured away, so park it instead: it
            // ships from the next page once the session is long enough, and dies with the tab if it
            // ended here
            if (isUnloading && (status === triggerMatching_1.ACTIVE || status === triggerMatching_1.SAMPLED)) {
                this._parkBufferForNextPage();
            }
            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;
                try {
                    (_a = _this._flushedSizeTracker) === null || _a === void 0 ? void 0 : _a.trackSize(snapshotBuffer.sessionId, snapshotBuffer.size);
                    _this._captureSnapshot(__assign(__assign(__assign(__assign({ $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 }, (_this._throttledMutationsDropped > 0
                        ? { $sdk_debug_replay_throttled_mutations_dropped: _this._throttledMutationsDropped }
                        : {})), (_this._oversizedMutationsDropped > 0
                        ? { $sdk_debug_replay_oversized_mutations_dropped: _this._oversizedMutationsDropped }
                        : {})), (_this._oversizedMutationBytesDropped > 0
                        ? {
                            $sdk_debug_replay_oversized_mutation_bytes_dropped: _this._oversizedMutationBytesDropped,
                        }
                        : {})), (_this._unstringifiableEventsDropped > 0
                        ? { $sdk_debug_replay_unstringifiable_events_dropped: _this._unstringifiableEventsDropped }
                        : {})));
                }
                catch (e) {
                    // one chunk that cannot be captured must not drop the chunks after it
                    logger.warn('could not capture snapshot chunk - skipping it', e);
                }
            });
            // A cancelled beforeunload or a later pagehide flush can ship a previously parked buffer.
            if (!(0, core_1.isUndefined)(this._lastParkedBufferSize)) {
                storage_1.sessionStore._remove(this._pendingBufferStorageKey);
                this._lastParkedBufferSize = undefined;
            }
            // 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: stop collecting but
        // keep what is held, so a release still ships the epoch from its start; the release
        // takes a fresh full snapshot to bridge the gap between cap and interaction
        if (this._holdFlushUntilInteraction &&
            (this._heldBufferOverflowed ||
                this._buffer.size + properties.$snapshot_bytes + additionalBytes > exports.RECORDING_MAX_EVENT_SIZE)) {
            this._heldBufferOverflowed = true;
            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];
            // During rotation the manager already owns the new session, but stop() must
            // evaluate the old buffer before start() adopts the new session's start time.
            return mostRecentSnapshot ? mostRecentSnapshot.timestamp - this._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_3.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();
            }
        }
        // Check before updating activity: rotation synchronously emits $session_ending,
        // which must use the old session's last activity, not the waking interaction.
        // Read-only checks still enforce the 24-hour cap in every idle state.
        var _b = this._sessionManager.checkAndGetSessionAndWindowId(!isUserInteraction, event.timestamp), windowId = _b.windowId, sessionId = _b.sessionId;
        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;
                }
            }
        }
        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;
            return _a = {
                    $recording_status: this.status,
                    // "active" does not mean "uploading": a held epoch keeps its buffer until the user
                    // interacts, and only this property tells a held session from a shipping one
                    $sdk_debug_replay_flush_hold_reason: this._flushHoldReason,
                    $sdk_debug_replay_internal_buffer_length: this._buffer.data.length,
                    $sdk_debug_replay_internal_buffer_size: this._buffer.size,
                    $sdk_debug_session_start: this._sessionStartTimestamp,
                    $sdk_debug_replay_flushed_size: (_b = this._flushedSizeTracker) === null || _b === void 0 ? void 0 : _b.currentTrackedSize(this.sessionId),
                    $sdk_debug_replay_rrweb_error: this._rrwebError
                },
                _a[constants_3.SDK_DEBUG_REPLAY_RRWEB_ATTACHED] = this._rrwebAttached,
                _a[constants_3.SDK_DEBUG_REPLAY_RRWEB_START_ATTEMPTED] = this._rrwebStartAttempted,
                _a;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(LazyLoadedSessionRecording.prototype, "_rrwebAttached", {
        /**
         * whether rrweb is observing the page, not merely constructed. rrweb returns its
         * stop handler synchronously even when it defers init() to a document event, so a
         * recorder that never observed anything would otherwise report itself as attached.
         */
        get: function () {
            var _a, _b, _c;
            if (!this._stopRrweb) {
                return false;
            }
            // recorder bundles older than isRecording can only report construction
            return (_c = (_b = (_a = getRRWebRecord()) === null || _a === void 0 ? void 0 : _a.isRecording) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : true;
        },
        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 {
                        // oxlint-disable-next-line typescript/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(__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), { 
            // rrweb defaults to the window load event, which waits for every image, font
            // and subframe, so a page whose load event is late or never fires records
            // nothing. A full snapshot only needs the DOM to exist, not every resource
            // loaded. A stylesheet still pending 5s after the snapshot keeps only its href,
            // so replay refetches it. Set after the spread so it is not a user option.
            recordAfter: 'DOMContentLoaded' }));
        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, {
                maskUrl: function (url) { return _this._maskReplayUrl(url); },
                attributeFilter: sessionRecordingOptions.attributeFilter,
                blockClass: sessionRecordingOptions.blockClass,
                blockSelector: sessionRecordingOptions.blockSelector,
                isRecordedElement: function (element) { return rrwebRecord.mirror.getId(element) > 0; },
                maskAllElementAttributes: sessionRecordingOptions.maskAllElementAttributes,
                maskAttributeFn: sessionRecordingOptions.maskAttributeFn,
                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