"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SessionReplayEventsIDBStore = exports.createStore = exports.defineObjectStores = exports.keyValDatabaseExists = exports.remoteConfigKey = exports.sequencesToSendKey = exports.currentSequenceKey = void 0;
var tslib_1 = require("tslib");
var analytics_client_common_1 = require("@amplitude/analytics-client-common");
var analytics_core_1 = require("@amplitude/analytics-core");
var idb_1 = require("idb");
var messages_1 = require("../messages");
var base_events_store_1 = require("./base-events-store");
var legacy_idb_types_1 = require("./legacy-idb-types");
exports.currentSequenceKey = 'sessionCurrentSequence';
exports.sequencesToSendKey = 'sequencesToSend';
exports.remoteConfigKey = 'remoteConfig';
var keyValDatabaseExists = function () {
    var globalScope = (0, analytics_client_common_1.getGlobalScope)();
    return new Promise(function (resolve, reject) {
        if (!globalScope) {
            return reject(new Error('Global scope not found'));
        }
        if (!globalScope.indexedDB) {
            return reject(new Error('Session Replay: cannot find indexedDB'));
        }
        try {
            var request_1 = globalScope.indexedDB.open('keyval-store');
            request_1.onupgradeneeded = function () {
                if (request_1.result.version === 1) {
                    request_1.result.close();
                    request_1.transaction && request_1.transaction.abort();
                    globalScope.indexedDB.deleteDatabase('keyval-store');
                    resolve();
                }
            };
            request_1.onsuccess = function () {
                resolve(request_1.result);
            };
        }
        catch (e) {
            reject(e);
        }
    });
};
exports.keyValDatabaseExists = keyValDatabaseExists;
var batchPromiseAll = function (promiseBatch) { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
    var chunkSize, batch;
    return tslib_1.__generator(this, function (_a) {
        switch (_a.label) {
            case 0:
                if (!(promiseBatch.length > 0)) return [3 /*break*/, 2];
                chunkSize = 10;
                batch = promiseBatch.splice(0, chunkSize);
                return [4 /*yield*/, Promise.all(batch)];
            case 1:
                _a.sent();
                return [3 /*break*/, 0];
            case 2: return [2 /*return*/];
        }
    });
}); };
var defineObjectStores = function (db) {
    var sequencesStore;
    var currentSequenceStore;
    if (!db.objectStoreNames.contains(exports.currentSequenceKey)) {
        currentSequenceStore = db.createObjectStore(exports.currentSequenceKey, {
            keyPath: 'sessionId',
        });
    }
    if (!db.objectStoreNames.contains(exports.sequencesToSendKey)) {
        sequencesStore = db.createObjectStore(exports.sequencesToSendKey, {
            keyPath: 'sequenceId',
            autoIncrement: true,
        });
        sequencesStore.createIndex('sessionId', 'sessionId');
    }
    return {
        sequencesStore: sequencesStore,
        currentSequenceStore: currentSequenceStore,
    };
};
exports.defineObjectStores = defineObjectStores;
var createStore = function (dbName) { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
    return tslib_1.__generator(this, function (_a) {
        switch (_a.label) {
            case 0: return [4 /*yield*/, (0, idb_1.openDB)(dbName, 1, {
                    upgrade: exports.defineObjectStores,
                })];
            case 1: return [2 /*return*/, _a.sent()];
        }
    });
}); };
exports.createStore = createStore;
var SessionReplayEventsIDBStore = /** @class */ (function (_super) {
    tslib_1.__extends(SessionReplayEventsIDBStore, _super);
    function SessionReplayEventsIDBStore(args) {
        var _this = _super.call(this, args) || this;
        _this.getSequencesToSend = function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
            var sequences, cursor, _a, sessionId, events, e_1;
            return tslib_1.__generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        _b.trys.push([0, 5, , 6]);
                        sequences = [];
                        return [4 /*yield*/, this.db.transaction('sequencesToSend').store.openCursor()];
                    case 1:
                        cursor = _b.sent();
                        _b.label = 2;
                    case 2:
                        if (!cursor) return [3 /*break*/, 4];
                        _a = cursor.value, sessionId = _a.sessionId, events = _a.events;
                        sequences.push({
                            events: events,
                            sequenceId: cursor.key,
                            sessionId: sessionId,
                        });
                        return [4 /*yield*/, cursor.continue()];
                    case 3:
                        cursor = _b.sent();
                        return [3 /*break*/, 2];
                    case 4: return [2 /*return*/, sequences];
                    case 5:
                        e_1 = _b.sent();
                        this.loggerProvider.warn("".concat(messages_1.STORAGE_FAILURE, ": ").concat(e_1));
                        return [3 /*break*/, 6];
                    case 6: return [2 /*return*/, undefined];
                }
            });
        }); };
        _this.storeCurrentSequence = function (sessionId) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
            var currentSequenceData, sequenceId, e_2;
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        _a.trys.push([0, 4, , 5]);
                        return [4 /*yield*/, this.db.get(exports.currentSequenceKey, sessionId)];
                    case 1:
                        currentSequenceData = _a.sent();
                        if (!currentSequenceData) {
                            return [2 /*return*/, undefined];
                        }
                        return [4 /*yield*/, this.db.put(exports.sequencesToSendKey, {
                                sessionId: sessionId,
                                events: currentSequenceData.events,
                            })];
                    case 2:
                        sequenceId = _a.sent();
                        return [4 /*yield*/, this.db.put(exports.currentSequenceKey, {
                                sessionId: sessionId,
                                events: [],
                            })];
                    case 3:
                        _a.sent();
                        return [2 /*return*/, tslib_1.__assign(tslib_1.__assign({}, currentSequenceData), { sessionId: sessionId, sequenceId: sequenceId })];
                    case 4:
                        e_2 = _a.sent();
                        this.loggerProvider.warn("".concat(messages_1.STORAGE_FAILURE, ": ").concat(e_2));
                        return [3 /*break*/, 5];
                    case 5: return [2 /*return*/, undefined];
                }
            });
        }); };
        _this.addEventToCurrentSequence = function (sessionId, event) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
            var tx, sequenceEvents, eventsToSend, updatedEvents, sequenceId, e_3;
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        _a.trys.push([0, 10, , 11]);
                        tx = this.db.transaction(exports.currentSequenceKey, 'readwrite');
                        return [4 /*yield*/, tx.store.get(sessionId)];
                    case 1:
                        sequenceEvents = _a.sent();
                        if (!!sequenceEvents) return [3 /*break*/, 3];
                        return [4 /*yield*/, tx.store.put({ sessionId: sessionId, events: [event] })];
                    case 2:
                        _a.sent();
                        return [2 /*return*/];
                    case 3:
                        eventsToSend = void 0;
                        if (!this.shouldSplitEventsList(sequenceEvents.events, event)) return [3 /*break*/, 5];
                        eventsToSend = sequenceEvents.events;
                        // set store to empty array
                        return [4 /*yield*/, tx.store.put({ sessionId: sessionId, events: [event] })];
                    case 4:
                        // set store to empty array
                        _a.sent();
                        return [3 /*break*/, 7];
                    case 5:
                        updatedEvents = sequenceEvents.events.concat(event);
                        return [4 /*yield*/, tx.store.put({ sessionId: sessionId, events: updatedEvents })];
                    case 6:
                        _a.sent();
                        _a.label = 7;
                    case 7: return [4 /*yield*/, tx.done];
                    case 8:
                        _a.sent();
                        if (!eventsToSend) {
                            return [2 /*return*/, undefined];
                        }
                        return [4 /*yield*/, this.storeSendingEvents(sessionId, eventsToSend)];
                    case 9:
                        sequenceId = _a.sent();
                        if (!sequenceId) {
                            return [2 /*return*/, undefined];
                        }
                        return [2 /*return*/, {
                                events: eventsToSend,
                                sessionId: sessionId,
                                sequenceId: sequenceId,
                            }];
                    case 10:
                        e_3 = _a.sent();
                        this.loggerProvider.warn("".concat(messages_1.STORAGE_FAILURE, ": ").concat(e_3));
                        return [3 /*break*/, 11];
                    case 11: return [2 /*return*/, undefined];
                }
            });
        }); };
        _this.storeSendingEvents = function (sessionId, events) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
            var sequenceId, e_4;
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        _a.trys.push([0, 2, , 3]);
                        return [4 /*yield*/, this.db.put(exports.sequencesToSendKey, {
                                sessionId: sessionId,
                                events: events,
                            })];
                    case 1:
                        sequenceId = _a.sent();
                        return [2 /*return*/, sequenceId];
                    case 2:
                        e_4 = _a.sent();
                        this.loggerProvider.warn("".concat(messages_1.STORAGE_FAILURE, ": ").concat(e_4));
                        return [3 /*break*/, 3];
                    case 3: return [2 /*return*/, undefined];
                }
            });
        }); };
        _this.cleanUpSessionEventsStore = function (_sessionId, sequenceId) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
            var e_5;
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (!sequenceId) {
                            return [2 /*return*/];
                        }
                        _a.label = 1;
                    case 1:
                        _a.trys.push([1, 3, , 4]);
                        return [4 /*yield*/, this.db.delete(exports.sequencesToSendKey, sequenceId)];
                    case 2:
                        _a.sent();
                        return [3 /*break*/, 4];
                    case 3:
                        e_5 = _a.sent();
                        this.loggerProvider.warn("".concat(messages_1.STORAGE_FAILURE, ": ").concat(e_5));
                        return [3 /*break*/, 4];
                    case 4: return [2 /*return*/];
                }
            });
        }); };
        _this.transitionFromKeyValStore = function (sessionId) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
            var keyValDb, transitionCurrentSessionSequences_1, storageKey, getAllRequest_1, transitionPromise, globalScope, e_6, e_7;
            var _this = this;
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        _a.trys.push([0, 6, , 7]);
                        return [4 /*yield*/, (0, exports.keyValDatabaseExists)()];
                    case 1:
                        keyValDb = _a.sent();
                        if (!keyValDb) {
                            return [2 /*return*/];
                        }
                        transitionCurrentSessionSequences_1 = function (numericSessionId, sessionStore) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
                            var currentSessionSequences, promisesToBatch;
                            var _this = this;
                            return tslib_1.__generator(this, function (_a) {
                                switch (_a.label) {
                                    case 0:
                                        currentSessionSequences = sessionStore.sessionSequences;
                                        promisesToBatch = [];
                                        Object.keys(currentSessionSequences).forEach(function (sequenceId) {
                                            var numericSequenceId = parseInt(sequenceId, 10);
                                            var sequence = currentSessionSequences[numericSequenceId];
                                            if (numericSequenceId === sessionStore.currentSequenceId) {
                                                var eventAddPromises = sequence.events.map(function (event) { return tslib_1.__awaiter(_this, void 0, void 0, function () { return tslib_1.__generator(this, function (_a) {
                                                    return [2 /*return*/, this.addEventToCurrentSequence(numericSessionId, event)];
                                                }); }); });
                                                promisesToBatch.concat(eventAddPromises);
                                            }
                                            else if (sequence.status !== legacy_idb_types_1.RecordingStatus.SENT) {
                                                promisesToBatch.push(_this.storeSendingEvents(numericSessionId, sequence.events));
                                            }
                                        });
                                        return [4 /*yield*/, batchPromiseAll(promisesToBatch)];
                                    case 1:
                                        _a.sent();
                                        return [2 /*return*/];
                                }
                            });
                        }); };
                        storageKey = "".concat(analytics_core_1.STORAGE_PREFIX, "_").concat(this.apiKey.substring(0, 10));
                        _a.label = 2;
                    case 2:
                        _a.trys.push([2, 4, , 5]);
                        getAllRequest_1 = keyValDb.transaction('keyval').objectStore('keyval').getAll(storageKey);
                        transitionPromise = new Promise(function (resolve) {
                            getAllRequest_1.onsuccess = function (e) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
                                var storedReplaySessionContextList, storedReplaySessionContexts, promisesToBatch_1;
                                var _this = this;
                                return tslib_1.__generator(this, function (_a) {
                                    switch (_a.label) {
                                        case 0:
                                            storedReplaySessionContextList = e && e.target.result;
                                            storedReplaySessionContexts = storedReplaySessionContextList && storedReplaySessionContextList[0];
                                            if (!storedReplaySessionContexts) return [3 /*break*/, 2];
                                            promisesToBatch_1 = [];
                                            Object.keys(storedReplaySessionContexts).forEach(function (storedSessionId) {
                                                var numericSessionId = parseInt(storedSessionId, 10);
                                                var oldSessionStore = storedReplaySessionContexts[numericSessionId];
                                                if (sessionId === numericSessionId) {
                                                    promisesToBatch_1.push(transitionCurrentSessionSequences_1(numericSessionId, oldSessionStore));
                                                }
                                                else {
                                                    var oldSessionSequences_1 = oldSessionStore.sessionSequences;
                                                    Object.keys(oldSessionSequences_1).forEach(function (sequenceId) {
                                                        var numericSequenceId = parseInt(sequenceId, 10);
                                                        if (oldSessionSequences_1[numericSequenceId].status !== legacy_idb_types_1.RecordingStatus.SENT) {
                                                            promisesToBatch_1.push(_this.storeSendingEvents(numericSessionId, oldSessionSequences_1[numericSequenceId].events));
                                                        }
                                                    });
                                                }
                                            });
                                            return [4 /*yield*/, batchPromiseAll(promisesToBatch_1)];
                                        case 1:
                                            _a.sent();
                                            _a.label = 2;
                                        case 2:
                                            resolve();
                                            return [2 /*return*/];
                                    }
                                });
                            }); };
                        });
                        return [4 /*yield*/, transitionPromise];
                    case 3:
                        _a.sent();
                        globalScope = (0, analytics_client_common_1.getGlobalScope)();
                        if (globalScope) {
                            globalScope.indexedDB.deleteDatabase('keyval-store');
                        }
                        return [3 /*break*/, 5];
                    case 4:
                        e_6 = _a.sent();
                        this.loggerProvider.warn("Failed to transition session replay events from keyval to new store: ".concat(e_6));
                        return [3 /*break*/, 5];
                    case 5: return [3 /*break*/, 7];
                    case 6:
                        e_7 = _a.sent();
                        this.loggerProvider.warn("Failed to access keyval store: ".concat(e_7, ". For more information, visit: https://www.docs.developers.amplitude.com/session-replay/sdks/standalone/#indexeddb-best-practices"));
                        return [3 /*break*/, 7];
                    case 7: return [2 /*return*/];
                }
            });
        }); };
        _this.apiKey = args.apiKey;
        _this.db = args.db;
        return _this;
    }
    SessionReplayEventsIDBStore.new = function (type, args, sessionId) {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var dbSuffix, dbName, db, eventsIDBStore, e_8;
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        _a.trys.push([0, 3, , 4]);
                        dbSuffix = type === 'replay' ? '' : "_".concat(type);
                        dbName = "".concat(args.apiKey.substring(0, 10), "_amp_session_replay_events").concat(dbSuffix);
                        return [4 /*yield*/, (0, exports.createStore)(dbName)];
                    case 1:
                        db = _a.sent();
                        eventsIDBStore = new SessionReplayEventsIDBStore(tslib_1.__assign(tslib_1.__assign({}, args), { db: db }));
                        return [4 /*yield*/, eventsIDBStore.transitionFromKeyValStore(sessionId)];
                    case 2:
                        _a.sent();
                        return [2 /*return*/, eventsIDBStore];
                    case 3:
                        e_8 = _a.sent();
                        args.loggerProvider.warn("".concat(messages_1.STORAGE_FAILURE, ": ").concat(e_8));
                        return [3 /*break*/, 4];
                    case 4: return [2 /*return*/];
                }
            });
        });
    };
    SessionReplayEventsIDBStore.prototype.getCurrentSequenceEvents = function (sessionId) {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var events, allEvents, _a, _b, events, e_9_1;
            var e_9, _c;
            return tslib_1.__generator(this, function (_d) {
                switch (_d.label) {
                    case 0:
                        if (!sessionId) return [3 /*break*/, 2];
                        return [4 /*yield*/, this.db.get('sessionCurrentSequence', sessionId)];
                    case 1:
                        events = _d.sent();
                        if (!events) {
                            return [2 /*return*/, undefined];
                        }
                        return [2 /*return*/, [events]];
                    case 2:
                        allEvents = [];
                        _d.label = 3;
                    case 3:
                        _d.trys.push([3, 8, 9, 10]);
                        return [4 /*yield*/, this.db.getAll('sessionCurrentSequence')];
                    case 4:
                        _a = tslib_1.__values.apply(void 0, [_d.sent()]), _b = _a.next();
                        _d.label = 5;
                    case 5:
                        if (!!_b.done) return [3 /*break*/, 7];
                        events = _b.value;
                        allEvents.push(events);
                        _d.label = 6;
                    case 6:
                        _b = _a.next();
                        return [3 /*break*/, 5];
                    case 7: return [3 /*break*/, 10];
                    case 8:
                        e_9_1 = _d.sent();
                        e_9 = { error: e_9_1 };
                        return [3 /*break*/, 10];
                    case 9:
                        try {
                            if (_b && !_b.done && (_c = _a.return)) _c.call(_a);
                        }
                        finally { if (e_9) throw e_9.error; }
                        return [7 /*endfinally*/];
                    case 10: return [2 /*return*/, allEvents];
                }
            });
        });
    };
    return SessionReplayEventsIDBStore;
}(base_events_store_1.BaseEventsStore));
exports.SessionReplayEventsIDBStore = SessionReplayEventsIDBStore;
//# sourceMappingURL=events-idb-store.js.map