@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.
@@ -25163,7 +25163,7 @@ const CONTINUOUS_DEBUGGING_TIMEOUT = 60000; // 1 minutes
25163
25163
  const DEBUG_SESSION_MAX_DURATION_SECONDS = 10 * 60 + 30; // TODO: move to shared config otel core
25164
25164
  const REMOTE_SESSION_RECORDING_START = 'remote-session-recording:start';
25165
25165
  const REMOTE_SESSION_RECORDING_STOP = 'remote-session-recording:stop';
25166
- const PACKAGE_VERSION_EXPORT = "2.0.36" || 0;
25166
+ const PACKAGE_VERSION_EXPORT = "2.0.39" || 0;
25167
25167
  // Regex patterns for OpenTelemetry ignore URLs
25168
25168
  const OTEL_IGNORE_URLS = [
25169
25169
  // Traces endpoint
@@ -26907,6 +26907,7 @@ __webpack_require__.r(__webpack_exports__);
26907
26907
 
26908
26908
  class RecorderBrowserSDK {
26909
26909
  constructor() {
26910
+ this.generation = 0;
26910
26911
  this.intervals = {
26911
26912
  restart: null,
26912
26913
  bufferSnapshot: null,
@@ -26942,12 +26943,15 @@ class RecorderBrowserSDK {
26942
26943
  throw new Error('Configuration not initialized. Call init() before start().');
26943
26944
  }
26944
26945
  this.startedAt = new Date().toISOString();
26946
+ const gen = ++this.generation;
26945
26947
  this.stopFn = (0,rrweb__WEBPACK_IMPORTED_MODULE_6__.record)({
26946
26948
  ...this._buildRecordOptions(),
26947
26949
  emit: async (event) => {
26950
+ if (gen !== this.generation)
26951
+ return;
26948
26952
  const ts = event.timestamp;
26949
26953
  if (!sessionId) {
26950
- await this._handleBufferOnlyEvent(event, ts);
26954
+ await this._handleBufferOnlyEvent(event, ts, gen);
26951
26955
  return;
26952
26956
  }
26953
26957
  this._handleLiveSessionEvent(event, ts, sessionId, sessionType);
@@ -26963,6 +26967,7 @@ class RecorderBrowserSDK {
26963
26967
  var _a;
26964
26968
  try {
26965
26969
  (_a = this.stopFn) === null || _a === void 0 ? void 0 : _a.call(this);
26970
+ this.stopFn = undefined;
26966
26971
  this.start(sessionId, sessionType);
26967
26972
  }
26968
26973
  catch (_e) {
@@ -26976,6 +26981,7 @@ class RecorderBrowserSDK {
26976
26981
  var _a, _b, _c;
26977
26982
  (_a = this.stopFn) === null || _a === void 0 ? void 0 : _a.call(this);
26978
26983
  this.stopFn = undefined;
26984
+ this.generation++;
26979
26985
  if (!((_b = this.config) === null || _b === void 0 ? void 0 : _b.useWebsocket)) {
26980
26986
  (_c = this.socketService) === null || _c === void 0 ? void 0 : _c.close();
26981
26987
  }
@@ -27015,13 +27021,15 @@ class RecorderBrowserSDK {
27015
27021
  * @param event - Event.
27016
27022
  * @param ts - Timestamp.
27017
27023
  */
27018
- async _handleBufferOnlyEvent(event, ts) {
27024
+ async _handleBufferOnlyEvent(event, ts, gen) {
27019
27025
  if (!this.crashBuffer)
27020
27026
  return;
27021
27027
  try {
27022
27028
  this._applyConsoleMasking(event);
27023
27029
  const packedEvent = (0,_rrweb_packer__WEBPACK_IMPORTED_MODULE_0__.pack)(event);
27024
27030
  this.stoppedAt = new Date(ts).toISOString();
27031
+ if (gen !== this.generation)
27032
+ return;
27025
27033
  await this.crashBuffer.appendEvent({
27026
27034
  ts,
27027
27035
  isFullSnapshot: event.type === _rrweb_types__WEBPACK_IMPORTED_MODULE_1__.EventType.FullSnapshot,
@@ -27295,10 +27303,11 @@ __webpack_require__.r(__webpack_exports__);
27295
27303
 
27296
27304
 
27297
27305
  class CrashBufferService {
27298
- constructor(db, tabId, windowMs) {
27306
+ constructor(db, tabId, windowMs, ready = Promise.resolve()) {
27299
27307
  this.db = db;
27300
27308
  this.tabId = tabId;
27301
27309
  this.windowMs = windowMs;
27310
+ this.ready = ready;
27302
27311
  this.lastPruneAt = 0;
27303
27312
  this.pruneInFlight = null;
27304
27313
  this.isActive = true;
@@ -27321,6 +27330,7 @@ class CrashBufferService {
27321
27330
  if (!this.isActive)
27322
27331
  return;
27323
27332
  const isFullSnapshot = Boolean(payload.isFullSnapshot);
27333
+ await this.ready;
27324
27334
  await this._safe(async () => {
27325
27335
  await this.db.appendEvent({
27326
27336
  tabId: this.tabId,
@@ -27341,6 +27351,7 @@ class CrashBufferService {
27341
27351
  if (!this.isActive)
27342
27352
  return;
27343
27353
  let errorEvent = null;
27354
+ await this.ready;
27344
27355
  await this._safe(async () => {
27345
27356
  const records = payload.map((p) => {
27346
27357
  var _a, _b;
@@ -27389,6 +27400,7 @@ class CrashBufferService {
27389
27400
  }
27390
27401
  async snapshot(_windowMs, now = Date.now()) {
27391
27402
  var _a, _b;
27403
+ await this.ready;
27392
27404
  const stoppedAt = now;
27393
27405
  const startedAt = Math.max(0, stoppedAt - this.windowMs);
27394
27406
  // Always include a full snapshot "anchor" if one exists at/before the window start.
@@ -28029,6 +28041,8 @@ class SocketService extends lib0_observable__WEBPACK_IMPORTED_MODULE_4__.Observa
28029
28041
  this.sessionId = null;
28030
28042
  this.usePostMessage = false;
28031
28043
  this.isInitialized = false;
28044
+ this.lastUserPayload = null;
28045
+ this.lastSubscribeSession = null;
28032
28046
  this.options = {
28033
28047
  apiKey: '',
28034
28048
  socketUrl: '',
@@ -28091,6 +28105,15 @@ class SocketService extends lib0_observable__WEBPACK_IMPORTED_MODULE_4__.Observa
28091
28105
  this.isConnecting = false;
28092
28106
  this.isConnected = true;
28093
28107
  this.usePostMessage = false;
28108
+ // Re-establish identity and session on every (re)connect: socket.io's auto-reconnect
28109
+ // fires 'ready' again after a drop, but the server has no memory of the previous
28110
+ // setUser/subscribe calls — the client must replay them.
28111
+ if (this.lastUserPayload && this.socket) {
28112
+ this.socket.emit(_config__WEBPACK_IMPORTED_MODULE_2__.SOCKET_SET_USER_EVENT, this.lastUserPayload);
28113
+ }
28114
+ if (this.lastSubscribeSession) {
28115
+ this._emitSubscribe(this.lastSubscribeSession);
28116
+ }
28094
28117
  this.flushQueue();
28095
28118
  });
28096
28119
  this.socket.on('disconnect', (err) => {
@@ -28157,16 +28180,33 @@ class SocketService extends lib0_observable__WEBPACK_IMPORTED_MODULE_4__.Observa
28157
28180
  this.emitSocketEvent(_config__WEBPACK_IMPORTED_MODULE_2__.SESSION_ADD_EVENT, event);
28158
28181
  }
28159
28182
  subscribeToSession(session) {
28183
+ // Remember the session so reconnects replay subscribe + started.
28184
+ this.lastSubscribeSession = session;
28185
+ this._emitSubscribe(session);
28186
+ }
28187
+ _emitSubscribe(session) {
28160
28188
  this.sessionId = session.shortId || session._id;
28161
- const payload = {
28189
+ const subscribePayload = {
28162
28190
  projectId: session.project,
28163
28191
  workspaceId: session.workspace,
28164
28192
  debugSessionId: this.sessionId,
28165
28193
  sessionType: session.creationType,
28166
28194
  };
28167
- this.emitSocketEvent(_config__WEBPACK_IMPORTED_MODULE_2__.SESSION_SUBSCRIBE_EVENT, payload);
28168
- // use long id instead of short id
28169
- this.emitSocketEvent(_config__WEBPACK_IMPORTED_MODULE_2__.SESSION_STARTED_EVENT, { debugSessionId: session._id });
28195
+ const startedPayload = { debugSessionId: session._id };
28196
+ // Send directly (not via the queue). The queue would cause a duplicate emit on
28197
+ // the next 'ready' alongside the replay, since 'ready' also replays lastSubscribeSession.
28198
+ if (this.usePostMessage) {
28199
+ this.sendViaPostMessage(_config__WEBPACK_IMPORTED_MODULE_2__.SESSION_SUBSCRIBE_EVENT, subscribePayload);
28200
+ this.sendViaPostMessage(_config__WEBPACK_IMPORTED_MODULE_2__.SESSION_STARTED_EVENT, startedPayload);
28201
+ }
28202
+ else if (this.socket && this.isConnected) {
28203
+ this.socket.emit(_config__WEBPACK_IMPORTED_MODULE_2__.SESSION_SUBSCRIBE_EVENT, subscribePayload);
28204
+ this.socket.emit(_config__WEBPACK_IMPORTED_MODULE_2__.SESSION_STARTED_EVENT, startedPayload);
28205
+ }
28206
+ else {
28207
+ // Not connected: 'ready' will replay via lastSubscribeSession once the socket is up.
28208
+ this._initConnection();
28209
+ }
28170
28210
  }
28171
28211
  unsubscribeFromSession(stopSession) {
28172
28212
  if (this.sessionId) {
@@ -28175,9 +28215,23 @@ class SocketService extends lib0_observable__WEBPACK_IMPORTED_MODULE_4__.Observa
28175
28215
  this.emitSocketEvent(_config__WEBPACK_IMPORTED_MODULE_2__.SESSION_STOPPED_EVENT, {});
28176
28216
  }
28177
28217
  }
28218
+ this.lastSubscribeSession = null;
28178
28219
  }
28179
28220
  setUser(data) {
28180
- this.emitSocketEvent(_config__WEBPACK_IMPORTED_MODULE_2__.SOCKET_SET_USER_EVENT, data);
28221
+ // Remember the last identity so 'ready' (initial connect + every reconnect) can replay it.
28222
+ // Send directly here rather than queuing: the queue would cause a duplicate emit on the
28223
+ // next 'ready' alongside the replay.
28224
+ this.lastUserPayload = data;
28225
+ if (this.usePostMessage) {
28226
+ this.sendViaPostMessage(_config__WEBPACK_IMPORTED_MODULE_2__.SOCKET_SET_USER_EVENT, data);
28227
+ }
28228
+ else if (this.socket && this.isConnected) {
28229
+ this.socket.emit(_config__WEBPACK_IMPORTED_MODULE_2__.SOCKET_SET_USER_EVENT, data);
28230
+ }
28231
+ else {
28232
+ // Not connected yet: 'ready' will replay lastUserPayload once the socket is up.
28233
+ this._initConnection();
28234
+ }
28181
28235
  }
28182
28236
  close() {
28183
28237
  return new Promise((resolve) => {
@@ -28420,13 +28474,11 @@ class SessionRecorder extends lib0_observable__WEBPACK_IMPORTED_MODULE_16__.Obse
28420
28474
  this._start();
28421
28475
  }
28422
28476
  else {
28423
- // Buffer-only recording when there is no active debug session.
28424
28477
  this._startBufferOnlyRecording();
28425
28478
  }
28426
28479
  this._registerWidgetEvents();
28427
28480
  this._registerSocketServiceListeners();
28428
28481
  _services_messaging_service__WEBPACK_IMPORTED_MODULE_9__["default"].sendMessage('state-change', this.sessionState);
28429
- // Emit init observable event
28430
28482
  this.emit('init', [this]);
28431
28483
  }
28432
28484
  _setupCrashBuffer() {
@@ -28434,7 +28486,8 @@ class SessionRecorder extends lib0_observable__WEBPACK_IMPORTED_MODULE_16__.Obse
28434
28486
  if ((_a = this._configs.buffering) === null || _a === void 0 ? void 0 : _a.enabled) {
28435
28487
  const windowMinutes = this._configs.buffering.windowMinutes || 0.5;
28436
28488
  const windowMs = Math.max(10000, windowMinutes * 60 * 1000);
28437
- this._crashBuffer = new _services_crashBuffer_service__WEBPACK_IMPORTED_MODULE_13__.CrashBufferService(this._bufferDb, this._tabId, windowMs);
28489
+ const ready = this._bufferDb.clearTab(this._tabId).catch(() => undefined);
28490
+ this._crashBuffer = new _services_crashBuffer_service__WEBPACK_IMPORTED_MODULE_13__.CrashBufferService(this._bufferDb, this._tabId, windowMs, ready);
28438
28491
  this._recorder.setCrashBuffer(this._crashBuffer);
28439
28492
  this._tracer.setCrashBuffer(this._crashBuffer);
28440
28493
  this._crashBuffer.on('error-span-appended', (payload) => {
@@ -28668,12 +28721,7 @@ class SessionRecorder extends lib0_observable__WEBPACK_IMPORTED_MODULE_16__.Obse
28668
28721
  }
28669
28722
  }
28670
28723
  async _flushBuffer(sessionId) {
28671
- var _a, _b;
28672
- if (!sessionId ||
28673
- !this._crashBuffer ||
28674
- this._isFlushingBuffer ||
28675
- !((_b = (_a = this._configs) === null || _a === void 0 ? void 0 : _a.buffering) === null || _b === void 0 ? void 0 : _b.enabled) ||
28676
- this.sessionState !== _types__WEBPACK_IMPORTED_MODULE_4__.SessionState.stopped) {
28724
+ if (!sessionId || !this._crashBuffer || this._isFlushingBuffer) {
28677
28725
  return null;
28678
28726
  }
28679
28727
  this._isFlushingBuffer = true;
@@ -28863,6 +28911,8 @@ class SessionRecorder extends lib0_observable__WEBPACK_IMPORTED_MODULE_16__.Obse
28863
28911
  });
28864
28912
  this._socketService.on(_config__WEBPACK_IMPORTED_MODULE_5__.SESSION_SAVE_BUFFER_EVENT, (payload) => {
28865
28913
  var _a;
28914
+ if (this.sessionState !== _types__WEBPACK_IMPORTED_MODULE_4__.SessionState.stopped)
28915
+ return;
28866
28916
  this._flushBuffer((_a = payload === null || payload === void 0 ? void 0 : payload.debugSession) === null || _a === void 0 ? void 0 : _a._id);
28867
28917
  });
28868
28918
  }
@@ -28933,7 +28983,11 @@ class SessionRecorder extends lib0_observable__WEBPACK_IMPORTED_MODULE_16__.Obse
28933
28983
  this._tracer.stop();
28934
28984
  this._recorder.stop();
28935
28985
  this._navigationRecorder.stop();
28936
- this._startBufferOnlyRecording();
28986
+ // rrweb assigns new node IDs on each record() call, so the next buffer
28987
+ // segment must not carry events from the previous generation. Await the
28988
+ // clear so its IDB tx can't race past the fresh FullSnapshot.
28989
+ const cleared = this._crashBuffer ? this._crashBuffer.clear() : Promise.resolve();
28990
+ void cleared.catch(() => undefined).then(() => this._startBufferOnlyRecording());
28937
28991
  }
28938
28992
  /**
28939
28993
  * Pause the session tracing and recording