@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/cjs/index.js CHANGED
@@ -973,6 +973,12 @@ function createStackParser(...parsers) {
973
973
  // Remove webpack (error: *) wrappers
974
974
  const cleanedLine = WEBPACK_ERROR_REGEXP.test(line) ? line.replace(WEBPACK_ERROR_REGEXP, '$1') : line;
975
975
 
976
+ // https://github.com/getsentry/sentry-javascript/issues/7813
977
+ // Skip Error: lines
978
+ if (cleanedLine.match(/\S*Error: /)) {
979
+ continue;
980
+ }
981
+
976
982
  for (const parser of sortedParsers) {
977
983
  const frame = parser(cleanedLine);
978
984
 
@@ -1157,6 +1163,8 @@ function supportsHistory() {
1157
1163
  // eslint-disable-next-line deprecation/deprecation
1158
1164
  const WINDOW$3 = getGlobalObject();
1159
1165
 
1166
+ const SENTRY_XHR_DATA_KEY = '__sentry_xhr_v2__';
1167
+
1160
1168
  /**
1161
1169
  * Instrument native APIs to call handlers that can be used to create breadcrumbs, APM spans etc.
1162
1170
  * - Console API
@@ -1269,11 +1277,13 @@ function instrumentFetch() {
1269
1277
 
1270
1278
  fill(WINDOW$3, 'fetch', function (originalFetch) {
1271
1279
  return function (...args) {
1280
+ const { method, url } = parseFetchArgs(args);
1281
+
1272
1282
  const handlerData = {
1273
1283
  args,
1274
1284
  fetchData: {
1275
- method: getFetchMethod(args),
1276
- url: getFetchUrl(args),
1285
+ method,
1286
+ url,
1277
1287
  },
1278
1288
  startTimestamp: Date.now(),
1279
1289
  };
@@ -1308,29 +1318,53 @@ function instrumentFetch() {
1308
1318
  });
1309
1319
  }
1310
1320
 
1311
- /* eslint-disable @typescript-eslint/no-unsafe-member-access */
1312
- /** Extract `method` from fetch call arguments */
1313
- function getFetchMethod(fetchArgs = []) {
1314
- if ('Request' in WINDOW$3 && isInstanceOf(fetchArgs[0], Request) && fetchArgs[0].method) {
1315
- return String(fetchArgs[0].method).toUpperCase();
1321
+ function hasProp(obj, prop) {
1322
+ return !!obj && typeof obj === 'object' && !!(obj )[prop];
1323
+ }
1324
+
1325
+ function getUrlFromResource(resource) {
1326
+ if (typeof resource === 'string') {
1327
+ return resource;
1316
1328
  }
1317
- if (fetchArgs[1] && fetchArgs[1].method) {
1318
- return String(fetchArgs[1].method).toUpperCase();
1329
+
1330
+ if (!resource) {
1331
+ return '';
1332
+ }
1333
+
1334
+ if (hasProp(resource, 'url')) {
1335
+ return resource.url;
1319
1336
  }
1320
- return 'GET';
1337
+
1338
+ if (resource.toString) {
1339
+ return resource.toString();
1340
+ }
1341
+
1342
+ return '';
1321
1343
  }
1322
1344
 
1323
- /** Extract `url` from fetch call arguments */
1324
- function getFetchUrl(fetchArgs = []) {
1325
- if (typeof fetchArgs[0] === 'string') {
1326
- return fetchArgs[0];
1345
+ /**
1346
+ * Parses the fetch arguments to find the used Http method and the url of the request
1347
+ */
1348
+ function parseFetchArgs(fetchArgs) {
1349
+ if (fetchArgs.length === 0) {
1350
+ return { method: 'GET', url: '' };
1327
1351
  }
1328
- if ('Request' in WINDOW$3 && isInstanceOf(fetchArgs[0], Request)) {
1329
- return fetchArgs[0].url;
1352
+
1353
+ if (fetchArgs.length === 2) {
1354
+ const [url, options] = fetchArgs ;
1355
+
1356
+ return {
1357
+ url: getUrlFromResource(url),
1358
+ method: hasProp(options, 'method') ? String(options.method).toUpperCase() : 'GET',
1359
+ };
1330
1360
  }
1331
- return String(fetchArgs[0]);
1361
+
1362
+ const arg = fetchArgs[0];
1363
+ return {
1364
+ url: getUrlFromResource(arg ),
1365
+ method: hasProp(arg, 'method') ? String(arg.method).toUpperCase() : 'GET',
1366
+ };
1332
1367
  }
1333
- /* eslint-enable @typescript-eslint/no-unsafe-member-access */
1334
1368
 
1335
1369
  /** JSDoc */
1336
1370
  function instrumentXHR() {
@@ -1343,10 +1377,11 @@ function instrumentXHR() {
1343
1377
  fill(xhrproto, 'open', function (originalOpen) {
1344
1378
  return function ( ...args) {
1345
1379
  const url = args[1];
1346
- const xhrInfo = (this.__sentry_xhr__ = {
1380
+ const xhrInfo = (this[SENTRY_XHR_DATA_KEY] = {
1347
1381
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
1348
1382
  method: isString$2(args[0]) ? args[0].toUpperCase() : args[0],
1349
1383
  url: args[1],
1384
+ request_headers: {},
1350
1385
  });
1351
1386
 
1352
1387
  // if Sentry key appears in URL, don't capture it as a request
@@ -1357,7 +1392,7 @@ function instrumentXHR() {
1357
1392
 
1358
1393
  const onreadystatechangeHandler = () => {
1359
1394
  // For whatever reason, this is not the same instance here as from the outer method
1360
- const xhrInfo = this.__sentry_xhr__;
1395
+ const xhrInfo = this[SENTRY_XHR_DATA_KEY];
1361
1396
 
1362
1397
  if (!xhrInfo) {
1363
1398
  return;
@@ -1392,14 +1427,32 @@ function instrumentXHR() {
1392
1427
  this.addEventListener('readystatechange', onreadystatechangeHandler);
1393
1428
  }
1394
1429
 
1430
+ // Intercepting `setRequestHeader` to access the request headers of XHR instance.
1431
+ // This will only work for user/library defined headers, not for the default/browser-assigned headers.
1432
+ // Request cookies are also unavailable for XHR, as `Cookie` header can't be defined by `setRequestHeader`.
1433
+ fill(this, 'setRequestHeader', function (original) {
1434
+ return function ( ...setRequestHeaderArgs) {
1435
+ const [header, value] = setRequestHeaderArgs ;
1436
+
1437
+ const xhrInfo = this[SENTRY_XHR_DATA_KEY];
1438
+
1439
+ if (xhrInfo) {
1440
+ xhrInfo.request_headers[header.toLowerCase()] = value;
1441
+ }
1442
+
1443
+ return original.apply(this, setRequestHeaderArgs);
1444
+ };
1445
+ });
1446
+
1395
1447
  return originalOpen.apply(this, args);
1396
1448
  };
1397
1449
  });
1398
1450
 
1399
1451
  fill(xhrproto, 'send', function (originalSend) {
1400
1452
  return function ( ...args) {
1401
- if (this.__sentry_xhr__ && args[0] !== undefined) {
1402
- this.__sentry_xhr__.body = args[0];
1453
+ const sentryXhrData = this[SENTRY_XHR_DATA_KEY];
1454
+ if (sentryXhrData && args[0] !== undefined) {
1455
+ sentryXhrData.body = args[0];
1403
1456
  }
1404
1457
 
1405
1458
  triggerHandlers('xhr', {
@@ -1698,13 +1751,15 @@ function instrumentError() {
1698
1751
  url,
1699
1752
  });
1700
1753
 
1701
- if (_oldOnErrorHandler) {
1754
+ if (_oldOnErrorHandler && !_oldOnErrorHandler.__SENTRY_LOADER__) {
1702
1755
  // eslint-disable-next-line prefer-rest-params
1703
1756
  return _oldOnErrorHandler.apply(this, arguments);
1704
1757
  }
1705
1758
 
1706
1759
  return false;
1707
1760
  };
1761
+
1762
+ WINDOW$3.onerror.__SENTRY_INSTRUMENTED__ = true;
1708
1763
  }
1709
1764
 
1710
1765
  let _oldOnUnhandledRejectionHandler = null;
@@ -1715,13 +1770,15 @@ function instrumentUnhandledRejection() {
1715
1770
  WINDOW$3.onunhandledrejection = function (e) {
1716
1771
  triggerHandlers('unhandledrejection', e);
1717
1772
 
1718
- if (_oldOnUnhandledRejectionHandler) {
1773
+ if (_oldOnUnhandledRejectionHandler && !_oldOnUnhandledRejectionHandler.__SENTRY_LOADER__) {
1719
1774
  // eslint-disable-next-line prefer-rest-params
1720
1775
  return _oldOnUnhandledRejectionHandler.apply(this, arguments);
1721
1776
  }
1722
1777
 
1723
1778
  return true;
1724
1779
  };
1780
+
1781
+ WINDOW$3.onunhandledrejection.__SENTRY_INSTRUMENTED__ = true;
1725
1782
  }
1726
1783
 
1727
1784
  /* eslint-disable @typescript-eslint/no-unsafe-member-access */
@@ -2019,7 +2076,7 @@ function loadModule(moduleName) {
2019
2076
  * @returns A normalized version of the object, or `"**non-serializable**"` if any errors are thrown during normalization.
2020
2077
  */
2021
2078
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
2022
- function normalize(input, depth = +Infinity, maxProperties = +Infinity) {
2079
+ function normalize(input, depth = 100, maxProperties = +Infinity) {
2023
2080
  try {
2024
2081
  // since we're at the outermost level, we don't provide a key
2025
2082
  return visit('', input, depth, maxProperties);
@@ -2065,7 +2122,10 @@ function visit(
2065
2122
  const [memoize, unmemoize] = memo;
2066
2123
 
2067
2124
  // Get the simple cases out of the way first
2068
- if (value === null || (['number', 'boolean', 'string'].includes(typeof value) && !isNaN$1(value))) {
2125
+ if (
2126
+ value == null || // this matches null and undefined -> eqeq not eqeqeq
2127
+ (['number', 'boolean', 'string'].includes(typeof value) && !isNaN$1(value))
2128
+ ) {
2069
2129
  return value ;
2070
2130
  }
2071
2131
 
@@ -2086,17 +2146,16 @@ function visit(
2086
2146
  return value ;
2087
2147
  }
2088
2148
 
2089
- // Do not normalize objects that we know have already been normalized. As a general rule, the
2090
- // "__sentry_skip_normalization__" property should only be used sparingly and only should only be set on objects that
2091
- // have already been normalized.
2092
- let overriddenDepth = depth;
2093
-
2094
- if (typeof (value )['__sentry_override_normalization_depth__'] === 'number') {
2095
- overriddenDepth = (value )['__sentry_override_normalization_depth__'] ;
2096
- }
2149
+ // We can set `__sentry_override_normalization_depth__` on an object to ensure that from there
2150
+ // We keep a certain amount of depth.
2151
+ // This should be used sparingly, e.g. we use it for the redux integration to ensure we get a certain amount of state.
2152
+ const remainingDepth =
2153
+ typeof (value )['__sentry_override_normalization_depth__'] === 'number'
2154
+ ? ((value )['__sentry_override_normalization_depth__'] )
2155
+ : depth;
2097
2156
 
2098
2157
  // We're also done if we've reached the max depth
2099
- if (overriddenDepth === 0) {
2158
+ if (remainingDepth === 0) {
2100
2159
  // At this point we know `serialized` is a string of the form `"[object XXXX]"`. Clean it up so it's just `"[XXXX]"`.
2101
2160
  return stringified.replace('object ', '');
2102
2161
  }
@@ -2112,7 +2171,7 @@ function visit(
2112
2171
  try {
2113
2172
  const jsonValue = valueWithToJSON.toJSON();
2114
2173
  // We need to normalize the return value of `.toJSON()` in case it has circular references
2115
- return visit('', jsonValue, overriddenDepth - 1, maxProperties, memo);
2174
+ return visit('', jsonValue, remainingDepth - 1, maxProperties, memo);
2116
2175
  } catch (err) {
2117
2176
  // pass (The built-in `toJSON` failed, but we can still try to do it ourselves)
2118
2177
  }
@@ -2141,7 +2200,7 @@ function visit(
2141
2200
 
2142
2201
  // Recursively visit all the child nodes
2143
2202
  const visitValue = visitable[visitKey];
2144
- normalized[visitKey] = visit(visitKey, visitValue, overriddenDepth - 1, maxProperties, memo);
2203
+ normalized[visitKey] = visit(visitKey, visitValue, remainingDepth - 1, maxProperties, memo);
2145
2204
 
2146
2205
  numAdded++;
2147
2206
  }
@@ -2153,6 +2212,7 @@ function visit(
2153
2212
  return normalized;
2154
2213
  }
2155
2214
 
2215
+ /* eslint-disable complexity */
2156
2216
  /**
2157
2217
  * Stringify the given value. Handles various known special values and types.
2158
2218
  *
@@ -2203,11 +2263,6 @@ function stringifyValue(
2203
2263
  return '[NaN]';
2204
2264
  }
2205
2265
 
2206
- // this catches `undefined` (but not `null`, which is a primitive and can be serialized on its own)
2207
- if (value === void 0) {
2208
- return '[undefined]';
2209
- }
2210
-
2211
2266
  if (typeof value === 'function') {
2212
2267
  return `[Function: ${getFunctionName(value)}]`;
2213
2268
  }
@@ -2225,11 +2280,19 @@ function stringifyValue(
2225
2280
  // them to strings means that instances of classes which haven't defined their `toStringTag` will just come out as
2226
2281
  // `"[object Object]"`. If we instead look at the constructor's name (which is the same as the name of the class),
2227
2282
  // we can make sure that only plain objects come out that way.
2228
- return `[object ${getConstructorName(value)}]`;
2283
+ const objName = getConstructorName(value);
2284
+
2285
+ // Handle HTML Elements
2286
+ if (/^HTML(\w*)Element$/.test(objName)) {
2287
+ return `[HTMLElement: ${objName}]`;
2288
+ }
2289
+
2290
+ return `[object ${objName}]`;
2229
2291
  } catch (err) {
2230
2292
  return `**non-serializable** (${err})`;
2231
2293
  }
2232
2294
  }
2295
+ /* eslint-enable complexity */
2233
2296
 
2234
2297
  function getConstructorName(value) {
2235
2298
  const prototype = Object.getPrototypeOf(value);
@@ -2540,9 +2603,7 @@ function makePromiseBuffer(limit) {
2540
2603
  * // environments where DOM might not be available
2541
2604
  * @returns parsed URL object
2542
2605
  */
2543
- function parseUrl(url)
2544
-
2545
- {
2606
+ function parseUrl(url) {
2546
2607
  if (!url) {
2547
2608
  return {};
2548
2609
  }
@@ -2560,6 +2621,8 @@ function parseUrl(url)
2560
2621
  host: match[4],
2561
2622
  path: match[5],
2562
2623
  protocol: match[2],
2624
+ search: query,
2625
+ hash: fragment,
2563
2626
  relative: match[5] + query + fragment, // everything minus origin
2564
2627
  };
2565
2628
  }
@@ -2944,6 +3007,7 @@ const ITEM_TYPE_TO_DATA_CATEGORY_MAP = {
2944
3007
  profile: 'profile',
2945
3008
  replay_event: 'replay',
2946
3009
  replay_recording: 'replay',
3010
+ check_in: 'monitor',
2947
3011
  };
2948
3012
 
2949
3013
  /**
@@ -2973,16 +3037,14 @@ function createEventEnvelopeHeaders(
2973
3037
  dsn,
2974
3038
  ) {
2975
3039
  const dynamicSamplingContext = event.sdkProcessingMetadata && event.sdkProcessingMetadata.dynamicSamplingContext;
2976
-
2977
3040
  return {
2978
3041
  event_id: event.event_id ,
2979
3042
  sent_at: new Date().toISOString(),
2980
3043
  ...(sdkInfo && { sdk: sdkInfo }),
2981
3044
  ...(!!tunnel && { dsn: dsnToString(dsn) }),
2982
- ...(event.type === 'transaction' &&
2983
- dynamicSamplingContext && {
2984
- trace: dropUndefinedKeys({ ...dynamicSamplingContext }),
2985
- }),
3045
+ ...(dynamicSamplingContext && {
3046
+ trace: dropUndefinedKeys({ ...dynamicSamplingContext }),
3047
+ }),
2986
3048
  };
2987
3049
  }
2988
3050
 
@@ -3687,9 +3749,16 @@ class Scope {
3687
3749
  // errors with transaction and it relies on that.
3688
3750
  if (this._span) {
3689
3751
  event.contexts = { trace: this._span.getTraceContext(), ...event.contexts };
3690
- const transactionName = this._span.transaction && this._span.transaction.name;
3691
- if (transactionName) {
3692
- event.tags = { transaction: transactionName, ...event.tags };
3752
+ const transaction = this._span.transaction;
3753
+ if (transaction) {
3754
+ event.sdkProcessingMetadata = {
3755
+ dynamicSamplingContext: transaction.getDynamicSamplingContext(),
3756
+ ...event.sdkProcessingMetadata,
3757
+ };
3758
+ const transactionName = transaction.name;
3759
+ if (transactionName) {
3760
+ event.tags = { transaction: transactionName, ...event.tags };
3761
+ }
3693
3762
  }
3694
3763
  }
3695
3764
 
@@ -3813,11 +3882,6 @@ const API_VERSION = 4;
3813
3882
  */
3814
3883
  const DEFAULT_BREADCRUMBS = 100;
3815
3884
 
3816
- /**
3817
- * A layer in the process stack.
3818
- * @hidden
3819
- */
3820
-
3821
3885
  /**
3822
3886
  * @inheritDoc
3823
3887
  */
@@ -4095,7 +4159,17 @@ class Hub {
4095
4159
  * @inheritDoc
4096
4160
  */
4097
4161
  startTransaction(context, customSamplingContext) {
4098
- return this._callExtensionMethod('startTransaction', context, customSamplingContext);
4162
+ const result = this._callExtensionMethod('startTransaction', context, customSamplingContext);
4163
+
4164
+ if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && !result) {
4165
+ // eslint-disable-next-line no-console
4166
+ console.warn(`Tracing extension 'startTransaction' has not been added. Call 'addTracingExtensions' before calling 'init':
4167
+ Sentry.addTracingExtensions();
4168
+ Sentry.init({...});
4169
+ `);
4170
+ }
4171
+
4172
+ return result;
4099
4173
  }
4100
4174
 
4101
4175
  /**
@@ -4180,13 +4254,10 @@ class Hub {
4180
4254
  */
4181
4255
  _sendSessionUpdate() {
4182
4256
  const { scope, client } = this.getStackTop();
4183
- if (!scope) return;
4184
4257
 
4185
4258
  const session = scope.getSession();
4186
- if (session) {
4187
- if (client && client.captureSession) {
4188
- client.captureSession(session);
4189
- }
4259
+ if (session && client && client.captureSession) {
4260
+ client.captureSession(session);
4190
4261
  }
4191
4262
  }
4192
4263
 
@@ -4256,47 +4327,28 @@ function getCurrentHub() {
4256
4327
  // Get main carrier (global for every environment)
4257
4328
  const registry = getMainCarrier();
4258
4329
 
4330
+ if (registry.__SENTRY__ && registry.__SENTRY__.acs) {
4331
+ const hub = registry.__SENTRY__.acs.getCurrentHub();
4332
+
4333
+ if (hub) {
4334
+ return hub;
4335
+ }
4336
+ }
4337
+
4338
+ // Return hub that lives on a global object
4339
+ return getGlobalHub(registry);
4340
+ }
4341
+
4342
+ function getGlobalHub(registry = getMainCarrier()) {
4259
4343
  // If there's no hub, or its an old API, assign a new one
4260
4344
  if (!hasHubOnCarrier(registry) || getHubFromCarrier(registry).isOlderThan(API_VERSION)) {
4261
4345
  setHubOnCarrier(registry, new Hub());
4262
4346
  }
4263
4347
 
4264
- // Prefer domains over global if they are there (applicable only to Node environment)
4265
- if (isNodeEnv()) {
4266
- return getHubFromActiveDomain(registry);
4267
- }
4268
4348
  // Return hub that lives on a global object
4269
4349
  return getHubFromCarrier(registry);
4270
4350
  }
4271
4351
 
4272
- /**
4273
- * Try to read the hub from an active domain, and fallback to the registry if one doesn't exist
4274
- * @returns discovered hub
4275
- */
4276
- function getHubFromActiveDomain(registry) {
4277
- try {
4278
- const sentry = getMainCarrier().__SENTRY__;
4279
- const activeDomain = sentry && sentry.extensions && sentry.extensions.domain && sentry.extensions.domain.active;
4280
-
4281
- // If there's no active domain, just return global hub
4282
- if (!activeDomain) {
4283
- return getHubFromCarrier(registry);
4284
- }
4285
-
4286
- // If there's no hub on current domain, or it's an old API, assign a new one
4287
- if (!hasHubOnCarrier(activeDomain) || getHubFromCarrier(activeDomain).isOlderThan(API_VERSION)) {
4288
- const registryHubTopStack = getHubFromCarrier(registry).getStackTop();
4289
- setHubOnCarrier(activeDomain, new Hub(registryHubTopStack.client, Scope.clone(registryHubTopStack.scope)));
4290
- }
4291
-
4292
- // Return hub that lives on a domain
4293
- return getHubFromCarrier(activeDomain);
4294
- } catch (_Oo) {
4295
- // Return hub that lives on a global object
4296
- return getHubFromCarrier(registry);
4297
- }
4298
- }
4299
-
4300
4352
  /**
4301
4353
  * This will tell whether a carrier has a hub on it or not
4302
4354
  * @param carrier object
@@ -4370,6 +4422,69 @@ var SpanStatus; (function (SpanStatus) {
4370
4422
  const DataLoss = 'data_loss'; SpanStatus["DataLoss"] = DataLoss;
4371
4423
  })(SpanStatus || (SpanStatus = {}));
4372
4424
 
4425
+ /**
4426
+ * Wraps a function with a transaction/span and finishes the span after the function is done.
4427
+ *
4428
+ * Note that if you have not enabled tracing extensions via `addTracingExtensions`, this function
4429
+ * will not generate spans, and the `span` returned from the callback may be undefined.
4430
+ *
4431
+ * This function is meant to be used internally and may break at any time. Use at your own risk.
4432
+ *
4433
+ * @internal
4434
+ * @private
4435
+ */
4436
+ function trace(
4437
+ context,
4438
+ callback,
4439
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
4440
+ onError = () => {},
4441
+ ) {
4442
+ const ctx = { ...context };
4443
+ // If a name is set and a description is not, set the description to the name.
4444
+ if (ctx.name !== undefined && ctx.description === undefined) {
4445
+ ctx.description = ctx.name;
4446
+ }
4447
+
4448
+ const hub = getCurrentHub();
4449
+ const scope = hub.getScope();
4450
+
4451
+ const parentSpan = scope.getSpan();
4452
+ const activeSpan = parentSpan ? parentSpan.startChild(ctx) : hub.startTransaction(ctx);
4453
+ scope.setSpan(activeSpan);
4454
+
4455
+ function finishAndSetSpan() {
4456
+ activeSpan && activeSpan.finish();
4457
+ hub.getScope().setSpan(parentSpan);
4458
+ }
4459
+
4460
+ let maybePromiseResult;
4461
+ try {
4462
+ maybePromiseResult = callback(activeSpan);
4463
+ } catch (e) {
4464
+ activeSpan && activeSpan.setStatus('internal_error');
4465
+ onError(e);
4466
+ finishAndSetSpan();
4467
+ throw e;
4468
+ }
4469
+
4470
+ if (isThenable(maybePromiseResult)) {
4471
+ Promise.resolve(maybePromiseResult).then(
4472
+ () => {
4473
+ finishAndSetSpan();
4474
+ },
4475
+ e => {
4476
+ activeSpan && activeSpan.setStatus('internal_error');
4477
+ onError(e);
4478
+ finishAndSetSpan();
4479
+ },
4480
+ );
4481
+ } else {
4482
+ finishAndSetSpan();
4483
+ }
4484
+
4485
+ return maybePromiseResult;
4486
+ }
4487
+
4373
4488
  // Note: All functions in this file are typed with a return value of `ReturnType<Hub[HUB_FUNCTION]>`,
4374
4489
  // where HUB_FUNCTION is some method on the Hub class.
4375
4490
  //
@@ -4711,7 +4826,11 @@ function prepareEvent(
4711
4826
 
4712
4827
  applyClientOptions(prepared, options);
4713
4828
  applyIntegrationsMetadata(prepared, integrations);
4714
- applyDebugMetadata(prepared, options.stackParser);
4829
+
4830
+ // Only apply debug metadata to error events.
4831
+ if (event.type === undefined) {
4832
+ applyDebugMetadata(prepared, options.stackParser);
4833
+ }
4715
4834
 
4716
4835
  // If we have scope given to us, use it as the base for further modifications.
4717
4836
  // This allows us to prevent unnecessary copying of data if `captureContext` is not provided.
@@ -4788,6 +4907,8 @@ function applyClientOptions(event, options) {
4788
4907
  }
4789
4908
  }
4790
4909
 
4910
+ const debugIdStackParserCache = new WeakMap();
4911
+
4791
4912
  /**
4792
4913
  * Applies debug metadata images to the event in order to apply source maps by looking up their debug ID.
4793
4914
  */
@@ -4798,10 +4919,28 @@ function applyDebugMetadata(event, stackParser) {
4798
4919
  return;
4799
4920
  }
4800
4921
 
4922
+ let debugIdStackFramesCache;
4923
+ const cachedDebugIdStackFrameCache = debugIdStackParserCache.get(stackParser);
4924
+ if (cachedDebugIdStackFrameCache) {
4925
+ debugIdStackFramesCache = cachedDebugIdStackFrameCache;
4926
+ } else {
4927
+ debugIdStackFramesCache = new Map();
4928
+ debugIdStackParserCache.set(stackParser, debugIdStackFramesCache);
4929
+ }
4930
+
4801
4931
  // Build a map of filename -> debug_id
4802
4932
  const filenameDebugIdMap = Object.keys(debugIdMap).reduce((acc, debugIdStackTrace) => {
4803
- const parsedStack = stackParser(debugIdStackTrace);
4804
- for (const stackFrame of parsedStack) {
4933
+ let parsedStack;
4934
+ const cachedParsedStack = debugIdStackFramesCache.get(debugIdStackTrace);
4935
+ if (cachedParsedStack) {
4936
+ parsedStack = cachedParsedStack;
4937
+ } else {
4938
+ parsedStack = stackParser(debugIdStackTrace);
4939
+ debugIdStackFramesCache.set(debugIdStackTrace, parsedStack);
4940
+ }
4941
+
4942
+ for (let i = parsedStack.length - 1; i >= 0; i--) {
4943
+ const stackFrame = parsedStack[i];
4805
4944
  if (stackFrame.filename) {
4806
4945
  acc[stackFrame.filename] = debugIdMap[debugIdStackTrace];
4807
4946
  break;
@@ -5706,7 +5845,7 @@ function getEventForEnvelopeItem(item, type) {
5706
5845
  return Array.isArray(item) ? (item )[1] : undefined;
5707
5846
  }
5708
5847
 
5709
- const SDK_VERSION = '7.46.0';
5848
+ const SDK_VERSION = '7.52.1';
5710
5849
 
5711
5850
  let originalFunctionToString;
5712
5851
 
@@ -5729,11 +5868,17 @@ class FunctionToString {constructor() { FunctionToString.prototype.__init.call(
5729
5868
  // eslint-disable-next-line @typescript-eslint/unbound-method
5730
5869
  originalFunctionToString = Function.prototype.toString;
5731
5870
 
5732
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
5733
- Function.prototype.toString = function ( ...args) {
5734
- const context = getOriginalFunction(this) || this;
5735
- return originalFunctionToString.apply(context, args);
5736
- };
5871
+ // intrinsics (like Function.prototype) might be immutable in some environments
5872
+ // e.g. Node with --frozen-intrinsics, XS (an embedded JavaScript engine) or SES (a JavaScript proposal)
5873
+ try {
5874
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5875
+ Function.prototype.toString = function ( ...args) {
5876
+ const context = getOriginalFunction(this) || this;
5877
+ return originalFunctionToString.apply(context, args);
5878
+ };
5879
+ } catch (e) {
5880
+ // ignore errors here, just don't patch this
5881
+ }
5737
5882
  }
5738
5883
  } FunctionToString.__initStatic();
5739
5884
 
@@ -5881,8 +6026,9 @@ function _getPossibleEventMessages(event) {
5881
6026
  return [event.message];
5882
6027
  }
5883
6028
  if (event.exception) {
6029
+ const { values } = event.exception;
5884
6030
  try {
5885
- const { type = '', value = '' } = (event.exception.values && event.exception.values[0]) || {};
6031
+ const { type = '', value = '' } = (values && values[values.length - 1]) || {};
5886
6032
  return [`${value}`, `${type}: ${value}`];
5887
6033
  } catch (oO) {
5888
6034
  (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error(`Cannot extract message for event ${getEventDescription(event)}`);
@@ -6541,12 +6687,14 @@ function _consoleBreadcrumb(handlerData) {
6541
6687
  function _xhrBreadcrumb(handlerData) {
6542
6688
  const { startTimestamp, endTimestamp } = handlerData;
6543
6689
 
6690
+ const sentryXhrData = handlerData.xhr[SENTRY_XHR_DATA_KEY];
6691
+
6544
6692
  // We only capture complete, non-sentry requests
6545
- if (!startTimestamp || !endTimestamp || !handlerData.xhr.__sentry_xhr__) {
6693
+ if (!startTimestamp || !endTimestamp || !sentryXhrData) {
6546
6694
  return;
6547
6695
  }
6548
6696
 
6549
- const { method, url, status_code, body } = handlerData.xhr.__sentry_xhr__;
6697
+ const { method, url, status_code, body } = sentryXhrData;
6550
6698
 
6551
6699
  const data = {
6552
6700
  method,
@@ -6664,6 +6812,43 @@ function _isEvent(event) {
6664
6812
  return event && !!(event ).target;
6665
6813
  }
6666
6814
 
6815
+ /**
6816
+ * Creates an envelope from a user feedback.
6817
+ */
6818
+ function createUserFeedbackEnvelope(
6819
+ feedback,
6820
+ {
6821
+ metadata,
6822
+ tunnel,
6823
+ dsn,
6824
+ }
6825
+
6826
+ ,
6827
+ ) {
6828
+ const headers = {
6829
+ event_id: feedback.event_id,
6830
+ sent_at: new Date().toISOString(),
6831
+ ...(metadata &&
6832
+ metadata.sdk && {
6833
+ sdk: {
6834
+ name: metadata.sdk.name,
6835
+ version: metadata.sdk.version,
6836
+ },
6837
+ }),
6838
+ ...(!!tunnel && !!dsn && { dsn: dsnToString(dsn) }),
6839
+ };
6840
+ const item = createUserFeedbackEnvelopeItem(feedback);
6841
+
6842
+ return createEnvelope(headers, [item]);
6843
+ }
6844
+
6845
+ function createUserFeedbackEnvelopeItem(feedback) {
6846
+ const feedbackHeaders = {
6847
+ type: 'user_report',
6848
+ };
6849
+ return [feedbackHeaders, feedback];
6850
+ }
6851
+
6667
6852
  /**
6668
6853
  * Configuration options for the Sentry Browser SDK.
6669
6854
  * @see @sentry/types Options for more information.
@@ -6746,6 +6931,23 @@ class BrowserClient extends BaseClient {
6746
6931
  super.sendEvent(event, hint);
6747
6932
  }
6748
6933
 
6934
+ /**
6935
+ * Sends user feedback to Sentry.
6936
+ */
6937
+ captureUserFeedback(feedback) {
6938
+ if (!this._isEnabled()) {
6939
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.warn('SDK not enabled, will not capture user feedback.');
6940
+ return;
6941
+ }
6942
+
6943
+ const envelope = createUserFeedbackEnvelope(feedback, {
6944
+ metadata: this.getSdkMetadata(),
6945
+ dsn: this.getDsn(),
6946
+ tunnel: this.getOptions().tunnel,
6947
+ });
6948
+ void this._sendEnvelope(envelope);
6949
+ }
6950
+
6749
6951
  /**
6750
6952
  * @inheritDoc
6751
6953
  */
@@ -6988,7 +7190,7 @@ function createFrame(filename, func, lineno, colno) {
6988
7190
 
6989
7191
  // Chromium based browsers: Chrome, Brave, new Opera, new Edge
6990
7192
  const chromeRegex =
6991
- /^\s*at (?:(.*\).*?|.*?) ?\((?:address at )?)?(?:async )?((?:file|https?|blob|chrome-extension|address|native|eval|webpack|<anonymous>|[-a-z]+:|.*bundle|\/)?.*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i;
7193
+ /^\s*at (?:(.+?\)(?: \[.+\])?|.*?) ?\((?:address at )?)?(?:async )?((?:<anonymous>|[-a-z]+:|.*bundle|\/)?.*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i;
6992
7194
  const chromeEvalRegex = /\((\S*)(?::(\d+))(?::(\d+))\)/;
6993
7195
 
6994
7196
  const chrome = line => {
@@ -7024,7 +7226,7 @@ const chromeStackLineParser = [CHROME_PRIORITY, chrome];
7024
7226
  // generates filenames without a prefix like `file://` the filenames in the stacktrace are just 42.js
7025
7227
  // We need this specific case for now because we want no other regex to match.
7026
7228
  const geckoREgex =
7027
- /^\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;
7229
+ /^\s*(.*?)(?:\((.*?)\))?(?:^|@)?((?:[-a-z]+)?:\/.*?|\[native code\]|[^@]*(?:bundle|\d+\.js)|\/[\w\-. /=]+)(?::(\d+))?(?::(\d+))?\s*$/i;
7028
7230
  const geckoEvalRegex = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i;
7029
7231
 
7030
7232
  const gecko = line => {
@@ -7056,8 +7258,7 @@ const gecko = line => {
7056
7258
 
7057
7259
  const geckoStackLineParser = [GECKO_PRIORITY, gecko];
7058
7260
 
7059
- const winjsRegex =
7060
- /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i;
7261
+ const winjsRegex = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:[-a-z]+):.*?):(\d+)(?::(\d+))?\)?\s*$/i;
7061
7262
 
7062
7263
  const winjs = line => {
7063
7264
  const parts = winjsRegex.exec(line);
@@ -8122,11 +8323,14 @@ const REPLAY_SESSION_KEY = 'sentryReplaySession';
8122
8323
  const REPLAY_EVENT_NAME = 'replay_event';
8123
8324
  const UNABLE_TO_SEND_REPLAY = 'Unable to send Replay';
8124
8325
 
8125
- // The idle limit for a session
8126
- const SESSION_IDLE_DURATION = 300000; // 5 minutes in ms
8326
+ // The idle limit for a session after which recording is paused.
8327
+ const SESSION_IDLE_PAUSE_DURATION = 300000; // 5 minutes in ms
8328
+
8329
+ // The idle limit for a session after which the session expires.
8330
+ const SESSION_IDLE_EXPIRE_DURATION = 900000; // 15 minutes in ms
8127
8331
 
8128
8332
  // The maximum length of a session
8129
- const MAX_SESSION_LIFE = 3600000; // 60 minutes
8333
+ const MAX_SESSION_LIFE = 3600000; // 60 minutes in ms
8130
8334
 
8131
8335
  /** Default flush delays */
8132
8336
  const DEFAULT_FLUSH_MIN_DELAY = 5000;
@@ -8135,13 +8339,16 @@ const DEFAULT_FLUSH_MIN_DELAY = 5000;
8135
8339
  const DEFAULT_FLUSH_MAX_DELAY = 5500;
8136
8340
 
8137
8341
  /* How long to wait for error checkouts */
8138
- const ERROR_CHECKOUT_TIME = 60000;
8342
+ const BUFFER_CHECKOUT_TIME = 60000;
8139
8343
 
8140
8344
  const RETRY_BASE_INTERVAL = 5000;
8141
8345
  const RETRY_MAX_COUNT = 3;
8142
8346
 
8143
- /* The max (uncompressed) size in bytes of a network body. Any body larger than this will be dropped. */
8144
- const NETWORK_BODY_MAX_SIZE = 300000;
8347
+ /* The max (uncompressed) size in bytes of a network body. Any body larger than this will be truncated. */
8348
+ const NETWORK_BODY_MAX_SIZE = 150000;
8349
+
8350
+ /* The max size of a single console arg that is captured. Any arg larger than this will be truncated. */
8351
+ const CONSOLE_ARG_MAX_SIZE = 5000;
8145
8352
 
8146
8353
  var NodeType$1;
8147
8354
  (function (NodeType) {
@@ -8178,7 +8385,7 @@ function maskInputValue({ input, maskInputSelector, unmaskInputSelector, maskInp
8178
8385
  if (unmaskInputSelector && input.matches(unmaskInputSelector)) {
8179
8386
  return text;
8180
8387
  }
8181
- if (input.hasAttribute('rr_is_password')) {
8388
+ if (input.hasAttribute('data-rr-is-password')) {
8182
8389
  type = 'password';
8183
8390
  }
8184
8391
  if (isInputTypeMasked({ maskInputOptions, tagName, type }) ||
@@ -8211,6 +8418,21 @@ function is2DCanvasBlank(canvas) {
8211
8418
  }
8212
8419
  return true;
8213
8420
  }
8421
+ function getInputType(element) {
8422
+ const type = element.type;
8423
+ return element.hasAttribute('data-rr-is-password')
8424
+ ? 'password'
8425
+ : type
8426
+ ? type.toLowerCase()
8427
+ : null;
8428
+ }
8429
+ function getInputValue(el, tagName, type) {
8430
+ typeof type === 'string' ? type.toLowerCase() : '';
8431
+ if (tagName === 'INPUT' && (type === 'radio' || type === 'checkbox')) {
8432
+ return el.getAttribute('value') || '';
8433
+ }
8434
+ return el.value;
8435
+ }
8214
8436
 
8215
8437
  let _id = 1;
8216
8438
  const tagNameRegex = new RegExp('[^a-z0-9-_:]');
@@ -8249,6 +8471,13 @@ function getCssRuleString(rule) {
8249
8471
  catch (_a) {
8250
8472
  }
8251
8473
  }
8474
+ return validateStringifiedCssRule(cssStringified);
8475
+ }
8476
+ function validateStringifiedCssRule(cssStringified) {
8477
+ if (cssStringified.indexOf(':') > -1) {
8478
+ const regex = /(\[(?:[\w-]+)[^\\])(:(?:[\w-]+)\])/gm;
8479
+ return cssStringified.replace(regex, '$1\\$2');
8480
+ }
8252
8481
  return cssStringified;
8253
8482
  }
8254
8483
  function isCSSImportRule(rule) {
@@ -8257,7 +8486,7 @@ function isCSSImportRule(rule) {
8257
8486
  function stringifyStyleSheet(sheet) {
8258
8487
  return sheet.cssRules
8259
8488
  ? Array.from(sheet.cssRules)
8260
- .map((rule) => rule.cssText || '')
8489
+ .map((rule) => rule.cssText ? validateStringifiedCssRule(rule.cssText) : '')
8261
8490
  .join('')
8262
8491
  : '';
8263
8492
  }
@@ -8592,14 +8821,15 @@ function serializeNode(n, options) {
8592
8821
  tagName === 'select' ||
8593
8822
  tagName === 'option') {
8594
8823
  const el = n;
8595
- const value = getInputValue(tagName, el, attributes);
8824
+ const type = getInputType(el);
8825
+ const value = getInputValue(el, tagName.toUpperCase(), type);
8596
8826
  const checked = n.checked;
8597
- if (attributes.type !== 'submit' &&
8598
- attributes.type !== 'button' &&
8827
+ if (type !== 'submit' &&
8828
+ type !== 'button' &&
8599
8829
  value) {
8600
8830
  attributes.value = maskInputValue({
8601
8831
  input: el,
8602
- type: attributes.type,
8832
+ type,
8603
8833
  tagName,
8604
8834
  value,
8605
8835
  maskInputSelector,
@@ -9072,15 +9302,8 @@ function snapshot(n, options) {
9072
9302
  function skipAttribute(tagName, attributeName, value) {
9073
9303
  return ((tagName === 'video' || tagName === 'audio') && attributeName === 'autoplay');
9074
9304
  }
9075
- function getInputValue(tagName, el, attributes) {
9076
- if (tagName === 'input' &&
9077
- (attributes.type === 'radio' || attributes.type === 'checkbox')) {
9078
- return el.getAttribute('value') || '';
9079
- }
9080
- return el.value;
9081
- }
9082
9305
 
9083
- var EventType;
9306
+ var EventType$1;
9084
9307
  (function (EventType) {
9085
9308
  EventType[EventType["DomContentLoaded"] = 0] = "DomContentLoaded";
9086
9309
  EventType[EventType["Load"] = 1] = "Load";
@@ -9089,7 +9312,7 @@ var EventType;
9089
9312
  EventType[EventType["Meta"] = 4] = "Meta";
9090
9313
  EventType[EventType["Custom"] = 5] = "Custom";
9091
9314
  EventType[EventType["Plugin"] = 6] = "Plugin";
9092
- })(EventType || (EventType = {}));
9315
+ })(EventType$1 || (EventType$1 = {}));
9093
9316
  var IncrementalSource;
9094
9317
  (function (IncrementalSource) {
9095
9318
  IncrementalSource[IncrementalSource["Mutation"] = 0] = "Mutation";
@@ -9704,9 +9927,9 @@ class MutationBuffer {
9704
9927
  this.attributes.push(item);
9705
9928
  }
9706
9929
  if (m.attributeName === 'type' &&
9707
- m.target.tagName === 'INPUT' &&
9930
+ target.tagName === 'INPUT' &&
9708
9931
  (m.oldValue || '').toLowerCase() === 'password') {
9709
- m.target.setAttribute('rr_is_password', 'true');
9932
+ target.setAttribute('data-rr-is-password', 'true');
9710
9933
  }
9711
9934
  if (m.attributeName === 'style') {
9712
9935
  const old = this.doc.createElement('span');
@@ -10103,27 +10326,25 @@ function initInputObserver({ inputCb, doc, mirror, blockClass, blockSelector, un
10103
10326
  isBlocked(target, blockClass, blockSelector, unblockSelector)) {
10104
10327
  return;
10105
10328
  }
10106
- let type = target.type;
10107
- if (target.classList.contains(ignoreClass) ||
10108
- (ignoreSelector && target.matches(ignoreSelector))) {
10329
+ const el = target;
10330
+ const type = getInputType(el);
10331
+ if (el.classList.contains(ignoreClass) ||
10332
+ (ignoreSelector && el.matches(ignoreSelector))) {
10109
10333
  return;
10110
10334
  }
10111
- let text = target.value;
10335
+ let text = getInputValue(el, tagName, type);
10112
10336
  let isChecked = false;
10113
- if (target.hasAttribute('rr_is_password')) {
10114
- type = 'password';
10115
- }
10116
10337
  if (type === 'radio' || type === 'checkbox') {
10117
10338
  isChecked = target.checked;
10118
10339
  }
10119
- else if (hasInputMaskOptions({
10340
+ if (hasInputMaskOptions({
10120
10341
  maskInputOptions,
10121
10342
  maskInputSelector,
10122
10343
  tagName,
10123
10344
  type,
10124
10345
  })) {
10125
10346
  text = maskInputValue({
10126
- input: target,
10347
+ input: el,
10127
10348
  maskInputOptions,
10128
10349
  maskInputSelector,
10129
10350
  unmaskInputSelector,
@@ -10140,8 +10361,18 @@ function initInputObserver({ inputCb, doc, mirror, blockClass, blockSelector, un
10140
10361
  .querySelectorAll(`input[type="radio"][name="${name}"]`)
10141
10362
  .forEach((el) => {
10142
10363
  if (el !== target) {
10364
+ const text = maskInputValue({
10365
+ input: el,
10366
+ maskInputOptions,
10367
+ maskInputSelector,
10368
+ unmaskInputSelector,
10369
+ tagName,
10370
+ type,
10371
+ value: getInputValue(el, tagName, type),
10372
+ maskInputFn,
10373
+ });
10143
10374
  cbWithDedup(el, callbackWrapper(wrapEventWithUserTriggeredFlag)({
10144
- text: el.value,
10375
+ text,
10145
10376
  isChecked: !isChecked,
10146
10377
  userTriggered: false,
10147
10378
  }, userTriggeredOnInput));
@@ -11054,17 +11285,17 @@ function record(options = {}) {
11054
11285
  wrappedEmit = (e, isCheckout) => {
11055
11286
  var _a;
11056
11287
  if (((_a = mutationBuffers[0]) === null || _a === void 0 ? void 0 : _a.isFrozen()) &&
11057
- e.type !== EventType.FullSnapshot &&
11058
- !(e.type === EventType.IncrementalSnapshot &&
11288
+ e.type !== EventType$1.FullSnapshot &&
11289
+ !(e.type === EventType$1.IncrementalSnapshot &&
11059
11290
  e.data.source === IncrementalSource.Mutation)) {
11060
11291
  mutationBuffers.forEach((buf) => buf.unfreeze());
11061
11292
  }
11062
11293
  emit(eventProcessor(e), isCheckout);
11063
- if (e.type === EventType.FullSnapshot) {
11294
+ if (e.type === EventType$1.FullSnapshot) {
11064
11295
  lastFullSnapshotEvent = e;
11065
11296
  incrementalSnapshotCount = 0;
11066
11297
  }
11067
- else if (e.type === EventType.IncrementalSnapshot) {
11298
+ else if (e.type === EventType$1.IncrementalSnapshot) {
11068
11299
  if (e.data.source === IncrementalSource.Mutation &&
11069
11300
  e.data.isAttachIframe) {
11070
11301
  return;
@@ -11080,16 +11311,16 @@ function record(options = {}) {
11080
11311
  };
11081
11312
  const wrappedMutationEmit = (m) => {
11082
11313
  wrappedEmit(wrapEvent({
11083
- type: EventType.IncrementalSnapshot,
11314
+ type: EventType$1.IncrementalSnapshot,
11084
11315
  data: Object.assign({ source: IncrementalSource.Mutation }, m),
11085
11316
  }));
11086
11317
  };
11087
11318
  const wrappedScrollEmit = (p) => wrappedEmit(wrapEvent({
11088
- type: EventType.IncrementalSnapshot,
11319
+ type: EventType$1.IncrementalSnapshot,
11089
11320
  data: Object.assign({ source: IncrementalSource.Scroll }, p),
11090
11321
  }));
11091
11322
  const wrappedCanvasMutationEmit = (p) => wrappedEmit(wrapEvent({
11092
- type: EventType.IncrementalSnapshot,
11323
+ type: EventType$1.IncrementalSnapshot,
11093
11324
  data: Object.assign({ source: IncrementalSource.CanvasMutation }, p),
11094
11325
  }));
11095
11326
  const iframeManager = new IframeManager({
@@ -11134,7 +11365,7 @@ function record(options = {}) {
11134
11365
  takeFullSnapshot = (isCheckout = false) => {
11135
11366
  var _a, _b, _c, _d;
11136
11367
  wrappedEmit(wrapEvent({
11137
- type: EventType.Meta,
11368
+ type: EventType$1.Meta,
11138
11369
  data: {
11139
11370
  href: window.location.href,
11140
11371
  width: getWindowWidth(),
@@ -11177,7 +11408,7 @@ function record(options = {}) {
11177
11408
  }
11178
11409
  mirror.map = idNodeMap;
11179
11410
  wrappedEmit(wrapEvent({
11180
- type: EventType.FullSnapshot,
11411
+ type: EventType$1.FullSnapshot,
11181
11412
  data: {
11182
11413
  node,
11183
11414
  initialOffset: {
@@ -11202,7 +11433,7 @@ function record(options = {}) {
11202
11433
  const handlers = [];
11203
11434
  handlers.push(on$1('DOMContentLoaded', () => {
11204
11435
  wrappedEmit(wrapEvent({
11205
- type: EventType.DomContentLoaded,
11436
+ type: EventType$1.DomContentLoaded,
11206
11437
  data: {},
11207
11438
  }));
11208
11439
  }));
@@ -11212,40 +11443,40 @@ function record(options = {}) {
11212
11443
  onMutation,
11213
11444
  mutationCb: wrappedMutationEmit,
11214
11445
  mousemoveCb: (positions, source) => wrappedEmit(wrapEvent({
11215
- type: EventType.IncrementalSnapshot,
11446
+ type: EventType$1.IncrementalSnapshot,
11216
11447
  data: {
11217
11448
  source,
11218
11449
  positions,
11219
11450
  },
11220
11451
  })),
11221
11452
  mouseInteractionCb: (d) => wrappedEmit(wrapEvent({
11222
- type: EventType.IncrementalSnapshot,
11453
+ type: EventType$1.IncrementalSnapshot,
11223
11454
  data: Object.assign({ source: IncrementalSource.MouseInteraction }, d),
11224
11455
  })),
11225
11456
  scrollCb: wrappedScrollEmit,
11226
11457
  viewportResizeCb: (d) => wrappedEmit(wrapEvent({
11227
- type: EventType.IncrementalSnapshot,
11458
+ type: EventType$1.IncrementalSnapshot,
11228
11459
  data: Object.assign({ source: IncrementalSource.ViewportResize }, d),
11229
11460
  })),
11230
11461
  inputCb: (v) => wrappedEmit(wrapEvent({
11231
- type: EventType.IncrementalSnapshot,
11462
+ type: EventType$1.IncrementalSnapshot,
11232
11463
  data: Object.assign({ source: IncrementalSource.Input }, v),
11233
11464
  })),
11234
11465
  mediaInteractionCb: (p) => wrappedEmit(wrapEvent({
11235
- type: EventType.IncrementalSnapshot,
11466
+ type: EventType$1.IncrementalSnapshot,
11236
11467
  data: Object.assign({ source: IncrementalSource.MediaInteraction }, p),
11237
11468
  })),
11238
11469
  styleSheetRuleCb: (r) => wrappedEmit(wrapEvent({
11239
- type: EventType.IncrementalSnapshot,
11470
+ type: EventType$1.IncrementalSnapshot,
11240
11471
  data: Object.assign({ source: IncrementalSource.StyleSheetRule }, r),
11241
11472
  })),
11242
11473
  styleDeclarationCb: (r) => wrappedEmit(wrapEvent({
11243
- type: EventType.IncrementalSnapshot,
11474
+ type: EventType$1.IncrementalSnapshot,
11244
11475
  data: Object.assign({ source: IncrementalSource.StyleDeclaration }, r),
11245
11476
  })),
11246
11477
  canvasMutationCb: wrappedCanvasMutationEmit,
11247
11478
  fontCb: (p) => wrappedEmit(wrapEvent({
11248
- type: EventType.IncrementalSnapshot,
11479
+ type: EventType$1.IncrementalSnapshot,
11249
11480
  data: Object.assign({ source: IncrementalSource.Font }, p),
11250
11481
  })),
11251
11482
  blockClass,
@@ -11278,7 +11509,7 @@ function record(options = {}) {
11278
11509
  observer: p.observer,
11279
11510
  options: p.options,
11280
11511
  callback: (payload) => wrappedEmit(wrapEvent({
11281
- type: EventType.Plugin,
11512
+ type: EventType$1.Plugin,
11282
11513
  data: {
11283
11514
  plugin: p.name,
11284
11515
  payload,
@@ -11306,7 +11537,7 @@ function record(options = {}) {
11306
11537
  else {
11307
11538
  handlers.push(on$1('load', () => {
11308
11539
  wrappedEmit(wrapEvent({
11309
- type: EventType.Load,
11540
+ type: EventType$1.Load,
11310
11541
  data: {},
11311
11542
  }));
11312
11543
  init();
@@ -11325,7 +11556,7 @@ record.addCustomEvent = (tag, payload) => {
11325
11556
  throw new Error('please add custom event after start recording');
11326
11557
  }
11327
11558
  wrappedEmit(wrapEvent({
11328
- type: EventType.Custom,
11559
+ type: EventType$1.Custom,
11329
11560
  data: {
11330
11561
  tag,
11331
11562
  payload,
@@ -11343,6 +11574,475 @@ record.takeFullSnapshot = (isCheckout) => {
11343
11574
  };
11344
11575
  record.mirror = mirror;
11345
11576
 
11577
+ /**
11578
+ * Create a breadcrumb for a replay.
11579
+ */
11580
+ function createBreadcrumb(
11581
+ breadcrumb,
11582
+ ) {
11583
+ return {
11584
+ timestamp: Date.now() / 1000,
11585
+ type: 'default',
11586
+ ...breadcrumb,
11587
+ };
11588
+ }
11589
+
11590
+ var NodeType;
11591
+ (function (NodeType) {
11592
+ NodeType[NodeType["Document"] = 0] = "Document";
11593
+ NodeType[NodeType["DocumentType"] = 1] = "DocumentType";
11594
+ NodeType[NodeType["Element"] = 2] = "Element";
11595
+ NodeType[NodeType["Text"] = 3] = "Text";
11596
+ NodeType[NodeType["CDATA"] = 4] = "CDATA";
11597
+ NodeType[NodeType["Comment"] = 5] = "Comment";
11598
+ })(NodeType || (NodeType = {}));
11599
+
11600
+ /**
11601
+ * Converts a timestamp to ms, if it was in s, or keeps it as ms.
11602
+ */
11603
+ function timestampToMs(timestamp) {
11604
+ const isMs = timestamp > 9999999999;
11605
+ return isMs ? timestamp : timestamp * 1000;
11606
+ }
11607
+
11608
+ /**
11609
+ * Add an event to the event buffer.
11610
+ * `isCheckout` is true if this is either the very first event, or an event triggered by `checkoutEveryNms`.
11611
+ */
11612
+ async function addEvent(
11613
+ replay,
11614
+ event,
11615
+ isCheckout,
11616
+ ) {
11617
+ if (!replay.eventBuffer) {
11618
+ // This implies that `_isEnabled` is false
11619
+ return null;
11620
+ }
11621
+
11622
+ if (replay.isPaused()) {
11623
+ // Do not add to event buffer when recording is paused
11624
+ return null;
11625
+ }
11626
+
11627
+ const timestampInMs = timestampToMs(event.timestamp);
11628
+
11629
+ // Throw out events that happen more than 5 minutes ago. This can happen if
11630
+ // page has been left open and idle for a long period of time and user
11631
+ // comes back to trigger a new session. The performance entries rely on
11632
+ // `performance.timeOrigin`, which is when the page first opened.
11633
+ if (timestampInMs + replay.timeouts.sessionIdlePause < Date.now()) {
11634
+ return null;
11635
+ }
11636
+
11637
+ try {
11638
+ if (isCheckout) {
11639
+ replay.eventBuffer.clear();
11640
+ }
11641
+
11642
+ return await replay.eventBuffer.addEvent(event);
11643
+ } catch (error) {
11644
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error(error);
11645
+ await replay.stop('addEvent');
11646
+
11647
+ const client = getCurrentHub().getClient();
11648
+
11649
+ if (client) {
11650
+ client.recordDroppedEvent('internal_sdk_error', 'replay');
11651
+ }
11652
+ }
11653
+ }
11654
+
11655
+ /**
11656
+ * Add a breadcrumb event to replay.
11657
+ */
11658
+ function addBreadcrumbEvent(replay, breadcrumb) {
11659
+ if (breadcrumb.category === 'sentry.transaction') {
11660
+ return;
11661
+ }
11662
+
11663
+ if (['ui.click', 'ui.input'].includes(breadcrumb.category )) {
11664
+ replay.triggerUserActivity();
11665
+ } else {
11666
+ replay.checkAndHandleExpiredSession();
11667
+ }
11668
+
11669
+ replay.addUpdate(() => {
11670
+ void addEvent(replay, {
11671
+ type: EventType$1.Custom,
11672
+ // TODO: We were converting from ms to seconds for breadcrumbs, spans,
11673
+ // but maybe we should just keep them as milliseconds
11674
+ timestamp: (breadcrumb.timestamp || 0) * 1000,
11675
+ data: {
11676
+ tag: 'breadcrumb',
11677
+ // normalize to max. 10 depth and 1_000 properties per object
11678
+ payload: normalize(breadcrumb, 10, 1000),
11679
+ },
11680
+ });
11681
+
11682
+ // Do not flush after console log messages
11683
+ return breadcrumb.category === 'console';
11684
+ });
11685
+ }
11686
+
11687
+ /**
11688
+ * Detect a slow click on a button/a tag,
11689
+ * and potentially create a corresponding breadcrumb.
11690
+ */
11691
+ function detectSlowClick(
11692
+ replay,
11693
+ config,
11694
+ clickBreadcrumb,
11695
+ node,
11696
+ ) {
11697
+ if (ignoreElement(node, config)) {
11698
+ return;
11699
+ }
11700
+
11701
+ /*
11702
+ We consider a slow click a click on a button/a, which does not trigger one of:
11703
+ - DOM mutation
11704
+ - Scroll (within 100ms)
11705
+ Within the given threshold time.
11706
+ After time timeout time, we stop listening and mark it as a slow click anyhow.
11707
+ */
11708
+
11709
+ let cleanup = () => {
11710
+ // replaced further down
11711
+ };
11712
+
11713
+ // After timeout time, def. consider this a slow click, and stop watching for mutations
11714
+ const timeout = setTimeout(() => {
11715
+ handleSlowClick(replay, clickBreadcrumb, config.timeout, 'timeout');
11716
+ cleanup();
11717
+ }, config.timeout);
11718
+
11719
+ const mutationHandler = () => {
11720
+ maybeHandleSlowClick(replay, clickBreadcrumb, config.threshold, config.timeout, 'mutation');
11721
+ cleanup();
11722
+ };
11723
+
11724
+ const scrollHandler = () => {
11725
+ maybeHandleSlowClick(replay, clickBreadcrumb, config.scrollTimeout, config.timeout, 'scroll');
11726
+ cleanup();
11727
+ };
11728
+
11729
+ const obs = new MutationObserver(mutationHandler);
11730
+
11731
+ obs.observe(WINDOW.document.documentElement, {
11732
+ attributes: true,
11733
+ characterData: true,
11734
+ childList: true,
11735
+ subtree: true,
11736
+ });
11737
+
11738
+ WINDOW.addEventListener('scroll', scrollHandler);
11739
+
11740
+ // Stop listening to scroll timeouts early
11741
+ const scrollTimeout = setTimeout(() => {
11742
+ WINDOW.removeEventListener('scroll', scrollHandler);
11743
+ }, config.scrollTimeout);
11744
+
11745
+ cleanup = () => {
11746
+ clearTimeout(timeout);
11747
+ clearTimeout(scrollTimeout);
11748
+ obs.disconnect();
11749
+ WINDOW.removeEventListener('scroll', scrollHandler);
11750
+ };
11751
+ }
11752
+
11753
+ function maybeHandleSlowClick(
11754
+ replay,
11755
+ clickBreadcrumb,
11756
+ threshold,
11757
+ timeout,
11758
+ endReason,
11759
+ ) {
11760
+ const now = Date.now();
11761
+ const timeAfterClickMs = now - clickBreadcrumb.timestamp * 1000;
11762
+
11763
+ if (timeAfterClickMs > threshold) {
11764
+ handleSlowClick(replay, clickBreadcrumb, Math.min(timeAfterClickMs, timeout), endReason);
11765
+ return true;
11766
+ }
11767
+
11768
+ return false;
11769
+ }
11770
+
11771
+ function handleSlowClick(
11772
+ replay,
11773
+ clickBreadcrumb,
11774
+ timeAfterClickMs,
11775
+ endReason,
11776
+ ) {
11777
+ const breadcrumb = {
11778
+ message: clickBreadcrumb.message,
11779
+ timestamp: clickBreadcrumb.timestamp,
11780
+ category: 'ui.slowClickDetected',
11781
+ data: {
11782
+ ...clickBreadcrumb.data,
11783
+ url: WINDOW.location.href,
11784
+ // TODO FN: add parametrized route, when possible
11785
+ timeAfterClickMs,
11786
+ endReason,
11787
+ },
11788
+ };
11789
+
11790
+ addBreadcrumbEvent(replay, breadcrumb);
11791
+ }
11792
+
11793
+ const SLOW_CLICK_IGNORE_TAGS = ['SELECT', 'OPTION'];
11794
+
11795
+ function ignoreElement(node, config) {
11796
+ // If <input> tag, we only want to consider input[type='submit'] & input[type='button']
11797
+ if (node.tagName === 'INPUT' && !['submit', 'button'].includes(node.getAttribute('type') || '')) {
11798
+ return true;
11799
+ }
11800
+
11801
+ if (SLOW_CLICK_IGNORE_TAGS.includes(node.tagName)) {
11802
+ return true;
11803
+ }
11804
+
11805
+ // If <a> tag, detect special variants that may not lead to an action
11806
+ // If target !== _self, we may open the link somewhere else, which would lead to no action
11807
+ // Also, when downloading a file, we may not leave the page, but still not trigger an action
11808
+ if (
11809
+ node.tagName === 'A' &&
11810
+ (node.hasAttribute('download') || (node.hasAttribute('target') && node.getAttribute('target') !== '_self'))
11811
+ ) {
11812
+ return true;
11813
+ }
11814
+
11815
+ if (config.ignoreSelector && node.matches(config.ignoreSelector)) {
11816
+ return true;
11817
+ }
11818
+
11819
+ return false;
11820
+ }
11821
+
11822
+ // Note that these are the serialized attributes and not attributes directly on
11823
+ // the DOM Node. Attributes we are interested in:
11824
+ const ATTRIBUTES_TO_RECORD = new Set([
11825
+ 'id',
11826
+ 'class',
11827
+ 'aria-label',
11828
+ 'role',
11829
+ 'name',
11830
+ 'alt',
11831
+ 'title',
11832
+ 'data-test-id',
11833
+ 'data-testid',
11834
+ ]);
11835
+
11836
+ /**
11837
+ * Inclusion list of attributes that we want to record from the DOM element
11838
+ */
11839
+ function getAttributesToRecord(attributes) {
11840
+ const obj = {};
11841
+ for (const key in attributes) {
11842
+ if (ATTRIBUTES_TO_RECORD.has(key)) {
11843
+ let normalizedKey = key;
11844
+
11845
+ if (key === 'data-testid' || key === 'data-test-id') {
11846
+ normalizedKey = 'testId';
11847
+ }
11848
+
11849
+ obj[normalizedKey] = attributes[key];
11850
+ }
11851
+ }
11852
+
11853
+ return obj;
11854
+ }
11855
+
11856
+ const handleDomListener = (
11857
+ replay,
11858
+ ) => {
11859
+ const slowClickExperiment = replay.getOptions()._experiments.slowClicks;
11860
+
11861
+ const slowClickConfig = slowClickExperiment
11862
+ ? {
11863
+ threshold: slowClickExperiment.threshold,
11864
+ timeout: slowClickExperiment.timeout,
11865
+ scrollTimeout: slowClickExperiment.scrollTimeout,
11866
+ ignoreSelector: slowClickExperiment.ignoreSelectors ? slowClickExperiment.ignoreSelectors.join(',') : '',
11867
+ }
11868
+ : undefined;
11869
+
11870
+ return (handlerData) => {
11871
+ if (!replay.isEnabled()) {
11872
+ return;
11873
+ }
11874
+
11875
+ const result = handleDom(handlerData);
11876
+
11877
+ if (!result) {
11878
+ return;
11879
+ }
11880
+
11881
+ const isClick = handlerData.name === 'click';
11882
+ const event = isClick && (handlerData.event );
11883
+ // Ignore clicks if ctrl/alt/meta keys are held down as they alter behavior of clicks (e.g. open in new tab)
11884
+ if (isClick && slowClickConfig && event && !event.altKey && !event.metaKey && !event.ctrlKey) {
11885
+ detectSlowClick(
11886
+ replay,
11887
+ slowClickConfig,
11888
+ result ,
11889
+ getClickTargetNode(handlerData.event) ,
11890
+ );
11891
+ }
11892
+
11893
+ addBreadcrumbEvent(replay, result);
11894
+ };
11895
+ };
11896
+
11897
+ /** Get the base DOM breadcrumb. */
11898
+ function getBaseDomBreadcrumb(target, message) {
11899
+ // `__sn` property is the serialized node created by rrweb
11900
+ const serializedNode = target && isRrwebNode(target) && target.__sn.type === NodeType.Element ? target.__sn : null;
11901
+
11902
+ return {
11903
+ message,
11904
+ data: serializedNode
11905
+ ? {
11906
+ nodeId: serializedNode.id,
11907
+ node: {
11908
+ id: serializedNode.id,
11909
+ tagName: serializedNode.tagName,
11910
+ textContent: target
11911
+ ? Array.from(target.childNodes)
11912
+ .map(
11913
+ (node) => '__sn' in node && node.__sn.type === NodeType.Text && node.__sn.textContent,
11914
+ )
11915
+ .filter(Boolean) // filter out empty values
11916
+ .map(text => (text ).trim())
11917
+ .join('')
11918
+ : '',
11919
+ attributes: getAttributesToRecord(serializedNode.attributes),
11920
+ },
11921
+ }
11922
+ : {},
11923
+ };
11924
+ }
11925
+
11926
+ /**
11927
+ * An event handler to react to DOM events.
11928
+ * Exported for tests.
11929
+ */
11930
+ function handleDom(handlerData) {
11931
+ const { target, message } = getDomTarget(handlerData);
11932
+
11933
+ return createBreadcrumb({
11934
+ category: `ui.${handlerData.name}`,
11935
+ ...getBaseDomBreadcrumb(target, message),
11936
+ });
11937
+ }
11938
+
11939
+ function getDomTarget(handlerData) {
11940
+ const isClick = handlerData.name === 'click';
11941
+
11942
+ let message;
11943
+ let target = null;
11944
+
11945
+ // Accessing event.target can throw (see getsentry/raven-js#838, #768)
11946
+ try {
11947
+ target = isClick ? getClickTargetNode(handlerData.event) : getTargetNode(handlerData.event);
11948
+ message = htmlTreeAsString(target, { maxStringLength: 200 }) || '<unknown>';
11949
+ } catch (e) {
11950
+ message = '<unknown>';
11951
+ }
11952
+
11953
+ return { target, message };
11954
+ }
11955
+
11956
+ function isRrwebNode(node) {
11957
+ return '__sn' in node;
11958
+ }
11959
+
11960
+ function getTargetNode(event) {
11961
+ if (isEventWithTarget(event)) {
11962
+ return event.target ;
11963
+ }
11964
+
11965
+ return event;
11966
+ }
11967
+
11968
+ const INTERACTIVE_SELECTOR = 'button,a';
11969
+
11970
+ // For clicks, we check if the target is inside of a button or link
11971
+ // If so, we use this as the target instead
11972
+ // This is useful because if you click on the image in <button><img></button>,
11973
+ // The target will be the image, not the button, which we don't want here
11974
+ function getClickTargetNode(event) {
11975
+ const target = getTargetNode(event);
11976
+
11977
+ if (!target || !(target instanceof Element)) {
11978
+ return target;
11979
+ }
11980
+
11981
+ const closestInteractive = target.closest(INTERACTIVE_SELECTOR);
11982
+ return closestInteractive || target;
11983
+ }
11984
+
11985
+ function isEventWithTarget(event) {
11986
+ return typeof event === 'object' && !!event && 'target' in event;
11987
+ }
11988
+
11989
+ /** Handle keyboard events & create breadcrumbs. */
11990
+ function handleKeyboardEvent(replay, event) {
11991
+ if (!replay.isEnabled()) {
11992
+ return;
11993
+ }
11994
+
11995
+ replay.triggerUserActivity();
11996
+
11997
+ const breadcrumb = getKeyboardBreadcrumb(event);
11998
+
11999
+ if (!breadcrumb) {
12000
+ return;
12001
+ }
12002
+
12003
+ addBreadcrumbEvent(replay, breadcrumb);
12004
+ }
12005
+
12006
+ /** exported only for tests */
12007
+ function getKeyboardBreadcrumb(event) {
12008
+ const { metaKey, shiftKey, ctrlKey, altKey, key, target } = event;
12009
+
12010
+ // never capture for input fields
12011
+ if (!target || isInputElement(target )) {
12012
+ return null;
12013
+ }
12014
+
12015
+ // Note: We do not consider shift here, as that means "uppercase"
12016
+ const hasModifierKey = metaKey || ctrlKey || altKey;
12017
+ const isCharacterKey = key.length === 1; // other keys like Escape, Tab, etc have a longer length
12018
+
12019
+ // Do not capture breadcrumb if only a word key is pressed
12020
+ // This could leak e.g. user input
12021
+ if (!hasModifierKey && isCharacterKey) {
12022
+ return null;
12023
+ }
12024
+
12025
+ const message = htmlTreeAsString(target, { maxStringLength: 200 }) || '<unknown>';
12026
+ const baseBreadcrumb = getBaseDomBreadcrumb(target , message);
12027
+
12028
+ return createBreadcrumb({
12029
+ category: 'ui.keyDown',
12030
+ message,
12031
+ data: {
12032
+ ...baseBreadcrumb.data,
12033
+ metaKey,
12034
+ shiftKey,
12035
+ ctrlKey,
12036
+ altKey,
12037
+ key,
12038
+ },
12039
+ });
12040
+ }
12041
+
12042
+ function isInputElement(target) {
12043
+ return target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable;
12044
+ }
12045
+
11346
12046
  const NAVIGATION_ENTRY_KEYS = [
11347
12047
  'name',
11348
12048
  'type',
@@ -11496,20 +12196,19 @@ class EventBufferArray {
11496
12196
  return this.events.length > 0;
11497
12197
  }
11498
12198
 
12199
+ /** @inheritdoc */
12200
+ get type() {
12201
+ return 'sync';
12202
+ }
12203
+
11499
12204
  /** @inheritdoc */
11500
12205
  destroy() {
11501
12206
  this.events = [];
11502
12207
  }
11503
12208
 
11504
12209
  /** @inheritdoc */
11505
- async addEvent(event, isCheckout) {
11506
- if (isCheckout) {
11507
- this.events = [event];
11508
- return;
11509
- }
11510
-
12210
+ async addEvent(event) {
11511
12211
  this.events.push(event);
11512
- return;
11513
12212
  }
11514
12213
 
11515
12214
  /** @inheritdoc */
@@ -11523,6 +12222,22 @@ class EventBufferArray {
11523
12222
  resolve(JSON.stringify(eventsRet));
11524
12223
  });
11525
12224
  }
12225
+
12226
+ /** @inheritdoc */
12227
+ clear() {
12228
+ this.events = [];
12229
+ }
12230
+
12231
+ /** @inheritdoc */
12232
+ getEarliestTimestamp() {
12233
+ const timestamp = this.events.map(event => event.timestamp).sort()[0];
12234
+
12235
+ if (!timestamp) {
12236
+ return null;
12237
+ }
12238
+
12239
+ return timestampToMs(timestamp);
12240
+ }
11526
12241
  }
11527
12242
 
11528
12243
  /**
@@ -11630,11 +12345,20 @@ class WorkerHandler {
11630
12345
  * Exported only for testing.
11631
12346
  */
11632
12347
  class EventBufferCompressionWorker {
11633
- /** @inheritdoc */
11634
12348
 
11635
12349
  constructor(worker) {
11636
12350
  this._worker = new WorkerHandler(worker);
11637
- this.hasEvents = false;
12351
+ this._earliestTimestamp = null;
12352
+ }
12353
+
12354
+ /** @inheritdoc */
12355
+ get hasEvents() {
12356
+ return !!this._earliestTimestamp;
12357
+ }
12358
+
12359
+ /** @inheritdoc */
12360
+ get type() {
12361
+ return 'worker';
11638
12362
  }
11639
12363
 
11640
12364
  /**
@@ -11657,13 +12381,10 @@ class EventBufferCompressionWorker {
11657
12381
  *
11658
12382
  * Returns true if event was successfuly received and processed by worker.
11659
12383
  */
11660
- async addEvent(event, isCheckout) {
11661
- this.hasEvents = true;
11662
-
11663
- if (isCheckout) {
11664
- // This event is a checkout, make sure worker buffer is cleared before
11665
- // proceeding.
11666
- await this._clear();
12384
+ addEvent(event) {
12385
+ const timestamp = timestampToMs(event.timestamp);
12386
+ if (!this._earliestTimestamp || timestamp < this._earliestTimestamp) {
12387
+ this._earliestTimestamp = timestamp;
11667
12388
  }
11668
12389
 
11669
12390
  return this._sendEventToWorker(event);
@@ -11676,6 +12397,18 @@ class EventBufferCompressionWorker {
11676
12397
  return this._finishRequest();
11677
12398
  }
11678
12399
 
12400
+ /** @inheritdoc */
12401
+ clear() {
12402
+ this._earliestTimestamp = null;
12403
+ // We do not wait on this, as we assume the order of messages is consistent for the worker
12404
+ void this._worker.postMessage('clear');
12405
+ }
12406
+
12407
+ /** @inheritdoc */
12408
+ getEarliestTimestamp() {
12409
+ return this._earliestTimestamp;
12410
+ }
12411
+
11679
12412
  /**
11680
12413
  * Send the event to the worker.
11681
12414
  */
@@ -11689,15 +12422,10 @@ class EventBufferCompressionWorker {
11689
12422
  async _finishRequest() {
11690
12423
  const response = await this._worker.postMessage('finish');
11691
12424
 
11692
- this.hasEvents = false;
12425
+ this._earliestTimestamp = null;
11693
12426
 
11694
12427
  return response;
11695
12428
  }
11696
-
11697
- /** Clear any pending events from the worker. */
11698
- _clear() {
11699
- return this._worker.postMessage('clear');
11700
- }
11701
12429
  }
11702
12430
 
11703
12431
  /**
@@ -11715,6 +12443,11 @@ class EventBufferProxy {
11715
12443
  this._ensureWorkerIsLoadedPromise = this._ensureWorkerIsLoaded();
11716
12444
  }
11717
12445
 
12446
+ /** @inheritdoc */
12447
+ get type() {
12448
+ return this._used.type;
12449
+ }
12450
+
11718
12451
  /** @inheritDoc */
11719
12452
  get hasEvents() {
11720
12453
  return this._used.hasEvents;
@@ -11726,13 +12459,23 @@ class EventBufferProxy {
11726
12459
  this._compression.destroy();
11727
12460
  }
11728
12461
 
12462
+ /** @inheritdoc */
12463
+ clear() {
12464
+ return this._used.clear();
12465
+ }
12466
+
12467
+ /** @inheritdoc */
12468
+ getEarliestTimestamp() {
12469
+ return this._used.getEarliestTimestamp();
12470
+ }
12471
+
11729
12472
  /**
11730
12473
  * Add an event to the event buffer.
11731
12474
  *
11732
12475
  * Returns true if event was successfully added.
11733
12476
  */
11734
- addEvent(event, isCheckout) {
11735
- return this._used.addEvent(event, isCheckout);
12477
+ addEvent(event) {
12478
+ return this._used.addEvent(event);
11736
12479
  }
11737
12480
 
11738
12481
  /** @inheritDoc */
@@ -11807,6 +12550,31 @@ function createEventBuffer({ useCompression }) {
11807
12550
  return new EventBufferArray();
11808
12551
  }
11809
12552
 
12553
+ /**
12554
+ * Removes the session from Session Storage and unsets session in replay instance
12555
+ */
12556
+ function clearSession(replay) {
12557
+ deleteSession();
12558
+ replay.session = undefined;
12559
+ }
12560
+
12561
+ /**
12562
+ * Deletes a session from storage
12563
+ */
12564
+ function deleteSession() {
12565
+ const hasSessionStorage = 'sessionStorage' in WINDOW;
12566
+
12567
+ if (!hasSessionStorage) {
12568
+ return;
12569
+ }
12570
+
12571
+ try {
12572
+ WINDOW.sessionStorage.removeItem(REPLAY_SESSION_KEY);
12573
+ } catch (e) {
12574
+ // Ignore potential SecurityError exceptions
12575
+ }
12576
+ }
12577
+
11810
12578
  /**
11811
12579
  * Given an initial timestamp and an expiry duration, checks to see if current
11812
12580
  * time should be considered as expired.
@@ -11837,11 +12605,26 @@ function isSessionExpired(session, timeouts, targetTime = +new Date()) {
11837
12605
  // First, check that maximum session length has not been exceeded
11838
12606
  isExpired(session.started, timeouts.maxSessionLife, targetTime) ||
11839
12607
  // check that the idle timeout has not been exceeded (i.e. user has
11840
- // performed an action within the last `idleTimeout` ms)
11841
- isExpired(session.lastActivity, timeouts.sessionIdle, targetTime)
12608
+ // performed an action within the last `sessionIdleExpire` ms)
12609
+ isExpired(session.lastActivity, timeouts.sessionIdleExpire, targetTime)
11842
12610
  );
11843
12611
  }
11844
12612
 
12613
+ /**
12614
+ * Given a sample rate, returns true if replay should be sampled.
12615
+ *
12616
+ * 1.0 = 100% sampling
12617
+ * 0.0 = 0% sampling
12618
+ */
12619
+ function isSampled(sampleRate) {
12620
+ if (sampleRate === undefined) {
12621
+ return false;
12622
+ }
12623
+
12624
+ // Math.random() returns a number in range of 0 to 1 (inclusive of 0, but not 1)
12625
+ return Math.random() < sampleRate;
12626
+ }
12627
+
11845
12628
  /**
11846
12629
  * Save a session to session storage.
11847
12630
  */
@@ -11858,21 +12641,6 @@ function saveSession(session) {
11858
12641
  }
11859
12642
  }
11860
12643
 
11861
- /**
11862
- * Given a sample rate, returns true if replay should be sampled.
11863
- *
11864
- * 1.0 = 100% sampling
11865
- * 0.0 = 0% sampling
11866
- */
11867
- function isSampled(sampleRate) {
11868
- if (sampleRate === undefined) {
11869
- return false;
11870
- }
11871
-
11872
- // Math.random() returns a number in range of 0 to 1 (inclusive of 0, but not 1)
11873
- return Math.random() < sampleRate;
11874
- }
11875
-
11876
12644
  /**
11877
12645
  * Get a session with defaults & applied sampling.
11878
12646
  */
@@ -11891,14 +12659,15 @@ function makeSession(session) {
11891
12659
  lastActivity,
11892
12660
  segmentId,
11893
12661
  sampled,
12662
+ shouldRefresh: true,
11894
12663
  };
11895
12664
  }
11896
12665
 
11897
12666
  /**
11898
12667
  * Get the sampled status for a session based on sample rates & current sampled status.
11899
12668
  */
11900
- function getSessionSampleType(sessionSampleRate, errorSampleRate) {
11901
- return isSampled(sessionSampleRate) ? 'session' : isSampled(errorSampleRate) ? 'error' : false;
12669
+ function getSessionSampleType(sessionSampleRate, allowBuffering) {
12670
+ return isSampled(sessionSampleRate) ? 'session' : allowBuffering ? 'buffer' : false;
11902
12671
  }
11903
12672
 
11904
12673
  /**
@@ -11906,8 +12675,8 @@ function getSessionSampleType(sessionSampleRate, errorSampleRate) {
11906
12675
  * that all replays will be saved to as attachments. Currently, we only expect
11907
12676
  * one of these Sentry events per "replay session".
11908
12677
  */
11909
- function createSession({ sessionSampleRate, errorSampleRate, stickySession = false }) {
11910
- const sampled = getSessionSampleType(sessionSampleRate, errorSampleRate);
12678
+ function createSession({ sessionSampleRate, allowBuffering, stickySession = false }) {
12679
+ const sampled = getSessionSampleType(sessionSampleRate, allowBuffering);
11911
12680
  const session = makeSession({
11912
12681
  sampled,
11913
12682
  });
@@ -11955,7 +12724,7 @@ function getSession({
11955
12724
  currentSession,
11956
12725
  stickySession,
11957
12726
  sessionSampleRate,
11958
- errorSampleRate,
12727
+ allowBuffering,
11959
12728
  }) {
11960
12729
  // If session exists and is passed, use it instead of always hitting session storage
11961
12730
  const session = currentSession || (stickySession && fetchSession());
@@ -11968,8 +12737,9 @@ function getSession({
11968
12737
 
11969
12738
  if (!isExpired) {
11970
12739
  return { type: 'saved', session };
11971
- } else if (session.sampled === 'error') {
11972
- // Error samples should not be re-created when expired, but instead we stop when the replay is done
12740
+ } else if (!session.shouldRefresh) {
12741
+ // In this case, stop
12742
+ // This is the case if we have an error session that is completed (=triggered an error)
11973
12743
  const discardedSession = makeSession({ sampled: false });
11974
12744
  return { type: 'new', session: discardedSession };
11975
12745
  } else {
@@ -11981,65 +12751,12 @@ function getSession({
11981
12751
  const newSession = createSession({
11982
12752
  stickySession,
11983
12753
  sessionSampleRate,
11984
- errorSampleRate,
12754
+ allowBuffering,
11985
12755
  });
11986
12756
 
11987
12757
  return { type: 'new', session: newSession };
11988
12758
  }
11989
12759
 
11990
- /**
11991
- * Add an event to the event buffer.
11992
- * `isCheckout` is true if this is either the very first event, or an event triggered by `checkoutEveryNms`.
11993
- */
11994
- async function addEvent(
11995
- replay,
11996
- event,
11997
- isCheckout,
11998
- ) {
11999
- if (!replay.eventBuffer) {
12000
- // This implies that `_isEnabled` is false
12001
- return null;
12002
- }
12003
-
12004
- if (replay.isPaused()) {
12005
- // Do not add to event buffer when recording is paused
12006
- return null;
12007
- }
12008
-
12009
- // TODO: sadness -- we will want to normalize timestamps to be in ms -
12010
- // requires coordination with frontend
12011
- const isMs = event.timestamp > 9999999999;
12012
- const timestampInMs = isMs ? event.timestamp : event.timestamp * 1000;
12013
-
12014
- // Throw out events that happen more than 5 minutes ago. This can happen if
12015
- // page has been left open and idle for a long period of time and user
12016
- // comes back to trigger a new session. The performance entries rely on
12017
- // `performance.timeOrigin`, which is when the page first opened.
12018
- if (timestampInMs + replay.timeouts.sessionIdle < Date.now()) {
12019
- return null;
12020
- }
12021
-
12022
- // Only record earliest event if a new session was created, otherwise it
12023
- // shouldn't be relevant
12024
- const earliestEvent = replay.getContext().earliestEvent;
12025
- if (replay.session && replay.session.segmentId === 0 && (!earliestEvent || timestampInMs < earliestEvent)) {
12026
- replay.getContext().earliestEvent = timestampInMs;
12027
- }
12028
-
12029
- try {
12030
- return await replay.eventBuffer.addEvent(event, isCheckout);
12031
- } catch (error) {
12032
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error(error);
12033
- replay.stop('addEvent');
12034
-
12035
- const client = getCurrentHub().getClient();
12036
-
12037
- if (client) {
12038
- client.recordDroppedEvent('internal_sdk_error', 'replay');
12039
- }
12040
- }
12041
- }
12042
-
12043
12760
  /** If the event is an error event */
12044
12761
  function isErrorEvent(event) {
12045
12762
  return !event.type;
@@ -12089,31 +12806,21 @@ function handleAfterSendEvent(replay) {
12089
12806
  return;
12090
12807
  }
12091
12808
 
12092
- // Add error to list of errorIds of replay
12809
+ // Add error to list of errorIds of replay. This is ok to do even if not
12810
+ // sampled because context will get reset at next checkout.
12811
+ // XXX: There is also a race condition where it's possible to capture an
12812
+ // error to Sentry before Replay SDK has loaded, but response returns after
12813
+ // it was loaded, and this gets called.
12093
12814
  if (event.event_id) {
12094
12815
  replay.getContext().errorIds.add(event.event_id);
12095
12816
  }
12096
12817
 
12097
- // Trigger error recording
12818
+ // If error event is tagged with replay id it means it was sampled (when in buffer mode)
12098
12819
  // Need to be very careful that this does not cause an infinite loop
12099
- if (
12100
- replay.recordingMode === 'error' &&
12101
- event.exception &&
12102
- event.message !== UNABLE_TO_SEND_REPLAY // ignore this error because otherwise we could loop indefinitely with trying to capture replay and failing
12103
- ) {
12104
- setTimeout(async () => {
12105
- // Allow flush to complete before resuming as a session recording, otherwise
12106
- // the checkout from `startRecording` may be included in the payload.
12107
- // Prefer to keep the error replay as a separate (and smaller) segment
12108
- // than the session replay.
12109
- await replay.flushImmediate();
12110
-
12111
- if (replay.stopRecording()) {
12112
- // Reset all "capture on error" configuration before
12113
- // starting a new recording
12114
- replay.recordingMode = 'session';
12115
- replay.startRecording();
12116
- }
12820
+ if (replay.recordingMode === 'buffer' && event.tags && event.tags.replayId) {
12821
+ setTimeout(() => {
12822
+ // Capture current event buffer as new replay
12823
+ void replay.sendBufferedReplayOrFlush();
12117
12824
  });
12118
12825
  }
12119
12826
  };
@@ -12135,166 +12842,6 @@ function isBaseTransportSend() {
12135
12842
  );
12136
12843
  }
12137
12844
 
12138
- var NodeType;
12139
- (function (NodeType) {
12140
- NodeType[NodeType["Document"] = 0] = "Document";
12141
- NodeType[NodeType["DocumentType"] = 1] = "DocumentType";
12142
- NodeType[NodeType["Element"] = 2] = "Element";
12143
- NodeType[NodeType["Text"] = 3] = "Text";
12144
- NodeType[NodeType["CDATA"] = 4] = "CDATA";
12145
- NodeType[NodeType["Comment"] = 5] = "Comment";
12146
- })(NodeType || (NodeType = {}));
12147
-
12148
- /**
12149
- * Create a breadcrumb for a replay.
12150
- */
12151
- function createBreadcrumb(
12152
- breadcrumb,
12153
- ) {
12154
- return {
12155
- timestamp: Date.now() / 1000,
12156
- type: 'default',
12157
- ...breadcrumb,
12158
- };
12159
- }
12160
-
12161
- /**
12162
- * Add a breadcrumb event to replay.
12163
- */
12164
- function addBreadcrumbEvent(replay, breadcrumb) {
12165
- if (breadcrumb.category === 'sentry.transaction') {
12166
- return;
12167
- }
12168
-
12169
- if (['ui.click', 'ui.input'].includes(breadcrumb.category )) {
12170
- replay.triggerUserActivity();
12171
- } else {
12172
- replay.checkAndHandleExpiredSession();
12173
- }
12174
-
12175
- replay.addUpdate(() => {
12176
- void addEvent(replay, {
12177
- type: EventType.Custom,
12178
- // TODO: We were converting from ms to seconds for breadcrumbs, spans,
12179
- // but maybe we should just keep them as milliseconds
12180
- timestamp: (breadcrumb.timestamp || 0) * 1000,
12181
- data: {
12182
- tag: 'breadcrumb',
12183
- payload: breadcrumb,
12184
- },
12185
- });
12186
-
12187
- // Do not flush after console log messages
12188
- return breadcrumb.category === 'console';
12189
- });
12190
- }
12191
-
12192
- // Note that these are the serialized attributes and not attributes directly on
12193
- // the DOM Node. Attributes we are interested in:
12194
- const ATTRIBUTES_TO_RECORD = new Set([
12195
- 'id',
12196
- 'class',
12197
- 'aria-label',
12198
- 'role',
12199
- 'name',
12200
- 'alt',
12201
- 'title',
12202
- 'data-test-id',
12203
- 'data-testid',
12204
- ]);
12205
-
12206
- /**
12207
- * Inclusion list of attributes that we want to record from the DOM element
12208
- */
12209
- function getAttributesToRecord(attributes) {
12210
- const obj = {};
12211
- for (const key in attributes) {
12212
- if (ATTRIBUTES_TO_RECORD.has(key)) {
12213
- let normalizedKey = key;
12214
-
12215
- if (key === 'data-testid' || key === 'data-test-id') {
12216
- normalizedKey = 'testId';
12217
- }
12218
-
12219
- obj[normalizedKey] = attributes[key];
12220
- }
12221
- }
12222
-
12223
- return obj;
12224
- }
12225
-
12226
- const handleDomListener =
12227
- (replay) =>
12228
- (handlerData) => {
12229
- if (!replay.isEnabled()) {
12230
- return;
12231
- }
12232
-
12233
- const result = handleDom(handlerData);
12234
-
12235
- if (!result) {
12236
- return;
12237
- }
12238
-
12239
- addBreadcrumbEvent(replay, result);
12240
- };
12241
-
12242
- /**
12243
- * An event handler to react to DOM events.
12244
- */
12245
- function handleDom(handlerData) {
12246
- let target;
12247
- let targetNode;
12248
-
12249
- // Accessing event.target can throw (see getsentry/raven-js#838, #768)
12250
- try {
12251
- targetNode = getTargetNode(handlerData);
12252
- target = htmlTreeAsString(targetNode);
12253
- } catch (e) {
12254
- target = '<unknown>';
12255
- }
12256
-
12257
- // `__sn` property is the serialized node created by rrweb
12258
- const serializedNode =
12259
- targetNode && '__sn' in targetNode && targetNode.__sn.type === NodeType.Element ? targetNode.__sn : null;
12260
-
12261
- return createBreadcrumb({
12262
- category: `ui.${handlerData.name}`,
12263
- message: target,
12264
- data: serializedNode
12265
- ? {
12266
- nodeId: serializedNode.id,
12267
- node: {
12268
- id: serializedNode.id,
12269
- tagName: serializedNode.tagName,
12270
- textContent: targetNode
12271
- ? Array.from(targetNode.childNodes)
12272
- .map(
12273
- (node) => '__sn' in node && node.__sn.type === NodeType.Text && node.__sn.textContent,
12274
- )
12275
- .filter(Boolean) // filter out empty values
12276
- .map(text => (text ).trim())
12277
- .join('')
12278
- : '',
12279
- attributes: getAttributesToRecord(serializedNode.attributes),
12280
- },
12281
- }
12282
- : {},
12283
- });
12284
- }
12285
-
12286
- function getTargetNode(handlerData) {
12287
- if (isEventWithTarget(handlerData.event)) {
12288
- return handlerData.event.target;
12289
- }
12290
-
12291
- return handlerData.event;
12292
- }
12293
-
12294
- function isEventWithTarget(event) {
12295
- return !!(event ).target;
12296
- }
12297
-
12298
12845
  /**
12299
12846
  * Returns true if we think the given event is an error originating inside of rrweb.
12300
12847
  */
@@ -12318,6 +12865,30 @@ function isRrwebError(event, hint) {
12318
12865
  });
12319
12866
  }
12320
12867
 
12868
+ /**
12869
+ * Determine if event should be sampled (only applies in buffer mode).
12870
+ * When an event is captured by `hanldleGlobalEvent`, when in buffer mode
12871
+ * we determine if we want to sample the error or not.
12872
+ */
12873
+ function shouldSampleForBufferEvent(replay, event) {
12874
+ if (replay.recordingMode !== 'buffer') {
12875
+ return false;
12876
+ }
12877
+
12878
+ // ignore this error because otherwise we could loop indefinitely with
12879
+ // trying to capture replay and failing
12880
+ if (event.message === UNABLE_TO_SEND_REPLAY) {
12881
+ return false;
12882
+ }
12883
+
12884
+ // Require the event to be an error event & to have an exception
12885
+ if (!event.exception || event.type) {
12886
+ return false;
12887
+ }
12888
+
12889
+ return isSampled(replay.getOptions().errorSampleRate);
12890
+ }
12891
+
12321
12892
  /**
12322
12893
  * Returns a listener to be added to `addGlobalEventProcessor(listener)`.
12323
12894
  */
@@ -12347,8 +12918,16 @@ function handleGlobalEventListener(
12347
12918
  return null;
12348
12919
  }
12349
12920
 
12350
- // Only tag transactions with replayId if not waiting for an error
12351
- if (isErrorEvent(event) || (isTransactionEvent(event) && replay.recordingMode === 'session')) {
12921
+ // When in buffer mode, we decide to sample here.
12922
+ // Later, in `handleAfterSendEvent`, if the replayId is set, we know that we sampled
12923
+ // And convert the buffer session to a full session
12924
+ const isErrorEventSampled = shouldSampleForBufferEvent(replay, event);
12925
+
12926
+ // Tag errors if it has been sampled in buffer mode, or if it is session mode
12927
+ // Only tag transactions if in session mode
12928
+ const shouldTagReplayId = isErrorEventSampled || replay.recordingMode === 'session';
12929
+
12930
+ if (shouldTagReplayId) {
12352
12931
  event.tags = { ...event.tags, replayId: replay.getSessionId() };
12353
12932
  }
12354
12933
 
@@ -12398,7 +12977,7 @@ function createPerformanceSpans(
12398
12977
  ) {
12399
12978
  return entries.map(({ type, start, end, name, data }) =>
12400
12979
  addEvent(replay, {
12401
- type: EventType.Custom,
12980
+ type: EventType$1.Custom,
12402
12981
  timestamp: start,
12403
12982
  data: {
12404
12983
  tag: 'performanceSpan',
@@ -12547,12 +13126,14 @@ function handleFetchSpanListener(replay) {
12547
13126
  function handleXhr(handlerData) {
12548
13127
  const { startTimestamp, endTimestamp, xhr } = handlerData;
12549
13128
 
12550
- if (!startTimestamp || !endTimestamp || !xhr.__sentry_xhr__) {
13129
+ const sentryXhrData = xhr[SENTRY_XHR_DATA_KEY];
13130
+
13131
+ if (!startTimestamp || !endTimestamp || !sentryXhrData) {
12551
13132
  return null;
12552
13133
  }
12553
13134
 
12554
13135
  // This is only used as a fallback, so we know the body sizes are never set here
12555
- const { method, url, status_code: statusCode } = xhr.__sentry_xhr__;
13136
+ const { method, url, status_code: statusCode } = sentryXhrData;
12556
13137
 
12557
13138
  if (url === undefined) {
12558
13139
  return null;
@@ -12585,6 +13166,393 @@ function handleXhrSpanListener(replay) {
12585
13166
  };
12586
13167
  }
12587
13168
 
13169
+ const OBJ = 10;
13170
+ const OBJ_KEY = 11;
13171
+ const OBJ_KEY_STR = 12;
13172
+ const OBJ_VAL = 13;
13173
+ const OBJ_VAL_STR = 14;
13174
+ const OBJ_VAL_COMPLETED = 15;
13175
+
13176
+ const ARR = 20;
13177
+ const ARR_VAL = 21;
13178
+ const ARR_VAL_STR = 22;
13179
+ const ARR_VAL_COMPLETED = 23;
13180
+
13181
+ const ALLOWED_PRIMITIVES = ['true', 'false', 'null'];
13182
+
13183
+ /**
13184
+ * Complete an incomplete JSON string.
13185
+ * This will ensure that the last element always has a `"~~"` to indicate it was truncated.
13186
+ * For example, `[1,2,` will be completed to `[1,2,"~~"]`
13187
+ * and `{"aa":"b` will be completed to `{"aa":"b~~"}`
13188
+ */
13189
+ function completeJson(incompleteJson, stack) {
13190
+ if (!stack.length) {
13191
+ return incompleteJson;
13192
+ }
13193
+
13194
+ let json = incompleteJson;
13195
+
13196
+ // Most checks are only needed for the last step in the stack
13197
+ const lastPos = stack.length - 1;
13198
+ const lastStep = stack[lastPos];
13199
+
13200
+ json = _fixLastStep(json, lastStep);
13201
+
13202
+ // Complete remaining steps - just add closing brackets
13203
+ for (let i = lastPos; i >= 0; i--) {
13204
+ const step = stack[i];
13205
+
13206
+ switch (step) {
13207
+ case OBJ:
13208
+ json = `${json}}`;
13209
+ break;
13210
+ case ARR:
13211
+ json = `${json}]`;
13212
+ break;
13213
+ }
13214
+ }
13215
+
13216
+ return json;
13217
+ }
13218
+
13219
+ function _fixLastStep(json, lastStep) {
13220
+ switch (lastStep) {
13221
+ // Object cases
13222
+ case OBJ:
13223
+ return `${json}"~~":"~~"`;
13224
+ case OBJ_KEY:
13225
+ return `${json}:"~~"`;
13226
+ case OBJ_KEY_STR:
13227
+ return `${json}~~":"~~"`;
13228
+ case OBJ_VAL:
13229
+ return _maybeFixIncompleteObjValue(json);
13230
+ case OBJ_VAL_STR:
13231
+ return `${json}~~"`;
13232
+ case OBJ_VAL_COMPLETED:
13233
+ return `${json},"~~":"~~"`;
13234
+
13235
+ // Array cases
13236
+ case ARR:
13237
+ return `${json}"~~"`;
13238
+ case ARR_VAL:
13239
+ return _maybeFixIncompleteArrValue(json);
13240
+ case ARR_VAL_STR:
13241
+ return `${json}~~"`;
13242
+ case ARR_VAL_COMPLETED:
13243
+ return `${json},"~~"`;
13244
+ }
13245
+
13246
+ return json;
13247
+ }
13248
+
13249
+ function _maybeFixIncompleteArrValue(json) {
13250
+ const pos = _findLastArrayDelimiter(json);
13251
+
13252
+ if (pos > -1) {
13253
+ const part = json.slice(pos + 1);
13254
+
13255
+ if (ALLOWED_PRIMITIVES.includes(part.trim())) {
13256
+ return `${json},"~~"`;
13257
+ }
13258
+
13259
+ // Everything else is replaced with `"~~"`
13260
+ return `${json.slice(0, pos + 1)}"~~"`;
13261
+ }
13262
+
13263
+ // fallback, this shouldn't happen, to be save
13264
+ return json;
13265
+ }
13266
+
13267
+ function _findLastArrayDelimiter(json) {
13268
+ for (let i = json.length - 1; i >= 0; i--) {
13269
+ const char = json[i];
13270
+
13271
+ if (char === ',' || char === '[') {
13272
+ return i;
13273
+ }
13274
+ }
13275
+
13276
+ return -1;
13277
+ }
13278
+
13279
+ function _maybeFixIncompleteObjValue(json) {
13280
+ const startPos = json.lastIndexOf(':');
13281
+
13282
+ const part = json.slice(startPos + 1);
13283
+
13284
+ if (ALLOWED_PRIMITIVES.includes(part.trim())) {
13285
+ return `${json},"~~":"~~"`;
13286
+ }
13287
+
13288
+ // Everything else is replaced with `"~~"`
13289
+ // This also means we do not have incomplete numbers, e.g `[1` is replaced with `["~~"]`
13290
+ return `${json.slice(0, startPos + 1)}"~~"`;
13291
+ }
13292
+
13293
+ /**
13294
+ * Evaluate an (incomplete) JSON string.
13295
+ */
13296
+ function evaluateJson(json) {
13297
+ const stack = [];
13298
+
13299
+ for (let pos = 0; pos < json.length; pos++) {
13300
+ _evaluateJsonPos(stack, json, pos);
13301
+ }
13302
+
13303
+ return stack;
13304
+ }
13305
+
13306
+ function _evaluateJsonPos(stack, json, pos) {
13307
+ const curStep = stack[stack.length - 1];
13308
+
13309
+ const char = json[pos];
13310
+
13311
+ const whitespaceRegex = /\s/;
13312
+
13313
+ if (whitespaceRegex.test(char)) {
13314
+ return;
13315
+ }
13316
+
13317
+ if (char === '"' && !_isEscaped(json, pos)) {
13318
+ _handleQuote(stack, curStep);
13319
+ return;
13320
+ }
13321
+
13322
+ switch (char) {
13323
+ case '{':
13324
+ _handleObj(stack, curStep);
13325
+ break;
13326
+ case '[':
13327
+ _handleArr(stack, curStep);
13328
+ break;
13329
+ case ':':
13330
+ _handleColon(stack, curStep);
13331
+ break;
13332
+ case ',':
13333
+ _handleComma(stack, curStep);
13334
+ break;
13335
+ case '}':
13336
+ _handleObjClose(stack, curStep);
13337
+ break;
13338
+ case ']':
13339
+ _handleArrClose(stack, curStep);
13340
+ break;
13341
+ }
13342
+ }
13343
+
13344
+ function _handleQuote(stack, curStep) {
13345
+ // End of obj value
13346
+ if (curStep === OBJ_VAL_STR) {
13347
+ stack.pop();
13348
+ stack.push(OBJ_VAL_COMPLETED);
13349
+ return;
13350
+ }
13351
+
13352
+ // End of arr value
13353
+ if (curStep === ARR_VAL_STR) {
13354
+ stack.pop();
13355
+ stack.push(ARR_VAL_COMPLETED);
13356
+ return;
13357
+ }
13358
+
13359
+ // Start of obj value
13360
+ if (curStep === OBJ_VAL) {
13361
+ stack.push(OBJ_VAL_STR);
13362
+ return;
13363
+ }
13364
+
13365
+ // Start of arr value
13366
+ if (curStep === ARR_VAL) {
13367
+ stack.push(ARR_VAL_STR);
13368
+ return;
13369
+ }
13370
+
13371
+ // Start of obj key
13372
+ if (curStep === OBJ) {
13373
+ stack.push(OBJ_KEY_STR);
13374
+ return;
13375
+ }
13376
+
13377
+ // End of obj key
13378
+ if (curStep === OBJ_KEY_STR) {
13379
+ stack.pop();
13380
+ stack.push(OBJ_KEY);
13381
+ return;
13382
+ }
13383
+ }
13384
+
13385
+ function _handleObj(stack, curStep) {
13386
+ // Initial object
13387
+ if (!curStep) {
13388
+ stack.push(OBJ);
13389
+ return;
13390
+ }
13391
+
13392
+ // New object as obj value
13393
+ if (curStep === OBJ_VAL) {
13394
+ stack.push(OBJ);
13395
+ return;
13396
+ }
13397
+
13398
+ // New object as array element
13399
+ if (curStep === ARR_VAL) {
13400
+ stack.push(OBJ);
13401
+ }
13402
+
13403
+ // New object as first array element
13404
+ if (curStep === ARR) {
13405
+ stack.push(OBJ);
13406
+ return;
13407
+ }
13408
+ }
13409
+
13410
+ function _handleArr(stack, curStep) {
13411
+ // Initial array
13412
+ if (!curStep) {
13413
+ stack.push(ARR);
13414
+ stack.push(ARR_VAL);
13415
+ return;
13416
+ }
13417
+
13418
+ // New array as obj value
13419
+ if (curStep === OBJ_VAL) {
13420
+ stack.push(ARR);
13421
+ stack.push(ARR_VAL);
13422
+ return;
13423
+ }
13424
+
13425
+ // New array as array element
13426
+ if (curStep === ARR_VAL) {
13427
+ stack.push(ARR);
13428
+ stack.push(ARR_VAL);
13429
+ }
13430
+
13431
+ // New array as first array element
13432
+ if (curStep === ARR) {
13433
+ stack.push(ARR);
13434
+ stack.push(ARR_VAL);
13435
+ return;
13436
+ }
13437
+ }
13438
+
13439
+ function _handleColon(stack, curStep) {
13440
+ if (curStep === OBJ_KEY) {
13441
+ stack.pop();
13442
+ stack.push(OBJ_VAL);
13443
+ }
13444
+ }
13445
+
13446
+ function _handleComma(stack, curStep) {
13447
+ // Comma after obj value
13448
+ if (curStep === OBJ_VAL) {
13449
+ stack.pop();
13450
+ return;
13451
+ }
13452
+ if (curStep === OBJ_VAL_COMPLETED) {
13453
+ // Pop OBJ_VAL_COMPLETED & OBJ_VAL
13454
+ stack.pop();
13455
+ stack.pop();
13456
+ return;
13457
+ }
13458
+
13459
+ // Comma after arr value
13460
+ if (curStep === ARR_VAL) {
13461
+ // do nothing - basically we'd pop ARR_VAL but add it right back
13462
+ return;
13463
+ }
13464
+
13465
+ if (curStep === ARR_VAL_COMPLETED) {
13466
+ // Pop ARR_VAL_COMPLETED
13467
+ stack.pop();
13468
+
13469
+ // basically we'd pop ARR_VAL but add it right back
13470
+ return;
13471
+ }
13472
+ }
13473
+
13474
+ function _handleObjClose(stack, curStep) {
13475
+ // Empty object {}
13476
+ if (curStep === OBJ) {
13477
+ stack.pop();
13478
+ }
13479
+
13480
+ // Object with element
13481
+ if (curStep === OBJ_VAL) {
13482
+ // Pop OBJ_VAL, OBJ
13483
+ stack.pop();
13484
+ stack.pop();
13485
+ }
13486
+
13487
+ // Obj with element
13488
+ if (curStep === OBJ_VAL_COMPLETED) {
13489
+ // Pop OBJ_VAL_COMPLETED, OBJ_VAL, OBJ
13490
+ stack.pop();
13491
+ stack.pop();
13492
+ stack.pop();
13493
+ }
13494
+
13495
+ // if was obj value, complete it
13496
+ if (stack[stack.length - 1] === OBJ_VAL) {
13497
+ stack.push(OBJ_VAL_COMPLETED);
13498
+ }
13499
+
13500
+ // if was arr value, complete it
13501
+ if (stack[stack.length - 1] === ARR_VAL) {
13502
+ stack.push(ARR_VAL_COMPLETED);
13503
+ }
13504
+ }
13505
+
13506
+ function _handleArrClose(stack, curStep) {
13507
+ // Empty array []
13508
+ if (curStep === ARR) {
13509
+ stack.pop();
13510
+ }
13511
+
13512
+ // Array with element
13513
+ if (curStep === ARR_VAL) {
13514
+ // Pop ARR_VAL, ARR
13515
+ stack.pop();
13516
+ stack.pop();
13517
+ }
13518
+
13519
+ // Array with element
13520
+ if (curStep === ARR_VAL_COMPLETED) {
13521
+ // Pop ARR_VAL_COMPLETED, ARR_VAL, ARR
13522
+ stack.pop();
13523
+ stack.pop();
13524
+ stack.pop();
13525
+ }
13526
+
13527
+ // if was obj value, complete it
13528
+ if (stack[stack.length - 1] === OBJ_VAL) {
13529
+ stack.push(OBJ_VAL_COMPLETED);
13530
+ }
13531
+
13532
+ // if was arr value, complete it
13533
+ if (stack[stack.length - 1] === ARR_VAL) {
13534
+ stack.push(ARR_VAL_COMPLETED);
13535
+ }
13536
+ }
13537
+
13538
+ function _isEscaped(str, pos) {
13539
+ const previousChar = str[pos - 1];
13540
+
13541
+ return previousChar === '\\' && !_isEscaped(str, pos - 1);
13542
+ }
13543
+
13544
+ /* eslint-disable max-lines */
13545
+
13546
+ /**
13547
+ * Takes an incomplete JSON string, and returns a hopefully valid JSON string.
13548
+ * Note that this _can_ fail, so you should check the return value is valid JSON.
13549
+ */
13550
+ function fixJson(incompleteJson) {
13551
+ const stack = evaluateJson(incompleteJson);
13552
+
13553
+ return completeJson(incompleteJson, stack);
13554
+ }
13555
+
12588
13556
  /** Get the size of a body. */
12589
13557
  function getBodySize(
12590
13558
  body,
@@ -12678,51 +13646,68 @@ function makeNetworkReplayBreadcrumb(
12678
13646
  return result;
12679
13647
  }
12680
13648
 
12681
- /** Get either a JSON network body, or a text representation. */
12682
- function getNetworkBody(bodyText) {
12683
- if (!bodyText) {
12684
- return;
12685
- }
12686
-
12687
- try {
12688
- return JSON.parse(bodyText);
12689
- } catch (e2) {
12690
- // return text
12691
- }
12692
-
12693
- return bodyText;
13649
+ /** Build the request or response part of a replay network breadcrumb that was skipped. */
13650
+ function buildSkippedNetworkRequestOrResponse(bodySize) {
13651
+ return {
13652
+ headers: {},
13653
+ size: bodySize,
13654
+ _meta: {
13655
+ warnings: ['URL_SKIPPED'],
13656
+ },
13657
+ };
12694
13658
  }
12695
13659
 
12696
13660
  /** Build the request or response part of a replay network breadcrumb. */
12697
13661
  function buildNetworkRequestOrResponse(
13662
+ headers,
12698
13663
  bodySize,
12699
13664
  body,
12700
13665
  ) {
12701
- if (!bodySize) {
13666
+ if (!bodySize && Object.keys(headers).length === 0) {
12702
13667
  return undefined;
12703
13668
  }
12704
13669
 
13670
+ if (!bodySize) {
13671
+ return {
13672
+ headers,
13673
+ };
13674
+ }
13675
+
12705
13676
  if (!body) {
12706
13677
  return {
13678
+ headers,
12707
13679
  size: bodySize,
12708
13680
  };
12709
13681
  }
12710
13682
 
12711
13683
  const info = {
13684
+ headers,
12712
13685
  size: bodySize,
12713
13686
  };
12714
13687
 
12715
- if (bodySize < NETWORK_BODY_MAX_SIZE) {
12716
- info.body = body;
12717
- } else {
13688
+ const { body: normalizedBody, warnings } = normalizeNetworkBody(body);
13689
+ info.body = normalizedBody;
13690
+ if (warnings.length > 0) {
12718
13691
  info._meta = {
12719
- errors: ['MAX_BODY_SIZE_EXCEEDED'],
13692
+ warnings,
12720
13693
  };
12721
13694
  }
12722
13695
 
12723
13696
  return info;
12724
13697
  }
12725
13698
 
13699
+ /** Filter a set of headers */
13700
+ function getAllowedHeaders(headers, allowedHeaders) {
13701
+ return Object.keys(headers).reduce((filteredHeaders, key) => {
13702
+ const normalizedKey = key.toLowerCase();
13703
+ // Avoid putting empty strings into the headers
13704
+ if (allowedHeaders.includes(normalizedKey) && headers[key]) {
13705
+ filteredHeaders[normalizedKey] = headers[key];
13706
+ }
13707
+ return filteredHeaders;
13708
+ }, {});
13709
+ }
13710
+
12726
13711
  function _serializeFormData(formData) {
12727
13712
  // This is a bit simplified, but gives us a decent estimate
12728
13713
  // This converts e.g. { name: 'Anne Smith', age: 13 } to 'name=Anne+Smith&age=13'
@@ -12730,6 +13715,78 @@ function _serializeFormData(formData) {
12730
13715
  return new URLSearchParams(formData).toString();
12731
13716
  }
12732
13717
 
13718
+ function normalizeNetworkBody(body)
13719
+
13720
+ {
13721
+ if (!body || typeof body !== 'string') {
13722
+ return {
13723
+ body,
13724
+ warnings: [],
13725
+ };
13726
+ }
13727
+
13728
+ const exceedsSizeLimit = body.length > NETWORK_BODY_MAX_SIZE;
13729
+
13730
+ if (_strIsProbablyJson(body)) {
13731
+ try {
13732
+ const json = exceedsSizeLimit ? fixJson(body.slice(0, NETWORK_BODY_MAX_SIZE)) : body;
13733
+ const normalizedBody = JSON.parse(json);
13734
+ return {
13735
+ body: normalizedBody,
13736
+ warnings: exceedsSizeLimit ? ['JSON_TRUNCATED'] : [],
13737
+ };
13738
+ } catch (e3) {
13739
+ return {
13740
+ body: exceedsSizeLimit ? `${body.slice(0, NETWORK_BODY_MAX_SIZE)}…` : body,
13741
+ warnings: exceedsSizeLimit ? ['INVALID_JSON', 'TEXT_TRUNCATED'] : ['INVALID_JSON'],
13742
+ };
13743
+ }
13744
+ }
13745
+
13746
+ return {
13747
+ body: exceedsSizeLimit ? `${body.slice(0, NETWORK_BODY_MAX_SIZE)}…` : body,
13748
+ warnings: exceedsSizeLimit ? ['TEXT_TRUNCATED'] : [],
13749
+ };
13750
+ }
13751
+
13752
+ function _strIsProbablyJson(str) {
13753
+ const first = str[0];
13754
+ const last = str[str.length - 1];
13755
+
13756
+ // Simple check: If this does not start & end with {} or [], it's not JSON
13757
+ return (first === '[' && last === ']') || (first === '{' && last === '}');
13758
+ }
13759
+
13760
+ /** Match an URL against a list of strings/Regex. */
13761
+ function urlMatches(url, urls) {
13762
+ const fullUrl = getFullUrl(url);
13763
+
13764
+ return stringMatchesSomePattern(fullUrl, urls);
13765
+ }
13766
+
13767
+ /** exported for tests */
13768
+ function getFullUrl(url, baseURI = WINDOW.document.baseURI) {
13769
+ // Short circuit for common cases:
13770
+ if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith(WINDOW.location.origin)) {
13771
+ return url;
13772
+ }
13773
+ const fixedUrl = new URL(url, baseURI);
13774
+
13775
+ // If these do not match, we are not dealing with a relative URL, so just return it
13776
+ if (fixedUrl.origin !== new URL(baseURI).origin) {
13777
+ return url;
13778
+ }
13779
+
13780
+ const fullUrl = fixedUrl.href;
13781
+
13782
+ // Remove trailing slashes, if they don't match the original URL
13783
+ if (!url.endsWith('/') && fullUrl.endsWith('/')) {
13784
+ return fullUrl.slice(0, -1);
13785
+ }
13786
+
13787
+ return fullUrl;
13788
+ }
13789
+
12733
13790
  /**
12734
13791
  * Capture a fetch breadcrumb to a replay.
12735
13792
  * This adds additional data (where approriate).
@@ -12737,7 +13794,9 @@ function _serializeFormData(formData) {
12737
13794
  async function captureFetchBreadcrumbToReplay(
12738
13795
  breadcrumb,
12739
13796
  hint,
12740
- options,
13797
+ options
13798
+
13799
+ ,
12741
13800
  ) {
12742
13801
  try {
12743
13802
  const data = await _prepareFetchData(breadcrumb, hint, options);
@@ -12764,6 +13823,7 @@ function enrichFetchBreadcrumb(
12764
13823
 
12765
13824
  const body = _getFetchRequestArgBody(input);
12766
13825
  const reqSize = getBodySize(body, options.textEncoder);
13826
+
12767
13827
  const resSize = response ? parseContentLengthHeader(response.headers.get('content-length')) : undefined;
12768
13828
 
12769
13829
  if (reqSize !== undefined) {
@@ -12777,97 +13837,109 @@ function enrichFetchBreadcrumb(
12777
13837
  async function _prepareFetchData(
12778
13838
  breadcrumb,
12779
13839
  hint,
12780
- options,
13840
+ options
13841
+
13842
+ ,
12781
13843
  ) {
12782
13844
  const { startTimestamp, endTimestamp } = hint;
12783
13845
 
12784
13846
  const {
12785
13847
  url,
12786
13848
  method,
12787
- status_code: statusCode,
13849
+ status_code: statusCode = 0,
12788
13850
  request_body_size: requestBodySize,
12789
13851
  response_body_size: responseBodySize,
12790
13852
  } = breadcrumb.data;
12791
13853
 
12792
- const request = _getRequestInfo(options, hint.input, requestBodySize);
12793
- const response = await _getResponseInfo(options, hint.response, responseBodySize);
13854
+ const captureDetails = urlMatches(url, options.networkDetailAllowUrls);
13855
+
13856
+ const request = captureDetails
13857
+ ? _getRequestInfo(options, hint.input, requestBodySize)
13858
+ : buildSkippedNetworkRequestOrResponse(requestBodySize);
13859
+ const response = await _getResponseInfo(captureDetails, options, hint.response, responseBodySize);
12794
13860
 
12795
13861
  return {
12796
13862
  startTimestamp,
12797
13863
  endTimestamp,
12798
13864
  url,
12799
13865
  method,
12800
- statusCode: statusCode || 0,
13866
+ statusCode,
12801
13867
  request,
12802
13868
  response,
12803
13869
  };
12804
13870
  }
12805
13871
 
12806
13872
  function _getRequestInfo(
12807
- { captureBodies },
13873
+ { networkCaptureBodies, networkRequestHeaders },
12808
13874
  input,
12809
13875
  requestBodySize,
12810
13876
  ) {
12811
- if (!captureBodies) {
12812
- return buildNetworkRequestOrResponse(requestBodySize, undefined);
13877
+ const headers = getRequestHeaders(input, networkRequestHeaders);
13878
+
13879
+ if (!networkCaptureBodies) {
13880
+ return buildNetworkRequestOrResponse(headers, requestBodySize, undefined);
12813
13881
  }
12814
13882
 
12815
13883
  // We only want to transmit string or string-like bodies
12816
13884
  const requestBody = _getFetchRequestArgBody(input);
12817
- const body = getNetworkBody(getBodyString(requestBody));
12818
- return buildNetworkRequestOrResponse(requestBodySize, body);
13885
+ const bodyStr = getBodyString(requestBody);
13886
+ return buildNetworkRequestOrResponse(headers, requestBodySize, bodyStr);
12819
13887
  }
12820
13888
 
12821
13889
  async function _getResponseInfo(
12822
- { captureBodies, textEncoder },
13890
+ captureDetails,
13891
+ {
13892
+ networkCaptureBodies,
13893
+ textEncoder,
13894
+ networkResponseHeaders,
13895
+ }
13896
+
13897
+ ,
12823
13898
  response,
12824
13899
  responseBodySize,
12825
13900
  ) {
12826
- if (!captureBodies && responseBodySize !== undefined) {
12827
- return buildNetworkRequestOrResponse(responseBodySize, undefined);
13901
+ if (!captureDetails && responseBodySize !== undefined) {
13902
+ return buildSkippedNetworkRequestOrResponse(responseBodySize);
13903
+ }
13904
+
13905
+ const headers = getAllHeaders(response.headers, networkResponseHeaders);
13906
+
13907
+ if (!networkCaptureBodies && responseBodySize !== undefined) {
13908
+ return buildNetworkRequestOrResponse(headers, responseBodySize, undefined);
12828
13909
  }
12829
13910
 
12830
13911
  // Only clone the response if we need to
12831
13912
  try {
12832
13913
  // We have to clone this, as the body can only be read once
12833
13914
  const res = response.clone();
12834
- const { body, bodyText } = await _parseFetchBody(res);
13915
+ const bodyText = await _parseFetchBody(res);
12835
13916
 
12836
13917
  const size =
12837
13918
  bodyText && bodyText.length && responseBodySize === undefined
12838
13919
  ? getBodySize(bodyText, textEncoder)
12839
13920
  : responseBodySize;
12840
13921
 
12841
- if (captureBodies) {
12842
- return buildNetworkRequestOrResponse(size, body);
13922
+ if (!captureDetails) {
13923
+ return buildSkippedNetworkRequestOrResponse(size);
12843
13924
  }
12844
13925
 
12845
- return buildNetworkRequestOrResponse(size, undefined);
13926
+ if (networkCaptureBodies) {
13927
+ return buildNetworkRequestOrResponse(headers, size, bodyText);
13928
+ }
13929
+
13930
+ return buildNetworkRequestOrResponse(headers, size, undefined);
12846
13931
  } catch (e) {
12847
13932
  // fallback
12848
- return buildNetworkRequestOrResponse(responseBodySize, undefined);
13933
+ return buildNetworkRequestOrResponse(headers, responseBodySize, undefined);
12849
13934
  }
12850
13935
  }
12851
13936
 
12852
- async function _parseFetchBody(
12853
- response,
12854
- ) {
12855
- let bodyText;
12856
-
13937
+ async function _parseFetchBody(response) {
12857
13938
  try {
12858
- bodyText = await response.text();
13939
+ return await response.text();
12859
13940
  } catch (e2) {
12860
- return {};
12861
- }
12862
-
12863
- try {
12864
- const body = JSON.parse(bodyText);
12865
- return { body, bodyText };
12866
- } catch (e3) {
12867
- // just send bodyText
13941
+ return undefined;
12868
13942
  }
12869
-
12870
- return { bodyText, body: bodyText };
12871
13943
  }
12872
13944
 
12873
13945
  function _getFetchRequestArgBody(fetchArgs = []) {
@@ -12879,6 +13951,56 @@ function _getFetchRequestArgBody(fetchArgs = []) {
12879
13951
  return (fetchArgs[1] ).body;
12880
13952
  }
12881
13953
 
13954
+ function getAllHeaders(headers, allowedHeaders) {
13955
+ const allHeaders = {};
13956
+
13957
+ allowedHeaders.forEach(header => {
13958
+ if (headers.get(header)) {
13959
+ allHeaders[header] = headers.get(header) ;
13960
+ }
13961
+ });
13962
+
13963
+ return allHeaders;
13964
+ }
13965
+
13966
+ function getRequestHeaders(fetchArgs, allowedHeaders) {
13967
+ if (fetchArgs.length === 1 && typeof fetchArgs[0] !== 'string') {
13968
+ return getHeadersFromOptions(fetchArgs[0] , allowedHeaders);
13969
+ }
13970
+
13971
+ if (fetchArgs.length === 2) {
13972
+ return getHeadersFromOptions(fetchArgs[1] , allowedHeaders);
13973
+ }
13974
+
13975
+ return {};
13976
+ }
13977
+
13978
+ function getHeadersFromOptions(
13979
+ input,
13980
+ allowedHeaders,
13981
+ ) {
13982
+ if (!input) {
13983
+ return {};
13984
+ }
13985
+
13986
+ const headers = input.headers;
13987
+
13988
+ if (!headers) {
13989
+ return {};
13990
+ }
13991
+
13992
+ if (headers instanceof Headers) {
13993
+ return getAllHeaders(headers, allowedHeaders);
13994
+ }
13995
+
13996
+ // We do not support this, as it is not really documented (anymore?)
13997
+ if (Array.isArray(headers)) {
13998
+ return {};
13999
+ }
14000
+
14001
+ return getAllowedHeaders(headers, allowedHeaders);
14002
+ }
14003
+
12882
14004
  /**
12883
14005
  * Capture an XHR breadcrumb to a replay.
12884
14006
  * This adds additional data (where approriate).
@@ -12929,12 +14051,12 @@ function _prepareXhrData(
12929
14051
  hint,
12930
14052
  options,
12931
14053
  ) {
12932
- const { startTimestamp, endTimestamp, input } = hint;
14054
+ const { startTimestamp, endTimestamp, input, xhr } = hint;
12933
14055
 
12934
14056
  const {
12935
14057
  url,
12936
14058
  method,
12937
- status_code: statusCode,
14059
+ status_code: statusCode = 0,
12938
14060
  request_body_size: requestBodySize,
12939
14061
  response_body_size: responseBodySize,
12940
14062
  } = breadcrumb.data;
@@ -12943,13 +14065,35 @@ function _prepareXhrData(
12943
14065
  return null;
12944
14066
  }
12945
14067
 
14068
+ if (!urlMatches(url, options.networkDetailAllowUrls)) {
14069
+ const request = buildSkippedNetworkRequestOrResponse(requestBodySize);
14070
+ const response = buildSkippedNetworkRequestOrResponse(responseBodySize);
14071
+ return {
14072
+ startTimestamp,
14073
+ endTimestamp,
14074
+ url,
14075
+ method,
14076
+ statusCode,
14077
+ request,
14078
+ response,
14079
+ };
14080
+ }
14081
+
14082
+ const xhrInfo = xhr[SENTRY_XHR_DATA_KEY];
14083
+ const networkRequestHeaders = xhrInfo
14084
+ ? getAllowedHeaders(xhrInfo.request_headers, options.networkRequestHeaders)
14085
+ : {};
14086
+ const networkResponseHeaders = getAllowedHeaders(getResponseHeaders(xhr), options.networkResponseHeaders);
14087
+
12946
14088
  const request = buildNetworkRequestOrResponse(
14089
+ networkRequestHeaders,
12947
14090
  requestBodySize,
12948
- options.captureBodies ? getNetworkBody(getBodyString(input)) : undefined,
14091
+ options.networkCaptureBodies ? getBodyString(input) : undefined,
12949
14092
  );
12950
14093
  const response = buildNetworkRequestOrResponse(
14094
+ networkResponseHeaders,
12951
14095
  responseBodySize,
12952
- options.captureBodies ? getNetworkBody(hint.xhr.responseText) : undefined,
14096
+ options.networkCaptureBodies ? hint.xhr.responseText : undefined,
12953
14097
  );
12954
14098
 
12955
14099
  return {
@@ -12957,12 +14101,26 @@ function _prepareXhrData(
12957
14101
  endTimestamp,
12958
14102
  url,
12959
14103
  method,
12960
- statusCode: statusCode || 0,
14104
+ statusCode,
12961
14105
  request,
12962
14106
  response,
12963
14107
  };
12964
14108
  }
12965
14109
 
14110
+ function getResponseHeaders(xhr) {
14111
+ const headers = xhr.getAllResponseHeaders();
14112
+
14113
+ if (!headers) {
14114
+ return {};
14115
+ }
14116
+
14117
+ return headers.split('\r\n').reduce((acc, line) => {
14118
+ const [key, value] = line.split(': ');
14119
+ acc[key.toLowerCase()] = value;
14120
+ return acc;
14121
+ }, {});
14122
+ }
14123
+
12966
14124
  /**
12967
14125
  * This method does two things:
12968
14126
  * - It enriches the regular XHR/fetch breadcrumbs with request/response size data
@@ -12975,10 +14133,16 @@ function handleNetworkBreadcrumbs(replay) {
12975
14133
  try {
12976
14134
  const textEncoder = new TextEncoder();
12977
14135
 
14136
+ const { networkDetailAllowUrls, networkCaptureBodies, networkRequestHeaders, networkResponseHeaders } =
14137
+ replay.getOptions();
14138
+
12978
14139
  const options = {
12979
14140
  replay,
12980
14141
  textEncoder,
12981
- captureBodies: replay.getOptions()._experiments.captureNetworkBodies || false,
14142
+ networkDetailAllowUrls,
14143
+ networkCaptureBodies,
14144
+ networkRequestHeaders,
14145
+ networkResponseHeaders,
12982
14146
  };
12983
14147
 
12984
14148
  if (client && client.on) {
@@ -13086,9 +14250,66 @@ function handleScope(scope) {
13086
14250
  return null;
13087
14251
  }
13088
14252
 
14253
+ if (newBreadcrumb.category === 'console') {
14254
+ return normalizeConsoleBreadcrumb(newBreadcrumb);
14255
+ }
14256
+
13089
14257
  return createBreadcrumb(newBreadcrumb);
13090
14258
  }
13091
14259
 
14260
+ /** exported for tests only */
14261
+ function normalizeConsoleBreadcrumb(breadcrumb) {
14262
+ const args = breadcrumb.data && breadcrumb.data.arguments;
14263
+
14264
+ if (!Array.isArray(args) || args.length === 0) {
14265
+ return createBreadcrumb(breadcrumb);
14266
+ }
14267
+
14268
+ let isTruncated = false;
14269
+
14270
+ // Avoid giant args captures
14271
+ const normalizedArgs = args.map(arg => {
14272
+ if (!arg) {
14273
+ return arg;
14274
+ }
14275
+ if (typeof arg === 'string') {
14276
+ if (arg.length > CONSOLE_ARG_MAX_SIZE) {
14277
+ isTruncated = true;
14278
+ return `${arg.slice(0, CONSOLE_ARG_MAX_SIZE)}…`;
14279
+ }
14280
+
14281
+ return arg;
14282
+ }
14283
+ if (typeof arg === 'object') {
14284
+ try {
14285
+ const normalizedArg = normalize(arg, 7);
14286
+ const stringified = JSON.stringify(normalizedArg);
14287
+ if (stringified.length > CONSOLE_ARG_MAX_SIZE) {
14288
+ const fixedJson = fixJson(stringified.slice(0, CONSOLE_ARG_MAX_SIZE));
14289
+ const json = JSON.parse(fixedJson);
14290
+ // We only set this after JSON.parse() was successfull, so we know we didn't run into `catch`
14291
+ isTruncated = true;
14292
+ return json;
14293
+ }
14294
+ return normalizedArg;
14295
+ } catch (e) {
14296
+ // fall back to default
14297
+ }
14298
+ }
14299
+
14300
+ return arg;
14301
+ });
14302
+
14303
+ return createBreadcrumb({
14304
+ ...breadcrumb,
14305
+ data: {
14306
+ ...breadcrumb.data,
14307
+ arguments: normalizedArgs,
14308
+ ...(isTruncated ? { _meta: { warnings: ['CONSOLE_ARG_TRUNCATED'] } } : {}),
14309
+ },
14310
+ });
14311
+ }
14312
+
13092
14313
  /**
13093
14314
  * Add global listeners that cannot be removed.
13094
14315
  */
@@ -13113,7 +14334,8 @@ function addGlobalListeners(replay) {
13113
14334
  client.on('afterSendEvent', handleAfterSendEvent(replay));
13114
14335
  client.on('createDsc', (dsc) => {
13115
14336
  const replayId = replay.getSessionId();
13116
- if (replayId) {
14337
+ // We do not want to set the DSC when in buffer mode, as that means the replay has not been sent (yet)
14338
+ if (replayId && replay.isEnabled() && replay.recordingMode === 'session') {
13117
14339
  dsc.replay_id = replayId;
13118
14340
  }
13119
14341
  });
@@ -13393,6 +14615,23 @@ function debounce(func, wait, options) {
13393
14615
  return debounced;
13394
14616
  }
13395
14617
 
14618
+ /* eslint-disable @typescript-eslint/naming-convention */
14619
+
14620
+ var EventType; (function (EventType) {
14621
+ const DomContentLoaded = 0; EventType[EventType["DomContentLoaded"] = DomContentLoaded] = "DomContentLoaded";
14622
+ const Load = 1; EventType[EventType["Load"] = Load] = "Load";
14623
+ const FullSnapshot = 2; EventType[EventType["FullSnapshot"] = FullSnapshot] = "FullSnapshot";
14624
+ const IncrementalSnapshot = 3; EventType[EventType["IncrementalSnapshot"] = IncrementalSnapshot] = "IncrementalSnapshot";
14625
+ const Meta = 4; EventType[EventType["Meta"] = Meta] = "Meta";
14626
+ const Custom = 5; EventType[EventType["Custom"] = Custom] = "Custom";
14627
+ const Plugin = 6; EventType[EventType["Plugin"] = Plugin] = "Plugin";
14628
+ })(EventType || (EventType = {}));
14629
+
14630
+ /**
14631
+ * This is a partial copy of rrweb's eventWithTime type which only contains the properties
14632
+ * we specifcally need in the SDK.
14633
+ */
14634
+
13396
14635
  /**
13397
14636
  * Handler for recording events.
13398
14637
  *
@@ -13421,7 +14660,7 @@ function getHandleRecordingEmit(replay) {
13421
14660
  // when an error occurs. Clear any state that happens before this current
13422
14661
  // checkout. This needs to happen before `addEvent()` which updates state
13423
14662
  // dependent on this reset.
13424
- if (replay.recordingMode === 'error' && isCheckout) {
14663
+ if (replay.recordingMode === 'buffer' && isCheckout) {
13425
14664
  replay.setInitialState();
13426
14665
  }
13427
14666
 
@@ -13435,6 +14674,14 @@ function getHandleRecordingEmit(replay) {
13435
14674
  return false;
13436
14675
  }
13437
14676
 
14677
+ // Additionally, create a meta event that will capture certain SDK settings.
14678
+ // In order to handle buffer mode, this needs to either be done when we
14679
+ // receive checkout events or at flush time.
14680
+ //
14681
+ // `isCheckout` is always true, but want to be explicit that it should
14682
+ // only be added for checkouts
14683
+ void addSettingsEvent(replay, isCheckout);
14684
+
13438
14685
  // If there is a previousSessionId after a full snapshot occurs, then
13439
14686
  // the replay session was started due to session expiration. The new session
13440
14687
  // is started before triggering a new checkout and contains the id
@@ -13445,10 +14692,10 @@ function getHandleRecordingEmit(replay) {
13445
14692
  return true;
13446
14693
  }
13447
14694
 
13448
- // See note above re: session start needs to reflect the most recent
13449
- // checkout.
13450
- if (replay.recordingMode === 'error' && replay.session) {
13451
- const { earliestEvent } = replay.getContext();
14695
+ // When in buffer mode, make sure we adjust the session started date to the current earliest event of the buffer
14696
+ // this should usually be the timestamp of the checkout event, but to be safe...
14697
+ if (replay.recordingMode === 'buffer' && replay.session && replay.eventBuffer) {
14698
+ const earliestEvent = replay.eventBuffer.getEarliestTimestamp();
13452
14699
  if (earliestEvent) {
13453
14700
  replay.session.started = earliestEvent;
13454
14701
 
@@ -13472,6 +14719,46 @@ function getHandleRecordingEmit(replay) {
13472
14719
  };
13473
14720
  }
13474
14721
 
14722
+ /**
14723
+ * Exported for tests
14724
+ */
14725
+ function createOptionsEvent(replay) {
14726
+ const options = replay.getOptions();
14727
+ return {
14728
+ type: EventType.Custom,
14729
+ timestamp: Date.now(),
14730
+ data: {
14731
+ tag: 'options',
14732
+ payload: {
14733
+ sessionSampleRate: options.sessionSampleRate,
14734
+ errorSampleRate: options.errorSampleRate,
14735
+ useCompressionOption: options.useCompression,
14736
+ blockAllMedia: options.blockAllMedia,
14737
+ maskAllText: options.maskAllText,
14738
+ maskAllInputs: options.maskAllInputs,
14739
+ useCompression: replay.eventBuffer ? replay.eventBuffer.type === 'worker' : false,
14740
+ networkDetailHasUrls: options.networkDetailAllowUrls.length > 0,
14741
+ networkCaptureBodies: options.networkCaptureBodies,
14742
+ networkRequestHasHeaders: options.networkRequestHeaders.length > 0,
14743
+ networkResponseHasHeaders: options.networkResponseHeaders.length > 0,
14744
+ },
14745
+ },
14746
+ };
14747
+ }
14748
+
14749
+ /**
14750
+ * Add a "meta" event that contains a simplified view on current configuration
14751
+ * options. This should only be included on the first segment of a recording.
14752
+ */
14753
+ function addSettingsEvent(replay, isCheckout) {
14754
+ // Only need to add this event when sending the first segment
14755
+ if (!isCheckout || !replay.session || replay.session.segmentId !== 0) {
14756
+ return Promise.resolve(null);
14757
+ }
14758
+
14759
+ return addEvent(replay, createOptionsEvent(replay), false);
14760
+ }
14761
+
13475
14762
  /**
13476
14763
  * Create a replay envelope ready to be sent.
13477
14764
  * This includes both the replay event, as well as the recording data.
@@ -13583,11 +14870,9 @@ async function sendReplayRequest({
13583
14870
  recordingData,
13584
14871
  replayId,
13585
14872
  segmentId: segment_id,
13586
- includeReplayStartTimestamp,
13587
14873
  eventContext,
13588
14874
  timestamp,
13589
14875
  session,
13590
- options,
13591
14876
  }) {
13592
14877
  const preparedRecordingData = prepareRecordingData({
13593
14878
  recordingData,
@@ -13609,9 +14894,8 @@ async function sendReplayRequest({
13609
14894
  }
13610
14895
 
13611
14896
  const baseEvent = {
13612
- // @ts-ignore private api
13613
14897
  type: REPLAY_EVENT_NAME,
13614
- ...(includeReplayStartTimestamp ? { replay_start_timestamp: initialTimestamp / 1000 } : {}),
14898
+ replay_start_timestamp: initialTimestamp / 1000,
13615
14899
  timestamp: timestamp / 1000,
13616
14900
  error_ids: errorIds,
13617
14901
  trace_ids: traceIds,
@@ -13630,15 +14914,6 @@ async function sendReplayRequest({
13630
14914
  return;
13631
14915
  }
13632
14916
 
13633
- replayEvent.contexts = {
13634
- ...replayEvent.contexts,
13635
- replay: {
13636
- ...(replayEvent.contexts && replayEvent.contexts.replay),
13637
- session_sample_rate: options.sessionSampleRate,
13638
- error_sample_rate: options.errorSampleRate,
13639
- },
13640
- };
13641
-
13642
14917
  /*
13643
14918
  For reference, the fully built event looks something like this:
13644
14919
  {
@@ -13669,10 +14944,6 @@ async function sendReplayRequest({
13669
14944
  },
13670
14945
  "sdkProcessingMetadata": {},
13671
14946
  "contexts": {
13672
- "replay": {
13673
- "session_sample_rate": 1,
13674
- "error_sample_rate": 0,
13675
- },
13676
14947
  },
13677
14948
  }
13678
14949
  */
@@ -13798,9 +15069,11 @@ class ReplayContainer {
13798
15069
  __init2() {this.performanceEvents = [];}
13799
15070
 
13800
15071
  /**
13801
- * Recording can happen in one of two modes:
13802
- * * session: Record the whole session, sending it continuously
13803
- * * error: Always keep the last 60s of recording, and when an error occurs, send it immediately
15072
+ * Recording can happen in one of three modes:
15073
+ * - session: Record the whole session, sending it continuously
15074
+ * - buffer: Always keep the last 60s of recording, requires:
15075
+ * - having replaysOnErrorSampleRate > 0 to capture replay when an error occurs
15076
+ * - or calling `flush()` to send the replay
13804
15077
  */
13805
15078
  __init3() {this.recordingMode = 'session';}
13806
15079
 
@@ -13809,7 +15082,8 @@ class ReplayContainer {
13809
15082
  * @hidden
13810
15083
  */
13811
15084
  __init4() {this.timeouts = {
13812
- sessionIdle: SESSION_IDLE_DURATION,
15085
+ sessionIdlePause: SESSION_IDLE_PAUSE_DURATION,
15086
+ sessionIdleExpire: SESSION_IDLE_EXPIRE_DURATION,
13813
15087
  maxSessionLife: MAX_SESSION_LIFE,
13814
15088
  }; }
13815
15089
 
@@ -13853,7 +15127,6 @@ class ReplayContainer {
13853
15127
  errorIds: new Set(),
13854
15128
  traceIds: new Set(),
13855
15129
  urls: [],
13856
- earliestEvent: null,
13857
15130
  initialTimestamp: Date.now(),
13858
15131
  initialUrl: '',
13859
15132
  };}
@@ -13863,7 +15136,7 @@ class ReplayContainer {
13863
15136
  recordingOptions,
13864
15137
  }
13865
15138
 
13866
- ) {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);
15139
+ ) {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);
13867
15140
  this._recordingOptions = recordingOptions;
13868
15141
  this._options = options;
13869
15142
 
@@ -13893,49 +15166,102 @@ class ReplayContainer {
13893
15166
  }
13894
15167
 
13895
15168
  /**
13896
- * Initializes the plugin.
13897
- *
13898
- * Creates or loads a session, attaches listeners to varying events (DOM,
13899
- * _performanceObserver, Recording, Sentry SDK, etc)
15169
+ * Initializes the plugin based on sampling configuration. Should not be
15170
+ * called outside of constructor.
13900
15171
  */
13901
- start() {
13902
- this.setInitialState();
15172
+ initializeSampling() {
15173
+ const { errorSampleRate, sessionSampleRate } = this._options;
13903
15174
 
13904
- if (!this._loadAndCheckSession()) {
15175
+ // If neither sample rate is > 0, then do nothing - user will need to call one of
15176
+ // `start()` or `startBuffering` themselves.
15177
+ if (errorSampleRate <= 0 && sessionSampleRate <= 0) {
13905
15178
  return;
13906
15179
  }
13907
15180
 
13908
- // If there is no session, then something bad has happened - can't continue
13909
- if (!this.session) {
13910
- this._handleException(new Error('No session found'));
15181
+ // Otherwise if there is _any_ sample rate set, try to load an existing
15182
+ // session, or create a new one.
15183
+ const isSessionSampled = this._loadAndCheckSession();
15184
+
15185
+ if (!isSessionSampled) {
15186
+ // This should only occur if `errorSampleRate` is 0 and was unsampled for
15187
+ // session-based replay. In this case there is nothing to do.
13911
15188
  return;
13912
15189
  }
13913
15190
 
13914
- if (!this.session.sampled) {
13915
- // If session was not sampled, then we do not initialize the integration at all.
15191
+ if (!this.session) {
15192
+ // This should not happen, something wrong has occurred
15193
+ this._handleException(new Error('Unable to initialize and create session'));
13916
15194
  return;
13917
15195
  }
13918
15196
 
13919
- // If session is sampled for errors, then we need to set the recordingMode
13920
- // to 'error', which will configure recording with different options.
13921
- if (this.session.sampled === 'error') {
13922
- this.recordingMode = 'error';
15197
+ if (this.session.sampled && this.session.sampled !== 'session') {
15198
+ // If not sampled as session-based, then recording mode will be `buffer`
15199
+ // Note that we don't explicitly check if `sampled === 'buffer'` because we
15200
+ // could have sessions from Session storage that are still `error` from
15201
+ // prior SDK version.
15202
+ this.recordingMode = 'buffer';
13923
15203
  }
13924
15204
 
13925
- // setup() is generally called on page load or manually - in both cases we
13926
- // should treat it as an activity
13927
- this._updateSessionActivity();
15205
+ this._initializeRecording();
15206
+ }
13928
15207
 
13929
- this.eventBuffer = createEventBuffer({
13930
- useCompression: this._options.useCompression,
15208
+ /**
15209
+ * Start a replay regardless of sampling rate. Calling this will always
15210
+ * create a new session. Will throw an error if replay is already in progress.
15211
+ *
15212
+ * Creates or loads a session, attaches listeners to varying events (DOM,
15213
+ * _performanceObserver, Recording, Sentry SDK, etc)
15214
+ */
15215
+ start() {
15216
+ if (this._isEnabled && this.recordingMode === 'session') {
15217
+ throw new Error('Replay recording is already in progress');
15218
+ }
15219
+
15220
+ if (this._isEnabled && this.recordingMode === 'buffer') {
15221
+ throw new Error('Replay buffering is in progress, call `flush()` to save the replay');
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
+ // This is intentional: create a new session-based replay when calling `start()`
15231
+ sessionSampleRate: 1,
15232
+ allowBuffering: false,
13931
15233
  });
13932
15234
 
13933
- this._addListeners();
15235
+ session.previousSessionId = previousSessionId;
15236
+ this.session = session;
13934
15237
 
13935
- // Need to set as enabled before we start recording, as `record()` can trigger a flush with a new checkout
13936
- this._isEnabled = true;
15238
+ this._initializeRecording();
15239
+ }
13937
15240
 
13938
- this.startRecording();
15241
+ /**
15242
+ * Start replay buffering. Buffers until `flush()` is called or, if
15243
+ * `replaysOnErrorSampleRate` > 0, an error occurs.
15244
+ */
15245
+ startBuffering() {
15246
+ if (this._isEnabled) {
15247
+ throw new Error('Replay recording is already in progress');
15248
+ }
15249
+
15250
+ const previousSessionId = this.session && this.session.id;
15251
+
15252
+ const { session } = getSession({
15253
+ timeouts: this.timeouts,
15254
+ stickySession: Boolean(this._options.stickySession),
15255
+ currentSession: this.session,
15256
+ sessionSampleRate: 0,
15257
+ allowBuffering: true,
15258
+ });
15259
+
15260
+ session.previousSessionId = previousSessionId;
15261
+ this.session = session;
15262
+
15263
+ this.recordingMode = 'buffer';
15264
+ this._initializeRecording();
13939
15265
  }
13940
15266
 
13941
15267
  /**
@@ -13950,7 +15276,7 @@ class ReplayContainer {
13950
15276
  // When running in error sampling mode, we need to overwrite `checkoutEveryNms`
13951
15277
  // Without this, it would record forever, until an error happens, which we don't want
13952
15278
  // instead, we'll always keep the last 60 seconds of replay before an error happened
13953
- ...(this.recordingMode === 'error' && { checkoutEveryNms: ERROR_CHECKOUT_TIME }),
15279
+ ...(this.recordingMode === 'buffer' && { checkoutEveryNms: BUFFER_CHECKOUT_TIME }),
13954
15280
  emit: getHandleRecordingEmit(this),
13955
15281
  onMutation: this._onMutationHandler,
13956
15282
  });
@@ -13961,17 +15287,18 @@ class ReplayContainer {
13961
15287
 
13962
15288
  /**
13963
15289
  * Stops the recording, if it was running.
13964
- * Returns true if it was stopped, else false.
15290
+ *
15291
+ * Returns true if it was previously stopped, or is now stopped,
15292
+ * otherwise false.
13965
15293
  */
13966
15294
  stopRecording() {
13967
15295
  try {
13968
15296
  if (this._stopRecording) {
13969
15297
  this._stopRecording();
13970
15298
  this._stopRecording = undefined;
13971
- return true;
13972
15299
  }
13973
15300
 
13974
- return false;
15301
+ return true;
13975
15302
  } catch (err) {
13976
15303
  this._handleException(err);
13977
15304
  return false;
@@ -13982,7 +15309,7 @@ class ReplayContainer {
13982
15309
  * Currently, this needs to be manually called (e.g. for tests). Sentry SDK
13983
15310
  * does not support a teardown
13984
15311
  */
13985
- stop(reason) {
15312
+ async stop(reason) {
13986
15313
  if (!this._isEnabled) {
13987
15314
  return;
13988
15315
  }
@@ -13998,12 +15325,24 @@ class ReplayContainer {
13998
15325
  log(msg);
13999
15326
  }
14000
15327
 
15328
+ // We can't move `_isEnabled` after awaiting a flush, otherwise we can
15329
+ // enter into an infinite loop when `stop()` is called while flushing.
14001
15330
  this._isEnabled = false;
14002
15331
  this._removeListeners();
14003
15332
  this.stopRecording();
15333
+
15334
+ this._debouncedFlush.cancel();
15335
+ // See comment above re: `_isEnabled`, we "force" a flush, ignoring the
15336
+ // `_isEnabled` state of the plugin since it was disabled above.
15337
+ await this._flush({ force: true });
15338
+
15339
+ // After flush, destroy event buffer
14004
15340
  this.eventBuffer && this.eventBuffer.destroy();
14005
15341
  this.eventBuffer = null;
14006
- this._debouncedFlush.cancel();
15342
+
15343
+ // Clear session from session storage, note this means if a new session
15344
+ // is started after, it will not have `previousSessionId`
15345
+ clearSession(this);
14007
15346
  } catch (err) {
14008
15347
  this._handleException(err);
14009
15348
  }
@@ -14034,6 +15373,45 @@ class ReplayContainer {
14034
15373
  this.startRecording();
14035
15374
  }
14036
15375
 
15376
+ /**
15377
+ * If not in "session" recording mode, flush event buffer which will create a new replay.
15378
+ * Unless `continueRecording` is false, the replay will continue to record and
15379
+ * behave as a "session"-based replay.
15380
+ *
15381
+ * Otherwise, queue up a flush.
15382
+ */
15383
+ async sendBufferedReplayOrFlush({ continueRecording = true } = {}) {
15384
+ if (this.recordingMode === 'session') {
15385
+ return this.flushImmediate();
15386
+ }
15387
+
15388
+ // Allow flush to complete before resuming as a session recording, otherwise
15389
+ // the checkout from `startRecording` may be included in the payload.
15390
+ // Prefer to keep the error replay as a separate (and smaller) segment
15391
+ // than the session replay.
15392
+ await this.flushImmediate();
15393
+
15394
+ const hasStoppedRecording = this.stopRecording();
15395
+
15396
+ if (!continueRecording || !hasStoppedRecording) {
15397
+ return;
15398
+ }
15399
+
15400
+ // Re-start recording, but in "session" recording mode
15401
+
15402
+ // Reset all "capture on error" configuration before
15403
+ // starting a new recording
15404
+ this.recordingMode = 'session';
15405
+
15406
+ // Once this session ends, we do not want to refresh it
15407
+ if (this.session) {
15408
+ this.session.shouldRefresh = false;
15409
+ this._maybeSaveSession();
15410
+ }
15411
+
15412
+ this.startRecording();
15413
+ }
15414
+
14037
15415
  /**
14038
15416
  * We want to batch uploads of replay events. Save events only if
14039
15417
  * `<flushMinDelay>` milliseconds have elapsed since the last event
@@ -14043,12 +15421,12 @@ class ReplayContainer {
14043
15421
  * processing and hand back control to caller.
14044
15422
  */
14045
15423
  addUpdate(cb) {
14046
- // We need to always run `cb` (e.g. in the case of `this.recordingMode == 'error'`)
15424
+ // We need to always run `cb` (e.g. in the case of `this.recordingMode == 'buffer'`)
14047
15425
  const cbResult = cb();
14048
15426
 
14049
15427
  // If this option is turned on then we will only want to call `flush`
14050
15428
  // explicitly
14051
- if (this.recordingMode === 'error') {
15429
+ if (this.recordingMode === 'buffer') {
14052
15430
  return;
14053
15431
  }
14054
15432
 
@@ -14120,12 +15498,12 @@ class ReplayContainer {
14120
15498
  const oldSessionId = this.getSessionId();
14121
15499
 
14122
15500
  // Prevent starting a new session if the last user activity is older than
14123
- // SESSION_IDLE_DURATION. Otherwise non-user activity can trigger a new
15501
+ // SESSION_IDLE_PAUSE_DURATION. Otherwise non-user activity can trigger a new
14124
15502
  // session+recording. This creates noisy replays that do not have much
14125
15503
  // content in them.
14126
15504
  if (
14127
15505
  this._lastActivity &&
14128
- isExpired(this._lastActivity, this.timeouts.sessionIdle) &&
15506
+ isExpired(this._lastActivity, this.timeouts.sessionIdlePause) &&
14129
15507
  this.session &&
14130
15508
  this.session.sampled === 'session'
14131
15509
  ) {
@@ -14175,6 +15553,30 @@ class ReplayContainer {
14175
15553
  this._context.urls.push(url);
14176
15554
  }
14177
15555
 
15556
+ /**
15557
+ * Initialize and start all listeners to varying events (DOM,
15558
+ * Performance Observer, Recording, Sentry SDK, etc)
15559
+ */
15560
+ _initializeRecording() {
15561
+ this.setInitialState();
15562
+
15563
+ // this method is generally called on page load or manually - in both cases
15564
+ // we should treat it as an activity
15565
+ this._updateSessionActivity();
15566
+
15567
+ this.eventBuffer = createEventBuffer({
15568
+ useCompression: this._options.useCompression,
15569
+ });
15570
+
15571
+ this._removeListeners();
15572
+ this._addListeners();
15573
+
15574
+ // Need to set as enabled before we start recording, as `record()` can trigger a flush with a new checkout
15575
+ this._isEnabled = true;
15576
+
15577
+ this.startRecording();
15578
+ }
15579
+
14178
15580
  /** A wrapper to conditionally capture exceptions. */
14179
15581
  _handleException(error) {
14180
15582
  (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error('[Replay]', error);
@@ -14194,7 +15596,7 @@ class ReplayContainer {
14194
15596
  stickySession: Boolean(this._options.stickySession),
14195
15597
  currentSession: this.session,
14196
15598
  sessionSampleRate: this._options.sessionSampleRate,
14197
- errorSampleRate: this._options.errorSampleRate,
15599
+ allowBuffering: this._options.errorSampleRate > 0,
14198
15600
  });
14199
15601
 
14200
15602
  // If session was newly created (i.e. was not loaded from storage), then
@@ -14211,7 +15613,7 @@ class ReplayContainer {
14211
15613
  this.session = session;
14212
15614
 
14213
15615
  if (!this.session.sampled) {
14214
- this.stop('session unsampled');
15616
+ void this.stop('session unsampled');
14215
15617
  return false;
14216
15618
  }
14217
15619
 
@@ -14226,6 +15628,7 @@ class ReplayContainer {
14226
15628
  WINDOW.document.addEventListener('visibilitychange', this._handleVisibilityChange);
14227
15629
  WINDOW.addEventListener('blur', this._handleWindowBlur);
14228
15630
  WINDOW.addEventListener('focus', this._handleWindowFocus);
15631
+ WINDOW.addEventListener('keydown', this._handleKeyboardEvent);
14229
15632
 
14230
15633
  // There is no way to remove these listeners, so ensure they are only added once
14231
15634
  if (!this._hasInitializedCoreListeners) {
@@ -14254,6 +15657,7 @@ class ReplayContainer {
14254
15657
 
14255
15658
  WINDOW.removeEventListener('blur', this._handleWindowBlur);
14256
15659
  WINDOW.removeEventListener('focus', this._handleWindowFocus);
15660
+ WINDOW.removeEventListener('keydown', this._handleKeyboardEvent);
14257
15661
 
14258
15662
  if (this._performanceObserver) {
14259
15663
  this._performanceObserver.disconnect();
@@ -14304,6 +15708,11 @@ class ReplayContainer {
14304
15708
  this._doChangeToForegroundTasks(breadcrumb);
14305
15709
  };}
14306
15710
 
15711
+ /** Ensure page remains active when a key is pressed. */
15712
+ __init16() {this._handleKeyboardEvent = (event) => {
15713
+ handleKeyboardEvent(this, event);
15714
+ };}
15715
+
14307
15716
  /**
14308
15717
  * Tasks to run when we consider a page to be hidden (via blurring and/or visibility)
14309
15718
  */
@@ -14335,7 +15744,7 @@ class ReplayContainer {
14335
15744
  const isSessionActive = this.checkAndHandleExpiredSession();
14336
15745
 
14337
15746
  if (!isSessionActive) {
14338
- // If the user has come back to the page within SESSION_IDLE_DURATION
15747
+ // If the user has come back to the page within SESSION_IDLE_PAUSE_DURATION
14339
15748
  // ms, we will re-use the existing session, otherwise create a new
14340
15749
  // session
14341
15750
  (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Document has become active, but session has expired');
@@ -14383,7 +15792,7 @@ class ReplayContainer {
14383
15792
  _createCustomBreadcrumb(breadcrumb) {
14384
15793
  this.addUpdate(() => {
14385
15794
  void addEvent(this, {
14386
- type: EventType.Custom,
15795
+ type: EventType$1.Custom,
14387
15796
  timestamp: breadcrumb.timestamp || 0,
14388
15797
  data: {
14389
15798
  tag: 'breadcrumb',
@@ -14409,7 +15818,7 @@ class ReplayContainer {
14409
15818
  * Only flush if `this.recordingMode === 'session'`
14410
15819
  */
14411
15820
  _conditionalFlush() {
14412
- if (this.recordingMode === 'error') {
15821
+ if (this.recordingMode === 'buffer') {
14413
15822
  return;
14414
15823
  }
14415
15824
 
@@ -14424,22 +15833,35 @@ class ReplayContainer {
14424
15833
  this._context.errorIds.clear();
14425
15834
  this._context.traceIds.clear();
14426
15835
  this._context.urls = [];
14427
- this._context.earliestEvent = null;
15836
+ }
15837
+
15838
+ /** Update the initial timestamp based on the buffer content. */
15839
+ _updateInitialTimestampFromEventBuffer() {
15840
+ const { session, eventBuffer } = this;
15841
+ if (!session || !eventBuffer) {
15842
+ return;
15843
+ }
15844
+
15845
+ // we only ever update this on the initial segment
15846
+ if (session.segmentId) {
15847
+ return;
15848
+ }
15849
+
15850
+ const earliestEvent = eventBuffer.getEarliestTimestamp();
15851
+ if (earliestEvent && earliestEvent < this._context.initialTimestamp) {
15852
+ this._context.initialTimestamp = earliestEvent;
15853
+ }
14428
15854
  }
14429
15855
 
14430
15856
  /**
14431
15857
  * Return and clear _context
14432
15858
  */
14433
15859
  _popEventContext() {
14434
- if (this._context.earliestEvent && this._context.earliestEvent < this._context.initialTimestamp) {
14435
- this._context.initialTimestamp = this._context.earliestEvent;
14436
- }
14437
-
14438
15860
  const _context = {
14439
15861
  initialTimestamp: this._context.initialTimestamp,
14440
15862
  initialUrl: this._context.initialUrl,
14441
- errorIds: Array.from(this._context.errorIds).filter(Boolean),
14442
- traceIds: Array.from(this._context.traceIds).filter(Boolean),
15863
+ errorIds: Array.from(this._context.errorIds),
15864
+ traceIds: Array.from(this._context.traceIds),
14443
15865
  urls: this._context.urls,
14444
15866
  };
14445
15867
 
@@ -14478,6 +15900,9 @@ class ReplayContainer {
14478
15900
  }
14479
15901
 
14480
15902
  try {
15903
+ // This uses the data from the eventBuffer, so we need to call this before `finish()
15904
+ this._updateInitialTimestampFromEventBuffer();
15905
+
14481
15906
  // Note this empties the event buffer regardless of outcome of sending replay
14482
15907
  const recordingData = await this.eventBuffer.finish();
14483
15908
 
@@ -14493,7 +15918,6 @@ class ReplayContainer {
14493
15918
  replayId,
14494
15919
  recordingData,
14495
15920
  segmentId,
14496
- includeReplayStartTimestamp: segmentId === 0,
14497
15921
  eventContext,
14498
15922
  session: this.session,
14499
15923
  options: this.getOptions(),
@@ -14505,7 +15929,7 @@ class ReplayContainer {
14505
15929
  // This means we retried 3 times and all of them failed,
14506
15930
  // or we ran into a problem we don't want to retry, like rate limiting.
14507
15931
  // In this case, we want to completely stop the replay - otherwise, we may get inconsistent segments
14508
- this.stop('sendReplay');
15932
+ void this.stop('sendReplay');
14509
15933
 
14510
15934
  const client = getCurrentHub().getClient();
14511
15935
 
@@ -14519,8 +15943,12 @@ class ReplayContainer {
14519
15943
  * Flush recording data to Sentry. Creates a lock so that only a single flush
14520
15944
  * can be active at a time. Do not call this directly.
14521
15945
  */
14522
- __init16() {this._flush = async () => {
14523
- if (!this._isEnabled) {
15946
+ __init17() {this._flush = async ({
15947
+ force = false,
15948
+ }
15949
+
15950
+ = {}) => {
15951
+ if (!this._isEnabled && !force) {
14524
15952
  // This can happen if e.g. the replay was stopped because of exceeding the retry limit
14525
15953
  return;
14526
15954
  }
@@ -14570,7 +15998,7 @@ class ReplayContainer {
14570
15998
  }
14571
15999
 
14572
16000
  /** Handler for rrweb.record.onMutation */
14573
- __init17() {this._onMutationHandler = (mutations) => {
16001
+ __init18() {this._onMutationHandler = (mutations) => {
14574
16002
  const count = mutations.length;
14575
16003
 
14576
16004
  const mutationLimit = this._options._experiments.mutationLimit || 0;
@@ -14704,6 +16132,8 @@ function isElectronNodeRenderer() {
14704
16132
  const MEDIA_SELECTORS =
14705
16133
  'img,image,svg,video,object,picture,embed,map,audio,link[rel="icon"],link[rel="apple-touch-icon"]';
14706
16134
 
16135
+ const DEFAULT_NETWORK_HEADERS = ['content-length', 'content-type', 'accept'];
16136
+
14707
16137
  let _initialized = false;
14708
16138
 
14709
16139
  /**
@@ -14744,6 +16174,11 @@ class Replay {
14744
16174
  maskAllInputs = true,
14745
16175
  blockAllMedia = true,
14746
16176
 
16177
+ networkDetailAllowUrls = [],
16178
+ networkCaptureBodies = true,
16179
+ networkRequestHeaders = [],
16180
+ networkResponseHeaders = [],
16181
+
14747
16182
  mask = [],
14748
16183
  unmask = [],
14749
16184
  block = [],
@@ -14802,6 +16237,13 @@ class Replay {
14802
16237
  errorSampleRate,
14803
16238
  useCompression,
14804
16239
  blockAllMedia,
16240
+ maskAllInputs,
16241
+ maskAllText,
16242
+ networkDetailAllowUrls,
16243
+ networkCaptureBodies,
16244
+ networkRequestHeaders: _getMergedNetworkHeaders(networkRequestHeaders),
16245
+ networkResponseHeaders: _getMergedNetworkHeaders(networkResponseHeaders),
16246
+
14805
16247
  _experiments,
14806
16248
  };
14807
16249
 
@@ -14855,14 +16297,7 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`,
14855
16297
  }
14856
16298
 
14857
16299
  /**
14858
- * We previously used to create a transaction in `setupOnce` and it would
14859
- * potentially create a transaction before some native SDK integrations have run
14860
- * and applied their own global event processor. An example is:
14861
- * https://github.com/getsentry/sentry-javascript/blob/b47ceafbdac7f8b99093ce6023726ad4687edc48/packages/browser/src/integrations/useragent.ts
14862
- *
14863
- * So we call `replay.setup` in next event loop as a workaround to wait for other
14864
- * global event processors to finish. This is no longer needed, but keeping it
14865
- * here to avoid any future issues.
16300
+ * Setup and initialize replay container
14866
16301
  */
14867
16302
  setupOnce() {
14868
16303
  if (!isBrowser()) {
@@ -14871,12 +16306,20 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`,
14871
16306
 
14872
16307
  this._setup();
14873
16308
 
14874
- // XXX: See method comments above
14875
- setTimeout(() => this.start());
16309
+ // Once upon a time, we tried to create a transaction in `setupOnce` and it would
16310
+ // potentially create a transaction before some native SDK integrations have run
16311
+ // and applied their own global event processor. An example is:
16312
+ // https://github.com/getsentry/sentry-javascript/blob/b47ceafbdac7f8b99093ce6023726ad4687edc48/packages/browser/src/integrations/useragent.ts
16313
+ //
16314
+ // So we call `this._initialize()` in next event loop as a workaround to wait for other
16315
+ // global event processors to finish. This is no longer needed, but keeping it
16316
+ // here to avoid any future issues.
16317
+ setTimeout(() => this._initialize());
14876
16318
  }
14877
16319
 
14878
16320
  /**
14879
- * Initializes the plugin.
16321
+ * Start a replay regardless of sampling rate. Calling this will always
16322
+ * create a new session. Will throw an error if replay is already in progress.
14880
16323
  *
14881
16324
  * Creates or loads a session, attaches listeners to varying events (DOM,
14882
16325
  * PerformanceObserver, Recording, Sentry SDK, etc)
@@ -14889,27 +16332,64 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`,
14889
16332
  this._replay.start();
14890
16333
  }
14891
16334
 
16335
+ /**
16336
+ * Start replay buffering. Buffers until `flush()` is called or, if
16337
+ * `replaysOnErrorSampleRate` > 0, until an error occurs.
16338
+ */
16339
+ startBuffering() {
16340
+ if (!this._replay) {
16341
+ return;
16342
+ }
16343
+
16344
+ this._replay.startBuffering();
16345
+ }
16346
+
14892
16347
  /**
14893
16348
  * Currently, this needs to be manually called (e.g. for tests). Sentry SDK
14894
16349
  * does not support a teardown
14895
16350
  */
14896
16351
  stop() {
14897
16352
  if (!this._replay) {
14898
- return;
16353
+ return Promise.resolve();
16354
+ }
16355
+
16356
+ return this._replay.stop();
16357
+ }
16358
+
16359
+ /**
16360
+ * If not in "session" recording mode, flush event buffer which will create a new replay.
16361
+ * Unless `continueRecording` is false, the replay will continue to record and
16362
+ * behave as a "session"-based replay.
16363
+ *
16364
+ * Otherwise, queue up a flush.
16365
+ */
16366
+ flush(options) {
16367
+ if (!this._replay || !this._replay.isEnabled()) {
16368
+ return Promise.resolve();
14899
16369
  }
14900
16370
 
14901
- this._replay.stop();
16371
+ return this._replay.sendBufferedReplayOrFlush(options);
14902
16372
  }
14903
16373
 
14904
16374
  /**
14905
- * Immediately send all pending events.
16375
+ * Get the current session ID.
14906
16376
  */
14907
- flush() {
16377
+ getReplayId() {
14908
16378
  if (!this._replay || !this._replay.isEnabled()) {
14909
16379
  return;
14910
16380
  }
14911
16381
 
14912
- return this._replay.flushImmediate();
16382
+ return this._replay.getSessionId();
16383
+ }
16384
+ /**
16385
+ * Initializes replay.
16386
+ */
16387
+ _initialize() {
16388
+ if (!this._replay) {
16389
+ return;
16390
+ }
16391
+
16392
+ this._replay.initializeSampling();
14913
16393
  }
14914
16394
 
14915
16395
  /** Setup the integration. */
@@ -14960,6 +16440,10 @@ function loadReplayOptionsFromClient(initialOptions) {
14960
16440
  return finalOptions;
14961
16441
  }
14962
16442
 
16443
+ function _getMergedNetworkHeaders(headers) {
16444
+ return [...DEFAULT_NETWORK_HEADERS, ...headers.map(header => header.toLowerCase())];
16445
+ }
16446
+
14963
16447
  /**
14964
16448
  * Polyfill for the optional chain operator, `?.`, given previous conversion of the expression into an array of values,
14965
16449
  * descriptors, and functions.
@@ -15397,6 +16881,9 @@ class Postgres {
15397
16881
  const span = _optionalChain([parentSpan, 'optionalAccess', _6 => _6.startChild, 'call', _7 => _7({
15398
16882
  description: typeof config === 'string' ? config : (config ).text,
15399
16883
  op: 'db',
16884
+ data: {
16885
+ 'db.system': 'postgresql',
16886
+ },
15400
16887
  })]);
15401
16888
 
15402
16889
  if (typeof callback === 'function') {
@@ -15473,6 +16960,9 @@ class Mysql {constructor() { Mysql.prototype.__init.call(this); }
15473
16960
  const span = _optionalChain([parentSpan, 'optionalAccess', _4 => _4.startChild, 'call', _5 => _5({
15474
16961
  description: typeof options === 'string' ? options : (options ).sql,
15475
16962
  op: 'db',
16963
+ data: {
16964
+ 'db.system': 'mysql',
16965
+ },
15476
16966
  })]);
15477
16967
 
15478
16968
  if (typeof callback === 'function') {
@@ -15694,6 +17184,7 @@ class Mongo {
15694
17184
  collectionName: collection.collectionName,
15695
17185
  dbName: collection.dbName,
15696
17186
  namespace: collection.namespace,
17187
+ 'db.system': 'mongodb',
15697
17188
  };
15698
17189
  const spanContext = {
15699
17190
  op: 'db',
@@ -15780,31 +17271,15 @@ class Prisma {
15780
17271
  }
15781
17272
 
15782
17273
  this._client.$use((params, next) => {
15783
- const scope = getCurrentHub().getScope();
15784
- const parentSpan = _optionalChain([scope, 'optionalAccess', _2 => _2.getSpan, 'call', _3 => _3()]);
15785
-
15786
17274
  const action = params.action;
15787
17275
  const model = params.model;
15788
-
15789
- const span = _optionalChain([parentSpan, 'optionalAccess', _4 => _4.startChild, 'call', _5 => _5({
15790
- description: model ? `${model} ${action}` : action,
15791
- op: 'db.sql.prisma',
15792
- })]);
15793
-
15794
- const rv = next(params);
15795
-
15796
- if (isThenable(rv)) {
15797
- return rv.then((res) => {
15798
- _optionalChain([span, 'optionalAccess', _6 => _6.finish, 'call', _7 => _7()]);
15799
- return res;
15800
- });
15801
- }
15802
-
15803
- _optionalChain([span, 'optionalAccess', _8 => _8.finish, 'call', _9 => _9()]);
15804
- return rv;
17276
+ return trace(
17277
+ { name: model ? `${model} ${action}` : action, op: 'db.sql.prisma', data: { 'db.system': 'prisma' } },
17278
+ () => next(params),
17279
+ );
15805
17280
  });
15806
17281
  }
15807
- }Prisma.__initStatic();
17282
+ } Prisma.__initStatic();
15808
17283
 
15809
17284
  /** Tracing integration for graphql package */
15810
17285
  class GraphQL {constructor() { GraphQL.prototype.__init.call(this); }
@@ -28978,7 +30453,7 @@ const configGenerator = () => {
28978
30453
  let release;
28979
30454
  try {
28980
30455
  environment !== null && environment !== void 0 ? environment : (environment = "staging");
28981
- release !== null && release !== void 0 ? release : (release = "1.1.23-binary-002");
30456
+ release !== null && release !== void 0 ? release : (release = "1.1.23-binary-004");
28982
30457
  }
28983
30458
  catch (_a) {
28984
30459
  console.error('sentry configGenerator error');
@@ -42045,7 +43520,9 @@ var AsapAction = (function (_super) {
42045
43520
  var actions = scheduler.actions;
42046
43521
  if (id != null && ((_a = actions[actions.length - 1]) === null || _a === void 0 ? void 0 : _a.id) !== id) {
42047
43522
  immediateProvider.clearImmediate(id);
42048
- scheduler._scheduled = undefined;
43523
+ if (scheduler._scheduled === id) {
43524
+ scheduler._scheduled = undefined;
43525
+ }
42049
43526
  }
42050
43527
  return undefined;
42051
43528
  };