@multiplayer-app/session-recorder-browser 2.0.38 → 2.0.40

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.js CHANGED
@@ -24263,7 +24263,7 @@ const CONTINUOUS_DEBUGGING_TIMEOUT = 60000; // 1 minutes
24263
24263
  const DEBUG_SESSION_MAX_DURATION_SECONDS = 10 * 60 + 30; // TODO: move to shared config otel core
24264
24264
  const REMOTE_SESSION_RECORDING_START = 'remote-session-recording:start';
24265
24265
  const REMOTE_SESSION_RECORDING_STOP = 'remote-session-recording:stop';
24266
- const PACKAGE_VERSION_EXPORT = "2.0.38" || 0;
24266
+ const PACKAGE_VERSION_EXPORT = "2.0.40" || 0;
24267
24267
  // Regex patterns for OpenTelemetry ignore URLs
24268
24268
  const OTEL_IGNORE_URLS = [
24269
24269
  // Traces endpoint
@@ -25951,6 +25951,7 @@ if (typeof XMLHttpRequest !== 'undefined') {
25951
25951
 
25952
25952
  class RecorderBrowserSDK {
25953
25953
  constructor() {
25954
+ this.generation = 0;
25954
25955
  this.intervals = {
25955
25956
  restart: null,
25956
25957
  bufferSnapshot: null,
@@ -25986,12 +25987,15 @@ class RecorderBrowserSDK {
25986
25987
  throw new Error('Configuration not initialized. Call init() before start().');
25987
25988
  }
25988
25989
  this.startedAt = new Date().toISOString();
25990
+ const gen = ++this.generation;
25989
25991
  this.stopFn = (0,rrweb__WEBPACK_IMPORTED_MODULE_6__.record)({
25990
25992
  ...this._buildRecordOptions(),
25991
25993
  emit: async (event) => {
25994
+ if (gen !== this.generation)
25995
+ return;
25992
25996
  const ts = event.timestamp;
25993
25997
  if (!sessionId) {
25994
- await this._handleBufferOnlyEvent(event, ts);
25998
+ await this._handleBufferOnlyEvent(event, ts, gen);
25995
25999
  return;
25996
26000
  }
25997
26001
  this._handleLiveSessionEvent(event, ts, sessionId, sessionType);
@@ -26007,6 +26011,7 @@ class RecorderBrowserSDK {
26007
26011
  var _a;
26008
26012
  try {
26009
26013
  (_a = this.stopFn) === null || _a === void 0 ? void 0 : _a.call(this);
26014
+ this.stopFn = undefined;
26010
26015
  this.start(sessionId, sessionType);
26011
26016
  }
26012
26017
  catch (_e) {
@@ -26020,6 +26025,7 @@ class RecorderBrowserSDK {
26020
26025
  var _a, _b, _c;
26021
26026
  (_a = this.stopFn) === null || _a === void 0 ? void 0 : _a.call(this);
26022
26027
  this.stopFn = undefined;
26028
+ this.generation++;
26023
26029
  if (!((_b = this.config) === null || _b === void 0 ? void 0 : _b.useWebsocket)) {
26024
26030
  (_c = this.socketService) === null || _c === void 0 ? void 0 : _c.close();
26025
26031
  }
@@ -26059,13 +26065,15 @@ class RecorderBrowserSDK {
26059
26065
  * @param event - Event.
26060
26066
  * @param ts - Timestamp.
26061
26067
  */
26062
- async _handleBufferOnlyEvent(event, ts) {
26068
+ async _handleBufferOnlyEvent(event, ts, gen) {
26063
26069
  if (!this.crashBuffer)
26064
26070
  return;
26065
26071
  try {
26066
26072
  this._applyConsoleMasking(event);
26067
26073
  const packedEvent = (0,_rrweb_packer__WEBPACK_IMPORTED_MODULE_0__.pack)(event);
26068
26074
  this.stoppedAt = new Date(ts).toISOString();
26075
+ if (gen !== this.generation)
26076
+ return;
26069
26077
  await this.crashBuffer.appendEvent({
26070
26078
  ts,
26071
26079
  isFullSnapshot: event.type === _rrweb_types__WEBPACK_IMPORTED_MODULE_1__.EventType.FullSnapshot,
@@ -27563,10 +27571,14 @@ class SessionRecorder extends lib0_observable__WEBPACK_IMPORTED_MODULE_16__.Obse
27563
27571
  }
27564
27572
  _startBufferOnlyRecording() {
27565
27573
  var _a, _b;
27566
- if (this.sessionId ||
27567
- !this._crashBuffer ||
27568
- !((_b = (_a = this._configs) === null || _a === void 0 ? void 0 : _a.buffering) === null || _b === void 0 ? void 0 : _b.enabled) ||
27569
- this.sessionState !== _types__WEBPACK_IMPORTED_MODULE_4__.SessionState.stopped) {
27574
+ // NOTE: `this.sessionId` is intentionally NOT checked. `_stop()` runs
27575
+ // before `_clearSession()` (the stopSession API call sits between them),
27576
+ // so the clear().then() chain fires while sessionId is still set.
27577
+ // Bailing on sessionId here meant rrweb never restarted after manual
27578
+ // stop, leaving the buffer with no FullSnapshot and silently breaking
27579
+ // exception-triggered flushBuffer. `_recorder.restart(null, ...)` passes
27580
+ // null explicitly, so it's safe regardless of `this.sessionId`.
27581
+ if (!this._crashBuffer || !((_b = (_a = this._configs) === null || _a === void 0 ? void 0 : _a.buffering) === null || _b === void 0 ? void 0 : _b.enabled) || this.sessionState !== _types__WEBPACK_IMPORTED_MODULE_4__.SessionState.stopped) {
27570
27582
  return;
27571
27583
  }
27572
27584
  void this._recorder.restart(null, _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_0__.SessionType.MANUAL);
@@ -27636,9 +27648,10 @@ class SessionRecorder extends lib0_observable__WEBPACK_IMPORTED_MODULE_16__.Obse
27636
27648
  async stop(comment) {
27637
27649
  try {
27638
27650
  this._checkOperation('stop');
27651
+ const sid = this.sessionId;
27639
27652
  this._stop();
27640
27653
  if (this.continuousRecording) {
27641
- await this._apiService.stopContinuousDebugSession(this.sessionId);
27654
+ await this._apiService.stopContinuousDebugSession(sid);
27642
27655
  this.sessionType = _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_0__.SessionType.MANUAL;
27643
27656
  }
27644
27657
  else {
@@ -27646,10 +27659,9 @@ class SessionRecorder extends lib0_observable__WEBPACK_IMPORTED_MODULE_16__.Obse
27646
27659
  sessionAttributes: { comment },
27647
27660
  stoppedAt: this._recorder.stoppedAt,
27648
27661
  };
27649
- const response = await this._apiService.stopSession(this.sessionId, request);
27662
+ const response = await this._apiService.stopSession(sid, request);
27650
27663
  _eventBus__WEBPACK_IMPORTED_MODULE_7__.recorderEventBus.emit(_config__WEBPACK_IMPORTED_MODULE_5__.SESSION_RESPONSE, response);
27651
27664
  }
27652
- this._clearSession();
27653
27665
  }
27654
27666
  catch (error) {
27655
27667
  this.error = error.message;
@@ -27685,15 +27697,15 @@ class SessionRecorder extends lib0_observable__WEBPACK_IMPORTED_MODULE_16__.Obse
27685
27697
  async cancel() {
27686
27698
  try {
27687
27699
  this._checkOperation('cancel');
27700
+ const sid = this.sessionId;
27688
27701
  this._stop();
27689
27702
  if (this.continuousRecording) {
27690
- await this._apiService.stopContinuousDebugSession(this.sessionId);
27703
+ await this._apiService.stopContinuousDebugSession(sid);
27691
27704
  this.sessionType = _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_0__.SessionType.MANUAL;
27692
27705
  }
27693
27706
  else {
27694
- await this._apiService.cancelSession(this.sessionId);
27707
+ await this._apiService.cancelSession(sid);
27695
27708
  }
27696
- this._clearSession();
27697
27709
  }
27698
27710
  catch (error) {
27699
27711
  this.error = error.message;
@@ -28007,7 +28019,18 @@ class SessionRecorder extends lib0_observable__WEBPACK_IMPORTED_MODULE_16__.Obse
28007
28019
  this._tracer.stop();
28008
28020
  this._recorder.stop();
28009
28021
  this._navigationRecorder.stop();
28010
- this._startBufferOnlyRecording();
28022
+ // Clear session identity synchronously. The buffer-restart chain and the
28023
+ // error-span-appended listener both gate on `this.sessionId === null`;
28024
+ // deferring this to `_clearSession()` (after the network stopSession
28025
+ // call) left them seeing a stale id and silently no-oping. Callers that
28026
+ // need the id for the stop/cancel API must capture it before _stop().
28027
+ this.session = null;
28028
+ this.sessionId = null;
28029
+ // rrweb assigns new node IDs on each record() call, so the next buffer
28030
+ // segment must not carry events from the previous generation. Await the
28031
+ // clear so its IDB tx can't race past the fresh FullSnapshot.
28032
+ const cleared = this._crashBuffer ? this._crashBuffer.clear() : Promise.resolve();
28033
+ void cleared.catch(() => undefined).then(() => this._startBufferOnlyRecording());
28011
28034
  }
28012
28035
  /**
28013
28036
  * Pause the session tracing and recording