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