@multiplayer-app/session-recorder-browser 2.0.36 → 2.0.39

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/dist/index.umd.js CHANGED
@@ -24436,7 +24436,7 @@ const CONTINUOUS_DEBUGGING_TIMEOUT = 60000; // 1 minutes
24436
24436
  const DEBUG_SESSION_MAX_DURATION_SECONDS = 10 * 60 + 30; // TODO: move to shared config otel core
24437
24437
  const REMOTE_SESSION_RECORDING_START = 'remote-session-recording:start';
24438
24438
  const REMOTE_SESSION_RECORDING_STOP = 'remote-session-recording:stop';
24439
- const PACKAGE_VERSION_EXPORT = "2.0.36" || 0;
24439
+ const PACKAGE_VERSION_EXPORT = "2.0.39" || 0;
24440
24440
  // Regex patterns for OpenTelemetry ignore URLs
24441
24441
  const OTEL_IGNORE_URLS = [
24442
24442
  // Traces endpoint
@@ -26144,6 +26144,7 @@ if (typeof XMLHttpRequest !== 'undefined') {
26144
26144
 
26145
26145
  class RecorderBrowserSDK {
26146
26146
  constructor() {
26147
+ this.generation = 0;
26147
26148
  this.intervals = {
26148
26149
  restart: null,
26149
26150
  bufferSnapshot: null,
@@ -26179,12 +26180,15 @@ class RecorderBrowserSDK {
26179
26180
  throw new Error('Configuration not initialized. Call init() before start().');
26180
26181
  }
26181
26182
  this.startedAt = new Date().toISOString();
26183
+ const gen = ++this.generation;
26182
26184
  this.stopFn = (0,rrweb__WEBPACK_IMPORTED_MODULE_6__.record)({
26183
26185
  ...this._buildRecordOptions(),
26184
26186
  emit: async (event) => {
26187
+ if (gen !== this.generation)
26188
+ return;
26185
26189
  const ts = event.timestamp;
26186
26190
  if (!sessionId) {
26187
- await this._handleBufferOnlyEvent(event, ts);
26191
+ await this._handleBufferOnlyEvent(event, ts, gen);
26188
26192
  return;
26189
26193
  }
26190
26194
  this._handleLiveSessionEvent(event, ts, sessionId, sessionType);
@@ -26200,6 +26204,7 @@ class RecorderBrowserSDK {
26200
26204
  var _a;
26201
26205
  try {
26202
26206
  (_a = this.stopFn) === null || _a === void 0 ? void 0 : _a.call(this);
26207
+ this.stopFn = undefined;
26203
26208
  this.start(sessionId, sessionType);
26204
26209
  }
26205
26210
  catch (_e) {
@@ -26213,6 +26218,7 @@ class RecorderBrowserSDK {
26213
26218
  var _a, _b, _c;
26214
26219
  (_a = this.stopFn) === null || _a === void 0 ? void 0 : _a.call(this);
26215
26220
  this.stopFn = undefined;
26221
+ this.generation++;
26216
26222
  if (!((_b = this.config) === null || _b === void 0 ? void 0 : _b.useWebsocket)) {
26217
26223
  (_c = this.socketService) === null || _c === void 0 ? void 0 : _c.close();
26218
26224
  }
@@ -26252,13 +26258,15 @@ class RecorderBrowserSDK {
26252
26258
  * @param event - Event.
26253
26259
  * @param ts - Timestamp.
26254
26260
  */
26255
- async _handleBufferOnlyEvent(event, ts) {
26261
+ async _handleBufferOnlyEvent(event, ts, gen) {
26256
26262
  if (!this.crashBuffer)
26257
26263
  return;
26258
26264
  try {
26259
26265
  this._applyConsoleMasking(event);
26260
26266
  const packedEvent = (0,_rrweb_packer__WEBPACK_IMPORTED_MODULE_0__.pack)(event);
26261
26267
  this.stoppedAt = new Date(ts).toISOString();
26268
+ if (gen !== this.generation)
26269
+ return;
26262
26270
  await this.crashBuffer.appendEvent({
26263
26271
  ts,
26264
26272
  isFullSnapshot: event.type === _rrweb_types__WEBPACK_IMPORTED_MODULE_1__.EventType.FullSnapshot,
@@ -26530,10 +26538,11 @@ class ApiService {
26530
26538
 
26531
26539
 
26532
26540
  class CrashBufferService {
26533
- constructor(db, tabId, windowMs) {
26541
+ constructor(db, tabId, windowMs, ready = Promise.resolve()) {
26534
26542
  this.db = db;
26535
26543
  this.tabId = tabId;
26536
26544
  this.windowMs = windowMs;
26545
+ this.ready = ready;
26537
26546
  this.lastPruneAt = 0;
26538
26547
  this.pruneInFlight = null;
26539
26548
  this.isActive = true;
@@ -26556,6 +26565,7 @@ class CrashBufferService {
26556
26565
  if (!this.isActive)
26557
26566
  return;
26558
26567
  const isFullSnapshot = Boolean(payload.isFullSnapshot);
26568
+ await this.ready;
26559
26569
  await this._safe(async () => {
26560
26570
  await this.db.appendEvent({
26561
26571
  tabId: this.tabId,
@@ -26576,6 +26586,7 @@ class CrashBufferService {
26576
26586
  if (!this.isActive)
26577
26587
  return;
26578
26588
  let errorEvent = null;
26589
+ await this.ready;
26579
26590
  await this._safe(async () => {
26580
26591
  const records = payload.map((p) => {
26581
26592
  var _a, _b;
@@ -26624,6 +26635,7 @@ class CrashBufferService {
26624
26635
  }
26625
26636
  async snapshot(_windowMs, now = Date.now()) {
26626
26637
  var _a, _b;
26638
+ await this.ready;
26627
26639
  const stoppedAt = now;
26628
26640
  const startedAt = Math.max(0, stoppedAt - this.windowMs);
26629
26641
  // Always include a full snapshot "anchor" if one exists at/before the window start.
@@ -27261,6 +27273,8 @@ class SocketService extends lib0_observable__WEBPACK_IMPORTED_MODULE_4__.Observa
27261
27273
  this.sessionId = null;
27262
27274
  this.usePostMessage = false;
27263
27275
  this.isInitialized = false;
27276
+ this.lastUserPayload = null;
27277
+ this.lastSubscribeSession = null;
27264
27278
  this.options = {
27265
27279
  apiKey: '',
27266
27280
  socketUrl: '',
@@ -27323,6 +27337,15 @@ class SocketService extends lib0_observable__WEBPACK_IMPORTED_MODULE_4__.Observa
27323
27337
  this.isConnecting = false;
27324
27338
  this.isConnected = true;
27325
27339
  this.usePostMessage = false;
27340
+ // Re-establish identity and session on every (re)connect: socket.io's auto-reconnect
27341
+ // fires 'ready' again after a drop, but the server has no memory of the previous
27342
+ // setUser/subscribe calls — the client must replay them.
27343
+ if (this.lastUserPayload && this.socket) {
27344
+ this.socket.emit(_config__WEBPACK_IMPORTED_MODULE_2__.SOCKET_SET_USER_EVENT, this.lastUserPayload);
27345
+ }
27346
+ if (this.lastSubscribeSession) {
27347
+ this._emitSubscribe(this.lastSubscribeSession);
27348
+ }
27326
27349
  this.flushQueue();
27327
27350
  });
27328
27351
  this.socket.on('disconnect', (err) => {
@@ -27389,16 +27412,33 @@ class SocketService extends lib0_observable__WEBPACK_IMPORTED_MODULE_4__.Observa
27389
27412
  this.emitSocketEvent(_config__WEBPACK_IMPORTED_MODULE_2__.SESSION_ADD_EVENT, event);
27390
27413
  }
27391
27414
  subscribeToSession(session) {
27415
+ // Remember the session so reconnects replay subscribe + started.
27416
+ this.lastSubscribeSession = session;
27417
+ this._emitSubscribe(session);
27418
+ }
27419
+ _emitSubscribe(session) {
27392
27420
  this.sessionId = session.shortId || session._id;
27393
- const payload = {
27421
+ const subscribePayload = {
27394
27422
  projectId: session.project,
27395
27423
  workspaceId: session.workspace,
27396
27424
  debugSessionId: this.sessionId,
27397
27425
  sessionType: session.creationType,
27398
27426
  };
27399
- this.emitSocketEvent(_config__WEBPACK_IMPORTED_MODULE_2__.SESSION_SUBSCRIBE_EVENT, payload);
27400
- // use long id instead of short id
27401
- this.emitSocketEvent(_config__WEBPACK_IMPORTED_MODULE_2__.SESSION_STARTED_EVENT, { debugSessionId: session._id });
27427
+ const startedPayload = { debugSessionId: session._id };
27428
+ // Send directly (not via the queue). The queue would cause a duplicate emit on
27429
+ // the next 'ready' alongside the replay, since 'ready' also replays lastSubscribeSession.
27430
+ if (this.usePostMessage) {
27431
+ this.sendViaPostMessage(_config__WEBPACK_IMPORTED_MODULE_2__.SESSION_SUBSCRIBE_EVENT, subscribePayload);
27432
+ this.sendViaPostMessage(_config__WEBPACK_IMPORTED_MODULE_2__.SESSION_STARTED_EVENT, startedPayload);
27433
+ }
27434
+ else if (this.socket && this.isConnected) {
27435
+ this.socket.emit(_config__WEBPACK_IMPORTED_MODULE_2__.SESSION_SUBSCRIBE_EVENT, subscribePayload);
27436
+ this.socket.emit(_config__WEBPACK_IMPORTED_MODULE_2__.SESSION_STARTED_EVENT, startedPayload);
27437
+ }
27438
+ else {
27439
+ // Not connected: 'ready' will replay via lastSubscribeSession once the socket is up.
27440
+ this._initConnection();
27441
+ }
27402
27442
  }
27403
27443
  unsubscribeFromSession(stopSession) {
27404
27444
  if (this.sessionId) {
@@ -27407,9 +27447,23 @@ class SocketService extends lib0_observable__WEBPACK_IMPORTED_MODULE_4__.Observa
27407
27447
  this.emitSocketEvent(_config__WEBPACK_IMPORTED_MODULE_2__.SESSION_STOPPED_EVENT, {});
27408
27448
  }
27409
27449
  }
27450
+ this.lastSubscribeSession = null;
27410
27451
  }
27411
27452
  setUser(data) {
27412
- this.emitSocketEvent(_config__WEBPACK_IMPORTED_MODULE_2__.SOCKET_SET_USER_EVENT, data);
27453
+ // Remember the last identity so 'ready' (initial connect + every reconnect) can replay it.
27454
+ // Send directly here rather than queuing: the queue would cause a duplicate emit on the
27455
+ // next 'ready' alongside the replay.
27456
+ this.lastUserPayload = data;
27457
+ if (this.usePostMessage) {
27458
+ this.sendViaPostMessage(_config__WEBPACK_IMPORTED_MODULE_2__.SOCKET_SET_USER_EVENT, data);
27459
+ }
27460
+ else if (this.socket && this.isConnected) {
27461
+ this.socket.emit(_config__WEBPACK_IMPORTED_MODULE_2__.SOCKET_SET_USER_EVENT, data);
27462
+ }
27463
+ else {
27464
+ // Not connected yet: 'ready' will replay lastUserPayload once the socket is up.
27465
+ this._initConnection();
27466
+ }
27413
27467
  }
27414
27468
  close() {
27415
27469
  return new Promise((resolve) => {
@@ -27651,13 +27705,11 @@ class SessionRecorder extends lib0_observable__WEBPACK_IMPORTED_MODULE_16__.Obse
27651
27705
  this._start();
27652
27706
  }
27653
27707
  else {
27654
- // Buffer-only recording when there is no active debug session.
27655
27708
  this._startBufferOnlyRecording();
27656
27709
  }
27657
27710
  this._registerWidgetEvents();
27658
27711
  this._registerSocketServiceListeners();
27659
27712
  _services_messaging_service__WEBPACK_IMPORTED_MODULE_9__["default"].sendMessage('state-change', this.sessionState);
27660
- // Emit init observable event
27661
27713
  this.emit('init', [this]);
27662
27714
  }
27663
27715
  _setupCrashBuffer() {
@@ -27665,7 +27717,8 @@ class SessionRecorder extends lib0_observable__WEBPACK_IMPORTED_MODULE_16__.Obse
27665
27717
  if ((_a = this._configs.buffering) === null || _a === void 0 ? void 0 : _a.enabled) {
27666
27718
  const windowMinutes = this._configs.buffering.windowMinutes || 0.5;
27667
27719
  const windowMs = Math.max(10000, windowMinutes * 60 * 1000);
27668
- this._crashBuffer = new _services_crashBuffer_service__WEBPACK_IMPORTED_MODULE_13__.CrashBufferService(this._bufferDb, this._tabId, windowMs);
27720
+ const ready = this._bufferDb.clearTab(this._tabId).catch(() => undefined);
27721
+ this._crashBuffer = new _services_crashBuffer_service__WEBPACK_IMPORTED_MODULE_13__.CrashBufferService(this._bufferDb, this._tabId, windowMs, ready);
27669
27722
  this._recorder.setCrashBuffer(this._crashBuffer);
27670
27723
  this._tracer.setCrashBuffer(this._crashBuffer);
27671
27724
  this._crashBuffer.on('error-span-appended', (payload) => {
@@ -27899,12 +27952,7 @@ class SessionRecorder extends lib0_observable__WEBPACK_IMPORTED_MODULE_16__.Obse
27899
27952
  }
27900
27953
  }
27901
27954
  async _flushBuffer(sessionId) {
27902
- var _a, _b;
27903
- if (!sessionId ||
27904
- !this._crashBuffer ||
27905
- this._isFlushingBuffer ||
27906
- !((_b = (_a = this._configs) === null || _a === void 0 ? void 0 : _a.buffering) === null || _b === void 0 ? void 0 : _b.enabled) ||
27907
- this.sessionState !== _types__WEBPACK_IMPORTED_MODULE_4__.SessionState.stopped) {
27955
+ if (!sessionId || !this._crashBuffer || this._isFlushingBuffer) {
27908
27956
  return null;
27909
27957
  }
27910
27958
  this._isFlushingBuffer = true;
@@ -28094,6 +28142,8 @@ class SessionRecorder extends lib0_observable__WEBPACK_IMPORTED_MODULE_16__.Obse
28094
28142
  });
28095
28143
  this._socketService.on(_config__WEBPACK_IMPORTED_MODULE_5__.SESSION_SAVE_BUFFER_EVENT, (payload) => {
28096
28144
  var _a;
28145
+ if (this.sessionState !== _types__WEBPACK_IMPORTED_MODULE_4__.SessionState.stopped)
28146
+ return;
28097
28147
  this._flushBuffer((_a = payload === null || payload === void 0 ? void 0 : payload.debugSession) === null || _a === void 0 ? void 0 : _a._id);
28098
28148
  });
28099
28149
  }
@@ -28164,7 +28214,11 @@ class SessionRecorder extends lib0_observable__WEBPACK_IMPORTED_MODULE_16__.Obse
28164
28214
  this._tracer.stop();
28165
28215
  this._recorder.stop();
28166
28216
  this._navigationRecorder.stop();
28167
- this._startBufferOnlyRecording();
28217
+ // rrweb assigns new node IDs on each record() call, so the next buffer
28218
+ // segment must not carry events from the previous generation. Await the
28219
+ // clear so its IDB tx can't race past the fresh FullSnapshot.
28220
+ const cleared = this._crashBuffer ? this._crashBuffer.clear() : Promise.resolve();
28221
+ void cleared.catch(() => undefined).then(() => this._startBufferOnlyRecording());
28168
28222
  }
28169
28223
  /**
28170
28224
  * Pause the session tracing and recording