@multiplayer-app/session-recorder-browser 1.3.13 → 1.3.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -24149,7 +24149,7 @@ const CONTINUOUS_DEBUGGING_TIMEOUT = 60000; // 1 minutes
24149
24149
  const DEBUG_SESSION_MAX_DURATION_SECONDS = 10 * 60 + 30; // TODO: move to shared config otel core
24150
24150
  const REMOTE_SESSION_RECORDING_START = 'remote-session-recording:start';
24151
24151
  const REMOTE_SESSION_RECORDING_STOP = 'remote-session-recording:stop';
24152
- const PACKAGE_VERSION_EXPORT = "1.3.13" || 0;
24152
+ const PACKAGE_VERSION_EXPORT = "1.3.15" || 0;
24153
24153
  // Regex patterns for OpenTelemetry ignore URLs
24154
24154
  const OTEL_IGNORE_URLS = [
24155
24155
  // Traces endpoint
@@ -25046,7 +25046,6 @@ const getElementTextContent = (element) => {
25046
25046
 
25047
25047
 
25048
25048
 
25049
-
25050
25049
  class TracerBrowserSDK {
25051
25050
  constructor() {
25052
25051
  this.sessionId = '';
@@ -25221,14 +25220,7 @@ class TracerBrowserSDK {
25221
25220
  try {
25222
25221
  const activeSpan = _opentelemetry_api__WEBPACK_IMPORTED_MODULE_10__.trace.getSpan(_opentelemetry_api__WEBPACK_IMPORTED_MODULE_11__.context.active());
25223
25222
  if (activeSpan) {
25224
- // Standard OTEL exception event + span status
25225
- _multiplayer_app_session_recorder_common__WEBPACK_IMPORTED_MODULE_1__.SessionRecorderSdk.captureException(error);
25226
- activeSpan.addEvent('exception', {
25227
- 'exception.type': error.name || 'Error',
25228
- 'exception.message': error.message,
25229
- 'exception.stacktrace': error.stack || '',
25230
- ...(errorInfo || {}),
25231
- });
25223
+ this._recordException(activeSpan, error, errorInfo);
25232
25224
  return;
25233
25225
  }
25234
25226
  // eslint-disable-next-line
@@ -25238,19 +25230,24 @@ class TracerBrowserSDK {
25238
25230
  try {
25239
25231
  const tracer = _opentelemetry_api__WEBPACK_IMPORTED_MODULE_10__.trace.getTracer('exception');
25240
25232
  const span = tracer.startSpan(error.name || 'Error');
25241
- span.recordException(error);
25242
- span.setStatus({ code: _opentelemetry_api__WEBPACK_IMPORTED_MODULE_12__.SpanStatusCode.ERROR, message: error.message });
25243
- span.addEvent('exception', {
25244
- 'exception.type': error.name || 'Error',
25245
- 'exception.message': error.message,
25246
- 'exception.stacktrace': error.stack || '',
25247
- ...(errorInfo || {}),
25248
- });
25233
+ this._recordException(span, error, errorInfo);
25249
25234
  span.end();
25250
25235
  // eslint-disable-next-line
25251
25236
  }
25252
25237
  catch (_ignored) { }
25253
25238
  }
25239
+ _recordException(span, error, errorInfo) {
25240
+ span.recordException(error);
25241
+ span.setStatus({ code: _opentelemetry_api__WEBPACK_IMPORTED_MODULE_12__.SpanStatusCode.ERROR, message: error.message });
25242
+ span.setAttribute('exception.type', error.name || 'Error');
25243
+ span.setAttribute('exception.message', error.message);
25244
+ span.setAttribute('exception.stacktrace', error.stack || '');
25245
+ if (errorInfo) {
25246
+ Object.entries(errorInfo).forEach(([key, value]) => {
25247
+ span.setAttribute(`error_info.${key}`, value);
25248
+ });
25249
+ }
25250
+ }
25254
25251
  _getSpanSessionIdProcessor() {
25255
25252
  return {
25256
25253
  forceFlush: () => Promise.resolve(),
@@ -25336,7 +25333,7 @@ const setShouldRecordHttpData = (shouldRecordBody, shouldRecordHeaders) => {
25336
25333
 
25337
25334
 
25338
25335
 
25339
- function _tryReadFetchBody({ body, url, }) {
25336
+ function _tryReadFetchBody({ body, url }) {
25340
25337
  if ((0,_utils_type_utils__WEBPACK_IMPORTED_MODULE_0__.isNullish)(body)) {
25341
25338
  return null;
25342
25339
  }
@@ -25359,32 +25356,82 @@ function _tryReadFetchBody({ body, url, }) {
25359
25356
  }
25360
25357
  return `[Fetch] Cannot read body of type ${toString.call(body)}`;
25361
25358
  }
25359
+ /**
25360
+ * Detects if a response is a streaming response that should NOT have its body read.
25361
+ * Reading the body of streaming responses (SSE, chunked streams, etc.) will either:
25362
+ * - Block forever (SSE streams never end)
25363
+ * - Corrupt the stream for the actual consumer
25364
+ */
25365
+ function _isStreamingResponse(response) {
25366
+ var _a, _b, _c;
25367
+ const contentType = (_b = (_a = response.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : '';
25368
+ // SSE - Server-Sent Events (infinite stream)
25369
+ if (contentType.includes('text/event-stream')) {
25370
+ return true;
25371
+ }
25372
+ // Binary streams that are typically long-running
25373
+ if (contentType.includes('application/octet-stream')) {
25374
+ return true;
25375
+ }
25376
+ // NDJSON streaming (newline-delimited JSON, common in streaming APIs)
25377
+ if (contentType.includes('application/x-ndjson') || contentType.includes('application/ndjson')) {
25378
+ return true;
25379
+ }
25380
+ // gRPC-web streaming
25381
+ if (contentType.includes('application/grpc')) {
25382
+ return true;
25383
+ }
25384
+ // Check for chunked transfer encoding (often indicates streaming)
25385
+ const transferEncoding = (_c = response.headers.get('transfer-encoding')) === null || _c === void 0 ? void 0 : _c.toLowerCase();
25386
+ if (transferEncoding === null || transferEncoding === void 0 ? void 0 : transferEncoding.includes('chunked')) {
25387
+ // Chunked alone isn't definitive, but combined with no content-length = streaming
25388
+ const contentLength = response.headers.get('content-length');
25389
+ if (!contentLength) {
25390
+ return true;
25391
+ }
25392
+ }
25393
+ return false;
25394
+ }
25395
+ /**
25396
+ * Safely reads response body for non-streaming responses.
25397
+ * Returns null for streaming responses to avoid blocking/corruption.
25398
+ */
25362
25399
  async function _tryReadResponseBody(response) {
25363
25400
  var _a, _b;
25401
+ // CRITICAL: Never attempt to read streaming response bodies
25402
+ if (_isStreamingResponse(response)) {
25403
+ return null;
25404
+ }
25364
25405
  try {
25365
- // Clone the response to avoid consuming the original stream
25406
+ // Clone the response to avoid consuming the original
25366
25407
  const clonedResponse = response.clone();
25367
- // Try different methods to read the body
25368
- if ((_a = response.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.includes('application/json')) {
25408
+ const contentType = (_b = (_a = response.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : '';
25409
+ // Check content-length to avoid reading massive responses
25410
+ const contentLength = response.headers.get('content-length');
25411
+ if (contentLength) {
25412
+ const length = parseInt(contentLength, 10);
25413
+ if (!isNaN(length) && length > _configs__WEBPACK_IMPORTED_MODULE_2__.configs.maxCapturingHttpPayloadSize) {
25414
+ return `[Fetch] Response too large (${length} bytes)`;
25415
+ }
25416
+ }
25417
+ if (contentType.includes('application/json')) {
25369
25418
  const json = await clonedResponse.json();
25370
25419
  return JSON.stringify(json);
25371
25420
  }
25372
- else if ((_b = response.headers.get('content-type')) === null || _b === void 0 ? void 0 : _b.includes('text/')) {
25421
+ if (contentType.includes('text/')) {
25373
25422
  return await clonedResponse.text();
25374
25423
  }
25375
- else {
25376
- // For other content types, try text first, fallback to arrayBuffer
25424
+ // For unknown types, attempt text read with timeout protection
25425
+ try {
25426
+ return await clonedResponse.text();
25427
+ }
25428
+ catch (_c) {
25377
25429
  try {
25378
- return await clonedResponse.text();
25430
+ const arrayBuffer = await clonedResponse.arrayBuffer();
25431
+ return `[Fetch] Binary data (${arrayBuffer.byteLength} bytes)`;
25379
25432
  }
25380
- catch (_c) {
25381
- try {
25382
- const arrayBuffer = await clonedResponse.arrayBuffer();
25383
- return `[Fetch] Binary data (${arrayBuffer.byteLength} bytes)`;
25384
- }
25385
- catch (_d) {
25386
- return '[Fetch] Unable to read response body';
25387
- }
25433
+ catch (_d) {
25434
+ return '[Fetch] Unable to read response body';
25388
25435
  }
25389
25436
  }
25390
25437
  }
@@ -25427,6 +25474,7 @@ if (typeof window !== 'undefined' && typeof window.fetch !== 'undefined') {
25427
25474
  }
25428
25475
  else {
25429
25476
  // @ts-ignore
25477
+ ;
25430
25478
  window.fetch.__mp_session_recorder_patched__ = true;
25431
25479
  // Store original fetch
25432
25480
  const originalFetch = window.fetch;
@@ -25448,7 +25496,9 @@ if (typeof window !== 'undefined' && typeof window.fetch !== 'undefined') {
25448
25496
  if (_configs__WEBPACK_IMPORTED_MODULE_2__.configs.shouldRecordBody) {
25449
25497
  const urlStr = inputIsRequest
25450
25498
  ? input.url
25451
- : (typeof input === 'string' || input instanceof URL ? String(input) : '');
25499
+ : typeof input === 'string' || input instanceof URL
25500
+ ? String(input)
25501
+ : '';
25452
25502
  // Only attempt to read the body from init (safe); avoid constructing/cloning Requests
25453
25503
  // If the caller passed a Request as input, we do not attempt to read its body here
25454
25504
  // eslint-disable-next-line
@@ -25456,10 +25506,9 @@ if (typeof window !== 'undefined' && typeof window.fetch !== 'undefined') {
25456
25506
  if (!(0,_utils_type_utils__WEBPACK_IMPORTED_MODULE_0__.isNullish)(candidateBody)) {
25457
25507
  const requestBody = _tryReadFetchBody({
25458
25508
  body: candidateBody,
25459
- url: urlStr,
25509
+ url: urlStr
25460
25510
  });
25461
- if ((requestBody === null || requestBody === void 0 ? void 0 : requestBody.length) &&
25462
- new Blob([requestBody]).size <= _configs__WEBPACK_IMPORTED_MODULE_2__.configs.maxCapturingHttpPayloadSize) {
25511
+ if ((requestBody === null || requestBody === void 0 ? void 0 : requestBody.length) && new Blob([requestBody]).size <= _configs__WEBPACK_IMPORTED_MODULE_2__.configs.maxCapturingHttpPayloadSize) {
25463
25512
  networkRequest.requestBody = requestBody;
25464
25513
  }
25465
25514
  }
@@ -25473,8 +25522,7 @@ if (typeof window !== 'undefined' && typeof window.fetch !== 'undefined') {
25473
25522
  }
25474
25523
  if (_configs__WEBPACK_IMPORTED_MODULE_2__.configs.shouldRecordBody) {
25475
25524
  const responseBody = await _tryReadResponseBody(response);
25476
- if ((responseBody === null || responseBody === void 0 ? void 0 : responseBody.length) &&
25477
- new Blob([responseBody]).size <= _configs__WEBPACK_IMPORTED_MODULE_2__.configs.maxCapturingHttpPayloadSize) {
25525
+ if ((responseBody === null || responseBody === void 0 ? void 0 : responseBody.length) && new Blob([responseBody]).size <= _configs__WEBPACK_IMPORTED_MODULE_2__.configs.maxCapturingHttpPayloadSize) {
25478
25526
  networkRequest.responseBody = responseBody;
25479
25527
  }
25480
25528
  }