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