@multiplayer-app/session-recorder-browser 2.0.87 → 2.0.89

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.
@@ -11,9 +11,10 @@ export declare const SESSION_UNSUBSCRIBE_EVENT = "debug-session:unsubscribe";
11
11
  export declare const SESSION_AUTO_CREATED = "debug-session:auto-created";
12
12
  export declare const SESSION_ADD_EVENT = "debug-session:rrweb:add-event";
13
13
  export declare const SESSION_SAVE_BUFFER_EVENT = "debug-session:save-buffer";
14
+ export declare const SESSION_READY_EVENT = "debug-session:ready";
15
+ export declare const SESSION_READY_EVENT_LEGACY = "debug-session-ready";
14
16
  export declare const SOCKET_SET_USER_EVENT = "socket:set-user";
15
17
  export declare const DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE = 100000;
16
- export declare const SESSION_READY_EVENT = "debug-session-ready";
17
18
  export declare const CONTINUOUS_DEBUGGING_TIMEOUT = 60000;
18
19
  export declare const DEBUG_SESSION_MAX_DURATION_SECONDS: number;
19
20
  export declare const REMOTE_SESSION_RECORDING_START = "remote-session-recording:start";
@@ -12,9 +12,11 @@ export const SESSION_AUTO_CREATED = 'debug-session:auto-created';
12
12
  export const SESSION_ADD_EVENT = 'debug-session:rrweb:add-event';
13
13
  // Backend-triggered flush of client-side crash buffer
14
14
  export const SESSION_SAVE_BUFFER_EVENT = 'debug-session:save-buffer';
15
+ export const SESSION_READY_EVENT = 'debug-session:ready';
16
+ // Deprecated event name for backwards compatibility
17
+ export const SESSION_READY_EVENT_LEGACY = 'debug-session-ready';
15
18
  export const SOCKET_SET_USER_EVENT = 'socket:set-user';
16
19
  export const DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE = 100000;
17
- export const SESSION_READY_EVENT = 'debug-session-ready';
18
20
  export const CONTINUOUS_DEBUGGING_TIMEOUT = 60000; // 1 minutes
19
21
  export const DEBUG_SESSION_MAX_DURATION_SECONDS = 10 * 60 + 30; // TODO: move to shared config otel core
20
22
  export const REMOTE_SESSION_RECORDING_START = 'remote-session-recording:start';
package/dist/index.js CHANGED
@@ -7030,6 +7030,7 @@ module.exports = {
7030
7030
  /* harmony export */ SESSION_ID_PROP_NAME: () => (/* binding */ SESSION_ID_PROP_NAME),
7031
7031
  /* harmony export */ SESSION_PROP_NAME: () => (/* binding */ SESSION_PROP_NAME),
7032
7032
  /* harmony export */ SESSION_READY_EVENT: () => (/* binding */ SESSION_READY_EVENT),
7033
+ /* harmony export */ SESSION_READY_EVENT_LEGACY: () => (/* binding */ SESSION_READY_EVENT_LEGACY),
7033
7034
  /* harmony export */ SESSION_SAVE_BUFFER_EVENT: () => (/* binding */ SESSION_SAVE_BUFFER_EVENT),
7034
7035
  /* harmony export */ SESSION_STARTED_EVENT: () => (/* binding */ SESSION_STARTED_EVENT),
7035
7036
  /* harmony export */ SESSION_STATE_PROP_NAME: () => (/* binding */ SESSION_STATE_PROP_NAME),
@@ -7054,14 +7055,16 @@ const SESSION_AUTO_CREATED = 'debug-session:auto-created';
7054
7055
  const SESSION_ADD_EVENT = 'debug-session:rrweb:add-event';
7055
7056
  // Backend-triggered flush of client-side crash buffer
7056
7057
  const SESSION_SAVE_BUFFER_EVENT = 'debug-session:save-buffer';
7058
+ const SESSION_READY_EVENT = 'debug-session:ready';
7059
+ // Deprecated event name for backwards compatibility
7060
+ const SESSION_READY_EVENT_LEGACY = 'debug-session-ready';
7057
7061
  const SOCKET_SET_USER_EVENT = 'socket:set-user';
7058
7062
  const DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE = 100000;
7059
- const SESSION_READY_EVENT = 'debug-session-ready';
7060
7063
  const CONTINUOUS_DEBUGGING_TIMEOUT = 60000; // 1 minutes
7061
7064
  const DEBUG_SESSION_MAX_DURATION_SECONDS = 10 * 60 + 30; // TODO: move to shared config otel core
7062
7065
  const REMOTE_SESSION_RECORDING_START = 'remote-session-recording:start';
7063
7066
  const REMOTE_SESSION_RECORDING_STOP = 'remote-session-recording:stop';
7064
- const PACKAGE_VERSION_EXPORT = "2.0.87" || 0;
7067
+ const PACKAGE_VERSION_EXPORT = "2.0.89" || 0;
7065
7068
  // Regex patterns for OpenTelemetry ignore URLs
7066
7069
  const OTEL_IGNORE_URLS = [
7067
7070
  // Traces endpoint
@@ -7199,6 +7202,7 @@ const BASE_CONFIG = {
7199
7202
  /* harmony export */ SESSION_ID_PROP_NAME: () => (/* reexport safe */ _constants__WEBPACK_IMPORTED_MODULE_0__.SESSION_ID_PROP_NAME),
7200
7203
  /* harmony export */ SESSION_PROP_NAME: () => (/* reexport safe */ _constants__WEBPACK_IMPORTED_MODULE_0__.SESSION_PROP_NAME),
7201
7204
  /* harmony export */ SESSION_READY_EVENT: () => (/* reexport safe */ _constants__WEBPACK_IMPORTED_MODULE_0__.SESSION_READY_EVENT),
7205
+ /* harmony export */ SESSION_READY_EVENT_LEGACY: () => (/* reexport safe */ _constants__WEBPACK_IMPORTED_MODULE_0__.SESSION_READY_EVENT_LEGACY),
7202
7206
  /* harmony export */ SESSION_SAVE_BUFFER_EVENT: () => (/* reexport safe */ _constants__WEBPACK_IMPORTED_MODULE_0__.SESSION_SAVE_BUFFER_EVENT),
7203
7207
  /* harmony export */ SESSION_STARTED_EVENT: () => (/* reexport safe */ _constants__WEBPACK_IMPORTED_MODULE_0__.SESSION_STARTED_EVENT),
7204
7208
  /* harmony export */ SESSION_STATE_PROP_NAME: () => (/* reexport safe */ _constants__WEBPACK_IMPORTED_MODULE_0__.SESSION_STATE_PROP_NAME),
@@ -7459,24 +7463,43 @@ const isBrowserExtension = isBrowser && SESSION_RECORDER_INJECTED in globalObj;
7459
7463
  /* harmony export */ setupListeners: () => (/* binding */ setupListeners)
7460
7464
  /* harmony export */ });
7461
7465
  /* harmony import */ var _services_messaging_service__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./services/messaging.service */ "./src/services/messaging.service.ts");
7462
- /* harmony import */ var _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @multiplayer-app/session-recorder-common */ "../session-recorder-common/dist/esm/index-browser.js");
7466
+ /* harmony import */ var _config_constants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./config/constants */ "./src/config/constants.ts");
7467
+ /* harmony import */ var _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @multiplayer-app/session-recorder-common */ "../session-recorder-common/dist/esm/index-browser.js");
7468
+
7463
7469
 
7464
7470
 
7465
7471
  function setupListeners(sessionRecorder) {
7466
- // Send ready message with session info
7467
- _services_messaging_service__WEBPACK_IMPORTED_MODULE_0__["default"].sendMessage('ready', {
7468
- session: sessionRecorder.session,
7469
- sessionState: sessionRecorder.sessionState,
7472
+ const announceState = () => {
7473
+ // Send continuous flag first so it is applied before the consumer reacts to
7474
+ // the 'ready' message (postMessage preserves delivery order).
7475
+ _services_messaging_service__WEBPACK_IMPORTED_MODULE_0__["default"].sendMessage('continuous-debugging', sessionRecorder.continuousRecording);
7476
+ _services_messaging_service__WEBPACK_IMPORTED_MODULE_0__["default"].sendMessage('ready', {
7477
+ session: sessionRecorder.session,
7478
+ sessionState: sessionRecorder.sessionState,
7479
+ // Lets the extension popup detect libs too old to support on-demand state
7480
+ // sync (the `get-state` request below).
7481
+ version: _config_constants__WEBPACK_IMPORTED_MODULE_1__.PACKAGE_VERSION_EXPORT,
7482
+ });
7483
+ };
7484
+ // Announce current state on load...
7485
+ announceState();
7486
+ // ...and whenever the extension asks (e.g. the popup was opened after the
7487
+ // recorder had already initialized, so it missed the initial announcement).
7488
+ _services_messaging_service__WEBPACK_IMPORTED_MODULE_0__["default"].on('get-state', () => {
7489
+ announceState();
7470
7490
  });
7471
7491
  _services_messaging_service__WEBPACK_IMPORTED_MODULE_0__["default"].on('init', (payload) => {
7472
7492
  sessionRecorder.init(payload);
7473
7493
  });
7474
7494
  _services_messaging_service__WEBPACK_IMPORTED_MODULE_0__["default"].on('start', (payload) => {
7475
- sessionRecorder.start(_multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionType.MANUAL, payload);
7495
+ sessionRecorder.start(_multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_2__.SessionType.MANUAL, payload);
7476
7496
  });
7477
7497
  _services_messaging_service__WEBPACK_IMPORTED_MODULE_0__["default"].on('end', (payload) => {
7478
7498
  sessionRecorder.stop(payload);
7479
7499
  });
7500
+ _services_messaging_service__WEBPACK_IMPORTED_MODULE_0__["default"].on('stop', (payload) => {
7501
+ sessionRecorder.stop(payload);
7502
+ });
7480
7503
  _services_messaging_service__WEBPACK_IMPORTED_MODULE_0__["default"].on('pause', () => {
7481
7504
  sessionRecorder.pause();
7482
7505
  });
@@ -7488,7 +7511,7 @@ function setupListeners(sessionRecorder) {
7488
7511
  });
7489
7512
  _services_messaging_service__WEBPACK_IMPORTED_MODULE_0__["default"].on('toggle-continuous-debugging', (payload) => {
7490
7513
  if (payload.enabled) {
7491
- sessionRecorder.start(_multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionType.CONTINUOUS, payload.session);
7514
+ sessionRecorder.start(_multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_2__.SessionType.CONTINUOUS, payload.session);
7492
7515
  }
7493
7516
  else {
7494
7517
  sessionRecorder.stop();
@@ -10278,7 +10301,10 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
10278
10301
  _services_messaging_service__WEBPACK_IMPORTED_MODULE_10__["default"].sendMessage('state-change', this._sessionState);
10279
10302
  (0,_utils__WEBPACK_IMPORTED_MODULE_4__.setStoredItem)(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_STATE_PROP_NAME, state);
10280
10303
  // Emit observable event to support React wrapper
10281
- this.emit('state-change', [this._sessionState || _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.stopped, this.sessionType]);
10304
+ this.emit('state-change', [
10305
+ this._sessionState || _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.stopped,
10306
+ this.sessionType,
10307
+ ]);
10282
10308
  }
10283
10309
  get session() {
10284
10310
  return this._session;
@@ -10339,6 +10365,10 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
10339
10365
  // Session ID and state are stored in sessionStorage (with fallback) to avoid multi-tab conflicts
10340
10366
  this._sessionId = null;
10341
10367
  this._sessionType = _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionType.MANUAL;
10368
+ // Guards against overlapping start() calls. `_checkOperation('start')` only
10369
+ // rejects once sessionState is `started`, but `_createSessionAndStart` is
10370
+ // async, so two rapid starts could both pass and create duplicate sessions.
10371
+ this._startInProgress = false;
10342
10372
  this._sessionState = null;
10343
10373
  this._session = null;
10344
10374
  this._sessionAttributes = null;
@@ -10349,10 +10379,18 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
10349
10379
  this._error = '';
10350
10380
  // Safety: avoid accessing storage in SSR/non-browser environments
10351
10381
  const isBrowser = typeof window !== 'undefined';
10352
- const sessionLocal = isBrowser ? (0,_utils__WEBPACK_IMPORTED_MODULE_4__.getStoredItem)(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_PROP_NAME, true) : null;
10353
- const sessionIdLocal = isBrowser ? (0,_utils__WEBPACK_IMPORTED_MODULE_4__.getStoredItem)(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_ID_PROP_NAME) : null;
10354
- const sessionStateLocal = isBrowser ? (0,_utils__WEBPACK_IMPORTED_MODULE_4__.getStoredItem)(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_STATE_PROP_NAME) : null;
10355
- const sessionTypeLocal = isBrowser ? (0,_utils__WEBPACK_IMPORTED_MODULE_4__.getStoredItem)(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_TYPE_PROP_NAME) : null;
10382
+ const sessionLocal = isBrowser
10383
+ ? (0,_utils__WEBPACK_IMPORTED_MODULE_4__.getStoredItem)(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_PROP_NAME, true)
10384
+ : null;
10385
+ const sessionIdLocal = isBrowser
10386
+ ? (0,_utils__WEBPACK_IMPORTED_MODULE_4__.getStoredItem)(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_ID_PROP_NAME)
10387
+ : null;
10388
+ const sessionStateLocal = isBrowser
10389
+ ? (0,_utils__WEBPACK_IMPORTED_MODULE_4__.getStoredItem)(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_STATE_PROP_NAME)
10390
+ : null;
10391
+ const sessionTypeLocal = isBrowser
10392
+ ? (0,_utils__WEBPACK_IMPORTED_MODULE_4__.getStoredItem)(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_TYPE_PROP_NAME)
10393
+ : null;
10356
10394
  if ((0,_utils__WEBPACK_IMPORTED_MODULE_4__.isSessionActive)(sessionLocal, sessionTypeLocal)) {
10357
10395
  this.session = sessionLocal;
10358
10396
  this.sessionId = sessionIdLocal;
@@ -10384,7 +10422,8 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
10384
10422
  // GC: remove orphaned crash buffers from old tabs.
10385
10423
  // Keep TTL large to avoid any accidental data loss.
10386
10424
  void this._bufferDb.sweepStaleTabs(10 * 60 * 60 * 1000);
10387
- (0,_patch__WEBPACK_IMPORTED_MODULE_7__.setMaxCapturingHttpPayloadSize)(this._configs.maxCapturingHttpPayloadSize || _config__WEBPACK_IMPORTED_MODULE_6__.DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE);
10425
+ (0,_patch__WEBPACK_IMPORTED_MODULE_7__.setMaxCapturingHttpPayloadSize)(this._configs.maxCapturingHttpPayloadSize ||
10426
+ _config__WEBPACK_IMPORTED_MODULE_6__.DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE);
10388
10427
  (0,_patch__WEBPACK_IMPORTED_MODULE_7__.setShouldRecordHttpData)(this._configs.captureBody, this._configs.captureHeaders);
10389
10428
  this._setupCrashBuffer();
10390
10429
  this._tracer.init(this._configs);
@@ -10406,7 +10445,9 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
10406
10445
  if (this._configs.apiKey) {
10407
10446
  this._recorder.init(this._configs, this._socketService);
10408
10447
  }
10409
- if (this.sessionId && (this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.started || this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.paused)) {
10448
+ if (this.sessionId &&
10449
+ (this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.started ||
10450
+ this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.paused)) {
10410
10451
  this._start();
10411
10452
  }
10412
10453
  else {
@@ -10460,7 +10501,8 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
10460
10501
  return;
10461
10502
  let hasFocus = true;
10462
10503
  try {
10463
- hasFocus = typeof document.hasFocus === 'function' ? document.hasFocus() : true;
10504
+ hasFocus =
10505
+ typeof document.hasFocus === 'function' ? document.hasFocus() : true;
10464
10506
  }
10465
10507
  catch (_e) {
10466
10508
  hasFocus = true;
@@ -10482,7 +10524,9 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
10482
10524
  // stop, leaving the buffer with no FullSnapshot and silently breaking
10483
10525
  // exception-triggered flushBuffer. `_recorder.restart(null, ...)` passes
10484
10526
  // null explicitly, so it's safe regardless of `this.sessionId`.
10485
- 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_5__.SessionState.stopped) {
10527
+ if (!this._crashBuffer ||
10528
+ !((_b = (_a = this._configs) === null || _a === void 0 ? void 0 : _a.buffering) === null || _b === void 0 ? void 0 : _b.enabled) ||
10529
+ this.sessionState !== _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.stopped) {
10486
10530
  return;
10487
10531
  }
10488
10532
  void this._recorder.restart(null, _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionType.MANUAL);
@@ -10531,9 +10575,13 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
10531
10575
  * @param session - the session to start
10532
10576
  */
10533
10577
  start(type = _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionType.MANUAL, session) {
10578
+ if (this._startInProgress)
10579
+ return;
10534
10580
  this._checkOperation('start');
10581
+ this._startInProgress = true;
10535
10582
  // If continuous recording is disabled, force plain mode
10536
- if (type === _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionType.CONTINUOUS && !this._configs.showContinuousRecording) {
10583
+ if (type === _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionType.CONTINUOUS &&
10584
+ !this._configs.showContinuousRecording) {
10537
10585
  type = _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionType.MANUAL;
10538
10586
  }
10539
10587
  this.sessionType = type;
@@ -10564,7 +10612,8 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
10564
10612
  stoppedAt: this._recorder.stoppedAt,
10565
10613
  };
10566
10614
  const response = await this._apiService.stopSession(sid, request);
10567
- _eventBus__WEBPACK_IMPORTED_MODULE_8__.recorderEventBus.emit(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_READY_EVENT, response._id);
10615
+ _eventBus__WEBPACK_IMPORTED_MODULE_8__.recorderEventBus.emit(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_READY_EVENT_LEGACY, response._id);
10616
+ _eventBus__WEBPACK_IMPORTED_MODULE_8__.recorderEventBus.emit(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_READY_EVENT, response);
10568
10617
  }
10569
10618
  }
10570
10619
  catch (error) {
@@ -10660,8 +10709,8 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
10660
10709
  this.error = (e === null || e === void 0 ? void 0 : e.message) || 'Failed to capture exception';
10661
10710
  }
10662
10711
  }
10663
- async _flushBuffer(sessionId, force = false) {
10664
- if (!sessionId || !this._crashBuffer || this._isFlushingBuffer) {
10712
+ async _flushBuffer(session, force = false) {
10713
+ if (!session || !this._crashBuffer || this._isFlushingBuffer) {
10665
10714
  return null;
10666
10715
  }
10667
10716
  this._isFlushingBuffer = true;
@@ -10672,8 +10721,10 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
10672
10721
  }
10673
10722
  await Promise.all([
10674
10723
  this._tracer.exportTraces(spans.map((s) => s.span)),
10675
- this._apiService.exportEvents(sessionId, { events: events.map((e) => e.event) }),
10676
- this._apiService.updateSessionAttributes(sessionId, {
10724
+ this._apiService.exportEvents(session._id, {
10725
+ events: events.map((e) => e.event),
10726
+ }),
10727
+ this._apiService.updateSessionAttributes(session._id, {
10677
10728
  startedAt: this._toCrashBufferSessionIso(startedAt),
10678
10729
  stoppedAt: this._toCrashBufferSessionIso(stoppedAt),
10679
10730
  sessionAttributes: this.sessionAttributes,
@@ -10688,7 +10739,8 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
10688
10739
  finally {
10689
10740
  await this._crashBuffer.clear();
10690
10741
  this._isFlushingBuffer = false;
10691
- _eventBus__WEBPACK_IMPORTED_MODULE_8__.recorderEventBus.emit(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_READY_EVENT, sessionId);
10742
+ _eventBus__WEBPACK_IMPORTED_MODULE_8__.recorderEventBus.emit(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_READY_EVENT_LEGACY, session._id);
10743
+ _eventBus__WEBPACK_IMPORTED_MODULE_8__.recorderEventBus.emit(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_READY_EVENT, session);
10692
10744
  }
10693
10745
  }
10694
10746
  _toCrashBufferSessionIso(ts) {
@@ -10777,7 +10829,8 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
10777
10829
  * Handle the safe stop event
10778
10830
  */
10779
10831
  _handleStop(comment) {
10780
- if (this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.started || this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.paused) {
10832
+ if (this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.started ||
10833
+ this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.paused) {
10781
10834
  this.stop(comment);
10782
10835
  }
10783
10836
  }
@@ -10801,7 +10854,8 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
10801
10854
  * Handle the safe cancel event
10802
10855
  */
10803
10856
  _handleCancel() {
10804
- if (this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.started || this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.paused) {
10857
+ if (this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.started ||
10858
+ this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.paused) {
10805
10859
  this.cancel();
10806
10860
  }
10807
10861
  }
@@ -10809,7 +10863,8 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
10809
10863
  * Handle the safe save event
10810
10864
  */
10811
10865
  _handleSave() {
10812
- if (this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.started && this.continuousRecording) {
10866
+ if (this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.started &&
10867
+ this.continuousRecording) {
10813
10868
  this.save();
10814
10869
  }
10815
10870
  }
@@ -10854,12 +10909,12 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
10854
10909
  }
10855
10910
  });
10856
10911
  this._socketService.on(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_SAVE_BUFFER_EVENT, (payload) => {
10857
- var _a, _b, _c;
10912
+ var _a, _b;
10858
10913
  if (this.sessionState !== _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.stopped)
10859
10914
  return;
10860
- this._flushBuffer((_a = payload === null || payload === void 0 ? void 0 : payload.debugSession) === null || _a === void 0 ? void 0 : _a._id, true);
10861
- if ((_b = payload === null || payload === void 0 ? void 0 : payload.debugSession) === null || _b === void 0 ? void 0 : _b.url) {
10862
- _eventBus__WEBPACK_IMPORTED_MODULE_8__.recorderEventBus.emit(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_AUTO_CREATED, (_c = payload === null || payload === void 0 ? void 0 : payload.debugSession) === null || _c === void 0 ? void 0 : _c.url);
10915
+ this._flushBuffer(payload === null || payload === void 0 ? void 0 : payload.debugSession, true);
10916
+ if ((_a = payload === null || payload === void 0 ? void 0 : payload.debugSession) === null || _a === void 0 ? void 0 : _a.url) {
10917
+ _eventBus__WEBPACK_IMPORTED_MODULE_8__.recorderEventBus.emit(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_AUTO_CREATED, (_b = payload === null || payload === void 0 ? void 0 : payload.debugSession) === null || _b === void 0 ? void 0 : _b.url);
10863
10918
  }
10864
10919
  });
10865
10920
  }
@@ -10867,7 +10922,7 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
10867
10922
  try {
10868
10923
  const session = await this._apiService.createErrorSession({ span });
10869
10924
  if (session === null || session === void 0 ? void 0 : session._id) {
10870
- this._flushBuffer(session._id);
10925
+ this._flushBuffer(session);
10871
10926
  }
10872
10927
  if (session === null || session === void 0 ? void 0 : session.url) {
10873
10928
  _eventBus__WEBPACK_IMPORTED_MODULE_8__.recorderEventBus.emit(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_AUTO_CREATED, session.url);
@@ -10887,18 +10942,25 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
10887
10942
  sessionAttributes: this.sessionAttributes,
10888
10943
  resourceAttributes: (0,_utils__WEBPACK_IMPORTED_MODULE_4__.getNavigatorInfo)(),
10889
10944
  name: this._getSessionName(),
10890
- ...(this._userAttributes ? { userAttributes: this._userAttributes } : {}),
10945
+ ...(this._userAttributes
10946
+ ? { userAttributes: this._userAttributes }
10947
+ : {}),
10891
10948
  };
10892
- const request = !this.continuousRecording ? payload : { debugSessionData: payload };
10949
+ const request = !this.continuousRecording
10950
+ ? payload
10951
+ : { debugSessionData: payload };
10893
10952
  const session = this.continuousRecording
10894
10953
  ? await this._apiService.startContinuousDebugSession(request, signal)
10895
10954
  : await this._apiService.startSession(request, signal);
10896
10955
  if (session) {
10897
- session.sessionType = this.continuousRecording ? _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionType.CONTINUOUS : _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionType.MANUAL;
10956
+ session.sessionType = this.continuousRecording
10957
+ ? _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionType.CONTINUOUS
10958
+ : _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionType.MANUAL;
10898
10959
  this._setupSessionAndStart(session, false);
10899
10960
  }
10900
10961
  }
10901
10962
  catch (error) {
10963
+ this._startInProgress = false;
10902
10964
  this.error = error.message;
10903
10965
  this.sessionState = _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.stopped;
10904
10966
  if (this.continuousRecording) {
@@ -10911,13 +10973,17 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
10911
10973
  */
10912
10974
  _start() {
10913
10975
  var _a, _b;
10976
+ this._startInProgress = false;
10914
10977
  this.sessionState = _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.started;
10915
10978
  // eslint-disable-next-line no-self-assign
10916
10979
  this.sessionType = this.sessionType;
10917
10980
  this._tracer.start(this.sessionId, this.sessionType);
10918
10981
  // Ensure we switch from buffer-only recording to session recording cleanly.
10919
10982
  void this._recorder.restart(this.sessionId, this.sessionType);
10920
- this._navigationRecorder.start({ sessionId: this.sessionId, sessionType: this.sessionType });
10983
+ this._navigationRecorder.start({
10984
+ sessionId: this.sessionId,
10985
+ sessionType: this.sessionType,
10986
+ });
10921
10987
  if (this.session) {
10922
10988
  this._socketService.subscribeToSession(this.session);
10923
10989
  this._sessionWidget.seconds = (0,_utils__WEBPACK_IMPORTED_MODULE_4__.getTimeDifferenceInSeconds)((_a = this.session) === null || _a === void 0 ? void 0 : _a.startedAt);
@@ -10945,8 +11011,12 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
10945
11011
  // rrweb assigns new node IDs on each record() call, so the next buffer
10946
11012
  // segment must not carry events from the previous generation. Await the
10947
11013
  // clear so its IDB tx can't race past the fresh FullSnapshot.
10948
- const cleared = this._crashBuffer ? this._crashBuffer.clear() : Promise.resolve();
10949
- void cleared.catch(() => undefined).then(() => this._startBufferOnlyRecording());
11014
+ const cleared = this._crashBuffer
11015
+ ? this._crashBuffer.clear()
11016
+ : Promise.resolve();
11017
+ void cleared
11018
+ .catch(() => undefined)
11019
+ .then(() => this._startBufferOnlyRecording());
10950
11020
  }
10951
11021
  /**
10952
11022
  * Pause the session tracing and recording
@@ -10982,7 +11052,10 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
10982
11052
  * @param sessionId - the session ID to set or clear
10983
11053
  */
10984
11054
  _setSession(session) {
10985
- this.session = { ...session, startedAt: session.startedAt || new Date().toISOString() };
11055
+ this.session = {
11056
+ ...session,
11057
+ startedAt: session.startedAt || new Date().toISOString(),
11058
+ };
10986
11059
  this.sessionId = (session === null || session === void 0 ? void 0 : session.shortId) || (session === null || session === void 0 ? void 0 : session._id);
10987
11060
  }
10988
11061
  _clearSession() {
@@ -11005,7 +11078,8 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
11005
11078
  }
11006
11079
  break;
11007
11080
  case 'stop':
11008
- if (this.sessionState !== _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.paused && this.sessionState !== _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.started) {
11081
+ if (this.sessionState !== _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.paused &&
11082
+ this.sessionState !== _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.started) {
11009
11083
  throw new Error('Cannot stop. Session is not currently started.');
11010
11084
  }
11011
11085
  break;
@@ -11083,7 +11157,10 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
11083
11157
  */
11084
11158
  _getSessionName(date = new Date()) {
11085
11159
  var _a, _b, _c;
11086
- const userName = ((_a = this.sessionAttributes) === null || _a === void 0 ? void 0 : _a.userName) || ((_b = this._userAttributes) === null || _b === void 0 ? void 0 : _b.userName) || ((_c = this._userAttributes) === null || _c === void 0 ? void 0 : _c.name) || '';
11160
+ const userName = ((_a = this.sessionAttributes) === null || _a === void 0 ? void 0 : _a.userName) ||
11161
+ ((_b = this._userAttributes) === null || _b === void 0 ? void 0 : _b.userName) ||
11162
+ ((_c = this._userAttributes) === null || _c === void 0 ? void 0 : _c.name) ||
11163
+ '';
11087
11164
  return userName
11088
11165
  ? `${userName}'s session on ${(0,_utils__WEBPACK_IMPORTED_MODULE_4__.getFormattedDate)(date, { month: 'short', day: 'numeric' })}`
11089
11166
  : `Session on ${(0,_utils__WEBPACK_IMPORTED_MODULE_4__.getFormattedDate)(date)}`;
package/dist/index.umd.js CHANGED
@@ -21463,6 +21463,7 @@ module.exports = {
21463
21463
  /* harmony export */ SESSION_ID_PROP_NAME: () => (/* binding */ SESSION_ID_PROP_NAME),
21464
21464
  /* harmony export */ SESSION_PROP_NAME: () => (/* binding */ SESSION_PROP_NAME),
21465
21465
  /* harmony export */ SESSION_READY_EVENT: () => (/* binding */ SESSION_READY_EVENT),
21466
+ /* harmony export */ SESSION_READY_EVENT_LEGACY: () => (/* binding */ SESSION_READY_EVENT_LEGACY),
21466
21467
  /* harmony export */ SESSION_SAVE_BUFFER_EVENT: () => (/* binding */ SESSION_SAVE_BUFFER_EVENT),
21467
21468
  /* harmony export */ SESSION_STARTED_EVENT: () => (/* binding */ SESSION_STARTED_EVENT),
21468
21469
  /* harmony export */ SESSION_STATE_PROP_NAME: () => (/* binding */ SESSION_STATE_PROP_NAME),
@@ -21487,14 +21488,16 @@ const SESSION_AUTO_CREATED = 'debug-session:auto-created';
21487
21488
  const SESSION_ADD_EVENT = 'debug-session:rrweb:add-event';
21488
21489
  // Backend-triggered flush of client-side crash buffer
21489
21490
  const SESSION_SAVE_BUFFER_EVENT = 'debug-session:save-buffer';
21491
+ const SESSION_READY_EVENT = 'debug-session:ready';
21492
+ // Deprecated event name for backwards compatibility
21493
+ const SESSION_READY_EVENT_LEGACY = 'debug-session-ready';
21490
21494
  const SOCKET_SET_USER_EVENT = 'socket:set-user';
21491
21495
  const DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE = 100000;
21492
- const SESSION_READY_EVENT = 'debug-session-ready';
21493
21496
  const CONTINUOUS_DEBUGGING_TIMEOUT = 60000; // 1 minutes
21494
21497
  const DEBUG_SESSION_MAX_DURATION_SECONDS = 10 * 60 + 30; // TODO: move to shared config otel core
21495
21498
  const REMOTE_SESSION_RECORDING_START = 'remote-session-recording:start';
21496
21499
  const REMOTE_SESSION_RECORDING_STOP = 'remote-session-recording:stop';
21497
- const PACKAGE_VERSION_EXPORT = "2.0.87" || 0;
21500
+ const PACKAGE_VERSION_EXPORT = "2.0.89" || 0;
21498
21501
  // Regex patterns for OpenTelemetry ignore URLs
21499
21502
  const OTEL_IGNORE_URLS = [
21500
21503
  // Traces endpoint
@@ -21634,6 +21637,7 @@ const BASE_CONFIG = {
21634
21637
  /* harmony export */ SESSION_ID_PROP_NAME: () => (/* reexport safe */ _constants__WEBPACK_IMPORTED_MODULE_0__.SESSION_ID_PROP_NAME),
21635
21638
  /* harmony export */ SESSION_PROP_NAME: () => (/* reexport safe */ _constants__WEBPACK_IMPORTED_MODULE_0__.SESSION_PROP_NAME),
21636
21639
  /* harmony export */ SESSION_READY_EVENT: () => (/* reexport safe */ _constants__WEBPACK_IMPORTED_MODULE_0__.SESSION_READY_EVENT),
21640
+ /* harmony export */ SESSION_READY_EVENT_LEGACY: () => (/* reexport safe */ _constants__WEBPACK_IMPORTED_MODULE_0__.SESSION_READY_EVENT_LEGACY),
21637
21641
  /* harmony export */ SESSION_SAVE_BUFFER_EVENT: () => (/* reexport safe */ _constants__WEBPACK_IMPORTED_MODULE_0__.SESSION_SAVE_BUFFER_EVENT),
21638
21642
  /* harmony export */ SESSION_STARTED_EVENT: () => (/* reexport safe */ _constants__WEBPACK_IMPORTED_MODULE_0__.SESSION_STARTED_EVENT),
21639
21643
  /* harmony export */ SESSION_STATE_PROP_NAME: () => (/* reexport safe */ _constants__WEBPACK_IMPORTED_MODULE_0__.SESSION_STATE_PROP_NAME),
@@ -21900,24 +21904,43 @@ const isBrowserExtension = isBrowser && SESSION_RECORDER_INJECTED in globalObj;
21900
21904
  /* harmony export */ setupListeners: () => (/* binding */ setupListeners)
21901
21905
  /* harmony export */ });
21902
21906
  /* harmony import */ var _services_messaging_service__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./services/messaging.service */ "./src/services/messaging.service.ts");
21903
- /* harmony import */ var _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @multiplayer-app/session-recorder-common */ "../session-recorder-common/dist/esm/index-browser.js");
21907
+ /* harmony import */ var _config_constants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./config/constants */ "./src/config/constants.ts");
21908
+ /* harmony import */ var _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @multiplayer-app/session-recorder-common */ "../session-recorder-common/dist/esm/index-browser.js");
21909
+
21904
21910
 
21905
21911
 
21906
21912
  function setupListeners(sessionRecorder) {
21907
- // Send ready message with session info
21908
- _services_messaging_service__WEBPACK_IMPORTED_MODULE_0__["default"].sendMessage('ready', {
21909
- session: sessionRecorder.session,
21910
- sessionState: sessionRecorder.sessionState,
21913
+ const announceState = () => {
21914
+ // Send continuous flag first so it is applied before the consumer reacts to
21915
+ // the 'ready' message (postMessage preserves delivery order).
21916
+ _services_messaging_service__WEBPACK_IMPORTED_MODULE_0__["default"].sendMessage('continuous-debugging', sessionRecorder.continuousRecording);
21917
+ _services_messaging_service__WEBPACK_IMPORTED_MODULE_0__["default"].sendMessage('ready', {
21918
+ session: sessionRecorder.session,
21919
+ sessionState: sessionRecorder.sessionState,
21920
+ // Lets the extension popup detect libs too old to support on-demand state
21921
+ // sync (the `get-state` request below).
21922
+ version: _config_constants__WEBPACK_IMPORTED_MODULE_1__.PACKAGE_VERSION_EXPORT,
21923
+ });
21924
+ };
21925
+ // Announce current state on load...
21926
+ announceState();
21927
+ // ...and whenever the extension asks (e.g. the popup was opened after the
21928
+ // recorder had already initialized, so it missed the initial announcement).
21929
+ _services_messaging_service__WEBPACK_IMPORTED_MODULE_0__["default"].on('get-state', () => {
21930
+ announceState();
21911
21931
  });
21912
21932
  _services_messaging_service__WEBPACK_IMPORTED_MODULE_0__["default"].on('init', (payload) => {
21913
21933
  sessionRecorder.init(payload);
21914
21934
  });
21915
21935
  _services_messaging_service__WEBPACK_IMPORTED_MODULE_0__["default"].on('start', (payload) => {
21916
- sessionRecorder.start(_multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionType.MANUAL, payload);
21936
+ sessionRecorder.start(_multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_2__.SessionType.MANUAL, payload);
21917
21937
  });
21918
21938
  _services_messaging_service__WEBPACK_IMPORTED_MODULE_0__["default"].on('end', (payload) => {
21919
21939
  sessionRecorder.stop(payload);
21920
21940
  });
21941
+ _services_messaging_service__WEBPACK_IMPORTED_MODULE_0__["default"].on('stop', (payload) => {
21942
+ sessionRecorder.stop(payload);
21943
+ });
21921
21944
  _services_messaging_service__WEBPACK_IMPORTED_MODULE_0__["default"].on('pause', () => {
21922
21945
  sessionRecorder.pause();
21923
21946
  });
@@ -21929,7 +21952,7 @@ function setupListeners(sessionRecorder) {
21929
21952
  });
21930
21953
  _services_messaging_service__WEBPACK_IMPORTED_MODULE_0__["default"].on('toggle-continuous-debugging', (payload) => {
21931
21954
  if (payload.enabled) {
21932
- sessionRecorder.start(_multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionType.CONTINUOUS, payload.session);
21955
+ sessionRecorder.start(_multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_2__.SessionType.CONTINUOUS, payload.session);
21933
21956
  }
21934
21957
  else {
21935
21958
  sessionRecorder.stop();
@@ -24739,7 +24762,10 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
24739
24762
  _services_messaging_service__WEBPACK_IMPORTED_MODULE_10__["default"].sendMessage('state-change', this._sessionState);
24740
24763
  (0,_utils__WEBPACK_IMPORTED_MODULE_4__.setStoredItem)(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_STATE_PROP_NAME, state);
24741
24764
  // Emit observable event to support React wrapper
24742
- this.emit('state-change', [this._sessionState || _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.stopped, this.sessionType]);
24765
+ this.emit('state-change', [
24766
+ this._sessionState || _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.stopped,
24767
+ this.sessionType,
24768
+ ]);
24743
24769
  }
24744
24770
  get session() {
24745
24771
  return this._session;
@@ -24800,6 +24826,10 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
24800
24826
  // Session ID and state are stored in sessionStorage (with fallback) to avoid multi-tab conflicts
24801
24827
  this._sessionId = null;
24802
24828
  this._sessionType = _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionType.MANUAL;
24829
+ // Guards against overlapping start() calls. `_checkOperation('start')` only
24830
+ // rejects once sessionState is `started`, but `_createSessionAndStart` is
24831
+ // async, so two rapid starts could both pass and create duplicate sessions.
24832
+ this._startInProgress = false;
24803
24833
  this._sessionState = null;
24804
24834
  this._session = null;
24805
24835
  this._sessionAttributes = null;
@@ -24810,10 +24840,18 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
24810
24840
  this._error = '';
24811
24841
  // Safety: avoid accessing storage in SSR/non-browser environments
24812
24842
  const isBrowser = typeof window !== 'undefined';
24813
- const sessionLocal = isBrowser ? (0,_utils__WEBPACK_IMPORTED_MODULE_4__.getStoredItem)(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_PROP_NAME, true) : null;
24814
- const sessionIdLocal = isBrowser ? (0,_utils__WEBPACK_IMPORTED_MODULE_4__.getStoredItem)(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_ID_PROP_NAME) : null;
24815
- const sessionStateLocal = isBrowser ? (0,_utils__WEBPACK_IMPORTED_MODULE_4__.getStoredItem)(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_STATE_PROP_NAME) : null;
24816
- const sessionTypeLocal = isBrowser ? (0,_utils__WEBPACK_IMPORTED_MODULE_4__.getStoredItem)(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_TYPE_PROP_NAME) : null;
24843
+ const sessionLocal = isBrowser
24844
+ ? (0,_utils__WEBPACK_IMPORTED_MODULE_4__.getStoredItem)(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_PROP_NAME, true)
24845
+ : null;
24846
+ const sessionIdLocal = isBrowser
24847
+ ? (0,_utils__WEBPACK_IMPORTED_MODULE_4__.getStoredItem)(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_ID_PROP_NAME)
24848
+ : null;
24849
+ const sessionStateLocal = isBrowser
24850
+ ? (0,_utils__WEBPACK_IMPORTED_MODULE_4__.getStoredItem)(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_STATE_PROP_NAME)
24851
+ : null;
24852
+ const sessionTypeLocal = isBrowser
24853
+ ? (0,_utils__WEBPACK_IMPORTED_MODULE_4__.getStoredItem)(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_TYPE_PROP_NAME)
24854
+ : null;
24817
24855
  if ((0,_utils__WEBPACK_IMPORTED_MODULE_4__.isSessionActive)(sessionLocal, sessionTypeLocal)) {
24818
24856
  this.session = sessionLocal;
24819
24857
  this.sessionId = sessionIdLocal;
@@ -24845,7 +24883,8 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
24845
24883
  // GC: remove orphaned crash buffers from old tabs.
24846
24884
  // Keep TTL large to avoid any accidental data loss.
24847
24885
  void this._bufferDb.sweepStaleTabs(10 * 60 * 60 * 1000);
24848
- (0,_patch__WEBPACK_IMPORTED_MODULE_7__.setMaxCapturingHttpPayloadSize)(this._configs.maxCapturingHttpPayloadSize || _config__WEBPACK_IMPORTED_MODULE_6__.DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE);
24886
+ (0,_patch__WEBPACK_IMPORTED_MODULE_7__.setMaxCapturingHttpPayloadSize)(this._configs.maxCapturingHttpPayloadSize ||
24887
+ _config__WEBPACK_IMPORTED_MODULE_6__.DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE);
24849
24888
  (0,_patch__WEBPACK_IMPORTED_MODULE_7__.setShouldRecordHttpData)(this._configs.captureBody, this._configs.captureHeaders);
24850
24889
  this._setupCrashBuffer();
24851
24890
  this._tracer.init(this._configs);
@@ -24867,7 +24906,9 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
24867
24906
  if (this._configs.apiKey) {
24868
24907
  this._recorder.init(this._configs, this._socketService);
24869
24908
  }
24870
- if (this.sessionId && (this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.started || this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.paused)) {
24909
+ if (this.sessionId &&
24910
+ (this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.started ||
24911
+ this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.paused)) {
24871
24912
  this._start();
24872
24913
  }
24873
24914
  else {
@@ -24921,7 +24962,8 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
24921
24962
  return;
24922
24963
  let hasFocus = true;
24923
24964
  try {
24924
- hasFocus = typeof document.hasFocus === 'function' ? document.hasFocus() : true;
24965
+ hasFocus =
24966
+ typeof document.hasFocus === 'function' ? document.hasFocus() : true;
24925
24967
  }
24926
24968
  catch (_e) {
24927
24969
  hasFocus = true;
@@ -24943,7 +24985,9 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
24943
24985
  // stop, leaving the buffer with no FullSnapshot and silently breaking
24944
24986
  // exception-triggered flushBuffer. `_recorder.restart(null, ...)` passes
24945
24987
  // null explicitly, so it's safe regardless of `this.sessionId`.
24946
- 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_5__.SessionState.stopped) {
24988
+ if (!this._crashBuffer ||
24989
+ !((_b = (_a = this._configs) === null || _a === void 0 ? void 0 : _a.buffering) === null || _b === void 0 ? void 0 : _b.enabled) ||
24990
+ this.sessionState !== _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.stopped) {
24947
24991
  return;
24948
24992
  }
24949
24993
  void this._recorder.restart(null, _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionType.MANUAL);
@@ -24992,9 +25036,13 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
24992
25036
  * @param session - the session to start
24993
25037
  */
24994
25038
  start(type = _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionType.MANUAL, session) {
25039
+ if (this._startInProgress)
25040
+ return;
24995
25041
  this._checkOperation('start');
25042
+ this._startInProgress = true;
24996
25043
  // If continuous recording is disabled, force plain mode
24997
- if (type === _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionType.CONTINUOUS && !this._configs.showContinuousRecording) {
25044
+ if (type === _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionType.CONTINUOUS &&
25045
+ !this._configs.showContinuousRecording) {
24998
25046
  type = _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionType.MANUAL;
24999
25047
  }
25000
25048
  this.sessionType = type;
@@ -25025,7 +25073,8 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
25025
25073
  stoppedAt: this._recorder.stoppedAt,
25026
25074
  };
25027
25075
  const response = await this._apiService.stopSession(sid, request);
25028
- _eventBus__WEBPACK_IMPORTED_MODULE_8__.recorderEventBus.emit(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_READY_EVENT, response._id);
25076
+ _eventBus__WEBPACK_IMPORTED_MODULE_8__.recorderEventBus.emit(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_READY_EVENT_LEGACY, response._id);
25077
+ _eventBus__WEBPACK_IMPORTED_MODULE_8__.recorderEventBus.emit(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_READY_EVENT, response);
25029
25078
  }
25030
25079
  }
25031
25080
  catch (error) {
@@ -25121,8 +25170,8 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
25121
25170
  this.error = (e === null || e === void 0 ? void 0 : e.message) || 'Failed to capture exception';
25122
25171
  }
25123
25172
  }
25124
- async _flushBuffer(sessionId, force = false) {
25125
- if (!sessionId || !this._crashBuffer || this._isFlushingBuffer) {
25173
+ async _flushBuffer(session, force = false) {
25174
+ if (!session || !this._crashBuffer || this._isFlushingBuffer) {
25126
25175
  return null;
25127
25176
  }
25128
25177
  this._isFlushingBuffer = true;
@@ -25133,8 +25182,10 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
25133
25182
  }
25134
25183
  await Promise.all([
25135
25184
  this._tracer.exportTraces(spans.map((s) => s.span)),
25136
- this._apiService.exportEvents(sessionId, { events: events.map((e) => e.event) }),
25137
- this._apiService.updateSessionAttributes(sessionId, {
25185
+ this._apiService.exportEvents(session._id, {
25186
+ events: events.map((e) => e.event),
25187
+ }),
25188
+ this._apiService.updateSessionAttributes(session._id, {
25138
25189
  startedAt: this._toCrashBufferSessionIso(startedAt),
25139
25190
  stoppedAt: this._toCrashBufferSessionIso(stoppedAt),
25140
25191
  sessionAttributes: this.sessionAttributes,
@@ -25149,7 +25200,8 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
25149
25200
  finally {
25150
25201
  await this._crashBuffer.clear();
25151
25202
  this._isFlushingBuffer = false;
25152
- _eventBus__WEBPACK_IMPORTED_MODULE_8__.recorderEventBus.emit(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_READY_EVENT, sessionId);
25203
+ _eventBus__WEBPACK_IMPORTED_MODULE_8__.recorderEventBus.emit(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_READY_EVENT_LEGACY, session._id);
25204
+ _eventBus__WEBPACK_IMPORTED_MODULE_8__.recorderEventBus.emit(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_READY_EVENT, session);
25153
25205
  }
25154
25206
  }
25155
25207
  _toCrashBufferSessionIso(ts) {
@@ -25238,7 +25290,8 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
25238
25290
  * Handle the safe stop event
25239
25291
  */
25240
25292
  _handleStop(comment) {
25241
- if (this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.started || this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.paused) {
25293
+ if (this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.started ||
25294
+ this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.paused) {
25242
25295
  this.stop(comment);
25243
25296
  }
25244
25297
  }
@@ -25262,7 +25315,8 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
25262
25315
  * Handle the safe cancel event
25263
25316
  */
25264
25317
  _handleCancel() {
25265
- if (this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.started || this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.paused) {
25318
+ if (this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.started ||
25319
+ this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.paused) {
25266
25320
  this.cancel();
25267
25321
  }
25268
25322
  }
@@ -25270,7 +25324,8 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
25270
25324
  * Handle the safe save event
25271
25325
  */
25272
25326
  _handleSave() {
25273
- if (this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.started && this.continuousRecording) {
25327
+ if (this.sessionState === _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.started &&
25328
+ this.continuousRecording) {
25274
25329
  this.save();
25275
25330
  }
25276
25331
  }
@@ -25315,12 +25370,12 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
25315
25370
  }
25316
25371
  });
25317
25372
  this._socketService.on(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_SAVE_BUFFER_EVENT, (payload) => {
25318
- var _a, _b, _c;
25373
+ var _a, _b;
25319
25374
  if (this.sessionState !== _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.stopped)
25320
25375
  return;
25321
- this._flushBuffer((_a = payload === null || payload === void 0 ? void 0 : payload.debugSession) === null || _a === void 0 ? void 0 : _a._id, true);
25322
- if ((_b = payload === null || payload === void 0 ? void 0 : payload.debugSession) === null || _b === void 0 ? void 0 : _b.url) {
25323
- _eventBus__WEBPACK_IMPORTED_MODULE_8__.recorderEventBus.emit(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_AUTO_CREATED, (_c = payload === null || payload === void 0 ? void 0 : payload.debugSession) === null || _c === void 0 ? void 0 : _c.url);
25376
+ this._flushBuffer(payload === null || payload === void 0 ? void 0 : payload.debugSession, true);
25377
+ if ((_a = payload === null || payload === void 0 ? void 0 : payload.debugSession) === null || _a === void 0 ? void 0 : _a.url) {
25378
+ _eventBus__WEBPACK_IMPORTED_MODULE_8__.recorderEventBus.emit(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_AUTO_CREATED, (_b = payload === null || payload === void 0 ? void 0 : payload.debugSession) === null || _b === void 0 ? void 0 : _b.url);
25324
25379
  }
25325
25380
  });
25326
25381
  }
@@ -25328,7 +25383,7 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
25328
25383
  try {
25329
25384
  const session = await this._apiService.createErrorSession({ span });
25330
25385
  if (session === null || session === void 0 ? void 0 : session._id) {
25331
- this._flushBuffer(session._id);
25386
+ this._flushBuffer(session);
25332
25387
  }
25333
25388
  if (session === null || session === void 0 ? void 0 : session.url) {
25334
25389
  _eventBus__WEBPACK_IMPORTED_MODULE_8__.recorderEventBus.emit(_config__WEBPACK_IMPORTED_MODULE_6__.SESSION_AUTO_CREATED, session.url);
@@ -25348,18 +25403,25 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
25348
25403
  sessionAttributes: this.sessionAttributes,
25349
25404
  resourceAttributes: (0,_utils__WEBPACK_IMPORTED_MODULE_4__.getNavigatorInfo)(),
25350
25405
  name: this._getSessionName(),
25351
- ...(this._userAttributes ? { userAttributes: this._userAttributes } : {}),
25406
+ ...(this._userAttributes
25407
+ ? { userAttributes: this._userAttributes }
25408
+ : {}),
25352
25409
  };
25353
- const request = !this.continuousRecording ? payload : { debugSessionData: payload };
25410
+ const request = !this.continuousRecording
25411
+ ? payload
25412
+ : { debugSessionData: payload };
25354
25413
  const session = this.continuousRecording
25355
25414
  ? await this._apiService.startContinuousDebugSession(request, signal)
25356
25415
  : await this._apiService.startSession(request, signal);
25357
25416
  if (session) {
25358
- session.sessionType = this.continuousRecording ? _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionType.CONTINUOUS : _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionType.MANUAL;
25417
+ session.sessionType = this.continuousRecording
25418
+ ? _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionType.CONTINUOUS
25419
+ : _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionType.MANUAL;
25359
25420
  this._setupSessionAndStart(session, false);
25360
25421
  }
25361
25422
  }
25362
25423
  catch (error) {
25424
+ this._startInProgress = false;
25363
25425
  this.error = error.message;
25364
25426
  this.sessionState = _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.stopped;
25365
25427
  if (this.continuousRecording) {
@@ -25372,13 +25434,17 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
25372
25434
  */
25373
25435
  _start() {
25374
25436
  var _a, _b;
25437
+ this._startInProgress = false;
25375
25438
  this.sessionState = _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.started;
25376
25439
  // eslint-disable-next-line no-self-assign
25377
25440
  this.sessionType = this.sessionType;
25378
25441
  this._tracer.start(this.sessionId, this.sessionType);
25379
25442
  // Ensure we switch from buffer-only recording to session recording cleanly.
25380
25443
  void this._recorder.restart(this.sessionId, this.sessionType);
25381
- this._navigationRecorder.start({ sessionId: this.sessionId, sessionType: this.sessionType });
25444
+ this._navigationRecorder.start({
25445
+ sessionId: this.sessionId,
25446
+ sessionType: this.sessionType,
25447
+ });
25382
25448
  if (this.session) {
25383
25449
  this._socketService.subscribeToSession(this.session);
25384
25450
  this._sessionWidget.seconds = (0,_utils__WEBPACK_IMPORTED_MODULE_4__.getTimeDifferenceInSeconds)((_a = this.session) === null || _a === void 0 ? void 0 : _a.startedAt);
@@ -25406,8 +25472,12 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
25406
25472
  // rrweb assigns new node IDs on each record() call, so the next buffer
25407
25473
  // segment must not carry events from the previous generation. Await the
25408
25474
  // clear so its IDB tx can't race past the fresh FullSnapshot.
25409
- const cleared = this._crashBuffer ? this._crashBuffer.clear() : Promise.resolve();
25410
- void cleared.catch(() => undefined).then(() => this._startBufferOnlyRecording());
25475
+ const cleared = this._crashBuffer
25476
+ ? this._crashBuffer.clear()
25477
+ : Promise.resolve();
25478
+ void cleared
25479
+ .catch(() => undefined)
25480
+ .then(() => this._startBufferOnlyRecording());
25411
25481
  }
25412
25482
  /**
25413
25483
  * Pause the session tracing and recording
@@ -25443,7 +25513,10 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
25443
25513
  * @param sessionId - the session ID to set or clear
25444
25514
  */
25445
25515
  _setSession(session) {
25446
- this.session = { ...session, startedAt: session.startedAt || new Date().toISOString() };
25516
+ this.session = {
25517
+ ...session,
25518
+ startedAt: session.startedAt || new Date().toISOString(),
25519
+ };
25447
25520
  this.sessionId = (session === null || session === void 0 ? void 0 : session.shortId) || (session === null || session === void 0 ? void 0 : session._id);
25448
25521
  }
25449
25522
  _clearSession() {
@@ -25466,7 +25539,8 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
25466
25539
  }
25467
25540
  break;
25468
25541
  case 'stop':
25469
- if (this.sessionState !== _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.paused && this.sessionState !== _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.started) {
25542
+ if (this.sessionState !== _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.paused &&
25543
+ this.sessionState !== _types__WEBPACK_IMPORTED_MODULE_5__.SessionState.started) {
25470
25544
  throw new Error('Cannot stop. Session is not currently started.');
25471
25545
  }
25472
25546
  break;
@@ -25544,7 +25618,10 @@ class SessionRecorder extends _observable__WEBPACK_IMPORTED_MODULE_0__.Observabl
25544
25618
  */
25545
25619
  _getSessionName(date = new Date()) {
25546
25620
  var _a, _b, _c;
25547
- const userName = ((_a = this.sessionAttributes) === null || _a === void 0 ? void 0 : _a.userName) || ((_b = this._userAttributes) === null || _b === void 0 ? void 0 : _b.userName) || ((_c = this._userAttributes) === null || _c === void 0 ? void 0 : _c.name) || '';
25621
+ const userName = ((_a = this.sessionAttributes) === null || _a === void 0 ? void 0 : _a.userName) ||
25622
+ ((_b = this._userAttributes) === null || _b === void 0 ? void 0 : _b.userName) ||
25623
+ ((_c = this._userAttributes) === null || _c === void 0 ? void 0 : _c.name) ||
25624
+ '';
25548
25625
  return userName
25549
25626
  ? `${userName}'s session on ${(0,_utils__WEBPACK_IMPORTED_MODULE_4__.getFormattedDate)(date, { month: 'short', day: 'numeric' })}`
25550
25627
  : `Session on ${(0,_utils__WEBPACK_IMPORTED_MODULE_4__.getFormattedDate)(date)}`;
package/dist/listeners.js CHANGED
@@ -1,10 +1,25 @@
1
1
  import messagingService from './services/messaging.service';
2
- import { SessionType } from '@multiplayer-app/session-recorder-common';
2
+ import { PACKAGE_VERSION_EXPORT } from './config/constants';
3
+ import { SessionType, } from '@multiplayer-app/session-recorder-common';
3
4
  export function setupListeners(sessionRecorder) {
4
- // Send ready message with session info
5
- messagingService.sendMessage('ready', {
6
- session: sessionRecorder.session,
7
- sessionState: sessionRecorder.sessionState,
5
+ const announceState = () => {
6
+ // Send continuous flag first so it is applied before the consumer reacts to
7
+ // the 'ready' message (postMessage preserves delivery order).
8
+ messagingService.sendMessage('continuous-debugging', sessionRecorder.continuousRecording);
9
+ messagingService.sendMessage('ready', {
10
+ session: sessionRecorder.session,
11
+ sessionState: sessionRecorder.sessionState,
12
+ // Lets the extension popup detect libs too old to support on-demand state
13
+ // sync (the `get-state` request below).
14
+ version: PACKAGE_VERSION_EXPORT,
15
+ });
16
+ };
17
+ // Announce current state on load...
18
+ announceState();
19
+ // ...and whenever the extension asks (e.g. the popup was opened after the
20
+ // recorder had already initialized, so it missed the initial announcement).
21
+ messagingService.on('get-state', () => {
22
+ announceState();
8
23
  });
9
24
  messagingService.on('init', (payload) => {
10
25
  sessionRecorder.init(payload);
@@ -15,6 +30,9 @@ export function setupListeners(sessionRecorder) {
15
30
  messagingService.on('end', (payload) => {
16
31
  sessionRecorder.stop(payload);
17
32
  });
33
+ messagingService.on('stop', (payload) => {
34
+ sessionRecorder.stop(payload);
35
+ });
18
36
  messagingService.on('pause', () => {
19
37
  sessionRecorder.pause();
20
38
  });
@@ -27,6 +27,7 @@ export declare class SessionRecorder extends Observable<SessionRecorderEvents> i
27
27
  get sessionType(): SessionType;
28
28
  set sessionType(sessionType: SessionType);
29
29
  get continuousRecording(): boolean;
30
+ private _startInProgress;
30
31
  private _sessionState;
31
32
  get sessionState(): SessionState | null;
32
33
  set sessionState(state: SessionState | null);
@@ -1,15 +1,15 @@
1
1
  import { Observable } from './observable';
2
- import { SessionType } from '@multiplayer-app/session-recorder-common';
2
+ import { SessionType, } from '@multiplayer-app/session-recorder-common';
3
3
  import { TracerBrowserSDK } from './otel';
4
4
  import { RecorderBrowserSDK } from './rrweb';
5
5
  import { getStoredItem, setStoredItem, getNavigatorInfo, getFormattedDate, getTimeDifferenceInSeconds, isSessionActive, getOrCreateTabId, } from './utils';
6
- import { SessionState } from './types';
7
- import { BASE_CONFIG, SESSION_PROP_NAME, SESSION_ID_PROP_NAME, SESSION_TYPE_PROP_NAME, SESSION_STATE_PROP_NAME, DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE, getSessionRecorderConfig, SESSION_AUTO_CREATED, SESSION_STOPPED_EVENT, REMOTE_SESSION_RECORDING_START, REMOTE_SESSION_RECORDING_STOP, SESSION_SAVE_BUFFER_EVENT, SESSION_READY_EVENT, } from './config';
8
- import { setShouldRecordHttpData, setMaxCapturingHttpPayloadSize } from './patch';
6
+ import { SessionState, } from './types';
7
+ import { BASE_CONFIG, SESSION_PROP_NAME, SESSION_ID_PROP_NAME, SESSION_TYPE_PROP_NAME, SESSION_STATE_PROP_NAME, DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE, getSessionRecorderConfig, SESSION_AUTO_CREATED, SESSION_STOPPED_EVENT, REMOTE_SESSION_RECORDING_START, REMOTE_SESSION_RECORDING_STOP, SESSION_SAVE_BUFFER_EVENT, SESSION_READY_EVENT_LEGACY, SESSION_READY_EVENT, } from './config';
8
+ import { setShouldRecordHttpData, setMaxCapturingHttpPayloadSize, } from './patch';
9
9
  import { recorderEventBus } from './eventBus';
10
10
  import { SessionWidget } from './sessionWidget';
11
11
  import messagingService from './services/messaging.service';
12
- import { ApiService } from './services/api.service';
12
+ import { ApiService, } from './services/api.service';
13
13
  import { SocketService } from './services/socket.service';
14
14
  import { IndexedDBService } from './services/indexedDb.service';
15
15
  import { CrashBufferService } from './services/crashBuffer.service';
@@ -51,7 +51,10 @@ export class SessionRecorder extends Observable {
51
51
  messagingService.sendMessage('state-change', this._sessionState);
52
52
  setStoredItem(SESSION_STATE_PROP_NAME, state);
53
53
  // Emit observable event to support React wrapper
54
- this.emit('state-change', [this._sessionState || SessionState.stopped, this.sessionType]);
54
+ this.emit('state-change', [
55
+ this._sessionState || SessionState.stopped,
56
+ this.sessionType,
57
+ ]);
55
58
  }
56
59
  get session() {
57
60
  return this._session;
@@ -112,6 +115,10 @@ export class SessionRecorder extends Observable {
112
115
  // Session ID and state are stored in sessionStorage (with fallback) to avoid multi-tab conflicts
113
116
  this._sessionId = null;
114
117
  this._sessionType = SessionType.MANUAL;
118
+ // Guards against overlapping start() calls. `_checkOperation('start')` only
119
+ // rejects once sessionState is `started`, but `_createSessionAndStart` is
120
+ // async, so two rapid starts could both pass and create duplicate sessions.
121
+ this._startInProgress = false;
115
122
  this._sessionState = null;
116
123
  this._session = null;
117
124
  this._sessionAttributes = null;
@@ -122,10 +129,18 @@ export class SessionRecorder extends Observable {
122
129
  this._error = '';
123
130
  // Safety: avoid accessing storage in SSR/non-browser environments
124
131
  const isBrowser = typeof window !== 'undefined';
125
- const sessionLocal = isBrowser ? getStoredItem(SESSION_PROP_NAME, true) : null;
126
- const sessionIdLocal = isBrowser ? getStoredItem(SESSION_ID_PROP_NAME) : null;
127
- const sessionStateLocal = isBrowser ? getStoredItem(SESSION_STATE_PROP_NAME) : null;
128
- const sessionTypeLocal = isBrowser ? getStoredItem(SESSION_TYPE_PROP_NAME) : null;
132
+ const sessionLocal = isBrowser
133
+ ? getStoredItem(SESSION_PROP_NAME, true)
134
+ : null;
135
+ const sessionIdLocal = isBrowser
136
+ ? getStoredItem(SESSION_ID_PROP_NAME)
137
+ : null;
138
+ const sessionStateLocal = isBrowser
139
+ ? getStoredItem(SESSION_STATE_PROP_NAME)
140
+ : null;
141
+ const sessionTypeLocal = isBrowser
142
+ ? getStoredItem(SESSION_TYPE_PROP_NAME)
143
+ : null;
129
144
  if (isSessionActive(sessionLocal, sessionTypeLocal)) {
130
145
  this.session = sessionLocal;
131
146
  this.sessionId = sessionIdLocal;
@@ -157,7 +172,8 @@ export class SessionRecorder extends Observable {
157
172
  // GC: remove orphaned crash buffers from old tabs.
158
173
  // Keep TTL large to avoid any accidental data loss.
159
174
  void this._bufferDb.sweepStaleTabs(10 * 60 * 60 * 1000);
160
- setMaxCapturingHttpPayloadSize(this._configs.maxCapturingHttpPayloadSize || DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE);
175
+ setMaxCapturingHttpPayloadSize(this._configs.maxCapturingHttpPayloadSize ||
176
+ DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE);
161
177
  setShouldRecordHttpData(this._configs.captureBody, this._configs.captureHeaders);
162
178
  this._setupCrashBuffer();
163
179
  this._tracer.init(this._configs);
@@ -179,7 +195,9 @@ export class SessionRecorder extends Observable {
179
195
  if (this._configs.apiKey) {
180
196
  this._recorder.init(this._configs, this._socketService);
181
197
  }
182
- if (this.sessionId && (this.sessionState === SessionState.started || this.sessionState === SessionState.paused)) {
198
+ if (this.sessionId &&
199
+ (this.sessionState === SessionState.started ||
200
+ this.sessionState === SessionState.paused)) {
183
201
  this._start();
184
202
  }
185
203
  else {
@@ -233,7 +251,8 @@ export class SessionRecorder extends Observable {
233
251
  return;
234
252
  let hasFocus = true;
235
253
  try {
236
- hasFocus = typeof document.hasFocus === 'function' ? document.hasFocus() : true;
254
+ hasFocus =
255
+ typeof document.hasFocus === 'function' ? document.hasFocus() : true;
237
256
  }
238
257
  catch (_e) {
239
258
  hasFocus = true;
@@ -255,7 +274,9 @@ export class SessionRecorder extends Observable {
255
274
  // stop, leaving the buffer with no FullSnapshot and silently breaking
256
275
  // exception-triggered flushBuffer. `_recorder.restart(null, ...)` passes
257
276
  // null explicitly, so it's safe regardless of `this.sessionId`.
258
- 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 !== SessionState.stopped) {
277
+ if (!this._crashBuffer ||
278
+ !((_b = (_a = this._configs) === null || _a === void 0 ? void 0 : _a.buffering) === null || _b === void 0 ? void 0 : _b.enabled) ||
279
+ this.sessionState !== SessionState.stopped) {
259
280
  return;
260
281
  }
261
282
  void this._recorder.restart(null, SessionType.MANUAL);
@@ -304,9 +325,13 @@ export class SessionRecorder extends Observable {
304
325
  * @param session - the session to start
305
326
  */
306
327
  start(type = SessionType.MANUAL, session) {
328
+ if (this._startInProgress)
329
+ return;
307
330
  this._checkOperation('start');
331
+ this._startInProgress = true;
308
332
  // If continuous recording is disabled, force plain mode
309
- if (type === SessionType.CONTINUOUS && !this._configs.showContinuousRecording) {
333
+ if (type === SessionType.CONTINUOUS &&
334
+ !this._configs.showContinuousRecording) {
310
335
  type = SessionType.MANUAL;
311
336
  }
312
337
  this.sessionType = type;
@@ -337,7 +362,8 @@ export class SessionRecorder extends Observable {
337
362
  stoppedAt: this._recorder.stoppedAt,
338
363
  };
339
364
  const response = await this._apiService.stopSession(sid, request);
340
- recorderEventBus.emit(SESSION_READY_EVENT, response._id);
365
+ recorderEventBus.emit(SESSION_READY_EVENT_LEGACY, response._id);
366
+ recorderEventBus.emit(SESSION_READY_EVENT, response);
341
367
  }
342
368
  }
343
369
  catch (error) {
@@ -433,8 +459,8 @@ export class SessionRecorder extends Observable {
433
459
  this.error = (e === null || e === void 0 ? void 0 : e.message) || 'Failed to capture exception';
434
460
  }
435
461
  }
436
- async _flushBuffer(sessionId, force = false) {
437
- if (!sessionId || !this._crashBuffer || this._isFlushingBuffer) {
462
+ async _flushBuffer(session, force = false) {
463
+ if (!session || !this._crashBuffer || this._isFlushingBuffer) {
438
464
  return null;
439
465
  }
440
466
  this._isFlushingBuffer = true;
@@ -445,8 +471,10 @@ export class SessionRecorder extends Observable {
445
471
  }
446
472
  await Promise.all([
447
473
  this._tracer.exportTraces(spans.map((s) => s.span)),
448
- this._apiService.exportEvents(sessionId, { events: events.map((e) => e.event) }),
449
- this._apiService.updateSessionAttributes(sessionId, {
474
+ this._apiService.exportEvents(session._id, {
475
+ events: events.map((e) => e.event),
476
+ }),
477
+ this._apiService.updateSessionAttributes(session._id, {
450
478
  startedAt: this._toCrashBufferSessionIso(startedAt),
451
479
  stoppedAt: this._toCrashBufferSessionIso(stoppedAt),
452
480
  sessionAttributes: this.sessionAttributes,
@@ -461,7 +489,8 @@ export class SessionRecorder extends Observable {
461
489
  finally {
462
490
  await this._crashBuffer.clear();
463
491
  this._isFlushingBuffer = false;
464
- recorderEventBus.emit(SESSION_READY_EVENT, sessionId);
492
+ recorderEventBus.emit(SESSION_READY_EVENT_LEGACY, session._id);
493
+ recorderEventBus.emit(SESSION_READY_EVENT, session);
465
494
  }
466
495
  }
467
496
  _toCrashBufferSessionIso(ts) {
@@ -550,7 +579,8 @@ export class SessionRecorder extends Observable {
550
579
  * Handle the safe stop event
551
580
  */
552
581
  _handleStop(comment) {
553
- if (this.sessionState === SessionState.started || this.sessionState === SessionState.paused) {
582
+ if (this.sessionState === SessionState.started ||
583
+ this.sessionState === SessionState.paused) {
554
584
  this.stop(comment);
555
585
  }
556
586
  }
@@ -574,7 +604,8 @@ export class SessionRecorder extends Observable {
574
604
  * Handle the safe cancel event
575
605
  */
576
606
  _handleCancel() {
577
- if (this.sessionState === SessionState.started || this.sessionState === SessionState.paused) {
607
+ if (this.sessionState === SessionState.started ||
608
+ this.sessionState === SessionState.paused) {
578
609
  this.cancel();
579
610
  }
580
611
  }
@@ -582,7 +613,8 @@ export class SessionRecorder extends Observable {
582
613
  * Handle the safe save event
583
614
  */
584
615
  _handleSave() {
585
- if (this.sessionState === SessionState.started && this.continuousRecording) {
616
+ if (this.sessionState === SessionState.started &&
617
+ this.continuousRecording) {
586
618
  this.save();
587
619
  }
588
620
  }
@@ -627,12 +659,12 @@ export class SessionRecorder extends Observable {
627
659
  }
628
660
  });
629
661
  this._socketService.on(SESSION_SAVE_BUFFER_EVENT, (payload) => {
630
- var _a, _b, _c;
662
+ var _a, _b;
631
663
  if (this.sessionState !== SessionState.stopped)
632
664
  return;
633
- this._flushBuffer((_a = payload === null || payload === void 0 ? void 0 : payload.debugSession) === null || _a === void 0 ? void 0 : _a._id, true);
634
- if ((_b = payload === null || payload === void 0 ? void 0 : payload.debugSession) === null || _b === void 0 ? void 0 : _b.url) {
635
- recorderEventBus.emit(SESSION_AUTO_CREATED, (_c = payload === null || payload === void 0 ? void 0 : payload.debugSession) === null || _c === void 0 ? void 0 : _c.url);
665
+ this._flushBuffer(payload === null || payload === void 0 ? void 0 : payload.debugSession, true);
666
+ if ((_a = payload === null || payload === void 0 ? void 0 : payload.debugSession) === null || _a === void 0 ? void 0 : _a.url) {
667
+ recorderEventBus.emit(SESSION_AUTO_CREATED, (_b = payload === null || payload === void 0 ? void 0 : payload.debugSession) === null || _b === void 0 ? void 0 : _b.url);
636
668
  }
637
669
  });
638
670
  }
@@ -640,7 +672,7 @@ export class SessionRecorder extends Observable {
640
672
  try {
641
673
  const session = await this._apiService.createErrorSession({ span });
642
674
  if (session === null || session === void 0 ? void 0 : session._id) {
643
- this._flushBuffer(session._id);
675
+ this._flushBuffer(session);
644
676
  }
645
677
  if (session === null || session === void 0 ? void 0 : session.url) {
646
678
  recorderEventBus.emit(SESSION_AUTO_CREATED, session.url);
@@ -660,18 +692,25 @@ export class SessionRecorder extends Observable {
660
692
  sessionAttributes: this.sessionAttributes,
661
693
  resourceAttributes: getNavigatorInfo(),
662
694
  name: this._getSessionName(),
663
- ...(this._userAttributes ? { userAttributes: this._userAttributes } : {}),
695
+ ...(this._userAttributes
696
+ ? { userAttributes: this._userAttributes }
697
+ : {}),
664
698
  };
665
- const request = !this.continuousRecording ? payload : { debugSessionData: payload };
699
+ const request = !this.continuousRecording
700
+ ? payload
701
+ : { debugSessionData: payload };
666
702
  const session = this.continuousRecording
667
703
  ? await this._apiService.startContinuousDebugSession(request, signal)
668
704
  : await this._apiService.startSession(request, signal);
669
705
  if (session) {
670
- session.sessionType = this.continuousRecording ? SessionType.CONTINUOUS : SessionType.MANUAL;
706
+ session.sessionType = this.continuousRecording
707
+ ? SessionType.CONTINUOUS
708
+ : SessionType.MANUAL;
671
709
  this._setupSessionAndStart(session, false);
672
710
  }
673
711
  }
674
712
  catch (error) {
713
+ this._startInProgress = false;
675
714
  this.error = error.message;
676
715
  this.sessionState = SessionState.stopped;
677
716
  if (this.continuousRecording) {
@@ -684,13 +723,17 @@ export class SessionRecorder extends Observable {
684
723
  */
685
724
  _start() {
686
725
  var _a, _b;
726
+ this._startInProgress = false;
687
727
  this.sessionState = SessionState.started;
688
728
  // eslint-disable-next-line no-self-assign
689
729
  this.sessionType = this.sessionType;
690
730
  this._tracer.start(this.sessionId, this.sessionType);
691
731
  // Ensure we switch from buffer-only recording to session recording cleanly.
692
732
  void this._recorder.restart(this.sessionId, this.sessionType);
693
- this._navigationRecorder.start({ sessionId: this.sessionId, sessionType: this.sessionType });
733
+ this._navigationRecorder.start({
734
+ sessionId: this.sessionId,
735
+ sessionType: this.sessionType,
736
+ });
694
737
  if (this.session) {
695
738
  this._socketService.subscribeToSession(this.session);
696
739
  this._sessionWidget.seconds = getTimeDifferenceInSeconds((_a = this.session) === null || _a === void 0 ? void 0 : _a.startedAt);
@@ -718,8 +761,12 @@ export class SessionRecorder extends Observable {
718
761
  // rrweb assigns new node IDs on each record() call, so the next buffer
719
762
  // segment must not carry events from the previous generation. Await the
720
763
  // clear so its IDB tx can't race past the fresh FullSnapshot.
721
- const cleared = this._crashBuffer ? this._crashBuffer.clear() : Promise.resolve();
722
- void cleared.catch(() => undefined).then(() => this._startBufferOnlyRecording());
764
+ const cleared = this._crashBuffer
765
+ ? this._crashBuffer.clear()
766
+ : Promise.resolve();
767
+ void cleared
768
+ .catch(() => undefined)
769
+ .then(() => this._startBufferOnlyRecording());
723
770
  }
724
771
  /**
725
772
  * Pause the session tracing and recording
@@ -755,7 +802,10 @@ export class SessionRecorder extends Observable {
755
802
  * @param sessionId - the session ID to set or clear
756
803
  */
757
804
  _setSession(session) {
758
- this.session = { ...session, startedAt: session.startedAt || new Date().toISOString() };
805
+ this.session = {
806
+ ...session,
807
+ startedAt: session.startedAt || new Date().toISOString(),
808
+ };
759
809
  this.sessionId = (session === null || session === void 0 ? void 0 : session.shortId) || (session === null || session === void 0 ? void 0 : session._id);
760
810
  }
761
811
  _clearSession() {
@@ -778,7 +828,8 @@ export class SessionRecorder extends Observable {
778
828
  }
779
829
  break;
780
830
  case 'stop':
781
- if (this.sessionState !== SessionState.paused && this.sessionState !== SessionState.started) {
831
+ if (this.sessionState !== SessionState.paused &&
832
+ this.sessionState !== SessionState.started) {
782
833
  throw new Error('Cannot stop. Session is not currently started.');
783
834
  }
784
835
  break;
@@ -856,7 +907,10 @@ export class SessionRecorder extends Observable {
856
907
  */
857
908
  _getSessionName(date = new Date()) {
858
909
  var _a, _b, _c;
859
- const userName = ((_a = this.sessionAttributes) === null || _a === void 0 ? void 0 : _a.userName) || ((_b = this._userAttributes) === null || _b === void 0 ? void 0 : _b.userName) || ((_c = this._userAttributes) === null || _c === void 0 ? void 0 : _c.name) || '';
910
+ const userName = ((_a = this.sessionAttributes) === null || _a === void 0 ? void 0 : _a.userName) ||
911
+ ((_b = this._userAttributes) === null || _b === void 0 ? void 0 : _b.userName) ||
912
+ ((_c = this._userAttributes) === null || _c === void 0 ? void 0 : _c.name) ||
913
+ '';
860
914
  return userName
861
915
  ? `${userName}'s session on ${getFormattedDate(date, { month: 'short', day: 'numeric' })}`
862
916
  : `Session on ${getFormattedDate(date)}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiplayer-app/session-recorder-browser",
3
- "version": "2.0.87",
3
+ "version": "2.0.89",
4
4
  "description": "Multiplayer Fullstack Session Recorder for Browser",
5
5
  "author": {
6
6
  "name": "Multiplayer Software, Inc.",
@@ -75,7 +75,7 @@
75
75
  "webpack-cli": "5.1.4"
76
76
  },
77
77
  "dependencies": {
78
- "@multiplayer-app/session-recorder-common": "2.0.87",
78
+ "@multiplayer-app/session-recorder-common": "2.0.89",
79
79
  "@opentelemetry/core": "2.0.1",
80
80
  "@opentelemetry/exporter-trace-otlp-http": "0.203.0",
81
81
  "@opentelemetry/instrumentation": "0.203.0",