@amplitude/session-replay-browser 1.40.0 → 1.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -0
- package/lib/cjs/config/local-config.d.ts +2 -1
- package/lib/cjs/config/local-config.d.ts.map +1 -1
- package/lib/cjs/config/local-config.js +3 -0
- package/lib/cjs/config/local-config.js.map +1 -1
- package/lib/cjs/config/types.d.ts +26 -0
- package/lib/cjs/config/types.d.ts.map +1 -1
- package/lib/cjs/config/types.js.map +1 -1
- package/lib/cjs/constants.d.ts +2 -0
- package/lib/cjs/constants.d.ts.map +1 -1
- package/lib/cjs/constants.js +3 -1
- package/lib/cjs/constants.js.map +1 -1
- package/lib/cjs/cross-origin-iframes.d.ts +28 -0
- package/lib/cjs/cross-origin-iframes.d.ts.map +1 -0
- package/lib/cjs/cross-origin-iframes.js +175 -0
- package/lib/cjs/cross-origin-iframes.js.map +1 -0
- package/lib/cjs/events/events-idb-store.d.ts +25 -4
- package/lib/cjs/events/events-idb-store.d.ts.map +1 -1
- package/lib/cjs/events/events-idb-store.js +257 -61
- package/lib/cjs/events/events-idb-store.js.map +1 -1
- package/lib/cjs/session-replay.d.ts +4 -0
- package/lib/cjs/session-replay.d.ts.map +1 -1
- package/lib/cjs/session-replay.js +118 -51
- package/lib/cjs/session-replay.js.map +1 -1
- package/lib/cjs/track-destination.d.ts.map +1 -1
- package/lib/cjs/track-destination.js +5 -1
- package/lib/cjs/track-destination.js.map +1 -1
- package/lib/cjs/utils/rrweb.d.ts +1 -0
- package/lib/cjs/utils/rrweb.d.ts.map +1 -1
- package/lib/cjs/utils/rrweb.js.map +1 -1
- package/lib/cjs/version.d.ts +1 -1
- package/lib/cjs/version.js +1 -1
- package/lib/cjs/version.js.map +1 -1
- package/lib/cjs/worker/index.js +1 -1
- package/lib/esm/config/local-config.d.ts +2 -1
- package/lib/esm/config/local-config.d.ts.map +1 -1
- package/lib/esm/config/local-config.js +3 -0
- package/lib/esm/config/local-config.js.map +1 -1
- package/lib/esm/config/types.d.ts +26 -0
- package/lib/esm/config/types.d.ts.map +1 -1
- package/lib/esm/config/types.js.map +1 -1
- package/lib/esm/constants.d.ts +2 -0
- package/lib/esm/constants.d.ts.map +1 -1
- package/lib/esm/constants.js +2 -0
- package/lib/esm/constants.js.map +1 -1
- package/lib/esm/cross-origin-iframes.d.ts +28 -0
- package/lib/esm/cross-origin-iframes.d.ts.map +1 -0
- package/lib/esm/cross-origin-iframes.js +170 -0
- package/lib/esm/cross-origin-iframes.js.map +1 -0
- package/lib/esm/events/events-idb-store.d.ts +25 -4
- package/lib/esm/events/events-idb-store.d.ts.map +1 -1
- package/lib/esm/events/events-idb-store.js +255 -61
- package/lib/esm/events/events-idb-store.js.map +1 -1
- package/lib/esm/session-replay.d.ts +4 -0
- package/lib/esm/session-replay.d.ts.map +1 -1
- package/lib/esm/session-replay.js +118 -51
- package/lib/esm/session-replay.js.map +1 -1
- package/lib/esm/track-destination.d.ts.map +1 -1
- package/lib/esm/track-destination.js +6 -2
- package/lib/esm/track-destination.js.map +1 -1
- package/lib/esm/utils/rrweb.d.ts +1 -0
- package/lib/esm/utils/rrweb.d.ts.map +1 -1
- package/lib/esm/utils/rrweb.js.map +1 -1
- package/lib/esm/version.d.ts +1 -1
- package/lib/esm/version.js +1 -1
- package/lib/esm/version.js.map +1 -1
- package/lib/esm/worker/index.js +1 -1
- package/lib/scripts/index-min.js +1 -1
- package/lib/scripts/index-min.js.gz +0 -0
- package/lib/scripts/index-min.js.map +1 -1
- package/lib/scripts/session-replay-browser-min.js +1 -1
- package/lib/scripts/session-replay-browser-min.js.gz +0 -0
- package/lib/scripts/session-replay-browser-min.js.map +1 -1
- package/lib/scripts/targeting-min.js +1 -1
- package/lib/scripts/targeting-min.js.gz +0 -0
- package/lib/scripts/targeting-min.js.map +1 -1
- package/lib/scripts/worker-min.js +1 -1
- package/lib/scripts/worker-min.js.gz +0 -0
- package/package.json +4 -4
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
import { DBSchema, IDBPDatabase } from 'idb';
|
|
2
2
|
import { EventType, Events, SendingSequencesReturn } from '../typings/session-replay';
|
|
3
3
|
import { BaseEventsStore, InstanceArgs as BaseInstanceArgs } from './base-events-store';
|
|
4
|
+
export declare function generateUUID(): string;
|
|
4
5
|
export declare const currentSequenceKey = "sessionCurrentSequence";
|
|
5
6
|
export declare const sequencesToSendKey = "sequencesToSend";
|
|
6
7
|
export declare const remoteConfigKey = "remoteConfig";
|
|
8
|
+
export declare const OPEN_DB_TIMEOUT_MS = 2000;
|
|
9
|
+
export declare const TX_DONE_TIMEOUT_MS = 5000;
|
|
10
|
+
/**
|
|
11
|
+
* Race a promise against a timeout. Resolves/rejects with the original
|
|
12
|
+
* promise's value when it settles first; rejects with a timeout error if
|
|
13
|
+
* `ms` elapses first. Either way the timer is cleared so we don't leak
|
|
14
|
+
* pending setTimeouts.
|
|
15
|
+
*/
|
|
16
|
+
export declare function withTimeout<T>(promise: Promise<T>, ms: number, message?: string): Promise<T>;
|
|
7
17
|
export interface SessionReplayDB extends DBSchema {
|
|
8
18
|
sessionCurrentSequence: {
|
|
9
19
|
key: number;
|
|
10
|
-
value: Omit<SendingSequencesReturn<number>, 'sequenceId'
|
|
20
|
+
value: Omit<SendingSequencesReturn<number>, 'sequenceId'> & {
|
|
21
|
+
tabId?: string;
|
|
22
|
+
};
|
|
11
23
|
};
|
|
12
24
|
sequencesToSend: {
|
|
13
25
|
key: number;
|
|
14
|
-
value: Omit<SendingSequencesReturn<number>, 'sequenceId'
|
|
26
|
+
value: Omit<SendingSequencesReturn<number>, 'sequenceId'> & {
|
|
27
|
+
tabId?: string;
|
|
28
|
+
};
|
|
15
29
|
indexes: {
|
|
16
30
|
sessionId: string | number;
|
|
17
31
|
};
|
|
@@ -25,11 +39,13 @@ export declare const createStore: (dbName: string) => Promise<IDBPDatabase<Sessi
|
|
|
25
39
|
type InstanceArgs = {
|
|
26
40
|
apiKey: string;
|
|
27
41
|
db: IDBPDatabase<SessionReplayDB>;
|
|
42
|
+
tabId: string;
|
|
28
43
|
onPersistentFailure?: () => void;
|
|
29
44
|
consecutiveFailureThreshold?: number;
|
|
30
45
|
} & BaseInstanceArgs;
|
|
31
46
|
export declare class SessionReplayEventsIDBStore extends BaseEventsStore<number> {
|
|
32
47
|
private readonly db;
|
|
48
|
+
private readonly tabId;
|
|
33
49
|
private readonly onPersistentFailure?;
|
|
34
50
|
private readonly consecutiveFailureThreshold;
|
|
35
51
|
private consecutiveFailures;
|
|
@@ -37,8 +53,13 @@ export declare class SessionReplayEventsIDBStore extends BaseEventsStore<number>
|
|
|
37
53
|
constructor(args: InstanceArgs);
|
|
38
54
|
private recordFailure;
|
|
39
55
|
private recordSuccess;
|
|
40
|
-
static new(type: EventType, args: Omit<InstanceArgs, 'db'>
|
|
41
|
-
|
|
56
|
+
static new(type: EventType, args: Omit<InstanceArgs, 'db' | 'tabId'> & {
|
|
57
|
+
tabId?: string;
|
|
58
|
+
}): Promise<SessionReplayEventsIDBStore | undefined>;
|
|
59
|
+
getCurrentSequenceEvents(sessionId?: number): Promise<{
|
|
60
|
+
events: Events;
|
|
61
|
+
sessionId: string | number;
|
|
62
|
+
}[] | undefined>;
|
|
42
63
|
getSequencesToSend: () => Promise<SendingSequencesReturn<number>[] | undefined>;
|
|
43
64
|
storeCurrentSequence: (sessionId: number) => Promise<{
|
|
44
65
|
sessionId: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events-idb-store.d.ts","sourceRoot":"","sources":["../../../src/events/events-idb-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAU,MAAM,KAAK,CAAC;AAErD,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACtF,OAAO,EAAE,eAAe,EAAE,YAAY,IAAI,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"events-idb-store.d.ts","sourceRoot":"","sources":["../../../src/events/events-idb-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAU,MAAM,KAAK,CAAC;AAErD,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACtF,OAAO,EAAE,eAAe,EAAE,YAAY,IAAI,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAMxF,wBAAgB,YAAY,IAAI,MAAM,CASrC;AAED,eAAO,MAAM,kBAAkB,2BAA2B,CAAC;AAC3D,eAAO,MAAM,kBAAkB,oBAAoB,CAAC;AACpD,eAAO,MAAM,eAAe,iBAAiB,CAAC;AAO9C,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAOvC,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAEvC;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,SAA4B,GAAG,OAAO,CAAC,CAAC,CAAC,CAc/G;AA0CD,MAAM,WAAW,eAAgB,SAAQ,QAAQ;IAC/C,sBAAsB,EAAE;QACtB,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,IAAI,CAAC,sBAAsB,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC,GAAG;YAAE,KAAK,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;KAChF,CAAC;IACF,eAAe,EAAE;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,IAAI,CAAC,sBAAsB,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC,GAAG;YAAE,KAAK,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAC/E,OAAO,EAAE;YAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAAA;SAAE,CAAC;KACzC,CAAC;CACH;AAED,eAAO,MAAM,kBAAkB,OAAQ,aAAa,eAAe,CAAC;;;CAmBnE,CAAC;AAEF,eAAO,MAAM,WAAW,WAAkB,MAAM,2CAa/C,CAAC;AAEF,KAAK,YAAY,GAAG;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,YAAY,CAAC,eAAe,CAAC,CAAC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,2BAA2B,CAAC,EAAE,MAAM,CAAC;CACtC,GAAG,gBAAgB,CAAC;AAErB,qBAAa,2BAA4B,SAAQ,eAAe,CAAC,MAAM,CAAC;IACtE,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAgC;IACnD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAa;IAClD,OAAO,CAAC,QAAQ,CAAC,2BAA2B,CAAS;IACrD,OAAO,CAAC,mBAAmB,CAAK;IAChC,OAAO,CAAC,oBAAoB,CAAS;gBAEzB,IAAI,EAAE,YAAY;IAa9B,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,aAAa;WAIR,GAAG,CACd,IAAI,EAAE,SAAS,EACf,IAAI,EAAE,IAAI,CAAC,YAAY,EAAE,IAAI,GAAG,OAAO,CAAC,GAAG;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAC5D,OAAO,CAAC,2BAA2B,GAAG,SAAS,CAAC;IAsB7C,wBAAwB,CAAC,SAAS,CAAC,EAAE,MAAM;;;;IA0BjD,kBAAkB,QAAa,QAAQ,uBAAuB,MAAM,CAAC,EAAE,GAAG,SAAS,CAAC,CAwDlF;IAEF,oBAAoB,cAAqB,MAAM;;;;mBAuE7C;IAEF,yBAAyB,cAAqB,MAAM,SAAS,MAAM;;;;mBA8FjE;IAEF,kBAAkB,cAAqB,MAAM,iDAc3C;IAEF,yBAAyB,eAAsB,MAAM,eAAe,MAAM,mBAWxE;CACH"}
|
|
@@ -1,14 +1,97 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SessionReplayEventsIDBStore = exports.createStore = exports.defineObjectStores = exports.remoteConfigKey = exports.sequencesToSendKey = exports.currentSequenceKey = void 0;
|
|
3
|
+
exports.SessionReplayEventsIDBStore = exports.createStore = exports.defineObjectStores = exports.withTimeout = exports.TX_DONE_TIMEOUT_MS = exports.OPEN_DB_TIMEOUT_MS = exports.remoteConfigKey = exports.sequencesToSendKey = exports.currentSequenceKey = exports.generateUUID = void 0;
|
|
4
4
|
var tslib_1 = require("tslib");
|
|
5
5
|
var idb_1 = require("idb");
|
|
6
6
|
var messages_1 = require("../messages");
|
|
7
7
|
var base_events_store_1 = require("./base-events-store");
|
|
8
8
|
var is_abort_error_1 = require("../utils/is-abort-error");
|
|
9
|
+
// crypto.randomUUID() requires a secure context (https). Fall back to a
|
|
10
|
+
// Math.random-based UUID for http origins or older browsers — tab IDs don't
|
|
11
|
+
// need to be cryptographically secure, just unique within a session.
|
|
12
|
+
function generateUUID() {
|
|
13
|
+
try {
|
|
14
|
+
return crypto.randomUUID();
|
|
15
|
+
}
|
|
16
|
+
catch (_a) {
|
|
17
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
18
|
+
var r = (Math.random() * 16) | 0;
|
|
19
|
+
return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.generateUUID = generateUUID;
|
|
9
24
|
exports.currentSequenceKey = 'sessionCurrentSequence';
|
|
10
25
|
exports.sequencesToSendKey = 'sequencesToSend';
|
|
11
26
|
exports.remoteConfigKey = 'remoteConfig';
|
|
27
|
+
// Timeout for openDB at init. IDB openDB can hang forever when another tab
|
|
28
|
+
// holds an open connection during a version upgrade, or when the DB is in a
|
|
29
|
+
// "closing" state (documented Chrome behaviour). When that happens we want
|
|
30
|
+
// SessionReplayEventsIDBStore.new() to bail out so the caller can fall back
|
|
31
|
+
// to the in-memory store.
|
|
32
|
+
exports.OPEN_DB_TIMEOUT_MS = 2000;
|
|
33
|
+
// Timeout for per-operation tx.done settlement. Mid-recording a readwrite
|
|
34
|
+
// transaction can stall (storage pressure in some browsers stalls instead of
|
|
35
|
+
// throwing); without a timeout, recordFailure() is never called and the
|
|
36
|
+
// memory fallback never triggers. The transaction may still settle later;
|
|
37
|
+
// the timedOut flag prevents double-counting alongside errorLogged.
|
|
38
|
+
exports.TX_DONE_TIMEOUT_MS = 5000;
|
|
39
|
+
/**
|
|
40
|
+
* Race a promise against a timeout. Resolves/rejects with the original
|
|
41
|
+
* promise's value when it settles first; rejects with a timeout error if
|
|
42
|
+
* `ms` elapses first. Either way the timer is cleared so we don't leak
|
|
43
|
+
* pending setTimeouts.
|
|
44
|
+
*/
|
|
45
|
+
function withTimeout(promise, ms, message) {
|
|
46
|
+
if (message === void 0) { message = 'IDB operation timed out'; }
|
|
47
|
+
return new Promise(function (resolve, reject) {
|
|
48
|
+
var timer = setTimeout(function () { return reject(new Error("".concat(message, " after ").concat(ms, "ms"))); }, ms);
|
|
49
|
+
promise.then(function (v) {
|
|
50
|
+
clearTimeout(timer);
|
|
51
|
+
resolve(v);
|
|
52
|
+
}, function (e) {
|
|
53
|
+
clearTimeout(timer);
|
|
54
|
+
reject(e);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
exports.withTimeout = withTimeout;
|
|
59
|
+
/**
|
|
60
|
+
* Arms a watchdog that fires `onTimeout` only if `tx.done` hasn't settled
|
|
61
|
+
* within `ms`. A "soft cancel" returned to the caller suppresses the timeout
|
|
62
|
+
* if the synchronous operation body completes without exception — important
|
|
63
|
+
* because in test environments fake timers can fire ahead of tx.done's commit
|
|
64
|
+
* microtask. Production behaviour is preserved: a genuinely stalled
|
|
65
|
+
* transaction (no commit, no error, individual op promises never resolved)
|
|
66
|
+
* cannot reach the soft-cancel call and the timeout fires as designed.
|
|
67
|
+
*
|
|
68
|
+
* Soft-cancel is only invoked once the awaits inside the operation's outer
|
|
69
|
+
* try block have all returned — i.e. the IDB driver acknowledged each
|
|
70
|
+
* individual put/get. If the driver accepts a put but the underlying
|
|
71
|
+
* transaction silently fails to commit (the production stall scenario),
|
|
72
|
+
* tx.done still hasn't settled, and recordFailure must NOT have been
|
|
73
|
+
* suppressed. The fix for that scenario in production is the tx.done.catch
|
|
74
|
+
* handler attached separately by callers, which catches the eventual abort.
|
|
75
|
+
*
|
|
76
|
+
* The soft-cancel pattern only suppresses the timeout for the "all puts
|
|
77
|
+
* resolved successfully" case — a case where tx.done is overwhelmingly
|
|
78
|
+
* likely to settle imminently in production. If it doesn't, callers can
|
|
79
|
+
* still detect the failure on the NEXT operation (which will fail to open
|
|
80
|
+
* a transaction or hit the same pressure), because all three methods use
|
|
81
|
+
* readwrite transactions on the same two stores, which IDB serializes:
|
|
82
|
+
* if T1's tx.done never settles, T2 is blocked waiting for T1 to commit or
|
|
83
|
+
* abort — T2's put/get requests never resolve, T2 never reaches its
|
|
84
|
+
* soft-cancel, and T2's watchdog fires, calling recordFailure().
|
|
85
|
+
*/
|
|
86
|
+
function armTxDoneTimeout(txDone, ms, onTimeout) {
|
|
87
|
+
var timer = setTimeout(onTimeout, ms);
|
|
88
|
+
// Belt-and-braces: clear the timer when tx.done settles, even though the
|
|
89
|
+
// primary cancel path is the caller's success-path cancel(). This covers
|
|
90
|
+
// the case where tx.done settles with no caller cancellation (shouldn't
|
|
91
|
+
// happen in current code paths, but cheap insurance).
|
|
92
|
+
txDone.then(function () { return clearTimeout(timer); }, function () { return clearTimeout(timer); });
|
|
93
|
+
return function () { return clearTimeout(timer); };
|
|
94
|
+
}
|
|
12
95
|
var defineObjectStores = function (db) {
|
|
13
96
|
var sequencesStore;
|
|
14
97
|
var currentSequenceStore;
|
|
@@ -33,10 +116,16 @@ exports.defineObjectStores = defineObjectStores;
|
|
|
33
116
|
var createStore = function (dbName) { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
|
|
34
117
|
return tslib_1.__generator(this, function (_a) {
|
|
35
118
|
switch (_a.label) {
|
|
36
|
-
case 0: return [4 /*yield*/, (0, idb_1.openDB)(dbName, 1, {
|
|
119
|
+
case 0: return [4 /*yield*/, withTimeout((0, idb_1.openDB)(dbName, 1, {
|
|
37
120
|
upgrade: exports.defineObjectStores,
|
|
38
|
-
})];
|
|
39
|
-
case 1:
|
|
121
|
+
}), exports.OPEN_DB_TIMEOUT_MS, 'IDB openDB timed out')];
|
|
122
|
+
case 1:
|
|
123
|
+
// Wrap openDB with a timeout so a hung connection (foreign tab holding an
|
|
124
|
+
// open handle during version upgrade, or "closing" DB) doesn't block the
|
|
125
|
+
// SDK from initialising. On timeout this rejects, which propagates up to
|
|
126
|
+
// SessionReplayEventsIDBStore.new()'s catch block, returning undefined and
|
|
127
|
+
// triggering the memory fallback.
|
|
128
|
+
return [2 /*return*/, _a.sent()];
|
|
40
129
|
}
|
|
41
130
|
});
|
|
42
131
|
}); };
|
|
@@ -50,12 +139,13 @@ var SessionReplayEventsIDBStore = /** @class */ (function (_super) {
|
|
|
50
139
|
_this.consecutiveFailures = 0;
|
|
51
140
|
_this.hasTriggeredFallback = false;
|
|
52
141
|
_this.getSequencesToSend = function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
|
|
53
|
-
var errorLogged, sequences, tx, cursor, _a, sessionId, events, e_1;
|
|
142
|
+
var errorLogged, timedOut, sequences, tx, cancelTimeout, cursor, _a, sessionId, events, e_1;
|
|
54
143
|
var _this = this;
|
|
55
144
|
return tslib_1.__generator(this, function (_b) {
|
|
56
145
|
switch (_b.label) {
|
|
57
146
|
case 0:
|
|
58
147
|
errorLogged = false;
|
|
148
|
+
timedOut = false;
|
|
59
149
|
_b.label = 1;
|
|
60
150
|
case 1:
|
|
61
151
|
_b.trys.push([1, 6, , 7]);
|
|
@@ -63,11 +153,19 @@ var SessionReplayEventsIDBStore = /** @class */ (function (_super) {
|
|
|
63
153
|
tx = this.db.transaction('sequencesToSend');
|
|
64
154
|
// Attach a catch handler immediately so tx.done rejections (e.g. AbortError after
|
|
65
155
|
// cursor traversal completes) are always handled without blocking the return path.
|
|
66
|
-
// The errorLogged
|
|
67
|
-
//
|
|
156
|
+
// The errorLogged / timedOut flags prevent double-logging and double-recording
|
|
157
|
+
// when the outer catch (or the timeout race) already fired for the same abort.
|
|
68
158
|
tx.done.catch(function (e) {
|
|
69
|
-
if (!errorLogged) {
|
|
159
|
+
if (!errorLogged && !timedOut) {
|
|
70
160
|
(0, is_abort_error_1.logIdbError)(_this.loggerProvider, "".concat(messages_1.STORAGE_FAILURE, ": ").concat(e), e);
|
|
161
|
+
_this.recordFailure();
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
cancelTimeout = armTxDoneTimeout(tx.done, exports.TX_DONE_TIMEOUT_MS, function () {
|
|
165
|
+
if (!errorLogged && !timedOut) {
|
|
166
|
+
timedOut = true;
|
|
167
|
+
(0, is_abort_error_1.logIdbError)(_this.loggerProvider, "".concat(messages_1.STORAGE_FAILURE, ": transaction timed out"));
|
|
168
|
+
_this.recordFailure();
|
|
71
169
|
}
|
|
72
170
|
});
|
|
73
171
|
return [4 /*yield*/, tx.store.openCursor()];
|
|
@@ -77,6 +175,12 @@ var SessionReplayEventsIDBStore = /** @class */ (function (_super) {
|
|
|
77
175
|
case 3:
|
|
78
176
|
if (!cursor) return [3 /*break*/, 5];
|
|
79
177
|
_a = cursor.value, sessionId = _a.sessionId, events = _a.events;
|
|
178
|
+
// Return all completed sequences regardless of tabId. Filtering by tab
|
|
179
|
+
// would cause event loss on page reload: a new store instance gets a
|
|
180
|
+
// fresh in-memory UUID and would never see sequences written by the
|
|
181
|
+
// previous instance. Completed sequences are safe to flush by any
|
|
182
|
+
// tab/instance; the server deduplicates, and cleanUpSessionEventsStore
|
|
183
|
+
// on an already-deleted key is a no-op.
|
|
80
184
|
sequences.push({
|
|
81
185
|
events: events,
|
|
82
186
|
sequenceId: cursor.key,
|
|
@@ -88,113 +192,187 @@ var SessionReplayEventsIDBStore = /** @class */ (function (_super) {
|
|
|
88
192
|
return [3 /*break*/, 3];
|
|
89
193
|
case 5:
|
|
90
194
|
this.recordSuccess();
|
|
195
|
+
cancelTimeout();
|
|
91
196
|
return [2 /*return*/, sequences];
|
|
92
197
|
case 6:
|
|
93
198
|
e_1 = _b.sent();
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
199
|
+
if (!timedOut) {
|
|
200
|
+
errorLogged = true;
|
|
201
|
+
(0, is_abort_error_1.logIdbError)(this.loggerProvider, "".concat(messages_1.STORAGE_FAILURE, ": ").concat(e_1), e_1);
|
|
202
|
+
this.recordFailure();
|
|
203
|
+
}
|
|
97
204
|
return [3 /*break*/, 7];
|
|
98
205
|
case 7: return [2 /*return*/, undefined];
|
|
99
206
|
}
|
|
100
207
|
});
|
|
101
208
|
}); };
|
|
102
209
|
_this.storeCurrentSequence = function (sessionId) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
|
|
103
|
-
var currentSequenceData, sequenceId, e_2;
|
|
210
|
+
var errorLogged, timedOut, tx, cancelTimeout, currentSequenceData, sequenceId, _tabId, rest, e_2;
|
|
211
|
+
var _this = this;
|
|
104
212
|
return tslib_1.__generator(this, function (_a) {
|
|
105
213
|
switch (_a.label) {
|
|
106
214
|
case 0:
|
|
107
|
-
|
|
108
|
-
|
|
215
|
+
errorLogged = false;
|
|
216
|
+
timedOut = false;
|
|
217
|
+
_a.label = 1;
|
|
109
218
|
case 1:
|
|
219
|
+
_a.trys.push([1, 5, , 6]);
|
|
220
|
+
tx = this.db.transaction([exports.currentSequenceKey, exports.sequencesToSendKey], 'readwrite');
|
|
221
|
+
tx.done.catch(function (e) {
|
|
222
|
+
if (!errorLogged && !timedOut) {
|
|
223
|
+
(0, is_abort_error_1.logIdbError)(_this.loggerProvider, "".concat(messages_1.STORAGE_FAILURE, ": ").concat(e), e);
|
|
224
|
+
_this.recordFailure();
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
cancelTimeout = armTxDoneTimeout(tx.done, exports.TX_DONE_TIMEOUT_MS, function () {
|
|
228
|
+
if (!errorLogged && !timedOut) {
|
|
229
|
+
timedOut = true;
|
|
230
|
+
(0, is_abort_error_1.logIdbError)(_this.loggerProvider, "".concat(messages_1.STORAGE_FAILURE, ": transaction timed out"));
|
|
231
|
+
_this.recordFailure();
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
return [4 /*yield*/, tx.objectStore(exports.currentSequenceKey).get(sessionId)];
|
|
235
|
+
case 2:
|
|
110
236
|
currentSequenceData = _a.sent();
|
|
111
|
-
if
|
|
112
|
-
|
|
237
|
+
// Skip promotion if the slot is empty or owned by another tab — let the owning
|
|
238
|
+
// tab promote its own events on its next addEventToCurrentSequence/storeCurrentSequence
|
|
239
|
+
// call (via Bug 1's foreign-tab promotion path).
|
|
240
|
+
// Don't call recordSuccess() here: no write was performed, so this is not
|
|
241
|
+
// evidence the storage layer is healthy — leave the failure counter unchanged.
|
|
242
|
+
if (!currentSequenceData || (currentSequenceData.tabId && currentSequenceData.tabId !== this.tabId)) {
|
|
243
|
+
cancelTimeout();
|
|
113
244
|
return [2 /*return*/, undefined];
|
|
114
245
|
}
|
|
115
|
-
|
|
246
|
+
// Skip empty sequences — no point writing a zero-event row to sequencesToSend.
|
|
247
|
+
if (currentSequenceData.events.length === 0) {
|
|
248
|
+
cancelTimeout();
|
|
249
|
+
return [2 /*return*/, undefined];
|
|
250
|
+
}
|
|
251
|
+
return [4 /*yield*/, tx.objectStore(exports.sequencesToSendKey).put({
|
|
116
252
|
sessionId: sessionId,
|
|
117
253
|
events: currentSequenceData.events,
|
|
254
|
+
tabId: this.tabId,
|
|
118
255
|
})];
|
|
119
|
-
case
|
|
256
|
+
case 3:
|
|
120
257
|
sequenceId = _a.sent();
|
|
121
|
-
return [4 /*yield*/,
|
|
258
|
+
return [4 /*yield*/, tx.objectStore(exports.currentSequenceKey).put({
|
|
122
259
|
sessionId: sessionId,
|
|
123
260
|
events: [],
|
|
261
|
+
tabId: this.tabId,
|
|
124
262
|
})];
|
|
125
|
-
case
|
|
263
|
+
case 4:
|
|
126
264
|
_a.sent();
|
|
127
265
|
this.recordSuccess();
|
|
128
|
-
|
|
129
|
-
|
|
266
|
+
cancelTimeout();
|
|
267
|
+
_tabId = currentSequenceData.tabId, rest = tslib_1.__rest(currentSequenceData, ["tabId"]);
|
|
268
|
+
return [2 /*return*/, tslib_1.__assign(tslib_1.__assign({}, rest), { sessionId: sessionId, sequenceId: sequenceId })];
|
|
269
|
+
case 5:
|
|
130
270
|
e_2 = _a.sent();
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
271
|
+
if (!timedOut) {
|
|
272
|
+
errorLogged = true;
|
|
273
|
+
(0, is_abort_error_1.logIdbError)(this.loggerProvider, "".concat(messages_1.STORAGE_FAILURE, ": ").concat(e_2), e_2);
|
|
274
|
+
this.recordFailure();
|
|
275
|
+
}
|
|
276
|
+
return [3 /*break*/, 6];
|
|
277
|
+
case 6: return [2 /*return*/, undefined];
|
|
135
278
|
}
|
|
136
279
|
});
|
|
137
280
|
}); };
|
|
138
281
|
_this.addEventToCurrentSequence = function (sessionId, event) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
|
|
139
|
-
var errorLogged, tx, sequenceEvents, eventsToSend, sequenceId, e_3;
|
|
282
|
+
var errorLogged, timedOut, tx, cancelTimeout, sequenceEvents, ownedSequence, eventsToSend, sequenceId, e_3;
|
|
140
283
|
var _this = this;
|
|
141
284
|
return tslib_1.__generator(this, function (_a) {
|
|
142
285
|
switch (_a.label) {
|
|
143
286
|
case 0:
|
|
144
287
|
errorLogged = false;
|
|
288
|
+
timedOut = false;
|
|
145
289
|
_a.label = 1;
|
|
146
290
|
case 1:
|
|
147
|
-
_a.trys.push([1,
|
|
291
|
+
_a.trys.push([1, 13, , 14]);
|
|
148
292
|
tx = this.db.transaction([exports.currentSequenceKey, exports.sequencesToSendKey], 'readwrite');
|
|
149
293
|
// Attach a catch handler immediately so tx.done rejections (e.g. AbortError after
|
|
150
294
|
// put succeeds but before auto-commit) are always handled without blocking.
|
|
151
|
-
// The errorLogged
|
|
152
|
-
//
|
|
295
|
+
// The errorLogged / timedOut flags prevent double-logging when the outer catch
|
|
296
|
+
// (or the timeout) already fired for the same transaction.
|
|
153
297
|
tx.done.catch(function (e) {
|
|
154
|
-
if (!errorLogged) {
|
|
298
|
+
if (!errorLogged && !timedOut) {
|
|
155
299
|
(0, is_abort_error_1.logIdbError)(_this.loggerProvider, "".concat(messages_1.STORAGE_FAILURE, ": ").concat(e), e);
|
|
300
|
+
_this.recordFailure();
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
cancelTimeout = armTxDoneTimeout(tx.done, exports.TX_DONE_TIMEOUT_MS, function () {
|
|
304
|
+
if (!errorLogged && !timedOut) {
|
|
305
|
+
timedOut = true;
|
|
306
|
+
(0, is_abort_error_1.logIdbError)(_this.loggerProvider, "".concat(messages_1.STORAGE_FAILURE, ": transaction timed out"));
|
|
307
|
+
_this.recordFailure();
|
|
156
308
|
}
|
|
157
309
|
});
|
|
158
310
|
return [4 /*yield*/, tx.objectStore(exports.currentSequenceKey).get(sessionId)];
|
|
159
311
|
case 2:
|
|
160
312
|
sequenceEvents = _a.sent();
|
|
161
|
-
if (
|
|
162
|
-
|
|
313
|
+
if (!((sequenceEvents === null || sequenceEvents === void 0 ? void 0 : sequenceEvents.tabId) && sequenceEvents.tabId !== this.tabId)) return [3 /*break*/, 6];
|
|
314
|
+
if (!(sequenceEvents.events.length > 0)) return [3 /*break*/, 4];
|
|
315
|
+
return [4 /*yield*/, tx.objectStore(exports.sequencesToSendKey).put({
|
|
316
|
+
sessionId: sessionId,
|
|
317
|
+
events: sequenceEvents.events,
|
|
318
|
+
tabId: sequenceEvents.tabId,
|
|
319
|
+
})];
|
|
163
320
|
case 3:
|
|
164
321
|
_a.sent();
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
case 4:
|
|
168
|
-
if (!!this.shouldSplitEventsList(sequenceEvents.events, event)) return [3 /*break*/, 6];
|
|
169
|
-
return [4 /*yield*/, tx.objectStore(exports.currentSequenceKey).put({ sessionId: sessionId, events: sequenceEvents.events.concat(event) })];
|
|
322
|
+
_a.label = 4;
|
|
323
|
+
case 4: return [4 /*yield*/, tx.objectStore(exports.currentSequenceKey).put({ sessionId: sessionId, events: [event], tabId: this.tabId })];
|
|
170
324
|
case 5:
|
|
171
325
|
_a.sent();
|
|
172
326
|
this.recordSuccess();
|
|
327
|
+
cancelTimeout();
|
|
173
328
|
return [2 /*return*/, undefined];
|
|
174
329
|
case 6:
|
|
175
|
-
|
|
176
|
-
return [
|
|
330
|
+
ownedSequence = sequenceEvents;
|
|
331
|
+
if (!!ownedSequence) return [3 /*break*/, 8];
|
|
332
|
+
return [4 /*yield*/, tx.objectStore(exports.currentSequenceKey).put({ sessionId: sessionId, events: [event], tabId: this.tabId })];
|
|
177
333
|
case 7:
|
|
334
|
+
_a.sent();
|
|
335
|
+
this.recordSuccess();
|
|
336
|
+
cancelTimeout();
|
|
337
|
+
return [2 /*return*/, undefined];
|
|
338
|
+
case 8:
|
|
339
|
+
if (!!this.shouldSplitEventsList(ownedSequence.events, event)) return [3 /*break*/, 10];
|
|
340
|
+
return [4 /*yield*/, tx
|
|
341
|
+
.objectStore(exports.currentSequenceKey)
|
|
342
|
+
.put({ sessionId: sessionId, events: ownedSequence.events.concat(event), tabId: this.tabId })];
|
|
343
|
+
case 9:
|
|
344
|
+
_a.sent();
|
|
345
|
+
this.recordSuccess();
|
|
346
|
+
cancelTimeout();
|
|
347
|
+
return [2 /*return*/, undefined];
|
|
348
|
+
case 10:
|
|
349
|
+
eventsToSend = ownedSequence.events;
|
|
350
|
+
return [4 /*yield*/, tx.objectStore(exports.currentSequenceKey).put({ sessionId: sessionId, events: [event], tabId: this.tabId })];
|
|
351
|
+
case 11:
|
|
178
352
|
_a.sent();
|
|
179
353
|
return [4 /*yield*/, tx.objectStore(exports.sequencesToSendKey).put({
|
|
180
354
|
sessionId: sessionId,
|
|
181
355
|
events: eventsToSend,
|
|
356
|
+
tabId: this.tabId,
|
|
182
357
|
})];
|
|
183
|
-
case
|
|
358
|
+
case 12:
|
|
184
359
|
sequenceId = _a.sent();
|
|
185
360
|
this.recordSuccess();
|
|
361
|
+
cancelTimeout();
|
|
186
362
|
return [2 /*return*/, {
|
|
187
363
|
events: eventsToSend,
|
|
188
364
|
sessionId: sessionId,
|
|
189
365
|
sequenceId: sequenceId,
|
|
190
366
|
}];
|
|
191
|
-
case
|
|
367
|
+
case 13:
|
|
192
368
|
e_3 = _a.sent();
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
369
|
+
if (!timedOut) {
|
|
370
|
+
errorLogged = true;
|
|
371
|
+
(0, is_abort_error_1.logIdbError)(this.loggerProvider, "".concat(messages_1.STORAGE_FAILURE, ": ").concat(e_3), e_3);
|
|
372
|
+
this.recordFailure();
|
|
373
|
+
}
|
|
374
|
+
return [3 /*break*/, 14];
|
|
375
|
+
case 14: return [2 /*return*/, undefined];
|
|
198
376
|
}
|
|
199
377
|
});
|
|
200
378
|
}); };
|
|
@@ -207,6 +385,7 @@ var SessionReplayEventsIDBStore = /** @class */ (function (_super) {
|
|
|
207
385
|
return [4 /*yield*/, this.db.put(exports.sequencesToSendKey, {
|
|
208
386
|
sessionId: sessionId,
|
|
209
387
|
events: events,
|
|
388
|
+
tabId: this.tabId,
|
|
210
389
|
})];
|
|
211
390
|
case 1:
|
|
212
391
|
sequenceId = _a.sent();
|
|
@@ -247,8 +426,14 @@ var SessionReplayEventsIDBStore = /** @class */ (function (_super) {
|
|
|
247
426
|
});
|
|
248
427
|
}); };
|
|
249
428
|
_this.db = args.db;
|
|
429
|
+
_this.tabId = args.tabId;
|
|
250
430
|
_this.onPersistentFailure = args.onPersistentFailure;
|
|
251
|
-
|
|
431
|
+
// Default threshold of 1: fall back to memory immediately on the first IDB failure.
|
|
432
|
+
// Session replay correctness is far more important than persistence, and IDB errors
|
|
433
|
+
// are typically the symptom of a deeper problem (storage pressure, locked DB, broken
|
|
434
|
+
// browser implementation) that won't recover within a single session. Memory store
|
|
435
|
+
// is always safe — fall back early.
|
|
436
|
+
_this.consecutiveFailureThreshold = (_a = args.consecutiveFailureThreshold) !== null && _a !== void 0 ? _a : 1;
|
|
252
437
|
return _this;
|
|
253
438
|
}
|
|
254
439
|
SessionReplayEventsIDBStore.prototype.recordFailure = function () {
|
|
@@ -263,20 +448,22 @@ var SessionReplayEventsIDBStore = /** @class */ (function (_super) {
|
|
|
263
448
|
this.consecutiveFailures = 0;
|
|
264
449
|
};
|
|
265
450
|
SessionReplayEventsIDBStore.new = function (type, args) {
|
|
451
|
+
var _a;
|
|
266
452
|
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
|
267
|
-
var dbSuffix, dbName, db, e_6;
|
|
268
|
-
return tslib_1.__generator(this, function (
|
|
269
|
-
switch (
|
|
453
|
+
var dbSuffix, dbName, db, tabId, e_6;
|
|
454
|
+
return tslib_1.__generator(this, function (_b) {
|
|
455
|
+
switch (_b.label) {
|
|
270
456
|
case 0:
|
|
271
|
-
|
|
457
|
+
_b.trys.push([0, 2, , 3]);
|
|
272
458
|
dbSuffix = type === 'replay' ? '' : "_".concat(type);
|
|
273
459
|
dbName = "".concat(args.apiKey.substring(0, 10), "_amp_session_replay_events").concat(dbSuffix);
|
|
274
460
|
return [4 /*yield*/, (0, exports.createStore)(dbName)];
|
|
275
461
|
case 1:
|
|
276
|
-
db =
|
|
277
|
-
|
|
462
|
+
db = _b.sent();
|
|
463
|
+
tabId = (_a = args.tabId) !== null && _a !== void 0 ? _a : generateUUID();
|
|
464
|
+
return [2 /*return*/, new SessionReplayEventsIDBStore(tslib_1.__assign(tslib_1.__assign({}, args), { db: db, tabId: tabId }))];
|
|
278
465
|
case 2:
|
|
279
|
-
e_6 =
|
|
466
|
+
e_6 = _b.sent();
|
|
280
467
|
(0, is_abort_error_1.logIdbError)(args.loggerProvider, "".concat(messages_1.STORAGE_FAILURE, ": ").concat(e_6), e_6);
|
|
281
468
|
return [3 /*break*/, 3];
|
|
282
469
|
case 3: return [2 /*return*/];
|
|
@@ -286,7 +473,7 @@ var SessionReplayEventsIDBStore = /** @class */ (function (_super) {
|
|
|
286
473
|
};
|
|
287
474
|
SessionReplayEventsIDBStore.prototype.getCurrentSequenceEvents = function (sessionId) {
|
|
288
475
|
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
|
289
|
-
var
|
|
476
|
+
var record, _tabId, rest, allEvents, _a, _b, record, _tabId, rest, e_7_1;
|
|
290
477
|
var e_7, _c;
|
|
291
478
|
return tslib_1.__generator(this, function (_d) {
|
|
292
479
|
switch (_d.label) {
|
|
@@ -294,11 +481,16 @@ var SessionReplayEventsIDBStore = /** @class */ (function (_super) {
|
|
|
294
481
|
if (!sessionId) return [3 /*break*/, 2];
|
|
295
482
|
return [4 /*yield*/, this.db.get('sessionCurrentSequence', sessionId)];
|
|
296
483
|
case 1:
|
|
297
|
-
|
|
298
|
-
if (!
|
|
484
|
+
record = _d.sent();
|
|
485
|
+
if (!record) {
|
|
486
|
+
return [2 /*return*/, undefined];
|
|
487
|
+
}
|
|
488
|
+
// Only return our own tab's record (or legacy untagged records).
|
|
489
|
+
if (record.tabId && record.tabId !== this.tabId) {
|
|
299
490
|
return [2 /*return*/, undefined];
|
|
300
491
|
}
|
|
301
|
-
|
|
492
|
+
_tabId = record.tabId, rest = tslib_1.__rest(record, ["tabId"]);
|
|
493
|
+
return [2 /*return*/, [rest]];
|
|
302
494
|
case 2:
|
|
303
495
|
allEvents = [];
|
|
304
496
|
_d.label = 3;
|
|
@@ -310,8 +502,12 @@ var SessionReplayEventsIDBStore = /** @class */ (function (_super) {
|
|
|
310
502
|
_d.label = 5;
|
|
311
503
|
case 5:
|
|
312
504
|
if (!!_b.done) return [3 /*break*/, 7];
|
|
313
|
-
|
|
314
|
-
|
|
505
|
+
record = _b.value;
|
|
506
|
+
if (record.tabId && record.tabId !== this.tabId) {
|
|
507
|
+
return [3 /*break*/, 6];
|
|
508
|
+
}
|
|
509
|
+
_tabId = record.tabId, rest = tslib_1.__rest(record, ["tabId"]);
|
|
510
|
+
allEvents.push(rest);
|
|
315
511
|
_d.label = 6;
|
|
316
512
|
case 6:
|
|
317
513
|
_b = _a.next();
|