@multiplayer-app/session-recorder-browser 1.2.28 → 1.2.30

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/README.md CHANGED
@@ -339,66 +339,6 @@ activeSpan.setAttribute(SessionRecorder.ATTR_MULTIPLAYER_CONTINUOUS_SESSION_AUTO
339
339
 
340
340
  ## Session Recorder for Next.js
341
341
 
342
- To integrate the MySessionRecorder component into your Next.js application, follow these steps:
343
-
344
- - Create a new file (e.g., MySessionRecorder.js or MySessionRecorder.tsx) in your root directory or a components directory.
345
-
346
- - Import the component
347
-
348
- In the newly created file, add the following code:
349
-
350
- ```javascript
351
- 'use client' // Mark as Client Component
352
- import { useEffect } from 'react'
353
- import SessionRecorder from '@multiplayer-app/session-recorder-browser'
354
-
355
- export default function MySessionRecorder() {
356
- useEffect(() => {
357
- if (typeof window !== 'undefined') {
358
- SessionRecorder.init({
359
- version: '{YOUR_APPLICATION_VERSION}',
360
- application: '{YOUR_APPLICATION_NAME}',
361
- environment: '{YOUR_APPLICATION_ENVIRONMENT}',
362
- apiKey: '{YOUR_API_KEY}',
363
- recordCanvas: true, // Enable canvas recording
364
- masking: {
365
- maskAllInputs: true,
366
- maskInputOptions: {
367
- password: true,
368
- email: false,
369
- tel: false
370
- }
371
- }
372
- })
373
-
374
- SessionRecorder.setSessionAttributes({
375
- userId: '{userId}',
376
- userName: '{userName}'
377
- })
378
- }
379
- }, [])
380
-
381
- return null // No UI output needed
382
- }
383
- ```
384
-
385
- Replace the placeholders with the actual information.
386
-
387
- Now, you can use the MySessionRecorder component in your application by adding it to your desired page or layout file:
388
-
389
- ```javascript
390
- import MySessionRecorder from './MySessionRecorder' // Adjust the path as necessary
391
-
392
- export default function MyApp() {
393
- return (
394
- <>
395
- <MySessionRecorder />
396
- {/* Other components */}
397
- </>
398
- )
399
- }
400
- ```
401
-
402
342
  ### Next.js 15.3+ (App Router) — instrumentation-client.ts
403
343
 
404
344
  On Next.js 15.3+ you can initialize the Session Recorder as early as possible with `src/instrumentation-client.ts|js`, which runs before hydration. You can also export `onRouterTransitionStart` to observe navigation. See the official docs: [instrumentation-client.ts](https://nextjs.org/docs/app/api-reference/file-conventions/instrumentation-client).
@@ -25094,7 +25094,7 @@ const DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE = 100000;
25094
25094
  const SESSION_RESPONSE = 'multiplayer-debug-session-response';
25095
25095
  const CONTINUOUS_DEBUGGING_TIMEOUT = 60000; // 1 minutes
25096
25096
  const DEBUG_SESSION_MAX_DURATION_SECONDS = 10 * 60 + 30; // TODO: move to shared config otel core
25097
- const PACKAGE_VERSION_EXPORT = "1.2.28" || 0;
25097
+ const PACKAGE_VERSION_EXPORT = "1.2.30" || 0;
25098
25098
  // Regex patterns for OpenTelemetry ignore URLs
25099
25099
  const OTEL_IGNORE_URLS = [
25100
25100
  // Traces endpoint
@@ -26369,91 +26369,93 @@ function _headersInitToObject(headersInit) {
26369
26369
  }
26370
26370
  return result;
26371
26371
  }
26372
- // Store original fetch
26373
- const originalFetch = window.fetch;
26374
- // Override fetch
26375
- window.fetch = async function (input,
26376
- // eslint-disable-next-line
26377
- init) {
26378
- const networkRequest = {};
26379
- // Capture request data
26380
- const inputIsRequest = typeof Request !== 'undefined' && input instanceof Request;
26381
- const safeToConstructRequest = !inputIsRequest || !input.bodyUsed;
26382
- // Only construct a new Request when it's safe (i.e., body not already used)
26383
- let requestForMetadata = null;
26384
- if (safeToConstructRequest) {
26385
- try {
26386
- requestForMetadata = new Request(input, init);
26387
- }
26388
- catch (_a) {
26389
- // If construction fails for any reason, fall back to using available data
26390
- requestForMetadata = null;
26391
- }
26392
- }
26393
- if (_configs__WEBPACK_IMPORTED_MODULE_2__.configs.recordRequestHeaders) {
26394
- if (requestForMetadata) {
26395
- networkRequest.requestHeaders = _headersToObject(requestForMetadata.headers);
26396
- }
26397
- else if (inputIsRequest) {
26398
- networkRequest.requestHeaders = _headersToObject(input.headers);
26399
- }
26400
- else {
26401
- networkRequest.requestHeaders = _headersInitToObject(init === null || init === void 0 ? void 0 : init.headers);
26402
- }
26403
- }
26404
- if (_configs__WEBPACK_IMPORTED_MODULE_2__.configs.shouldRecordBody) {
26405
- // Prefer reading from the safely constructed Request; else fallback to init.body
26406
- const urlStr = inputIsRequest
26407
- ? input.url
26408
- : (typeof input === 'string' || input instanceof URL ? String(input) : '');
26409
- const candidateBody = requestForMetadata
26410
- ? requestForMetadata.body
26411
- : (inputIsRequest ? init === null || init === void 0 ? void 0 : init.body : init === null || init === void 0 ? void 0 : init.body);
26412
- if (!(0,_utils_type_utils__WEBPACK_IMPORTED_MODULE_0__.isNullish)(candidateBody)) {
26413
- const requestBody = _tryReadFetchBody({
26414
- body: candidateBody,
26415
- url: urlStr,
26416
- });
26417
- if ((requestBody === null || requestBody === void 0 ? void 0 : requestBody.length) &&
26418
- new Blob([requestBody]).size <= _configs__WEBPACK_IMPORTED_MODULE_2__.configs.maxCapturingHttpPayloadSize) {
26419
- networkRequest.requestBody = requestBody;
26372
+ if (typeof window !== 'undefined' && typeof window.fetch !== 'undefined') {
26373
+ // Store original fetch
26374
+ const originalFetch = window.fetch;
26375
+ // Override fetch
26376
+ window.fetch = async function (input,
26377
+ // eslint-disable-next-line
26378
+ init) {
26379
+ const networkRequest = {};
26380
+ // Capture request data
26381
+ const inputIsRequest = typeof Request !== 'undefined' && input instanceof Request;
26382
+ const safeToConstructRequest = !inputIsRequest || !input.bodyUsed;
26383
+ // Only construct a new Request when it's safe (i.e., body not already used)
26384
+ let requestForMetadata = null;
26385
+ if (safeToConstructRequest) {
26386
+ try {
26387
+ requestForMetadata = new Request(input, init);
26388
+ }
26389
+ catch (_a) {
26390
+ // If construction fails for any reason, fall back to using available data
26391
+ requestForMetadata = null;
26420
26392
  }
26421
26393
  }
26422
- }
26423
- try {
26424
- // Make the actual fetch request
26425
- const response = await originalFetch(input, init);
26426
- // Capture response data
26427
- if (_configs__WEBPACK_IMPORTED_MODULE_2__.configs.recordResponseHeaders) {
26428
- networkRequest.responseHeaders = _headersToObject(response.headers);
26394
+ if (_configs__WEBPACK_IMPORTED_MODULE_2__.configs.recordRequestHeaders) {
26395
+ if (requestForMetadata) {
26396
+ networkRequest.requestHeaders = _headersToObject(requestForMetadata.headers);
26397
+ }
26398
+ else if (inputIsRequest) {
26399
+ networkRequest.requestHeaders = _headersToObject(input.headers);
26400
+ }
26401
+ else {
26402
+ networkRequest.requestHeaders = _headersInitToObject(init === null || init === void 0 ? void 0 : init.headers);
26403
+ }
26429
26404
  }
26430
26405
  if (_configs__WEBPACK_IMPORTED_MODULE_2__.configs.shouldRecordBody) {
26431
- const responseBody = await _tryReadResponseBody(response);
26432
- if ((responseBody === null || responseBody === void 0 ? void 0 : responseBody.length) &&
26433
- new Blob([responseBody]).size <= _configs__WEBPACK_IMPORTED_MODULE_2__.configs.maxCapturingHttpPayloadSize) {
26434
- networkRequest.responseBody = responseBody;
26406
+ // Prefer reading from the safely constructed Request; else fallback to init.body
26407
+ const urlStr = inputIsRequest
26408
+ ? input.url
26409
+ : (typeof input === 'string' || input instanceof URL ? String(input) : '');
26410
+ const candidateBody = requestForMetadata
26411
+ ? requestForMetadata.body
26412
+ : (inputIsRequest ? init === null || init === void 0 ? void 0 : init.body : init === null || init === void 0 ? void 0 : init.body);
26413
+ if (!(0,_utils_type_utils__WEBPACK_IMPORTED_MODULE_0__.isNullish)(candidateBody)) {
26414
+ const requestBody = _tryReadFetchBody({
26415
+ body: candidateBody,
26416
+ url: urlStr,
26417
+ });
26418
+ if ((requestBody === null || requestBody === void 0 ? void 0 : requestBody.length) &&
26419
+ new Blob([requestBody]).size <= _configs__WEBPACK_IMPORTED_MODULE_2__.configs.maxCapturingHttpPayloadSize) {
26420
+ networkRequest.requestBody = requestBody;
26421
+ }
26435
26422
  }
26436
26423
  }
26437
- // Attach network request data to the response for later access
26438
- // @ts-ignore
26439
- response.networkRequest = networkRequest;
26440
- return response;
26441
- }
26442
- catch (error) {
26443
- // Even if the fetch fails, we can still capture the request data
26444
- // Attach captured request data to the thrown error for downstream handling
26445
- // @ts-ignore
26446
- if (error && typeof error === 'object') {
26424
+ try {
26425
+ // Make the actual fetch request
26426
+ const response = await originalFetch(input, init);
26427
+ // Capture response data
26428
+ if (_configs__WEBPACK_IMPORTED_MODULE_2__.configs.recordResponseHeaders) {
26429
+ networkRequest.responseHeaders = _headersToObject(response.headers);
26430
+ }
26431
+ if (_configs__WEBPACK_IMPORTED_MODULE_2__.configs.shouldRecordBody) {
26432
+ const responseBody = await _tryReadResponseBody(response);
26433
+ if ((responseBody === null || responseBody === void 0 ? void 0 : responseBody.length) &&
26434
+ new Blob([responseBody]).size <= _configs__WEBPACK_IMPORTED_MODULE_2__.configs.maxCapturingHttpPayloadSize) {
26435
+ networkRequest.responseBody = responseBody;
26436
+ }
26437
+ }
26438
+ // Attach network request data to the response for later access
26447
26439
  // @ts-ignore
26448
- error.networkRequest = networkRequest;
26440
+ response.networkRequest = networkRequest;
26441
+ return response;
26449
26442
  }
26450
- throw error;
26451
- }
26452
- };
26453
- // Preserve the original fetch function's properties
26454
- Object.setPrototypeOf(window.fetch, originalFetch);
26455
- Object.defineProperty(window.fetch, 'name', { value: 'fetch' });
26456
- Object.defineProperty(window.fetch, 'length', { value: originalFetch.length });
26443
+ catch (error) {
26444
+ // Even if the fetch fails, we can still capture the request data
26445
+ // Attach captured request data to the thrown error for downstream handling
26446
+ // @ts-ignore
26447
+ if (error && typeof error === 'object') {
26448
+ // @ts-ignore
26449
+ error.networkRequest = networkRequest;
26450
+ }
26451
+ throw error;
26452
+ }
26453
+ };
26454
+ // Preserve the original fetch function's properties
26455
+ Object.setPrototypeOf(window.fetch, originalFetch);
26456
+ Object.defineProperty(window.fetch, 'name', { value: 'fetch' });
26457
+ Object.defineProperty(window.fetch, 'length', { value: originalFetch.length });
26458
+ }
26457
26459
 
26458
26460
 
26459
26461
  /***/ }),
@@ -26518,64 +26520,86 @@ function _tryReadXHRBody({ body, url, }) {
26518
26520
  }
26519
26521
  return `[XHR] Cannot read body of type ${toString.call(body)}`;
26520
26522
  }
26521
- (function (xhr) {
26522
- const originalOpen = XMLHttpRequest.prototype.open;
26523
- xhr.open = function (method, url, async = true, username, password) {
26524
- const xhr = this;
26525
- const networkRequest = {};
26523
+ function _isWithinPayloadLimit(payload) {
26524
+ try {
26525
+ if (typeof Blob !== 'undefined') {
26526
+ return new Blob([payload]).size <= _configs__WEBPACK_IMPORTED_MODULE_2__.configs.maxCapturingHttpPayloadSize;
26527
+ }
26528
+ }
26529
+ catch (_a) {
26530
+ // ignore and fallback to string length
26531
+ }
26532
+ return payload.length <= _configs__WEBPACK_IMPORTED_MODULE_2__.configs.maxCapturingHttpPayloadSize;
26533
+ }
26534
+ // Only patch XHR in environments where it exists (avoid SSR/Node)
26535
+ if (typeof XMLHttpRequest !== 'undefined') {
26536
+ (function (xhr) {
26537
+ // Idempotency guard: avoid double-patching
26526
26538
  // @ts-ignore
26527
- const requestHeaders = {};
26528
- const originalSetRequestHeader = xhr.setRequestHeader.bind(xhr);
26529
- xhr.setRequestHeader = (header, value) => {
26530
- requestHeaders[header] = value;
26531
- return originalSetRequestHeader(header, value);
26532
- };
26533
- if (_configs__WEBPACK_IMPORTED_MODULE_2__.configs.recordRequestHeaders) {
26534
- networkRequest.requestHeaders = requestHeaders;
26539
+ if (xhr.__mp_session_recorder_patched__) {
26540
+ return;
26535
26541
  }
26536
- const originalSend = xhr.send.bind(xhr);
26537
- xhr.send = (body) => {
26538
- if (_configs__WEBPACK_IMPORTED_MODULE_2__.configs.shouldRecordBody) {
26539
- const requestBody = _tryReadXHRBody({ body, url });
26540
- if ((requestBody === null || requestBody === void 0 ? void 0 : requestBody.length)
26541
- && new Blob([requestBody]).size <= _configs__WEBPACK_IMPORTED_MODULE_2__.configs.maxCapturingHttpPayloadSize) {
26542
- networkRequest.requestBody = requestBody;
26543
- }
26544
- }
26545
- return originalSend(body);
26546
- };
26547
- xhr.addEventListener('readystatechange', () => {
26548
- if (xhr.readyState !== xhr.DONE) {
26549
- return;
26550
- }
26542
+ // @ts-ignore
26543
+ ;
26544
+ xhr.__mp_session_recorder_patched__ = true;
26545
+ const originalOpen = xhr.open;
26546
+ xhr.open = function (method, url, async = true, username, password) {
26547
+ const xhr = this;
26548
+ const networkRequest = {};
26551
26549
  // @ts-ignore
26552
- const responseHeaders = {};
26553
- const rawHeaders = xhr.getAllResponseHeaders();
26554
- const headers = rawHeaders.trim().split(/[\r\n]+/);
26555
- headers.forEach((line) => {
26556
- const parts = line.split(': ');
26557
- const header = parts.shift();
26558
- const value = parts.join(': ');
26559
- if (header) {
26560
- responseHeaders[header] = value;
26550
+ const requestHeaders = {};
26551
+ const originalSetRequestHeader = xhr.setRequestHeader.bind(xhr);
26552
+ xhr.setRequestHeader = (header, value) => {
26553
+ requestHeaders[header] = value;
26554
+ return originalSetRequestHeader(header, value);
26555
+ };
26556
+ if (_configs__WEBPACK_IMPORTED_MODULE_2__.configs.recordRequestHeaders) {
26557
+ networkRequest.requestHeaders = requestHeaders;
26558
+ }
26559
+ const originalSend = xhr.send.bind(xhr);
26560
+ xhr.send = (body) => {
26561
+ if (_configs__WEBPACK_IMPORTED_MODULE_2__.configs.shouldRecordBody) {
26562
+ const requestBody = _tryReadXHRBody({ body, url });
26563
+ if ((requestBody === null || requestBody === void 0 ? void 0 : requestBody.length)
26564
+ && _isWithinPayloadLimit(requestBody)) {
26565
+ networkRequest.requestBody = requestBody;
26566
+ }
26561
26567
  }
26562
- });
26563
- if (_configs__WEBPACK_IMPORTED_MODULE_2__.configs.recordResponseHeaders) {
26564
- networkRequest.responseHeaders = responseHeaders;
26565
- }
26566
- if (_configs__WEBPACK_IMPORTED_MODULE_2__.configs.shouldRecordBody) {
26567
- const responseBody = _tryReadXHRBody({ body: xhr.response, url });
26568
- if ((responseBody === null || responseBody === void 0 ? void 0 : responseBody.length)
26569
- && new Blob([responseBody]).size <= _configs__WEBPACK_IMPORTED_MODULE_2__.configs.maxCapturingHttpPayloadSize) {
26570
- networkRequest.responseBody = responseBody;
26568
+ return originalSend(body);
26569
+ };
26570
+ xhr.addEventListener('readystatechange', () => {
26571
+ if (xhr.readyState !== xhr.DONE) {
26572
+ return;
26571
26573
  }
26572
- }
26573
- });
26574
- // @ts-ignore
26575
- xhr.networkRequest = networkRequest;
26576
- originalOpen.call(xhr, method, url, async, username, password);
26577
- };
26578
- })(XMLHttpRequest.prototype);
26574
+ // @ts-ignore
26575
+ const responseHeaders = {};
26576
+ const rawHeaders = xhr.getAllResponseHeaders();
26577
+ const headers = rawHeaders.trim().split(/[\r\n]+/);
26578
+ headers.forEach((line) => {
26579
+ const parts = line.split(': ');
26580
+ const header = parts.shift();
26581
+ const value = parts.join(': ');
26582
+ if (header) {
26583
+ responseHeaders[header] = value;
26584
+ }
26585
+ });
26586
+ if (_configs__WEBPACK_IMPORTED_MODULE_2__.configs.recordResponseHeaders) {
26587
+ networkRequest.responseHeaders = responseHeaders;
26588
+ }
26589
+ if (_configs__WEBPACK_IMPORTED_MODULE_2__.configs.shouldRecordBody) {
26590
+ const responseBody = _tryReadXHRBody({ body: xhr.response, url });
26591
+ if ((responseBody === null || responseBody === void 0 ? void 0 : responseBody.length)
26592
+ && _isWithinPayloadLimit(responseBody)) {
26593
+ networkRequest.responseBody = responseBody;
26594
+ }
26595
+ }
26596
+ });
26597
+ // @ts-ignore
26598
+ xhr.networkRequest = networkRequest;
26599
+ originalOpen.call(xhr, method, url, async, username, password);
26600
+ };
26601
+ })(XMLHttpRequest.prototype);
26602
+ }
26579
26603
 
26580
26604
 
26581
26605
  /***/ }),
@@ -27265,7 +27289,7 @@ class SessionRecorder extends lib0_observable__WEBPACK_IMPORTED_MODULE_14__.Obse
27265
27289
  *
27266
27290
  * This element is used to control the start/stop recording functionality in the session widget UI.
27267
27291
  *
27268
- * @returns {HTMLButtonElement} The recorder button element from the session widget.
27292
+ * @returns {HTMLButtonElement | null} The recorder button element from the session widget.
27269
27293
  */
27270
27294
  get sessionWidgetButtonElement() {
27271
27295
  return this._sessionWidget.recorderButton;
@@ -27293,10 +27317,12 @@ class SessionRecorder extends lib0_observable__WEBPACK_IMPORTED_MODULE_14__.Obse
27293
27317
  * Error message getter and setter
27294
27318
  */
27295
27319
  this._error = '';
27296
- const sessionLocal = (0,_utils__WEBPACK_IMPORTED_MODULE_2__.getStoredItem)(_config__WEBPACK_IMPORTED_MODULE_4__.SESSION_PROP_NAME, true);
27297
- const sessionIdLocal = (0,_utils__WEBPACK_IMPORTED_MODULE_2__.getStoredItem)(_config__WEBPACK_IMPORTED_MODULE_4__.SESSION_ID_PROP_NAME);
27298
- const sessionStateLocal = (0,_utils__WEBPACK_IMPORTED_MODULE_2__.getStoredItem)(_config__WEBPACK_IMPORTED_MODULE_4__.SESSION_STATE_PROP_NAME);
27299
- const sessionTypeLocal = (0,_utils__WEBPACK_IMPORTED_MODULE_2__.getStoredItem)(_config__WEBPACK_IMPORTED_MODULE_4__.SESSION_TYPE_PROP_NAME);
27320
+ // Safety: avoid accessing storage in SSR/non-browser environments
27321
+ const isBrowser = typeof window !== 'undefined';
27322
+ const sessionLocal = isBrowser ? (0,_utils__WEBPACK_IMPORTED_MODULE_2__.getStoredItem)(_config__WEBPACK_IMPORTED_MODULE_4__.SESSION_PROP_NAME, true) : null;
27323
+ const sessionIdLocal = isBrowser ? (0,_utils__WEBPACK_IMPORTED_MODULE_2__.getStoredItem)(_config__WEBPACK_IMPORTED_MODULE_4__.SESSION_ID_PROP_NAME) : null;
27324
+ const sessionStateLocal = isBrowser ? (0,_utils__WEBPACK_IMPORTED_MODULE_2__.getStoredItem)(_config__WEBPACK_IMPORTED_MODULE_4__.SESSION_STATE_PROP_NAME) : null;
27325
+ const sessionTypeLocal = isBrowser ? (0,_utils__WEBPACK_IMPORTED_MODULE_2__.getStoredItem)(_config__WEBPACK_IMPORTED_MODULE_4__.SESSION_TYPE_PROP_NAME) : null;
27300
27326
  if ((0,_utils__WEBPACK_IMPORTED_MODULE_2__.isSessionActive)(sessionLocal, sessionTypeLocal)) {
27301
27327
  this.session = sessionLocal;
27302
27328
  this.sessionId = sessionIdLocal;
@@ -27319,6 +27345,9 @@ class SessionRecorder extends lib0_observable__WEBPACK_IMPORTED_MODULE_14__.Obse
27319
27345
  * @param configs - custom configurations for session debugger
27320
27346
  */
27321
27347
  init(configs) {
27348
+ if (typeof window === 'undefined') {
27349
+ return;
27350
+ }
27322
27351
  this._configs = (0,_config__WEBPACK_IMPORTED_MODULE_4__.getSessionRecorderConfig)({ ...this._configs, ...configs });
27323
27352
  this._isInitialized = true;
27324
27353
  this._checkOperation('init');
@@ -28299,13 +28328,13 @@ class SessionWidget extends lib0_observable__WEBPACK_IMPORTED_MODULE_7__.Observa
28299
28328
  this._isStarted = false;
28300
28329
  this._isPaused = false;
28301
28330
  this._isInitialized = false;
28302
- this._recorderPlacement = '';
28303
28331
  this._error = '';
28304
- this._initialPopoverVisible = false;
28332
+ this._recorderPlacement = '';
28305
28333
  this._finalPopoverVisible = false;
28306
- this._buttonState = _buttonStateConfigs__WEBPACK_IMPORTED_MODULE_6__.ButtonState.IDLE;
28334
+ this._initialPopoverVisible = false;
28307
28335
  this._continuousRecording = false;
28308
28336
  this._showContinuousRecording = true;
28337
+ this._buttonState = _buttonStateConfigs__WEBPACK_IMPORTED_MODULE_6__.ButtonState.IDLE;
28309
28338
  this._widgetTextOverrides = _config__WEBPACK_IMPORTED_MODULE_4__.DEFAULT_WIDGET_TEXT_CONFIG;
28310
28339
  this.commentTextarea = null;
28311
28340
  this.dragManager = null;
@@ -28331,21 +28360,6 @@ class SessionWidget extends lib0_observable__WEBPACK_IMPORTED_MODULE_7__.Observa
28331
28360
  }
28332
28361
  }
28333
28362
  };
28334
- this.recorderButton = document.createElement('button');
28335
- this.initialPopover = document.createElement('div');
28336
- this.finalPopover = document.createElement('div');
28337
- this.overlay = document.createElement('div');
28338
- this.toast = document.createElement('div');
28339
- this.submitSessionDialog = document.createElement('div');
28340
- this.uiManager = new _UIManager__WEBPACK_IMPORTED_MODULE_5__.UIManager(this.recorderButton, this.initialPopover, this.finalPopover, this.overlay, this.submitSessionDialog, this.toast, _config__WEBPACK_IMPORTED_MODULE_4__.DEFAULT_WIDGET_TEXT_CONFIG, true);
28341
- this.uiManager.setRecorderButtonProps();
28342
- this.uiManager.setInitialPopoverProps();
28343
- this.uiManager.setFinalPopoverProps();
28344
- this.uiManager.setOverlayProps();
28345
- this.uiManager.setSubmitSessionDialogProps();
28346
- this.uiManager.setToastProps();
28347
- this.commentTextarea = this.finalPopover.querySelector('.mp-session-debugger-popover-textarea');
28348
- this.observeButtonDraggableMode();
28349
28363
  }
28350
28364
  updateState(state, continuousRecording) {
28351
28365
  this._continuousRecording = continuousRecording;
@@ -28416,9 +28430,32 @@ class SessionWidget extends lib0_observable__WEBPACK_IMPORTED_MODULE_7__.Observa
28416
28430
  }
28417
28431
  });
28418
28432
  }
28433
+ initUiManager() {
28434
+ this.recorderButton = document.createElement('button');
28435
+ this.initialPopover = document.createElement('div');
28436
+ this.finalPopover = document.createElement('div');
28437
+ this.overlay = document.createElement('div');
28438
+ this.toast = document.createElement('div');
28439
+ this.submitSessionDialog = document.createElement('div');
28440
+ // Recreate UIManager with proper config
28441
+ this.uiManager = new _UIManager__WEBPACK_IMPORTED_MODULE_5__.UIManager(this.recorderButton, this.initialPopover, this.finalPopover, this.overlay, this.submitSessionDialog, this.toast, this._widgetTextOverrides, this._showContinuousRecording);
28442
+ // Re-initialize templates with new config
28443
+ this.uiManager.setRecorderButtonProps();
28444
+ this.uiManager.setInitialPopoverProps();
28445
+ this.uiManager.setFinalPopoverProps();
28446
+ this.uiManager.setOverlayProps();
28447
+ this.uiManager.setSubmitSessionDialogProps();
28448
+ this.uiManager.setToastProps();
28449
+ this.commentTextarea = this.finalPopover.querySelector('.mp-session-debugger-popover-textarea');
28450
+ this.observeButtonDraggableMode();
28451
+ }
28419
28452
  init(options) {
28420
28453
  if (this._isInitialized)
28421
28454
  return;
28455
+ // Safety guard: avoid DOM access in SSR/non-browser environments
28456
+ if (typeof document === 'undefined') {
28457
+ return;
28458
+ }
28422
28459
  this._isInitialized = true;
28423
28460
  this.showRecorderButton = options.showWidget;
28424
28461
  this._showContinuousRecording = options.showContinuousRecording;
@@ -28426,15 +28463,7 @@ class SessionWidget extends lib0_observable__WEBPACK_IMPORTED_MODULE_7__.Observa
28426
28463
  ...this._widgetTextOverrides,
28427
28464
  ...options.widgetTextOverrides,
28428
28465
  };
28429
- // Recreate UIManager with proper config
28430
- this.uiManager = new _UIManager__WEBPACK_IMPORTED_MODULE_5__.UIManager(this.recorderButton, this.initialPopover, this.finalPopover, this.overlay, this.submitSessionDialog, this.toast, this._widgetTextOverrides, this._showContinuousRecording);
28431
- // Re-initialize templates with new config
28432
- this.uiManager.setRecorderButtonProps();
28433
- this.uiManager.setInitialPopoverProps();
28434
- this.uiManager.setFinalPopoverProps();
28435
- this.uiManager.setOverlayProps();
28436
- this.uiManager.setSubmitSessionDialogProps();
28437
- this.uiManager.setToastProps();
28466
+ this.initUiManager();
28438
28467
  const elements = [this.toast];
28439
28468
  if (options.showWidget) {
28440
28469
  elements.push(this.recorderButton, this.initialPopover, this.finalPopover, this.submitSessionDialog);
@@ -29574,20 +29603,30 @@ __webpack_require__.r(__webpack_exports__);
29574
29603
  /**
29575
29604
  * LocalStorage utility functions
29576
29605
  */
29606
+ const hasLocalStorage = typeof window !== 'undefined' && !!window.localStorage;
29577
29607
  const getStoredItem = (key, parse) => {
29578
- const item = localStorage === null || localStorage === void 0 ? void 0 : localStorage.getItem(key);
29608
+ if (!hasLocalStorage) {
29609
+ return parse ? null : null;
29610
+ }
29611
+ const item = window.localStorage.getItem(key);
29579
29612
  return parse ? (item ? JSON.parse(item) : null) : item;
29580
29613
  };
29581
29614
  const setStoredItem = (key, value) => {
29615
+ if (!hasLocalStorage) {
29616
+ return;
29617
+ }
29582
29618
  if (value === null || value === undefined) {
29583
- localStorage === null || localStorage === void 0 ? void 0 : localStorage.removeItem(key);
29619
+ window.localStorage.removeItem(key);
29584
29620
  }
29585
29621
  else {
29586
- localStorage === null || localStorage === void 0 ? void 0 : localStorage.setItem(key, typeof value === 'string' ? value : JSON.stringify(value));
29622
+ window.localStorage.setItem(key, typeof value === 'string' ? value : JSON.stringify(value));
29587
29623
  }
29588
29624
  };
29589
29625
  const removeStoredItem = (key) => {
29590
- localStorage === null || localStorage === void 0 ? void 0 : localStorage.removeItem(key);
29626
+ if (!hasLocalStorage) {
29627
+ return;
29628
+ }
29629
+ window.localStorage.removeItem(key);
29591
29630
  };
29592
29631
 
29593
29632
 
@@ -49234,12 +49273,29 @@ __webpack_require__.r(__webpack_exports__);
49234
49273
 
49235
49274
 
49236
49275
 
49237
- const SessionRecorderInstance = new _sessionRecorder__WEBPACK_IMPORTED_MODULE_3__.SessionRecorder();
49238
- // Attach the instance to the global object (window in browser)
49239
- if (typeof window !== 'undefined') {
49240
- window['__SESSION_RECORDER_LOADED'] = true;
49241
- window['SessionRecorder'] = SessionRecorderInstance;
49242
- (0,_listeners__WEBPACK_IMPORTED_MODULE_1__.setupListeners)(SessionRecorderInstance);
49276
+ // Create or reuse a single global instance, but be safe in non-browser environments
49277
+ const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
49278
+ // Prefer globalThis when available; fall back to window in browsers
49279
+ const globalObj = typeof globalThis !== 'undefined'
49280
+ ? globalThis
49281
+ : (isBrowser ? window : {});
49282
+ let SessionRecorderInstance;
49283
+ if (isBrowser) {
49284
+ // Reuse existing instance if already injected (e.g., by an extension)
49285
+ const existing = globalObj['SessionRecorder'];
49286
+ SessionRecorderInstance = existing !== null && existing !== void 0 ? existing : new _sessionRecorder__WEBPACK_IMPORTED_MODULE_3__.SessionRecorder();
49287
+ // Attach to the global object for reuse across bundles/loads
49288
+ globalObj['SessionRecorder'] = SessionRecorderInstance;
49289
+ globalObj['__SESSION_RECORDER_LOADED'] = true;
49290
+ // Ensure listeners are set up only once
49291
+ if (!globalObj['__SESSION_RECORDER_LISTENERS_SETUP__']) {
49292
+ (0,_listeners__WEBPACK_IMPORTED_MODULE_1__.setupListeners)(SessionRecorderInstance);
49293
+ globalObj['__SESSION_RECORDER_LISTENERS_SETUP__'] = true;
49294
+ }
49295
+ }
49296
+ else {
49297
+ // SSR / non-DOM environments: create an instance but don't touch globals or listeners
49298
+ SessionRecorderInstance = new _sessionRecorder__WEBPACK_IMPORTED_MODULE_3__.SessionRecorder();
49243
49299
  }
49244
49300
 
49245
49301
  /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (SessionRecorderInstance);