@myinterview/widget-react 1.1.22-development-2bfa0c3 → 1.1.22-development-1379a04
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/esm/index.js
CHANGED
|
@@ -2071,7 +2071,10 @@ function visit(
|
|
|
2071
2071
|
const [memoize, unmemoize] = memo;
|
|
2072
2072
|
|
|
2073
2073
|
// Get the simple cases out of the way first
|
|
2074
|
-
if (
|
|
2074
|
+
if (
|
|
2075
|
+
value == null || // this matches null and undefined -> eqeq not eqeqeq
|
|
2076
|
+
(['number', 'boolean', 'string'].includes(typeof value) && !isNaN$1(value))
|
|
2077
|
+
) {
|
|
2075
2078
|
return value ;
|
|
2076
2079
|
}
|
|
2077
2080
|
|
|
@@ -2209,11 +2212,6 @@ function stringifyValue(
|
|
|
2209
2212
|
return '[NaN]';
|
|
2210
2213
|
}
|
|
2211
2214
|
|
|
2212
|
-
// this catches `undefined` (but not `null`, which is a primitive and can be serialized on its own)
|
|
2213
|
-
if (value === void 0) {
|
|
2214
|
-
return '[undefined]';
|
|
2215
|
-
}
|
|
2216
|
-
|
|
2217
2215
|
if (typeof value === 'function') {
|
|
2218
2216
|
return `[Function: ${getFunctionName(value)}]`;
|
|
2219
2217
|
}
|
|
@@ -5796,7 +5794,7 @@ function getEventForEnvelopeItem(item, type) {
|
|
|
5796
5794
|
return Array.isArray(item) ? (item )[1] : undefined;
|
|
5797
5795
|
}
|
|
5798
5796
|
|
|
5799
|
-
const SDK_VERSION = '7.
|
|
5797
|
+
const SDK_VERSION = '7.52.1';
|
|
5800
5798
|
|
|
5801
5799
|
let originalFunctionToString;
|
|
5802
5800
|
|
|
@@ -5977,8 +5975,9 @@ function _getPossibleEventMessages(event) {
|
|
|
5977
5975
|
return [event.message];
|
|
5978
5976
|
}
|
|
5979
5977
|
if (event.exception) {
|
|
5978
|
+
const { values } = event.exception;
|
|
5980
5979
|
try {
|
|
5981
|
-
const { type = '', value = '' } = (
|
|
5980
|
+
const { type = '', value = '' } = (values && values[values.length - 1]) || {};
|
|
5982
5981
|
return [`${value}`, `${type}: ${value}`];
|
|
5983
5982
|
} catch (oO) {
|
|
5984
5983
|
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error(`Cannot extract message for event ${getEventDescription(event)}`);
|
|
@@ -8335,7 +8334,7 @@ function maskInputValue({ input, maskInputSelector, unmaskInputSelector, maskInp
|
|
|
8335
8334
|
if (unmaskInputSelector && input.matches(unmaskInputSelector)) {
|
|
8336
8335
|
return text;
|
|
8337
8336
|
}
|
|
8338
|
-
if (input.hasAttribute('
|
|
8337
|
+
if (input.hasAttribute('data-rr-is-password')) {
|
|
8339
8338
|
type = 'password';
|
|
8340
8339
|
}
|
|
8341
8340
|
if (isInputTypeMasked({ maskInputOptions, tagName, type }) ||
|
|
@@ -8368,6 +8367,21 @@ function is2DCanvasBlank(canvas) {
|
|
|
8368
8367
|
}
|
|
8369
8368
|
return true;
|
|
8370
8369
|
}
|
|
8370
|
+
function getInputType(element) {
|
|
8371
|
+
const type = element.type;
|
|
8372
|
+
return element.hasAttribute('data-rr-is-password')
|
|
8373
|
+
? 'password'
|
|
8374
|
+
: type
|
|
8375
|
+
? type.toLowerCase()
|
|
8376
|
+
: null;
|
|
8377
|
+
}
|
|
8378
|
+
function getInputValue(el, tagName, type) {
|
|
8379
|
+
typeof type === 'string' ? type.toLowerCase() : '';
|
|
8380
|
+
if (tagName === 'INPUT' && (type === 'radio' || type === 'checkbox')) {
|
|
8381
|
+
return el.getAttribute('value') || '';
|
|
8382
|
+
}
|
|
8383
|
+
return el.value;
|
|
8384
|
+
}
|
|
8371
8385
|
|
|
8372
8386
|
let _id = 1;
|
|
8373
8387
|
const tagNameRegex = new RegExp('[^a-z0-9-_:]');
|
|
@@ -8406,6 +8420,13 @@ function getCssRuleString(rule) {
|
|
|
8406
8420
|
catch (_a) {
|
|
8407
8421
|
}
|
|
8408
8422
|
}
|
|
8423
|
+
return validateStringifiedCssRule(cssStringified);
|
|
8424
|
+
}
|
|
8425
|
+
function validateStringifiedCssRule(cssStringified) {
|
|
8426
|
+
if (cssStringified.indexOf(':') > -1) {
|
|
8427
|
+
const regex = /(\[(?:[\w-]+)[^\\])(:(?:[\w-]+)\])/gm;
|
|
8428
|
+
return cssStringified.replace(regex, '$1\\$2');
|
|
8429
|
+
}
|
|
8409
8430
|
return cssStringified;
|
|
8410
8431
|
}
|
|
8411
8432
|
function isCSSImportRule(rule) {
|
|
@@ -8414,7 +8435,7 @@ function isCSSImportRule(rule) {
|
|
|
8414
8435
|
function stringifyStyleSheet(sheet) {
|
|
8415
8436
|
return sheet.cssRules
|
|
8416
8437
|
? Array.from(sheet.cssRules)
|
|
8417
|
-
.map((rule) => rule.cssText
|
|
8438
|
+
.map((rule) => rule.cssText ? validateStringifiedCssRule(rule.cssText) : '')
|
|
8418
8439
|
.join('')
|
|
8419
8440
|
: '';
|
|
8420
8441
|
}
|
|
@@ -8749,14 +8770,15 @@ function serializeNode(n, options) {
|
|
|
8749
8770
|
tagName === 'select' ||
|
|
8750
8771
|
tagName === 'option') {
|
|
8751
8772
|
const el = n;
|
|
8752
|
-
const
|
|
8773
|
+
const type = getInputType(el);
|
|
8774
|
+
const value = getInputValue(el, tagName.toUpperCase(), type);
|
|
8753
8775
|
const checked = n.checked;
|
|
8754
|
-
if (
|
|
8755
|
-
|
|
8776
|
+
if (type !== 'submit' &&
|
|
8777
|
+
type !== 'button' &&
|
|
8756
8778
|
value) {
|
|
8757
8779
|
attributes.value = maskInputValue({
|
|
8758
8780
|
input: el,
|
|
8759
|
-
type
|
|
8781
|
+
type,
|
|
8760
8782
|
tagName,
|
|
8761
8783
|
value,
|
|
8762
8784
|
maskInputSelector,
|
|
@@ -9229,15 +9251,8 @@ function snapshot(n, options) {
|
|
|
9229
9251
|
function skipAttribute(tagName, attributeName, value) {
|
|
9230
9252
|
return ((tagName === 'video' || tagName === 'audio') && attributeName === 'autoplay');
|
|
9231
9253
|
}
|
|
9232
|
-
function getInputValue(tagName, el, attributes) {
|
|
9233
|
-
if (tagName === 'input' &&
|
|
9234
|
-
(attributes.type === 'radio' || attributes.type === 'checkbox')) {
|
|
9235
|
-
return el.getAttribute('value') || '';
|
|
9236
|
-
}
|
|
9237
|
-
return el.value;
|
|
9238
|
-
}
|
|
9239
9254
|
|
|
9240
|
-
var EventType;
|
|
9255
|
+
var EventType$1;
|
|
9241
9256
|
(function (EventType) {
|
|
9242
9257
|
EventType[EventType["DomContentLoaded"] = 0] = "DomContentLoaded";
|
|
9243
9258
|
EventType[EventType["Load"] = 1] = "Load";
|
|
@@ -9246,7 +9261,7 @@ var EventType;
|
|
|
9246
9261
|
EventType[EventType["Meta"] = 4] = "Meta";
|
|
9247
9262
|
EventType[EventType["Custom"] = 5] = "Custom";
|
|
9248
9263
|
EventType[EventType["Plugin"] = 6] = "Plugin";
|
|
9249
|
-
})(EventType || (EventType = {}));
|
|
9264
|
+
})(EventType$1 || (EventType$1 = {}));
|
|
9250
9265
|
var IncrementalSource;
|
|
9251
9266
|
(function (IncrementalSource) {
|
|
9252
9267
|
IncrementalSource[IncrementalSource["Mutation"] = 0] = "Mutation";
|
|
@@ -9861,9 +9876,9 @@ class MutationBuffer {
|
|
|
9861
9876
|
this.attributes.push(item);
|
|
9862
9877
|
}
|
|
9863
9878
|
if (m.attributeName === 'type' &&
|
|
9864
|
-
|
|
9879
|
+
target.tagName === 'INPUT' &&
|
|
9865
9880
|
(m.oldValue || '').toLowerCase() === 'password') {
|
|
9866
|
-
|
|
9881
|
+
target.setAttribute('data-rr-is-password', 'true');
|
|
9867
9882
|
}
|
|
9868
9883
|
if (m.attributeName === 'style') {
|
|
9869
9884
|
const old = this.doc.createElement('span');
|
|
@@ -10260,27 +10275,25 @@ function initInputObserver({ inputCb, doc, mirror, blockClass, blockSelector, un
|
|
|
10260
10275
|
isBlocked(target, blockClass, blockSelector, unblockSelector)) {
|
|
10261
10276
|
return;
|
|
10262
10277
|
}
|
|
10263
|
-
|
|
10264
|
-
|
|
10265
|
-
|
|
10278
|
+
const el = target;
|
|
10279
|
+
const type = getInputType(el);
|
|
10280
|
+
if (el.classList.contains(ignoreClass) ||
|
|
10281
|
+
(ignoreSelector && el.matches(ignoreSelector))) {
|
|
10266
10282
|
return;
|
|
10267
10283
|
}
|
|
10268
|
-
let text =
|
|
10284
|
+
let text = getInputValue(el, tagName, type);
|
|
10269
10285
|
let isChecked = false;
|
|
10270
|
-
if (target.hasAttribute('rr_is_password')) {
|
|
10271
|
-
type = 'password';
|
|
10272
|
-
}
|
|
10273
10286
|
if (type === 'radio' || type === 'checkbox') {
|
|
10274
10287
|
isChecked = target.checked;
|
|
10275
10288
|
}
|
|
10276
|
-
|
|
10289
|
+
if (hasInputMaskOptions({
|
|
10277
10290
|
maskInputOptions,
|
|
10278
10291
|
maskInputSelector,
|
|
10279
10292
|
tagName,
|
|
10280
10293
|
type,
|
|
10281
10294
|
})) {
|
|
10282
10295
|
text = maskInputValue({
|
|
10283
|
-
input:
|
|
10296
|
+
input: el,
|
|
10284
10297
|
maskInputOptions,
|
|
10285
10298
|
maskInputSelector,
|
|
10286
10299
|
unmaskInputSelector,
|
|
@@ -10297,8 +10310,18 @@ function initInputObserver({ inputCb, doc, mirror, blockClass, blockSelector, un
|
|
|
10297
10310
|
.querySelectorAll(`input[type="radio"][name="${name}"]`)
|
|
10298
10311
|
.forEach((el) => {
|
|
10299
10312
|
if (el !== target) {
|
|
10313
|
+
const text = maskInputValue({
|
|
10314
|
+
input: el,
|
|
10315
|
+
maskInputOptions,
|
|
10316
|
+
maskInputSelector,
|
|
10317
|
+
unmaskInputSelector,
|
|
10318
|
+
tagName,
|
|
10319
|
+
type,
|
|
10320
|
+
value: getInputValue(el, tagName, type),
|
|
10321
|
+
maskInputFn,
|
|
10322
|
+
});
|
|
10300
10323
|
cbWithDedup(el, callbackWrapper(wrapEventWithUserTriggeredFlag)({
|
|
10301
|
-
text
|
|
10324
|
+
text,
|
|
10302
10325
|
isChecked: !isChecked,
|
|
10303
10326
|
userTriggered: false,
|
|
10304
10327
|
}, userTriggeredOnInput));
|
|
@@ -11211,17 +11234,17 @@ function record(options = {}) {
|
|
|
11211
11234
|
wrappedEmit = (e, isCheckout) => {
|
|
11212
11235
|
var _a;
|
|
11213
11236
|
if (((_a = mutationBuffers[0]) === null || _a === void 0 ? void 0 : _a.isFrozen()) &&
|
|
11214
|
-
e.type !== EventType.FullSnapshot &&
|
|
11215
|
-
!(e.type === EventType.IncrementalSnapshot &&
|
|
11237
|
+
e.type !== EventType$1.FullSnapshot &&
|
|
11238
|
+
!(e.type === EventType$1.IncrementalSnapshot &&
|
|
11216
11239
|
e.data.source === IncrementalSource.Mutation)) {
|
|
11217
11240
|
mutationBuffers.forEach((buf) => buf.unfreeze());
|
|
11218
11241
|
}
|
|
11219
11242
|
emit(eventProcessor(e), isCheckout);
|
|
11220
|
-
if (e.type === EventType.FullSnapshot) {
|
|
11243
|
+
if (e.type === EventType$1.FullSnapshot) {
|
|
11221
11244
|
lastFullSnapshotEvent = e;
|
|
11222
11245
|
incrementalSnapshotCount = 0;
|
|
11223
11246
|
}
|
|
11224
|
-
else if (e.type === EventType.IncrementalSnapshot) {
|
|
11247
|
+
else if (e.type === EventType$1.IncrementalSnapshot) {
|
|
11225
11248
|
if (e.data.source === IncrementalSource.Mutation &&
|
|
11226
11249
|
e.data.isAttachIframe) {
|
|
11227
11250
|
return;
|
|
@@ -11237,16 +11260,16 @@ function record(options = {}) {
|
|
|
11237
11260
|
};
|
|
11238
11261
|
const wrappedMutationEmit = (m) => {
|
|
11239
11262
|
wrappedEmit(wrapEvent({
|
|
11240
|
-
type: EventType.IncrementalSnapshot,
|
|
11263
|
+
type: EventType$1.IncrementalSnapshot,
|
|
11241
11264
|
data: Object.assign({ source: IncrementalSource.Mutation }, m),
|
|
11242
11265
|
}));
|
|
11243
11266
|
};
|
|
11244
11267
|
const wrappedScrollEmit = (p) => wrappedEmit(wrapEvent({
|
|
11245
|
-
type: EventType.IncrementalSnapshot,
|
|
11268
|
+
type: EventType$1.IncrementalSnapshot,
|
|
11246
11269
|
data: Object.assign({ source: IncrementalSource.Scroll }, p),
|
|
11247
11270
|
}));
|
|
11248
11271
|
const wrappedCanvasMutationEmit = (p) => wrappedEmit(wrapEvent({
|
|
11249
|
-
type: EventType.IncrementalSnapshot,
|
|
11272
|
+
type: EventType$1.IncrementalSnapshot,
|
|
11250
11273
|
data: Object.assign({ source: IncrementalSource.CanvasMutation }, p),
|
|
11251
11274
|
}));
|
|
11252
11275
|
const iframeManager = new IframeManager({
|
|
@@ -11291,7 +11314,7 @@ function record(options = {}) {
|
|
|
11291
11314
|
takeFullSnapshot = (isCheckout = false) => {
|
|
11292
11315
|
var _a, _b, _c, _d;
|
|
11293
11316
|
wrappedEmit(wrapEvent({
|
|
11294
|
-
type: EventType.Meta,
|
|
11317
|
+
type: EventType$1.Meta,
|
|
11295
11318
|
data: {
|
|
11296
11319
|
href: window.location.href,
|
|
11297
11320
|
width: getWindowWidth(),
|
|
@@ -11334,7 +11357,7 @@ function record(options = {}) {
|
|
|
11334
11357
|
}
|
|
11335
11358
|
mirror.map = idNodeMap;
|
|
11336
11359
|
wrappedEmit(wrapEvent({
|
|
11337
|
-
type: EventType.FullSnapshot,
|
|
11360
|
+
type: EventType$1.FullSnapshot,
|
|
11338
11361
|
data: {
|
|
11339
11362
|
node,
|
|
11340
11363
|
initialOffset: {
|
|
@@ -11359,7 +11382,7 @@ function record(options = {}) {
|
|
|
11359
11382
|
const handlers = [];
|
|
11360
11383
|
handlers.push(on$1('DOMContentLoaded', () => {
|
|
11361
11384
|
wrappedEmit(wrapEvent({
|
|
11362
|
-
type: EventType.DomContentLoaded,
|
|
11385
|
+
type: EventType$1.DomContentLoaded,
|
|
11363
11386
|
data: {},
|
|
11364
11387
|
}));
|
|
11365
11388
|
}));
|
|
@@ -11369,40 +11392,40 @@ function record(options = {}) {
|
|
|
11369
11392
|
onMutation,
|
|
11370
11393
|
mutationCb: wrappedMutationEmit,
|
|
11371
11394
|
mousemoveCb: (positions, source) => wrappedEmit(wrapEvent({
|
|
11372
|
-
type: EventType.IncrementalSnapshot,
|
|
11395
|
+
type: EventType$1.IncrementalSnapshot,
|
|
11373
11396
|
data: {
|
|
11374
11397
|
source,
|
|
11375
11398
|
positions,
|
|
11376
11399
|
},
|
|
11377
11400
|
})),
|
|
11378
11401
|
mouseInteractionCb: (d) => wrappedEmit(wrapEvent({
|
|
11379
|
-
type: EventType.IncrementalSnapshot,
|
|
11402
|
+
type: EventType$1.IncrementalSnapshot,
|
|
11380
11403
|
data: Object.assign({ source: IncrementalSource.MouseInteraction }, d),
|
|
11381
11404
|
})),
|
|
11382
11405
|
scrollCb: wrappedScrollEmit,
|
|
11383
11406
|
viewportResizeCb: (d) => wrappedEmit(wrapEvent({
|
|
11384
|
-
type: EventType.IncrementalSnapshot,
|
|
11407
|
+
type: EventType$1.IncrementalSnapshot,
|
|
11385
11408
|
data: Object.assign({ source: IncrementalSource.ViewportResize }, d),
|
|
11386
11409
|
})),
|
|
11387
11410
|
inputCb: (v) => wrappedEmit(wrapEvent({
|
|
11388
|
-
type: EventType.IncrementalSnapshot,
|
|
11411
|
+
type: EventType$1.IncrementalSnapshot,
|
|
11389
11412
|
data: Object.assign({ source: IncrementalSource.Input }, v),
|
|
11390
11413
|
})),
|
|
11391
11414
|
mediaInteractionCb: (p) => wrappedEmit(wrapEvent({
|
|
11392
|
-
type: EventType.IncrementalSnapshot,
|
|
11415
|
+
type: EventType$1.IncrementalSnapshot,
|
|
11393
11416
|
data: Object.assign({ source: IncrementalSource.MediaInteraction }, p),
|
|
11394
11417
|
})),
|
|
11395
11418
|
styleSheetRuleCb: (r) => wrappedEmit(wrapEvent({
|
|
11396
|
-
type: EventType.IncrementalSnapshot,
|
|
11419
|
+
type: EventType$1.IncrementalSnapshot,
|
|
11397
11420
|
data: Object.assign({ source: IncrementalSource.StyleSheetRule }, r),
|
|
11398
11421
|
})),
|
|
11399
11422
|
styleDeclarationCb: (r) => wrappedEmit(wrapEvent({
|
|
11400
|
-
type: EventType.IncrementalSnapshot,
|
|
11423
|
+
type: EventType$1.IncrementalSnapshot,
|
|
11401
11424
|
data: Object.assign({ source: IncrementalSource.StyleDeclaration }, r),
|
|
11402
11425
|
})),
|
|
11403
11426
|
canvasMutationCb: wrappedCanvasMutationEmit,
|
|
11404
11427
|
fontCb: (p) => wrappedEmit(wrapEvent({
|
|
11405
|
-
type: EventType.IncrementalSnapshot,
|
|
11428
|
+
type: EventType$1.IncrementalSnapshot,
|
|
11406
11429
|
data: Object.assign({ source: IncrementalSource.Font }, p),
|
|
11407
11430
|
})),
|
|
11408
11431
|
blockClass,
|
|
@@ -11435,7 +11458,7 @@ function record(options = {}) {
|
|
|
11435
11458
|
observer: p.observer,
|
|
11436
11459
|
options: p.options,
|
|
11437
11460
|
callback: (payload) => wrappedEmit(wrapEvent({
|
|
11438
|
-
type: EventType.Plugin,
|
|
11461
|
+
type: EventType$1.Plugin,
|
|
11439
11462
|
data: {
|
|
11440
11463
|
plugin: p.name,
|
|
11441
11464
|
payload,
|
|
@@ -11463,7 +11486,7 @@ function record(options = {}) {
|
|
|
11463
11486
|
else {
|
|
11464
11487
|
handlers.push(on$1('load', () => {
|
|
11465
11488
|
wrappedEmit(wrapEvent({
|
|
11466
|
-
type: EventType.Load,
|
|
11489
|
+
type: EventType$1.Load,
|
|
11467
11490
|
data: {},
|
|
11468
11491
|
}));
|
|
11469
11492
|
init();
|
|
@@ -11482,7 +11505,7 @@ record.addCustomEvent = (tag, payload) => {
|
|
|
11482
11505
|
throw new Error('please add custom event after start recording');
|
|
11483
11506
|
}
|
|
11484
11507
|
wrappedEmit(wrapEvent({
|
|
11485
|
-
type: EventType.Custom,
|
|
11508
|
+
type: EventType$1.Custom,
|
|
11486
11509
|
data: {
|
|
11487
11510
|
tag,
|
|
11488
11511
|
payload,
|
|
@@ -11500,6 +11523,475 @@ record.takeFullSnapshot = (isCheckout) => {
|
|
|
11500
11523
|
};
|
|
11501
11524
|
record.mirror = mirror;
|
|
11502
11525
|
|
|
11526
|
+
/**
|
|
11527
|
+
* Create a breadcrumb for a replay.
|
|
11528
|
+
*/
|
|
11529
|
+
function createBreadcrumb(
|
|
11530
|
+
breadcrumb,
|
|
11531
|
+
) {
|
|
11532
|
+
return {
|
|
11533
|
+
timestamp: Date.now() / 1000,
|
|
11534
|
+
type: 'default',
|
|
11535
|
+
...breadcrumb,
|
|
11536
|
+
};
|
|
11537
|
+
}
|
|
11538
|
+
|
|
11539
|
+
var NodeType;
|
|
11540
|
+
(function (NodeType) {
|
|
11541
|
+
NodeType[NodeType["Document"] = 0] = "Document";
|
|
11542
|
+
NodeType[NodeType["DocumentType"] = 1] = "DocumentType";
|
|
11543
|
+
NodeType[NodeType["Element"] = 2] = "Element";
|
|
11544
|
+
NodeType[NodeType["Text"] = 3] = "Text";
|
|
11545
|
+
NodeType[NodeType["CDATA"] = 4] = "CDATA";
|
|
11546
|
+
NodeType[NodeType["Comment"] = 5] = "Comment";
|
|
11547
|
+
})(NodeType || (NodeType = {}));
|
|
11548
|
+
|
|
11549
|
+
/**
|
|
11550
|
+
* Converts a timestamp to ms, if it was in s, or keeps it as ms.
|
|
11551
|
+
*/
|
|
11552
|
+
function timestampToMs(timestamp) {
|
|
11553
|
+
const isMs = timestamp > 9999999999;
|
|
11554
|
+
return isMs ? timestamp : timestamp * 1000;
|
|
11555
|
+
}
|
|
11556
|
+
|
|
11557
|
+
/**
|
|
11558
|
+
* Add an event to the event buffer.
|
|
11559
|
+
* `isCheckout` is true if this is either the very first event, or an event triggered by `checkoutEveryNms`.
|
|
11560
|
+
*/
|
|
11561
|
+
async function addEvent(
|
|
11562
|
+
replay,
|
|
11563
|
+
event,
|
|
11564
|
+
isCheckout,
|
|
11565
|
+
) {
|
|
11566
|
+
if (!replay.eventBuffer) {
|
|
11567
|
+
// This implies that `_isEnabled` is false
|
|
11568
|
+
return null;
|
|
11569
|
+
}
|
|
11570
|
+
|
|
11571
|
+
if (replay.isPaused()) {
|
|
11572
|
+
// Do not add to event buffer when recording is paused
|
|
11573
|
+
return null;
|
|
11574
|
+
}
|
|
11575
|
+
|
|
11576
|
+
const timestampInMs = timestampToMs(event.timestamp);
|
|
11577
|
+
|
|
11578
|
+
// Throw out events that happen more than 5 minutes ago. This can happen if
|
|
11579
|
+
// page has been left open and idle for a long period of time and user
|
|
11580
|
+
// comes back to trigger a new session. The performance entries rely on
|
|
11581
|
+
// `performance.timeOrigin`, which is when the page first opened.
|
|
11582
|
+
if (timestampInMs + replay.timeouts.sessionIdlePause < Date.now()) {
|
|
11583
|
+
return null;
|
|
11584
|
+
}
|
|
11585
|
+
|
|
11586
|
+
try {
|
|
11587
|
+
if (isCheckout) {
|
|
11588
|
+
replay.eventBuffer.clear();
|
|
11589
|
+
}
|
|
11590
|
+
|
|
11591
|
+
return await replay.eventBuffer.addEvent(event);
|
|
11592
|
+
} catch (error) {
|
|
11593
|
+
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error(error);
|
|
11594
|
+
await replay.stop('addEvent');
|
|
11595
|
+
|
|
11596
|
+
const client = getCurrentHub().getClient();
|
|
11597
|
+
|
|
11598
|
+
if (client) {
|
|
11599
|
+
client.recordDroppedEvent('internal_sdk_error', 'replay');
|
|
11600
|
+
}
|
|
11601
|
+
}
|
|
11602
|
+
}
|
|
11603
|
+
|
|
11604
|
+
/**
|
|
11605
|
+
* Add a breadcrumb event to replay.
|
|
11606
|
+
*/
|
|
11607
|
+
function addBreadcrumbEvent(replay, breadcrumb) {
|
|
11608
|
+
if (breadcrumb.category === 'sentry.transaction') {
|
|
11609
|
+
return;
|
|
11610
|
+
}
|
|
11611
|
+
|
|
11612
|
+
if (['ui.click', 'ui.input'].includes(breadcrumb.category )) {
|
|
11613
|
+
replay.triggerUserActivity();
|
|
11614
|
+
} else {
|
|
11615
|
+
replay.checkAndHandleExpiredSession();
|
|
11616
|
+
}
|
|
11617
|
+
|
|
11618
|
+
replay.addUpdate(() => {
|
|
11619
|
+
void addEvent(replay, {
|
|
11620
|
+
type: EventType$1.Custom,
|
|
11621
|
+
// TODO: We were converting from ms to seconds for breadcrumbs, spans,
|
|
11622
|
+
// but maybe we should just keep them as milliseconds
|
|
11623
|
+
timestamp: (breadcrumb.timestamp || 0) * 1000,
|
|
11624
|
+
data: {
|
|
11625
|
+
tag: 'breadcrumb',
|
|
11626
|
+
// normalize to max. 10 depth and 1_000 properties per object
|
|
11627
|
+
payload: normalize(breadcrumb, 10, 1000),
|
|
11628
|
+
},
|
|
11629
|
+
});
|
|
11630
|
+
|
|
11631
|
+
// Do not flush after console log messages
|
|
11632
|
+
return breadcrumb.category === 'console';
|
|
11633
|
+
});
|
|
11634
|
+
}
|
|
11635
|
+
|
|
11636
|
+
/**
|
|
11637
|
+
* Detect a slow click on a button/a tag,
|
|
11638
|
+
* and potentially create a corresponding breadcrumb.
|
|
11639
|
+
*/
|
|
11640
|
+
function detectSlowClick(
|
|
11641
|
+
replay,
|
|
11642
|
+
config,
|
|
11643
|
+
clickBreadcrumb,
|
|
11644
|
+
node,
|
|
11645
|
+
) {
|
|
11646
|
+
if (ignoreElement(node, config)) {
|
|
11647
|
+
return;
|
|
11648
|
+
}
|
|
11649
|
+
|
|
11650
|
+
/*
|
|
11651
|
+
We consider a slow click a click on a button/a, which does not trigger one of:
|
|
11652
|
+
- DOM mutation
|
|
11653
|
+
- Scroll (within 100ms)
|
|
11654
|
+
Within the given threshold time.
|
|
11655
|
+
After time timeout time, we stop listening and mark it as a slow click anyhow.
|
|
11656
|
+
*/
|
|
11657
|
+
|
|
11658
|
+
let cleanup = () => {
|
|
11659
|
+
// replaced further down
|
|
11660
|
+
};
|
|
11661
|
+
|
|
11662
|
+
// After timeout time, def. consider this a slow click, and stop watching for mutations
|
|
11663
|
+
const timeout = setTimeout(() => {
|
|
11664
|
+
handleSlowClick(replay, clickBreadcrumb, config.timeout, 'timeout');
|
|
11665
|
+
cleanup();
|
|
11666
|
+
}, config.timeout);
|
|
11667
|
+
|
|
11668
|
+
const mutationHandler = () => {
|
|
11669
|
+
maybeHandleSlowClick(replay, clickBreadcrumb, config.threshold, config.timeout, 'mutation');
|
|
11670
|
+
cleanup();
|
|
11671
|
+
};
|
|
11672
|
+
|
|
11673
|
+
const scrollHandler = () => {
|
|
11674
|
+
maybeHandleSlowClick(replay, clickBreadcrumb, config.scrollTimeout, config.timeout, 'scroll');
|
|
11675
|
+
cleanup();
|
|
11676
|
+
};
|
|
11677
|
+
|
|
11678
|
+
const obs = new MutationObserver(mutationHandler);
|
|
11679
|
+
|
|
11680
|
+
obs.observe(WINDOW.document.documentElement, {
|
|
11681
|
+
attributes: true,
|
|
11682
|
+
characterData: true,
|
|
11683
|
+
childList: true,
|
|
11684
|
+
subtree: true,
|
|
11685
|
+
});
|
|
11686
|
+
|
|
11687
|
+
WINDOW.addEventListener('scroll', scrollHandler);
|
|
11688
|
+
|
|
11689
|
+
// Stop listening to scroll timeouts early
|
|
11690
|
+
const scrollTimeout = setTimeout(() => {
|
|
11691
|
+
WINDOW.removeEventListener('scroll', scrollHandler);
|
|
11692
|
+
}, config.scrollTimeout);
|
|
11693
|
+
|
|
11694
|
+
cleanup = () => {
|
|
11695
|
+
clearTimeout(timeout);
|
|
11696
|
+
clearTimeout(scrollTimeout);
|
|
11697
|
+
obs.disconnect();
|
|
11698
|
+
WINDOW.removeEventListener('scroll', scrollHandler);
|
|
11699
|
+
};
|
|
11700
|
+
}
|
|
11701
|
+
|
|
11702
|
+
function maybeHandleSlowClick(
|
|
11703
|
+
replay,
|
|
11704
|
+
clickBreadcrumb,
|
|
11705
|
+
threshold,
|
|
11706
|
+
timeout,
|
|
11707
|
+
endReason,
|
|
11708
|
+
) {
|
|
11709
|
+
const now = Date.now();
|
|
11710
|
+
const timeAfterClickMs = now - clickBreadcrumb.timestamp * 1000;
|
|
11711
|
+
|
|
11712
|
+
if (timeAfterClickMs > threshold) {
|
|
11713
|
+
handleSlowClick(replay, clickBreadcrumb, Math.min(timeAfterClickMs, timeout), endReason);
|
|
11714
|
+
return true;
|
|
11715
|
+
}
|
|
11716
|
+
|
|
11717
|
+
return false;
|
|
11718
|
+
}
|
|
11719
|
+
|
|
11720
|
+
function handleSlowClick(
|
|
11721
|
+
replay,
|
|
11722
|
+
clickBreadcrumb,
|
|
11723
|
+
timeAfterClickMs,
|
|
11724
|
+
endReason,
|
|
11725
|
+
) {
|
|
11726
|
+
const breadcrumb = {
|
|
11727
|
+
message: clickBreadcrumb.message,
|
|
11728
|
+
timestamp: clickBreadcrumb.timestamp,
|
|
11729
|
+
category: 'ui.slowClickDetected',
|
|
11730
|
+
data: {
|
|
11731
|
+
...clickBreadcrumb.data,
|
|
11732
|
+
url: WINDOW.location.href,
|
|
11733
|
+
// TODO FN: add parametrized route, when possible
|
|
11734
|
+
timeAfterClickMs,
|
|
11735
|
+
endReason,
|
|
11736
|
+
},
|
|
11737
|
+
};
|
|
11738
|
+
|
|
11739
|
+
addBreadcrumbEvent(replay, breadcrumb);
|
|
11740
|
+
}
|
|
11741
|
+
|
|
11742
|
+
const SLOW_CLICK_IGNORE_TAGS = ['SELECT', 'OPTION'];
|
|
11743
|
+
|
|
11744
|
+
function ignoreElement(node, config) {
|
|
11745
|
+
// If <input> tag, we only want to consider input[type='submit'] & input[type='button']
|
|
11746
|
+
if (node.tagName === 'INPUT' && !['submit', 'button'].includes(node.getAttribute('type') || '')) {
|
|
11747
|
+
return true;
|
|
11748
|
+
}
|
|
11749
|
+
|
|
11750
|
+
if (SLOW_CLICK_IGNORE_TAGS.includes(node.tagName)) {
|
|
11751
|
+
return true;
|
|
11752
|
+
}
|
|
11753
|
+
|
|
11754
|
+
// If <a> tag, detect special variants that may not lead to an action
|
|
11755
|
+
// If target !== _self, we may open the link somewhere else, which would lead to no action
|
|
11756
|
+
// Also, when downloading a file, we may not leave the page, but still not trigger an action
|
|
11757
|
+
if (
|
|
11758
|
+
node.tagName === 'A' &&
|
|
11759
|
+
(node.hasAttribute('download') || (node.hasAttribute('target') && node.getAttribute('target') !== '_self'))
|
|
11760
|
+
) {
|
|
11761
|
+
return true;
|
|
11762
|
+
}
|
|
11763
|
+
|
|
11764
|
+
if (config.ignoreSelector && node.matches(config.ignoreSelector)) {
|
|
11765
|
+
return true;
|
|
11766
|
+
}
|
|
11767
|
+
|
|
11768
|
+
return false;
|
|
11769
|
+
}
|
|
11770
|
+
|
|
11771
|
+
// Note that these are the serialized attributes and not attributes directly on
|
|
11772
|
+
// the DOM Node. Attributes we are interested in:
|
|
11773
|
+
const ATTRIBUTES_TO_RECORD = new Set([
|
|
11774
|
+
'id',
|
|
11775
|
+
'class',
|
|
11776
|
+
'aria-label',
|
|
11777
|
+
'role',
|
|
11778
|
+
'name',
|
|
11779
|
+
'alt',
|
|
11780
|
+
'title',
|
|
11781
|
+
'data-test-id',
|
|
11782
|
+
'data-testid',
|
|
11783
|
+
]);
|
|
11784
|
+
|
|
11785
|
+
/**
|
|
11786
|
+
* Inclusion list of attributes that we want to record from the DOM element
|
|
11787
|
+
*/
|
|
11788
|
+
function getAttributesToRecord(attributes) {
|
|
11789
|
+
const obj = {};
|
|
11790
|
+
for (const key in attributes) {
|
|
11791
|
+
if (ATTRIBUTES_TO_RECORD.has(key)) {
|
|
11792
|
+
let normalizedKey = key;
|
|
11793
|
+
|
|
11794
|
+
if (key === 'data-testid' || key === 'data-test-id') {
|
|
11795
|
+
normalizedKey = 'testId';
|
|
11796
|
+
}
|
|
11797
|
+
|
|
11798
|
+
obj[normalizedKey] = attributes[key];
|
|
11799
|
+
}
|
|
11800
|
+
}
|
|
11801
|
+
|
|
11802
|
+
return obj;
|
|
11803
|
+
}
|
|
11804
|
+
|
|
11805
|
+
const handleDomListener = (
|
|
11806
|
+
replay,
|
|
11807
|
+
) => {
|
|
11808
|
+
const slowClickExperiment = replay.getOptions()._experiments.slowClicks;
|
|
11809
|
+
|
|
11810
|
+
const slowClickConfig = slowClickExperiment
|
|
11811
|
+
? {
|
|
11812
|
+
threshold: slowClickExperiment.threshold,
|
|
11813
|
+
timeout: slowClickExperiment.timeout,
|
|
11814
|
+
scrollTimeout: slowClickExperiment.scrollTimeout,
|
|
11815
|
+
ignoreSelector: slowClickExperiment.ignoreSelectors ? slowClickExperiment.ignoreSelectors.join(',') : '',
|
|
11816
|
+
}
|
|
11817
|
+
: undefined;
|
|
11818
|
+
|
|
11819
|
+
return (handlerData) => {
|
|
11820
|
+
if (!replay.isEnabled()) {
|
|
11821
|
+
return;
|
|
11822
|
+
}
|
|
11823
|
+
|
|
11824
|
+
const result = handleDom(handlerData);
|
|
11825
|
+
|
|
11826
|
+
if (!result) {
|
|
11827
|
+
return;
|
|
11828
|
+
}
|
|
11829
|
+
|
|
11830
|
+
const isClick = handlerData.name === 'click';
|
|
11831
|
+
const event = isClick && (handlerData.event );
|
|
11832
|
+
// Ignore clicks if ctrl/alt/meta keys are held down as they alter behavior of clicks (e.g. open in new tab)
|
|
11833
|
+
if (isClick && slowClickConfig && event && !event.altKey && !event.metaKey && !event.ctrlKey) {
|
|
11834
|
+
detectSlowClick(
|
|
11835
|
+
replay,
|
|
11836
|
+
slowClickConfig,
|
|
11837
|
+
result ,
|
|
11838
|
+
getClickTargetNode(handlerData.event) ,
|
|
11839
|
+
);
|
|
11840
|
+
}
|
|
11841
|
+
|
|
11842
|
+
addBreadcrumbEvent(replay, result);
|
|
11843
|
+
};
|
|
11844
|
+
};
|
|
11845
|
+
|
|
11846
|
+
/** Get the base DOM breadcrumb. */
|
|
11847
|
+
function getBaseDomBreadcrumb(target, message) {
|
|
11848
|
+
// `__sn` property is the serialized node created by rrweb
|
|
11849
|
+
const serializedNode = target && isRrwebNode(target) && target.__sn.type === NodeType.Element ? target.__sn : null;
|
|
11850
|
+
|
|
11851
|
+
return {
|
|
11852
|
+
message,
|
|
11853
|
+
data: serializedNode
|
|
11854
|
+
? {
|
|
11855
|
+
nodeId: serializedNode.id,
|
|
11856
|
+
node: {
|
|
11857
|
+
id: serializedNode.id,
|
|
11858
|
+
tagName: serializedNode.tagName,
|
|
11859
|
+
textContent: target
|
|
11860
|
+
? Array.from(target.childNodes)
|
|
11861
|
+
.map(
|
|
11862
|
+
(node) => '__sn' in node && node.__sn.type === NodeType.Text && node.__sn.textContent,
|
|
11863
|
+
)
|
|
11864
|
+
.filter(Boolean) // filter out empty values
|
|
11865
|
+
.map(text => (text ).trim())
|
|
11866
|
+
.join('')
|
|
11867
|
+
: '',
|
|
11868
|
+
attributes: getAttributesToRecord(serializedNode.attributes),
|
|
11869
|
+
},
|
|
11870
|
+
}
|
|
11871
|
+
: {},
|
|
11872
|
+
};
|
|
11873
|
+
}
|
|
11874
|
+
|
|
11875
|
+
/**
|
|
11876
|
+
* An event handler to react to DOM events.
|
|
11877
|
+
* Exported for tests.
|
|
11878
|
+
*/
|
|
11879
|
+
function handleDom(handlerData) {
|
|
11880
|
+
const { target, message } = getDomTarget(handlerData);
|
|
11881
|
+
|
|
11882
|
+
return createBreadcrumb({
|
|
11883
|
+
category: `ui.${handlerData.name}`,
|
|
11884
|
+
...getBaseDomBreadcrumb(target, message),
|
|
11885
|
+
});
|
|
11886
|
+
}
|
|
11887
|
+
|
|
11888
|
+
function getDomTarget(handlerData) {
|
|
11889
|
+
const isClick = handlerData.name === 'click';
|
|
11890
|
+
|
|
11891
|
+
let message;
|
|
11892
|
+
let target = null;
|
|
11893
|
+
|
|
11894
|
+
// Accessing event.target can throw (see getsentry/raven-js#838, #768)
|
|
11895
|
+
try {
|
|
11896
|
+
target = isClick ? getClickTargetNode(handlerData.event) : getTargetNode(handlerData.event);
|
|
11897
|
+
message = htmlTreeAsString(target, { maxStringLength: 200 }) || '<unknown>';
|
|
11898
|
+
} catch (e) {
|
|
11899
|
+
message = '<unknown>';
|
|
11900
|
+
}
|
|
11901
|
+
|
|
11902
|
+
return { target, message };
|
|
11903
|
+
}
|
|
11904
|
+
|
|
11905
|
+
function isRrwebNode(node) {
|
|
11906
|
+
return '__sn' in node;
|
|
11907
|
+
}
|
|
11908
|
+
|
|
11909
|
+
function getTargetNode(event) {
|
|
11910
|
+
if (isEventWithTarget(event)) {
|
|
11911
|
+
return event.target ;
|
|
11912
|
+
}
|
|
11913
|
+
|
|
11914
|
+
return event;
|
|
11915
|
+
}
|
|
11916
|
+
|
|
11917
|
+
const INTERACTIVE_SELECTOR = 'button,a';
|
|
11918
|
+
|
|
11919
|
+
// For clicks, we check if the target is inside of a button or link
|
|
11920
|
+
// If so, we use this as the target instead
|
|
11921
|
+
// This is useful because if you click on the image in <button><img></button>,
|
|
11922
|
+
// The target will be the image, not the button, which we don't want here
|
|
11923
|
+
function getClickTargetNode(event) {
|
|
11924
|
+
const target = getTargetNode(event);
|
|
11925
|
+
|
|
11926
|
+
if (!target || !(target instanceof Element)) {
|
|
11927
|
+
return target;
|
|
11928
|
+
}
|
|
11929
|
+
|
|
11930
|
+
const closestInteractive = target.closest(INTERACTIVE_SELECTOR);
|
|
11931
|
+
return closestInteractive || target;
|
|
11932
|
+
}
|
|
11933
|
+
|
|
11934
|
+
function isEventWithTarget(event) {
|
|
11935
|
+
return typeof event === 'object' && !!event && 'target' in event;
|
|
11936
|
+
}
|
|
11937
|
+
|
|
11938
|
+
/** Handle keyboard events & create breadcrumbs. */
|
|
11939
|
+
function handleKeyboardEvent(replay, event) {
|
|
11940
|
+
if (!replay.isEnabled()) {
|
|
11941
|
+
return;
|
|
11942
|
+
}
|
|
11943
|
+
|
|
11944
|
+
replay.triggerUserActivity();
|
|
11945
|
+
|
|
11946
|
+
const breadcrumb = getKeyboardBreadcrumb(event);
|
|
11947
|
+
|
|
11948
|
+
if (!breadcrumb) {
|
|
11949
|
+
return;
|
|
11950
|
+
}
|
|
11951
|
+
|
|
11952
|
+
addBreadcrumbEvent(replay, breadcrumb);
|
|
11953
|
+
}
|
|
11954
|
+
|
|
11955
|
+
/** exported only for tests */
|
|
11956
|
+
function getKeyboardBreadcrumb(event) {
|
|
11957
|
+
const { metaKey, shiftKey, ctrlKey, altKey, key, target } = event;
|
|
11958
|
+
|
|
11959
|
+
// never capture for input fields
|
|
11960
|
+
if (!target || isInputElement(target )) {
|
|
11961
|
+
return null;
|
|
11962
|
+
}
|
|
11963
|
+
|
|
11964
|
+
// Note: We do not consider shift here, as that means "uppercase"
|
|
11965
|
+
const hasModifierKey = metaKey || ctrlKey || altKey;
|
|
11966
|
+
const isCharacterKey = key.length === 1; // other keys like Escape, Tab, etc have a longer length
|
|
11967
|
+
|
|
11968
|
+
// Do not capture breadcrumb if only a word key is pressed
|
|
11969
|
+
// This could leak e.g. user input
|
|
11970
|
+
if (!hasModifierKey && isCharacterKey) {
|
|
11971
|
+
return null;
|
|
11972
|
+
}
|
|
11973
|
+
|
|
11974
|
+
const message = htmlTreeAsString(target, { maxStringLength: 200 }) || '<unknown>';
|
|
11975
|
+
const baseBreadcrumb = getBaseDomBreadcrumb(target , message);
|
|
11976
|
+
|
|
11977
|
+
return createBreadcrumb({
|
|
11978
|
+
category: 'ui.keyDown',
|
|
11979
|
+
message,
|
|
11980
|
+
data: {
|
|
11981
|
+
...baseBreadcrumb.data,
|
|
11982
|
+
metaKey,
|
|
11983
|
+
shiftKey,
|
|
11984
|
+
ctrlKey,
|
|
11985
|
+
altKey,
|
|
11986
|
+
key,
|
|
11987
|
+
},
|
|
11988
|
+
});
|
|
11989
|
+
}
|
|
11990
|
+
|
|
11991
|
+
function isInputElement(target) {
|
|
11992
|
+
return target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable;
|
|
11993
|
+
}
|
|
11994
|
+
|
|
11503
11995
|
const NAVIGATION_ENTRY_KEYS = [
|
|
11504
11996
|
'name',
|
|
11505
11997
|
'type',
|
|
@@ -11653,20 +12145,19 @@ class EventBufferArray {
|
|
|
11653
12145
|
return this.events.length > 0;
|
|
11654
12146
|
}
|
|
11655
12147
|
|
|
12148
|
+
/** @inheritdoc */
|
|
12149
|
+
get type() {
|
|
12150
|
+
return 'sync';
|
|
12151
|
+
}
|
|
12152
|
+
|
|
11656
12153
|
/** @inheritdoc */
|
|
11657
12154
|
destroy() {
|
|
11658
12155
|
this.events = [];
|
|
11659
12156
|
}
|
|
11660
12157
|
|
|
11661
12158
|
/** @inheritdoc */
|
|
11662
|
-
async addEvent(event
|
|
11663
|
-
if (isCheckout) {
|
|
11664
|
-
this.events = [event];
|
|
11665
|
-
return;
|
|
11666
|
-
}
|
|
11667
|
-
|
|
12159
|
+
async addEvent(event) {
|
|
11668
12160
|
this.events.push(event);
|
|
11669
|
-
return;
|
|
11670
12161
|
}
|
|
11671
12162
|
|
|
11672
12163
|
/** @inheritdoc */
|
|
@@ -11680,6 +12171,22 @@ class EventBufferArray {
|
|
|
11680
12171
|
resolve(JSON.stringify(eventsRet));
|
|
11681
12172
|
});
|
|
11682
12173
|
}
|
|
12174
|
+
|
|
12175
|
+
/** @inheritdoc */
|
|
12176
|
+
clear() {
|
|
12177
|
+
this.events = [];
|
|
12178
|
+
}
|
|
12179
|
+
|
|
12180
|
+
/** @inheritdoc */
|
|
12181
|
+
getEarliestTimestamp() {
|
|
12182
|
+
const timestamp = this.events.map(event => event.timestamp).sort()[0];
|
|
12183
|
+
|
|
12184
|
+
if (!timestamp) {
|
|
12185
|
+
return null;
|
|
12186
|
+
}
|
|
12187
|
+
|
|
12188
|
+
return timestampToMs(timestamp);
|
|
12189
|
+
}
|
|
11683
12190
|
}
|
|
11684
12191
|
|
|
11685
12192
|
/**
|
|
@@ -11787,11 +12294,20 @@ class WorkerHandler {
|
|
|
11787
12294
|
* Exported only for testing.
|
|
11788
12295
|
*/
|
|
11789
12296
|
class EventBufferCompressionWorker {
|
|
11790
|
-
/** @inheritdoc */
|
|
11791
12297
|
|
|
11792
12298
|
constructor(worker) {
|
|
11793
12299
|
this._worker = new WorkerHandler(worker);
|
|
11794
|
-
this.
|
|
12300
|
+
this._earliestTimestamp = null;
|
|
12301
|
+
}
|
|
12302
|
+
|
|
12303
|
+
/** @inheritdoc */
|
|
12304
|
+
get hasEvents() {
|
|
12305
|
+
return !!this._earliestTimestamp;
|
|
12306
|
+
}
|
|
12307
|
+
|
|
12308
|
+
/** @inheritdoc */
|
|
12309
|
+
get type() {
|
|
12310
|
+
return 'worker';
|
|
11795
12311
|
}
|
|
11796
12312
|
|
|
11797
12313
|
/**
|
|
@@ -11814,13 +12330,10 @@ class EventBufferCompressionWorker {
|
|
|
11814
12330
|
*
|
|
11815
12331
|
* Returns true if event was successfuly received and processed by worker.
|
|
11816
12332
|
*/
|
|
11817
|
-
|
|
11818
|
-
|
|
11819
|
-
|
|
11820
|
-
|
|
11821
|
-
// This event is a checkout, make sure worker buffer is cleared before
|
|
11822
|
-
// proceeding.
|
|
11823
|
-
await this._clear();
|
|
12333
|
+
addEvent(event) {
|
|
12334
|
+
const timestamp = timestampToMs(event.timestamp);
|
|
12335
|
+
if (!this._earliestTimestamp || timestamp < this._earliestTimestamp) {
|
|
12336
|
+
this._earliestTimestamp = timestamp;
|
|
11824
12337
|
}
|
|
11825
12338
|
|
|
11826
12339
|
return this._sendEventToWorker(event);
|
|
@@ -11833,6 +12346,18 @@ class EventBufferCompressionWorker {
|
|
|
11833
12346
|
return this._finishRequest();
|
|
11834
12347
|
}
|
|
11835
12348
|
|
|
12349
|
+
/** @inheritdoc */
|
|
12350
|
+
clear() {
|
|
12351
|
+
this._earliestTimestamp = null;
|
|
12352
|
+
// We do not wait on this, as we assume the order of messages is consistent for the worker
|
|
12353
|
+
void this._worker.postMessage('clear');
|
|
12354
|
+
}
|
|
12355
|
+
|
|
12356
|
+
/** @inheritdoc */
|
|
12357
|
+
getEarliestTimestamp() {
|
|
12358
|
+
return this._earliestTimestamp;
|
|
12359
|
+
}
|
|
12360
|
+
|
|
11836
12361
|
/**
|
|
11837
12362
|
* Send the event to the worker.
|
|
11838
12363
|
*/
|
|
@@ -11846,15 +12371,10 @@ class EventBufferCompressionWorker {
|
|
|
11846
12371
|
async _finishRequest() {
|
|
11847
12372
|
const response = await this._worker.postMessage('finish');
|
|
11848
12373
|
|
|
11849
|
-
this.
|
|
12374
|
+
this._earliestTimestamp = null;
|
|
11850
12375
|
|
|
11851
12376
|
return response;
|
|
11852
12377
|
}
|
|
11853
|
-
|
|
11854
|
-
/** Clear any pending events from the worker. */
|
|
11855
|
-
_clear() {
|
|
11856
|
-
return this._worker.postMessage('clear');
|
|
11857
|
-
}
|
|
11858
12378
|
}
|
|
11859
12379
|
|
|
11860
12380
|
/**
|
|
@@ -11872,6 +12392,11 @@ class EventBufferProxy {
|
|
|
11872
12392
|
this._ensureWorkerIsLoadedPromise = this._ensureWorkerIsLoaded();
|
|
11873
12393
|
}
|
|
11874
12394
|
|
|
12395
|
+
/** @inheritdoc */
|
|
12396
|
+
get type() {
|
|
12397
|
+
return this._used.type;
|
|
12398
|
+
}
|
|
12399
|
+
|
|
11875
12400
|
/** @inheritDoc */
|
|
11876
12401
|
get hasEvents() {
|
|
11877
12402
|
return this._used.hasEvents;
|
|
@@ -11883,13 +12408,23 @@ class EventBufferProxy {
|
|
|
11883
12408
|
this._compression.destroy();
|
|
11884
12409
|
}
|
|
11885
12410
|
|
|
12411
|
+
/** @inheritdoc */
|
|
12412
|
+
clear() {
|
|
12413
|
+
return this._used.clear();
|
|
12414
|
+
}
|
|
12415
|
+
|
|
12416
|
+
/** @inheritdoc */
|
|
12417
|
+
getEarliestTimestamp() {
|
|
12418
|
+
return this._used.getEarliestTimestamp();
|
|
12419
|
+
}
|
|
12420
|
+
|
|
11886
12421
|
/**
|
|
11887
12422
|
* Add an event to the event buffer.
|
|
11888
12423
|
*
|
|
11889
12424
|
* Returns true if event was successfully added.
|
|
11890
12425
|
*/
|
|
11891
|
-
addEvent(event
|
|
11892
|
-
return this._used.addEvent(event
|
|
12426
|
+
addEvent(event) {
|
|
12427
|
+
return this._used.addEvent(event);
|
|
11893
12428
|
}
|
|
11894
12429
|
|
|
11895
12430
|
/** @inheritDoc */
|
|
@@ -12171,59 +12706,6 @@ function getSession({
|
|
|
12171
12706
|
return { type: 'new', session: newSession };
|
|
12172
12707
|
}
|
|
12173
12708
|
|
|
12174
|
-
/**
|
|
12175
|
-
* Add an event to the event buffer.
|
|
12176
|
-
* `isCheckout` is true if this is either the very first event, or an event triggered by `checkoutEveryNms`.
|
|
12177
|
-
*/
|
|
12178
|
-
async function addEvent(
|
|
12179
|
-
replay,
|
|
12180
|
-
event,
|
|
12181
|
-
isCheckout,
|
|
12182
|
-
) {
|
|
12183
|
-
if (!replay.eventBuffer) {
|
|
12184
|
-
// This implies that `_isEnabled` is false
|
|
12185
|
-
return null;
|
|
12186
|
-
}
|
|
12187
|
-
|
|
12188
|
-
if (replay.isPaused()) {
|
|
12189
|
-
// Do not add to event buffer when recording is paused
|
|
12190
|
-
return null;
|
|
12191
|
-
}
|
|
12192
|
-
|
|
12193
|
-
// TODO: sadness -- we will want to normalize timestamps to be in ms -
|
|
12194
|
-
// requires coordination with frontend
|
|
12195
|
-
const isMs = event.timestamp > 9999999999;
|
|
12196
|
-
const timestampInMs = isMs ? event.timestamp : event.timestamp * 1000;
|
|
12197
|
-
|
|
12198
|
-
// Throw out events that happen more than 5 minutes ago. This can happen if
|
|
12199
|
-
// page has been left open and idle for a long period of time and user
|
|
12200
|
-
// comes back to trigger a new session. The performance entries rely on
|
|
12201
|
-
// `performance.timeOrigin`, which is when the page first opened.
|
|
12202
|
-
if (timestampInMs + replay.timeouts.sessionIdlePause < Date.now()) {
|
|
12203
|
-
return null;
|
|
12204
|
-
}
|
|
12205
|
-
|
|
12206
|
-
// Only record earliest event if a new session was created, otherwise it
|
|
12207
|
-
// shouldn't be relevant
|
|
12208
|
-
const earliestEvent = replay.getContext().earliestEvent;
|
|
12209
|
-
if (replay.session && replay.session.segmentId === 0 && (!earliestEvent || timestampInMs < earliestEvent)) {
|
|
12210
|
-
replay.getContext().earliestEvent = timestampInMs;
|
|
12211
|
-
}
|
|
12212
|
-
|
|
12213
|
-
try {
|
|
12214
|
-
return await replay.eventBuffer.addEvent(event, isCheckout);
|
|
12215
|
-
} catch (error) {
|
|
12216
|
-
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error(error);
|
|
12217
|
-
await replay.stop('addEvent');
|
|
12218
|
-
|
|
12219
|
-
const client = getCurrentHub().getClient();
|
|
12220
|
-
|
|
12221
|
-
if (client) {
|
|
12222
|
-
client.recordDroppedEvent('internal_sdk_error', 'replay');
|
|
12223
|
-
}
|
|
12224
|
-
}
|
|
12225
|
-
}
|
|
12226
|
-
|
|
12227
12709
|
/** If the event is an error event */
|
|
12228
12710
|
function isErrorEvent(event) {
|
|
12229
12711
|
return !event.type;
|
|
@@ -12273,22 +12755,18 @@ function handleAfterSendEvent(replay) {
|
|
|
12273
12755
|
return;
|
|
12274
12756
|
}
|
|
12275
12757
|
|
|
12276
|
-
// Add error to list of errorIds of replay
|
|
12758
|
+
// Add error to list of errorIds of replay. This is ok to do even if not
|
|
12759
|
+
// sampled because context will get reset at next checkout.
|
|
12760
|
+
// XXX: There is also a race condition where it's possible to capture an
|
|
12761
|
+
// error to Sentry before Replay SDK has loaded, but response returns after
|
|
12762
|
+
// it was loaded, and this gets called.
|
|
12277
12763
|
if (event.event_id) {
|
|
12278
12764
|
replay.getContext().errorIds.add(event.event_id);
|
|
12279
12765
|
}
|
|
12280
12766
|
|
|
12281
|
-
//
|
|
12767
|
+
// If error event is tagged with replay id it means it was sampled (when in buffer mode)
|
|
12282
12768
|
// Need to be very careful that this does not cause an infinite loop
|
|
12283
|
-
if (
|
|
12284
|
-
replay.recordingMode === 'buffer' &&
|
|
12285
|
-
event.exception &&
|
|
12286
|
-
event.message !== UNABLE_TO_SEND_REPLAY // ignore this error because otherwise we could loop indefinitely with trying to capture replay and failing
|
|
12287
|
-
) {
|
|
12288
|
-
if (!isSampled(replay.getOptions().errorSampleRate)) {
|
|
12289
|
-
return;
|
|
12290
|
-
}
|
|
12291
|
-
|
|
12769
|
+
if (replay.recordingMode === 'buffer' && event.tags && event.tags.replayId) {
|
|
12292
12770
|
setTimeout(() => {
|
|
12293
12771
|
// Capture current event buffer as new replay
|
|
12294
12772
|
void replay.sendBufferedReplayOrFlush();
|
|
@@ -12313,167 +12791,6 @@ function isBaseTransportSend() {
|
|
|
12313
12791
|
);
|
|
12314
12792
|
}
|
|
12315
12793
|
|
|
12316
|
-
var NodeType;
|
|
12317
|
-
(function (NodeType) {
|
|
12318
|
-
NodeType[NodeType["Document"] = 0] = "Document";
|
|
12319
|
-
NodeType[NodeType["DocumentType"] = 1] = "DocumentType";
|
|
12320
|
-
NodeType[NodeType["Element"] = 2] = "Element";
|
|
12321
|
-
NodeType[NodeType["Text"] = 3] = "Text";
|
|
12322
|
-
NodeType[NodeType["CDATA"] = 4] = "CDATA";
|
|
12323
|
-
NodeType[NodeType["Comment"] = 5] = "Comment";
|
|
12324
|
-
})(NodeType || (NodeType = {}));
|
|
12325
|
-
|
|
12326
|
-
/**
|
|
12327
|
-
* Create a breadcrumb for a replay.
|
|
12328
|
-
*/
|
|
12329
|
-
function createBreadcrumb(
|
|
12330
|
-
breadcrumb,
|
|
12331
|
-
) {
|
|
12332
|
-
return {
|
|
12333
|
-
timestamp: Date.now() / 1000,
|
|
12334
|
-
type: 'default',
|
|
12335
|
-
...breadcrumb,
|
|
12336
|
-
};
|
|
12337
|
-
}
|
|
12338
|
-
|
|
12339
|
-
/**
|
|
12340
|
-
* Add a breadcrumb event to replay.
|
|
12341
|
-
*/
|
|
12342
|
-
function addBreadcrumbEvent(replay, breadcrumb) {
|
|
12343
|
-
if (breadcrumb.category === 'sentry.transaction') {
|
|
12344
|
-
return;
|
|
12345
|
-
}
|
|
12346
|
-
|
|
12347
|
-
if (['ui.click', 'ui.input'].includes(breadcrumb.category )) {
|
|
12348
|
-
replay.triggerUserActivity();
|
|
12349
|
-
} else {
|
|
12350
|
-
replay.checkAndHandleExpiredSession();
|
|
12351
|
-
}
|
|
12352
|
-
|
|
12353
|
-
replay.addUpdate(() => {
|
|
12354
|
-
void addEvent(replay, {
|
|
12355
|
-
type: EventType.Custom,
|
|
12356
|
-
// TODO: We were converting from ms to seconds for breadcrumbs, spans,
|
|
12357
|
-
// but maybe we should just keep them as milliseconds
|
|
12358
|
-
timestamp: (breadcrumb.timestamp || 0) * 1000,
|
|
12359
|
-
data: {
|
|
12360
|
-
tag: 'breadcrumb',
|
|
12361
|
-
// normalize to max. 10 depth and 1_000 properties per object
|
|
12362
|
-
payload: normalize(breadcrumb, 10, 1000),
|
|
12363
|
-
},
|
|
12364
|
-
});
|
|
12365
|
-
|
|
12366
|
-
// Do not flush after console log messages
|
|
12367
|
-
return breadcrumb.category === 'console';
|
|
12368
|
-
});
|
|
12369
|
-
}
|
|
12370
|
-
|
|
12371
|
-
// Note that these are the serialized attributes and not attributes directly on
|
|
12372
|
-
// the DOM Node. Attributes we are interested in:
|
|
12373
|
-
const ATTRIBUTES_TO_RECORD = new Set([
|
|
12374
|
-
'id',
|
|
12375
|
-
'class',
|
|
12376
|
-
'aria-label',
|
|
12377
|
-
'role',
|
|
12378
|
-
'name',
|
|
12379
|
-
'alt',
|
|
12380
|
-
'title',
|
|
12381
|
-
'data-test-id',
|
|
12382
|
-
'data-testid',
|
|
12383
|
-
]);
|
|
12384
|
-
|
|
12385
|
-
/**
|
|
12386
|
-
* Inclusion list of attributes that we want to record from the DOM element
|
|
12387
|
-
*/
|
|
12388
|
-
function getAttributesToRecord(attributes) {
|
|
12389
|
-
const obj = {};
|
|
12390
|
-
for (const key in attributes) {
|
|
12391
|
-
if (ATTRIBUTES_TO_RECORD.has(key)) {
|
|
12392
|
-
let normalizedKey = key;
|
|
12393
|
-
|
|
12394
|
-
if (key === 'data-testid' || key === 'data-test-id') {
|
|
12395
|
-
normalizedKey = 'testId';
|
|
12396
|
-
}
|
|
12397
|
-
|
|
12398
|
-
obj[normalizedKey] = attributes[key];
|
|
12399
|
-
}
|
|
12400
|
-
}
|
|
12401
|
-
|
|
12402
|
-
return obj;
|
|
12403
|
-
}
|
|
12404
|
-
|
|
12405
|
-
const handleDomListener =
|
|
12406
|
-
(replay) =>
|
|
12407
|
-
(handlerData) => {
|
|
12408
|
-
if (!replay.isEnabled()) {
|
|
12409
|
-
return;
|
|
12410
|
-
}
|
|
12411
|
-
|
|
12412
|
-
const result = handleDom(handlerData);
|
|
12413
|
-
|
|
12414
|
-
if (!result) {
|
|
12415
|
-
return;
|
|
12416
|
-
}
|
|
12417
|
-
|
|
12418
|
-
addBreadcrumbEvent(replay, result);
|
|
12419
|
-
};
|
|
12420
|
-
|
|
12421
|
-
/**
|
|
12422
|
-
* An event handler to react to DOM events.
|
|
12423
|
-
*/
|
|
12424
|
-
function handleDom(handlerData) {
|
|
12425
|
-
let target;
|
|
12426
|
-
let targetNode;
|
|
12427
|
-
|
|
12428
|
-
// Accessing event.target can throw (see getsentry/raven-js#838, #768)
|
|
12429
|
-
try {
|
|
12430
|
-
targetNode = getTargetNode(handlerData);
|
|
12431
|
-
target = htmlTreeAsString(targetNode);
|
|
12432
|
-
} catch (e) {
|
|
12433
|
-
target = '<unknown>';
|
|
12434
|
-
}
|
|
12435
|
-
|
|
12436
|
-
// `__sn` property is the serialized node created by rrweb
|
|
12437
|
-
const serializedNode =
|
|
12438
|
-
targetNode && '__sn' in targetNode && targetNode.__sn.type === NodeType.Element ? targetNode.__sn : null;
|
|
12439
|
-
|
|
12440
|
-
return createBreadcrumb({
|
|
12441
|
-
category: `ui.${handlerData.name}`,
|
|
12442
|
-
message: target,
|
|
12443
|
-
data: serializedNode
|
|
12444
|
-
? {
|
|
12445
|
-
nodeId: serializedNode.id,
|
|
12446
|
-
node: {
|
|
12447
|
-
id: serializedNode.id,
|
|
12448
|
-
tagName: serializedNode.tagName,
|
|
12449
|
-
textContent: targetNode
|
|
12450
|
-
? Array.from(targetNode.childNodes)
|
|
12451
|
-
.map(
|
|
12452
|
-
(node) => '__sn' in node && node.__sn.type === NodeType.Text && node.__sn.textContent,
|
|
12453
|
-
)
|
|
12454
|
-
.filter(Boolean) // filter out empty values
|
|
12455
|
-
.map(text => (text ).trim())
|
|
12456
|
-
.join('')
|
|
12457
|
-
: '',
|
|
12458
|
-
attributes: getAttributesToRecord(serializedNode.attributes),
|
|
12459
|
-
},
|
|
12460
|
-
}
|
|
12461
|
-
: {},
|
|
12462
|
-
});
|
|
12463
|
-
}
|
|
12464
|
-
|
|
12465
|
-
function getTargetNode(handlerData) {
|
|
12466
|
-
if (isEventWithTarget(handlerData.event)) {
|
|
12467
|
-
return handlerData.event.target;
|
|
12468
|
-
}
|
|
12469
|
-
|
|
12470
|
-
return handlerData.event;
|
|
12471
|
-
}
|
|
12472
|
-
|
|
12473
|
-
function isEventWithTarget(event) {
|
|
12474
|
-
return !!(event ).target;
|
|
12475
|
-
}
|
|
12476
|
-
|
|
12477
12794
|
/**
|
|
12478
12795
|
* Returns true if we think the given event is an error originating inside of rrweb.
|
|
12479
12796
|
*/
|
|
@@ -12497,6 +12814,30 @@ function isRrwebError(event, hint) {
|
|
|
12497
12814
|
});
|
|
12498
12815
|
}
|
|
12499
12816
|
|
|
12817
|
+
/**
|
|
12818
|
+
* Determine if event should be sampled (only applies in buffer mode).
|
|
12819
|
+
* When an event is captured by `hanldleGlobalEvent`, when in buffer mode
|
|
12820
|
+
* we determine if we want to sample the error or not.
|
|
12821
|
+
*/
|
|
12822
|
+
function shouldSampleForBufferEvent(replay, event) {
|
|
12823
|
+
if (replay.recordingMode !== 'buffer') {
|
|
12824
|
+
return false;
|
|
12825
|
+
}
|
|
12826
|
+
|
|
12827
|
+
// ignore this error because otherwise we could loop indefinitely with
|
|
12828
|
+
// trying to capture replay and failing
|
|
12829
|
+
if (event.message === UNABLE_TO_SEND_REPLAY) {
|
|
12830
|
+
return false;
|
|
12831
|
+
}
|
|
12832
|
+
|
|
12833
|
+
// Require the event to be an error event & to have an exception
|
|
12834
|
+
if (!event.exception || event.type) {
|
|
12835
|
+
return false;
|
|
12836
|
+
}
|
|
12837
|
+
|
|
12838
|
+
return isSampled(replay.getOptions().errorSampleRate);
|
|
12839
|
+
}
|
|
12840
|
+
|
|
12500
12841
|
/**
|
|
12501
12842
|
* Returns a listener to be added to `addGlobalEventProcessor(listener)`.
|
|
12502
12843
|
*/
|
|
@@ -12526,8 +12867,16 @@ function handleGlobalEventListener(
|
|
|
12526
12867
|
return null;
|
|
12527
12868
|
}
|
|
12528
12869
|
|
|
12529
|
-
//
|
|
12530
|
-
if
|
|
12870
|
+
// When in buffer mode, we decide to sample here.
|
|
12871
|
+
// Later, in `handleAfterSendEvent`, if the replayId is set, we know that we sampled
|
|
12872
|
+
// And convert the buffer session to a full session
|
|
12873
|
+
const isErrorEventSampled = shouldSampleForBufferEvent(replay, event);
|
|
12874
|
+
|
|
12875
|
+
// Tag errors if it has been sampled in buffer mode, or if it is session mode
|
|
12876
|
+
// Only tag transactions if in session mode
|
|
12877
|
+
const shouldTagReplayId = isErrorEventSampled || replay.recordingMode === 'session';
|
|
12878
|
+
|
|
12879
|
+
if (shouldTagReplayId) {
|
|
12531
12880
|
event.tags = { ...event.tags, replayId: replay.getSessionId() };
|
|
12532
12881
|
}
|
|
12533
12882
|
|
|
@@ -12577,7 +12926,7 @@ function createPerformanceSpans(
|
|
|
12577
12926
|
) {
|
|
12578
12927
|
return entries.map(({ type, start, end, name, data }) =>
|
|
12579
12928
|
addEvent(replay, {
|
|
12580
|
-
type: EventType.Custom,
|
|
12929
|
+
type: EventType$1.Custom,
|
|
12581
12930
|
timestamp: start,
|
|
12582
12931
|
data: {
|
|
12583
12932
|
tag: 'performanceSpan',
|
|
@@ -13359,7 +13708,32 @@ function _strIsProbablyJson(str) {
|
|
|
13359
13708
|
|
|
13360
13709
|
/** Match an URL against a list of strings/Regex. */
|
|
13361
13710
|
function urlMatches(url, urls) {
|
|
13362
|
-
|
|
13711
|
+
const fullUrl = getFullUrl(url);
|
|
13712
|
+
|
|
13713
|
+
return stringMatchesSomePattern(fullUrl, urls);
|
|
13714
|
+
}
|
|
13715
|
+
|
|
13716
|
+
/** exported for tests */
|
|
13717
|
+
function getFullUrl(url, baseURI = WINDOW.document.baseURI) {
|
|
13718
|
+
// Short circuit for common cases:
|
|
13719
|
+
if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith(WINDOW.location.origin)) {
|
|
13720
|
+
return url;
|
|
13721
|
+
}
|
|
13722
|
+
const fixedUrl = new URL(url, baseURI);
|
|
13723
|
+
|
|
13724
|
+
// If these do not match, we are not dealing with a relative URL, so just return it
|
|
13725
|
+
if (fixedUrl.origin !== new URL(baseURI).origin) {
|
|
13726
|
+
return url;
|
|
13727
|
+
}
|
|
13728
|
+
|
|
13729
|
+
const fullUrl = fixedUrl.href;
|
|
13730
|
+
|
|
13731
|
+
// Remove trailing slashes, if they don't match the original URL
|
|
13732
|
+
if (!url.endsWith('/') && fullUrl.endsWith('/')) {
|
|
13733
|
+
return fullUrl.slice(0, -1);
|
|
13734
|
+
}
|
|
13735
|
+
|
|
13736
|
+
return fullUrl;
|
|
13363
13737
|
}
|
|
13364
13738
|
|
|
13365
13739
|
/**
|
|
@@ -13909,7 +14283,8 @@ function addGlobalListeners(replay) {
|
|
|
13909
14283
|
client.on('afterSendEvent', handleAfterSendEvent(replay));
|
|
13910
14284
|
client.on('createDsc', (dsc) => {
|
|
13911
14285
|
const replayId = replay.getSessionId();
|
|
13912
|
-
|
|
14286
|
+
// We do not want to set the DSC when in buffer mode, as that means the replay has not been sent (yet)
|
|
14287
|
+
if (replayId && replay.isEnabled() && replay.recordingMode === 'session') {
|
|
13913
14288
|
dsc.replay_id = replayId;
|
|
13914
14289
|
}
|
|
13915
14290
|
});
|
|
@@ -14189,6 +14564,23 @@ function debounce(func, wait, options) {
|
|
|
14189
14564
|
return debounced;
|
|
14190
14565
|
}
|
|
14191
14566
|
|
|
14567
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
14568
|
+
|
|
14569
|
+
var EventType; (function (EventType) {
|
|
14570
|
+
const DomContentLoaded = 0; EventType[EventType["DomContentLoaded"] = DomContentLoaded] = "DomContentLoaded";
|
|
14571
|
+
const Load = 1; EventType[EventType["Load"] = Load] = "Load";
|
|
14572
|
+
const FullSnapshot = 2; EventType[EventType["FullSnapshot"] = FullSnapshot] = "FullSnapshot";
|
|
14573
|
+
const IncrementalSnapshot = 3; EventType[EventType["IncrementalSnapshot"] = IncrementalSnapshot] = "IncrementalSnapshot";
|
|
14574
|
+
const Meta = 4; EventType[EventType["Meta"] = Meta] = "Meta";
|
|
14575
|
+
const Custom = 5; EventType[EventType["Custom"] = Custom] = "Custom";
|
|
14576
|
+
const Plugin = 6; EventType[EventType["Plugin"] = Plugin] = "Plugin";
|
|
14577
|
+
})(EventType || (EventType = {}));
|
|
14578
|
+
|
|
14579
|
+
/**
|
|
14580
|
+
* This is a partial copy of rrweb's eventWithTime type which only contains the properties
|
|
14581
|
+
* we specifcally need in the SDK.
|
|
14582
|
+
*/
|
|
14583
|
+
|
|
14192
14584
|
/**
|
|
14193
14585
|
* Handler for recording events.
|
|
14194
14586
|
*
|
|
@@ -14231,6 +14623,14 @@ function getHandleRecordingEmit(replay) {
|
|
|
14231
14623
|
return false;
|
|
14232
14624
|
}
|
|
14233
14625
|
|
|
14626
|
+
// Additionally, create a meta event that will capture certain SDK settings.
|
|
14627
|
+
// In order to handle buffer mode, this needs to either be done when we
|
|
14628
|
+
// receive checkout events or at flush time.
|
|
14629
|
+
//
|
|
14630
|
+
// `isCheckout` is always true, but want to be explicit that it should
|
|
14631
|
+
// only be added for checkouts
|
|
14632
|
+
void addSettingsEvent(replay, isCheckout);
|
|
14633
|
+
|
|
14234
14634
|
// If there is a previousSessionId after a full snapshot occurs, then
|
|
14235
14635
|
// the replay session was started due to session expiration. The new session
|
|
14236
14636
|
// is started before triggering a new checkout and contains the id
|
|
@@ -14241,10 +14641,10 @@ function getHandleRecordingEmit(replay) {
|
|
|
14241
14641
|
return true;
|
|
14242
14642
|
}
|
|
14243
14643
|
|
|
14244
|
-
//
|
|
14245
|
-
// checkout
|
|
14246
|
-
if (replay.recordingMode === 'buffer' && replay.session) {
|
|
14247
|
-
const
|
|
14644
|
+
// When in buffer mode, make sure we adjust the session started date to the current earliest event of the buffer
|
|
14645
|
+
// this should usually be the timestamp of the checkout event, but to be safe...
|
|
14646
|
+
if (replay.recordingMode === 'buffer' && replay.session && replay.eventBuffer) {
|
|
14647
|
+
const earliestEvent = replay.eventBuffer.getEarliestTimestamp();
|
|
14248
14648
|
if (earliestEvent) {
|
|
14249
14649
|
replay.session.started = earliestEvent;
|
|
14250
14650
|
|
|
@@ -14268,6 +14668,46 @@ function getHandleRecordingEmit(replay) {
|
|
|
14268
14668
|
};
|
|
14269
14669
|
}
|
|
14270
14670
|
|
|
14671
|
+
/**
|
|
14672
|
+
* Exported for tests
|
|
14673
|
+
*/
|
|
14674
|
+
function createOptionsEvent(replay) {
|
|
14675
|
+
const options = replay.getOptions();
|
|
14676
|
+
return {
|
|
14677
|
+
type: EventType.Custom,
|
|
14678
|
+
timestamp: Date.now(),
|
|
14679
|
+
data: {
|
|
14680
|
+
tag: 'options',
|
|
14681
|
+
payload: {
|
|
14682
|
+
sessionSampleRate: options.sessionSampleRate,
|
|
14683
|
+
errorSampleRate: options.errorSampleRate,
|
|
14684
|
+
useCompressionOption: options.useCompression,
|
|
14685
|
+
blockAllMedia: options.blockAllMedia,
|
|
14686
|
+
maskAllText: options.maskAllText,
|
|
14687
|
+
maskAllInputs: options.maskAllInputs,
|
|
14688
|
+
useCompression: replay.eventBuffer ? replay.eventBuffer.type === 'worker' : false,
|
|
14689
|
+
networkDetailHasUrls: options.networkDetailAllowUrls.length > 0,
|
|
14690
|
+
networkCaptureBodies: options.networkCaptureBodies,
|
|
14691
|
+
networkRequestHasHeaders: options.networkRequestHeaders.length > 0,
|
|
14692
|
+
networkResponseHasHeaders: options.networkResponseHeaders.length > 0,
|
|
14693
|
+
},
|
|
14694
|
+
},
|
|
14695
|
+
};
|
|
14696
|
+
}
|
|
14697
|
+
|
|
14698
|
+
/**
|
|
14699
|
+
* Add a "meta" event that contains a simplified view on current configuration
|
|
14700
|
+
* options. This should only be included on the first segment of a recording.
|
|
14701
|
+
*/
|
|
14702
|
+
function addSettingsEvent(replay, isCheckout) {
|
|
14703
|
+
// Only need to add this event when sending the first segment
|
|
14704
|
+
if (!isCheckout || !replay.session || replay.session.segmentId !== 0) {
|
|
14705
|
+
return Promise.resolve(null);
|
|
14706
|
+
}
|
|
14707
|
+
|
|
14708
|
+
return addEvent(replay, createOptionsEvent(replay), false);
|
|
14709
|
+
}
|
|
14710
|
+
|
|
14271
14711
|
/**
|
|
14272
14712
|
* Create a replay envelope ready to be sent.
|
|
14273
14713
|
* This includes both the replay event, as well as the recording data.
|
|
@@ -14382,7 +14822,6 @@ async function sendReplayRequest({
|
|
|
14382
14822
|
eventContext,
|
|
14383
14823
|
timestamp,
|
|
14384
14824
|
session,
|
|
14385
|
-
options,
|
|
14386
14825
|
}) {
|
|
14387
14826
|
const preparedRecordingData = prepareRecordingData({
|
|
14388
14827
|
recordingData,
|
|
@@ -14424,15 +14863,6 @@ async function sendReplayRequest({
|
|
|
14424
14863
|
return;
|
|
14425
14864
|
}
|
|
14426
14865
|
|
|
14427
|
-
replayEvent.contexts = {
|
|
14428
|
-
...replayEvent.contexts,
|
|
14429
|
-
replay: {
|
|
14430
|
-
...(replayEvent.contexts && replayEvent.contexts.replay),
|
|
14431
|
-
session_sample_rate: options.sessionSampleRate,
|
|
14432
|
-
error_sample_rate: options.errorSampleRate,
|
|
14433
|
-
},
|
|
14434
|
-
};
|
|
14435
|
-
|
|
14436
14866
|
/*
|
|
14437
14867
|
For reference, the fully built event looks something like this:
|
|
14438
14868
|
{
|
|
@@ -14463,10 +14893,6 @@ async function sendReplayRequest({
|
|
|
14463
14893
|
},
|
|
14464
14894
|
"sdkProcessingMetadata": {},
|
|
14465
14895
|
"contexts": {
|
|
14466
|
-
"replay": {
|
|
14467
|
-
"session_sample_rate": 1,
|
|
14468
|
-
"error_sample_rate": 0,
|
|
14469
|
-
},
|
|
14470
14896
|
},
|
|
14471
14897
|
}
|
|
14472
14898
|
*/
|
|
@@ -14650,7 +15076,6 @@ class ReplayContainer {
|
|
|
14650
15076
|
errorIds: new Set(),
|
|
14651
15077
|
traceIds: new Set(),
|
|
14652
15078
|
urls: [],
|
|
14653
|
-
earliestEvent: null,
|
|
14654
15079
|
initialTimestamp: Date.now(),
|
|
14655
15080
|
initialUrl: '',
|
|
14656
15081
|
};}
|
|
@@ -14660,7 +15085,7 @@ class ReplayContainer {
|
|
|
14660
15085
|
recordingOptions,
|
|
14661
15086
|
}
|
|
14662
15087
|
|
|
14663
|
-
) {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);
|
|
15088
|
+
) {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);
|
|
14664
15089
|
this._recordingOptions = recordingOptions;
|
|
14665
15090
|
this._options = options;
|
|
14666
15091
|
|
|
@@ -15152,6 +15577,7 @@ class ReplayContainer {
|
|
|
15152
15577
|
WINDOW.document.addEventListener('visibilitychange', this._handleVisibilityChange);
|
|
15153
15578
|
WINDOW.addEventListener('blur', this._handleWindowBlur);
|
|
15154
15579
|
WINDOW.addEventListener('focus', this._handleWindowFocus);
|
|
15580
|
+
WINDOW.addEventListener('keydown', this._handleKeyboardEvent);
|
|
15155
15581
|
|
|
15156
15582
|
// There is no way to remove these listeners, so ensure they are only added once
|
|
15157
15583
|
if (!this._hasInitializedCoreListeners) {
|
|
@@ -15180,6 +15606,7 @@ class ReplayContainer {
|
|
|
15180
15606
|
|
|
15181
15607
|
WINDOW.removeEventListener('blur', this._handleWindowBlur);
|
|
15182
15608
|
WINDOW.removeEventListener('focus', this._handleWindowFocus);
|
|
15609
|
+
WINDOW.removeEventListener('keydown', this._handleKeyboardEvent);
|
|
15183
15610
|
|
|
15184
15611
|
if (this._performanceObserver) {
|
|
15185
15612
|
this._performanceObserver.disconnect();
|
|
@@ -15230,6 +15657,11 @@ class ReplayContainer {
|
|
|
15230
15657
|
this._doChangeToForegroundTasks(breadcrumb);
|
|
15231
15658
|
};}
|
|
15232
15659
|
|
|
15660
|
+
/** Ensure page remains active when a key is pressed. */
|
|
15661
|
+
__init16() {this._handleKeyboardEvent = (event) => {
|
|
15662
|
+
handleKeyboardEvent(this, event);
|
|
15663
|
+
};}
|
|
15664
|
+
|
|
15233
15665
|
/**
|
|
15234
15666
|
* Tasks to run when we consider a page to be hidden (via blurring and/or visibility)
|
|
15235
15667
|
*/
|
|
@@ -15309,7 +15741,7 @@ class ReplayContainer {
|
|
|
15309
15741
|
_createCustomBreadcrumb(breadcrumb) {
|
|
15310
15742
|
this.addUpdate(() => {
|
|
15311
15743
|
void addEvent(this, {
|
|
15312
|
-
type: EventType.Custom,
|
|
15744
|
+
type: EventType$1.Custom,
|
|
15313
15745
|
timestamp: breadcrumb.timestamp || 0,
|
|
15314
15746
|
data: {
|
|
15315
15747
|
tag: 'breadcrumb',
|
|
@@ -15350,22 +15782,35 @@ class ReplayContainer {
|
|
|
15350
15782
|
this._context.errorIds.clear();
|
|
15351
15783
|
this._context.traceIds.clear();
|
|
15352
15784
|
this._context.urls = [];
|
|
15353
|
-
|
|
15785
|
+
}
|
|
15786
|
+
|
|
15787
|
+
/** Update the initial timestamp based on the buffer content. */
|
|
15788
|
+
_updateInitialTimestampFromEventBuffer() {
|
|
15789
|
+
const { session, eventBuffer } = this;
|
|
15790
|
+
if (!session || !eventBuffer) {
|
|
15791
|
+
return;
|
|
15792
|
+
}
|
|
15793
|
+
|
|
15794
|
+
// we only ever update this on the initial segment
|
|
15795
|
+
if (session.segmentId) {
|
|
15796
|
+
return;
|
|
15797
|
+
}
|
|
15798
|
+
|
|
15799
|
+
const earliestEvent = eventBuffer.getEarliestTimestamp();
|
|
15800
|
+
if (earliestEvent && earliestEvent < this._context.initialTimestamp) {
|
|
15801
|
+
this._context.initialTimestamp = earliestEvent;
|
|
15802
|
+
}
|
|
15354
15803
|
}
|
|
15355
15804
|
|
|
15356
15805
|
/**
|
|
15357
15806
|
* Return and clear _context
|
|
15358
15807
|
*/
|
|
15359
15808
|
_popEventContext() {
|
|
15360
|
-
if (this._context.earliestEvent && this._context.earliestEvent < this._context.initialTimestamp) {
|
|
15361
|
-
this._context.initialTimestamp = this._context.earliestEvent;
|
|
15362
|
-
}
|
|
15363
|
-
|
|
15364
15809
|
const _context = {
|
|
15365
15810
|
initialTimestamp: this._context.initialTimestamp,
|
|
15366
15811
|
initialUrl: this._context.initialUrl,
|
|
15367
|
-
errorIds: Array.from(this._context.errorIds)
|
|
15368
|
-
traceIds: Array.from(this._context.traceIds)
|
|
15812
|
+
errorIds: Array.from(this._context.errorIds),
|
|
15813
|
+
traceIds: Array.from(this._context.traceIds),
|
|
15369
15814
|
urls: this._context.urls,
|
|
15370
15815
|
};
|
|
15371
15816
|
|
|
@@ -15404,6 +15849,9 @@ class ReplayContainer {
|
|
|
15404
15849
|
}
|
|
15405
15850
|
|
|
15406
15851
|
try {
|
|
15852
|
+
// This uses the data from the eventBuffer, so we need to call this before `finish()
|
|
15853
|
+
this._updateInitialTimestampFromEventBuffer();
|
|
15854
|
+
|
|
15407
15855
|
// Note this empties the event buffer regardless of outcome of sending replay
|
|
15408
15856
|
const recordingData = await this.eventBuffer.finish();
|
|
15409
15857
|
|
|
@@ -15444,7 +15892,7 @@ class ReplayContainer {
|
|
|
15444
15892
|
* Flush recording data to Sentry. Creates a lock so that only a single flush
|
|
15445
15893
|
* can be active at a time. Do not call this directly.
|
|
15446
15894
|
*/
|
|
15447
|
-
|
|
15895
|
+
__init17() {this._flush = async ({
|
|
15448
15896
|
force = false,
|
|
15449
15897
|
}
|
|
15450
15898
|
|
|
@@ -15499,7 +15947,7 @@ class ReplayContainer {
|
|
|
15499
15947
|
}
|
|
15500
15948
|
|
|
15501
15949
|
/** Handler for rrweb.record.onMutation */
|
|
15502
|
-
|
|
15950
|
+
__init18() {this._onMutationHandler = (mutations) => {
|
|
15503
15951
|
const count = mutations.length;
|
|
15504
15952
|
|
|
15505
15953
|
const mutationLimit = this._options._experiments.mutationLimit || 0;
|
|
@@ -15738,6 +16186,8 @@ class Replay {
|
|
|
15738
16186
|
errorSampleRate,
|
|
15739
16187
|
useCompression,
|
|
15740
16188
|
blockAllMedia,
|
|
16189
|
+
maskAllInputs,
|
|
16190
|
+
maskAllText,
|
|
15741
16191
|
networkDetailAllowUrls,
|
|
15742
16192
|
networkCaptureBodies,
|
|
15743
16193
|
networkRequestHeaders: _getMergedNetworkHeaders(networkRequestHeaders),
|
|
@@ -24965,6 +25415,7 @@ var ACTIONS$7;
|
|
|
24965
25415
|
ACTIONS["SET_MEDIA_RECORDER"] = "SET_MEDIA_RECORDER";
|
|
24966
25416
|
ACTIONS["SET_MIC_ERROR"] = "SET_MIC_ERROR";
|
|
24967
25417
|
ACTIONS["SET_PERMISSION_LISTENER"] = "SET_PERMISSION_LISTENER";
|
|
25418
|
+
ACTIONS["SET_ACTIVELY_STOPPED"] = "SET_ACTIVELY_STOPPED";
|
|
24968
25419
|
})(ACTIONS$7 || (ACTIONS$7 = {}));
|
|
24969
25420
|
var EVENTS$6;
|
|
24970
25421
|
(function (EVENTS) {
|
|
@@ -43515,6 +43966,7 @@ const initialState = {
|
|
|
43515
43966
|
isSafeMode: false,
|
|
43516
43967
|
isAutoStart: false,
|
|
43517
43968
|
isMicError: false,
|
|
43969
|
+
isActivelyStopped: false,
|
|
43518
43970
|
};
|
|
43519
43971
|
const recorderMachineV2 = createMachine({
|
|
43520
43972
|
predictableActionArguments: true,
|
|
@@ -43733,6 +44185,7 @@ const recorderMachineV2 = createMachine({
|
|
|
43733
44185
|
on: {
|
|
43734
44186
|
[EVENTS$6.STOP_RECORDING]: {
|
|
43735
44187
|
actions: [
|
|
44188
|
+
ACTIONS$7.SET_ACTIVELY_STOPPED,
|
|
43736
44189
|
ACTIONS$7.STOP_COUNT_DOWN_ACTOR,
|
|
43737
44190
|
ACTIONS$7.STOP_MEDIA_RECORDER,
|
|
43738
44191
|
ACTIONS$7.STOP_MICROPHONE_MACHINE,
|
|
@@ -43945,7 +44398,7 @@ const recorderMachineV2 = createMachine({
|
|
|
43945
44398
|
webm = new File([blob], Date.now().toString(), {
|
|
43946
44399
|
type: mimeType,
|
|
43947
44400
|
});
|
|
43948
|
-
callback({ type:
|
|
44401
|
+
callback({ type: EVENTS$6.SEND_CURRENT_TAKE, data: { webm, videoLength } });
|
|
43949
44402
|
})
|
|
43950
44403
|
.catch((error) => {
|
|
43951
44404
|
console.error('fixVideoMetadata', error);
|
|
@@ -43994,6 +44447,7 @@ const recorderMachineV2 = createMachine({
|
|
|
43994
44447
|
[ACTIONS$7.INIT_COUNT_DOWN_ACTOR]: assign$2({
|
|
43995
44448
|
countDownRef: (context, event, meta) => spawn(counterMachine.withContext(Object.assign(Object.assign({}, counterMachine.context), { callback: meta.action.data.callback })), { name: 'countDownRef' }),
|
|
43996
44449
|
}),
|
|
44450
|
+
[ACTIONS$7.SET_ACTIVELY_STOPPED]: assign$2((context) => ({ isActivelyStopped: true })),
|
|
43997
44451
|
[ACTIONS$7.STOP_COUNT_DOWN_ACTOR]: assign$2((context) => {
|
|
43998
44452
|
context.countDownRef.stop();
|
|
43999
44453
|
return {
|
|
@@ -44064,7 +44518,7 @@ const recorderMachineV2 = createMachine({
|
|
|
44064
44518
|
// send success event to the parent, to be able to proceed to the next step
|
|
44065
44519
|
[ACTIONS$7.SEND_SUCCESS_TO_PARENT]: sendParent$1((context, event) => (Object.assign(Object.assign({}, event), { type: EVENTS$5.READY_TO_START_RECORDING }))),
|
|
44066
44520
|
// on stop recording send webm file to storage machine
|
|
44067
|
-
[ACTIONS$7.SEND_CURRENT_TAKE]: sendParent$1((
|
|
44521
|
+
[ACTIONS$7.SEND_CURRENT_TAKE]: sendParent$1(({ isActivelyStopped }, event) => (Object.assign(Object.assign({}, event), { type: EVENTS$5.STOP_RECORDING, data: Object.assign(Object.assign({}, event.data), { isActivelyStopped }) }))),
|
|
44068
44522
|
// send null param to mic machine to be able to stop sound detecting
|
|
44069
44523
|
[ACTIONS$7.STOP_MICROPHONE_MACHINE]: send$2((_, _event) => ({ type: EVENTS$3.ON_SET_MEDIA_STREAM, data: null }), { to: (context) => context.microphoneRef }),
|
|
44070
44524
|
// clean mic error, this is really sensitive, !! a lot of user can't start recording coz of it
|
|
@@ -44593,7 +45047,7 @@ const MAPPED_EVENT_TYPES = {
|
|
|
44593
45047
|
PRACTICE: EVENT_TYPES.TIMES_UP,
|
|
44594
45048
|
},
|
|
44595
45049
|
};
|
|
44596
|
-
const questionEventFormatter = (questionActionType) => pure_1(({ recordingType, questions, currentQuestion, widgetConfig, currentTake }) => {
|
|
45050
|
+
const questionEventFormatter = (questionActionType) => pure_1(({ recordingType, questions, currentQuestion, widgetConfig, currentTake }, event) => {
|
|
44597
45051
|
var _a, _b;
|
|
44598
45052
|
const isQuestionMode = recordingType === TAKE_TYPES.QUESTION;
|
|
44599
45053
|
const currentQuestionFile = isQuestionMode ? (_b = (_a = widgetConfig === null || widgetConfig === void 0 ? void 0 : widgetConfig.video) === null || _a === void 0 ? void 0 : _a.videos) === null || _b === void 0 ? void 0 : _b[currentQuestion - 1] : null;
|
|
@@ -44601,10 +45055,11 @@ const questionEventFormatter = (questionActionType) => pure_1(({ recordingType,
|
|
|
44601
45055
|
const isAssessment = currentQuestionObj.answerType && currentQuestionObj.answerType !== ANSWER_TYPES.VIDEO;
|
|
44602
45056
|
return {
|
|
44603
45057
|
type: ACTIONS$6.EMIT_TRACKING_EVENT,
|
|
44604
|
-
data: Object.assign({ eventType: isQuestionMode ? MAPPED_EVENT_TYPES[questionActionType].QUESTION : MAPPED_EVENT_TYPES[questionActionType].PRACTICE }, (currentQuestionFile && { extraData: Object.assign(Object.assign(
|
|
45058
|
+
data: Object.assign({ eventType: isQuestionMode ? MAPPED_EVENT_TYPES[questionActionType].QUESTION : MAPPED_EVENT_TYPES[questionActionType].PRACTICE }, (currentQuestionFile && { extraData: Object.assign(Object.assign({ questionFilename: currentQuestionFile.filename, questionNumber: currentQuestion }, (!isAssessment && {
|
|
44605
45059
|
currentTake,
|
|
44606
45060
|
numberOfTakes: currentQuestionObj.numOfRetakes,
|
|
44607
|
-
|
|
45061
|
+
isActivelyStopped: (event.data || {}).isActivelyStopped,
|
|
45062
|
+
})), { questionType: currentQuestionObj.videoQuestion ? 'video' : 'text', answerType: currentQuestionObj.answerType || ANSWER_TYPES.VIDEO }) })),
|
|
44608
45063
|
};
|
|
44609
45064
|
});
|
|
44610
45065
|
const accWidgetMachine = createMachine({
|