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

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.26" || 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,39 @@ 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) {
26317
+ if (!error)
26318
+ return;
26319
+ // Try to record on the currently active span first
26320
+ const activeSpan = _opentelemetry_api__WEBPACK_IMPORTED_MODULE_10__.trace.getSpan(_opentelemetry_api__WEBPACK_IMPORTED_MODULE_11__.context.active());
26321
+ if (activeSpan) {
26322
+ try {
26323
+ _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionRecorderSdk.captureException(error);
26324
+ return;
26325
+ }
26326
+ catch (_e) {
26327
+ // fallthrough to creating a dedicated span
26328
+ }
26329
+ }
26330
+ try {
26331
+ const tracer = _opentelemetry_api__WEBPACK_IMPORTED_MODULE_10__.trace.getTracer('session-recorder');
26332
+ const span = tracer.startSpan('exception');
26333
+ span.recordException(error);
26334
+ span.setStatus({ code: _opentelemetry_api__WEBPACK_IMPORTED_MODULE_12__.SpanStatusCode.ERROR, message: error.message });
26335
+ span.end();
26336
+ }
26337
+ catch (_err) {
26338
+ // eslint-disable-next-line no-console
26339
+ if (true) {
26340
+ console.warn('[MULTIPLAYER_SESSION_RECORDER] Failed to capture exception', _err);
26341
+ }
26342
+ }
26343
+ }
26304
26344
  _getSpanSessionIdProcessor() {
26305
26345
  return {
26306
26346
  forceFlush: () => Promise.resolve(),
@@ -26314,6 +26354,28 @@ class TracerBrowserSDK {
26314
26354
  },
26315
26355
  };
26316
26356
  }
26357
+ _registerGlobalErrorListeners() {
26358
+ if (this.globalErrorListenersRegistered)
26359
+ return;
26360
+ if (typeof window === 'undefined')
26361
+ return;
26362
+ const errorHandler = (event) => {
26363
+ const err = (event === null || event === void 0 ? void 0 : event.error) instanceof Error
26364
+ ? event.error
26365
+ : new Error((event === null || event === void 0 ? void 0 : event.message) || 'Script error');
26366
+ this.captureException(err);
26367
+ };
26368
+ const rejectionHandler = (event) => {
26369
+ const reason = (event && 'reason' in event) ? event.reason : undefined;
26370
+ const err = reason instanceof Error
26371
+ ? reason
26372
+ : new Error(typeof reason === 'string' ? reason : 'Unhandled promise rejection');
26373
+ this.captureException(err);
26374
+ };
26375
+ window.addEventListener('error', errorHandler);
26376
+ window.addEventListener('unhandledrejection', rejectionHandler);
26377
+ this.globalErrorListenersRegistered = true;
26378
+ }
26317
26379
  }
26318
26380
 
26319
26381
 
@@ -27523,6 +27585,18 @@ class SessionRecorder extends lib0_observable__WEBPACK_IMPORTED_MODULE_14__.Obse
27523
27585
  set recordingButtonClickHandler(handler) {
27524
27586
  this._sessionWidget.buttonClickExternalHandler = handler;
27525
27587
  }
27588
+ /**
27589
+ * Capture an exception manually and send it as an error trace.
27590
+ */
27591
+ captureException(error) {
27592
+ try {
27593
+ const normalizedError = this._normalizeError(error);
27594
+ this._tracer.captureException(normalizedError);
27595
+ }
27596
+ catch (e) {
27597
+ this.error = (e === null || e === void 0 ? void 0 : e.message) || 'Failed to capture exception';
27598
+ }
27599
+ }
27526
27600
  /**
27527
27601
  * @description Check if session should be started/stopped automatically
27528
27602
  * @param {ISession} [sessionPayload]
@@ -27770,6 +27844,18 @@ class SessionRecorder extends lib0_observable__WEBPACK_IMPORTED_MODULE_14__.Obse
27770
27844
  break;
27771
27845
  }
27772
27846
  }
27847
+ _normalizeError(error) {
27848
+ if (error instanceof Error)
27849
+ return error;
27850
+ if (typeof error === 'string')
27851
+ return new Error(error);
27852
+ try {
27853
+ return new Error(JSON.stringify(error));
27854
+ }
27855
+ catch (_e) {
27856
+ return new Error(String(error));
27857
+ }
27858
+ }
27773
27859
  }
27774
27860
 
27775
27861