@myinterview/widget-react 1.1.23-binary-002 → 1.1.23-binary-004

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/esm/index.js CHANGED
@@ -947,6 +947,12 @@ function createStackParser(...parsers) {
947
947
  // Remove webpack (error: *) wrappers
948
948
  const cleanedLine = WEBPACK_ERROR_REGEXP.test(line) ? line.replace(WEBPACK_ERROR_REGEXP, '$1') : line;
949
949
 
950
+ // https://github.com/getsentry/sentry-javascript/issues/7813
951
+ // Skip Error: lines
952
+ if (cleanedLine.match(/\S*Error: /)) {
953
+ continue;
954
+ }
955
+
950
956
  for (const parser of sortedParsers) {
951
957
  const frame = parser(cleanedLine);
952
958
 
@@ -1131,6 +1137,8 @@ function supportsHistory() {
1131
1137
  // eslint-disable-next-line deprecation/deprecation
1132
1138
  const WINDOW$3 = getGlobalObject();
1133
1139
 
1140
+ const SENTRY_XHR_DATA_KEY = '__sentry_xhr_v2__';
1141
+
1134
1142
  /**
1135
1143
  * Instrument native APIs to call handlers that can be used to create breadcrumbs, APM spans etc.
1136
1144
  * - Console API
@@ -1243,11 +1251,13 @@ function instrumentFetch() {
1243
1251
 
1244
1252
  fill(WINDOW$3, 'fetch', function (originalFetch) {
1245
1253
  return function (...args) {
1254
+ const { method, url } = parseFetchArgs(args);
1255
+
1246
1256
  const handlerData = {
1247
1257
  args,
1248
1258
  fetchData: {
1249
- method: getFetchMethod(args),
1250
- url: getFetchUrl(args),
1259
+ method,
1260
+ url,
1251
1261
  },
1252
1262
  startTimestamp: Date.now(),
1253
1263
  };
@@ -1282,29 +1292,53 @@ function instrumentFetch() {
1282
1292
  });
1283
1293
  }
1284
1294
 
1285
- /* eslint-disable @typescript-eslint/no-unsafe-member-access */
1286
- /** Extract `method` from fetch call arguments */
1287
- function getFetchMethod(fetchArgs = []) {
1288
- if ('Request' in WINDOW$3 && isInstanceOf(fetchArgs[0], Request) && fetchArgs[0].method) {
1289
- return String(fetchArgs[0].method).toUpperCase();
1295
+ function hasProp(obj, prop) {
1296
+ return !!obj && typeof obj === 'object' && !!(obj )[prop];
1297
+ }
1298
+
1299
+ function getUrlFromResource(resource) {
1300
+ if (typeof resource === 'string') {
1301
+ return resource;
1290
1302
  }
1291
- if (fetchArgs[1] && fetchArgs[1].method) {
1292
- return String(fetchArgs[1].method).toUpperCase();
1303
+
1304
+ if (!resource) {
1305
+ return '';
1306
+ }
1307
+
1308
+ if (hasProp(resource, 'url')) {
1309
+ return resource.url;
1293
1310
  }
1294
- return 'GET';
1311
+
1312
+ if (resource.toString) {
1313
+ return resource.toString();
1314
+ }
1315
+
1316
+ return '';
1295
1317
  }
1296
1318
 
1297
- /** Extract `url` from fetch call arguments */
1298
- function getFetchUrl(fetchArgs = []) {
1299
- if (typeof fetchArgs[0] === 'string') {
1300
- return fetchArgs[0];
1319
+ /**
1320
+ * Parses the fetch arguments to find the used Http method and the url of the request
1321
+ */
1322
+ function parseFetchArgs(fetchArgs) {
1323
+ if (fetchArgs.length === 0) {
1324
+ return { method: 'GET', url: '' };
1301
1325
  }
1302
- if ('Request' in WINDOW$3 && isInstanceOf(fetchArgs[0], Request)) {
1303
- return fetchArgs[0].url;
1326
+
1327
+ if (fetchArgs.length === 2) {
1328
+ const [url, options] = fetchArgs ;
1329
+
1330
+ return {
1331
+ url: getUrlFromResource(url),
1332
+ method: hasProp(options, 'method') ? String(options.method).toUpperCase() : 'GET',
1333
+ };
1304
1334
  }
1305
- return String(fetchArgs[0]);
1335
+
1336
+ const arg = fetchArgs[0];
1337
+ return {
1338
+ url: getUrlFromResource(arg ),
1339
+ method: hasProp(arg, 'method') ? String(arg.method).toUpperCase() : 'GET',
1340
+ };
1306
1341
  }
1307
- /* eslint-enable @typescript-eslint/no-unsafe-member-access */
1308
1342
 
1309
1343
  /** JSDoc */
1310
1344
  function instrumentXHR() {
@@ -1317,10 +1351,11 @@ function instrumentXHR() {
1317
1351
  fill(xhrproto, 'open', function (originalOpen) {
1318
1352
  return function ( ...args) {
1319
1353
  const url = args[1];
1320
- const xhrInfo = (this.__sentry_xhr__ = {
1354
+ const xhrInfo = (this[SENTRY_XHR_DATA_KEY] = {
1321
1355
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
1322
1356
  method: isString$2(args[0]) ? args[0].toUpperCase() : args[0],
1323
1357
  url: args[1],
1358
+ request_headers: {},
1324
1359
  });
1325
1360
 
1326
1361
  // if Sentry key appears in URL, don't capture it as a request
@@ -1331,7 +1366,7 @@ function instrumentXHR() {
1331
1366
 
1332
1367
  const onreadystatechangeHandler = () => {
1333
1368
  // For whatever reason, this is not the same instance here as from the outer method
1334
- const xhrInfo = this.__sentry_xhr__;
1369
+ const xhrInfo = this[SENTRY_XHR_DATA_KEY];
1335
1370
 
1336
1371
  if (!xhrInfo) {
1337
1372
  return;
@@ -1366,14 +1401,32 @@ function instrumentXHR() {
1366
1401
  this.addEventListener('readystatechange', onreadystatechangeHandler);
1367
1402
  }
1368
1403
 
1404
+ // Intercepting `setRequestHeader` to access the request headers of XHR instance.
1405
+ // This will only work for user/library defined headers, not for the default/browser-assigned headers.
1406
+ // Request cookies are also unavailable for XHR, as `Cookie` header can't be defined by `setRequestHeader`.
1407
+ fill(this, 'setRequestHeader', function (original) {
1408
+ return function ( ...setRequestHeaderArgs) {
1409
+ const [header, value] = setRequestHeaderArgs ;
1410
+
1411
+ const xhrInfo = this[SENTRY_XHR_DATA_KEY];
1412
+
1413
+ if (xhrInfo) {
1414
+ xhrInfo.request_headers[header.toLowerCase()] = value;
1415
+ }
1416
+
1417
+ return original.apply(this, setRequestHeaderArgs);
1418
+ };
1419
+ });
1420
+
1369
1421
  return originalOpen.apply(this, args);
1370
1422
  };
1371
1423
  });
1372
1424
 
1373
1425
  fill(xhrproto, 'send', function (originalSend) {
1374
1426
  return function ( ...args) {
1375
- if (this.__sentry_xhr__ && args[0] !== undefined) {
1376
- this.__sentry_xhr__.body = args[0];
1427
+ const sentryXhrData = this[SENTRY_XHR_DATA_KEY];
1428
+ if (sentryXhrData && args[0] !== undefined) {
1429
+ sentryXhrData.body = args[0];
1377
1430
  }
1378
1431
 
1379
1432
  triggerHandlers('xhr', {
@@ -1672,13 +1725,15 @@ function instrumentError() {
1672
1725
  url,
1673
1726
  });
1674
1727
 
1675
- if (_oldOnErrorHandler) {
1728
+ if (_oldOnErrorHandler && !_oldOnErrorHandler.__SENTRY_LOADER__) {
1676
1729
  // eslint-disable-next-line prefer-rest-params
1677
1730
  return _oldOnErrorHandler.apply(this, arguments);
1678
1731
  }
1679
1732
 
1680
1733
  return false;
1681
1734
  };
1735
+
1736
+ WINDOW$3.onerror.__SENTRY_INSTRUMENTED__ = true;
1682
1737
  }
1683
1738
 
1684
1739
  let _oldOnUnhandledRejectionHandler = null;
@@ -1689,13 +1744,15 @@ function instrumentUnhandledRejection() {
1689
1744
  WINDOW$3.onunhandledrejection = function (e) {
1690
1745
  triggerHandlers('unhandledrejection', e);
1691
1746
 
1692
- if (_oldOnUnhandledRejectionHandler) {
1747
+ if (_oldOnUnhandledRejectionHandler && !_oldOnUnhandledRejectionHandler.__SENTRY_LOADER__) {
1693
1748
  // eslint-disable-next-line prefer-rest-params
1694
1749
  return _oldOnUnhandledRejectionHandler.apply(this, arguments);
1695
1750
  }
1696
1751
 
1697
1752
  return true;
1698
1753
  };
1754
+
1755
+ WINDOW$3.onunhandledrejection.__SENTRY_INSTRUMENTED__ = true;
1699
1756
  }
1700
1757
 
1701
1758
  /* eslint-disable @typescript-eslint/no-unsafe-member-access */
@@ -1993,7 +2050,7 @@ function loadModule(moduleName) {
1993
2050
  * @returns A normalized version of the object, or `"**non-serializable**"` if any errors are thrown during normalization.
1994
2051
  */
1995
2052
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1996
- function normalize(input, depth = +Infinity, maxProperties = +Infinity) {
2053
+ function normalize(input, depth = 100, maxProperties = +Infinity) {
1997
2054
  try {
1998
2055
  // since we're at the outermost level, we don't provide a key
1999
2056
  return visit('', input, depth, maxProperties);
@@ -2039,7 +2096,10 @@ function visit(
2039
2096
  const [memoize, unmemoize] = memo;
2040
2097
 
2041
2098
  // Get the simple cases out of the way first
2042
- if (value === null || (['number', 'boolean', 'string'].includes(typeof value) && !isNaN$1(value))) {
2099
+ if (
2100
+ value == null || // this matches null and undefined -> eqeq not eqeqeq
2101
+ (['number', 'boolean', 'string'].includes(typeof value) && !isNaN$1(value))
2102
+ ) {
2043
2103
  return value ;
2044
2104
  }
2045
2105
 
@@ -2060,17 +2120,16 @@ function visit(
2060
2120
  return value ;
2061
2121
  }
2062
2122
 
2063
- // Do not normalize objects that we know have already been normalized. As a general rule, the
2064
- // "__sentry_skip_normalization__" property should only be used sparingly and only should only be set on objects that
2065
- // have already been normalized.
2066
- let overriddenDepth = depth;
2067
-
2068
- if (typeof (value )['__sentry_override_normalization_depth__'] === 'number') {
2069
- overriddenDepth = (value )['__sentry_override_normalization_depth__'] ;
2070
- }
2123
+ // We can set `__sentry_override_normalization_depth__` on an object to ensure that from there
2124
+ // We keep a certain amount of depth.
2125
+ // This should be used sparingly, e.g. we use it for the redux integration to ensure we get a certain amount of state.
2126
+ const remainingDepth =
2127
+ typeof (value )['__sentry_override_normalization_depth__'] === 'number'
2128
+ ? ((value )['__sentry_override_normalization_depth__'] )
2129
+ : depth;
2071
2130
 
2072
2131
  // We're also done if we've reached the max depth
2073
- if (overriddenDepth === 0) {
2132
+ if (remainingDepth === 0) {
2074
2133
  // At this point we know `serialized` is a string of the form `"[object XXXX]"`. Clean it up so it's just `"[XXXX]"`.
2075
2134
  return stringified.replace('object ', '');
2076
2135
  }
@@ -2086,7 +2145,7 @@ function visit(
2086
2145
  try {
2087
2146
  const jsonValue = valueWithToJSON.toJSON();
2088
2147
  // We need to normalize the return value of `.toJSON()` in case it has circular references
2089
- return visit('', jsonValue, overriddenDepth - 1, maxProperties, memo);
2148
+ return visit('', jsonValue, remainingDepth - 1, maxProperties, memo);
2090
2149
  } catch (err) {
2091
2150
  // pass (The built-in `toJSON` failed, but we can still try to do it ourselves)
2092
2151
  }
@@ -2115,7 +2174,7 @@ function visit(
2115
2174
 
2116
2175
  // Recursively visit all the child nodes
2117
2176
  const visitValue = visitable[visitKey];
2118
- normalized[visitKey] = visit(visitKey, visitValue, overriddenDepth - 1, maxProperties, memo);
2177
+ normalized[visitKey] = visit(visitKey, visitValue, remainingDepth - 1, maxProperties, memo);
2119
2178
 
2120
2179
  numAdded++;
2121
2180
  }
@@ -2127,6 +2186,7 @@ function visit(
2127
2186
  return normalized;
2128
2187
  }
2129
2188
 
2189
+ /* eslint-disable complexity */
2130
2190
  /**
2131
2191
  * Stringify the given value. Handles various known special values and types.
2132
2192
  *
@@ -2177,11 +2237,6 @@ function stringifyValue(
2177
2237
  return '[NaN]';
2178
2238
  }
2179
2239
 
2180
- // this catches `undefined` (but not `null`, which is a primitive and can be serialized on its own)
2181
- if (value === void 0) {
2182
- return '[undefined]';
2183
- }
2184
-
2185
2240
  if (typeof value === 'function') {
2186
2241
  return `[Function: ${getFunctionName(value)}]`;
2187
2242
  }
@@ -2199,11 +2254,19 @@ function stringifyValue(
2199
2254
  // them to strings means that instances of classes which haven't defined their `toStringTag` will just come out as
2200
2255
  // `"[object Object]"`. If we instead look at the constructor's name (which is the same as the name of the class),
2201
2256
  // we can make sure that only plain objects come out that way.
2202
- return `[object ${getConstructorName(value)}]`;
2257
+ const objName = getConstructorName(value);
2258
+
2259
+ // Handle HTML Elements
2260
+ if (/^HTML(\w*)Element$/.test(objName)) {
2261
+ return `[HTMLElement: ${objName}]`;
2262
+ }
2263
+
2264
+ return `[object ${objName}]`;
2203
2265
  } catch (err) {
2204
2266
  return `**non-serializable** (${err})`;
2205
2267
  }
2206
2268
  }
2269
+ /* eslint-enable complexity */
2207
2270
 
2208
2271
  function getConstructorName(value) {
2209
2272
  const prototype = Object.getPrototypeOf(value);
@@ -2514,9 +2577,7 @@ function makePromiseBuffer(limit) {
2514
2577
  * // environments where DOM might not be available
2515
2578
  * @returns parsed URL object
2516
2579
  */
2517
- function parseUrl(url)
2518
-
2519
- {
2580
+ function parseUrl(url) {
2520
2581
  if (!url) {
2521
2582
  return {};
2522
2583
  }
@@ -2534,6 +2595,8 @@ function parseUrl(url)
2534
2595
  host: match[4],
2535
2596
  path: match[5],
2536
2597
  protocol: match[2],
2598
+ search: query,
2599
+ hash: fragment,
2537
2600
  relative: match[5] + query + fragment, // everything minus origin
2538
2601
  };
2539
2602
  }
@@ -2918,6 +2981,7 @@ const ITEM_TYPE_TO_DATA_CATEGORY_MAP = {
2918
2981
  profile: 'profile',
2919
2982
  replay_event: 'replay',
2920
2983
  replay_recording: 'replay',
2984
+ check_in: 'monitor',
2921
2985
  };
2922
2986
 
2923
2987
  /**
@@ -2947,16 +3011,14 @@ function createEventEnvelopeHeaders(
2947
3011
  dsn,
2948
3012
  ) {
2949
3013
  const dynamicSamplingContext = event.sdkProcessingMetadata && event.sdkProcessingMetadata.dynamicSamplingContext;
2950
-
2951
3014
  return {
2952
3015
  event_id: event.event_id ,
2953
3016
  sent_at: new Date().toISOString(),
2954
3017
  ...(sdkInfo && { sdk: sdkInfo }),
2955
3018
  ...(!!tunnel && { dsn: dsnToString(dsn) }),
2956
- ...(event.type === 'transaction' &&
2957
- dynamicSamplingContext && {
2958
- trace: dropUndefinedKeys({ ...dynamicSamplingContext }),
2959
- }),
3019
+ ...(dynamicSamplingContext && {
3020
+ trace: dropUndefinedKeys({ ...dynamicSamplingContext }),
3021
+ }),
2960
3022
  };
2961
3023
  }
2962
3024
 
@@ -3661,9 +3723,16 @@ class Scope {
3661
3723
  // errors with transaction and it relies on that.
3662
3724
  if (this._span) {
3663
3725
  event.contexts = { trace: this._span.getTraceContext(), ...event.contexts };
3664
- const transactionName = this._span.transaction && this._span.transaction.name;
3665
- if (transactionName) {
3666
- event.tags = { transaction: transactionName, ...event.tags };
3726
+ const transaction = this._span.transaction;
3727
+ if (transaction) {
3728
+ event.sdkProcessingMetadata = {
3729
+ dynamicSamplingContext: transaction.getDynamicSamplingContext(),
3730
+ ...event.sdkProcessingMetadata,
3731
+ };
3732
+ const transactionName = transaction.name;
3733
+ if (transactionName) {
3734
+ event.tags = { transaction: transactionName, ...event.tags };
3735
+ }
3667
3736
  }
3668
3737
  }
3669
3738
 
@@ -3787,11 +3856,6 @@ const API_VERSION = 4;
3787
3856
  */
3788
3857
  const DEFAULT_BREADCRUMBS = 100;
3789
3858
 
3790
- /**
3791
- * A layer in the process stack.
3792
- * @hidden
3793
- */
3794
-
3795
3859
  /**
3796
3860
  * @inheritDoc
3797
3861
  */
@@ -4069,7 +4133,17 @@ class Hub {
4069
4133
  * @inheritDoc
4070
4134
  */
4071
4135
  startTransaction(context, customSamplingContext) {
4072
- return this._callExtensionMethod('startTransaction', context, customSamplingContext);
4136
+ const result = this._callExtensionMethod('startTransaction', context, customSamplingContext);
4137
+
4138
+ if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && !result) {
4139
+ // eslint-disable-next-line no-console
4140
+ console.warn(`Tracing extension 'startTransaction' has not been added. Call 'addTracingExtensions' before calling 'init':
4141
+ Sentry.addTracingExtensions();
4142
+ Sentry.init({...});
4143
+ `);
4144
+ }
4145
+
4146
+ return result;
4073
4147
  }
4074
4148
 
4075
4149
  /**
@@ -4154,13 +4228,10 @@ class Hub {
4154
4228
  */
4155
4229
  _sendSessionUpdate() {
4156
4230
  const { scope, client } = this.getStackTop();
4157
- if (!scope) return;
4158
4231
 
4159
4232
  const session = scope.getSession();
4160
- if (session) {
4161
- if (client && client.captureSession) {
4162
- client.captureSession(session);
4163
- }
4233
+ if (session && client && client.captureSession) {
4234
+ client.captureSession(session);
4164
4235
  }
4165
4236
  }
4166
4237
 
@@ -4230,47 +4301,28 @@ function getCurrentHub() {
4230
4301
  // Get main carrier (global for every environment)
4231
4302
  const registry = getMainCarrier();
4232
4303
 
4304
+ if (registry.__SENTRY__ && registry.__SENTRY__.acs) {
4305
+ const hub = registry.__SENTRY__.acs.getCurrentHub();
4306
+
4307
+ if (hub) {
4308
+ return hub;
4309
+ }
4310
+ }
4311
+
4312
+ // Return hub that lives on a global object
4313
+ return getGlobalHub(registry);
4314
+ }
4315
+
4316
+ function getGlobalHub(registry = getMainCarrier()) {
4233
4317
  // If there's no hub, or its an old API, assign a new one
4234
4318
  if (!hasHubOnCarrier(registry) || getHubFromCarrier(registry).isOlderThan(API_VERSION)) {
4235
4319
  setHubOnCarrier(registry, new Hub());
4236
4320
  }
4237
4321
 
4238
- // Prefer domains over global if they are there (applicable only to Node environment)
4239
- if (isNodeEnv()) {
4240
- return getHubFromActiveDomain(registry);
4241
- }
4242
4322
  // Return hub that lives on a global object
4243
4323
  return getHubFromCarrier(registry);
4244
4324
  }
4245
4325
 
4246
- /**
4247
- * Try to read the hub from an active domain, and fallback to the registry if one doesn't exist
4248
- * @returns discovered hub
4249
- */
4250
- function getHubFromActiveDomain(registry) {
4251
- try {
4252
- const sentry = getMainCarrier().__SENTRY__;
4253
- const activeDomain = sentry && sentry.extensions && sentry.extensions.domain && sentry.extensions.domain.active;
4254
-
4255
- // If there's no active domain, just return global hub
4256
- if (!activeDomain) {
4257
- return getHubFromCarrier(registry);
4258
- }
4259
-
4260
- // If there's no hub on current domain, or it's an old API, assign a new one
4261
- if (!hasHubOnCarrier(activeDomain) || getHubFromCarrier(activeDomain).isOlderThan(API_VERSION)) {
4262
- const registryHubTopStack = getHubFromCarrier(registry).getStackTop();
4263
- setHubOnCarrier(activeDomain, new Hub(registryHubTopStack.client, Scope.clone(registryHubTopStack.scope)));
4264
- }
4265
-
4266
- // Return hub that lives on a domain
4267
- return getHubFromCarrier(activeDomain);
4268
- } catch (_Oo) {
4269
- // Return hub that lives on a global object
4270
- return getHubFromCarrier(registry);
4271
- }
4272
- }
4273
-
4274
4326
  /**
4275
4327
  * This will tell whether a carrier has a hub on it or not
4276
4328
  * @param carrier object
@@ -4344,6 +4396,69 @@ var SpanStatus; (function (SpanStatus) {
4344
4396
  const DataLoss = 'data_loss'; SpanStatus["DataLoss"] = DataLoss;
4345
4397
  })(SpanStatus || (SpanStatus = {}));
4346
4398
 
4399
+ /**
4400
+ * Wraps a function with a transaction/span and finishes the span after the function is done.
4401
+ *
4402
+ * Note that if you have not enabled tracing extensions via `addTracingExtensions`, this function
4403
+ * will not generate spans, and the `span` returned from the callback may be undefined.
4404
+ *
4405
+ * This function is meant to be used internally and may break at any time. Use at your own risk.
4406
+ *
4407
+ * @internal
4408
+ * @private
4409
+ */
4410
+ function trace(
4411
+ context,
4412
+ callback,
4413
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
4414
+ onError = () => {},
4415
+ ) {
4416
+ const ctx = { ...context };
4417
+ // If a name is set and a description is not, set the description to the name.
4418
+ if (ctx.name !== undefined && ctx.description === undefined) {
4419
+ ctx.description = ctx.name;
4420
+ }
4421
+
4422
+ const hub = getCurrentHub();
4423
+ const scope = hub.getScope();
4424
+
4425
+ const parentSpan = scope.getSpan();
4426
+ const activeSpan = parentSpan ? parentSpan.startChild(ctx) : hub.startTransaction(ctx);
4427
+ scope.setSpan(activeSpan);
4428
+
4429
+ function finishAndSetSpan() {
4430
+ activeSpan && activeSpan.finish();
4431
+ hub.getScope().setSpan(parentSpan);
4432
+ }
4433
+
4434
+ let maybePromiseResult;
4435
+ try {
4436
+ maybePromiseResult = callback(activeSpan);
4437
+ } catch (e) {
4438
+ activeSpan && activeSpan.setStatus('internal_error');
4439
+ onError(e);
4440
+ finishAndSetSpan();
4441
+ throw e;
4442
+ }
4443
+
4444
+ if (isThenable(maybePromiseResult)) {
4445
+ Promise.resolve(maybePromiseResult).then(
4446
+ () => {
4447
+ finishAndSetSpan();
4448
+ },
4449
+ e => {
4450
+ activeSpan && activeSpan.setStatus('internal_error');
4451
+ onError(e);
4452
+ finishAndSetSpan();
4453
+ },
4454
+ );
4455
+ } else {
4456
+ finishAndSetSpan();
4457
+ }
4458
+
4459
+ return maybePromiseResult;
4460
+ }
4461
+
4347
4462
  // Note: All functions in this file are typed with a return value of `ReturnType<Hub[HUB_FUNCTION]>`,
4348
4463
  // where HUB_FUNCTION is some method on the Hub class.
4349
4464
  //
@@ -4685,7 +4800,11 @@ function prepareEvent(
4685
4800
 
4686
4801
  applyClientOptions(prepared, options);
4687
4802
  applyIntegrationsMetadata(prepared, integrations);
4688
- applyDebugMetadata(prepared, options.stackParser);
4803
+
4804
+ // Only apply debug metadata to error events.
4805
+ if (event.type === undefined) {
4806
+ applyDebugMetadata(prepared, options.stackParser);
4807
+ }
4689
4808
 
4690
4809
  // If we have scope given to us, use it as the base for further modifications.
4691
4810
  // This allows us to prevent unnecessary copying of data if `captureContext` is not provided.
@@ -4762,6 +4881,8 @@ function applyClientOptions(event, options) {
4762
4881
  }
4763
4882
  }
4764
4883
 
4884
+ const debugIdStackParserCache = new WeakMap();
4885
+
4765
4886
  /**
4766
4887
  * Applies debug metadata images to the event in order to apply source maps by looking up their debug ID.
4767
4888
  */
@@ -4772,10 +4893,28 @@ function applyDebugMetadata(event, stackParser) {
4772
4893
  return;
4773
4894
  }
4774
4895
 
4896
+ let debugIdStackFramesCache;
4897
+ const cachedDebugIdStackFrameCache = debugIdStackParserCache.get(stackParser);
4898
+ if (cachedDebugIdStackFrameCache) {
4899
+ debugIdStackFramesCache = cachedDebugIdStackFrameCache;
4900
+ } else {
4901
+ debugIdStackFramesCache = new Map();
4902
+ debugIdStackParserCache.set(stackParser, debugIdStackFramesCache);
4903
+ }
4904
+
4775
4905
  // Build a map of filename -> debug_id
4776
4906
  const filenameDebugIdMap = Object.keys(debugIdMap).reduce((acc, debugIdStackTrace) => {
4777
- const parsedStack = stackParser(debugIdStackTrace);
4778
- for (const stackFrame of parsedStack) {
4907
+ let parsedStack;
4908
+ const cachedParsedStack = debugIdStackFramesCache.get(debugIdStackTrace);
4909
+ if (cachedParsedStack) {
4910
+ parsedStack = cachedParsedStack;
4911
+ } else {
4912
+ parsedStack = stackParser(debugIdStackTrace);
4913
+ debugIdStackFramesCache.set(debugIdStackTrace, parsedStack);
4914
+ }
4915
+
4916
+ for (let i = parsedStack.length - 1; i >= 0; i--) {
4917
+ const stackFrame = parsedStack[i];
4779
4918
  if (stackFrame.filename) {
4780
4919
  acc[stackFrame.filename] = debugIdMap[debugIdStackTrace];
4781
4920
  break;
@@ -5680,7 +5819,7 @@ function getEventForEnvelopeItem(item, type) {
5680
5819
  return Array.isArray(item) ? (item )[1] : undefined;
5681
5820
  }
5682
5821
 
5683
- const SDK_VERSION = '7.46.0';
5822
+ const SDK_VERSION = '7.52.1';
5684
5823
 
5685
5824
  let originalFunctionToString;
5686
5825
 
@@ -5703,11 +5842,17 @@ class FunctionToString {constructor() { FunctionToString.prototype.__init.call(
5703
5842
  // eslint-disable-next-line @typescript-eslint/unbound-method
5704
5843
  originalFunctionToString = Function.prototype.toString;
5705
5844
 
5706
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
5707
- Function.prototype.toString = function ( ...args) {
5708
- const context = getOriginalFunction(this) || this;
5709
- return originalFunctionToString.apply(context, args);
5710
- };
5845
+ // intrinsics (like Function.prototype) might be immutable in some environments
5846
+ // e.g. Node with --frozen-intrinsics, XS (an embedded JavaScript engine) or SES (a JavaScript proposal)
5847
+ try {
5848
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5849
+ Function.prototype.toString = function ( ...args) {
5850
+ const context = getOriginalFunction(this) || this;
5851
+ return originalFunctionToString.apply(context, args);
5852
+ };
5853
+ } catch (e) {
5854
+ // ignore errors here, just don't patch this
5855
+ }
5711
5856
  }
5712
5857
  } FunctionToString.__initStatic();
5713
5858
 
@@ -5855,8 +6000,9 @@ function _getPossibleEventMessages(event) {
5855
6000
  return [event.message];
5856
6001
  }
5857
6002
  if (event.exception) {
6003
+ const { values } = event.exception;
5858
6004
  try {
5859
- const { type = '', value = '' } = (event.exception.values && event.exception.values[0]) || {};
6005
+ const { type = '', value = '' } = (values && values[values.length - 1]) || {};
5860
6006
  return [`${value}`, `${type}: ${value}`];
5861
6007
  } catch (oO) {
5862
6008
  (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error(`Cannot extract message for event ${getEventDescription(event)}`);
@@ -6515,12 +6661,14 @@ function _consoleBreadcrumb(handlerData) {
6515
6661
  function _xhrBreadcrumb(handlerData) {
6516
6662
  const { startTimestamp, endTimestamp } = handlerData;
6517
6663
 
6664
+ const sentryXhrData = handlerData.xhr[SENTRY_XHR_DATA_KEY];
6665
+
6518
6666
  // We only capture complete, non-sentry requests
6519
- if (!startTimestamp || !endTimestamp || !handlerData.xhr.__sentry_xhr__) {
6667
+ if (!startTimestamp || !endTimestamp || !sentryXhrData) {
6520
6668
  return;
6521
6669
  }
6522
6670
 
6523
- const { method, url, status_code, body } = handlerData.xhr.__sentry_xhr__;
6671
+ const { method, url, status_code, body } = sentryXhrData;
6524
6672
 
6525
6673
  const data = {
6526
6674
  method,
@@ -6638,6 +6786,43 @@ function _isEvent(event) {
6638
6786
  return event && !!(event ).target;
6639
6787
  }
6640
6788
 
6789
+ /**
6790
+ * Creates an envelope from a user feedback.
6791
+ */
6792
+ function createUserFeedbackEnvelope(
6793
+ feedback,
6794
+ {
6795
+ metadata,
6796
+ tunnel,
6797
+ dsn,
6798
+ }
6799
+
6800
+ ,
6801
+ ) {
6802
+ const headers = {
6803
+ event_id: feedback.event_id,
6804
+ sent_at: new Date().toISOString(),
6805
+ ...(metadata &&
6806
+ metadata.sdk && {
6807
+ sdk: {
6808
+ name: metadata.sdk.name,
6809
+ version: metadata.sdk.version,
6810
+ },
6811
+ }),
6812
+ ...(!!tunnel && !!dsn && { dsn: dsnToString(dsn) }),
6813
+ };
6814
+ const item = createUserFeedbackEnvelopeItem(feedback);
6815
+
6816
+ return createEnvelope(headers, [item]);
6817
+ }
6818
+
6819
+ function createUserFeedbackEnvelopeItem(feedback) {
6820
+ const feedbackHeaders = {
6821
+ type: 'user_report',
6822
+ };
6823
+ return [feedbackHeaders, feedback];
6824
+ }
6825
+
6641
6826
  /**
6642
6827
  * Configuration options for the Sentry Browser SDK.
6643
6828
  * @see @sentry/types Options for more information.
@@ -6720,6 +6905,23 @@ class BrowserClient extends BaseClient {
6720
6905
  super.sendEvent(event, hint);
6721
6906
  }
6722
6907
 
6908
+ /**
6909
+ * Sends user feedback to Sentry.
6910
+ */
6911
+ captureUserFeedback(feedback) {
6912
+ if (!this._isEnabled()) {
6913
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.warn('SDK not enabled, will not capture user feedback.');
6914
+ return;
6915
+ }
6916
+
6917
+ const envelope = createUserFeedbackEnvelope(feedback, {
6918
+ metadata: this.getSdkMetadata(),
6919
+ dsn: this.getDsn(),
6920
+ tunnel: this.getOptions().tunnel,
6921
+ });
6922
+ void this._sendEnvelope(envelope);
6923
+ }
6924
+
6723
6925
  /**
6724
6926
  * @inheritDoc
6725
6927
  */
@@ -6962,7 +7164,7 @@ function createFrame(filename, func, lineno, colno) {
6962
7164
 
6963
7165
  // Chromium based browsers: Chrome, Brave, new Opera, new Edge
6964
7166
  const chromeRegex =
6965
- /^\s*at (?:(.*\).*?|.*?) ?\((?:address at )?)?(?:async )?((?:file|https?|blob|chrome-extension|address|native|eval|webpack|<anonymous>|[-a-z]+:|.*bundle|\/)?.*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i;
7167
+ /^\s*at (?:(.+?\)(?: \[.+\])?|.*?) ?\((?:address at )?)?(?:async )?((?:<anonymous>|[-a-z]+:|.*bundle|\/)?.*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i;
6966
7168
  const chromeEvalRegex = /\((\S*)(?::(\d+))(?::(\d+))\)/;
6967
7169
 
6968
7170
  const chrome = line => {
@@ -6998,7 +7200,7 @@ const chromeStackLineParser = [CHROME_PRIORITY, chrome];
6998
7200
  // generates filenames without a prefix like `file://` the filenames in the stacktrace are just 42.js
6999
7201
  // We need this specific case for now because we want no other regex to match.
7000
7202
  const geckoREgex =
7001
- /^\s*(.*?)(?:\((.*?)\))?(?:^|@)?((?:file|https?|blob|chrome|webpack|resource|moz-extension|safari-extension|safari-web-extension|capacitor)?:\/.*?|\[native code\]|[^@]*(?:bundle|\d+\.js)|\/[\w\-. /=]+)(?::(\d+))?(?::(\d+))?\s*$/i;
7203
+ /^\s*(.*?)(?:\((.*?)\))?(?:^|@)?((?:[-a-z]+)?:\/.*?|\[native code\]|[^@]*(?:bundle|\d+\.js)|\/[\w\-. /=]+)(?::(\d+))?(?::(\d+))?\s*$/i;
7002
7204
  const geckoEvalRegex = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i;
7003
7205
 
7004
7206
  const gecko = line => {
@@ -7030,8 +7232,7 @@ const gecko = line => {
7030
7232
 
7031
7233
  const geckoStackLineParser = [GECKO_PRIORITY, gecko];
7032
7234
 
7033
- const winjsRegex =
7034
- /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i;
7235
+ const winjsRegex = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:[-a-z]+):.*?):(\d+)(?::(\d+))?\)?\s*$/i;
7035
7236
 
7036
7237
  const winjs = line => {
7037
7238
  const parts = winjsRegex.exec(line);
@@ -8096,11 +8297,14 @@ const REPLAY_SESSION_KEY = 'sentryReplaySession';
8096
8297
  const REPLAY_EVENT_NAME = 'replay_event';
8097
8298
  const UNABLE_TO_SEND_REPLAY = 'Unable to send Replay';
8098
8299
 
8099
- // The idle limit for a session
8100
- const SESSION_IDLE_DURATION = 300000; // 5 minutes in ms
8300
+ // The idle limit for a session after which recording is paused.
8301
+ const SESSION_IDLE_PAUSE_DURATION = 300000; // 5 minutes in ms
8302
+
8303
+ // The idle limit for a session after which the session expires.
8304
+ const SESSION_IDLE_EXPIRE_DURATION = 900000; // 15 minutes in ms
8101
8305
 
8102
8306
  // The maximum length of a session
8103
- const MAX_SESSION_LIFE = 3600000; // 60 minutes
8307
+ const MAX_SESSION_LIFE = 3600000; // 60 minutes in ms
8104
8308
 
8105
8309
  /** Default flush delays */
8106
8310
  const DEFAULT_FLUSH_MIN_DELAY = 5000;
@@ -8109,13 +8313,16 @@ const DEFAULT_FLUSH_MIN_DELAY = 5000;
8109
8313
  const DEFAULT_FLUSH_MAX_DELAY = 5500;
8110
8314
 
8111
8315
  /* How long to wait for error checkouts */
8112
- const ERROR_CHECKOUT_TIME = 60000;
8316
+ const BUFFER_CHECKOUT_TIME = 60000;
8113
8317
 
8114
8318
  const RETRY_BASE_INTERVAL = 5000;
8115
8319
  const RETRY_MAX_COUNT = 3;
8116
8320
 
8117
- /* The max (uncompressed) size in bytes of a network body. Any body larger than this will be dropped. */
8118
- const NETWORK_BODY_MAX_SIZE = 300000;
8321
+ /* The max (uncompressed) size in bytes of a network body. Any body larger than this will be truncated. */
8322
+ const NETWORK_BODY_MAX_SIZE = 150000;
8323
+
8324
+ /* The max size of a single console arg that is captured. Any arg larger than this will be truncated. */
8325
+ const CONSOLE_ARG_MAX_SIZE = 5000;
8119
8326
 
8120
8327
  var NodeType$1;
8121
8328
  (function (NodeType) {
@@ -8152,7 +8359,7 @@ function maskInputValue({ input, maskInputSelector, unmaskInputSelector, maskInp
8152
8359
  if (unmaskInputSelector && input.matches(unmaskInputSelector)) {
8153
8360
  return text;
8154
8361
  }
8155
- if (input.hasAttribute('rr_is_password')) {
8362
+ if (input.hasAttribute('data-rr-is-password')) {
8156
8363
  type = 'password';
8157
8364
  }
8158
8365
  if (isInputTypeMasked({ maskInputOptions, tagName, type }) ||
@@ -8185,6 +8392,21 @@ function is2DCanvasBlank(canvas) {
8185
8392
  }
8186
8393
  return true;
8187
8394
  }
8395
+ function getInputType(element) {
8396
+ const type = element.type;
8397
+ return element.hasAttribute('data-rr-is-password')
8398
+ ? 'password'
8399
+ : type
8400
+ ? type.toLowerCase()
8401
+ : null;
8402
+ }
8403
+ function getInputValue(el, tagName, type) {
8404
+ typeof type === 'string' ? type.toLowerCase() : '';
8405
+ if (tagName === 'INPUT' && (type === 'radio' || type === 'checkbox')) {
8406
+ return el.getAttribute('value') || '';
8407
+ }
8408
+ return el.value;
8409
+ }
8188
8410
 
8189
8411
  let _id = 1;
8190
8412
  const tagNameRegex = new RegExp('[^a-z0-9-_:]');
@@ -8223,6 +8445,13 @@ function getCssRuleString(rule) {
8223
8445
  catch (_a) {
8224
8446
  }
8225
8447
  }
8448
+ return validateStringifiedCssRule(cssStringified);
8449
+ }
8450
+ function validateStringifiedCssRule(cssStringified) {
8451
+ if (cssStringified.indexOf(':') > -1) {
8452
+ const regex = /(\[(?:[\w-]+)[^\\])(:(?:[\w-]+)\])/gm;
8453
+ return cssStringified.replace(regex, '$1\\$2');
8454
+ }
8226
8455
  return cssStringified;
8227
8456
  }
8228
8457
  function isCSSImportRule(rule) {
@@ -8231,7 +8460,7 @@ function isCSSImportRule(rule) {
8231
8460
  function stringifyStyleSheet(sheet) {
8232
8461
  return sheet.cssRules
8233
8462
  ? Array.from(sheet.cssRules)
8234
- .map((rule) => rule.cssText || '')
8463
+ .map((rule) => rule.cssText ? validateStringifiedCssRule(rule.cssText) : '')
8235
8464
  .join('')
8236
8465
  : '';
8237
8466
  }
@@ -8566,14 +8795,15 @@ function serializeNode(n, options) {
8566
8795
  tagName === 'select' ||
8567
8796
  tagName === 'option') {
8568
8797
  const el = n;
8569
- const value = getInputValue(tagName, el, attributes);
8798
+ const type = getInputType(el);
8799
+ const value = getInputValue(el, tagName.toUpperCase(), type);
8570
8800
  const checked = n.checked;
8571
- if (attributes.type !== 'submit' &&
8572
- attributes.type !== 'button' &&
8801
+ if (type !== 'submit' &&
8802
+ type !== 'button' &&
8573
8803
  value) {
8574
8804
  attributes.value = maskInputValue({
8575
8805
  input: el,
8576
- type: attributes.type,
8806
+ type,
8577
8807
  tagName,
8578
8808
  value,
8579
8809
  maskInputSelector,
@@ -9046,15 +9276,8 @@ function snapshot(n, options) {
9046
9276
  function skipAttribute(tagName, attributeName, value) {
9047
9277
  return ((tagName === 'video' || tagName === 'audio') && attributeName === 'autoplay');
9048
9278
  }
9049
- function getInputValue(tagName, el, attributes) {
9050
- if (tagName === 'input' &&
9051
- (attributes.type === 'radio' || attributes.type === 'checkbox')) {
9052
- return el.getAttribute('value') || '';
9053
- }
9054
- return el.value;
9055
- }
9056
9279
 
9057
- var EventType;
9280
+ var EventType$1;
9058
9281
  (function (EventType) {
9059
9282
  EventType[EventType["DomContentLoaded"] = 0] = "DomContentLoaded";
9060
9283
  EventType[EventType["Load"] = 1] = "Load";
@@ -9063,7 +9286,7 @@ var EventType;
9063
9286
  EventType[EventType["Meta"] = 4] = "Meta";
9064
9287
  EventType[EventType["Custom"] = 5] = "Custom";
9065
9288
  EventType[EventType["Plugin"] = 6] = "Plugin";
9066
- })(EventType || (EventType = {}));
9289
+ })(EventType$1 || (EventType$1 = {}));
9067
9290
  var IncrementalSource;
9068
9291
  (function (IncrementalSource) {
9069
9292
  IncrementalSource[IncrementalSource["Mutation"] = 0] = "Mutation";
@@ -9678,9 +9901,9 @@ class MutationBuffer {
9678
9901
  this.attributes.push(item);
9679
9902
  }
9680
9903
  if (m.attributeName === 'type' &&
9681
- m.target.tagName === 'INPUT' &&
9904
+ target.tagName === 'INPUT' &&
9682
9905
  (m.oldValue || '').toLowerCase() === 'password') {
9683
- m.target.setAttribute('rr_is_password', 'true');
9906
+ target.setAttribute('data-rr-is-password', 'true');
9684
9907
  }
9685
9908
  if (m.attributeName === 'style') {
9686
9909
  const old = this.doc.createElement('span');
@@ -10077,27 +10300,25 @@ function initInputObserver({ inputCb, doc, mirror, blockClass, blockSelector, un
10077
10300
  isBlocked(target, blockClass, blockSelector, unblockSelector)) {
10078
10301
  return;
10079
10302
  }
10080
- let type = target.type;
10081
- if (target.classList.contains(ignoreClass) ||
10082
- (ignoreSelector && target.matches(ignoreSelector))) {
10303
+ const el = target;
10304
+ const type = getInputType(el);
10305
+ if (el.classList.contains(ignoreClass) ||
10306
+ (ignoreSelector && el.matches(ignoreSelector))) {
10083
10307
  return;
10084
10308
  }
10085
- let text = target.value;
10309
+ let text = getInputValue(el, tagName, type);
10086
10310
  let isChecked = false;
10087
- if (target.hasAttribute('rr_is_password')) {
10088
- type = 'password';
10089
- }
10090
10311
  if (type === 'radio' || type === 'checkbox') {
10091
10312
  isChecked = target.checked;
10092
10313
  }
10093
- else if (hasInputMaskOptions({
10314
+ if (hasInputMaskOptions({
10094
10315
  maskInputOptions,
10095
10316
  maskInputSelector,
10096
10317
  tagName,
10097
10318
  type,
10098
10319
  })) {
10099
10320
  text = maskInputValue({
10100
- input: target,
10321
+ input: el,
10101
10322
  maskInputOptions,
10102
10323
  maskInputSelector,
10103
10324
  unmaskInputSelector,
@@ -10114,8 +10335,18 @@ function initInputObserver({ inputCb, doc, mirror, blockClass, blockSelector, un
10114
10335
  .querySelectorAll(`input[type="radio"][name="${name}"]`)
10115
10336
  .forEach((el) => {
10116
10337
  if (el !== target) {
10338
+ const text = maskInputValue({
10339
+ input: el,
10340
+ maskInputOptions,
10341
+ maskInputSelector,
10342
+ unmaskInputSelector,
10343
+ tagName,
10344
+ type,
10345
+ value: getInputValue(el, tagName, type),
10346
+ maskInputFn,
10347
+ });
10117
10348
  cbWithDedup(el, callbackWrapper(wrapEventWithUserTriggeredFlag)({
10118
- text: el.value,
10349
+ text,
10119
10350
  isChecked: !isChecked,
10120
10351
  userTriggered: false,
10121
10352
  }, userTriggeredOnInput));
@@ -11028,17 +11259,17 @@ function record(options = {}) {
11028
11259
  wrappedEmit = (e, isCheckout) => {
11029
11260
  var _a;
11030
11261
  if (((_a = mutationBuffers[0]) === null || _a === void 0 ? void 0 : _a.isFrozen()) &&
11031
- e.type !== EventType.FullSnapshot &&
11032
- !(e.type === EventType.IncrementalSnapshot &&
11262
+ e.type !== EventType$1.FullSnapshot &&
11263
+ !(e.type === EventType$1.IncrementalSnapshot &&
11033
11264
  e.data.source === IncrementalSource.Mutation)) {
11034
11265
  mutationBuffers.forEach((buf) => buf.unfreeze());
11035
11266
  }
11036
11267
  emit(eventProcessor(e), isCheckout);
11037
- if (e.type === EventType.FullSnapshot) {
11268
+ if (e.type === EventType$1.FullSnapshot) {
11038
11269
  lastFullSnapshotEvent = e;
11039
11270
  incrementalSnapshotCount = 0;
11040
11271
  }
11041
- else if (e.type === EventType.IncrementalSnapshot) {
11272
+ else if (e.type === EventType$1.IncrementalSnapshot) {
11042
11273
  if (e.data.source === IncrementalSource.Mutation &&
11043
11274
  e.data.isAttachIframe) {
11044
11275
  return;
@@ -11054,16 +11285,16 @@ function record(options = {}) {
11054
11285
  };
11055
11286
  const wrappedMutationEmit = (m) => {
11056
11287
  wrappedEmit(wrapEvent({
11057
- type: EventType.IncrementalSnapshot,
11288
+ type: EventType$1.IncrementalSnapshot,
11058
11289
  data: Object.assign({ source: IncrementalSource.Mutation }, m),
11059
11290
  }));
11060
11291
  };
11061
11292
  const wrappedScrollEmit = (p) => wrappedEmit(wrapEvent({
11062
- type: EventType.IncrementalSnapshot,
11293
+ type: EventType$1.IncrementalSnapshot,
11063
11294
  data: Object.assign({ source: IncrementalSource.Scroll }, p),
11064
11295
  }));
11065
11296
  const wrappedCanvasMutationEmit = (p) => wrappedEmit(wrapEvent({
11066
- type: EventType.IncrementalSnapshot,
11297
+ type: EventType$1.IncrementalSnapshot,
11067
11298
  data: Object.assign({ source: IncrementalSource.CanvasMutation }, p),
11068
11299
  }));
11069
11300
  const iframeManager = new IframeManager({
@@ -11108,7 +11339,7 @@ function record(options = {}) {
11108
11339
  takeFullSnapshot = (isCheckout = false) => {
11109
11340
  var _a, _b, _c, _d;
11110
11341
  wrappedEmit(wrapEvent({
11111
- type: EventType.Meta,
11342
+ type: EventType$1.Meta,
11112
11343
  data: {
11113
11344
  href: window.location.href,
11114
11345
  width: getWindowWidth(),
@@ -11151,7 +11382,7 @@ function record(options = {}) {
11151
11382
  }
11152
11383
  mirror.map = idNodeMap;
11153
11384
  wrappedEmit(wrapEvent({
11154
- type: EventType.FullSnapshot,
11385
+ type: EventType$1.FullSnapshot,
11155
11386
  data: {
11156
11387
  node,
11157
11388
  initialOffset: {
@@ -11176,7 +11407,7 @@ function record(options = {}) {
11176
11407
  const handlers = [];
11177
11408
  handlers.push(on$1('DOMContentLoaded', () => {
11178
11409
  wrappedEmit(wrapEvent({
11179
- type: EventType.DomContentLoaded,
11410
+ type: EventType$1.DomContentLoaded,
11180
11411
  data: {},
11181
11412
  }));
11182
11413
  }));
@@ -11186,40 +11417,40 @@ function record(options = {}) {
11186
11417
  onMutation,
11187
11418
  mutationCb: wrappedMutationEmit,
11188
11419
  mousemoveCb: (positions, source) => wrappedEmit(wrapEvent({
11189
- type: EventType.IncrementalSnapshot,
11420
+ type: EventType$1.IncrementalSnapshot,
11190
11421
  data: {
11191
11422
  source,
11192
11423
  positions,
11193
11424
  },
11194
11425
  })),
11195
11426
  mouseInteractionCb: (d) => wrappedEmit(wrapEvent({
11196
- type: EventType.IncrementalSnapshot,
11427
+ type: EventType$1.IncrementalSnapshot,
11197
11428
  data: Object.assign({ source: IncrementalSource.MouseInteraction }, d),
11198
11429
  })),
11199
11430
  scrollCb: wrappedScrollEmit,
11200
11431
  viewportResizeCb: (d) => wrappedEmit(wrapEvent({
11201
- type: EventType.IncrementalSnapshot,
11432
+ type: EventType$1.IncrementalSnapshot,
11202
11433
  data: Object.assign({ source: IncrementalSource.ViewportResize }, d),
11203
11434
  })),
11204
11435
  inputCb: (v) => wrappedEmit(wrapEvent({
11205
- type: EventType.IncrementalSnapshot,
11436
+ type: EventType$1.IncrementalSnapshot,
11206
11437
  data: Object.assign({ source: IncrementalSource.Input }, v),
11207
11438
  })),
11208
11439
  mediaInteractionCb: (p) => wrappedEmit(wrapEvent({
11209
- type: EventType.IncrementalSnapshot,
11440
+ type: EventType$1.IncrementalSnapshot,
11210
11441
  data: Object.assign({ source: IncrementalSource.MediaInteraction }, p),
11211
11442
  })),
11212
11443
  styleSheetRuleCb: (r) => wrappedEmit(wrapEvent({
11213
- type: EventType.IncrementalSnapshot,
11444
+ type: EventType$1.IncrementalSnapshot,
11214
11445
  data: Object.assign({ source: IncrementalSource.StyleSheetRule }, r),
11215
11446
  })),
11216
11447
  styleDeclarationCb: (r) => wrappedEmit(wrapEvent({
11217
- type: EventType.IncrementalSnapshot,
11448
+ type: EventType$1.IncrementalSnapshot,
11218
11449
  data: Object.assign({ source: IncrementalSource.StyleDeclaration }, r),
11219
11450
  })),
11220
11451
  canvasMutationCb: wrappedCanvasMutationEmit,
11221
11452
  fontCb: (p) => wrappedEmit(wrapEvent({
11222
- type: EventType.IncrementalSnapshot,
11453
+ type: EventType$1.IncrementalSnapshot,
11223
11454
  data: Object.assign({ source: IncrementalSource.Font }, p),
11224
11455
  })),
11225
11456
  blockClass,
@@ -11252,7 +11483,7 @@ function record(options = {}) {
11252
11483
  observer: p.observer,
11253
11484
  options: p.options,
11254
11485
  callback: (payload) => wrappedEmit(wrapEvent({
11255
- type: EventType.Plugin,
11486
+ type: EventType$1.Plugin,
11256
11487
  data: {
11257
11488
  plugin: p.name,
11258
11489
  payload,
@@ -11280,7 +11511,7 @@ function record(options = {}) {
11280
11511
  else {
11281
11512
  handlers.push(on$1('load', () => {
11282
11513
  wrappedEmit(wrapEvent({
11283
- type: EventType.Load,
11514
+ type: EventType$1.Load,
11284
11515
  data: {},
11285
11516
  }));
11286
11517
  init();
@@ -11299,7 +11530,7 @@ record.addCustomEvent = (tag, payload) => {
11299
11530
  throw new Error('please add custom event after start recording');
11300
11531
  }
11301
11532
  wrappedEmit(wrapEvent({
11302
- type: EventType.Custom,
11533
+ type: EventType$1.Custom,
11303
11534
  data: {
11304
11535
  tag,
11305
11536
  payload,
@@ -11317,6 +11548,475 @@ record.takeFullSnapshot = (isCheckout) => {
11317
11548
  };
11318
11549
  record.mirror = mirror;
11319
11550
 
11551
+ /**
11552
+ * Create a breadcrumb for a replay.
11553
+ */
11554
+ function createBreadcrumb(
11555
+ breadcrumb,
11556
+ ) {
11557
+ return {
11558
+ timestamp: Date.now() / 1000,
11559
+ type: 'default',
11560
+ ...breadcrumb,
11561
+ };
11562
+ }
11563
+
11564
+ var NodeType;
11565
+ (function (NodeType) {
11566
+ NodeType[NodeType["Document"] = 0] = "Document";
11567
+ NodeType[NodeType["DocumentType"] = 1] = "DocumentType";
11568
+ NodeType[NodeType["Element"] = 2] = "Element";
11569
+ NodeType[NodeType["Text"] = 3] = "Text";
11570
+ NodeType[NodeType["CDATA"] = 4] = "CDATA";
11571
+ NodeType[NodeType["Comment"] = 5] = "Comment";
11572
+ })(NodeType || (NodeType = {}));
11573
+
11574
+ /**
11575
+ * Converts a timestamp to ms, if it was in s, or keeps it as ms.
11576
+ */
11577
+ function timestampToMs(timestamp) {
11578
+ const isMs = timestamp > 9999999999;
11579
+ return isMs ? timestamp : timestamp * 1000;
11580
+ }
11581
+
11582
+ /**
11583
+ * Add an event to the event buffer.
11584
+ * `isCheckout` is true if this is either the very first event, or an event triggered by `checkoutEveryNms`.
11585
+ */
11586
+ async function addEvent(
11587
+ replay,
11588
+ event,
11589
+ isCheckout,
11590
+ ) {
11591
+ if (!replay.eventBuffer) {
11592
+ // This implies that `_isEnabled` is false
11593
+ return null;
11594
+ }
11595
+
11596
+ if (replay.isPaused()) {
11597
+ // Do not add to event buffer when recording is paused
11598
+ return null;
11599
+ }
11600
+
11601
+ const timestampInMs = timestampToMs(event.timestamp);
11602
+
11603
+ // Throw out events that happen more than 5 minutes ago. This can happen if
11604
+ // page has been left open and idle for a long period of time and user
11605
+ // comes back to trigger a new session. The performance entries rely on
11606
+ // `performance.timeOrigin`, which is when the page first opened.
11607
+ if (timestampInMs + replay.timeouts.sessionIdlePause < Date.now()) {
11608
+ return null;
11609
+ }
11610
+
11611
+ try {
11612
+ if (isCheckout) {
11613
+ replay.eventBuffer.clear();
11614
+ }
11615
+
11616
+ return await replay.eventBuffer.addEvent(event);
11617
+ } catch (error) {
11618
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error(error);
11619
+ await replay.stop('addEvent');
11620
+
11621
+ const client = getCurrentHub().getClient();
11622
+
11623
+ if (client) {
11624
+ client.recordDroppedEvent('internal_sdk_error', 'replay');
11625
+ }
11626
+ }
11627
+ }
11628
+
11629
+ /**
11630
+ * Add a breadcrumb event to replay.
11631
+ */
11632
+ function addBreadcrumbEvent(replay, breadcrumb) {
11633
+ if (breadcrumb.category === 'sentry.transaction') {
11634
+ return;
11635
+ }
11636
+
11637
+ if (['ui.click', 'ui.input'].includes(breadcrumb.category )) {
11638
+ replay.triggerUserActivity();
11639
+ } else {
11640
+ replay.checkAndHandleExpiredSession();
11641
+ }
11642
+
11643
+ replay.addUpdate(() => {
11644
+ void addEvent(replay, {
11645
+ type: EventType$1.Custom,
11646
+ // TODO: We were converting from ms to seconds for breadcrumbs, spans,
11647
+ // but maybe we should just keep them as milliseconds
11648
+ timestamp: (breadcrumb.timestamp || 0) * 1000,
11649
+ data: {
11650
+ tag: 'breadcrumb',
11651
+ // normalize to max. 10 depth and 1_000 properties per object
11652
+ payload: normalize(breadcrumb, 10, 1000),
11653
+ },
11654
+ });
11655
+
11656
+ // Do not flush after console log messages
11657
+ return breadcrumb.category === 'console';
11658
+ });
11659
+ }
11660
+
11661
+ /**
11662
+ * Detect a slow click on a button/a tag,
11663
+ * and potentially create a corresponding breadcrumb.
11664
+ */
11665
+ function detectSlowClick(
11666
+ replay,
11667
+ config,
11668
+ clickBreadcrumb,
11669
+ node,
11670
+ ) {
11671
+ if (ignoreElement(node, config)) {
11672
+ return;
11673
+ }
11674
+
11675
+ /*
11676
+ We consider a slow click a click on a button/a, which does not trigger one of:
11677
+ - DOM mutation
11678
+ - Scroll (within 100ms)
11679
+ Within the given threshold time.
11680
+ After time timeout time, we stop listening and mark it as a slow click anyhow.
11681
+ */
11682
+
11683
+ let cleanup = () => {
11684
+ // replaced further down
11685
+ };
11686
+
11687
+ // After timeout time, def. consider this a slow click, and stop watching for mutations
11688
+ const timeout = setTimeout(() => {
11689
+ handleSlowClick(replay, clickBreadcrumb, config.timeout, 'timeout');
11690
+ cleanup();
11691
+ }, config.timeout);
11692
+
11693
+ const mutationHandler = () => {
11694
+ maybeHandleSlowClick(replay, clickBreadcrumb, config.threshold, config.timeout, 'mutation');
11695
+ cleanup();
11696
+ };
11697
+
11698
+ const scrollHandler = () => {
11699
+ maybeHandleSlowClick(replay, clickBreadcrumb, config.scrollTimeout, config.timeout, 'scroll');
11700
+ cleanup();
11701
+ };
11702
+
11703
+ const obs = new MutationObserver(mutationHandler);
11704
+
11705
+ obs.observe(WINDOW.document.documentElement, {
11706
+ attributes: true,
11707
+ characterData: true,
11708
+ childList: true,
11709
+ subtree: true,
11710
+ });
11711
+
11712
+ WINDOW.addEventListener('scroll', scrollHandler);
11713
+
11714
+ // Stop listening to scroll timeouts early
11715
+ const scrollTimeout = setTimeout(() => {
11716
+ WINDOW.removeEventListener('scroll', scrollHandler);
11717
+ }, config.scrollTimeout);
11718
+
11719
+ cleanup = () => {
11720
+ clearTimeout(timeout);
11721
+ clearTimeout(scrollTimeout);
11722
+ obs.disconnect();
11723
+ WINDOW.removeEventListener('scroll', scrollHandler);
11724
+ };
11725
+ }
11726
+
11727
+ function maybeHandleSlowClick(
11728
+ replay,
11729
+ clickBreadcrumb,
11730
+ threshold,
11731
+ timeout,
11732
+ endReason,
11733
+ ) {
11734
+ const now = Date.now();
11735
+ const timeAfterClickMs = now - clickBreadcrumb.timestamp * 1000;
11736
+
11737
+ if (timeAfterClickMs > threshold) {
11738
+ handleSlowClick(replay, clickBreadcrumb, Math.min(timeAfterClickMs, timeout), endReason);
11739
+ return true;
11740
+ }
11741
+
11742
+ return false;
11743
+ }
11744
+
11745
+ function handleSlowClick(
11746
+ replay,
11747
+ clickBreadcrumb,
11748
+ timeAfterClickMs,
11749
+ endReason,
11750
+ ) {
11751
+ const breadcrumb = {
11752
+ message: clickBreadcrumb.message,
11753
+ timestamp: clickBreadcrumb.timestamp,
11754
+ category: 'ui.slowClickDetected',
11755
+ data: {
11756
+ ...clickBreadcrumb.data,
11757
+ url: WINDOW.location.href,
11758
+ // TODO FN: add parametrized route, when possible
11759
+ timeAfterClickMs,
11760
+ endReason,
11761
+ },
11762
+ };
11763
+
11764
+ addBreadcrumbEvent(replay, breadcrumb);
11765
+ }
11766
+
11767
+ const SLOW_CLICK_IGNORE_TAGS = ['SELECT', 'OPTION'];
11768
+
11769
+ function ignoreElement(node, config) {
11770
+ // If <input> tag, we only want to consider input[type='submit'] & input[type='button']
11771
+ if (node.tagName === 'INPUT' && !['submit', 'button'].includes(node.getAttribute('type') || '')) {
11772
+ return true;
11773
+ }
11774
+
11775
+ if (SLOW_CLICK_IGNORE_TAGS.includes(node.tagName)) {
11776
+ return true;
11777
+ }
11778
+
11779
+ // If <a> tag, detect special variants that may not lead to an action
11780
+ // If target !== _self, we may open the link somewhere else, which would lead to no action
11781
+ // Also, when downloading a file, we may not leave the page, but still not trigger an action
11782
+ if (
11783
+ node.tagName === 'A' &&
11784
+ (node.hasAttribute('download') || (node.hasAttribute('target') && node.getAttribute('target') !== '_self'))
11785
+ ) {
11786
+ return true;
11787
+ }
11788
+
11789
+ if (config.ignoreSelector && node.matches(config.ignoreSelector)) {
11790
+ return true;
11791
+ }
11792
+
11793
+ return false;
11794
+ }
11795
+
11796
+ // Note that these are the serialized attributes and not attributes directly on
11797
+ // the DOM Node. Attributes we are interested in:
11798
+ const ATTRIBUTES_TO_RECORD = new Set([
11799
+ 'id',
11800
+ 'class',
11801
+ 'aria-label',
11802
+ 'role',
11803
+ 'name',
11804
+ 'alt',
11805
+ 'title',
11806
+ 'data-test-id',
11807
+ 'data-testid',
11808
+ ]);
11809
+
11810
+ /**
11811
+ * Inclusion list of attributes that we want to record from the DOM element
11812
+ */
11813
+ function getAttributesToRecord(attributes) {
11814
+ const obj = {};
11815
+ for (const key in attributes) {
11816
+ if (ATTRIBUTES_TO_RECORD.has(key)) {
11817
+ let normalizedKey = key;
11818
+
11819
+ if (key === 'data-testid' || key === 'data-test-id') {
11820
+ normalizedKey = 'testId';
11821
+ }
11822
+
11823
+ obj[normalizedKey] = attributes[key];
11824
+ }
11825
+ }
11826
+
11827
+ return obj;
11828
+ }
11829
+
11830
+ const handleDomListener = (
11831
+ replay,
11832
+ ) => {
11833
+ const slowClickExperiment = replay.getOptions()._experiments.slowClicks;
11834
+
11835
+ const slowClickConfig = slowClickExperiment
11836
+ ? {
11837
+ threshold: slowClickExperiment.threshold,
11838
+ timeout: slowClickExperiment.timeout,
11839
+ scrollTimeout: slowClickExperiment.scrollTimeout,
11840
+ ignoreSelector: slowClickExperiment.ignoreSelectors ? slowClickExperiment.ignoreSelectors.join(',') : '',
11841
+ }
11842
+ : undefined;
11843
+
11844
+ return (handlerData) => {
11845
+ if (!replay.isEnabled()) {
11846
+ return;
11847
+ }
11848
+
11849
+ const result = handleDom(handlerData);
11850
+
11851
+ if (!result) {
11852
+ return;
11853
+ }
11854
+
11855
+ const isClick = handlerData.name === 'click';
11856
+ const event = isClick && (handlerData.event );
11857
+ // Ignore clicks if ctrl/alt/meta keys are held down as they alter behavior of clicks (e.g. open in new tab)
11858
+ if (isClick && slowClickConfig && event && !event.altKey && !event.metaKey && !event.ctrlKey) {
11859
+ detectSlowClick(
11860
+ replay,
11861
+ slowClickConfig,
11862
+ result ,
11863
+ getClickTargetNode(handlerData.event) ,
11864
+ );
11865
+ }
11866
+
11867
+ addBreadcrumbEvent(replay, result);
11868
+ };
11869
+ };
11870
+
11871
+ /** Get the base DOM breadcrumb. */
11872
+ function getBaseDomBreadcrumb(target, message) {
11873
+ // `__sn` property is the serialized node created by rrweb
11874
+ const serializedNode = target && isRrwebNode(target) && target.__sn.type === NodeType.Element ? target.__sn : null;
11875
+
11876
+ return {
11877
+ message,
11878
+ data: serializedNode
11879
+ ? {
11880
+ nodeId: serializedNode.id,
11881
+ node: {
11882
+ id: serializedNode.id,
11883
+ tagName: serializedNode.tagName,
11884
+ textContent: target
11885
+ ? Array.from(target.childNodes)
11886
+ .map(
11887
+ (node) => '__sn' in node && node.__sn.type === NodeType.Text && node.__sn.textContent,
11888
+ )
11889
+ .filter(Boolean) // filter out empty values
11890
+ .map(text => (text ).trim())
11891
+ .join('')
11892
+ : '',
11893
+ attributes: getAttributesToRecord(serializedNode.attributes),
11894
+ },
11895
+ }
11896
+ : {},
11897
+ };
11898
+ }
11899
+
11900
+ /**
11901
+ * An event handler to react to DOM events.
11902
+ * Exported for tests.
11903
+ */
11904
+ function handleDom(handlerData) {
11905
+ const { target, message } = getDomTarget(handlerData);
11906
+
11907
+ return createBreadcrumb({
11908
+ category: `ui.${handlerData.name}`,
11909
+ ...getBaseDomBreadcrumb(target, message),
11910
+ });
11911
+ }
11912
+
11913
+ function getDomTarget(handlerData) {
11914
+ const isClick = handlerData.name === 'click';
11915
+
11916
+ let message;
11917
+ let target = null;
11918
+
11919
+ // Accessing event.target can throw (see getsentry/raven-js#838, #768)
11920
+ try {
11921
+ target = isClick ? getClickTargetNode(handlerData.event) : getTargetNode(handlerData.event);
11922
+ message = htmlTreeAsString(target, { maxStringLength: 200 }) || '<unknown>';
11923
+ } catch (e) {
11924
+ message = '<unknown>';
11925
+ }
11926
+
11927
+ return { target, message };
11928
+ }
11929
+
11930
+ function isRrwebNode(node) {
11931
+ return '__sn' in node;
11932
+ }
11933
+
11934
+ function getTargetNode(event) {
11935
+ if (isEventWithTarget(event)) {
11936
+ return event.target ;
11937
+ }
11938
+
11939
+ return event;
11940
+ }
11941
+
11942
+ const INTERACTIVE_SELECTOR = 'button,a';
11943
+
11944
+ // For clicks, we check if the target is inside of a button or link
11945
+ // If so, we use this as the target instead
11946
+ // This is useful because if you click on the image in <button><img></button>,
11947
+ // The target will be the image, not the button, which we don't want here
11948
+ function getClickTargetNode(event) {
11949
+ const target = getTargetNode(event);
11950
+
11951
+ if (!target || !(target instanceof Element)) {
11952
+ return target;
11953
+ }
11954
+
11955
+ const closestInteractive = target.closest(INTERACTIVE_SELECTOR);
11956
+ return closestInteractive || target;
11957
+ }
11958
+
11959
+ function isEventWithTarget(event) {
11960
+ return typeof event === 'object' && !!event && 'target' in event;
11961
+ }
11962
+
11963
+ /** Handle keyboard events & create breadcrumbs. */
11964
+ function handleKeyboardEvent(replay, event) {
11965
+ if (!replay.isEnabled()) {
11966
+ return;
11967
+ }
11968
+
11969
+ replay.triggerUserActivity();
11970
+
11971
+ const breadcrumb = getKeyboardBreadcrumb(event);
11972
+
11973
+ if (!breadcrumb) {
11974
+ return;
11975
+ }
11976
+
11977
+ addBreadcrumbEvent(replay, breadcrumb);
11978
+ }
11979
+
11980
+ /** exported only for tests */
11981
+ function getKeyboardBreadcrumb(event) {
11982
+ const { metaKey, shiftKey, ctrlKey, altKey, key, target } = event;
11983
+
11984
+ // never capture for input fields
11985
+ if (!target || isInputElement(target )) {
11986
+ return null;
11987
+ }
11988
+
11989
+ // Note: We do not consider shift here, as that means "uppercase"
11990
+ const hasModifierKey = metaKey || ctrlKey || altKey;
11991
+ const isCharacterKey = key.length === 1; // other keys like Escape, Tab, etc have a longer length
11992
+
11993
+ // Do not capture breadcrumb if only a word key is pressed
11994
+ // This could leak e.g. user input
11995
+ if (!hasModifierKey && isCharacterKey) {
11996
+ return null;
11997
+ }
11998
+
11999
+ const message = htmlTreeAsString(target, { maxStringLength: 200 }) || '<unknown>';
12000
+ const baseBreadcrumb = getBaseDomBreadcrumb(target , message);
12001
+
12002
+ return createBreadcrumb({
12003
+ category: 'ui.keyDown',
12004
+ message,
12005
+ data: {
12006
+ ...baseBreadcrumb.data,
12007
+ metaKey,
12008
+ shiftKey,
12009
+ ctrlKey,
12010
+ altKey,
12011
+ key,
12012
+ },
12013
+ });
12014
+ }
12015
+
12016
+ function isInputElement(target) {
12017
+ return target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable;
12018
+ }
12019
+
11320
12020
  const NAVIGATION_ENTRY_KEYS = [
11321
12021
  'name',
11322
12022
  'type',
@@ -11470,20 +12170,19 @@ class EventBufferArray {
11470
12170
  return this.events.length > 0;
11471
12171
  }
11472
12172
 
12173
+ /** @inheritdoc */
12174
+ get type() {
12175
+ return 'sync';
12176
+ }
12177
+
11473
12178
  /** @inheritdoc */
11474
12179
  destroy() {
11475
12180
  this.events = [];
11476
12181
  }
11477
12182
 
11478
12183
  /** @inheritdoc */
11479
- async addEvent(event, isCheckout) {
11480
- if (isCheckout) {
11481
- this.events = [event];
11482
- return;
11483
- }
11484
-
12184
+ async addEvent(event) {
11485
12185
  this.events.push(event);
11486
- return;
11487
12186
  }
11488
12187
 
11489
12188
  /** @inheritdoc */
@@ -11497,6 +12196,22 @@ class EventBufferArray {
11497
12196
  resolve(JSON.stringify(eventsRet));
11498
12197
  });
11499
12198
  }
12199
+
12200
+ /** @inheritdoc */
12201
+ clear() {
12202
+ this.events = [];
12203
+ }
12204
+
12205
+ /** @inheritdoc */
12206
+ getEarliestTimestamp() {
12207
+ const timestamp = this.events.map(event => event.timestamp).sort()[0];
12208
+
12209
+ if (!timestamp) {
12210
+ return null;
12211
+ }
12212
+
12213
+ return timestampToMs(timestamp);
12214
+ }
11500
12215
  }
11501
12216
 
11502
12217
  /**
@@ -11604,11 +12319,20 @@ class WorkerHandler {
11604
12319
  * Exported only for testing.
11605
12320
  */
11606
12321
  class EventBufferCompressionWorker {
11607
- /** @inheritdoc */
11608
12322
 
11609
12323
  constructor(worker) {
11610
12324
  this._worker = new WorkerHandler(worker);
11611
- this.hasEvents = false;
12325
+ this._earliestTimestamp = null;
12326
+ }
12327
+
12328
+ /** @inheritdoc */
12329
+ get hasEvents() {
12330
+ return !!this._earliestTimestamp;
12331
+ }
12332
+
12333
+ /** @inheritdoc */
12334
+ get type() {
12335
+ return 'worker';
11612
12336
  }
11613
12337
 
11614
12338
  /**
@@ -11631,13 +12355,10 @@ class EventBufferCompressionWorker {
11631
12355
  *
11632
12356
  * Returns true if event was successfuly received and processed by worker.
11633
12357
  */
11634
- async addEvent(event, isCheckout) {
11635
- this.hasEvents = true;
11636
-
11637
- if (isCheckout) {
11638
- // This event is a checkout, make sure worker buffer is cleared before
11639
- // proceeding.
11640
- await this._clear();
12358
+ addEvent(event) {
12359
+ const timestamp = timestampToMs(event.timestamp);
12360
+ if (!this._earliestTimestamp || timestamp < this._earliestTimestamp) {
12361
+ this._earliestTimestamp = timestamp;
11641
12362
  }
11642
12363
 
11643
12364
  return this._sendEventToWorker(event);
@@ -11650,6 +12371,18 @@ class EventBufferCompressionWorker {
11650
12371
  return this._finishRequest();
11651
12372
  }
11652
12373
 
12374
+ /** @inheritdoc */
12375
+ clear() {
12376
+ this._earliestTimestamp = null;
12377
+ // We do not wait on this, as we assume the order of messages is consistent for the worker
12378
+ void this._worker.postMessage('clear');
12379
+ }
12380
+
12381
+ /** @inheritdoc */
12382
+ getEarliestTimestamp() {
12383
+ return this._earliestTimestamp;
12384
+ }
12385
+
11653
12386
  /**
11654
12387
  * Send the event to the worker.
11655
12388
  */
@@ -11663,15 +12396,10 @@ class EventBufferCompressionWorker {
11663
12396
  async _finishRequest() {
11664
12397
  const response = await this._worker.postMessage('finish');
11665
12398
 
11666
- this.hasEvents = false;
12399
+ this._earliestTimestamp = null;
11667
12400
 
11668
12401
  return response;
11669
12402
  }
11670
-
11671
- /** Clear any pending events from the worker. */
11672
- _clear() {
11673
- return this._worker.postMessage('clear');
11674
- }
11675
12403
  }
11676
12404
 
11677
12405
  /**
@@ -11689,6 +12417,11 @@ class EventBufferProxy {
11689
12417
  this._ensureWorkerIsLoadedPromise = this._ensureWorkerIsLoaded();
11690
12418
  }
11691
12419
 
12420
+ /** @inheritdoc */
12421
+ get type() {
12422
+ return this._used.type;
12423
+ }
12424
+
11692
12425
  /** @inheritDoc */
11693
12426
  get hasEvents() {
11694
12427
  return this._used.hasEvents;
@@ -11700,13 +12433,23 @@ class EventBufferProxy {
11700
12433
  this._compression.destroy();
11701
12434
  }
11702
12435
 
12436
+ /** @inheritdoc */
12437
+ clear() {
12438
+ return this._used.clear();
12439
+ }
12440
+
12441
+ /** @inheritdoc */
12442
+ getEarliestTimestamp() {
12443
+ return this._used.getEarliestTimestamp();
12444
+ }
12445
+
11703
12446
  /**
11704
12447
  * Add an event to the event buffer.
11705
12448
  *
11706
12449
  * Returns true if event was successfully added.
11707
12450
  */
11708
- addEvent(event, isCheckout) {
11709
- return this._used.addEvent(event, isCheckout);
12451
+ addEvent(event) {
12452
+ return this._used.addEvent(event);
11710
12453
  }
11711
12454
 
11712
12455
  /** @inheritDoc */
@@ -11781,6 +12524,31 @@ function createEventBuffer({ useCompression }) {
11781
12524
  return new EventBufferArray();
11782
12525
  }
11783
12526
 
12527
+ /**
12528
+ * Removes the session from Session Storage and unsets session in replay instance
12529
+ */
12530
+ function clearSession(replay) {
12531
+ deleteSession();
12532
+ replay.session = undefined;
12533
+ }
12534
+
12535
+ /**
12536
+ * Deletes a session from storage
12537
+ */
12538
+ function deleteSession() {
12539
+ const hasSessionStorage = 'sessionStorage' in WINDOW;
12540
+
12541
+ if (!hasSessionStorage) {
12542
+ return;
12543
+ }
12544
+
12545
+ try {
12546
+ WINDOW.sessionStorage.removeItem(REPLAY_SESSION_KEY);
12547
+ } catch (e) {
12548
+ // Ignore potential SecurityError exceptions
12549
+ }
12550
+ }
12551
+
11784
12552
  /**
11785
12553
  * Given an initial timestamp and an expiry duration, checks to see if current
11786
12554
  * time should be considered as expired.
@@ -11811,11 +12579,26 @@ function isSessionExpired(session, timeouts, targetTime = +new Date()) {
11811
12579
  // First, check that maximum session length has not been exceeded
11812
12580
  isExpired(session.started, timeouts.maxSessionLife, targetTime) ||
11813
12581
  // check that the idle timeout has not been exceeded (i.e. user has
11814
- // performed an action within the last `idleTimeout` ms)
11815
- isExpired(session.lastActivity, timeouts.sessionIdle, targetTime)
12582
+ // performed an action within the last `sessionIdleExpire` ms)
12583
+ isExpired(session.lastActivity, timeouts.sessionIdleExpire, targetTime)
11816
12584
  );
11817
12585
  }
11818
12586
 
12587
+ /**
12588
+ * Given a sample rate, returns true if replay should be sampled.
12589
+ *
12590
+ * 1.0 = 100% sampling
12591
+ * 0.0 = 0% sampling
12592
+ */
12593
+ function isSampled(sampleRate) {
12594
+ if (sampleRate === undefined) {
12595
+ return false;
12596
+ }
12597
+
12598
+ // Math.random() returns a number in range of 0 to 1 (inclusive of 0, but not 1)
12599
+ return Math.random() < sampleRate;
12600
+ }
12601
+
11819
12602
  /**
11820
12603
  * Save a session to session storage.
11821
12604
  */
@@ -11832,21 +12615,6 @@ function saveSession(session) {
11832
12615
  }
11833
12616
  }
11834
12617
 
11835
- /**
11836
- * Given a sample rate, returns true if replay should be sampled.
11837
- *
11838
- * 1.0 = 100% sampling
11839
- * 0.0 = 0% sampling
11840
- */
11841
- function isSampled(sampleRate) {
11842
- if (sampleRate === undefined) {
11843
- return false;
11844
- }
11845
-
11846
- // Math.random() returns a number in range of 0 to 1 (inclusive of 0, but not 1)
11847
- return Math.random() < sampleRate;
11848
- }
11849
-
11850
12618
  /**
11851
12619
  * Get a session with defaults & applied sampling.
11852
12620
  */
@@ -11865,14 +12633,15 @@ function makeSession(session) {
11865
12633
  lastActivity,
11866
12634
  segmentId,
11867
12635
  sampled,
12636
+ shouldRefresh: true,
11868
12637
  };
11869
12638
  }
11870
12639
 
11871
12640
  /**
11872
12641
  * Get the sampled status for a session based on sample rates & current sampled status.
11873
12642
  */
11874
- function getSessionSampleType(sessionSampleRate, errorSampleRate) {
11875
- return isSampled(sessionSampleRate) ? 'session' : isSampled(errorSampleRate) ? 'error' : false;
12643
+ function getSessionSampleType(sessionSampleRate, allowBuffering) {
12644
+ return isSampled(sessionSampleRate) ? 'session' : allowBuffering ? 'buffer' : false;
11876
12645
  }
11877
12646
 
11878
12647
  /**
@@ -11880,8 +12649,8 @@ function getSessionSampleType(sessionSampleRate, errorSampleRate) {
11880
12649
  * that all replays will be saved to as attachments. Currently, we only expect
11881
12650
  * one of these Sentry events per "replay session".
11882
12651
  */
11883
- function createSession({ sessionSampleRate, errorSampleRate, stickySession = false }) {
11884
- const sampled = getSessionSampleType(sessionSampleRate, errorSampleRate);
12652
+ function createSession({ sessionSampleRate, allowBuffering, stickySession = false }) {
12653
+ const sampled = getSessionSampleType(sessionSampleRate, allowBuffering);
11885
12654
  const session = makeSession({
11886
12655
  sampled,
11887
12656
  });
@@ -11929,7 +12698,7 @@ function getSession({
11929
12698
  currentSession,
11930
12699
  stickySession,
11931
12700
  sessionSampleRate,
11932
- errorSampleRate,
12701
+ allowBuffering,
11933
12702
  }) {
11934
12703
  // If session exists and is passed, use it instead of always hitting session storage
11935
12704
  const session = currentSession || (stickySession && fetchSession());
@@ -11942,8 +12711,9 @@ function getSession({
11942
12711
 
11943
12712
  if (!isExpired) {
11944
12713
  return { type: 'saved', session };
11945
- } else if (session.sampled === 'error') {
11946
- // Error samples should not be re-created when expired, but instead we stop when the replay is done
12714
+ } else if (!session.shouldRefresh) {
12715
+ // In this case, stop
12716
+ // This is the case if we have an error session that is completed (=triggered an error)
11947
12717
  const discardedSession = makeSession({ sampled: false });
11948
12718
  return { type: 'new', session: discardedSession };
11949
12719
  } else {
@@ -11955,65 +12725,12 @@ function getSession({
11955
12725
  const newSession = createSession({
11956
12726
  stickySession,
11957
12727
  sessionSampleRate,
11958
- errorSampleRate,
12728
+ allowBuffering,
11959
12729
  });
11960
12730
 
11961
12731
  return { type: 'new', session: newSession };
11962
12732
  }
11963
12733
 
11964
- /**
11965
- * Add an event to the event buffer.
11966
- * `isCheckout` is true if this is either the very first event, or an event triggered by `checkoutEveryNms`.
11967
- */
11968
- async function addEvent(
11969
- replay,
11970
- event,
11971
- isCheckout,
11972
- ) {
11973
- if (!replay.eventBuffer) {
11974
- // This implies that `_isEnabled` is false
11975
- return null;
11976
- }
11977
-
11978
- if (replay.isPaused()) {
11979
- // Do not add to event buffer when recording is paused
11980
- return null;
11981
- }
11982
-
11983
- // TODO: sadness -- we will want to normalize timestamps to be in ms -
11984
- // requires coordination with frontend
11985
- const isMs = event.timestamp > 9999999999;
11986
- const timestampInMs = isMs ? event.timestamp : event.timestamp * 1000;
11987
-
11988
- // Throw out events that happen more than 5 minutes ago. This can happen if
11989
- // page has been left open and idle for a long period of time and user
11990
- // comes back to trigger a new session. The performance entries rely on
11991
- // `performance.timeOrigin`, which is when the page first opened.
11992
- if (timestampInMs + replay.timeouts.sessionIdle < Date.now()) {
11993
- return null;
11994
- }
11995
-
11996
- // Only record earliest event if a new session was created, otherwise it
11997
- // shouldn't be relevant
11998
- const earliestEvent = replay.getContext().earliestEvent;
11999
- if (replay.session && replay.session.segmentId === 0 && (!earliestEvent || timestampInMs < earliestEvent)) {
12000
- replay.getContext().earliestEvent = timestampInMs;
12001
- }
12002
-
12003
- try {
12004
- return await replay.eventBuffer.addEvent(event, isCheckout);
12005
- } catch (error) {
12006
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error(error);
12007
- replay.stop('addEvent');
12008
-
12009
- const client = getCurrentHub().getClient();
12010
-
12011
- if (client) {
12012
- client.recordDroppedEvent('internal_sdk_error', 'replay');
12013
- }
12014
- }
12015
- }
12016
-
12017
12734
  /** If the event is an error event */
12018
12735
  function isErrorEvent(event) {
12019
12736
  return !event.type;
@@ -12063,31 +12780,21 @@ function handleAfterSendEvent(replay) {
12063
12780
  return;
12064
12781
  }
12065
12782
 
12066
- // Add error to list of errorIds of replay
12783
+ // Add error to list of errorIds of replay. This is ok to do even if not
12784
+ // sampled because context will get reset at next checkout.
12785
+ // XXX: There is also a race condition where it's possible to capture an
12786
+ // error to Sentry before Replay SDK has loaded, but response returns after
12787
+ // it was loaded, and this gets called.
12067
12788
  if (event.event_id) {
12068
12789
  replay.getContext().errorIds.add(event.event_id);
12069
12790
  }
12070
12791
 
12071
- // Trigger error recording
12792
+ // If error event is tagged with replay id it means it was sampled (when in buffer mode)
12072
12793
  // Need to be very careful that this does not cause an infinite loop
12073
- if (
12074
- replay.recordingMode === 'error' &&
12075
- event.exception &&
12076
- event.message !== UNABLE_TO_SEND_REPLAY // ignore this error because otherwise we could loop indefinitely with trying to capture replay and failing
12077
- ) {
12078
- setTimeout(async () => {
12079
- // Allow flush to complete before resuming as a session recording, otherwise
12080
- // the checkout from `startRecording` may be included in the payload.
12081
- // Prefer to keep the error replay as a separate (and smaller) segment
12082
- // than the session replay.
12083
- await replay.flushImmediate();
12084
-
12085
- if (replay.stopRecording()) {
12086
- // Reset all "capture on error" configuration before
12087
- // starting a new recording
12088
- replay.recordingMode = 'session';
12089
- replay.startRecording();
12090
- }
12794
+ if (replay.recordingMode === 'buffer' && event.tags && event.tags.replayId) {
12795
+ setTimeout(() => {
12796
+ // Capture current event buffer as new replay
12797
+ void replay.sendBufferedReplayOrFlush();
12091
12798
  });
12092
12799
  }
12093
12800
  };
@@ -12109,166 +12816,6 @@ function isBaseTransportSend() {
12109
12816
  );
12110
12817
  }
12111
12818
 
12112
- var NodeType;
12113
- (function (NodeType) {
12114
- NodeType[NodeType["Document"] = 0] = "Document";
12115
- NodeType[NodeType["DocumentType"] = 1] = "DocumentType";
12116
- NodeType[NodeType["Element"] = 2] = "Element";
12117
- NodeType[NodeType["Text"] = 3] = "Text";
12118
- NodeType[NodeType["CDATA"] = 4] = "CDATA";
12119
- NodeType[NodeType["Comment"] = 5] = "Comment";
12120
- })(NodeType || (NodeType = {}));
12121
-
12122
- /**
12123
- * Create a breadcrumb for a replay.
12124
- */
12125
- function createBreadcrumb(
12126
- breadcrumb,
12127
- ) {
12128
- return {
12129
- timestamp: Date.now() / 1000,
12130
- type: 'default',
12131
- ...breadcrumb,
12132
- };
12133
- }
12134
-
12135
- /**
12136
- * Add a breadcrumb event to replay.
12137
- */
12138
- function addBreadcrumbEvent(replay, breadcrumb) {
12139
- if (breadcrumb.category === 'sentry.transaction') {
12140
- return;
12141
- }
12142
-
12143
- if (['ui.click', 'ui.input'].includes(breadcrumb.category )) {
12144
- replay.triggerUserActivity();
12145
- } else {
12146
- replay.checkAndHandleExpiredSession();
12147
- }
12148
-
12149
- replay.addUpdate(() => {
12150
- void addEvent(replay, {
12151
- type: EventType.Custom,
12152
- // TODO: We were converting from ms to seconds for breadcrumbs, spans,
12153
- // but maybe we should just keep them as milliseconds
12154
- timestamp: (breadcrumb.timestamp || 0) * 1000,
12155
- data: {
12156
- tag: 'breadcrumb',
12157
- payload: breadcrumb,
12158
- },
12159
- });
12160
-
12161
- // Do not flush after console log messages
12162
- return breadcrumb.category === 'console';
12163
- });
12164
- }
12165
-
12166
- // Note that these are the serialized attributes and not attributes directly on
12167
- // the DOM Node. Attributes we are interested in:
12168
- const ATTRIBUTES_TO_RECORD = new Set([
12169
- 'id',
12170
- 'class',
12171
- 'aria-label',
12172
- 'role',
12173
- 'name',
12174
- 'alt',
12175
- 'title',
12176
- 'data-test-id',
12177
- 'data-testid',
12178
- ]);
12179
-
12180
- /**
12181
- * Inclusion list of attributes that we want to record from the DOM element
12182
- */
12183
- function getAttributesToRecord(attributes) {
12184
- const obj = {};
12185
- for (const key in attributes) {
12186
- if (ATTRIBUTES_TO_RECORD.has(key)) {
12187
- let normalizedKey = key;
12188
-
12189
- if (key === 'data-testid' || key === 'data-test-id') {
12190
- normalizedKey = 'testId';
12191
- }
12192
-
12193
- obj[normalizedKey] = attributes[key];
12194
- }
12195
- }
12196
-
12197
- return obj;
12198
- }
12199
-
12200
- const handleDomListener =
12201
- (replay) =>
12202
- (handlerData) => {
12203
- if (!replay.isEnabled()) {
12204
- return;
12205
- }
12206
-
12207
- const result = handleDom(handlerData);
12208
-
12209
- if (!result) {
12210
- return;
12211
- }
12212
-
12213
- addBreadcrumbEvent(replay, result);
12214
- };
12215
-
12216
- /**
12217
- * An event handler to react to DOM events.
12218
- */
12219
- function handleDom(handlerData) {
12220
- let target;
12221
- let targetNode;
12222
-
12223
- // Accessing event.target can throw (see getsentry/raven-js#838, #768)
12224
- try {
12225
- targetNode = getTargetNode(handlerData);
12226
- target = htmlTreeAsString(targetNode);
12227
- } catch (e) {
12228
- target = '<unknown>';
12229
- }
12230
-
12231
- // `__sn` property is the serialized node created by rrweb
12232
- const serializedNode =
12233
- targetNode && '__sn' in targetNode && targetNode.__sn.type === NodeType.Element ? targetNode.__sn : null;
12234
-
12235
- return createBreadcrumb({
12236
- category: `ui.${handlerData.name}`,
12237
- message: target,
12238
- data: serializedNode
12239
- ? {
12240
- nodeId: serializedNode.id,
12241
- node: {
12242
- id: serializedNode.id,
12243
- tagName: serializedNode.tagName,
12244
- textContent: targetNode
12245
- ? Array.from(targetNode.childNodes)
12246
- .map(
12247
- (node) => '__sn' in node && node.__sn.type === NodeType.Text && node.__sn.textContent,
12248
- )
12249
- .filter(Boolean) // filter out empty values
12250
- .map(text => (text ).trim())
12251
- .join('')
12252
- : '',
12253
- attributes: getAttributesToRecord(serializedNode.attributes),
12254
- },
12255
- }
12256
- : {},
12257
- });
12258
- }
12259
-
12260
- function getTargetNode(handlerData) {
12261
- if (isEventWithTarget(handlerData.event)) {
12262
- return handlerData.event.target;
12263
- }
12264
-
12265
- return handlerData.event;
12266
- }
12267
-
12268
- function isEventWithTarget(event) {
12269
- return !!(event ).target;
12270
- }
12271
-
12272
12819
  /**
12273
12820
  * Returns true if we think the given event is an error originating inside of rrweb.
12274
12821
  */
@@ -12292,6 +12839,30 @@ function isRrwebError(event, hint) {
12292
12839
  });
12293
12840
  }
12294
12841
 
12842
+ /**
12843
+ * Determine if event should be sampled (only applies in buffer mode).
12844
+ * When an event is captured by `hanldleGlobalEvent`, when in buffer mode
12845
+ * we determine if we want to sample the error or not.
12846
+ */
12847
+ function shouldSampleForBufferEvent(replay, event) {
12848
+ if (replay.recordingMode !== 'buffer') {
12849
+ return false;
12850
+ }
12851
+
12852
+ // ignore this error because otherwise we could loop indefinitely with
12853
+ // trying to capture replay and failing
12854
+ if (event.message === UNABLE_TO_SEND_REPLAY) {
12855
+ return false;
12856
+ }
12857
+
12858
+ // Require the event to be an error event & to have an exception
12859
+ if (!event.exception || event.type) {
12860
+ return false;
12861
+ }
12862
+
12863
+ return isSampled(replay.getOptions().errorSampleRate);
12864
+ }
12865
+
12295
12866
  /**
12296
12867
  * Returns a listener to be added to `addGlobalEventProcessor(listener)`.
12297
12868
  */
@@ -12321,8 +12892,16 @@ function handleGlobalEventListener(
12321
12892
  return null;
12322
12893
  }
12323
12894
 
12324
- // Only tag transactions with replayId if not waiting for an error
12325
- if (isErrorEvent(event) || (isTransactionEvent(event) && replay.recordingMode === 'session')) {
12895
+ // When in buffer mode, we decide to sample here.
12896
+ // Later, in `handleAfterSendEvent`, if the replayId is set, we know that we sampled
12897
+ // And convert the buffer session to a full session
12898
+ const isErrorEventSampled = shouldSampleForBufferEvent(replay, event);
12899
+
12900
+ // Tag errors if it has been sampled in buffer mode, or if it is session mode
12901
+ // Only tag transactions if in session mode
12902
+ const shouldTagReplayId = isErrorEventSampled || replay.recordingMode === 'session';
12903
+
12904
+ if (shouldTagReplayId) {
12326
12905
  event.tags = { ...event.tags, replayId: replay.getSessionId() };
12327
12906
  }
12328
12907
 
@@ -12372,7 +12951,7 @@ function createPerformanceSpans(
12372
12951
  ) {
12373
12952
  return entries.map(({ type, start, end, name, data }) =>
12374
12953
  addEvent(replay, {
12375
- type: EventType.Custom,
12954
+ type: EventType$1.Custom,
12376
12955
  timestamp: start,
12377
12956
  data: {
12378
12957
  tag: 'performanceSpan',
@@ -12521,12 +13100,14 @@ function handleFetchSpanListener(replay) {
12521
13100
  function handleXhr(handlerData) {
12522
13101
  const { startTimestamp, endTimestamp, xhr } = handlerData;
12523
13102
 
12524
- if (!startTimestamp || !endTimestamp || !xhr.__sentry_xhr__) {
13103
+ const sentryXhrData = xhr[SENTRY_XHR_DATA_KEY];
13104
+
13105
+ if (!startTimestamp || !endTimestamp || !sentryXhrData) {
12525
13106
  return null;
12526
13107
  }
12527
13108
 
12528
13109
  // This is only used as a fallback, so we know the body sizes are never set here
12529
- const { method, url, status_code: statusCode } = xhr.__sentry_xhr__;
13110
+ const { method, url, status_code: statusCode } = sentryXhrData;
12530
13111
 
12531
13112
  if (url === undefined) {
12532
13113
  return null;
@@ -12559,6 +13140,393 @@ function handleXhrSpanListener(replay) {
12559
13140
  };
12560
13141
  }
12561
13142
 
13143
+ const OBJ = 10;
13144
+ const OBJ_KEY = 11;
13145
+ const OBJ_KEY_STR = 12;
13146
+ const OBJ_VAL = 13;
13147
+ const OBJ_VAL_STR = 14;
13148
+ const OBJ_VAL_COMPLETED = 15;
13149
+
13150
+ const ARR = 20;
13151
+ const ARR_VAL = 21;
13152
+ const ARR_VAL_STR = 22;
13153
+ const ARR_VAL_COMPLETED = 23;
13154
+
13155
+ const ALLOWED_PRIMITIVES = ['true', 'false', 'null'];
13156
+
13157
+ /**
13158
+ * Complete an incomplete JSON string.
13159
+ * This will ensure that the last element always has a `"~~"` to indicate it was truncated.
13160
+ * For example, `[1,2,` will be completed to `[1,2,"~~"]`
13161
+ * and `{"aa":"b` will be completed to `{"aa":"b~~"}`
13162
+ */
13163
+ function completeJson(incompleteJson, stack) {
13164
+ if (!stack.length) {
13165
+ return incompleteJson;
13166
+ }
13167
+
13168
+ let json = incompleteJson;
13169
+
13170
+ // Most checks are only needed for the last step in the stack
13171
+ const lastPos = stack.length - 1;
13172
+ const lastStep = stack[lastPos];
13173
+
13174
+ json = _fixLastStep(json, lastStep);
13175
+
13176
+ // Complete remaining steps - just add closing brackets
13177
+ for (let i = lastPos; i >= 0; i--) {
13178
+ const step = stack[i];
13179
+
13180
+ switch (step) {
13181
+ case OBJ:
13182
+ json = `${json}}`;
13183
+ break;
13184
+ case ARR:
13185
+ json = `${json}]`;
13186
+ break;
13187
+ }
13188
+ }
13189
+
13190
+ return json;
13191
+ }
13192
+
13193
+ function _fixLastStep(json, lastStep) {
13194
+ switch (lastStep) {
13195
+ // Object cases
13196
+ case OBJ:
13197
+ return `${json}"~~":"~~"`;
13198
+ case OBJ_KEY:
13199
+ return `${json}:"~~"`;
13200
+ case OBJ_KEY_STR:
13201
+ return `${json}~~":"~~"`;
13202
+ case OBJ_VAL:
13203
+ return _maybeFixIncompleteObjValue(json);
13204
+ case OBJ_VAL_STR:
13205
+ return `${json}~~"`;
13206
+ case OBJ_VAL_COMPLETED:
13207
+ return `${json},"~~":"~~"`;
13208
+
13209
+ // Array cases
13210
+ case ARR:
13211
+ return `${json}"~~"`;
13212
+ case ARR_VAL:
13213
+ return _maybeFixIncompleteArrValue(json);
13214
+ case ARR_VAL_STR:
13215
+ return `${json}~~"`;
13216
+ case ARR_VAL_COMPLETED:
13217
+ return `${json},"~~"`;
13218
+ }
13219
+
13220
+ return json;
13221
+ }
13222
+
13223
+ function _maybeFixIncompleteArrValue(json) {
13224
+ const pos = _findLastArrayDelimiter(json);
13225
+
13226
+ if (pos > -1) {
13227
+ const part = json.slice(pos + 1);
13228
+
13229
+ if (ALLOWED_PRIMITIVES.includes(part.trim())) {
13230
+ return `${json},"~~"`;
13231
+ }
13232
+
13233
+ // Everything else is replaced with `"~~"`
13234
+ return `${json.slice(0, pos + 1)}"~~"`;
13235
+ }
13236
+
13237
+ // fallback, this shouldn't happen, to be save
13238
+ return json;
13239
+ }
13240
+
13241
+ function _findLastArrayDelimiter(json) {
13242
+ for (let i = json.length - 1; i >= 0; i--) {
13243
+ const char = json[i];
13244
+
13245
+ if (char === ',' || char === '[') {
13246
+ return i;
13247
+ }
13248
+ }
13249
+
13250
+ return -1;
13251
+ }
13252
+
13253
+ function _maybeFixIncompleteObjValue(json) {
13254
+ const startPos = json.lastIndexOf(':');
13255
+
13256
+ const part = json.slice(startPos + 1);
13257
+
13258
+ if (ALLOWED_PRIMITIVES.includes(part.trim())) {
13259
+ return `${json},"~~":"~~"`;
13260
+ }
13261
+
13262
+ // Everything else is replaced with `"~~"`
13263
+ // This also means we do not have incomplete numbers, e.g `[1` is replaced with `["~~"]`
13264
+ return `${json.slice(0, startPos + 1)}"~~"`;
13265
+ }
13266
+
13267
+ /**
13268
+ * Evaluate an (incomplete) JSON string.
13269
+ */
13270
+ function evaluateJson(json) {
13271
+ const stack = [];
13272
+
13273
+ for (let pos = 0; pos < json.length; pos++) {
13274
+ _evaluateJsonPos(stack, json, pos);
13275
+ }
13276
+
13277
+ return stack;
13278
+ }
13279
+
13280
+ function _evaluateJsonPos(stack, json, pos) {
13281
+ const curStep = stack[stack.length - 1];
13282
+
13283
+ const char = json[pos];
13284
+
13285
+ const whitespaceRegex = /\s/;
13286
+
13287
+ if (whitespaceRegex.test(char)) {
13288
+ return;
13289
+ }
13290
+
13291
+ if (char === '"' && !_isEscaped(json, pos)) {
13292
+ _handleQuote(stack, curStep);
13293
+ return;
13294
+ }
13295
+
13296
+ switch (char) {
13297
+ case '{':
13298
+ _handleObj(stack, curStep);
13299
+ break;
13300
+ case '[':
13301
+ _handleArr(stack, curStep);
13302
+ break;
13303
+ case ':':
13304
+ _handleColon(stack, curStep);
13305
+ break;
13306
+ case ',':
13307
+ _handleComma(stack, curStep);
13308
+ break;
13309
+ case '}':
13310
+ _handleObjClose(stack, curStep);
13311
+ break;
13312
+ case ']':
13313
+ _handleArrClose(stack, curStep);
13314
+ break;
13315
+ }
13316
+ }
13317
+
13318
+ function _handleQuote(stack, curStep) {
13319
+ // End of obj value
13320
+ if (curStep === OBJ_VAL_STR) {
13321
+ stack.pop();
13322
+ stack.push(OBJ_VAL_COMPLETED);
13323
+ return;
13324
+ }
13325
+
13326
+ // End of arr value
13327
+ if (curStep === ARR_VAL_STR) {
13328
+ stack.pop();
13329
+ stack.push(ARR_VAL_COMPLETED);
13330
+ return;
13331
+ }
13332
+
13333
+ // Start of obj value
13334
+ if (curStep === OBJ_VAL) {
13335
+ stack.push(OBJ_VAL_STR);
13336
+ return;
13337
+ }
13338
+
13339
+ // Start of arr value
13340
+ if (curStep === ARR_VAL) {
13341
+ stack.push(ARR_VAL_STR);
13342
+ return;
13343
+ }
13344
+
13345
+ // Start of obj key
13346
+ if (curStep === OBJ) {
13347
+ stack.push(OBJ_KEY_STR);
13348
+ return;
13349
+ }
13350
+
13351
+ // End of obj key
13352
+ if (curStep === OBJ_KEY_STR) {
13353
+ stack.pop();
13354
+ stack.push(OBJ_KEY);
13355
+ return;
13356
+ }
13357
+ }
13358
+
13359
+ function _handleObj(stack, curStep) {
13360
+ // Initial object
13361
+ if (!curStep) {
13362
+ stack.push(OBJ);
13363
+ return;
13364
+ }
13365
+
13366
+ // New object as obj value
13367
+ if (curStep === OBJ_VAL) {
13368
+ stack.push(OBJ);
13369
+ return;
13370
+ }
13371
+
13372
+ // New object as array element
13373
+ if (curStep === ARR_VAL) {
13374
+ stack.push(OBJ);
13375
+ }
13376
+
13377
+ // New object as first array element
13378
+ if (curStep === ARR) {
13379
+ stack.push(OBJ);
13380
+ return;
13381
+ }
13382
+ }
13383
+
13384
+ function _handleArr(stack, curStep) {
13385
+ // Initial array
13386
+ if (!curStep) {
13387
+ stack.push(ARR);
13388
+ stack.push(ARR_VAL);
13389
+ return;
13390
+ }
13391
+
13392
+ // New array as obj value
13393
+ if (curStep === OBJ_VAL) {
13394
+ stack.push(ARR);
13395
+ stack.push(ARR_VAL);
13396
+ return;
13397
+ }
13398
+
13399
+ // New array as array element
13400
+ if (curStep === ARR_VAL) {
13401
+ stack.push(ARR);
13402
+ stack.push(ARR_VAL);
13403
+ }
13404
+
13405
+ // New array as first array element
13406
+ if (curStep === ARR) {
13407
+ stack.push(ARR);
13408
+ stack.push(ARR_VAL);
13409
+ return;
13410
+ }
13411
+ }
13412
+
13413
+ function _handleColon(stack, curStep) {
13414
+ if (curStep === OBJ_KEY) {
13415
+ stack.pop();
13416
+ stack.push(OBJ_VAL);
13417
+ }
13418
+ }
13419
+
13420
+ function _handleComma(stack, curStep) {
13421
+ // Comma after obj value
13422
+ if (curStep === OBJ_VAL) {
13423
+ stack.pop();
13424
+ return;
13425
+ }
13426
+ if (curStep === OBJ_VAL_COMPLETED) {
13427
+ // Pop OBJ_VAL_COMPLETED & OBJ_VAL
13428
+ stack.pop();
13429
+ stack.pop();
13430
+ return;
13431
+ }
13432
+
13433
+ // Comma after arr value
13434
+ if (curStep === ARR_VAL) {
13435
+ // do nothing - basically we'd pop ARR_VAL but add it right back
13436
+ return;
13437
+ }
13438
+
13439
+ if (curStep === ARR_VAL_COMPLETED) {
13440
+ // Pop ARR_VAL_COMPLETED
13441
+ stack.pop();
13442
+
13443
+ // basically we'd pop ARR_VAL but add it right back
13444
+ return;
13445
+ }
13446
+ }
13447
+
13448
+ function _handleObjClose(stack, curStep) {
13449
+ // Empty object {}
13450
+ if (curStep === OBJ) {
13451
+ stack.pop();
13452
+ }
13453
+
13454
+ // Object with element
13455
+ if (curStep === OBJ_VAL) {
13456
+ // Pop OBJ_VAL, OBJ
13457
+ stack.pop();
13458
+ stack.pop();
13459
+ }
13460
+
13461
+ // Obj with element
13462
+ if (curStep === OBJ_VAL_COMPLETED) {
13463
+ // Pop OBJ_VAL_COMPLETED, OBJ_VAL, OBJ
13464
+ stack.pop();
13465
+ stack.pop();
13466
+ stack.pop();
13467
+ }
13468
+
13469
+ // if was obj value, complete it
13470
+ if (stack[stack.length - 1] === OBJ_VAL) {
13471
+ stack.push(OBJ_VAL_COMPLETED);
13472
+ }
13473
+
13474
+ // if was arr value, complete it
13475
+ if (stack[stack.length - 1] === ARR_VAL) {
13476
+ stack.push(ARR_VAL_COMPLETED);
13477
+ }
13478
+ }
13479
+
13480
+ function _handleArrClose(stack, curStep) {
13481
+ // Empty array []
13482
+ if (curStep === ARR) {
13483
+ stack.pop();
13484
+ }
13485
+
13486
+ // Array with element
13487
+ if (curStep === ARR_VAL) {
13488
+ // Pop ARR_VAL, ARR
13489
+ stack.pop();
13490
+ stack.pop();
13491
+ }
13492
+
13493
+ // Array with element
13494
+ if (curStep === ARR_VAL_COMPLETED) {
13495
+ // Pop ARR_VAL_COMPLETED, ARR_VAL, ARR
13496
+ stack.pop();
13497
+ stack.pop();
13498
+ stack.pop();
13499
+ }
13500
+
13501
+ // if was obj value, complete it
13502
+ if (stack[stack.length - 1] === OBJ_VAL) {
13503
+ stack.push(OBJ_VAL_COMPLETED);
13504
+ }
13505
+
13506
+ // if was arr value, complete it
13507
+ if (stack[stack.length - 1] === ARR_VAL) {
13508
+ stack.push(ARR_VAL_COMPLETED);
13509
+ }
13510
+ }
13511
+
13512
+ function _isEscaped(str, pos) {
13513
+ const previousChar = str[pos - 1];
13514
+
13515
+ return previousChar === '\\' && !_isEscaped(str, pos - 1);
13516
+ }
13517
+
13518
+ /* eslint-disable max-lines */
13519
+
13520
+ /**
13521
+ * Takes an incomplete JSON string, and returns a hopefully valid JSON string.
13522
+ * Note that this _can_ fail, so you should check the return value is valid JSON.
13523
+ */
13524
+ function fixJson(incompleteJson) {
13525
+ const stack = evaluateJson(incompleteJson);
13526
+
13527
+ return completeJson(incompleteJson, stack);
13528
+ }
13529
+
12562
13530
  /** Get the size of a body. */
12563
13531
  function getBodySize(
12564
13532
  body,
@@ -12652,51 +13620,68 @@ function makeNetworkReplayBreadcrumb(
12652
13620
  return result;
12653
13621
  }
12654
13622
 
12655
- /** Get either a JSON network body, or a text representation. */
12656
- function getNetworkBody(bodyText) {
12657
- if (!bodyText) {
12658
- return;
12659
- }
12660
-
12661
- try {
12662
- return JSON.parse(bodyText);
12663
- } catch (e2) {
12664
- // return text
12665
- }
12666
-
12667
- return bodyText;
13623
+ /** Build the request or response part of a replay network breadcrumb that was skipped. */
13624
+ function buildSkippedNetworkRequestOrResponse(bodySize) {
13625
+ return {
13626
+ headers: {},
13627
+ size: bodySize,
13628
+ _meta: {
13629
+ warnings: ['URL_SKIPPED'],
13630
+ },
13631
+ };
12668
13632
  }
12669
13633
 
12670
13634
  /** Build the request or response part of a replay network breadcrumb. */
12671
13635
  function buildNetworkRequestOrResponse(
13636
+ headers,
12672
13637
  bodySize,
12673
13638
  body,
12674
13639
  ) {
12675
- if (!bodySize) {
13640
+ if (!bodySize && Object.keys(headers).length === 0) {
12676
13641
  return undefined;
12677
13642
  }
12678
13643
 
13644
+ if (!bodySize) {
13645
+ return {
13646
+ headers,
13647
+ };
13648
+ }
13649
+
12679
13650
  if (!body) {
12680
13651
  return {
13652
+ headers,
12681
13653
  size: bodySize,
12682
13654
  };
12683
13655
  }
12684
13656
 
12685
13657
  const info = {
13658
+ headers,
12686
13659
  size: bodySize,
12687
13660
  };
12688
13661
 
12689
- if (bodySize < NETWORK_BODY_MAX_SIZE) {
12690
- info.body = body;
12691
- } else {
13662
+ const { body: normalizedBody, warnings } = normalizeNetworkBody(body);
13663
+ info.body = normalizedBody;
13664
+ if (warnings.length > 0) {
12692
13665
  info._meta = {
12693
- errors: ['MAX_BODY_SIZE_EXCEEDED'],
13666
+ warnings,
12694
13667
  };
12695
13668
  }
12696
13669
 
12697
13670
  return info;
12698
13671
  }
12699
13672
 
13673
+ /** Filter a set of headers */
13674
+ function getAllowedHeaders(headers, allowedHeaders) {
13675
+ return Object.keys(headers).reduce((filteredHeaders, key) => {
13676
+ const normalizedKey = key.toLowerCase();
13677
+ // Avoid putting empty strings into the headers
13678
+ if (allowedHeaders.includes(normalizedKey) && headers[key]) {
13679
+ filteredHeaders[normalizedKey] = headers[key];
13680
+ }
13681
+ return filteredHeaders;
13682
+ }, {});
13683
+ }
13684
+
12700
13685
  function _serializeFormData(formData) {
12701
13686
  // This is a bit simplified, but gives us a decent estimate
12702
13687
  // This converts e.g. { name: 'Anne Smith', age: 13 } to 'name=Anne+Smith&age=13'
@@ -12704,6 +13689,78 @@ function _serializeFormData(formData) {
12704
13689
  return new URLSearchParams(formData).toString();
12705
13690
  }
12706
13691
 
13692
+ function normalizeNetworkBody(body)
13693
+
13694
+ {
13695
+ if (!body || typeof body !== 'string') {
13696
+ return {
13697
+ body,
13698
+ warnings: [],
13699
+ };
13700
+ }
13701
+
13702
+ const exceedsSizeLimit = body.length > NETWORK_BODY_MAX_SIZE;
13703
+
13704
+ if (_strIsProbablyJson(body)) {
13705
+ try {
13706
+ const json = exceedsSizeLimit ? fixJson(body.slice(0, NETWORK_BODY_MAX_SIZE)) : body;
13707
+ const normalizedBody = JSON.parse(json);
13708
+ return {
13709
+ body: normalizedBody,
13710
+ warnings: exceedsSizeLimit ? ['JSON_TRUNCATED'] : [],
13711
+ };
13712
+ } catch (e3) {
13713
+ return {
13714
+ body: exceedsSizeLimit ? `${body.slice(0, NETWORK_BODY_MAX_SIZE)}…` : body,
13715
+ warnings: exceedsSizeLimit ? ['INVALID_JSON', 'TEXT_TRUNCATED'] : ['INVALID_JSON'],
13716
+ };
13717
+ }
13718
+ }
13719
+
13720
+ return {
13721
+ body: exceedsSizeLimit ? `${body.slice(0, NETWORK_BODY_MAX_SIZE)}…` : body,
13722
+ warnings: exceedsSizeLimit ? ['TEXT_TRUNCATED'] : [],
13723
+ };
13724
+ }
13725
+
13726
+ function _strIsProbablyJson(str) {
13727
+ const first = str[0];
13728
+ const last = str[str.length - 1];
13729
+
13730
+ // Simple check: If this does not start & end with {} or [], it's not JSON
13731
+ return (first === '[' && last === ']') || (first === '{' && last === '}');
13732
+ }
13733
+
13734
+ /** Match an URL against a list of strings/Regex. */
13735
+ function urlMatches(url, urls) {
13736
+ const fullUrl = getFullUrl(url);
13737
+
13738
+ return stringMatchesSomePattern(fullUrl, urls);
13739
+ }
13740
+
13741
+ /** exported for tests */
13742
+ function getFullUrl(url, baseURI = WINDOW.document.baseURI) {
13743
+ // Short circuit for common cases:
13744
+ if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith(WINDOW.location.origin)) {
13745
+ return url;
13746
+ }
13747
+ const fixedUrl = new URL(url, baseURI);
13748
+
13749
+ // If these do not match, we are not dealing with a relative URL, so just return it
13750
+ if (fixedUrl.origin !== new URL(baseURI).origin) {
13751
+ return url;
13752
+ }
13753
+
13754
+ const fullUrl = fixedUrl.href;
13755
+
13756
+ // Remove trailing slashes, if they don't match the original URL
13757
+ if (!url.endsWith('/') && fullUrl.endsWith('/')) {
13758
+ return fullUrl.slice(0, -1);
13759
+ }
13760
+
13761
+ return fullUrl;
13762
+ }
13763
+
12707
13764
  /**
12708
13765
  * Capture a fetch breadcrumb to a replay.
12709
13766
  * This adds additional data (where approriate).
@@ -12711,7 +13768,9 @@ function _serializeFormData(formData) {
12711
13768
  async function captureFetchBreadcrumbToReplay(
12712
13769
  breadcrumb,
12713
13770
  hint,
12714
- options,
13771
+ options
13772
+
13773
+ ,
12715
13774
  ) {
12716
13775
  try {
12717
13776
  const data = await _prepareFetchData(breadcrumb, hint, options);
@@ -12738,6 +13797,7 @@ function enrichFetchBreadcrumb(
12738
13797
 
12739
13798
  const body = _getFetchRequestArgBody(input);
12740
13799
  const reqSize = getBodySize(body, options.textEncoder);
13800
+
12741
13801
  const resSize = response ? parseContentLengthHeader(response.headers.get('content-length')) : undefined;
12742
13802
 
12743
13803
  if (reqSize !== undefined) {
@@ -12751,97 +13811,109 @@ function enrichFetchBreadcrumb(
12751
13811
  async function _prepareFetchData(
12752
13812
  breadcrumb,
12753
13813
  hint,
12754
- options,
13814
+ options
13815
+
13816
+ ,
12755
13817
  ) {
12756
13818
  const { startTimestamp, endTimestamp } = hint;
12757
13819
 
12758
13820
  const {
12759
13821
  url,
12760
13822
  method,
12761
- status_code: statusCode,
13823
+ status_code: statusCode = 0,
12762
13824
  request_body_size: requestBodySize,
12763
13825
  response_body_size: responseBodySize,
12764
13826
  } = breadcrumb.data;
12765
13827
 
12766
- const request = _getRequestInfo(options, hint.input, requestBodySize);
12767
- const response = await _getResponseInfo(options, hint.response, responseBodySize);
13828
+ const captureDetails = urlMatches(url, options.networkDetailAllowUrls);
13829
+
13830
+ const request = captureDetails
13831
+ ? _getRequestInfo(options, hint.input, requestBodySize)
13832
+ : buildSkippedNetworkRequestOrResponse(requestBodySize);
13833
+ const response = await _getResponseInfo(captureDetails, options, hint.response, responseBodySize);
12768
13834
 
12769
13835
  return {
12770
13836
  startTimestamp,
12771
13837
  endTimestamp,
12772
13838
  url,
12773
13839
  method,
12774
- statusCode: statusCode || 0,
13840
+ statusCode,
12775
13841
  request,
12776
13842
  response,
12777
13843
  };
12778
13844
  }
12779
13845
 
12780
13846
  function _getRequestInfo(
12781
- { captureBodies },
13847
+ { networkCaptureBodies, networkRequestHeaders },
12782
13848
  input,
12783
13849
  requestBodySize,
12784
13850
  ) {
12785
- if (!captureBodies) {
12786
- return buildNetworkRequestOrResponse(requestBodySize, undefined);
13851
+ const headers = getRequestHeaders(input, networkRequestHeaders);
13852
+
13853
+ if (!networkCaptureBodies) {
13854
+ return buildNetworkRequestOrResponse(headers, requestBodySize, undefined);
12787
13855
  }
12788
13856
 
12789
13857
  // We only want to transmit string or string-like bodies
12790
13858
  const requestBody = _getFetchRequestArgBody(input);
12791
- const body = getNetworkBody(getBodyString(requestBody));
12792
- return buildNetworkRequestOrResponse(requestBodySize, body);
13859
+ const bodyStr = getBodyString(requestBody);
13860
+ return buildNetworkRequestOrResponse(headers, requestBodySize, bodyStr);
12793
13861
  }
12794
13862
 
12795
13863
  async function _getResponseInfo(
12796
- { captureBodies, textEncoder },
13864
+ captureDetails,
13865
+ {
13866
+ networkCaptureBodies,
13867
+ textEncoder,
13868
+ networkResponseHeaders,
13869
+ }
13870
+
13871
+ ,
12797
13872
  response,
12798
13873
  responseBodySize,
12799
13874
  ) {
12800
- if (!captureBodies && responseBodySize !== undefined) {
12801
- return buildNetworkRequestOrResponse(responseBodySize, undefined);
13875
+ if (!captureDetails && responseBodySize !== undefined) {
13876
+ return buildSkippedNetworkRequestOrResponse(responseBodySize);
13877
+ }
13878
+
13879
+ const headers = getAllHeaders(response.headers, networkResponseHeaders);
13880
+
13881
+ if (!networkCaptureBodies && responseBodySize !== undefined) {
13882
+ return buildNetworkRequestOrResponse(headers, responseBodySize, undefined);
12802
13883
  }
12803
13884
 
12804
13885
  // Only clone the response if we need to
12805
13886
  try {
12806
13887
  // We have to clone this, as the body can only be read once
12807
13888
  const res = response.clone();
12808
- const { body, bodyText } = await _parseFetchBody(res);
13889
+ const bodyText = await _parseFetchBody(res);
12809
13890
 
12810
13891
  const size =
12811
13892
  bodyText && bodyText.length && responseBodySize === undefined
12812
13893
  ? getBodySize(bodyText, textEncoder)
12813
13894
  : responseBodySize;
12814
13895
 
12815
- if (captureBodies) {
12816
- return buildNetworkRequestOrResponse(size, body);
13896
+ if (!captureDetails) {
13897
+ return buildSkippedNetworkRequestOrResponse(size);
12817
13898
  }
12818
13899
 
12819
- return buildNetworkRequestOrResponse(size, undefined);
13900
+ if (networkCaptureBodies) {
13901
+ return buildNetworkRequestOrResponse(headers, size, bodyText);
13902
+ }
13903
+
13904
+ return buildNetworkRequestOrResponse(headers, size, undefined);
12820
13905
  } catch (e) {
12821
13906
  // fallback
12822
- return buildNetworkRequestOrResponse(responseBodySize, undefined);
13907
+ return buildNetworkRequestOrResponse(headers, responseBodySize, undefined);
12823
13908
  }
12824
13909
  }
12825
13910
 
12826
- async function _parseFetchBody(
12827
- response,
12828
- ) {
12829
- let bodyText;
12830
-
13911
+ async function _parseFetchBody(response) {
12831
13912
  try {
12832
- bodyText = await response.text();
13913
+ return await response.text();
12833
13914
  } catch (e2) {
12834
- return {};
12835
- }
12836
-
12837
- try {
12838
- const body = JSON.parse(bodyText);
12839
- return { body, bodyText };
12840
- } catch (e3) {
12841
- // just send bodyText
13915
+ return undefined;
12842
13916
  }
12843
-
12844
- return { bodyText, body: bodyText };
12845
13917
  }
12846
13918
 
12847
13919
  function _getFetchRequestArgBody(fetchArgs = []) {
@@ -12853,6 +13925,56 @@ function _getFetchRequestArgBody(fetchArgs = []) {
12853
13925
  return (fetchArgs[1] ).body;
12854
13926
  }
12855
13927
 
13928
+ function getAllHeaders(headers, allowedHeaders) {
13929
+ const allHeaders = {};
13930
+
13931
+ allowedHeaders.forEach(header => {
13932
+ if (headers.get(header)) {
13933
+ allHeaders[header] = headers.get(header) ;
13934
+ }
13935
+ });
13936
+
13937
+ return allHeaders;
13938
+ }
13939
+
13940
+ function getRequestHeaders(fetchArgs, allowedHeaders) {
13941
+ if (fetchArgs.length === 1 && typeof fetchArgs[0] !== 'string') {
13942
+ return getHeadersFromOptions(fetchArgs[0] , allowedHeaders);
13943
+ }
13944
+
13945
+ if (fetchArgs.length === 2) {
13946
+ return getHeadersFromOptions(fetchArgs[1] , allowedHeaders);
13947
+ }
13948
+
13949
+ return {};
13950
+ }
13951
+
13952
+ function getHeadersFromOptions(
13953
+ input,
13954
+ allowedHeaders,
13955
+ ) {
13956
+ if (!input) {
13957
+ return {};
13958
+ }
13959
+
13960
+ const headers = input.headers;
13961
+
13962
+ if (!headers) {
13963
+ return {};
13964
+ }
13965
+
13966
+ if (headers instanceof Headers) {
13967
+ return getAllHeaders(headers, allowedHeaders);
13968
+ }
13969
+
13970
+ // We do not support this, as it is not really documented (anymore?)
13971
+ if (Array.isArray(headers)) {
13972
+ return {};
13973
+ }
13974
+
13975
+ return getAllowedHeaders(headers, allowedHeaders);
13976
+ }
13977
+
12856
13978
  /**
12857
13979
  * Capture an XHR breadcrumb to a replay.
12858
13980
  * This adds additional data (where approriate).
@@ -12903,12 +14025,12 @@ function _prepareXhrData(
12903
14025
  hint,
12904
14026
  options,
12905
14027
  ) {
12906
- const { startTimestamp, endTimestamp, input } = hint;
14028
+ const { startTimestamp, endTimestamp, input, xhr } = hint;
12907
14029
 
12908
14030
  const {
12909
14031
  url,
12910
14032
  method,
12911
- status_code: statusCode,
14033
+ status_code: statusCode = 0,
12912
14034
  request_body_size: requestBodySize,
12913
14035
  response_body_size: responseBodySize,
12914
14036
  } = breadcrumb.data;
@@ -12917,13 +14039,35 @@ function _prepareXhrData(
12917
14039
  return null;
12918
14040
  }
12919
14041
 
14042
+ if (!urlMatches(url, options.networkDetailAllowUrls)) {
14043
+ const request = buildSkippedNetworkRequestOrResponse(requestBodySize);
14044
+ const response = buildSkippedNetworkRequestOrResponse(responseBodySize);
14045
+ return {
14046
+ startTimestamp,
14047
+ endTimestamp,
14048
+ url,
14049
+ method,
14050
+ statusCode,
14051
+ request,
14052
+ response,
14053
+ };
14054
+ }
14055
+
14056
+ const xhrInfo = xhr[SENTRY_XHR_DATA_KEY];
14057
+ const networkRequestHeaders = xhrInfo
14058
+ ? getAllowedHeaders(xhrInfo.request_headers, options.networkRequestHeaders)
14059
+ : {};
14060
+ const networkResponseHeaders = getAllowedHeaders(getResponseHeaders(xhr), options.networkResponseHeaders);
14061
+
12920
14062
  const request = buildNetworkRequestOrResponse(
14063
+ networkRequestHeaders,
12921
14064
  requestBodySize,
12922
- options.captureBodies ? getNetworkBody(getBodyString(input)) : undefined,
14065
+ options.networkCaptureBodies ? getBodyString(input) : undefined,
12923
14066
  );
12924
14067
  const response = buildNetworkRequestOrResponse(
14068
+ networkResponseHeaders,
12925
14069
  responseBodySize,
12926
- options.captureBodies ? getNetworkBody(hint.xhr.responseText) : undefined,
14070
+ options.networkCaptureBodies ? hint.xhr.responseText : undefined,
12927
14071
  );
12928
14072
 
12929
14073
  return {
@@ -12931,12 +14075,26 @@ function _prepareXhrData(
12931
14075
  endTimestamp,
12932
14076
  url,
12933
14077
  method,
12934
- statusCode: statusCode || 0,
14078
+ statusCode,
12935
14079
  request,
12936
14080
  response,
12937
14081
  };
12938
14082
  }
12939
14083
 
14084
+ function getResponseHeaders(xhr) {
14085
+ const headers = xhr.getAllResponseHeaders();
14086
+
14087
+ if (!headers) {
14088
+ return {};
14089
+ }
14090
+
14091
+ return headers.split('\r\n').reduce((acc, line) => {
14092
+ const [key, value] = line.split(': ');
14093
+ acc[key.toLowerCase()] = value;
14094
+ return acc;
14095
+ }, {});
14096
+ }
14097
+
12940
14098
  /**
12941
14099
  * This method does two things:
12942
14100
  * - It enriches the regular XHR/fetch breadcrumbs with request/response size data
@@ -12949,10 +14107,16 @@ function handleNetworkBreadcrumbs(replay) {
12949
14107
  try {
12950
14108
  const textEncoder = new TextEncoder();
12951
14109
 
14110
+ const { networkDetailAllowUrls, networkCaptureBodies, networkRequestHeaders, networkResponseHeaders } =
14111
+ replay.getOptions();
14112
+
12952
14113
  const options = {
12953
14114
  replay,
12954
14115
  textEncoder,
12955
- captureBodies: replay.getOptions()._experiments.captureNetworkBodies || false,
14116
+ networkDetailAllowUrls,
14117
+ networkCaptureBodies,
14118
+ networkRequestHeaders,
14119
+ networkResponseHeaders,
12956
14120
  };
12957
14121
 
12958
14122
  if (client && client.on) {
@@ -13060,9 +14224,66 @@ function handleScope(scope) {
13060
14224
  return null;
13061
14225
  }
13062
14226
 
14227
+ if (newBreadcrumb.category === 'console') {
14228
+ return normalizeConsoleBreadcrumb(newBreadcrumb);
14229
+ }
14230
+
13063
14231
  return createBreadcrumb(newBreadcrumb);
13064
14232
  }
13065
14233
 
14234
+ /** exported for tests only */
14235
+ function normalizeConsoleBreadcrumb(breadcrumb) {
14236
+ const args = breadcrumb.data && breadcrumb.data.arguments;
14237
+
14238
+ if (!Array.isArray(args) || args.length === 0) {
14239
+ return createBreadcrumb(breadcrumb);
14240
+ }
14241
+
14242
+ let isTruncated = false;
14243
+
14244
+ // Avoid giant args captures
14245
+ const normalizedArgs = args.map(arg => {
14246
+ if (!arg) {
14247
+ return arg;
14248
+ }
14249
+ if (typeof arg === 'string') {
14250
+ if (arg.length > CONSOLE_ARG_MAX_SIZE) {
14251
+ isTruncated = true;
14252
+ return `${arg.slice(0, CONSOLE_ARG_MAX_SIZE)}…`;
14253
+ }
14254
+
14255
+ return arg;
14256
+ }
14257
+ if (typeof arg === 'object') {
14258
+ try {
14259
+ const normalizedArg = normalize(arg, 7);
14260
+ const stringified = JSON.stringify(normalizedArg);
14261
+ if (stringified.length > CONSOLE_ARG_MAX_SIZE) {
14262
+ const fixedJson = fixJson(stringified.slice(0, CONSOLE_ARG_MAX_SIZE));
14263
+ const json = JSON.parse(fixedJson);
14264
+ // We only set this after JSON.parse() was successfull, so we know we didn't run into `catch`
14265
+ isTruncated = true;
14266
+ return json;
14267
+ }
14268
+ return normalizedArg;
14269
+ } catch (e) {
14270
+ // fall back to default
14271
+ }
14272
+ }
14273
+
14274
+ return arg;
14275
+ });
14276
+
14277
+ return createBreadcrumb({
14278
+ ...breadcrumb,
14279
+ data: {
14280
+ ...breadcrumb.data,
14281
+ arguments: normalizedArgs,
14282
+ ...(isTruncated ? { _meta: { warnings: ['CONSOLE_ARG_TRUNCATED'] } } : {}),
14283
+ },
14284
+ });
14285
+ }
14286
+
13066
14287
  /**
13067
14288
  * Add global listeners that cannot be removed.
13068
14289
  */
@@ -13087,7 +14308,8 @@ function addGlobalListeners(replay) {
13087
14308
  client.on('afterSendEvent', handleAfterSendEvent(replay));
13088
14309
  client.on('createDsc', (dsc) => {
13089
14310
  const replayId = replay.getSessionId();
13090
- if (replayId) {
14311
+ // We do not want to set the DSC when in buffer mode, as that means the replay has not been sent (yet)
14312
+ if (replayId && replay.isEnabled() && replay.recordingMode === 'session') {
13091
14313
  dsc.replay_id = replayId;
13092
14314
  }
13093
14315
  });
@@ -13367,6 +14589,23 @@ function debounce(func, wait, options) {
13367
14589
  return debounced;
13368
14590
  }
13369
14591
 
14592
+ /* eslint-disable @typescript-eslint/naming-convention */
14593
+
14594
+ var EventType; (function (EventType) {
14595
+ const DomContentLoaded = 0; EventType[EventType["DomContentLoaded"] = DomContentLoaded] = "DomContentLoaded";
14596
+ const Load = 1; EventType[EventType["Load"] = Load] = "Load";
14597
+ const FullSnapshot = 2; EventType[EventType["FullSnapshot"] = FullSnapshot] = "FullSnapshot";
14598
+ const IncrementalSnapshot = 3; EventType[EventType["IncrementalSnapshot"] = IncrementalSnapshot] = "IncrementalSnapshot";
14599
+ const Meta = 4; EventType[EventType["Meta"] = Meta] = "Meta";
14600
+ const Custom = 5; EventType[EventType["Custom"] = Custom] = "Custom";
14601
+ const Plugin = 6; EventType[EventType["Plugin"] = Plugin] = "Plugin";
14602
+ })(EventType || (EventType = {}));
14603
+
14604
+ /**
14605
+ * This is a partial copy of rrweb's eventWithTime type which only contains the properties
14606
+ * we specifcally need in the SDK.
14607
+ */
14608
+
13370
14609
  /**
13371
14610
  * Handler for recording events.
13372
14611
  *
@@ -13395,7 +14634,7 @@ function getHandleRecordingEmit(replay) {
13395
14634
  // when an error occurs. Clear any state that happens before this current
13396
14635
  // checkout. This needs to happen before `addEvent()` which updates state
13397
14636
  // dependent on this reset.
13398
- if (replay.recordingMode === 'error' && isCheckout) {
14637
+ if (replay.recordingMode === 'buffer' && isCheckout) {
13399
14638
  replay.setInitialState();
13400
14639
  }
13401
14640
 
@@ -13409,6 +14648,14 @@ function getHandleRecordingEmit(replay) {
13409
14648
  return false;
13410
14649
  }
13411
14650
 
14651
+ // Additionally, create a meta event that will capture certain SDK settings.
14652
+ // In order to handle buffer mode, this needs to either be done when we
14653
+ // receive checkout events or at flush time.
14654
+ //
14655
+ // `isCheckout` is always true, but want to be explicit that it should
14656
+ // only be added for checkouts
14657
+ void addSettingsEvent(replay, isCheckout);
14658
+
13412
14659
  // If there is a previousSessionId after a full snapshot occurs, then
13413
14660
  // the replay session was started due to session expiration. The new session
13414
14661
  // is started before triggering a new checkout and contains the id
@@ -13419,10 +14666,10 @@ function getHandleRecordingEmit(replay) {
13419
14666
  return true;
13420
14667
  }
13421
14668
 
13422
- // See note above re: session start needs to reflect the most recent
13423
- // checkout.
13424
- if (replay.recordingMode === 'error' && replay.session) {
13425
- const { earliestEvent } = replay.getContext();
14669
+ // When in buffer mode, make sure we adjust the session started date to the current earliest event of the buffer
14670
+ // this should usually be the timestamp of the checkout event, but to be safe...
14671
+ if (replay.recordingMode === 'buffer' && replay.session && replay.eventBuffer) {
14672
+ const earliestEvent = replay.eventBuffer.getEarliestTimestamp();
13426
14673
  if (earliestEvent) {
13427
14674
  replay.session.started = earliestEvent;
13428
14675
 
@@ -13446,6 +14693,46 @@ function getHandleRecordingEmit(replay) {
13446
14693
  };
13447
14694
  }
13448
14695
 
14696
+ /**
14697
+ * Exported for tests
14698
+ */
14699
+ function createOptionsEvent(replay) {
14700
+ const options = replay.getOptions();
14701
+ return {
14702
+ type: EventType.Custom,
14703
+ timestamp: Date.now(),
14704
+ data: {
14705
+ tag: 'options',
14706
+ payload: {
14707
+ sessionSampleRate: options.sessionSampleRate,
14708
+ errorSampleRate: options.errorSampleRate,
14709
+ useCompressionOption: options.useCompression,
14710
+ blockAllMedia: options.blockAllMedia,
14711
+ maskAllText: options.maskAllText,
14712
+ maskAllInputs: options.maskAllInputs,
14713
+ useCompression: replay.eventBuffer ? replay.eventBuffer.type === 'worker' : false,
14714
+ networkDetailHasUrls: options.networkDetailAllowUrls.length > 0,
14715
+ networkCaptureBodies: options.networkCaptureBodies,
14716
+ networkRequestHasHeaders: options.networkRequestHeaders.length > 0,
14717
+ networkResponseHasHeaders: options.networkResponseHeaders.length > 0,
14718
+ },
14719
+ },
14720
+ };
14721
+ }
14722
+
14723
+ /**
14724
+ * Add a "meta" event that contains a simplified view on current configuration
14725
+ * options. This should only be included on the first segment of a recording.
14726
+ */
14727
+ function addSettingsEvent(replay, isCheckout) {
14728
+ // Only need to add this event when sending the first segment
14729
+ if (!isCheckout || !replay.session || replay.session.segmentId !== 0) {
14730
+ return Promise.resolve(null);
14731
+ }
14732
+
14733
+ return addEvent(replay, createOptionsEvent(replay), false);
14734
+ }
14735
+
13449
14736
  /**
13450
14737
  * Create a replay envelope ready to be sent.
13451
14738
  * This includes both the replay event, as well as the recording data.
@@ -13557,11 +14844,9 @@ async function sendReplayRequest({
13557
14844
  recordingData,
13558
14845
  replayId,
13559
14846
  segmentId: segment_id,
13560
- includeReplayStartTimestamp,
13561
14847
  eventContext,
13562
14848
  timestamp,
13563
14849
  session,
13564
- options,
13565
14850
  }) {
13566
14851
  const preparedRecordingData = prepareRecordingData({
13567
14852
  recordingData,
@@ -13583,9 +14868,8 @@ async function sendReplayRequest({
13583
14868
  }
13584
14869
 
13585
14870
  const baseEvent = {
13586
- // @ts-ignore private api
13587
14871
  type: REPLAY_EVENT_NAME,
13588
- ...(includeReplayStartTimestamp ? { replay_start_timestamp: initialTimestamp / 1000 } : {}),
14872
+ replay_start_timestamp: initialTimestamp / 1000,
13589
14873
  timestamp: timestamp / 1000,
13590
14874
  error_ids: errorIds,
13591
14875
  trace_ids: traceIds,
@@ -13604,15 +14888,6 @@ async function sendReplayRequest({
13604
14888
  return;
13605
14889
  }
13606
14890
 
13607
- replayEvent.contexts = {
13608
- ...replayEvent.contexts,
13609
- replay: {
13610
- ...(replayEvent.contexts && replayEvent.contexts.replay),
13611
- session_sample_rate: options.sessionSampleRate,
13612
- error_sample_rate: options.errorSampleRate,
13613
- },
13614
- };
13615
-
13616
14891
  /*
13617
14892
  For reference, the fully built event looks something like this:
13618
14893
  {
@@ -13643,10 +14918,6 @@ async function sendReplayRequest({
13643
14918
  },
13644
14919
  "sdkProcessingMetadata": {},
13645
14920
  "contexts": {
13646
- "replay": {
13647
- "session_sample_rate": 1,
13648
- "error_sample_rate": 0,
13649
- },
13650
14921
  },
13651
14922
  }
13652
14923
  */
@@ -13772,9 +15043,11 @@ class ReplayContainer {
13772
15043
  __init2() {this.performanceEvents = [];}
13773
15044
 
13774
15045
  /**
13775
- * Recording can happen in one of two modes:
13776
- * * session: Record the whole session, sending it continuously
13777
- * * error: Always keep the last 60s of recording, and when an error occurs, send it immediately
15046
+ * Recording can happen in one of three modes:
15047
+ * - session: Record the whole session, sending it continuously
15048
+ * - buffer: Always keep the last 60s of recording, requires:
15049
+ * - having replaysOnErrorSampleRate > 0 to capture replay when an error occurs
15050
+ * - or calling `flush()` to send the replay
13778
15051
  */
13779
15052
  __init3() {this.recordingMode = 'session';}
13780
15053
 
@@ -13783,7 +15056,8 @@ class ReplayContainer {
13783
15056
  * @hidden
13784
15057
  */
13785
15058
  __init4() {this.timeouts = {
13786
- sessionIdle: SESSION_IDLE_DURATION,
15059
+ sessionIdlePause: SESSION_IDLE_PAUSE_DURATION,
15060
+ sessionIdleExpire: SESSION_IDLE_EXPIRE_DURATION,
13787
15061
  maxSessionLife: MAX_SESSION_LIFE,
13788
15062
  }; }
13789
15063
 
@@ -13827,7 +15101,6 @@ class ReplayContainer {
13827
15101
  errorIds: new Set(),
13828
15102
  traceIds: new Set(),
13829
15103
  urls: [],
13830
- earliestEvent: null,
13831
15104
  initialTimestamp: Date.now(),
13832
15105
  initialUrl: '',
13833
15106
  };}
@@ -13837,7 +15110,7 @@ class ReplayContainer {
13837
15110
  recordingOptions,
13838
15111
  }
13839
15112
 
13840
- ) {ReplayContainer.prototype.__init.call(this);ReplayContainer.prototype.__init2.call(this);ReplayContainer.prototype.__init3.call(this);ReplayContainer.prototype.__init4.call(this);ReplayContainer.prototype.__init5.call(this);ReplayContainer.prototype.__init6.call(this);ReplayContainer.prototype.__init7.call(this);ReplayContainer.prototype.__init8.call(this);ReplayContainer.prototype.__init9.call(this);ReplayContainer.prototype.__init10.call(this);ReplayContainer.prototype.__init11.call(this);ReplayContainer.prototype.__init12.call(this);ReplayContainer.prototype.__init13.call(this);ReplayContainer.prototype.__init14.call(this);ReplayContainer.prototype.__init15.call(this);ReplayContainer.prototype.__init16.call(this);ReplayContainer.prototype.__init17.call(this);
15113
+ ) {ReplayContainer.prototype.__init.call(this);ReplayContainer.prototype.__init2.call(this);ReplayContainer.prototype.__init3.call(this);ReplayContainer.prototype.__init4.call(this);ReplayContainer.prototype.__init5.call(this);ReplayContainer.prototype.__init6.call(this);ReplayContainer.prototype.__init7.call(this);ReplayContainer.prototype.__init8.call(this);ReplayContainer.prototype.__init9.call(this);ReplayContainer.prototype.__init10.call(this);ReplayContainer.prototype.__init11.call(this);ReplayContainer.prototype.__init12.call(this);ReplayContainer.prototype.__init13.call(this);ReplayContainer.prototype.__init14.call(this);ReplayContainer.prototype.__init15.call(this);ReplayContainer.prototype.__init16.call(this);ReplayContainer.prototype.__init17.call(this);ReplayContainer.prototype.__init18.call(this);
13841
15114
  this._recordingOptions = recordingOptions;
13842
15115
  this._options = options;
13843
15116
 
@@ -13867,49 +15140,102 @@ class ReplayContainer {
13867
15140
  }
13868
15141
 
13869
15142
  /**
13870
- * Initializes the plugin.
13871
- *
13872
- * Creates or loads a session, attaches listeners to varying events (DOM,
13873
- * _performanceObserver, Recording, Sentry SDK, etc)
15143
+ * Initializes the plugin based on sampling configuration. Should not be
15144
+ * called outside of constructor.
13874
15145
  */
13875
- start() {
13876
- this.setInitialState();
15146
+ initializeSampling() {
15147
+ const { errorSampleRate, sessionSampleRate } = this._options;
13877
15148
 
13878
- if (!this._loadAndCheckSession()) {
15149
+ // If neither sample rate is > 0, then do nothing - user will need to call one of
15150
+ // `start()` or `startBuffering` themselves.
15151
+ if (errorSampleRate <= 0 && sessionSampleRate <= 0) {
13879
15152
  return;
13880
15153
  }
13881
15154
 
13882
- // If there is no session, then something bad has happened - can't continue
13883
- if (!this.session) {
13884
- this._handleException(new Error('No session found'));
15155
+ // Otherwise if there is _any_ sample rate set, try to load an existing
15156
+ // session, or create a new one.
15157
+ const isSessionSampled = this._loadAndCheckSession();
15158
+
15159
+ if (!isSessionSampled) {
15160
+ // This should only occur if `errorSampleRate` is 0 and was unsampled for
15161
+ // session-based replay. In this case there is nothing to do.
13885
15162
  return;
13886
15163
  }
13887
15164
 
13888
- if (!this.session.sampled) {
13889
- // If session was not sampled, then we do not initialize the integration at all.
15165
+ if (!this.session) {
15166
+ // This should not happen, something wrong has occurred
15167
+ this._handleException(new Error('Unable to initialize and create session'));
13890
15168
  return;
13891
15169
  }
13892
15170
 
13893
- // If session is sampled for errors, then we need to set the recordingMode
13894
- // to 'error', which will configure recording with different options.
13895
- if (this.session.sampled === 'error') {
13896
- this.recordingMode = 'error';
15171
+ if (this.session.sampled && this.session.sampled !== 'session') {
15172
+ // If not sampled as session-based, then recording mode will be `buffer`
15173
+ // Note that we don't explicitly check if `sampled === 'buffer'` because we
15174
+ // could have sessions from Session storage that are still `error` from
15175
+ // prior SDK version.
15176
+ this.recordingMode = 'buffer';
13897
15177
  }
13898
15178
 
13899
- // setup() is generally called on page load or manually - in both cases we
13900
- // should treat it as an activity
13901
- this._updateSessionActivity();
15179
+ this._initializeRecording();
15180
+ }
13902
15181
 
13903
- this.eventBuffer = createEventBuffer({
13904
- useCompression: this._options.useCompression,
15182
+ /**
15183
+ * Start a replay regardless of sampling rate. Calling this will always
15184
+ * create a new session. Will throw an error if replay is already in progress.
15185
+ *
15186
+ * Creates or loads a session, attaches listeners to varying events (DOM,
15187
+ * _performanceObserver, Recording, Sentry SDK, etc)
15188
+ */
15189
+ start() {
15190
+ if (this._isEnabled && this.recordingMode === 'session') {
15191
+ throw new Error('Replay recording is already in progress');
15192
+ }
15193
+
15194
+ if (this._isEnabled && this.recordingMode === 'buffer') {
15195
+ throw new Error('Replay buffering is in progress, call `flush()` to save the replay');
15196
+ }
15197
+
15198
+ const previousSessionId = this.session && this.session.id;
15199
+
15200
+ const { session } = getSession({
15201
+ timeouts: this.timeouts,
15202
+ stickySession: Boolean(this._options.stickySession),
15203
+ currentSession: this.session,
15204
+ // This is intentional: create a new session-based replay when calling `start()`
15205
+ sessionSampleRate: 1,
15206
+ allowBuffering: false,
13905
15207
  });
13906
15208
 
13907
- this._addListeners();
15209
+ session.previousSessionId = previousSessionId;
15210
+ this.session = session;
13908
15211
 
13909
- // Need to set as enabled before we start recording, as `record()` can trigger a flush with a new checkout
13910
- this._isEnabled = true;
15212
+ this._initializeRecording();
15213
+ }
13911
15214
 
13912
- this.startRecording();
15215
+ /**
15216
+ * Start replay buffering. Buffers until `flush()` is called or, if
15217
+ * `replaysOnErrorSampleRate` > 0, an error occurs.
15218
+ */
15219
+ startBuffering() {
15220
+ if (this._isEnabled) {
15221
+ throw new Error('Replay recording is already in progress');
15222
+ }
15223
+
15224
+ const previousSessionId = this.session && this.session.id;
15225
+
15226
+ const { session } = getSession({
15227
+ timeouts: this.timeouts,
15228
+ stickySession: Boolean(this._options.stickySession),
15229
+ currentSession: this.session,
15230
+ sessionSampleRate: 0,
15231
+ allowBuffering: true,
15232
+ });
15233
+
15234
+ session.previousSessionId = previousSessionId;
15235
+ this.session = session;
15236
+
15237
+ this.recordingMode = 'buffer';
15238
+ this._initializeRecording();
13913
15239
  }
13914
15240
 
13915
15241
  /**
@@ -13924,7 +15250,7 @@ class ReplayContainer {
13924
15250
  // When running in error sampling mode, we need to overwrite `checkoutEveryNms`
13925
15251
  // Without this, it would record forever, until an error happens, which we don't want
13926
15252
  // instead, we'll always keep the last 60 seconds of replay before an error happened
13927
- ...(this.recordingMode === 'error' && { checkoutEveryNms: ERROR_CHECKOUT_TIME }),
15253
+ ...(this.recordingMode === 'buffer' && { checkoutEveryNms: BUFFER_CHECKOUT_TIME }),
13928
15254
  emit: getHandleRecordingEmit(this),
13929
15255
  onMutation: this._onMutationHandler,
13930
15256
  });
@@ -13935,17 +15261,18 @@ class ReplayContainer {
13935
15261
 
13936
15262
  /**
13937
15263
  * Stops the recording, if it was running.
13938
- * Returns true if it was stopped, else false.
15264
+ *
15265
+ * Returns true if it was previously stopped, or is now stopped,
15266
+ * otherwise false.
13939
15267
  */
13940
15268
  stopRecording() {
13941
15269
  try {
13942
15270
  if (this._stopRecording) {
13943
15271
  this._stopRecording();
13944
15272
  this._stopRecording = undefined;
13945
- return true;
13946
15273
  }
13947
15274
 
13948
- return false;
15275
+ return true;
13949
15276
  } catch (err) {
13950
15277
  this._handleException(err);
13951
15278
  return false;
@@ -13956,7 +15283,7 @@ class ReplayContainer {
13956
15283
  * Currently, this needs to be manually called (e.g. for tests). Sentry SDK
13957
15284
  * does not support a teardown
13958
15285
  */
13959
- stop(reason) {
15286
+ async stop(reason) {
13960
15287
  if (!this._isEnabled) {
13961
15288
  return;
13962
15289
  }
@@ -13972,12 +15299,24 @@ class ReplayContainer {
13972
15299
  log(msg);
13973
15300
  }
13974
15301
 
15302
+ // We can't move `_isEnabled` after awaiting a flush, otherwise we can
15303
+ // enter into an infinite loop when `stop()` is called while flushing.
13975
15304
  this._isEnabled = false;
13976
15305
  this._removeListeners();
13977
15306
  this.stopRecording();
15307
+
15308
+ this._debouncedFlush.cancel();
15309
+ // See comment above re: `_isEnabled`, we "force" a flush, ignoring the
15310
+ // `_isEnabled` state of the plugin since it was disabled above.
15311
+ await this._flush({ force: true });
15312
+
15313
+ // After flush, destroy event buffer
13978
15314
  this.eventBuffer && this.eventBuffer.destroy();
13979
15315
  this.eventBuffer = null;
13980
- this._debouncedFlush.cancel();
15316
+
15317
+ // Clear session from session storage, note this means if a new session
15318
+ // is started after, it will not have `previousSessionId`
15319
+ clearSession(this);
13981
15320
  } catch (err) {
13982
15321
  this._handleException(err);
13983
15322
  }
@@ -14008,6 +15347,45 @@ class ReplayContainer {
14008
15347
  this.startRecording();
14009
15348
  }
14010
15349
 
15350
+ /**
15351
+ * If not in "session" recording mode, flush event buffer which will create a new replay.
15352
+ * Unless `continueRecording` is false, the replay will continue to record and
15353
+ * behave as a "session"-based replay.
15354
+ *
15355
+ * Otherwise, queue up a flush.
15356
+ */
15357
+ async sendBufferedReplayOrFlush({ continueRecording = true } = {}) {
15358
+ if (this.recordingMode === 'session') {
15359
+ return this.flushImmediate();
15360
+ }
15361
+
15362
+ // Allow flush to complete before resuming as a session recording, otherwise
15363
+ // the checkout from `startRecording` may be included in the payload.
15364
+ // Prefer to keep the error replay as a separate (and smaller) segment
15365
+ // than the session replay.
15366
+ await this.flushImmediate();
15367
+
15368
+ const hasStoppedRecording = this.stopRecording();
15369
+
15370
+ if (!continueRecording || !hasStoppedRecording) {
15371
+ return;
15372
+ }
15373
+
15374
+ // Re-start recording, but in "session" recording mode
15375
+
15376
+ // Reset all "capture on error" configuration before
15377
+ // starting a new recording
15378
+ this.recordingMode = 'session';
15379
+
15380
+ // Once this session ends, we do not want to refresh it
15381
+ if (this.session) {
15382
+ this.session.shouldRefresh = false;
15383
+ this._maybeSaveSession();
15384
+ }
15385
+
15386
+ this.startRecording();
15387
+ }
15388
+
14011
15389
  /**
14012
15390
  * We want to batch uploads of replay events. Save events only if
14013
15391
  * `<flushMinDelay>` milliseconds have elapsed since the last event
@@ -14017,12 +15395,12 @@ class ReplayContainer {
14017
15395
  * processing and hand back control to caller.
14018
15396
  */
14019
15397
  addUpdate(cb) {
14020
- // We need to always run `cb` (e.g. in the case of `this.recordingMode == 'error'`)
15398
+ // We need to always run `cb` (e.g. in the case of `this.recordingMode == 'buffer'`)
14021
15399
  const cbResult = cb();
14022
15400
 
14023
15401
  // If this option is turned on then we will only want to call `flush`
14024
15402
  // explicitly
14025
- if (this.recordingMode === 'error') {
15403
+ if (this.recordingMode === 'buffer') {
14026
15404
  return;
14027
15405
  }
14028
15406
 
@@ -14094,12 +15472,12 @@ class ReplayContainer {
14094
15472
  const oldSessionId = this.getSessionId();
14095
15473
 
14096
15474
  // Prevent starting a new session if the last user activity is older than
14097
- // SESSION_IDLE_DURATION. Otherwise non-user activity can trigger a new
15475
+ // SESSION_IDLE_PAUSE_DURATION. Otherwise non-user activity can trigger a new
14098
15476
  // session+recording. This creates noisy replays that do not have much
14099
15477
  // content in them.
14100
15478
  if (
14101
15479
  this._lastActivity &&
14102
- isExpired(this._lastActivity, this.timeouts.sessionIdle) &&
15480
+ isExpired(this._lastActivity, this.timeouts.sessionIdlePause) &&
14103
15481
  this.session &&
14104
15482
  this.session.sampled === 'session'
14105
15483
  ) {
@@ -14149,6 +15527,30 @@ class ReplayContainer {
14149
15527
  this._context.urls.push(url);
14150
15528
  }
14151
15529
 
15530
+ /**
15531
+ * Initialize and start all listeners to varying events (DOM,
15532
+ * Performance Observer, Recording, Sentry SDK, etc)
15533
+ */
15534
+ _initializeRecording() {
15535
+ this.setInitialState();
15536
+
15537
+ // this method is generally called on page load or manually - in both cases
15538
+ // we should treat it as an activity
15539
+ this._updateSessionActivity();
15540
+
15541
+ this.eventBuffer = createEventBuffer({
15542
+ useCompression: this._options.useCompression,
15543
+ });
15544
+
15545
+ this._removeListeners();
15546
+ this._addListeners();
15547
+
15548
+ // Need to set as enabled before we start recording, as `record()` can trigger a flush with a new checkout
15549
+ this._isEnabled = true;
15550
+
15551
+ this.startRecording();
15552
+ }
15553
+
14152
15554
  /** A wrapper to conditionally capture exceptions. */
14153
15555
  _handleException(error) {
14154
15556
  (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error('[Replay]', error);
@@ -14168,7 +15570,7 @@ class ReplayContainer {
14168
15570
  stickySession: Boolean(this._options.stickySession),
14169
15571
  currentSession: this.session,
14170
15572
  sessionSampleRate: this._options.sessionSampleRate,
14171
- errorSampleRate: this._options.errorSampleRate,
15573
+ allowBuffering: this._options.errorSampleRate > 0,
14172
15574
  });
14173
15575
 
14174
15576
  // If session was newly created (i.e. was not loaded from storage), then
@@ -14185,7 +15587,7 @@ class ReplayContainer {
14185
15587
  this.session = session;
14186
15588
 
14187
15589
  if (!this.session.sampled) {
14188
- this.stop('session unsampled');
15590
+ void this.stop('session unsampled');
14189
15591
  return false;
14190
15592
  }
14191
15593
 
@@ -14200,6 +15602,7 @@ class ReplayContainer {
14200
15602
  WINDOW.document.addEventListener('visibilitychange', this._handleVisibilityChange);
14201
15603
  WINDOW.addEventListener('blur', this._handleWindowBlur);
14202
15604
  WINDOW.addEventListener('focus', this._handleWindowFocus);
15605
+ WINDOW.addEventListener('keydown', this._handleKeyboardEvent);
14203
15606
 
14204
15607
  // There is no way to remove these listeners, so ensure they are only added once
14205
15608
  if (!this._hasInitializedCoreListeners) {
@@ -14228,6 +15631,7 @@ class ReplayContainer {
14228
15631
 
14229
15632
  WINDOW.removeEventListener('blur', this._handleWindowBlur);
14230
15633
  WINDOW.removeEventListener('focus', this._handleWindowFocus);
15634
+ WINDOW.removeEventListener('keydown', this._handleKeyboardEvent);
14231
15635
 
14232
15636
  if (this._performanceObserver) {
14233
15637
  this._performanceObserver.disconnect();
@@ -14278,6 +15682,11 @@ class ReplayContainer {
14278
15682
  this._doChangeToForegroundTasks(breadcrumb);
14279
15683
  };}
14280
15684
 
15685
+ /** Ensure page remains active when a key is pressed. */
15686
+ __init16() {this._handleKeyboardEvent = (event) => {
15687
+ handleKeyboardEvent(this, event);
15688
+ };}
15689
+
14281
15690
  /**
14282
15691
  * Tasks to run when we consider a page to be hidden (via blurring and/or visibility)
14283
15692
  */
@@ -14309,7 +15718,7 @@ class ReplayContainer {
14309
15718
  const isSessionActive = this.checkAndHandleExpiredSession();
14310
15719
 
14311
15720
  if (!isSessionActive) {
14312
- // If the user has come back to the page within SESSION_IDLE_DURATION
15721
+ // If the user has come back to the page within SESSION_IDLE_PAUSE_DURATION
14313
15722
  // ms, we will re-use the existing session, otherwise create a new
14314
15723
  // session
14315
15724
  (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Document has become active, but session has expired');
@@ -14357,7 +15766,7 @@ class ReplayContainer {
14357
15766
  _createCustomBreadcrumb(breadcrumb) {
14358
15767
  this.addUpdate(() => {
14359
15768
  void addEvent(this, {
14360
- type: EventType.Custom,
15769
+ type: EventType$1.Custom,
14361
15770
  timestamp: breadcrumb.timestamp || 0,
14362
15771
  data: {
14363
15772
  tag: 'breadcrumb',
@@ -14383,7 +15792,7 @@ class ReplayContainer {
14383
15792
  * Only flush if `this.recordingMode === 'session'`
14384
15793
  */
14385
15794
  _conditionalFlush() {
14386
- if (this.recordingMode === 'error') {
15795
+ if (this.recordingMode === 'buffer') {
14387
15796
  return;
14388
15797
  }
14389
15798
 
@@ -14398,22 +15807,35 @@ class ReplayContainer {
14398
15807
  this._context.errorIds.clear();
14399
15808
  this._context.traceIds.clear();
14400
15809
  this._context.urls = [];
14401
- this._context.earliestEvent = null;
15810
+ }
15811
+
15812
+ /** Update the initial timestamp based on the buffer content. */
15813
+ _updateInitialTimestampFromEventBuffer() {
15814
+ const { session, eventBuffer } = this;
15815
+ if (!session || !eventBuffer) {
15816
+ return;
15817
+ }
15818
+
15819
+ // we only ever update this on the initial segment
15820
+ if (session.segmentId) {
15821
+ return;
15822
+ }
15823
+
15824
+ const earliestEvent = eventBuffer.getEarliestTimestamp();
15825
+ if (earliestEvent && earliestEvent < this._context.initialTimestamp) {
15826
+ this._context.initialTimestamp = earliestEvent;
15827
+ }
14402
15828
  }
14403
15829
 
14404
15830
  /**
14405
15831
  * Return and clear _context
14406
15832
  */
14407
15833
  _popEventContext() {
14408
- if (this._context.earliestEvent && this._context.earliestEvent < this._context.initialTimestamp) {
14409
- this._context.initialTimestamp = this._context.earliestEvent;
14410
- }
14411
-
14412
15834
  const _context = {
14413
15835
  initialTimestamp: this._context.initialTimestamp,
14414
15836
  initialUrl: this._context.initialUrl,
14415
- errorIds: Array.from(this._context.errorIds).filter(Boolean),
14416
- traceIds: Array.from(this._context.traceIds).filter(Boolean),
15837
+ errorIds: Array.from(this._context.errorIds),
15838
+ traceIds: Array.from(this._context.traceIds),
14417
15839
  urls: this._context.urls,
14418
15840
  };
14419
15841
 
@@ -14452,6 +15874,9 @@ class ReplayContainer {
14452
15874
  }
14453
15875
 
14454
15876
  try {
15877
+ // This uses the data from the eventBuffer, so we need to call this before `finish()
15878
+ this._updateInitialTimestampFromEventBuffer();
15879
+
14455
15880
  // Note this empties the event buffer regardless of outcome of sending replay
14456
15881
  const recordingData = await this.eventBuffer.finish();
14457
15882
 
@@ -14467,7 +15892,6 @@ class ReplayContainer {
14467
15892
  replayId,
14468
15893
  recordingData,
14469
15894
  segmentId,
14470
- includeReplayStartTimestamp: segmentId === 0,
14471
15895
  eventContext,
14472
15896
  session: this.session,
14473
15897
  options: this.getOptions(),
@@ -14479,7 +15903,7 @@ class ReplayContainer {
14479
15903
  // This means we retried 3 times and all of them failed,
14480
15904
  // or we ran into a problem we don't want to retry, like rate limiting.
14481
15905
  // In this case, we want to completely stop the replay - otherwise, we may get inconsistent segments
14482
- this.stop('sendReplay');
15906
+ void this.stop('sendReplay');
14483
15907
 
14484
15908
  const client = getCurrentHub().getClient();
14485
15909
 
@@ -14493,8 +15917,12 @@ class ReplayContainer {
14493
15917
  * Flush recording data to Sentry. Creates a lock so that only a single flush
14494
15918
  * can be active at a time. Do not call this directly.
14495
15919
  */
14496
- __init16() {this._flush = async () => {
14497
- if (!this._isEnabled) {
15920
+ __init17() {this._flush = async ({
15921
+ force = false,
15922
+ }
15923
+
15924
+ = {}) => {
15925
+ if (!this._isEnabled && !force) {
14498
15926
  // This can happen if e.g. the replay was stopped because of exceeding the retry limit
14499
15927
  return;
14500
15928
  }
@@ -14544,7 +15972,7 @@ class ReplayContainer {
14544
15972
  }
14545
15973
 
14546
15974
  /** Handler for rrweb.record.onMutation */
14547
- __init17() {this._onMutationHandler = (mutations) => {
15975
+ __init18() {this._onMutationHandler = (mutations) => {
14548
15976
  const count = mutations.length;
14549
15977
 
14550
15978
  const mutationLimit = this._options._experiments.mutationLimit || 0;
@@ -14678,6 +16106,8 @@ function isElectronNodeRenderer() {
14678
16106
  const MEDIA_SELECTORS =
14679
16107
  'img,image,svg,video,object,picture,embed,map,audio,link[rel="icon"],link[rel="apple-touch-icon"]';
14680
16108
 
16109
+ const DEFAULT_NETWORK_HEADERS = ['content-length', 'content-type', 'accept'];
16110
+
14681
16111
  let _initialized = false;
14682
16112
 
14683
16113
  /**
@@ -14718,6 +16148,11 @@ class Replay {
14718
16148
  maskAllInputs = true,
14719
16149
  blockAllMedia = true,
14720
16150
 
16151
+ networkDetailAllowUrls = [],
16152
+ networkCaptureBodies = true,
16153
+ networkRequestHeaders = [],
16154
+ networkResponseHeaders = [],
16155
+
14721
16156
  mask = [],
14722
16157
  unmask = [],
14723
16158
  block = [],
@@ -14776,6 +16211,13 @@ class Replay {
14776
16211
  errorSampleRate,
14777
16212
  useCompression,
14778
16213
  blockAllMedia,
16214
+ maskAllInputs,
16215
+ maskAllText,
16216
+ networkDetailAllowUrls,
16217
+ networkCaptureBodies,
16218
+ networkRequestHeaders: _getMergedNetworkHeaders(networkRequestHeaders),
16219
+ networkResponseHeaders: _getMergedNetworkHeaders(networkResponseHeaders),
16220
+
14779
16221
  _experiments,
14780
16222
  };
14781
16223
 
@@ -14829,14 +16271,7 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`,
14829
16271
  }
14830
16272
 
14831
16273
  /**
14832
- * We previously used to create a transaction in `setupOnce` and it would
14833
- * potentially create a transaction before some native SDK integrations have run
14834
- * and applied their own global event processor. An example is:
14835
- * https://github.com/getsentry/sentry-javascript/blob/b47ceafbdac7f8b99093ce6023726ad4687edc48/packages/browser/src/integrations/useragent.ts
14836
- *
14837
- * So we call `replay.setup` in next event loop as a workaround to wait for other
14838
- * global event processors to finish. This is no longer needed, but keeping it
14839
- * here to avoid any future issues.
16274
+ * Setup and initialize replay container
14840
16275
  */
14841
16276
  setupOnce() {
14842
16277
  if (!isBrowser()) {
@@ -14845,12 +16280,20 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`,
14845
16280
 
14846
16281
  this._setup();
14847
16282
 
14848
- // XXX: See method comments above
14849
- setTimeout(() => this.start());
16283
+ // Once upon a time, we tried to create a transaction in `setupOnce` and it would
16284
+ // potentially create a transaction before some native SDK integrations have run
16285
+ // and applied their own global event processor. An example is:
16286
+ // https://github.com/getsentry/sentry-javascript/blob/b47ceafbdac7f8b99093ce6023726ad4687edc48/packages/browser/src/integrations/useragent.ts
16287
+ //
16288
+ // So we call `this._initialize()` in next event loop as a workaround to wait for other
16289
+ // global event processors to finish. This is no longer needed, but keeping it
16290
+ // here to avoid any future issues.
16291
+ setTimeout(() => this._initialize());
14850
16292
  }
14851
16293
 
14852
16294
  /**
14853
- * Initializes the plugin.
16295
+ * Start a replay regardless of sampling rate. Calling this will always
16296
+ * create a new session. Will throw an error if replay is already in progress.
14854
16297
  *
14855
16298
  * Creates or loads a session, attaches listeners to varying events (DOM,
14856
16299
  * PerformanceObserver, Recording, Sentry SDK, etc)
@@ -14863,27 +16306,64 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`,
14863
16306
  this._replay.start();
14864
16307
  }
14865
16308
 
16309
+ /**
16310
+ * Start replay buffering. Buffers until `flush()` is called or, if
16311
+ * `replaysOnErrorSampleRate` > 0, until an error occurs.
16312
+ */
16313
+ startBuffering() {
16314
+ if (!this._replay) {
16315
+ return;
16316
+ }
16317
+
16318
+ this._replay.startBuffering();
16319
+ }
16320
+
14866
16321
  /**
14867
16322
  * Currently, this needs to be manually called (e.g. for tests). Sentry SDK
14868
16323
  * does not support a teardown
14869
16324
  */
14870
16325
  stop() {
14871
16326
  if (!this._replay) {
14872
- return;
16327
+ return Promise.resolve();
16328
+ }
16329
+
16330
+ return this._replay.stop();
16331
+ }
16332
+
16333
+ /**
16334
+ * If not in "session" recording mode, flush event buffer which will create a new replay.
16335
+ * Unless `continueRecording` is false, the replay will continue to record and
16336
+ * behave as a "session"-based replay.
16337
+ *
16338
+ * Otherwise, queue up a flush.
16339
+ */
16340
+ flush(options) {
16341
+ if (!this._replay || !this._replay.isEnabled()) {
16342
+ return Promise.resolve();
14873
16343
  }
14874
16344
 
14875
- this._replay.stop();
16345
+ return this._replay.sendBufferedReplayOrFlush(options);
14876
16346
  }
14877
16347
 
14878
16348
  /**
14879
- * Immediately send all pending events.
16349
+ * Get the current session ID.
14880
16350
  */
14881
- flush() {
16351
+ getReplayId() {
14882
16352
  if (!this._replay || !this._replay.isEnabled()) {
14883
16353
  return;
14884
16354
  }
14885
16355
 
14886
- return this._replay.flushImmediate();
16356
+ return this._replay.getSessionId();
16357
+ }
16358
+ /**
16359
+ * Initializes replay.
16360
+ */
16361
+ _initialize() {
16362
+ if (!this._replay) {
16363
+ return;
16364
+ }
16365
+
16366
+ this._replay.initializeSampling();
14887
16367
  }
14888
16368
 
14889
16369
  /** Setup the integration. */
@@ -14934,6 +16414,10 @@ function loadReplayOptionsFromClient(initialOptions) {
14934
16414
  return finalOptions;
14935
16415
  }
14936
16416
 
16417
+ function _getMergedNetworkHeaders(headers) {
16418
+ return [...DEFAULT_NETWORK_HEADERS, ...headers.map(header => header.toLowerCase())];
16419
+ }
16420
+
14937
16421
  /**
14938
16422
  * Polyfill for the optional chain operator, `?.`, given previous conversion of the expression into an array of values,
14939
16423
  * descriptors, and functions.
@@ -15371,6 +16855,9 @@ class Postgres {
15371
16855
  const span = _optionalChain([parentSpan, 'optionalAccess', _6 => _6.startChild, 'call', _7 => _7({
15372
16856
  description: typeof config === 'string' ? config : (config ).text,
15373
16857
  op: 'db',
16858
+ data: {
16859
+ 'db.system': 'postgresql',
16860
+ },
15374
16861
  })]);
15375
16862
 
15376
16863
  if (typeof callback === 'function') {
@@ -15447,6 +16934,9 @@ class Mysql {constructor() { Mysql.prototype.__init.call(this); }
15447
16934
  const span = _optionalChain([parentSpan, 'optionalAccess', _4 => _4.startChild, 'call', _5 => _5({
15448
16935
  description: typeof options === 'string' ? options : (options ).sql,
15449
16936
  op: 'db',
16937
+ data: {
16938
+ 'db.system': 'mysql',
16939
+ },
15450
16940
  })]);
15451
16941
 
15452
16942
  if (typeof callback === 'function') {
@@ -15668,6 +17158,7 @@ class Mongo {
15668
17158
  collectionName: collection.collectionName,
15669
17159
  dbName: collection.dbName,
15670
17160
  namespace: collection.namespace,
17161
+ 'db.system': 'mongodb',
15671
17162
  };
15672
17163
  const spanContext = {
15673
17164
  op: 'db',
@@ -15754,31 +17245,15 @@ class Prisma {
15754
17245
  }
15755
17246
 
15756
17247
  this._client.$use((params, next) => {
15757
- const scope = getCurrentHub().getScope();
15758
- const parentSpan = _optionalChain([scope, 'optionalAccess', _2 => _2.getSpan, 'call', _3 => _3()]);
15759
-
15760
17248
  const action = params.action;
15761
17249
  const model = params.model;
15762
-
15763
- const span = _optionalChain([parentSpan, 'optionalAccess', _4 => _4.startChild, 'call', _5 => _5({
15764
- description: model ? `${model} ${action}` : action,
15765
- op: 'db.sql.prisma',
15766
- })]);
15767
-
15768
- const rv = next(params);
15769
-
15770
- if (isThenable(rv)) {
15771
- return rv.then((res) => {
15772
- _optionalChain([span, 'optionalAccess', _6 => _6.finish, 'call', _7 => _7()]);
15773
- return res;
15774
- });
15775
- }
15776
-
15777
- _optionalChain([span, 'optionalAccess', _8 => _8.finish, 'call', _9 => _9()]);
15778
- return rv;
17250
+ return trace(
17251
+ { name: model ? `${model} ${action}` : action, op: 'db.sql.prisma', data: { 'db.system': 'prisma' } },
17252
+ () => next(params),
17253
+ );
15779
17254
  });
15780
17255
  }
15781
- }Prisma.__initStatic();
17256
+ } Prisma.__initStatic();
15782
17257
 
15783
17258
  /** Tracing integration for graphql package */
15784
17259
  class GraphQL {constructor() { GraphQL.prototype.__init.call(this); }
@@ -28952,7 +30427,7 @@ const configGenerator = () => {
28952
30427
  let release;
28953
30428
  try {
28954
30429
  environment !== null && environment !== void 0 ? environment : (environment = "staging");
28955
- release !== null && release !== void 0 ? release : (release = "1.1.23-binary-002");
30430
+ release !== null && release !== void 0 ? release : (release = "1.1.23-binary-004");
28956
30431
  }
28957
30432
  catch (_a) {
28958
30433
  console.error('sentry configGenerator error');
@@ -42019,7 +43494,9 @@ var AsapAction = (function (_super) {
42019
43494
  var actions = scheduler.actions;
42020
43495
  if (id != null && ((_a = actions[actions.length - 1]) === null || _a === void 0 ? void 0 : _a.id) !== id) {
42021
43496
  immediateProvider.clearImmediate(id);
42022
- scheduler._scheduled = undefined;
43497
+ if (scheduler._scheduled === id) {
43498
+ scheduler._scheduled = undefined;
43499
+ }
42023
43500
  }
42024
43501
  return undefined;
42025
43502
  };