@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,11 +1,92 @@
|
|
|
1
|
-
import { __assign, __awaiter, __extends, __generator, __values } from "tslib";
|
|
1
|
+
import { __assign, __awaiter, __extends, __generator, __rest, __values } from "tslib";
|
|
2
2
|
import { openDB } from 'idb';
|
|
3
3
|
import { STORAGE_FAILURE } from '../messages';
|
|
4
4
|
import { BaseEventsStore } from './base-events-store';
|
|
5
5
|
import { logIdbError } from '../utils/is-abort-error';
|
|
6
|
+
// crypto.randomUUID() requires a secure context (https). Fall back to a
|
|
7
|
+
// Math.random-based UUID for http origins or older browsers — tab IDs don't
|
|
8
|
+
// need to be cryptographically secure, just unique within a session.
|
|
9
|
+
export function generateUUID() {
|
|
10
|
+
try {
|
|
11
|
+
return crypto.randomUUID();
|
|
12
|
+
}
|
|
13
|
+
catch (_a) {
|
|
14
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
15
|
+
var r = (Math.random() * 16) | 0;
|
|
16
|
+
return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
6
20
|
export var currentSequenceKey = 'sessionCurrentSequence';
|
|
7
21
|
export var sequencesToSendKey = 'sequencesToSend';
|
|
8
22
|
export var remoteConfigKey = 'remoteConfig';
|
|
23
|
+
// Timeout for openDB at init. IDB openDB can hang forever when another tab
|
|
24
|
+
// holds an open connection during a version upgrade, or when the DB is in a
|
|
25
|
+
// "closing" state (documented Chrome behaviour). When that happens we want
|
|
26
|
+
// SessionReplayEventsIDBStore.new() to bail out so the caller can fall back
|
|
27
|
+
// to the in-memory store.
|
|
28
|
+
export var OPEN_DB_TIMEOUT_MS = 2000;
|
|
29
|
+
// Timeout for per-operation tx.done settlement. Mid-recording a readwrite
|
|
30
|
+
// transaction can stall (storage pressure in some browsers stalls instead of
|
|
31
|
+
// throwing); without a timeout, recordFailure() is never called and the
|
|
32
|
+
// memory fallback never triggers. The transaction may still settle later;
|
|
33
|
+
// the timedOut flag prevents double-counting alongside errorLogged.
|
|
34
|
+
export var TX_DONE_TIMEOUT_MS = 5000;
|
|
35
|
+
/**
|
|
36
|
+
* Race a promise against a timeout. Resolves/rejects with the original
|
|
37
|
+
* promise's value when it settles first; rejects with a timeout error if
|
|
38
|
+
* `ms` elapses first. Either way the timer is cleared so we don't leak
|
|
39
|
+
* pending setTimeouts.
|
|
40
|
+
*/
|
|
41
|
+
export function withTimeout(promise, ms, message) {
|
|
42
|
+
if (message === void 0) { message = 'IDB operation timed out'; }
|
|
43
|
+
return new Promise(function (resolve, reject) {
|
|
44
|
+
var timer = setTimeout(function () { return reject(new Error("".concat(message, " after ").concat(ms, "ms"))); }, ms);
|
|
45
|
+
promise.then(function (v) {
|
|
46
|
+
clearTimeout(timer);
|
|
47
|
+
resolve(v);
|
|
48
|
+
}, function (e) {
|
|
49
|
+
clearTimeout(timer);
|
|
50
|
+
reject(e);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Arms a watchdog that fires `onTimeout` only if `tx.done` hasn't settled
|
|
56
|
+
* within `ms`. A "soft cancel" returned to the caller suppresses the timeout
|
|
57
|
+
* if the synchronous operation body completes without exception — important
|
|
58
|
+
* because in test environments fake timers can fire ahead of tx.done's commit
|
|
59
|
+
* microtask. Production behaviour is preserved: a genuinely stalled
|
|
60
|
+
* transaction (no commit, no error, individual op promises never resolved)
|
|
61
|
+
* cannot reach the soft-cancel call and the timeout fires as designed.
|
|
62
|
+
*
|
|
63
|
+
* Soft-cancel is only invoked once the awaits inside the operation's outer
|
|
64
|
+
* try block have all returned — i.e. the IDB driver acknowledged each
|
|
65
|
+
* individual put/get. If the driver accepts a put but the underlying
|
|
66
|
+
* transaction silently fails to commit (the production stall scenario),
|
|
67
|
+
* tx.done still hasn't settled, and recordFailure must NOT have been
|
|
68
|
+
* suppressed. The fix for that scenario in production is the tx.done.catch
|
|
69
|
+
* handler attached separately by callers, which catches the eventual abort.
|
|
70
|
+
*
|
|
71
|
+
* The soft-cancel pattern only suppresses the timeout for the "all puts
|
|
72
|
+
* resolved successfully" case — a case where tx.done is overwhelmingly
|
|
73
|
+
* likely to settle imminently in production. If it doesn't, callers can
|
|
74
|
+
* still detect the failure on the NEXT operation (which will fail to open
|
|
75
|
+
* a transaction or hit the same pressure), because all three methods use
|
|
76
|
+
* readwrite transactions on the same two stores, which IDB serializes:
|
|
77
|
+
* if T1's tx.done never settles, T2 is blocked waiting for T1 to commit or
|
|
78
|
+
* abort — T2's put/get requests never resolve, T2 never reaches its
|
|
79
|
+
* soft-cancel, and T2's watchdog fires, calling recordFailure().
|
|
80
|
+
*/
|
|
81
|
+
function armTxDoneTimeout(txDone, ms, onTimeout) {
|
|
82
|
+
var timer = setTimeout(onTimeout, ms);
|
|
83
|
+
// Belt-and-braces: clear the timer when tx.done settles, even though the
|
|
84
|
+
// primary cancel path is the caller's success-path cancel(). This covers
|
|
85
|
+
// the case where tx.done settles with no caller cancellation (shouldn't
|
|
86
|
+
// happen in current code paths, but cheap insurance).
|
|
87
|
+
txDone.then(function () { return clearTimeout(timer); }, function () { return clearTimeout(timer); });
|
|
88
|
+
return function () { return clearTimeout(timer); };
|
|
89
|
+
}
|
|
9
90
|
export var defineObjectStores = function (db) {
|
|
10
91
|
var sequencesStore;
|
|
11
92
|
var currentSequenceStore;
|
|
@@ -29,10 +110,16 @@ export var defineObjectStores = function (db) {
|
|
|
29
110
|
export var createStore = function (dbName) { return __awaiter(void 0, void 0, void 0, function () {
|
|
30
111
|
return __generator(this, function (_a) {
|
|
31
112
|
switch (_a.label) {
|
|
32
|
-
case 0: return [4 /*yield*/, openDB(dbName, 1, {
|
|
113
|
+
case 0: return [4 /*yield*/, withTimeout(openDB(dbName, 1, {
|
|
33
114
|
upgrade: defineObjectStores,
|
|
34
|
-
})];
|
|
35
|
-
case 1:
|
|
115
|
+
}), OPEN_DB_TIMEOUT_MS, 'IDB openDB timed out')];
|
|
116
|
+
case 1:
|
|
117
|
+
// Wrap openDB with a timeout so a hung connection (foreign tab holding an
|
|
118
|
+
// open handle during version upgrade, or "closing" DB) doesn't block the
|
|
119
|
+
// SDK from initialising. On timeout this rejects, which propagates up to
|
|
120
|
+
// SessionReplayEventsIDBStore.new()'s catch block, returning undefined and
|
|
121
|
+
// triggering the memory fallback.
|
|
122
|
+
return [2 /*return*/, _a.sent()];
|
|
36
123
|
}
|
|
37
124
|
});
|
|
38
125
|
}); };
|
|
@@ -45,12 +132,13 @@ var SessionReplayEventsIDBStore = /** @class */ (function (_super) {
|
|
|
45
132
|
_this.consecutiveFailures = 0;
|
|
46
133
|
_this.hasTriggeredFallback = false;
|
|
47
134
|
_this.getSequencesToSend = function () { return __awaiter(_this, void 0, void 0, function () {
|
|
48
|
-
var errorLogged, sequences, tx, cursor, _a, sessionId, events, e_1;
|
|
135
|
+
var errorLogged, timedOut, sequences, tx, cancelTimeout, cursor, _a, sessionId, events, e_1;
|
|
49
136
|
var _this = this;
|
|
50
137
|
return __generator(this, function (_b) {
|
|
51
138
|
switch (_b.label) {
|
|
52
139
|
case 0:
|
|
53
140
|
errorLogged = false;
|
|
141
|
+
timedOut = false;
|
|
54
142
|
_b.label = 1;
|
|
55
143
|
case 1:
|
|
56
144
|
_b.trys.push([1, 6, , 7]);
|
|
@@ -58,11 +146,19 @@ var SessionReplayEventsIDBStore = /** @class */ (function (_super) {
|
|
|
58
146
|
tx = this.db.transaction('sequencesToSend');
|
|
59
147
|
// Attach a catch handler immediately so tx.done rejections (e.g. AbortError after
|
|
60
148
|
// cursor traversal completes) are always handled without blocking the return path.
|
|
61
|
-
// The errorLogged
|
|
62
|
-
//
|
|
149
|
+
// The errorLogged / timedOut flags prevent double-logging and double-recording
|
|
150
|
+
// when the outer catch (or the timeout race) already fired for the same abort.
|
|
63
151
|
tx.done.catch(function (e) {
|
|
64
|
-
if (!errorLogged) {
|
|
152
|
+
if (!errorLogged && !timedOut) {
|
|
65
153
|
logIdbError(_this.loggerProvider, "".concat(STORAGE_FAILURE, ": ").concat(e), e);
|
|
154
|
+
_this.recordFailure();
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
cancelTimeout = armTxDoneTimeout(tx.done, TX_DONE_TIMEOUT_MS, function () {
|
|
158
|
+
if (!errorLogged && !timedOut) {
|
|
159
|
+
timedOut = true;
|
|
160
|
+
logIdbError(_this.loggerProvider, "".concat(STORAGE_FAILURE, ": transaction timed out"));
|
|
161
|
+
_this.recordFailure();
|
|
66
162
|
}
|
|
67
163
|
});
|
|
68
164
|
return [4 /*yield*/, tx.store.openCursor()];
|
|
@@ -72,6 +168,12 @@ var SessionReplayEventsIDBStore = /** @class */ (function (_super) {
|
|
|
72
168
|
case 3:
|
|
73
169
|
if (!cursor) return [3 /*break*/, 5];
|
|
74
170
|
_a = cursor.value, sessionId = _a.sessionId, events = _a.events;
|
|
171
|
+
// Return all completed sequences regardless of tabId. Filtering by tab
|
|
172
|
+
// would cause event loss on page reload: a new store instance gets a
|
|
173
|
+
// fresh in-memory UUID and would never see sequences written by the
|
|
174
|
+
// previous instance. Completed sequences are safe to flush by any
|
|
175
|
+
// tab/instance; the server deduplicates, and cleanUpSessionEventsStore
|
|
176
|
+
// on an already-deleted key is a no-op.
|
|
75
177
|
sequences.push({
|
|
76
178
|
events: events,
|
|
77
179
|
sequenceId: cursor.key,
|
|
@@ -83,113 +185,187 @@ var SessionReplayEventsIDBStore = /** @class */ (function (_super) {
|
|
|
83
185
|
return [3 /*break*/, 3];
|
|
84
186
|
case 5:
|
|
85
187
|
this.recordSuccess();
|
|
188
|
+
cancelTimeout();
|
|
86
189
|
return [2 /*return*/, sequences];
|
|
87
190
|
case 6:
|
|
88
191
|
e_1 = _b.sent();
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
192
|
+
if (!timedOut) {
|
|
193
|
+
errorLogged = true;
|
|
194
|
+
logIdbError(this.loggerProvider, "".concat(STORAGE_FAILURE, ": ").concat(e_1), e_1);
|
|
195
|
+
this.recordFailure();
|
|
196
|
+
}
|
|
92
197
|
return [3 /*break*/, 7];
|
|
93
198
|
case 7: return [2 /*return*/, undefined];
|
|
94
199
|
}
|
|
95
200
|
});
|
|
96
201
|
}); };
|
|
97
202
|
_this.storeCurrentSequence = function (sessionId) { return __awaiter(_this, void 0, void 0, function () {
|
|
98
|
-
var currentSequenceData, sequenceId, e_2;
|
|
203
|
+
var errorLogged, timedOut, tx, cancelTimeout, currentSequenceData, sequenceId, _tabId, rest, e_2;
|
|
204
|
+
var _this = this;
|
|
99
205
|
return __generator(this, function (_a) {
|
|
100
206
|
switch (_a.label) {
|
|
101
207
|
case 0:
|
|
102
|
-
|
|
103
|
-
|
|
208
|
+
errorLogged = false;
|
|
209
|
+
timedOut = false;
|
|
210
|
+
_a.label = 1;
|
|
104
211
|
case 1:
|
|
212
|
+
_a.trys.push([1, 5, , 6]);
|
|
213
|
+
tx = this.db.transaction([currentSequenceKey, sequencesToSendKey], 'readwrite');
|
|
214
|
+
tx.done.catch(function (e) {
|
|
215
|
+
if (!errorLogged && !timedOut) {
|
|
216
|
+
logIdbError(_this.loggerProvider, "".concat(STORAGE_FAILURE, ": ").concat(e), e);
|
|
217
|
+
_this.recordFailure();
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
cancelTimeout = armTxDoneTimeout(tx.done, TX_DONE_TIMEOUT_MS, function () {
|
|
221
|
+
if (!errorLogged && !timedOut) {
|
|
222
|
+
timedOut = true;
|
|
223
|
+
logIdbError(_this.loggerProvider, "".concat(STORAGE_FAILURE, ": transaction timed out"));
|
|
224
|
+
_this.recordFailure();
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
return [4 /*yield*/, tx.objectStore(currentSequenceKey).get(sessionId)];
|
|
228
|
+
case 2:
|
|
105
229
|
currentSequenceData = _a.sent();
|
|
106
|
-
if
|
|
107
|
-
|
|
230
|
+
// Skip promotion if the slot is empty or owned by another tab — let the owning
|
|
231
|
+
// tab promote its own events on its next addEventToCurrentSequence/storeCurrentSequence
|
|
232
|
+
// call (via Bug 1's foreign-tab promotion path).
|
|
233
|
+
// Don't call recordSuccess() here: no write was performed, so this is not
|
|
234
|
+
// evidence the storage layer is healthy — leave the failure counter unchanged.
|
|
235
|
+
if (!currentSequenceData || (currentSequenceData.tabId && currentSequenceData.tabId !== this.tabId)) {
|
|
236
|
+
cancelTimeout();
|
|
108
237
|
return [2 /*return*/, undefined];
|
|
109
238
|
}
|
|
110
|
-
|
|
239
|
+
// Skip empty sequences — no point writing a zero-event row to sequencesToSend.
|
|
240
|
+
if (currentSequenceData.events.length === 0) {
|
|
241
|
+
cancelTimeout();
|
|
242
|
+
return [2 /*return*/, undefined];
|
|
243
|
+
}
|
|
244
|
+
return [4 /*yield*/, tx.objectStore(sequencesToSendKey).put({
|
|
111
245
|
sessionId: sessionId,
|
|
112
246
|
events: currentSequenceData.events,
|
|
247
|
+
tabId: this.tabId,
|
|
113
248
|
})];
|
|
114
|
-
case
|
|
249
|
+
case 3:
|
|
115
250
|
sequenceId = _a.sent();
|
|
116
|
-
return [4 /*yield*/,
|
|
251
|
+
return [4 /*yield*/, tx.objectStore(currentSequenceKey).put({
|
|
117
252
|
sessionId: sessionId,
|
|
118
253
|
events: [],
|
|
254
|
+
tabId: this.tabId,
|
|
119
255
|
})];
|
|
120
|
-
case
|
|
256
|
+
case 4:
|
|
121
257
|
_a.sent();
|
|
122
258
|
this.recordSuccess();
|
|
123
|
-
|
|
124
|
-
|
|
259
|
+
cancelTimeout();
|
|
260
|
+
_tabId = currentSequenceData.tabId, rest = __rest(currentSequenceData, ["tabId"]);
|
|
261
|
+
return [2 /*return*/, __assign(__assign({}, rest), { sessionId: sessionId, sequenceId: sequenceId })];
|
|
262
|
+
case 5:
|
|
125
263
|
e_2 = _a.sent();
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
264
|
+
if (!timedOut) {
|
|
265
|
+
errorLogged = true;
|
|
266
|
+
logIdbError(this.loggerProvider, "".concat(STORAGE_FAILURE, ": ").concat(e_2), e_2);
|
|
267
|
+
this.recordFailure();
|
|
268
|
+
}
|
|
269
|
+
return [3 /*break*/, 6];
|
|
270
|
+
case 6: return [2 /*return*/, undefined];
|
|
130
271
|
}
|
|
131
272
|
});
|
|
132
273
|
}); };
|
|
133
274
|
_this.addEventToCurrentSequence = function (sessionId, event) { return __awaiter(_this, void 0, void 0, function () {
|
|
134
|
-
var errorLogged, tx, sequenceEvents, eventsToSend, sequenceId, e_3;
|
|
275
|
+
var errorLogged, timedOut, tx, cancelTimeout, sequenceEvents, ownedSequence, eventsToSend, sequenceId, e_3;
|
|
135
276
|
var _this = this;
|
|
136
277
|
return __generator(this, function (_a) {
|
|
137
278
|
switch (_a.label) {
|
|
138
279
|
case 0:
|
|
139
280
|
errorLogged = false;
|
|
281
|
+
timedOut = false;
|
|
140
282
|
_a.label = 1;
|
|
141
283
|
case 1:
|
|
142
|
-
_a.trys.push([1,
|
|
284
|
+
_a.trys.push([1, 13, , 14]);
|
|
143
285
|
tx = this.db.transaction([currentSequenceKey, sequencesToSendKey], 'readwrite');
|
|
144
286
|
// Attach a catch handler immediately so tx.done rejections (e.g. AbortError after
|
|
145
287
|
// put succeeds but before auto-commit) are always handled without blocking.
|
|
146
|
-
// The errorLogged
|
|
147
|
-
//
|
|
288
|
+
// The errorLogged / timedOut flags prevent double-logging when the outer catch
|
|
289
|
+
// (or the timeout) already fired for the same transaction.
|
|
148
290
|
tx.done.catch(function (e) {
|
|
149
|
-
if (!errorLogged) {
|
|
291
|
+
if (!errorLogged && !timedOut) {
|
|
150
292
|
logIdbError(_this.loggerProvider, "".concat(STORAGE_FAILURE, ": ").concat(e), e);
|
|
293
|
+
_this.recordFailure();
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
cancelTimeout = armTxDoneTimeout(tx.done, TX_DONE_TIMEOUT_MS, function () {
|
|
297
|
+
if (!errorLogged && !timedOut) {
|
|
298
|
+
timedOut = true;
|
|
299
|
+
logIdbError(_this.loggerProvider, "".concat(STORAGE_FAILURE, ": transaction timed out"));
|
|
300
|
+
_this.recordFailure();
|
|
151
301
|
}
|
|
152
302
|
});
|
|
153
303
|
return [4 /*yield*/, tx.objectStore(currentSequenceKey).get(sessionId)];
|
|
154
304
|
case 2:
|
|
155
305
|
sequenceEvents = _a.sent();
|
|
156
|
-
if (
|
|
157
|
-
|
|
306
|
+
if (!((sequenceEvents === null || sequenceEvents === void 0 ? void 0 : sequenceEvents.tabId) && sequenceEvents.tabId !== this.tabId)) return [3 /*break*/, 6];
|
|
307
|
+
if (!(sequenceEvents.events.length > 0)) return [3 /*break*/, 4];
|
|
308
|
+
return [4 /*yield*/, tx.objectStore(sequencesToSendKey).put({
|
|
309
|
+
sessionId: sessionId,
|
|
310
|
+
events: sequenceEvents.events,
|
|
311
|
+
tabId: sequenceEvents.tabId,
|
|
312
|
+
})];
|
|
158
313
|
case 3:
|
|
159
314
|
_a.sent();
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
case 4:
|
|
163
|
-
if (!!this.shouldSplitEventsList(sequenceEvents.events, event)) return [3 /*break*/, 6];
|
|
164
|
-
return [4 /*yield*/, tx.objectStore(currentSequenceKey).put({ sessionId: sessionId, events: sequenceEvents.events.concat(event) })];
|
|
315
|
+
_a.label = 4;
|
|
316
|
+
case 4: return [4 /*yield*/, tx.objectStore(currentSequenceKey).put({ sessionId: sessionId, events: [event], tabId: this.tabId })];
|
|
165
317
|
case 5:
|
|
166
318
|
_a.sent();
|
|
167
319
|
this.recordSuccess();
|
|
320
|
+
cancelTimeout();
|
|
168
321
|
return [2 /*return*/, undefined];
|
|
169
322
|
case 6:
|
|
170
|
-
|
|
171
|
-
return [
|
|
323
|
+
ownedSequence = sequenceEvents;
|
|
324
|
+
if (!!ownedSequence) return [3 /*break*/, 8];
|
|
325
|
+
return [4 /*yield*/, tx.objectStore(currentSequenceKey).put({ sessionId: sessionId, events: [event], tabId: this.tabId })];
|
|
172
326
|
case 7:
|
|
327
|
+
_a.sent();
|
|
328
|
+
this.recordSuccess();
|
|
329
|
+
cancelTimeout();
|
|
330
|
+
return [2 /*return*/, undefined];
|
|
331
|
+
case 8:
|
|
332
|
+
if (!!this.shouldSplitEventsList(ownedSequence.events, event)) return [3 /*break*/, 10];
|
|
333
|
+
return [4 /*yield*/, tx
|
|
334
|
+
.objectStore(currentSequenceKey)
|
|
335
|
+
.put({ sessionId: sessionId, events: ownedSequence.events.concat(event), tabId: this.tabId })];
|
|
336
|
+
case 9:
|
|
337
|
+
_a.sent();
|
|
338
|
+
this.recordSuccess();
|
|
339
|
+
cancelTimeout();
|
|
340
|
+
return [2 /*return*/, undefined];
|
|
341
|
+
case 10:
|
|
342
|
+
eventsToSend = ownedSequence.events;
|
|
343
|
+
return [4 /*yield*/, tx.objectStore(currentSequenceKey).put({ sessionId: sessionId, events: [event], tabId: this.tabId })];
|
|
344
|
+
case 11:
|
|
173
345
|
_a.sent();
|
|
174
346
|
return [4 /*yield*/, tx.objectStore(sequencesToSendKey).put({
|
|
175
347
|
sessionId: sessionId,
|
|
176
348
|
events: eventsToSend,
|
|
349
|
+
tabId: this.tabId,
|
|
177
350
|
})];
|
|
178
|
-
case
|
|
351
|
+
case 12:
|
|
179
352
|
sequenceId = _a.sent();
|
|
180
353
|
this.recordSuccess();
|
|
354
|
+
cancelTimeout();
|
|
181
355
|
return [2 /*return*/, {
|
|
182
356
|
events: eventsToSend,
|
|
183
357
|
sessionId: sessionId,
|
|
184
358
|
sequenceId: sequenceId,
|
|
185
359
|
}];
|
|
186
|
-
case
|
|
360
|
+
case 13:
|
|
187
361
|
e_3 = _a.sent();
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
362
|
+
if (!timedOut) {
|
|
363
|
+
errorLogged = true;
|
|
364
|
+
logIdbError(this.loggerProvider, "".concat(STORAGE_FAILURE, ": ").concat(e_3), e_3);
|
|
365
|
+
this.recordFailure();
|
|
366
|
+
}
|
|
367
|
+
return [3 /*break*/, 14];
|
|
368
|
+
case 14: return [2 /*return*/, undefined];
|
|
193
369
|
}
|
|
194
370
|
});
|
|
195
371
|
}); };
|
|
@@ -202,6 +378,7 @@ var SessionReplayEventsIDBStore = /** @class */ (function (_super) {
|
|
|
202
378
|
return [4 /*yield*/, this.db.put(sequencesToSendKey, {
|
|
203
379
|
sessionId: sessionId,
|
|
204
380
|
events: events,
|
|
381
|
+
tabId: this.tabId,
|
|
205
382
|
})];
|
|
206
383
|
case 1:
|
|
207
384
|
sequenceId = _a.sent();
|
|
@@ -242,8 +419,14 @@ var SessionReplayEventsIDBStore = /** @class */ (function (_super) {
|
|
|
242
419
|
});
|
|
243
420
|
}); };
|
|
244
421
|
_this.db = args.db;
|
|
422
|
+
_this.tabId = args.tabId;
|
|
245
423
|
_this.onPersistentFailure = args.onPersistentFailure;
|
|
246
|
-
|
|
424
|
+
// Default threshold of 1: fall back to memory immediately on the first IDB failure.
|
|
425
|
+
// Session replay correctness is far more important than persistence, and IDB errors
|
|
426
|
+
// are typically the symptom of a deeper problem (storage pressure, locked DB, broken
|
|
427
|
+
// browser implementation) that won't recover within a single session. Memory store
|
|
428
|
+
// is always safe — fall back early.
|
|
429
|
+
_this.consecutiveFailureThreshold = (_a = args.consecutiveFailureThreshold) !== null && _a !== void 0 ? _a : 1;
|
|
247
430
|
return _this;
|
|
248
431
|
}
|
|
249
432
|
SessionReplayEventsIDBStore.prototype.recordFailure = function () {
|
|
@@ -258,20 +441,22 @@ var SessionReplayEventsIDBStore = /** @class */ (function (_super) {
|
|
|
258
441
|
this.consecutiveFailures = 0;
|
|
259
442
|
};
|
|
260
443
|
SessionReplayEventsIDBStore.new = function (type, args) {
|
|
444
|
+
var _a;
|
|
261
445
|
return __awaiter(this, void 0, void 0, function () {
|
|
262
|
-
var dbSuffix, dbName, db, e_6;
|
|
263
|
-
return __generator(this, function (
|
|
264
|
-
switch (
|
|
446
|
+
var dbSuffix, dbName, db, tabId, e_6;
|
|
447
|
+
return __generator(this, function (_b) {
|
|
448
|
+
switch (_b.label) {
|
|
265
449
|
case 0:
|
|
266
|
-
|
|
450
|
+
_b.trys.push([0, 2, , 3]);
|
|
267
451
|
dbSuffix = type === 'replay' ? '' : "_".concat(type);
|
|
268
452
|
dbName = "".concat(args.apiKey.substring(0, 10), "_amp_session_replay_events").concat(dbSuffix);
|
|
269
453
|
return [4 /*yield*/, createStore(dbName)];
|
|
270
454
|
case 1:
|
|
271
|
-
db =
|
|
272
|
-
|
|
455
|
+
db = _b.sent();
|
|
456
|
+
tabId = (_a = args.tabId) !== null && _a !== void 0 ? _a : generateUUID();
|
|
457
|
+
return [2 /*return*/, new SessionReplayEventsIDBStore(__assign(__assign({}, args), { db: db, tabId: tabId }))];
|
|
273
458
|
case 2:
|
|
274
|
-
e_6 =
|
|
459
|
+
e_6 = _b.sent();
|
|
275
460
|
logIdbError(args.loggerProvider, "".concat(STORAGE_FAILURE, ": ").concat(e_6), e_6);
|
|
276
461
|
return [3 /*break*/, 3];
|
|
277
462
|
case 3: return [2 /*return*/];
|
|
@@ -281,7 +466,7 @@ var SessionReplayEventsIDBStore = /** @class */ (function (_super) {
|
|
|
281
466
|
};
|
|
282
467
|
SessionReplayEventsIDBStore.prototype.getCurrentSequenceEvents = function (sessionId) {
|
|
283
468
|
return __awaiter(this, void 0, void 0, function () {
|
|
284
|
-
var
|
|
469
|
+
var record, _tabId, rest, allEvents, _a, _b, record, _tabId, rest, e_7_1;
|
|
285
470
|
var e_7, _c;
|
|
286
471
|
return __generator(this, function (_d) {
|
|
287
472
|
switch (_d.label) {
|
|
@@ -289,11 +474,16 @@ var SessionReplayEventsIDBStore = /** @class */ (function (_super) {
|
|
|
289
474
|
if (!sessionId) return [3 /*break*/, 2];
|
|
290
475
|
return [4 /*yield*/, this.db.get('sessionCurrentSequence', sessionId)];
|
|
291
476
|
case 1:
|
|
292
|
-
|
|
293
|
-
if (!
|
|
477
|
+
record = _d.sent();
|
|
478
|
+
if (!record) {
|
|
479
|
+
return [2 /*return*/, undefined];
|
|
480
|
+
}
|
|
481
|
+
// Only return our own tab's record (or legacy untagged records).
|
|
482
|
+
if (record.tabId && record.tabId !== this.tabId) {
|
|
294
483
|
return [2 /*return*/, undefined];
|
|
295
484
|
}
|
|
296
|
-
|
|
485
|
+
_tabId = record.tabId, rest = __rest(record, ["tabId"]);
|
|
486
|
+
return [2 /*return*/, [rest]];
|
|
297
487
|
case 2:
|
|
298
488
|
allEvents = [];
|
|
299
489
|
_d.label = 3;
|
|
@@ -305,8 +495,12 @@ var SessionReplayEventsIDBStore = /** @class */ (function (_super) {
|
|
|
305
495
|
_d.label = 5;
|
|
306
496
|
case 5:
|
|
307
497
|
if (!!_b.done) return [3 /*break*/, 7];
|
|
308
|
-
|
|
309
|
-
|
|
498
|
+
record = _b.value;
|
|
499
|
+
if (record.tabId && record.tabId !== this.tabId) {
|
|
500
|
+
return [3 /*break*/, 6];
|
|
501
|
+
}
|
|
502
|
+
_tabId = record.tabId, rest = __rest(record, ["tabId"]);
|
|
503
|
+
allEvents.push(rest);
|
|
310
504
|
_d.label = 6;
|
|
311
505
|
case 6:
|
|
312
506
|
_b = _a.next();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events-idb-store.js","sourceRoot":"","sources":["../../../src/events/events-idb-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { DBSchema, IDBPDatabase, openDB } from 'idb';\nimport { STORAGE_FAILURE } from '../messages';\nimport { EventType, Events, SendingSequencesReturn } from '../typings/session-replay';\nimport { BaseEventsStore, InstanceArgs as BaseInstanceArgs } from './base-events-store';\nimport { logIdbError } from '../utils/is-abort-error';\n\nexport const currentSequenceKey = 'sessionCurrentSequence';\nexport const sequencesToSendKey = 'sequencesToSend';\nexport const remoteConfigKey = 'remoteConfig';\n\nexport interface SessionReplayDB extends DBSchema {\n sessionCurrentSequence: {\n key: number;\n value: Omit<SendingSequencesReturn<number>, 'sequenceId'>;\n };\n sequencesToSend: {\n key: number;\n value: Omit<SendingSequencesReturn<number>, 'sequenceId'>;\n indexes: { sessionId: string | number };\n };\n}\n\nexport const defineObjectStores = (db: IDBPDatabase<SessionReplayDB>) => {\n let sequencesStore;\n let currentSequenceStore;\n if (!db.objectStoreNames.contains(currentSequenceKey)) {\n currentSequenceStore = db.createObjectStore(currentSequenceKey, {\n keyPath: 'sessionId',\n });\n }\n if (!db.objectStoreNames.contains(sequencesToSendKey)) {\n sequencesStore = db.createObjectStore(sequencesToSendKey, {\n keyPath: 'sequenceId',\n autoIncrement: true,\n });\n sequencesStore.createIndex('sessionId', 'sessionId');\n }\n return {\n sequencesStore,\n currentSequenceStore,\n };\n};\n\nexport const createStore = async (dbName: string) => {\n return await openDB<SessionReplayDB>(dbName, 1, {\n upgrade: defineObjectStores,\n });\n};\n\ntype InstanceArgs = {\n apiKey: string;\n db: IDBPDatabase<SessionReplayDB>;\n onPersistentFailure?: () => void;\n consecutiveFailureThreshold?: number;\n} & BaseInstanceArgs;\n\nexport class SessionReplayEventsIDBStore extends BaseEventsStore<number> {\n private readonly db: IDBPDatabase<SessionReplayDB>;\n private readonly onPersistentFailure?: () => void;\n private readonly consecutiveFailureThreshold: number;\n private consecutiveFailures = 0;\n private hasTriggeredFallback = false;\n\n constructor(args: InstanceArgs) {\n super(args);\n this.db = args.db;\n this.onPersistentFailure = args.onPersistentFailure;\n this.consecutiveFailureThreshold = args.consecutiveFailureThreshold ?? 3;\n }\n\n private recordFailure() {\n this.consecutiveFailures++;\n if (!this.hasTriggeredFallback && this.consecutiveFailures >= this.consecutiveFailureThreshold) {\n this.hasTriggeredFallback = true;\n this.onPersistentFailure?.();\n }\n }\n\n private recordSuccess() {\n this.consecutiveFailures = 0;\n }\n\n static async new(type: EventType, args: Omit<InstanceArgs, 'db'>): Promise<SessionReplayEventsIDBStore | undefined> {\n try {\n const dbSuffix = type === 'replay' ? '' : `_${type}`;\n const dbName = `${args.apiKey.substring(0, 10)}_amp_session_replay_events${dbSuffix}`;\n const db = await createStore(dbName);\n return new SessionReplayEventsIDBStore({\n ...args,\n db,\n });\n } catch (e) {\n logIdbError(args.loggerProvider, `${STORAGE_FAILURE}: ${e as string}`, e);\n }\n return;\n }\n\n async getCurrentSequenceEvents(sessionId?: number) {\n if (sessionId) {\n const events = await this.db.get('sessionCurrentSequence', sessionId);\n if (!events) {\n return undefined;\n }\n return [events];\n }\n\n const allEvents = [];\n for (const events of await this.db.getAll('sessionCurrentSequence')) {\n allEvents.push(events);\n }\n\n return allEvents;\n }\n\n getSequencesToSend = async (): Promise<SendingSequencesReturn<number>[] | undefined> => {\n let errorLogged = false;\n try {\n const sequences: SendingSequencesReturn<number>[] = [];\n const tx = this.db.transaction('sequencesToSend');\n // Attach a catch handler immediately so tx.done rejections (e.g. AbortError after\n // cursor traversal completes) are always handled without blocking the return path.\n // The errorLogged flag prevents double-logging when the outer catch already fired\n // for the same abort (e.g. cursor.continue() threw mid-traversal).\n tx.done.catch((e: unknown) => {\n if (!errorLogged) {\n logIdbError(this.loggerProvider, `${STORAGE_FAILURE}: ${e as string}`, e);\n }\n });\n let cursor = await tx.store.openCursor();\n while (cursor) {\n const { sessionId, events } = cursor.value;\n sequences.push({\n events,\n sequenceId: cursor.key,\n sessionId,\n });\n cursor = await cursor.continue();\n }\n\n this.recordSuccess();\n return sequences;\n } catch (e) {\n errorLogged = true;\n logIdbError(this.loggerProvider, `${STORAGE_FAILURE}: ${e as string}`, e);\n this.recordFailure();\n }\n return undefined;\n };\n\n storeCurrentSequence = async (sessionId: number) => {\n try {\n const currentSequenceData = await this.db.get<'sessionCurrentSequence'>(currentSequenceKey, sessionId);\n if (!currentSequenceData) {\n this.recordSuccess();\n return undefined;\n }\n\n const sequenceId = await this.db.put<'sequencesToSend'>(sequencesToSendKey, {\n sessionId: sessionId,\n events: currentSequenceData.events,\n });\n\n await this.db.put<'sessionCurrentSequence'>(currentSequenceKey, {\n sessionId,\n events: [],\n });\n\n this.recordSuccess();\n return {\n ...currentSequenceData,\n sessionId,\n sequenceId,\n };\n } catch (e) {\n logIdbError(this.loggerProvider, `${STORAGE_FAILURE}: ${e as string}`, e);\n this.recordFailure();\n }\n return undefined;\n };\n\n addEventToCurrentSequence = async (sessionId: number, event: string) => {\n let errorLogged = false;\n try {\n // Always open a readwrite transaction over both stores so that the read and\n // any subsequent write are atomic. IDB serializes readwrite transactions on\n // overlapping stores, so concurrent fire-and-forget callers (events-manager\n // does not await this method) are queued by the engine rather than interleaving\n // — eliminating the TOCTOU race that a narrow-read + separate-write approach\n // would introduce on the split path.\n const tx = this.db.transaction([currentSequenceKey, sequencesToSendKey], 'readwrite');\n // Attach a catch handler immediately so tx.done rejections (e.g. AbortError after\n // put succeeds but before auto-commit) are always handled without blocking.\n // The errorLogged flag prevents double-logging when the outer catch already fired\n // for the same abort (e.g. a put() threw).\n tx.done.catch((e: unknown) => {\n if (!errorLogged) {\n logIdbError(this.loggerProvider, `${STORAGE_FAILURE}: ${e as string}`, e);\n }\n });\n const sequenceEvents = await tx.objectStore(currentSequenceKey).get(sessionId);\n\n if (!sequenceEvents) {\n await tx.objectStore(currentSequenceKey).put({ sessionId, events: [event] });\n this.recordSuccess();\n return undefined;\n }\n\n if (!this.shouldSplitEventsList(sequenceEvents.events, event)) {\n await tx.objectStore(currentSequenceKey).put({ sessionId, events: sequenceEvents.events.concat(event) });\n this.recordSuccess();\n return undefined;\n }\n\n // Split path: reset sessionCurrentSequence and write the old events to\n // sequencesToSend atomically within the same transaction.\n const eventsToSend = sequenceEvents.events;\n await tx.objectStore(currentSequenceKey).put({ sessionId, events: [event] });\n const sequenceId = await tx.objectStore(sequencesToSendKey).put({\n sessionId,\n events: eventsToSend,\n });\n\n this.recordSuccess();\n return {\n events: eventsToSend,\n sessionId,\n sequenceId,\n };\n } catch (e) {\n errorLogged = true;\n logIdbError(this.loggerProvider, `${STORAGE_FAILURE}: ${e as string}`, e);\n this.recordFailure();\n }\n return undefined;\n };\n\n storeSendingEvents = async (sessionId: number, events: Events) => {\n try {\n const sequenceId = await this.db.put<'sequencesToSend'>(sequencesToSendKey, {\n sessionId: sessionId,\n events: events,\n });\n this.recordSuccess();\n return sequenceId;\n } catch (e) {\n logIdbError(this.loggerProvider, `${STORAGE_FAILURE}: ${e as string}`, e);\n this.recordFailure();\n }\n return undefined;\n };\n\n cleanUpSessionEventsStore = async (_sessionId: number, sequenceId?: number) => {\n if (!sequenceId) {\n return;\n }\n try {\n await this.db.delete<'sequencesToSend'>(sequencesToSendKey, sequenceId);\n this.recordSuccess();\n } catch (e) {\n logIdbError(this.loggerProvider, `${STORAGE_FAILURE}: ${e as string}`, e);\n this.recordFailure();\n }\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"events-idb-store.js","sourceRoot":"","sources":["../../../src/events/events-idb-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { DBSchema, IDBPDatabase, openDB } from 'idb';\nimport { STORAGE_FAILURE } from '../messages';\nimport { EventType, Events, SendingSequencesReturn } from '../typings/session-replay';\nimport { BaseEventsStore, InstanceArgs as BaseInstanceArgs } from './base-events-store';\nimport { logIdbError } from '../utils/is-abort-error';\n\n// crypto.randomUUID() requires a secure context (https). Fall back to a\n// Math.random-based UUID for http origins or older browsers — tab IDs don't\n// need to be cryptographically secure, just unique within a session.\nexport function generateUUID(): string {\n try {\n return crypto.randomUUID();\n } catch {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);\n });\n }\n}\n\nexport const currentSequenceKey = 'sessionCurrentSequence';\nexport const sequencesToSendKey = 'sequencesToSend';\nexport const remoteConfigKey = 'remoteConfig';\n\n// Timeout for openDB at init. IDB openDB can hang forever when another tab\n// holds an open connection during a version upgrade, or when the DB is in a\n// \"closing\" state (documented Chrome behaviour). When that happens we want\n// SessionReplayEventsIDBStore.new() to bail out so the caller can fall back\n// to the in-memory store.\nexport const OPEN_DB_TIMEOUT_MS = 2000;\n\n// Timeout for per-operation tx.done settlement. Mid-recording a readwrite\n// transaction can stall (storage pressure in some browsers stalls instead of\n// throwing); without a timeout, recordFailure() is never called and the\n// memory fallback never triggers. The transaction may still settle later;\n// the timedOut flag prevents double-counting alongside errorLogged.\nexport const TX_DONE_TIMEOUT_MS = 5000;\n\n/**\n * Race a promise against a timeout. Resolves/rejects with the original\n * promise's value when it settles first; rejects with a timeout error if\n * `ms` elapses first. Either way the timer is cleared so we don't leak\n * pending setTimeouts.\n */\nexport function withTimeout<T>(promise: Promise<T>, ms: number, message = 'IDB operation timed out'): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => reject(new Error(`${message} after ${ms}ms`)), ms);\n promise.then(\n (v) => {\n clearTimeout(timer);\n resolve(v);\n },\n (e) => {\n clearTimeout(timer);\n reject(e);\n },\n );\n });\n}\n\n/**\n * Arms a watchdog that fires `onTimeout` only if `tx.done` hasn't settled\n * within `ms`. A \"soft cancel\" returned to the caller suppresses the timeout\n * if the synchronous operation body completes without exception — important\n * because in test environments fake timers can fire ahead of tx.done's commit\n * microtask. Production behaviour is preserved: a genuinely stalled\n * transaction (no commit, no error, individual op promises never resolved)\n * cannot reach the soft-cancel call and the timeout fires as designed.\n *\n * Soft-cancel is only invoked once the awaits inside the operation's outer\n * try block have all returned — i.e. the IDB driver acknowledged each\n * individual put/get. If the driver accepts a put but the underlying\n * transaction silently fails to commit (the production stall scenario),\n * tx.done still hasn't settled, and recordFailure must NOT have been\n * suppressed. The fix for that scenario in production is the tx.done.catch\n * handler attached separately by callers, which catches the eventual abort.\n *\n * The soft-cancel pattern only suppresses the timeout for the \"all puts\n * resolved successfully\" case — a case where tx.done is overwhelmingly\n * likely to settle imminently in production. If it doesn't, callers can\n * still detect the failure on the NEXT operation (which will fail to open\n * a transaction or hit the same pressure), because all three methods use\n * readwrite transactions on the same two stores, which IDB serializes:\n * if T1's tx.done never settles, T2 is blocked waiting for T1 to commit or\n * abort — T2's put/get requests never resolve, T2 never reaches its\n * soft-cancel, and T2's watchdog fires, calling recordFailure().\n */\nfunction armTxDoneTimeout(txDone: Promise<unknown>, ms: number, onTimeout: () => void): () => void {\n const timer = setTimeout(onTimeout, ms);\n // Belt-and-braces: clear the timer when tx.done settles, even though the\n // primary cancel path is the caller's success-path cancel(). This covers\n // the case where tx.done settles with no caller cancellation (shouldn't\n // happen in current code paths, but cheap insurance).\n txDone.then(\n () => clearTimeout(timer),\n () => clearTimeout(timer),\n );\n return () => clearTimeout(timer);\n}\n\nexport interface SessionReplayDB extends DBSchema {\n sessionCurrentSequence: {\n key: number;\n value: Omit<SendingSequencesReturn<number>, 'sequenceId'> & { tabId?: string };\n };\n sequencesToSend: {\n key: number;\n value: Omit<SendingSequencesReturn<number>, 'sequenceId'> & { tabId?: string };\n indexes: { sessionId: string | number };\n };\n}\n\nexport const defineObjectStores = (db: IDBPDatabase<SessionReplayDB>) => {\n let sequencesStore;\n let currentSequenceStore;\n if (!db.objectStoreNames.contains(currentSequenceKey)) {\n currentSequenceStore = db.createObjectStore(currentSequenceKey, {\n keyPath: 'sessionId',\n });\n }\n if (!db.objectStoreNames.contains(sequencesToSendKey)) {\n sequencesStore = db.createObjectStore(sequencesToSendKey, {\n keyPath: 'sequenceId',\n autoIncrement: true,\n });\n sequencesStore.createIndex('sessionId', 'sessionId');\n }\n return {\n sequencesStore,\n currentSequenceStore,\n };\n};\n\nexport const createStore = async (dbName: string) => {\n // Wrap openDB with a timeout so a hung connection (foreign tab holding an\n // open handle during version upgrade, or \"closing\" DB) doesn't block the\n // SDK from initialising. On timeout this rejects, which propagates up to\n // SessionReplayEventsIDBStore.new()'s catch block, returning undefined and\n // triggering the memory fallback.\n return await withTimeout(\n openDB<SessionReplayDB>(dbName, 1, {\n upgrade: defineObjectStores,\n }),\n OPEN_DB_TIMEOUT_MS,\n 'IDB openDB timed out',\n );\n};\n\ntype InstanceArgs = {\n apiKey: string;\n db: IDBPDatabase<SessionReplayDB>;\n tabId: string;\n onPersistentFailure?: () => void;\n consecutiveFailureThreshold?: number;\n} & BaseInstanceArgs;\n\nexport class SessionReplayEventsIDBStore extends BaseEventsStore<number> {\n private readonly db: IDBPDatabase<SessionReplayDB>;\n private readonly tabId: string;\n private readonly onPersistentFailure?: () => void;\n private readonly consecutiveFailureThreshold: number;\n private consecutiveFailures = 0;\n private hasTriggeredFallback = false;\n\n constructor(args: InstanceArgs) {\n super(args);\n this.db = args.db;\n this.tabId = args.tabId;\n this.onPersistentFailure = args.onPersistentFailure;\n // Default threshold of 1: fall back to memory immediately on the first IDB failure.\n // Session replay correctness is far more important than persistence, and IDB errors\n // are typically the symptom of a deeper problem (storage pressure, locked DB, broken\n // browser implementation) that won't recover within a single session. Memory store\n // is always safe — fall back early.\n this.consecutiveFailureThreshold = args.consecutiveFailureThreshold ?? 1;\n }\n\n private recordFailure() {\n this.consecutiveFailures++;\n if (!this.hasTriggeredFallback && this.consecutiveFailures >= this.consecutiveFailureThreshold) {\n this.hasTriggeredFallback = true;\n this.onPersistentFailure?.();\n }\n }\n\n private recordSuccess() {\n this.consecutiveFailures = 0;\n }\n\n static async new(\n type: EventType,\n args: Omit<InstanceArgs, 'db' | 'tabId'> & { tabId?: string },\n ): Promise<SessionReplayEventsIDBStore | undefined> {\n try {\n const dbSuffix = type === 'replay' ? '' : `_${type}`;\n const dbName = `${args.apiKey.substring(0, 10)}_amp_session_replay_events${dbSuffix}`;\n const db = await createStore(dbName);\n // Generate a fresh in-memory UUID per store instance. sessionStorage is\n // intentionally avoided: standalone session-replay customers (without the\n // analytics-browser SDK) would be exposed to a new storage surface they\n // did not consent to, and persistence across page reloads is not needed —\n // completed sequences in sequencesToSend are flushed by any tab/instance.\n const tabId = args.tabId ?? generateUUID();\n return new SessionReplayEventsIDBStore({\n ...args,\n db,\n tabId,\n });\n } catch (e) {\n logIdbError(args.loggerProvider, `${STORAGE_FAILURE}: ${e as string}`, e);\n }\n return;\n }\n\n async getCurrentSequenceEvents(sessionId?: number) {\n if (sessionId) {\n const record = await this.db.get('sessionCurrentSequence', sessionId);\n if (!record) {\n return undefined;\n }\n // Only return our own tab's record (or legacy untagged records).\n if (record.tabId && record.tabId !== this.tabId) {\n return undefined;\n }\n const { tabId: _tabId, ...rest } = record;\n return [rest];\n }\n\n const allEvents = [];\n for (const record of await this.db.getAll('sessionCurrentSequence')) {\n if (record.tabId && record.tabId !== this.tabId) {\n continue;\n }\n const { tabId: _tabId, ...rest } = record;\n allEvents.push(rest);\n }\n\n return allEvents;\n }\n\n getSequencesToSend = async (): Promise<SendingSequencesReturn<number>[] | undefined> => {\n let errorLogged = false;\n let timedOut = false;\n try {\n const sequences: SendingSequencesReturn<number>[] = [];\n const tx = this.db.transaction('sequencesToSend');\n // Attach a catch handler immediately so tx.done rejections (e.g. AbortError after\n // cursor traversal completes) are always handled without blocking the return path.\n // The errorLogged / timedOut flags prevent double-logging and double-recording\n // when the outer catch (or the timeout race) already fired for the same abort.\n tx.done.catch((e: unknown) => {\n if (!errorLogged && !timedOut) {\n logIdbError(this.loggerProvider, `${STORAGE_FAILURE}: ${e as string}`, e);\n this.recordFailure();\n }\n });\n // Arm a watchdog so a stalled transaction (no error, no commit, e.g.\n // storage pressure on some browsers) still trips the failure counter.\n // The watchdog fires only when tx.done genuinely never settles AND the\n // operation's success path didn't run; if tx.done rejects (abort), the\n // tx.done.catch handler above is the sole recorder of failure.\n const cancelTimeout = armTxDoneTimeout(tx.done, TX_DONE_TIMEOUT_MS, () => {\n if (!errorLogged && !timedOut) {\n timedOut = true;\n logIdbError(this.loggerProvider, `${STORAGE_FAILURE}: transaction timed out`);\n this.recordFailure();\n }\n });\n let cursor = await tx.store.openCursor();\n while (cursor) {\n const { sessionId, events } = cursor.value;\n // Return all completed sequences regardless of tabId. Filtering by tab\n // would cause event loss on page reload: a new store instance gets a\n // fresh in-memory UUID and would never see sequences written by the\n // previous instance. Completed sequences are safe to flush by any\n // tab/instance; the server deduplicates, and cleanUpSessionEventsStore\n // on an already-deleted key is a no-op.\n sequences.push({\n events,\n sequenceId: cursor.key,\n sessionId,\n });\n cursor = await cursor.continue();\n }\n\n this.recordSuccess();\n cancelTimeout();\n return sequences;\n } catch (e) {\n if (!timedOut) {\n errorLogged = true;\n logIdbError(this.loggerProvider, `${STORAGE_FAILURE}: ${e as string}`, e);\n this.recordFailure();\n }\n }\n return undefined;\n };\n\n storeCurrentSequence = async (sessionId: number) => {\n let errorLogged = false;\n let timedOut = false;\n try {\n // Wrap the read of sessionCurrentSequence and the writes to sequencesToSend +\n // sessionCurrentSequence in a single readwrite transaction so the three operations\n // commit or roll back atomically. Without this, a concurrent addEventToCurrentSequence\n // call could interleave and either lose the events being promoted or duplicate them\n // (storeCurrentSequence reads N events, addEvent appends an N+1th, storeCurrentSequence\n // writes only the first N back to sequencesToSend, then resets the slot — losing N+1).\n const tx = this.db.transaction([currentSequenceKey, sequencesToSendKey], 'readwrite');\n tx.done.catch((e: unknown) => {\n if (!errorLogged && !timedOut) {\n logIdbError(this.loggerProvider, `${STORAGE_FAILURE}: ${e as string}`, e);\n this.recordFailure();\n }\n });\n // Stalled-transaction protection: see armTxDoneTimeout in getSequencesToSend.\n const cancelTimeout = armTxDoneTimeout(tx.done, TX_DONE_TIMEOUT_MS, () => {\n if (!errorLogged && !timedOut) {\n timedOut = true;\n logIdbError(this.loggerProvider, `${STORAGE_FAILURE}: transaction timed out`);\n this.recordFailure();\n }\n });\n\n const currentSequenceData = await tx.objectStore(currentSequenceKey).get(sessionId);\n // Skip promotion if the slot is empty or owned by another tab — let the owning\n // tab promote its own events on its next addEventToCurrentSequence/storeCurrentSequence\n // call (via Bug 1's foreign-tab promotion path).\n // Don't call recordSuccess() here: no write was performed, so this is not\n // evidence the storage layer is healthy — leave the failure counter unchanged.\n if (!currentSequenceData || (currentSequenceData.tabId && currentSequenceData.tabId !== this.tabId)) {\n cancelTimeout();\n return undefined;\n }\n\n // Skip empty sequences — no point writing a zero-event row to sequencesToSend.\n if (currentSequenceData.events.length === 0) {\n cancelTimeout();\n return undefined;\n }\n\n const sequenceId = await tx.objectStore(sequencesToSendKey).put({\n sessionId,\n events: currentSequenceData.events,\n tabId: this.tabId,\n });\n\n await tx.objectStore(currentSequenceKey).put({\n sessionId,\n events: [],\n tabId: this.tabId,\n });\n\n this.recordSuccess();\n cancelTimeout();\n const { tabId: _tabId, ...rest } = currentSequenceData;\n return {\n ...rest,\n sessionId,\n sequenceId,\n };\n } catch (e) {\n if (!timedOut) {\n errorLogged = true;\n logIdbError(this.loggerProvider, `${STORAGE_FAILURE}: ${e as string}`, e);\n this.recordFailure();\n }\n }\n return undefined;\n };\n\n addEventToCurrentSequence = async (sessionId: number, event: string) => {\n let errorLogged = false;\n let timedOut = false;\n try {\n // Always open a readwrite transaction over both stores so that the read and\n // any subsequent write are atomic. IDB serializes readwrite transactions on\n // overlapping stores, so concurrent fire-and-forget callers (events-manager\n // does not await this method) are queued by the engine rather than interleaving\n // — eliminating the TOCTOU race that a narrow-read + separate-write approach\n // would introduce on the split path.\n const tx = this.db.transaction([currentSequenceKey, sequencesToSendKey], 'readwrite');\n // Attach a catch handler immediately so tx.done rejections (e.g. AbortError after\n // put succeeds but before auto-commit) are always handled without blocking.\n // The errorLogged / timedOut flags prevent double-logging when the outer catch\n // (or the timeout) already fired for the same transaction.\n tx.done.catch((e: unknown) => {\n if (!errorLogged && !timedOut) {\n logIdbError(this.loggerProvider, `${STORAGE_FAILURE}: ${e as string}`, e);\n this.recordFailure();\n }\n });\n // Stalled-transaction protection: see armTxDoneTimeout in getSequencesToSend.\n const cancelTimeout = armTxDoneTimeout(tx.done, TX_DONE_TIMEOUT_MS, () => {\n if (!errorLogged && !timedOut) {\n timedOut = true;\n logIdbError(this.loggerProvider, `${STORAGE_FAILURE}: transaction timed out`);\n this.recordFailure();\n }\n });\n const sequenceEvents = await tx.objectStore(currentSequenceKey).get(sessionId);\n\n // Foreign-tab record path: another tab owns the current-sequence slot for this\n // sessionId. Don't silently overwrite — that would drop the foreign tab's\n // in-progress events. Promote them to sequencesToSend (tabId kept for forensics)\n // before claiming the slot for ourselves. getSequencesToSend no longer filters\n // by tabId, so either tab may flush the promoted sequence; server deduplicates.\n if (sequenceEvents?.tabId && sequenceEvents.tabId !== this.tabId) {\n if (sequenceEvents.events.length > 0) {\n await tx.objectStore(sequencesToSendKey).put({\n sessionId,\n events: sequenceEvents.events,\n tabId: sequenceEvents.tabId,\n });\n }\n await tx.objectStore(currentSequenceKey).put({ sessionId, events: [event], tabId: this.tabId });\n this.recordSuccess();\n cancelTimeout();\n return undefined;\n }\n\n // ownedSequence is either undefined (no record yet) or this tab's record.\n const ownedSequence = sequenceEvents;\n\n if (!ownedSequence) {\n await tx.objectStore(currentSequenceKey).put({ sessionId, events: [event], tabId: this.tabId });\n this.recordSuccess();\n cancelTimeout();\n return undefined;\n }\n\n if (!this.shouldSplitEventsList(ownedSequence.events, event)) {\n await tx\n .objectStore(currentSequenceKey)\n .put({ sessionId, events: ownedSequence.events.concat(event), tabId: this.tabId });\n this.recordSuccess();\n cancelTimeout();\n return undefined;\n }\n\n // Split path: reset sessionCurrentSequence and write the old events to\n // sequencesToSend atomically within the same transaction.\n const eventsToSend = ownedSequence.events;\n await tx.objectStore(currentSequenceKey).put({ sessionId, events: [event], tabId: this.tabId });\n const sequenceId = await tx.objectStore(sequencesToSendKey).put({\n sessionId,\n events: eventsToSend,\n tabId: this.tabId,\n });\n\n this.recordSuccess();\n cancelTimeout();\n return {\n events: eventsToSend,\n sessionId,\n sequenceId,\n };\n } catch (e) {\n if (!timedOut) {\n errorLogged = true;\n logIdbError(this.loggerProvider, `${STORAGE_FAILURE}: ${e as string}`, e);\n this.recordFailure();\n }\n }\n return undefined;\n };\n\n storeSendingEvents = async (sessionId: number, events: Events) => {\n try {\n const sequenceId = await this.db.put<'sequencesToSend'>(sequencesToSendKey, {\n sessionId: sessionId,\n events: events,\n tabId: this.tabId,\n });\n this.recordSuccess();\n return sequenceId;\n } catch (e) {\n logIdbError(this.loggerProvider, `${STORAGE_FAILURE}: ${e as string}`, e);\n this.recordFailure();\n }\n return undefined;\n };\n\n cleanUpSessionEventsStore = async (_sessionId: number, sequenceId?: number) => {\n if (!sequenceId) {\n return;\n }\n try {\n await this.db.delete<'sequencesToSend'>(sequencesToSendKey, sequenceId);\n this.recordSuccess();\n } catch (e) {\n logIdbError(this.loggerProvider, `${STORAGE_FAILURE}: ${e as string}`, e);\n this.recordFailure();\n }\n };\n}\n"]}
|
|
@@ -30,6 +30,8 @@ export declare class SessionReplay implements AmplitudeSessionReplay {
|
|
|
30
30
|
private recordEventsPendingShouldLogMetadata;
|
|
31
31
|
/** Cleanup for URL change listener used to re-evaluate targeting on SPA route changes */
|
|
32
32
|
private urlChangeCleanup;
|
|
33
|
+
private crossOriginIframeCoordinator;
|
|
34
|
+
private crossOriginParentSignalCleanup;
|
|
33
35
|
/** Monotonic counter to ignore stale URL-change targeting results */
|
|
34
36
|
private latestUrlChangeTargetingEvaluationId;
|
|
35
37
|
constructor();
|
|
@@ -71,6 +73,8 @@ export declare class SessionReplay implements AmplitudeSessionReplay {
|
|
|
71
73
|
private getRecordFunction;
|
|
72
74
|
recordEvents(shouldLogMetadata?: boolean): Promise<void>;
|
|
73
75
|
private _recordEvents;
|
|
76
|
+
private buildRRWebRecordOptions;
|
|
77
|
+
private _recordEventsInChildMode;
|
|
74
78
|
addCustomRRWebEvent: (eventName: CustomRRwebEvent, eventData?: {
|
|
75
79
|
[key: string]: any;
|
|
76
80
|
}, addStorageInfo?: boolean) => Promise<void>;
|