@multiplayer-app/session-recorder-browser 1.2.25 → 1.2.27

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
@@ -259,6 +259,32 @@ SessionRecorder.save()
259
259
  SessionRecorder.stop('Finished session') // optional: pass reason for stopping the session
260
260
  ```
261
261
 
262
+ ### Capture exceptions
263
+
264
+ The browser SDK captures uncaught errors and unhandled promise rejections automatically and turns them into error traces that are linked to your session.
265
+
266
+ For each error span we record:
267
+
268
+ - status set to `ERROR`
269
+ - standard exception attributes: `exception.type`, `exception.message`, `exception.stacktrace`
270
+
271
+ Manual reporting (e.g. inside try/catch or library boundaries):
272
+
273
+ ```javascript
274
+ import SessionRecorder from '@multiplayer-app/session-recorder-browser'
275
+
276
+ try {
277
+ // code that may throw
278
+ } catch (err) {
279
+ SessionRecorder.captureException(err) // Error | unknown | string
280
+ }
281
+
282
+ // You can also send arbitrary reasons
283
+ SessionRecorder.captureException('Payment form validation failed')
284
+ ```
285
+
286
+ When running in `CONTINUOUS` mode, any captured exception automatically marks the current trace as an error and auto‑saves the rolling window so you can replay the seconds leading up to the failure.
287
+
262
288
  Continuous session recordings may also be saved from within any service or component involved in a trace by adding the attributes below to a span:
263
289
 
264
290
  ```javascript
@@ -25243,7 +25243,7 @@ const DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE = 100000;
25243
25243
  const SESSION_RESPONSE = 'multiplayer-debug-session-response';
25244
25244
  const CONTINUOUS_DEBUGGING_TIMEOUT = 60000; // 1 minutes
25245
25245
  const DEBUG_SESSION_MAX_DURATION_SECONDS = 10 * 60 + 30; // TODO: move to shared config otel core
25246
- const PACKAGE_VERSION_EXPORT = "1.2.25" || 0;
25246
+ const PACKAGE_VERSION_EXPORT = "1.2.27" || 0;
25247
25247
  // Regex patterns for OpenTelemetry ignore URLs
25248
25248
  const OTEL_IGNORE_URLS = [
25249
25249
  // Traces endpoint
@@ -26128,6 +26128,9 @@ __webpack_require__.r(__webpack_exports__);
26128
26128
  /* harmony import */ var _opentelemetry_instrumentation__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! @opentelemetry/instrumentation */ "../../node_modules/@opentelemetry/instrumentation/build/esm/autoLoader.js");
26129
26129
  /* harmony import */ var _opentelemetry_auto_instrumentations_web__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @opentelemetry/auto-instrumentations-web */ "../../node_modules/@opentelemetry/auto-instrumentations-web/build/esm/index.js");
26130
26130
  /* 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");
26131
+ /* harmony import */ var _opentelemetry_api__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! @opentelemetry/api */ "../../node_modules/@opentelemetry/api/build/esm/trace-api.js");
26132
+ /* harmony import */ var _opentelemetry_api__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! @opentelemetry/api */ "../../node_modules/@opentelemetry/api/build/esm/context-api.js");
26133
+ /* harmony import */ var _opentelemetry_api__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! @opentelemetry/api */ "../../node_modules/@opentelemetry/api/build/esm/trace/status.js");
26131
26134
  /* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../config */ "./src/config/index.ts");
26132
26135
  /* harmony import */ var _helpers__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./helpers */ "./src/otel/helpers.ts");
26133
26136
 
@@ -26140,10 +26143,13 @@ __webpack_require__.r(__webpack_exports__);
26140
26143
 
26141
26144
 
26142
26145
 
26146
+
26147
+
26143
26148
  class TracerBrowserSDK {
26144
26149
  constructor() {
26145
26150
  this.allowedElements = new Set(['A', 'BUTTON']);
26146
26151
  this.sessionId = '';
26152
+ this.globalErrorListenersRegistered = false;
26147
26153
  }
26148
26154
  setSessionId(sessionId, sessionType = _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionType.PLAIN) {
26149
26155
  this.sessionId = sessionId;
@@ -26282,6 +26288,7 @@ class TracerBrowserSDK {
26282
26288
  }),
26283
26289
  ],
26284
26290
  });
26291
+ this._registerGlobalErrorListeners();
26285
26292
  }
26286
26293
  start(sessionId, sessionType) {
26287
26294
  if (!this.tracerProvider) {
@@ -26301,6 +26308,46 @@ class TracerBrowserSDK {
26301
26308
  }
26302
26309
  this.exporter.setApiKey(apiKey);
26303
26310
  }
26311
+ /**
26312
+ * Capture an exception as an error span/event.
26313
+ * If there is an active span, the exception will be recorded on it.
26314
+ * Otherwise, a short-lived span will be created to hold the exception event.
26315
+ */
26316
+ captureException(error, errorInfo) {
26317
+ if (!error)
26318
+ return;
26319
+ // Prefer attaching to the active span to keep correlation intact
26320
+ try {
26321
+ const activeSpan = _opentelemetry_api__WEBPACK_IMPORTED_MODULE_10__.trace.getSpan(_opentelemetry_api__WEBPACK_IMPORTED_MODULE_11__.context.active());
26322
+ if (activeSpan) {
26323
+ // Standard OTEL exception event + span status
26324
+ _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionRecorderSdk.captureException(error);
26325
+ activeSpan.addEvent('exception', {
26326
+ 'exception.type': error.name || 'Error',
26327
+ 'exception.message': error.message,
26328
+ 'exception.stacktrace': error.stack || '',
26329
+ ...(errorInfo || {}),
26330
+ });
26331
+ return;
26332
+ }
26333
+ }
26334
+ catch (_ignored) { }
26335
+ // Fallback: create a short-lived span to hold the exception details
26336
+ try {
26337
+ const tracer = _opentelemetry_api__WEBPACK_IMPORTED_MODULE_10__.trace.getTracer('exception');
26338
+ const span = tracer.startSpan(error.name || 'Error');
26339
+ span.recordException(error);
26340
+ span.setStatus({ code: _opentelemetry_api__WEBPACK_IMPORTED_MODULE_12__.SpanStatusCode.ERROR, message: error.message });
26341
+ span.addEvent('exception', {
26342
+ 'exception.type': error.name || 'Error',
26343
+ 'exception.message': error.message,
26344
+ 'exception.stacktrace': error.stack || '',
26345
+ ...(errorInfo || {}),
26346
+ });
26347
+ span.end();
26348
+ }
26349
+ catch (_ignored) { }
26350
+ }
26304
26351
  _getSpanSessionIdProcessor() {
26305
26352
  return {
26306
26353
  forceFlush: () => Promise.resolve(),
@@ -26314,6 +26361,28 @@ class TracerBrowserSDK {
26314
26361
  },
26315
26362
  };
26316
26363
  }
26364
+ _registerGlobalErrorListeners() {
26365
+ if (this.globalErrorListenersRegistered)
26366
+ return;
26367
+ if (typeof window === 'undefined')
26368
+ return;
26369
+ const errorHandler = (event) => {
26370
+ const err = (event === null || event === void 0 ? void 0 : event.error) instanceof Error
26371
+ ? event.error
26372
+ : new Error((event === null || event === void 0 ? void 0 : event.message) || 'Script error');
26373
+ this.captureException(err);
26374
+ };
26375
+ const rejectionHandler = (event) => {
26376
+ const reason = (event && 'reason' in event) ? event.reason : undefined;
26377
+ const err = reason instanceof Error
26378
+ ? reason
26379
+ : new Error(typeof reason === 'string' ? reason : 'Unhandled promise rejection');
26380
+ this.captureException(err);
26381
+ };
26382
+ window.addEventListener('error', errorHandler);
26383
+ window.addEventListener('unhandledrejection', rejectionHandler);
26384
+ this.globalErrorListenersRegistered = true;
26385
+ }
26317
26386
  }
26318
26387
 
26319
26388
 
@@ -27523,6 +27592,19 @@ class SessionRecorder extends lib0_observable__WEBPACK_IMPORTED_MODULE_14__.Obse
27523
27592
  set recordingButtonClickHandler(handler) {
27524
27593
  this._sessionWidget.buttonClickExternalHandler = handler;
27525
27594
  }
27595
+ /**
27596
+ * Capture an exception manually and send it as an error trace.
27597
+ */
27598
+ captureException(error, errorInfo) {
27599
+ try {
27600
+ const normalizedError = this._normalizeError(error);
27601
+ const normalizedErrorInfo = this._normalizeErrorInfo(errorInfo);
27602
+ this._tracer.captureException(normalizedError, normalizedErrorInfo);
27603
+ }
27604
+ catch (e) {
27605
+ this.error = (e === null || e === void 0 ? void 0 : e.message) || 'Failed to capture exception';
27606
+ }
27607
+ }
27526
27608
  /**
27527
27609
  * @description Check if session should be started/stopped automatically
27528
27610
  * @param {ISession} [sessionPayload]
@@ -27770,6 +27852,28 @@ class SessionRecorder extends lib0_observable__WEBPACK_IMPORTED_MODULE_14__.Obse
27770
27852
  break;
27771
27853
  }
27772
27854
  }
27855
+ _normalizeError(error) {
27856
+ if (error instanceof Error)
27857
+ return error;
27858
+ if (typeof error === 'string')
27859
+ return new Error(error);
27860
+ try {
27861
+ return new Error(JSON.stringify(error));
27862
+ }
27863
+ catch (_e) {
27864
+ return new Error(String(error));
27865
+ }
27866
+ }
27867
+ _normalizeErrorInfo(errorInfo) {
27868
+ if (!errorInfo)
27869
+ return {};
27870
+ try {
27871
+ return JSON.parse(JSON.stringify(errorInfo));
27872
+ }
27873
+ catch (_e) {
27874
+ return { errorInfo: String(errorInfo) };
27875
+ }
27876
+ }
27773
27877
  }
27774
27878
 
27775
27879