"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SessionReplay = void 0;
var tslib_1 = require("tslib");
var analytics_core_1 = require("@amplitude/analytics-core");
// Import only specific types to avoid pulling in the entire rrweb-types package
var rrweb_types_1 = require("@amplitude/rrweb-types");
var joined_config_1 = require("./config/joined-config");
var constants_1 = require("./constants");
var helpers_1 = require("./helpers");
var event_compressor_1 = require("./events/event-compressor");
var events_manager_1 = require("./events/events-manager");
var multi_manager_1 = require("./events/multi-manager");
var click_1 = require("./hooks/click");
var scroll_1 = require("./hooks/scroll");
var identifiers_1 = require("./identifiers");
var logger_1 = require("./logger");
var targeting_manager_1 = require("./targeting/targeting-manager");
var version_1 = require("./version");
var url_tracking_plugin_1 = require("./plugins/url-tracking-plugin");
var SessionReplay = /** @class */ (function () {
    function SessionReplay() {
        var _this = this;
        this.name = '@amplitude/session-replay-browser';
        this.recordCancelCallback = null;
        this.eventCount = 0;
        this.sessionTargetingMatch = false;
        // Visible for testing only
        this.pageLeaveFns = [];
        // Cache the dynamically imported record function
        this.recordFunction = null;
        /** Cleanup for URL change listener used to re-evaluate targeting on SPA route changes */
        this.urlChangeCleanup = null;
        /** Monotonic counter to ignore stale URL-change targeting results */
        this.latestUrlChangeTargetingEvaluationId = 0;
        this.teardownEventListeners = function (teardown) {
            var globalScope = (0, analytics_core_1.getGlobalScope)();
            if (globalScope) {
                globalScope.removeEventListener('blur', _this.blurListener);
                globalScope.removeEventListener('focus', _this.focusListener);
                !teardown && globalScope.addEventListener('blur', _this.blurListener);
                !teardown && globalScope.addEventListener('focus', _this.focusListener);
                // prefer pagehide to unload events, this is the standard going forward. it is not
                // 100% reliable, but is bfcache-compatible.
                if (globalScope.self && 'onpagehide' in globalScope.self) {
                    globalScope.removeEventListener('pagehide', _this.pageLeaveListener);
                    !teardown && globalScope.addEventListener('pagehide', _this.pageLeaveListener);
                }
                else {
                    // this has performance implications, but is the only way we can reliably send events
                    // in browser that don't support pagehide.
                    globalScope.removeEventListener('beforeunload', _this.pageLeaveListener);
                    !teardown && globalScope.addEventListener('beforeunload', _this.pageLeaveListener);
                }
            }
        };
        this.blurListener = function () {
            _this.sendEvents();
        };
        this.focusListener = function () {
            // Restart recording on focus to ensure that when user
            // switches tabs, we take a full snapshot
            void _this.recordEvents(false);
        };
        /**
         * This is an instance member so that if init is called multiple times
         * it doesn't add another listener to the page leave event. This is to
         * prevent duplicate listener actions from firing.
         */
        this.pageLeaveListener = function (e) {
            _this.pageLeaveFns.forEach(function (fn) {
                fn(e);
            });
        };
        this.evaluateTargetingAndCapture = function (targetingParams, isInit, forceRestart, forceTargetingReevaluation) {
            if (isInit === void 0) { isInit = false; }
            if (forceRestart === void 0) { forceRestart = false; }
            if (forceTargetingReevaluation === void 0) { forceTargetingReevaluation = false; }
            return tslib_1.__awaiter(_this, void 0, void 0, function () {
                var targetingConfig, shouldReEvaluate, urlChangeEvaluationId, eventForTargeting, pageUrl, pageForTargeting, targetingMatch;
                var _a, _b, _c, _d, _e, _f;
                return tslib_1.__generator(this, function (_g) {
                    switch (_g.label) {
                        case 0:
                            if (!this.identifiers || !this.identifiers.sessionId || !this.config) {
                                if (this.identifiers && !this.identifiers.sessionId) {
                                    this.loggerProvider.log('Session ID has not been set yet, cannot evaluate targeting for Session Replay.');
                                }
                                else {
                                    this.loggerProvider.warn('Session replay init has not been called, cannot evaluate targeting.');
                                }
                                return [2 /*return*/];
                            }
                            // Handle cases where there's no targeting config
                            if (!this.config.targetingConfig) {
                                if (isInit) {
                                    this.loggerProvider.log('Targeting config has not been set yet, cannot evaluate targeting.');
                                }
                                else {
                                    this.loggerProvider.log('No targeting config set, skipping initialization/recording for event.');
                                    return [2 /*return*/];
                                }
                            }
                            // Store targeting parameters for use in getShouldRecord
                            this.lastTargetingParams = targetingParams;
                            targetingConfig = this.config.targetingConfig;
                            shouldReEvaluate = targetingConfig && !this.sessionTargetingMatch;
                            if (!shouldReEvaluate) return [3 /*break*/, 2];
                            urlChangeEvaluationId = forceTargetingReevaluation ? this.latestUrlChangeTargetingEvaluationId : undefined;
                            eventForTargeting = targetingParams.event;
                            if (eventForTargeting &&
                                Object.values(analytics_core_1.SpecialEventType).includes(eventForTargeting.event_type)) {
                                eventForTargeting = undefined;
                            }
                            pageUrl = (_e = (_b = (_a = targetingParams.page) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : (_d = (_c = (0, analytics_core_1.getGlobalScope)()) === null || _c === void 0 ? void 0 : _c.location) === null || _d === void 0 ? void 0 : _d.href) !== null && _e !== void 0 ? _e : '';
                            pageForTargeting = (_f = targetingParams.page) !== null && _f !== void 0 ? _f : (pageUrl !== '' ? { url: pageUrl } : undefined);
                            return [4 /*yield*/, (0, targeting_manager_1.evaluateTargetingAndStore)({
                                    sessionId: this.identifiers.sessionId,
                                    targetingConfig: targetingConfig,
                                    loggerProvider: this.loggerProvider,
                                    apiKey: this.config.apiKey,
                                    targetingParams: {
                                        userProperties: targetingParams.userProperties,
                                        event: eventForTargeting,
                                        page: pageForTargeting,
                                    },
                                    urlChange: forceTargetingReevaluation,
                                })];
                        case 1:
                            targetingMatch = _g.sent();
                            if (forceTargetingReevaluation &&
                                urlChangeEvaluationId !== undefined &&
                                urlChangeEvaluationId !== this.latestUrlChangeTargetingEvaluationId) {
                                this.loggerProvider.debug("Ignoring stale URL-change targeting result #".concat(urlChangeEvaluationId, "; latest is #").concat(this.latestUrlChangeTargetingEvaluationId, "."));
                                return [2 /*return*/];
                            }
                            // Keep targeting match monotonic within a session: once true, always true.
                            // This avoids races where an older in-flight evaluation resolves false after
                            // a newer evaluation already resolved true.
                            this.sessionTargetingMatch = this.sessionTargetingMatch || targetingMatch;
                            this.loggerProvider.debug(JSON.stringify({
                                name: 'targeted replay capture config',
                                sessionTargetingMatch: this.sessionTargetingMatch,
                                event: eventForTargeting,
                                targetingParams: targetingParams,
                            }, null, 2));
                            _g.label = 2;
                        case 2:
                            if (!isInit) return [3 /*break*/, 3];
                            void this.initialize(true);
                            return [3 /*break*/, 5];
                        case 3:
                            if (!(forceRestart || !this.recordCancelCallback)) return [3 /*break*/, 5];
                            this.loggerProvider.log('Recording events for session due to forceRestart or no ongoing recording.');
                            return [4 /*yield*/, this.recordEvents()];
                        case 4:
                            _g.sent();
                            _g.label = 5;
                        case 5: return [2 /*return*/];
                    }
                });
            });
        };
        this.addCustomRRWebEvent = function (eventName, eventData, addStorageInfo) {
            if (eventData === void 0) { eventData = {}; }
            if (addStorageInfo === void 0) { addStorageInfo = true; }
            return tslib_1.__awaiter(_this, void 0, void 0, function () {
                var debugInfo, config, storageSizeData, e_1;
                return tslib_1.__generator(this, function (_a) {
                    switch (_a.label) {
                        case 0:
                            _a.trys.push([0, 3, , 4]);
                            debugInfo = undefined;
                            config = this.config;
                            if (!(config && eventName !== constants_1.CustomRRwebEvent.METADATA)) return [3 /*break*/, 2];
                            debugInfo = {
                                config: (0, helpers_1.getDebugConfig)(config),
                                version: version_1.VERSION,
                            };
                            if (!addStorageInfo) return [3 /*break*/, 2];
                            return [4 /*yield*/, (0, helpers_1.getStorageSize)()];
                        case 1:
                            storageSizeData = _a.sent();
                            debugInfo = tslib_1.__assign(tslib_1.__assign({}, storageSizeData), debugInfo);
                            _a.label = 2;
                        case 2:
                            // Check first to ensure we are recording
                            if (this.recordCancelCallback && this.recordFunction) {
                                this.recordFunction.addCustomEvent(eventName, tslib_1.__assign(tslib_1.__assign({}, eventData), debugInfo));
                            }
                            else {
                                this.loggerProvider.debug("Not able to add custom replay capture event ".concat(eventName, " due to no ongoing recording."));
                            }
                            return [3 /*break*/, 4];
                        case 3:
                            e_1 = _a.sent();
                            this.loggerProvider.debug('Error while adding custom replay capture event: ', e_1);
                            return [3 /*break*/, 4];
                        case 4: return [2 /*return*/];
                    }
                });
            });
        };
        this.stopRecordingEvents = function () {
            var _a;
            try {
                _this.loggerProvider.log('Session Replay capture stopping.');
                _this.recordCancelCallback && _this.recordCancelCallback();
                _this.recordCancelCallback = null;
                (_a = _this.networkObservers) === null || _a === void 0 ? void 0 : _a.stop();
            }
            catch (error) {
                var typedError = error;
                _this.loggerProvider.warn("Error occurred while stopping replay capture: ".concat(typedError.toString()));
            }
        };
        this.loggerProvider = new logger_1.SafeLoggerProvider(new analytics_core_1.Logger());
    }
    SessionReplay.prototype.init = function (apiKey, options) {
        return (0, analytics_core_1.returnWrapper)(this._init(apiKey, options));
    };
    /**
     * Subscribes to SPA URL changes via the URL tracking plugin and re-evaluates targeting so we
     * can start/stop recording when the user navigates to a page that matches or no longer matches.
     */
    SessionReplay.prototype.setupUrlChangeListenerForTargeting = function () {
        var _this = this;
        var _a;
        // If init() runs multiple times, remove the previous URL-change subscription first
        // so we don't leak callbacks and trigger duplicate targeting evaluations.
        (_a = this.urlChangeCleanup) === null || _a === void 0 ? void 0 : _a.call(this);
        var globalScope = (0, analytics_core_1.getGlobalScope)();
        if (!(globalScope === null || globalScope === void 0 ? void 0 : globalScope.location)) {
            return;
        }
        var onUrlChange = function (href) {
            var evaluationId = ++_this.latestUrlChangeTargetingEvaluationId;
            void _this.evaluateTargetingAndCapture({
                userProperties: {},
                event: undefined,
                page: { url: href },
            }, false, false, true);
            _this.loggerProvider.debug("Queued URL-change targeting re-evaluation #".concat(evaluationId, " for ").concat(href, "."));
        };
        var unsubscribe = url_tracking_plugin_1.subscribeToUrlChanges(globalScope, onUrlChange);
        this.urlChangeCleanup = function () {
            unsubscribe();
            _this.urlChangeCleanup = null;
        };
    };
    SessionReplay.prototype.getCurrentPageForTargeting = function () {
        var _a, _b;
        var currentUrl = (_b = (_a = (0, analytics_core_1.getGlobalScope)()) === null || _a === void 0 ? void 0 : _a.location) === null || _b === void 0 ? void 0 : _b.href;
        return currentUrl != null ? { url: currentUrl } : undefined;
    };
    SessionReplay.prototype._init = function (apiKey, options) {
        var _a, _b, _c, _d, _e, _f, _g, _h;
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var _j, _k, joinedConfig, localConfig, remoteConfig, scrollWatcher, managers, storeType, rrwebEventManager, error_1, typedError, payloadBatcher, interactionEventManager, error_2, typedError, workerScript, globalScope, compressionScript, messenger;
            return tslib_1.__generator(this, function (_l) {
                switch (_l.label) {
                    case 0:
                        // Re-init should always tear down any previous URL-change subscription, even when the
                        // next config has no targeting config and we don't subscribe again.
                        (_a = this.urlChangeCleanup) === null || _a === void 0 ? void 0 : _a.call(this);
                        this.loggerProvider = new logger_1.SafeLoggerProvider(options.loggerProvider || new analytics_core_1.Logger());
                        Object.prototype.hasOwnProperty.call(options, 'logLevel') &&
                            this.loggerProvider.enable(options.logLevel);
                        this.identifiers = new identifiers_1.SessionIdentifiers({ sessionId: options.sessionId, deviceId: options.deviceId });
                        _j = this;
                        return [4 /*yield*/, (0, joined_config_1.createSessionReplayJoinedConfigGenerator)(apiKey, options)];
                    case 1:
                        _j.joinedConfigGenerator = _l.sent();
                        return [4 /*yield*/, this.joinedConfigGenerator.generateJoinedConfig()];
                    case 2:
                        _k = _l.sent(), joinedConfig = _k.joinedConfig, localConfig = _k.localConfig, remoteConfig = _k.remoteConfig;
                        this.config = joinedConfig;
                        this.setMetadata(options.sessionId, joinedConfig, localConfig, remoteConfig, (_b = options.version) === null || _b === void 0 ? void 0 : _b.version, version_1.VERSION, (_c = options.version) === null || _c === void 0 ? void 0 : _c.type);
                        if (options.sessionId && ((_d = this.config.interactionConfig) === null || _d === void 0 ? void 0 : _d.enabled)) {
                            scrollWatcher = scroll_1.ScrollWatcher.default({
                                sessionId: options.sessionId,
                                type: 'interaction',
                            }, this.config);
                            this.pageLeaveFns = [scrollWatcher.send(this.getDeviceId.bind(this)).bind(scrollWatcher)];
                            this.scrollHook = scrollWatcher.hook.bind(scrollWatcher);
                            this.clickHandler = new click_1.ClickHandler(this.loggerProvider, scrollWatcher);
                        }
                        managers = [];
                        storeType = this.config.storeType;
                        if (storeType === 'idb' && !((_e = (0, analytics_core_1.getGlobalScope)()) === null || _e === void 0 ? void 0 : _e.indexedDB)) {
                            storeType = 'memory';
                            this.loggerProvider.warn('Could not use preferred indexedDB storage, reverting to in memory option.');
                        }
                        this.loggerProvider.log("Using ".concat(storeType, " for event storage."));
                        _l.label = 3;
                    case 3:
                        _l.trys.push([3, 5, , 6]);
                        return [4 /*yield*/, (0, events_manager_1.createEventsManager)({
                                config: this.config,
                                type: 'replay',
                                storeType: storeType,
                            })];
                    case 4:
                        rrwebEventManager = _l.sent();
                        managers.push({ name: 'replay', manager: rrwebEventManager });
                        return [3 /*break*/, 6];
                    case 5:
                        error_1 = _l.sent();
                        typedError = error_1;
                        this.loggerProvider.warn("Error occurred while creating replay events manager: ".concat(typedError.toString()));
                        return [3 /*break*/, 6];
                    case 6:
                        if (!((_f = this.config.interactionConfig) === null || _f === void 0 ? void 0 : _f.enabled)) return [3 /*break*/, 10];
                        payloadBatcher = this.config.interactionConfig.batch ? click_1.clickBatcher : click_1.clickNonBatcher;
                        _l.label = 7;
                    case 7:
                        _l.trys.push([7, 9, , 10]);
                        return [4 /*yield*/, (0, events_manager_1.createEventsManager)({
                                config: this.config,
                                type: 'interaction',
                                minInterval: (_g = this.config.interactionConfig.trackEveryNms) !== null && _g !== void 0 ? _g : constants_1.INTERACTION_MIN_INTERVAL,
                                maxInterval: constants_1.INTERACTION_MAX_INTERVAL,
                                payloadBatcher: payloadBatcher,
                                storeType: storeType,
                            })];
                    case 8:
                        interactionEventManager = _l.sent();
                        managers.push({ name: 'interaction', manager: interactionEventManager });
                        return [3 /*break*/, 10];
                    case 9:
                        error_2 = _l.sent();
                        typedError = error_2;
                        this.loggerProvider.warn("Error occurred while creating interaction events manager: ".concat(typedError.toString()));
                        return [3 /*break*/, 10];
                    case 10:
                        this.eventsManager = new (multi_manager_1.MultiEventManager.bind.apply(multi_manager_1.MultiEventManager, tslib_1.__spreadArray([void 0], tslib_1.__read(managers), false)))();
                        // To prevent too many threads.
                        if (this.eventCompressor) {
                            this.eventCompressor.terminate();
                        }
                        workerScript = undefined;
                        globalScope = (0, analytics_core_1.getGlobalScope)();
                        if (!(this.config.useWebWorker && globalScope && globalScope.Worker)) return [3 /*break*/, 12];
                        return [4 /*yield*/, Promise.resolve().then(function () { return tslib_1.__importStar(require('./worker')); })];
                    case 11:
                        compressionScript = (_l.sent()).compressionScript;
                        workerScript = compressionScript;
                        _l.label = 12;
                    case 12:
                        this.eventCompressor = new event_compressor_1.EventCompressor(this.eventsManager, this.config, this.getDeviceId(), workerScript);
                        return [4 /*yield*/, this.initializeNetworkObservers()];
                    case 13:
                        _l.sent();
                        // Enable background capture when this page is opened by the Amplitude app
                        // (window.opener exists). Uses the shared messenger singleton so that if
                        // autocapture is also loaded, both share a single messenger and script load.
                        if ((_h = (0, analytics_core_1.getGlobalScope)()) === null || _h === void 0 ? void 0 : _h.opener) {
                            messenger = (0, analytics_core_1.getOrCreateWindowMessenger)();
                            (0, analytics_core_1.enableBackgroundCapture)(messenger);
                            messenger.setup(tslib_1.__assign({ logger: this.loggerProvider }, (this.config.serverZone && { endpoint: analytics_core_1.AMPLITUDE_ORIGINS_MAP[this.config.serverZone] })));
                        }
                        this.loggerProvider.log('Installing @amplitude/session-replay-browser.');
                        this.teardownEventListeners(false);
                        return [4 /*yield*/, this.evaluateTargetingAndCapture({ userProperties: options.userProperties, page: this.getCurrentPageForTargeting() }, true)];
                    case 14:
                        _l.sent();
                        if (this.config.targetingConfig) {
                            this.setupUrlChangeListenerForTargeting();
                        }
                        return [2 /*return*/];
                }
            });
        });
    };
    SessionReplay.prototype.setSessionId = function (sessionId, deviceId) {
        return (0, analytics_core_1.returnWrapper)(this.asyncSetSessionId(sessionId, deviceId));
    };
    SessionReplay.prototype.asyncSetSessionId = function (sessionId, deviceId, options) {
        var _a;
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var previousSessionId, deviceIdForReplayId, joinedConfig;
            return tslib_1.__generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        // Invalidate any in-flight URL-change re-evaluations from the previous session.
                        this.latestUrlChangeTargetingEvaluationId++;
                        this.sessionTargetingMatch = false;
                        this.lastShouldRecordDecision = undefined; // Reset targeting decision for new session
                        previousSessionId = this.identifiers && this.identifiers.sessionId;
                        if (previousSessionId) {
                            this.sendEvents(previousSessionId);
                        }
                        deviceIdForReplayId = deviceId || this.getDeviceId();
                        this.identifiers = new identifiers_1.SessionIdentifiers({
                            sessionId: sessionId,
                            deviceId: deviceIdForReplayId,
                        });
                        if (!(this.joinedConfigGenerator && previousSessionId)) return [3 /*break*/, 2];
                        return [4 /*yield*/, this.joinedConfigGenerator.generateJoinedConfig()];
                    case 1:
                        joinedConfig = (_b.sent()).joinedConfig;
                        this.config = joinedConfig;
                        _b.label = 2;
                    case 2:
                        if (!((_a = this.config) === null || _a === void 0 ? void 0 : _a.targetingConfig)) return [3 /*break*/, 4];
                        return [4 /*yield*/, this.evaluateTargetingAndCapture({ userProperties: options === null || options === void 0 ? void 0 : options.userProperties, page: this.getCurrentPageForTargeting() }, false, true)];
                    case 3:
                        _b.sent();
                        return [3 /*break*/, 6];
                    case 4: return [4 /*yield*/, this.recordEvents()];
                    case 5:
                        _b.sent();
                        _b.label = 6;
                    case 6: return [2 /*return*/];
                }
            });
        });
    };
    SessionReplay.prototype.getSessionReplayProperties = function () {
        var _a;
        var config = this.config;
        var identifiers = this.identifiers;
        if (!config || !identifiers) {
            this.loggerProvider.warn('Session replay init has not been called, cannot get session replay properties.');
            return {};
        }
        var shouldRecord = this.getShouldRecord();
        var eventProperties = {};
        if (shouldRecord) {
            eventProperties = (_a = {},
                _a[constants_1.DEFAULT_SESSION_REPLAY_PROPERTY] = identifiers.sessionReplayId ? identifiers.sessionReplayId : null,
                _a);
            if (config.debugMode) {
                eventProperties[constants_1.SESSION_REPLAY_DEBUG_PROPERTY] = JSON.stringify({
                    appHash: (0, analytics_core_1.generateHashCode)(config.apiKey).toString(),
                });
            }
        }
        void this.addCustomRRWebEvent(constants_1.CustomRRwebEvent.GET_SR_PROPS, {
            shouldRecord: shouldRecord,
            eventProperties: eventProperties,
        }, this.eventCount === 10);
        if (this.eventCount === 10) {
            this.eventCount = 0;
        }
        this.eventCount++;
        return eventProperties;
    };
    SessionReplay.prototype.sendEvents = function (sessionId) {
        var _a;
        var sessionIdToSend = sessionId || ((_a = this.identifiers) === null || _a === void 0 ? void 0 : _a.sessionId);
        var deviceId = this.getDeviceId();
        this.eventsManager &&
            sessionIdToSend &&
            deviceId &&
            this.eventsManager.sendCurrentSequenceEvents({ sessionId: sessionIdToSend, deviceId: deviceId });
    };
    SessionReplay.prototype.initialize = function (shouldSendStoredEvents) {
        var _a;
        if (shouldSendStoredEvents === void 0) { shouldSendStoredEvents = false; }
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var deviceId;
            return tslib_1.__generator(this, function (_b) {
                if (!((_a = this.identifiers) === null || _a === void 0 ? void 0 : _a.sessionId)) {
                    this.loggerProvider.log("Session is not being recorded due to lack of session id.");
                    return [2 /*return*/, Promise.resolve()];
                }
                deviceId = this.getDeviceId();
                if (!deviceId) {
                    this.loggerProvider.log("Session is not being recorded due to lack of device id.");
                    return [2 /*return*/, Promise.resolve()];
                }
                this.eventsManager && shouldSendStoredEvents && void this.eventsManager.sendStoredEvents({ deviceId: deviceId });
                return [2 /*return*/, this.recordEvents()];
            });
        });
    };
    SessionReplay.prototype.shouldOptOut = function () {
        var _a, _b;
        var identityStoreOptOut;
        if ((_a = this.config) === null || _a === void 0 ? void 0 : _a.instanceName) {
            var identityStore = (0, analytics_core_1.getAnalyticsConnector)(this.config.instanceName).identityStore;
            identityStoreOptOut = identityStore.getIdentity().optOut;
        }
        return identityStoreOptOut !== undefined ? identityStoreOptOut : (_b = this.config) === null || _b === void 0 ? void 0 : _b.optOut;
    };
    SessionReplay.prototype.getShouldRecord = function () {
        if (!this.identifiers || !this.config || !this.identifiers.sessionId) {
            this.loggerProvider.warn("Session is not being recorded due to lack of config, please call sessionReplay.init.");
            return false;
        }
        if (!this.config.captureEnabled) {
            this.loggerProvider.log("Session ".concat(this.identifiers.sessionId, " not being captured due to capture being disabled for project or because the remote config could not be fetched."));
            return false;
        }
        if (this.shouldOptOut()) {
            this.loggerProvider.log("Opting session ".concat(this.identifiers.sessionId, " out of recording due to optOut config."));
            return false;
        }
        var shouldRecord = false;
        var message = '';
        var matched = false;
        // If targetingConfig exists, we'll use the sessionTargetingMatch to determine whether to record
        // Otherwise, we'll evaluate the session against the overall sample rate
        if (this.config.targetingConfig) {
            if (!this.sessionTargetingMatch) {
                message = "Not capturing replays for session ".concat(this.identifiers.sessionId, " due to not matching targeting conditions.");
                this.loggerProvider.log(message);
                shouldRecord = false;
                matched = false;
            }
            else {
                message = "Capturing replays for session ".concat(this.identifiers.sessionId, " due to matching targeting conditions.");
                this.loggerProvider.log(message);
                shouldRecord = true;
                matched = true;
            }
        }
        else {
            var isInSample = (0, analytics_core_1.isTimestampInSample)(this.identifiers.sessionId, this.config.sampleRate);
            if (!isInSample) {
                message = "Opting session ".concat(this.identifiers.sessionId, " out of recording due to sample rate.");
                this.loggerProvider.log(message);
                shouldRecord = false;
                matched = false;
            }
            else {
                shouldRecord = true;
                matched = true;
            }
        }
        // Only send custom rrweb event for targeting decision when the decision changes
        if (this.lastShouldRecordDecision !== shouldRecord && this.config.targetingConfig) {
            void this.addCustomRRWebEvent(constants_1.CustomRRwebEvent.TARGETING_DECISION, {
                message: message,
                sessionId: this.identifiers.sessionId,
                matched: matched,
                targetingParams: this.lastTargetingParams,
            });
            this.lastShouldRecordDecision = shouldRecord;
        }
        return shouldRecord;
    };
    SessionReplay.prototype.getBlockSelectors = function () {
        var _a, _b, _c;
        // For some reason, this defaults to empty array ([]) if undefined in the compiled script.
        // Empty arrays cause errors when being evaluated in Safari.
        // Force the selector to be undefined if it's an empty array.
        var blockSelector = (_c = (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.privacyConfig) === null || _b === void 0 ? void 0 : _b.blockSelector) !== null && _c !== void 0 ? _c : [];
        if (blockSelector.length === 0) {
            return undefined;
        }
        return blockSelector;
    };
    SessionReplay.prototype.getMaskTextSelectors = function () {
        var _a, _b, _c, _d;
        if (((_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.privacyConfig) === null || _b === void 0 ? void 0 : _b.defaultMaskLevel) === 'conservative') {
            return '*';
        }
        var maskSelector = (_d = (_c = this.config) === null || _c === void 0 ? void 0 : _c.privacyConfig) === null || _d === void 0 ? void 0 : _d.maskSelector;
        if (!maskSelector) {
            return;
        }
        return maskSelector;
    };
    SessionReplay.prototype.getRecordingPlugins = function (loggingConfig) {
        var _a, _b, _c, _d, _e, _f;
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var plugins, urlTrackingPlugin, getRecordConsolePlugin, error_3;
            return tslib_1.__generator(this, function (_g) {
                switch (_g.label) {
                    case 0:
                        plugins = [];
                        // Add URL tracking plugin
                        try {
                            urlTrackingPlugin = (0, url_tracking_plugin_1.createUrlTrackingPlugin)({
                                ugcFilterRules: ((_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.interactionConfig) === null || _b === void 0 ? void 0 : _b.ugcFilterRules) || [],
                                enablePolling: ((_c = this.config) === null || _c === void 0 ? void 0 : _c.enableUrlChangePolling) || false,
                                pollingInterval: (_d = this.config) === null || _d === void 0 ? void 0 : _d.urlChangePollingInterval,
                                captureDocumentTitle: (_e = this.config) === null || _e === void 0 ? void 0 : _e.captureDocumentTitle,
                            });
                            plugins.push(urlTrackingPlugin);
                        }
                        catch (error) {
                            this.loggerProvider.warn('Failed to create URL tracking plugin:', error);
                        }
                        if (!((_f = loggingConfig === null || loggingConfig === void 0 ? void 0 : loggingConfig.console) === null || _f === void 0 ? void 0 : _f.enabled)) return [3 /*break*/, 4];
                        _g.label = 1;
                    case 1:
                        _g.trys.push([1, 3, , 4]);
                        return [4 /*yield*/, Promise.resolve().then(function () { return tslib_1.__importStar(require('@amplitude/rrweb-plugin-console-record')); })];
                    case 2:
                        getRecordConsolePlugin = (_g.sent()).getRecordConsolePlugin;
                        plugins.push(getRecordConsolePlugin({ level: loggingConfig.console.levels }));
                        return [3 /*break*/, 4];
                    case 3:
                        error_3 = _g.sent();
                        this.loggerProvider.warn('Failed to load console plugin:', error_3);
                        return [3 /*break*/, 4];
                    case 4: return [2 /*return*/, plugins.length > 0 ? plugins : undefined];
                }
            });
        });
    };
    SessionReplay.prototype.getRecordFunction = function () {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var record, error_4;
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (this.recordFunction) {
                            return [2 /*return*/, this.recordFunction];
                        }
                        _a.label = 1;
                    case 1:
                        _a.trys.push([1, 3, , 4]);
                        return [4 /*yield*/, Promise.resolve().then(function () { return tslib_1.__importStar(require('@amplitude/rrweb-record')); })];
                    case 2:
                        record = (_a.sent()).record;
                        this.recordFunction = record;
                        return [2 /*return*/, record];
                    case 3:
                        error_4 = _a.sent();
                        this.loggerProvider.warn('Failed to load rrweb-record module:', error_4);
                        return [2 /*return*/, null];
                    case 4: return [2 /*return*/];
                }
            });
        });
    };
    SessionReplay.prototype.recordEvents = function (shouldLogMetadata) {
        var _a, _b, _c, _d, _e, _f, _g, _h;
        if (shouldLogMetadata === void 0) { shouldLogMetadata = true; }
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var config, shouldRecord, sessionId, recordFunction, networkLoggingConfig, trackUrl, ignoredUrls, privacyConfig, interactionConfig, loggingConfig, hooks, ugcFilterRules, _j, _k, error_5;
            var _l;
            var _this = this;
            return tslib_1.__generator(this, function (_m) {
                switch (_m.label) {
                    case 0:
                        config = this.config;
                        shouldRecord = this.getShouldRecord();
                        sessionId = (_a = this.identifiers) === null || _a === void 0 ? void 0 : _a.sessionId;
                        if (!shouldRecord || !sessionId || !config) {
                            return [2 /*return*/];
                        }
                        this.stopRecordingEvents();
                        return [4 /*yield*/, this.getRecordFunction()];
                    case 1:
                        recordFunction = _m.sent();
                        // May be undefined if cannot import rrweb-record
                        if (!recordFunction) {
                            return [2 /*return*/];
                        }
                        return [4 /*yield*/, this.initializeNetworkObservers()];
                    case 2:
                        _m.sent();
                        networkLoggingConfig = (_b = config.loggingConfig) === null || _b === void 0 ? void 0 : _b.network;
                        trackUrl = (0, helpers_1.getServerUrl)(config.serverZone, config.trackServerUrl);
                        ignoredUrls = [constants_1.SESSION_REPLAY_SERVER_URL, constants_1.SESSION_REPLAY_EU_URL, constants_1.SESSION_REPLAY_STAGING_URL, trackUrl];
                        (_c = this.networkObservers) === null || _c === void 0 ? void 0 : _c.start(function (event) {
                            if (ignoredUrls.some(function (url) { return event.url.startsWith(url); }))
                                return;
                            void _this.addCustomRRWebEvent(constants_1.CustomRRwebEvent.FETCH_REQUEST, event);
                        }, networkLoggingConfig);
                        privacyConfig = config.privacyConfig, interactionConfig = config.interactionConfig, loggingConfig = config.loggingConfig;
                        hooks = (interactionConfig === null || interactionConfig === void 0 ? void 0 : interactionConfig.enabled)
                            ? {
                                mouseInteraction: this.eventsManager &&
                                    ((_d = this.clickHandler) === null || _d === void 0 ? void 0 : _d.createHook({
                                        eventsManager: this.eventsManager,
                                        sessionId: sessionId,
                                        deviceIdFn: this.getDeviceId.bind(this),
                                        mirror: recordFunction.mirror,
                                        ugcFilterRules: (_e = interactionConfig.ugcFilterRules) !== null && _e !== void 0 ? _e : [],
                                        performanceOptions: (_f = config.performanceConfig) === null || _f === void 0 ? void 0 : _f.interaction,
                                    })),
                                scroll: this.scrollHook,
                            }
                            : {};
                        ugcFilterRules = (interactionConfig === null || interactionConfig === void 0 ? void 0 : interactionConfig.enabled) && interactionConfig.ugcFilterRules ? interactionConfig.ugcFilterRules : [];
                        this.loggerProvider.log("Session Replay capture beginning for ".concat(sessionId, "."));
                        _m.label = 3;
                    case 3:
                        _m.trys.push([3, 5, , 6]);
                        _j = this;
                        _k = recordFunction;
                        _l = {
                            emit: function (event) {
                                if (_this.shouldOptOut()) {
                                    _this.loggerProvider.log("Opting session ".concat(sessionId, " out of recording due to optOut config."));
                                    _this.stopRecordingEvents();
                                    _this.sendEvents();
                                    return;
                                }
                                if (event.type === rrweb_types_1.EventType.Meta) {
                                    event.data.href = (0, helpers_1.getPageUrl)(event.data.href, ugcFilterRules);
                                }
                                if (_this.eventCompressor) {
                                    // Schedule processing during idle time if the browser supports requestIdleCallback
                                    _this.eventCompressor.enqueueEvent(event, sessionId);
                                }
                            },
                            inlineStylesheet: config.shouldInlineStylesheet,
                            hooks: hooks,
                            maskAllInputs: true,
                            maskTextClass: constants_1.MASK_TEXT_CLASS,
                            blockClass: constants_1.BLOCK_CLASS,
                            blockSelector: this.getBlockSelectors(),
                            applyBackgroundColorToBlockedElements: config.applyBackgroundColorToBlockedElements,
                            maskInputFn: (0, helpers_1.maskFn)('input', privacyConfig),
                            maskTextFn: (0, helpers_1.maskFn)('text', privacyConfig),
                            maskTextSelector: this.getMaskTextSelectors(),
                            recordCanvas: false,
                            slimDOMOptions: {
                                script: (_g = config.omitElementTags) === null || _g === void 0 ? void 0 : _g.script,
                                comment: (_h = config.omitElementTags) === null || _h === void 0 ? void 0 : _h.comment,
                            },
                            errorHandler: function (error) {
                                var typedError = error;
                                // styled-components relies on this error being thrown and bubbled up, rrweb is otherwise suppressing it
                                if (typedError.message.includes('insertRule') && typedError.message.includes('CSSStyleSheet')) {
                                    throw typedError;
                                }
                                // rrweb does monkey patching on certain window functions such as CSSStyleSheet.proptype.insertRule,
                                // and errors from external clients calling these functions can get suppressed. Styled components depend
                                // on these errors being re-thrown.
                                if (typedError._external_) {
                                    throw typedError;
                                }
                                _this.loggerProvider.warn('Error while capturing replay: ', typedError.toString());
                                // Return true so that we don't clutter user's consoles with internal rrweb errors
                                return true;
                            }
                        };
                        return [4 /*yield*/, this.getRecordingPlugins(loggingConfig)];
                    case 4:
                        _j.recordCancelCallback = _k.apply(void 0, [(_l.plugins = _m.sent(),
                                _l)]);
                        void this.addCustomRRWebEvent(constants_1.CustomRRwebEvent.DEBUG_INFO);
                        if (shouldLogMetadata) {
                            void this.addCustomRRWebEvent(constants_1.CustomRRwebEvent.METADATA, this.metadata);
                        }
                        return [3 /*break*/, 6];
                    case 5:
                        error_5 = _m.sent();
                        this.loggerProvider.warn('Failed to initialize session replay:', error_5);
                        return [3 /*break*/, 6];
                    case 6: return [2 /*return*/];
                }
            });
        });
    };
    SessionReplay.prototype.getDeviceId = function () {
        var _a;
        return (_a = this.identifiers) === null || _a === void 0 ? void 0 : _a.deviceId;
    };
    SessionReplay.prototype.getSessionId = function () {
        var _a;
        return (_a = this.identifiers) === null || _a === void 0 ? void 0 : _a.sessionId;
    };
    SessionReplay.prototype.flush = function (useRetry) {
        var _a;
        if (useRetry === void 0) { useRetry = false; }
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            return tslib_1.__generator(this, function (_b) {
                return [2 /*return*/, (_a = this.eventsManager) === null || _a === void 0 ? void 0 : _a.flush(useRetry)];
            });
        });
    };
    SessionReplay.prototype.shutdown = function () {
        var _a;
        (_a = this.urlChangeCleanup) === null || _a === void 0 ? void 0 : _a.call(this);
        this.teardownEventListeners(true);
        this.stopRecordingEvents();
        this.sendEvents();
    };
    SessionReplay.prototype.mapSDKType = function (sdkType) {
        if (sdkType === 'plugin') {
            return '@amplitude/plugin-session-replay-browser';
        }
        if (sdkType === 'segment') {
            return '@amplitude/segment-session-replay-plugin';
        }
        return null;
    };
    SessionReplay.prototype.setMetadata = function (sessionId, joinedConfig, localConfig, remoteConfig, replaySDKVersion, standaloneSDKVersion, sdkType) {
        var hashValue = (sessionId === null || sessionId === void 0 ? void 0 : sessionId.toString()) ? (0, analytics_core_1.generateHashCode)(sessionId.toString()) : undefined;
        this.metadata = {
            joinedConfig: joinedConfig,
            localConfig: localConfig,
            remoteConfig: remoteConfig,
            sessionId: sessionId,
            hashValue: hashValue,
            sampleRate: joinedConfig.sampleRate,
            replaySDKType: this.mapSDKType(sdkType),
            replaySDKVersion: replaySDKVersion,
            standaloneSDKType: '@amplitude/session-replay-browser',
            standaloneSDKVersion: standaloneSDKVersion,
        };
    };
    SessionReplay.prototype.initializeNetworkObservers = function () {
        var _a, _b, _c;
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var NetworkObserversClass, error_6;
            return tslib_1.__generator(this, function (_d) {
                switch (_d.label) {
                    case 0:
                        if (!(((_c = (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.loggingConfig) === null || _b === void 0 ? void 0 : _b.network) === null || _c === void 0 ? void 0 : _c.enabled) && !this.networkObservers)) return [3 /*break*/, 4];
                        _d.label = 1;
                    case 1:
                        _d.trys.push([1, 3, , 4]);
                        return [4 /*yield*/, Promise.resolve().then(function () { return tslib_1.__importStar(require('./observers')); })];
                    case 2:
                        NetworkObserversClass = (_d.sent()).NetworkObservers;
                        this.networkObservers = new NetworkObserversClass();
                        return [3 /*break*/, 4];
                    case 3:
                        error_6 = _d.sent();
                        this.loggerProvider.warn('Failed to import or instantiate NetworkObservers:', error_6);
                        return [3 /*break*/, 4];
                    case 4: return [2 /*return*/];
                }
            });
        });
    };
    return SessionReplay;
}());
exports.SessionReplay = SessionReplay;
//# sourceMappingURL=session-replay.js.map