@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.
Files changed (79) hide show
  1. package/README.md +85 -0
  2. package/lib/cjs/config/local-config.d.ts +2 -1
  3. package/lib/cjs/config/local-config.d.ts.map +1 -1
  4. package/lib/cjs/config/local-config.js +3 -0
  5. package/lib/cjs/config/local-config.js.map +1 -1
  6. package/lib/cjs/config/types.d.ts +26 -0
  7. package/lib/cjs/config/types.d.ts.map +1 -1
  8. package/lib/cjs/config/types.js.map +1 -1
  9. package/lib/cjs/constants.d.ts +2 -0
  10. package/lib/cjs/constants.d.ts.map +1 -1
  11. package/lib/cjs/constants.js +3 -1
  12. package/lib/cjs/constants.js.map +1 -1
  13. package/lib/cjs/cross-origin-iframes.d.ts +28 -0
  14. package/lib/cjs/cross-origin-iframes.d.ts.map +1 -0
  15. package/lib/cjs/cross-origin-iframes.js +175 -0
  16. package/lib/cjs/cross-origin-iframes.js.map +1 -0
  17. package/lib/cjs/events/events-idb-store.d.ts +25 -4
  18. package/lib/cjs/events/events-idb-store.d.ts.map +1 -1
  19. package/lib/cjs/events/events-idb-store.js +257 -61
  20. package/lib/cjs/events/events-idb-store.js.map +1 -1
  21. package/lib/cjs/session-replay.d.ts +4 -0
  22. package/lib/cjs/session-replay.d.ts.map +1 -1
  23. package/lib/cjs/session-replay.js +118 -51
  24. package/lib/cjs/session-replay.js.map +1 -1
  25. package/lib/cjs/track-destination.d.ts.map +1 -1
  26. package/lib/cjs/track-destination.js +5 -1
  27. package/lib/cjs/track-destination.js.map +1 -1
  28. package/lib/cjs/utils/rrweb.d.ts +1 -0
  29. package/lib/cjs/utils/rrweb.d.ts.map +1 -1
  30. package/lib/cjs/utils/rrweb.js.map +1 -1
  31. package/lib/cjs/version.d.ts +1 -1
  32. package/lib/cjs/version.js +1 -1
  33. package/lib/cjs/version.js.map +1 -1
  34. package/lib/cjs/worker/index.js +1 -1
  35. package/lib/esm/config/local-config.d.ts +2 -1
  36. package/lib/esm/config/local-config.d.ts.map +1 -1
  37. package/lib/esm/config/local-config.js +3 -0
  38. package/lib/esm/config/local-config.js.map +1 -1
  39. package/lib/esm/config/types.d.ts +26 -0
  40. package/lib/esm/config/types.d.ts.map +1 -1
  41. package/lib/esm/config/types.js.map +1 -1
  42. package/lib/esm/constants.d.ts +2 -0
  43. package/lib/esm/constants.d.ts.map +1 -1
  44. package/lib/esm/constants.js +2 -0
  45. package/lib/esm/constants.js.map +1 -1
  46. package/lib/esm/cross-origin-iframes.d.ts +28 -0
  47. package/lib/esm/cross-origin-iframes.d.ts.map +1 -0
  48. package/lib/esm/cross-origin-iframes.js +170 -0
  49. package/lib/esm/cross-origin-iframes.js.map +1 -0
  50. package/lib/esm/events/events-idb-store.d.ts +25 -4
  51. package/lib/esm/events/events-idb-store.d.ts.map +1 -1
  52. package/lib/esm/events/events-idb-store.js +255 -61
  53. package/lib/esm/events/events-idb-store.js.map +1 -1
  54. package/lib/esm/session-replay.d.ts +4 -0
  55. package/lib/esm/session-replay.d.ts.map +1 -1
  56. package/lib/esm/session-replay.js +118 -51
  57. package/lib/esm/session-replay.js.map +1 -1
  58. package/lib/esm/track-destination.d.ts.map +1 -1
  59. package/lib/esm/track-destination.js +6 -2
  60. package/lib/esm/track-destination.js.map +1 -1
  61. package/lib/esm/utils/rrweb.d.ts +1 -0
  62. package/lib/esm/utils/rrweb.d.ts.map +1 -1
  63. package/lib/esm/utils/rrweb.js.map +1 -1
  64. package/lib/esm/version.d.ts +1 -1
  65. package/lib/esm/version.js +1 -1
  66. package/lib/esm/version.js.map +1 -1
  67. package/lib/esm/worker/index.js +1 -1
  68. package/lib/scripts/index-min.js +1 -1
  69. package/lib/scripts/index-min.js.gz +0 -0
  70. package/lib/scripts/index-min.js.map +1 -1
  71. package/lib/scripts/session-replay-browser-min.js +1 -1
  72. package/lib/scripts/session-replay-browser-min.js.gz +0 -0
  73. package/lib/scripts/session-replay-browser-min.js.map +1 -1
  74. package/lib/scripts/targeting-min.js +1 -1
  75. package/lib/scripts/targeting-min.js.gz +0 -0
  76. package/lib/scripts/targeting-min.js.map +1 -1
  77. package/lib/scripts/worker-min.js +1 -1
  78. package/lib/scripts/worker-min.js.gz +0 -0
  79. 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'>): Promise<SessionReplayEventsIDBStore | undefined>;
41
- getCurrentSequenceEvents(sessionId?: number): Promise<Omit<SendingSequencesReturn<number>, "sequenceId">[] | undefined>;
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;AAGxF,eAAO,MAAM,kBAAkB,2BAA2B,CAAC;AAC3D,eAAO,MAAM,kBAAkB,oBAAoB,CAAC;AACpD,eAAO,MAAM,eAAe,iBAAiB,CAAC;AAE9C,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,CAAC;KAC3D,CAAC;IACF,eAAe,EAAE;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,IAAI,CAAC,sBAAsB,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC,CAAC;QAC1D,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,2CAI/C,CAAC;AAEF,KAAK,YAAY,GAAG;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,YAAY,CAAC,eAAe,CAAC,CAAC;IAClC,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,mBAAmB,CAAC,CAAa;IAClD,OAAO,CAAC,QAAQ,CAAC,2BAA2B,CAAS;IACrD,OAAO,CAAC,mBAAmB,CAAK;IAChC,OAAO,CAAC,oBAAoB,CAAS;gBAEzB,IAAI,EAAE,YAAY;IAO9B,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,aAAa;WAIR,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC,2BAA2B,GAAG,SAAS,CAAC;IAe7G,wBAAwB,CAAC,SAAS,CAAC,EAAE,MAAM;IAiBjD,kBAAkB,QAAa,QAAQ,uBAAuB,MAAM,CAAC,EAAE,GAAG,SAAS,CAAC,CAiClF;IAEF,oBAAoB,cAAqB,MAAM;;;;mBA6B7C;IAEF,yBAAyB,cAAqB,MAAM,SAAS,MAAM;;;;mBAsDjE;IAEF,kBAAkB,cAAqB,MAAM,iDAa3C;IAEF,yBAAyB,eAAsB,MAAM,eAAe,MAAM,mBAWxE;CACH"}
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: return [2 /*return*/, _a.sent()];
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 flag prevents double-logging when the outer catch already fired
67
- // for the same abort (e.g. cursor.continue() threw mid-traversal).
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
- errorLogged = true;
95
- (0, is_abort_error_1.logIdbError)(this.loggerProvider, "".concat(messages_1.STORAGE_FAILURE, ": ").concat(e_1), e_1);
96
- this.recordFailure();
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
- _a.trys.push([0, 4, , 5]);
108
- return [4 /*yield*/, this.db.get(exports.currentSequenceKey, sessionId)];
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 (!currentSequenceData) {
112
- this.recordSuccess();
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
- return [4 /*yield*/, this.db.put(exports.sequencesToSendKey, {
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 2:
256
+ case 3:
120
257
  sequenceId = _a.sent();
121
- return [4 /*yield*/, this.db.put(exports.currentSequenceKey, {
258
+ return [4 /*yield*/, tx.objectStore(exports.currentSequenceKey).put({
122
259
  sessionId: sessionId,
123
260
  events: [],
261
+ tabId: this.tabId,
124
262
  })];
125
- case 3:
263
+ case 4:
126
264
  _a.sent();
127
265
  this.recordSuccess();
128
- return [2 /*return*/, tslib_1.__assign(tslib_1.__assign({}, currentSequenceData), { sessionId: sessionId, sequenceId: sequenceId })];
129
- case 4:
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
- (0, is_abort_error_1.logIdbError)(this.loggerProvider, "".concat(messages_1.STORAGE_FAILURE, ": ").concat(e_2), e_2);
132
- this.recordFailure();
133
- return [3 /*break*/, 5];
134
- case 5: return [2 /*return*/, undefined];
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, 9, , 10]);
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 flag prevents double-logging when the outer catch already fired
152
- // for the same abort (e.g. a put() threw).
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 (!!sequenceEvents) return [3 /*break*/, 4];
162
- return [4 /*yield*/, tx.objectStore(exports.currentSequenceKey).put({ sessionId: sessionId, events: [event] })];
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
- this.recordSuccess();
166
- return [2 /*return*/, undefined];
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
- eventsToSend = sequenceEvents.events;
176
- return [4 /*yield*/, tx.objectStore(exports.currentSequenceKey).put({ sessionId: sessionId, events: [event] })];
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 8:
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 9:
367
+ case 13:
192
368
  e_3 = _a.sent();
193
- errorLogged = true;
194
- (0, is_abort_error_1.logIdbError)(this.loggerProvider, "".concat(messages_1.STORAGE_FAILURE, ": ").concat(e_3), e_3);
195
- this.recordFailure();
196
- return [3 /*break*/, 10];
197
- case 10: return [2 /*return*/, undefined];
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
- _this.consecutiveFailureThreshold = (_a = args.consecutiveFailureThreshold) !== null && _a !== void 0 ? _a : 3;
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 (_a) {
269
- switch (_a.label) {
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
- _a.trys.push([0, 2, , 3]);
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 = _a.sent();
277
- return [2 /*return*/, new SessionReplayEventsIDBStore(tslib_1.__assign(tslib_1.__assign({}, args), { db: db }))];
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 = _a.sent();
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 events, allEvents, _a, _b, events, e_7_1;
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
- events = _d.sent();
298
- if (!events) {
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
- return [2 /*return*/, [events]];
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
- events = _b.value;
314
- allEvents.push(events);
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();