@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/cjs/index.js
CHANGED
|
@@ -2097,7 +2097,10 @@ function visit(
|
|
|
2097
2097
|
const [memoize, unmemoize] = memo;
|
|
2098
2098
|
|
|
2099
2099
|
// Get the simple cases out of the way first
|
|
2100
|
-
if (
|
|
2100
|
+
if (
|
|
2101
|
+
value == null || // this matches null and undefined -> eqeq not eqeqeq
|
|
2102
|
+
(['number', 'boolean', 'string'].includes(typeof value) && !isNaN$1(value))
|
|
2103
|
+
) {
|
|
2101
2104
|
return value ;
|
|
2102
2105
|
}
|
|
2103
2106
|
|
|
@@ -2235,11 +2238,6 @@ function stringifyValue(
|
|
|
2235
2238
|
return '[NaN]';
|
|
2236
2239
|
}
|
|
2237
2240
|
|
|
2238
|
-
// this catches `undefined` (but not `null`, which is a primitive and can be serialized on its own)
|
|
2239
|
-
if (value === void 0) {
|
|
2240
|
-
return '[undefined]';
|
|
2241
|
-
}
|
|
2242
|
-
|
|
2243
2241
|
if (typeof value === 'function') {
|
|
2244
2242
|
return `[Function: ${getFunctionName(value)}]`;
|
|
2245
2243
|
}
|
|
@@ -5822,7 +5820,7 @@ function getEventForEnvelopeItem(item, type) {
|
|
|
5822
5820
|
return Array.isArray(item) ? (item )[1] : undefined;
|
|
5823
5821
|
}
|
|
5824
5822
|
|
|
5825
|
-
const SDK_VERSION = '7.
|
|
5823
|
+
const SDK_VERSION = '7.52.1';
|
|
5826
5824
|
|
|
5827
5825
|
let originalFunctionToString;
|
|
5828
5826
|
|
|
@@ -6003,8 +6001,9 @@ function _getPossibleEventMessages(event) {
|
|
|
6003
6001
|
return [event.message];
|
|
6004
6002
|
}
|
|
6005
6003
|
if (event.exception) {
|
|
6004
|
+
const { values } = event.exception;
|
|
6006
6005
|
try {
|
|
6007
|
-
const { type = '', value = '' } = (
|
|
6006
|
+
const { type = '', value = '' } = (values && values[values.length - 1]) || {};
|
|
6008
6007
|
return [`${value}`, `${type}: ${value}`];
|
|
6009
6008
|
} catch (oO) {
|
|
6010
6009
|
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error(`Cannot extract message for event ${getEventDescription(event)}`);
|
|
@@ -8361,7 +8360,7 @@ function maskInputValue({ input, maskInputSelector, unmaskInputSelector, maskInp
|
|
|
8361
8360
|
if (unmaskInputSelector && input.matches(unmaskInputSelector)) {
|
|
8362
8361
|
return text;
|
|
8363
8362
|
}
|
|
8364
|
-
if (input.hasAttribute('
|
|
8363
|
+
if (input.hasAttribute('data-rr-is-password')) {
|
|
8365
8364
|
type = 'password';
|
|
8366
8365
|
}
|
|
8367
8366
|
if (isInputTypeMasked({ maskInputOptions, tagName, type }) ||
|
|
@@ -8394,6 +8393,21 @@ function is2DCanvasBlank(canvas) {
|
|
|
8394
8393
|
}
|
|
8395
8394
|
return true;
|
|
8396
8395
|
}
|
|
8396
|
+
function getInputType(element) {
|
|
8397
|
+
const type = element.type;
|
|
8398
|
+
return element.hasAttribute('data-rr-is-password')
|
|
8399
|
+
? 'password'
|
|
8400
|
+
: type
|
|
8401
|
+
? type.toLowerCase()
|
|
8402
|
+
: null;
|
|
8403
|
+
}
|
|
8404
|
+
function getInputValue(el, tagName, type) {
|
|
8405
|
+
typeof type === 'string' ? type.toLowerCase() : '';
|
|
8406
|
+
if (tagName === 'INPUT' && (type === 'radio' || type === 'checkbox')) {
|
|
8407
|
+
return el.getAttribute('value') || '';
|
|
8408
|
+
}
|
|
8409
|
+
return el.value;
|
|
8410
|
+
}
|
|
8397
8411
|
|
|
8398
8412
|
let _id = 1;
|
|
8399
8413
|
const tagNameRegex = new RegExp('[^a-z0-9-_:]');
|
|
@@ -8432,6 +8446,13 @@ function getCssRuleString(rule) {
|
|
|
8432
8446
|
catch (_a) {
|
|
8433
8447
|
}
|
|
8434
8448
|
}
|
|
8449
|
+
return validateStringifiedCssRule(cssStringified);
|
|
8450
|
+
}
|
|
8451
|
+
function validateStringifiedCssRule(cssStringified) {
|
|
8452
|
+
if (cssStringified.indexOf(':') > -1) {
|
|
8453
|
+
const regex = /(\[(?:[\w-]+)[^\\])(:(?:[\w-]+)\])/gm;
|
|
8454
|
+
return cssStringified.replace(regex, '$1\\$2');
|
|
8455
|
+
}
|
|
8435
8456
|
return cssStringified;
|
|
8436
8457
|
}
|
|
8437
8458
|
function isCSSImportRule(rule) {
|
|
@@ -8440,7 +8461,7 @@ function isCSSImportRule(rule) {
|
|
|
8440
8461
|
function stringifyStyleSheet(sheet) {
|
|
8441
8462
|
return sheet.cssRules
|
|
8442
8463
|
? Array.from(sheet.cssRules)
|
|
8443
|
-
.map((rule) => rule.cssText
|
|
8464
|
+
.map((rule) => rule.cssText ? validateStringifiedCssRule(rule.cssText) : '')
|
|
8444
8465
|
.join('')
|
|
8445
8466
|
: '';
|
|
8446
8467
|
}
|
|
@@ -8775,14 +8796,15 @@ function serializeNode(n, options) {
|
|
|
8775
8796
|
tagName === 'select' ||
|
|
8776
8797
|
tagName === 'option') {
|
|
8777
8798
|
const el = n;
|
|
8778
|
-
const
|
|
8799
|
+
const type = getInputType(el);
|
|
8800
|
+
const value = getInputValue(el, tagName.toUpperCase(), type);
|
|
8779
8801
|
const checked = n.checked;
|
|
8780
|
-
if (
|
|
8781
|
-
|
|
8802
|
+
if (type !== 'submit' &&
|
|
8803
|
+
type !== 'button' &&
|
|
8782
8804
|
value) {
|
|
8783
8805
|
attributes.value = maskInputValue({
|
|
8784
8806
|
input: el,
|
|
8785
|
-
type
|
|
8807
|
+
type,
|
|
8786
8808
|
tagName,
|
|
8787
8809
|
value,
|
|
8788
8810
|
maskInputSelector,
|
|
@@ -9255,15 +9277,8 @@ function snapshot(n, options) {
|
|
|
9255
9277
|
function skipAttribute(tagName, attributeName, value) {
|
|
9256
9278
|
return ((tagName === 'video' || tagName === 'audio') && attributeName === 'autoplay');
|
|
9257
9279
|
}
|
|
9258
|
-
function getInputValue(tagName, el, attributes) {
|
|
9259
|
-
if (tagName === 'input' &&
|
|
9260
|
-
(attributes.type === 'radio' || attributes.type === 'checkbox')) {
|
|
9261
|
-
return el.getAttribute('value') || '';
|
|
9262
|
-
}
|
|
9263
|
-
return el.value;
|
|
9264
|
-
}
|
|
9265
9280
|
|
|
9266
|
-
var EventType;
|
|
9281
|
+
var EventType$1;
|
|
9267
9282
|
(function (EventType) {
|
|
9268
9283
|
EventType[EventType["DomContentLoaded"] = 0] = "DomContentLoaded";
|
|
9269
9284
|
EventType[EventType["Load"] = 1] = "Load";
|
|
@@ -9272,7 +9287,7 @@ var EventType;
|
|
|
9272
9287
|
EventType[EventType["Meta"] = 4] = "Meta";
|
|
9273
9288
|
EventType[EventType["Custom"] = 5] = "Custom";
|
|
9274
9289
|
EventType[EventType["Plugin"] = 6] = "Plugin";
|
|
9275
|
-
})(EventType || (EventType = {}));
|
|
9290
|
+
})(EventType$1 || (EventType$1 = {}));
|
|
9276
9291
|
var IncrementalSource;
|
|
9277
9292
|
(function (IncrementalSource) {
|
|
9278
9293
|
IncrementalSource[IncrementalSource["Mutation"] = 0] = "Mutation";
|
|
@@ -9887,9 +9902,9 @@ class MutationBuffer {
|
|
|
9887
9902
|
this.attributes.push(item);
|
|
9888
9903
|
}
|
|
9889
9904
|
if (m.attributeName === 'type' &&
|
|
9890
|
-
|
|
9905
|
+
target.tagName === 'INPUT' &&
|
|
9891
9906
|
(m.oldValue || '').toLowerCase() === 'password') {
|
|
9892
|
-
|
|
9907
|
+
target.setAttribute('data-rr-is-password', 'true');
|
|
9893
9908
|
}
|
|
9894
9909
|
if (m.attributeName === 'style') {
|
|
9895
9910
|
const old = this.doc.createElement('span');
|
|
@@ -10286,27 +10301,25 @@ function initInputObserver({ inputCb, doc, mirror, blockClass, blockSelector, un
|
|
|
10286
10301
|
isBlocked(target, blockClass, blockSelector, unblockSelector)) {
|
|
10287
10302
|
return;
|
|
10288
10303
|
}
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10304
|
+
const el = target;
|
|
10305
|
+
const type = getInputType(el);
|
|
10306
|
+
if (el.classList.contains(ignoreClass) ||
|
|
10307
|
+
(ignoreSelector && el.matches(ignoreSelector))) {
|
|
10292
10308
|
return;
|
|
10293
10309
|
}
|
|
10294
|
-
let text =
|
|
10310
|
+
let text = getInputValue(el, tagName, type);
|
|
10295
10311
|
let isChecked = false;
|
|
10296
|
-
if (target.hasAttribute('rr_is_password')) {
|
|
10297
|
-
type = 'password';
|
|
10298
|
-
}
|
|
10299
10312
|
if (type === 'radio' || type === 'checkbox') {
|
|
10300
10313
|
isChecked = target.checked;
|
|
10301
10314
|
}
|
|
10302
|
-
|
|
10315
|
+
if (hasInputMaskOptions({
|
|
10303
10316
|
maskInputOptions,
|
|
10304
10317
|
maskInputSelector,
|
|
10305
10318
|
tagName,
|
|
10306
10319
|
type,
|
|
10307
10320
|
})) {
|
|
10308
10321
|
text = maskInputValue({
|
|
10309
|
-
input:
|
|
10322
|
+
input: el,
|
|
10310
10323
|
maskInputOptions,
|
|
10311
10324
|
maskInputSelector,
|
|
10312
10325
|
unmaskInputSelector,
|
|
@@ -10323,8 +10336,18 @@ function initInputObserver({ inputCb, doc, mirror, blockClass, blockSelector, un
|
|
|
10323
10336
|
.querySelectorAll(`input[type="radio"][name="${name}"]`)
|
|
10324
10337
|
.forEach((el) => {
|
|
10325
10338
|
if (el !== target) {
|
|
10339
|
+
const text = maskInputValue({
|
|
10340
|
+
input: el,
|
|
10341
|
+
maskInputOptions,
|
|
10342
|
+
maskInputSelector,
|
|
10343
|
+
unmaskInputSelector,
|
|
10344
|
+
tagName,
|
|
10345
|
+
type,
|
|
10346
|
+
value: getInputValue(el, tagName, type),
|
|
10347
|
+
maskInputFn,
|
|
10348
|
+
});
|
|
10326
10349
|
cbWithDedup(el, callbackWrapper(wrapEventWithUserTriggeredFlag)({
|
|
10327
|
-
text
|
|
10350
|
+
text,
|
|
10328
10351
|
isChecked: !isChecked,
|
|
10329
10352
|
userTriggered: false,
|
|
10330
10353
|
}, userTriggeredOnInput));
|
|
@@ -11237,17 +11260,17 @@ function record(options = {}) {
|
|
|
11237
11260
|
wrappedEmit = (e, isCheckout) => {
|
|
11238
11261
|
var _a;
|
|
11239
11262
|
if (((_a = mutationBuffers[0]) === null || _a === void 0 ? void 0 : _a.isFrozen()) &&
|
|
11240
|
-
e.type !== EventType.FullSnapshot &&
|
|
11241
|
-
!(e.type === EventType.IncrementalSnapshot &&
|
|
11263
|
+
e.type !== EventType$1.FullSnapshot &&
|
|
11264
|
+
!(e.type === EventType$1.IncrementalSnapshot &&
|
|
11242
11265
|
e.data.source === IncrementalSource.Mutation)) {
|
|
11243
11266
|
mutationBuffers.forEach((buf) => buf.unfreeze());
|
|
11244
11267
|
}
|
|
11245
11268
|
emit(eventProcessor(e), isCheckout);
|
|
11246
|
-
if (e.type === EventType.FullSnapshot) {
|
|
11269
|
+
if (e.type === EventType$1.FullSnapshot) {
|
|
11247
11270
|
lastFullSnapshotEvent = e;
|
|
11248
11271
|
incrementalSnapshotCount = 0;
|
|
11249
11272
|
}
|
|
11250
|
-
else if (e.type === EventType.IncrementalSnapshot) {
|
|
11273
|
+
else if (e.type === EventType$1.IncrementalSnapshot) {
|
|
11251
11274
|
if (e.data.source === IncrementalSource.Mutation &&
|
|
11252
11275
|
e.data.isAttachIframe) {
|
|
11253
11276
|
return;
|
|
@@ -11263,16 +11286,16 @@ function record(options = {}) {
|
|
|
11263
11286
|
};
|
|
11264
11287
|
const wrappedMutationEmit = (m) => {
|
|
11265
11288
|
wrappedEmit(wrapEvent({
|
|
11266
|
-
type: EventType.IncrementalSnapshot,
|
|
11289
|
+
type: EventType$1.IncrementalSnapshot,
|
|
11267
11290
|
data: Object.assign({ source: IncrementalSource.Mutation }, m),
|
|
11268
11291
|
}));
|
|
11269
11292
|
};
|
|
11270
11293
|
const wrappedScrollEmit = (p) => wrappedEmit(wrapEvent({
|
|
11271
|
-
type: EventType.IncrementalSnapshot,
|
|
11294
|
+
type: EventType$1.IncrementalSnapshot,
|
|
11272
11295
|
data: Object.assign({ source: IncrementalSource.Scroll }, p),
|
|
11273
11296
|
}));
|
|
11274
11297
|
const wrappedCanvasMutationEmit = (p) => wrappedEmit(wrapEvent({
|
|
11275
|
-
type: EventType.IncrementalSnapshot,
|
|
11298
|
+
type: EventType$1.IncrementalSnapshot,
|
|
11276
11299
|
data: Object.assign({ source: IncrementalSource.CanvasMutation }, p),
|
|
11277
11300
|
}));
|
|
11278
11301
|
const iframeManager = new IframeManager({
|
|
@@ -11317,7 +11340,7 @@ function record(options = {}) {
|
|
|
11317
11340
|
takeFullSnapshot = (isCheckout = false) => {
|
|
11318
11341
|
var _a, _b, _c, _d;
|
|
11319
11342
|
wrappedEmit(wrapEvent({
|
|
11320
|
-
type: EventType.Meta,
|
|
11343
|
+
type: EventType$1.Meta,
|
|
11321
11344
|
data: {
|
|
11322
11345
|
href: window.location.href,
|
|
11323
11346
|
width: getWindowWidth(),
|
|
@@ -11360,7 +11383,7 @@ function record(options = {}) {
|
|
|
11360
11383
|
}
|
|
11361
11384
|
mirror.map = idNodeMap;
|
|
11362
11385
|
wrappedEmit(wrapEvent({
|
|
11363
|
-
type: EventType.FullSnapshot,
|
|
11386
|
+
type: EventType$1.FullSnapshot,
|
|
11364
11387
|
data: {
|
|
11365
11388
|
node,
|
|
11366
11389
|
initialOffset: {
|
|
@@ -11385,7 +11408,7 @@ function record(options = {}) {
|
|
|
11385
11408
|
const handlers = [];
|
|
11386
11409
|
handlers.push(on$1('DOMContentLoaded', () => {
|
|
11387
11410
|
wrappedEmit(wrapEvent({
|
|
11388
|
-
type: EventType.DomContentLoaded,
|
|
11411
|
+
type: EventType$1.DomContentLoaded,
|
|
11389
11412
|
data: {},
|
|
11390
11413
|
}));
|
|
11391
11414
|
}));
|
|
@@ -11395,40 +11418,40 @@ function record(options = {}) {
|
|
|
11395
11418
|
onMutation,
|
|
11396
11419
|
mutationCb: wrappedMutationEmit,
|
|
11397
11420
|
mousemoveCb: (positions, source) => wrappedEmit(wrapEvent({
|
|
11398
|
-
type: EventType.IncrementalSnapshot,
|
|
11421
|
+
type: EventType$1.IncrementalSnapshot,
|
|
11399
11422
|
data: {
|
|
11400
11423
|
source,
|
|
11401
11424
|
positions,
|
|
11402
11425
|
},
|
|
11403
11426
|
})),
|
|
11404
11427
|
mouseInteractionCb: (d) => wrappedEmit(wrapEvent({
|
|
11405
|
-
type: EventType.IncrementalSnapshot,
|
|
11428
|
+
type: EventType$1.IncrementalSnapshot,
|
|
11406
11429
|
data: Object.assign({ source: IncrementalSource.MouseInteraction }, d),
|
|
11407
11430
|
})),
|
|
11408
11431
|
scrollCb: wrappedScrollEmit,
|
|
11409
11432
|
viewportResizeCb: (d) => wrappedEmit(wrapEvent({
|
|
11410
|
-
type: EventType.IncrementalSnapshot,
|
|
11433
|
+
type: EventType$1.IncrementalSnapshot,
|
|
11411
11434
|
data: Object.assign({ source: IncrementalSource.ViewportResize }, d),
|
|
11412
11435
|
})),
|
|
11413
11436
|
inputCb: (v) => wrappedEmit(wrapEvent({
|
|
11414
|
-
type: EventType.IncrementalSnapshot,
|
|
11437
|
+
type: EventType$1.IncrementalSnapshot,
|
|
11415
11438
|
data: Object.assign({ source: IncrementalSource.Input }, v),
|
|
11416
11439
|
})),
|
|
11417
11440
|
mediaInteractionCb: (p) => wrappedEmit(wrapEvent({
|
|
11418
|
-
type: EventType.IncrementalSnapshot,
|
|
11441
|
+
type: EventType$1.IncrementalSnapshot,
|
|
11419
11442
|
data: Object.assign({ source: IncrementalSource.MediaInteraction }, p),
|
|
11420
11443
|
})),
|
|
11421
11444
|
styleSheetRuleCb: (r) => wrappedEmit(wrapEvent({
|
|
11422
|
-
type: EventType.IncrementalSnapshot,
|
|
11445
|
+
type: EventType$1.IncrementalSnapshot,
|
|
11423
11446
|
data: Object.assign({ source: IncrementalSource.StyleSheetRule }, r),
|
|
11424
11447
|
})),
|
|
11425
11448
|
styleDeclarationCb: (r) => wrappedEmit(wrapEvent({
|
|
11426
|
-
type: EventType.IncrementalSnapshot,
|
|
11449
|
+
type: EventType$1.IncrementalSnapshot,
|
|
11427
11450
|
data: Object.assign({ source: IncrementalSource.StyleDeclaration }, r),
|
|
11428
11451
|
})),
|
|
11429
11452
|
canvasMutationCb: wrappedCanvasMutationEmit,
|
|
11430
11453
|
fontCb: (p) => wrappedEmit(wrapEvent({
|
|
11431
|
-
type: EventType.IncrementalSnapshot,
|
|
11454
|
+
type: EventType$1.IncrementalSnapshot,
|
|
11432
11455
|
data: Object.assign({ source: IncrementalSource.Font }, p),
|
|
11433
11456
|
})),
|
|
11434
11457
|
blockClass,
|
|
@@ -11461,7 +11484,7 @@ function record(options = {}) {
|
|
|
11461
11484
|
observer: p.observer,
|
|
11462
11485
|
options: p.options,
|
|
11463
11486
|
callback: (payload) => wrappedEmit(wrapEvent({
|
|
11464
|
-
type: EventType.Plugin,
|
|
11487
|
+
type: EventType$1.Plugin,
|
|
11465
11488
|
data: {
|
|
11466
11489
|
plugin: p.name,
|
|
11467
11490
|
payload,
|
|
@@ -11489,7 +11512,7 @@ function record(options = {}) {
|
|
|
11489
11512
|
else {
|
|
11490
11513
|
handlers.push(on$1('load', () => {
|
|
11491
11514
|
wrappedEmit(wrapEvent({
|
|
11492
|
-
type: EventType.Load,
|
|
11515
|
+
type: EventType$1.Load,
|
|
11493
11516
|
data: {},
|
|
11494
11517
|
}));
|
|
11495
11518
|
init();
|
|
@@ -11508,7 +11531,7 @@ record.addCustomEvent = (tag, payload) => {
|
|
|
11508
11531
|
throw new Error('please add custom event after start recording');
|
|
11509
11532
|
}
|
|
11510
11533
|
wrappedEmit(wrapEvent({
|
|
11511
|
-
type: EventType.Custom,
|
|
11534
|
+
type: EventType$1.Custom,
|
|
11512
11535
|
data: {
|
|
11513
11536
|
tag,
|
|
11514
11537
|
payload,
|
|
@@ -11526,6 +11549,475 @@ record.takeFullSnapshot = (isCheckout) => {
|
|
|
11526
11549
|
};
|
|
11527
11550
|
record.mirror = mirror;
|
|
11528
11551
|
|
|
11552
|
+
/**
|
|
11553
|
+
* Create a breadcrumb for a replay.
|
|
11554
|
+
*/
|
|
11555
|
+
function createBreadcrumb(
|
|
11556
|
+
breadcrumb,
|
|
11557
|
+
) {
|
|
11558
|
+
return {
|
|
11559
|
+
timestamp: Date.now() / 1000,
|
|
11560
|
+
type: 'default',
|
|
11561
|
+
...breadcrumb,
|
|
11562
|
+
};
|
|
11563
|
+
}
|
|
11564
|
+
|
|
11565
|
+
var NodeType;
|
|
11566
|
+
(function (NodeType) {
|
|
11567
|
+
NodeType[NodeType["Document"] = 0] = "Document";
|
|
11568
|
+
NodeType[NodeType["DocumentType"] = 1] = "DocumentType";
|
|
11569
|
+
NodeType[NodeType["Element"] = 2] = "Element";
|
|
11570
|
+
NodeType[NodeType["Text"] = 3] = "Text";
|
|
11571
|
+
NodeType[NodeType["CDATA"] = 4] = "CDATA";
|
|
11572
|
+
NodeType[NodeType["Comment"] = 5] = "Comment";
|
|
11573
|
+
})(NodeType || (NodeType = {}));
|
|
11574
|
+
|
|
11575
|
+
/**
|
|
11576
|
+
* Converts a timestamp to ms, if it was in s, or keeps it as ms.
|
|
11577
|
+
*/
|
|
11578
|
+
function timestampToMs(timestamp) {
|
|
11579
|
+
const isMs = timestamp > 9999999999;
|
|
11580
|
+
return isMs ? timestamp : timestamp * 1000;
|
|
11581
|
+
}
|
|
11582
|
+
|
|
11583
|
+
/**
|
|
11584
|
+
* Add an event to the event buffer.
|
|
11585
|
+
* `isCheckout` is true if this is either the very first event, or an event triggered by `checkoutEveryNms`.
|
|
11586
|
+
*/
|
|
11587
|
+
async function addEvent(
|
|
11588
|
+
replay,
|
|
11589
|
+
event,
|
|
11590
|
+
isCheckout,
|
|
11591
|
+
) {
|
|
11592
|
+
if (!replay.eventBuffer) {
|
|
11593
|
+
// This implies that `_isEnabled` is false
|
|
11594
|
+
return null;
|
|
11595
|
+
}
|
|
11596
|
+
|
|
11597
|
+
if (replay.isPaused()) {
|
|
11598
|
+
// Do not add to event buffer when recording is paused
|
|
11599
|
+
return null;
|
|
11600
|
+
}
|
|
11601
|
+
|
|
11602
|
+
const timestampInMs = timestampToMs(event.timestamp);
|
|
11603
|
+
|
|
11604
|
+
// Throw out events that happen more than 5 minutes ago. This can happen if
|
|
11605
|
+
// page has been left open and idle for a long period of time and user
|
|
11606
|
+
// comes back to trigger a new session. The performance entries rely on
|
|
11607
|
+
// `performance.timeOrigin`, which is when the page first opened.
|
|
11608
|
+
if (timestampInMs + replay.timeouts.sessionIdlePause < Date.now()) {
|
|
11609
|
+
return null;
|
|
11610
|
+
}
|
|
11611
|
+
|
|
11612
|
+
try {
|
|
11613
|
+
if (isCheckout) {
|
|
11614
|
+
replay.eventBuffer.clear();
|
|
11615
|
+
}
|
|
11616
|
+
|
|
11617
|
+
return await replay.eventBuffer.addEvent(event);
|
|
11618
|
+
} catch (error) {
|
|
11619
|
+
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error(error);
|
|
11620
|
+
await replay.stop('addEvent');
|
|
11621
|
+
|
|
11622
|
+
const client = getCurrentHub().getClient();
|
|
11623
|
+
|
|
11624
|
+
if (client) {
|
|
11625
|
+
client.recordDroppedEvent('internal_sdk_error', 'replay');
|
|
11626
|
+
}
|
|
11627
|
+
}
|
|
11628
|
+
}
|
|
11629
|
+
|
|
11630
|
+
/**
|
|
11631
|
+
* Add a breadcrumb event to replay.
|
|
11632
|
+
*/
|
|
11633
|
+
function addBreadcrumbEvent(replay, breadcrumb) {
|
|
11634
|
+
if (breadcrumb.category === 'sentry.transaction') {
|
|
11635
|
+
return;
|
|
11636
|
+
}
|
|
11637
|
+
|
|
11638
|
+
if (['ui.click', 'ui.input'].includes(breadcrumb.category )) {
|
|
11639
|
+
replay.triggerUserActivity();
|
|
11640
|
+
} else {
|
|
11641
|
+
replay.checkAndHandleExpiredSession();
|
|
11642
|
+
}
|
|
11643
|
+
|
|
11644
|
+
replay.addUpdate(() => {
|
|
11645
|
+
void addEvent(replay, {
|
|
11646
|
+
type: EventType$1.Custom,
|
|
11647
|
+
// TODO: We were converting from ms to seconds for breadcrumbs, spans,
|
|
11648
|
+
// but maybe we should just keep them as milliseconds
|
|
11649
|
+
timestamp: (breadcrumb.timestamp || 0) * 1000,
|
|
11650
|
+
data: {
|
|
11651
|
+
tag: 'breadcrumb',
|
|
11652
|
+
// normalize to max. 10 depth and 1_000 properties per object
|
|
11653
|
+
payload: normalize(breadcrumb, 10, 1000),
|
|
11654
|
+
},
|
|
11655
|
+
});
|
|
11656
|
+
|
|
11657
|
+
// Do not flush after console log messages
|
|
11658
|
+
return breadcrumb.category === 'console';
|
|
11659
|
+
});
|
|
11660
|
+
}
|
|
11661
|
+
|
|
11662
|
+
/**
|
|
11663
|
+
* Detect a slow click on a button/a tag,
|
|
11664
|
+
* and potentially create a corresponding breadcrumb.
|
|
11665
|
+
*/
|
|
11666
|
+
function detectSlowClick(
|
|
11667
|
+
replay,
|
|
11668
|
+
config,
|
|
11669
|
+
clickBreadcrumb,
|
|
11670
|
+
node,
|
|
11671
|
+
) {
|
|
11672
|
+
if (ignoreElement(node, config)) {
|
|
11673
|
+
return;
|
|
11674
|
+
}
|
|
11675
|
+
|
|
11676
|
+
/*
|
|
11677
|
+
We consider a slow click a click on a button/a, which does not trigger one of:
|
|
11678
|
+
- DOM mutation
|
|
11679
|
+
- Scroll (within 100ms)
|
|
11680
|
+
Within the given threshold time.
|
|
11681
|
+
After time timeout time, we stop listening and mark it as a slow click anyhow.
|
|
11682
|
+
*/
|
|
11683
|
+
|
|
11684
|
+
let cleanup = () => {
|
|
11685
|
+
// replaced further down
|
|
11686
|
+
};
|
|
11687
|
+
|
|
11688
|
+
// After timeout time, def. consider this a slow click, and stop watching for mutations
|
|
11689
|
+
const timeout = setTimeout(() => {
|
|
11690
|
+
handleSlowClick(replay, clickBreadcrumb, config.timeout, 'timeout');
|
|
11691
|
+
cleanup();
|
|
11692
|
+
}, config.timeout);
|
|
11693
|
+
|
|
11694
|
+
const mutationHandler = () => {
|
|
11695
|
+
maybeHandleSlowClick(replay, clickBreadcrumb, config.threshold, config.timeout, 'mutation');
|
|
11696
|
+
cleanup();
|
|
11697
|
+
};
|
|
11698
|
+
|
|
11699
|
+
const scrollHandler = () => {
|
|
11700
|
+
maybeHandleSlowClick(replay, clickBreadcrumb, config.scrollTimeout, config.timeout, 'scroll');
|
|
11701
|
+
cleanup();
|
|
11702
|
+
};
|
|
11703
|
+
|
|
11704
|
+
const obs = new MutationObserver(mutationHandler);
|
|
11705
|
+
|
|
11706
|
+
obs.observe(WINDOW.document.documentElement, {
|
|
11707
|
+
attributes: true,
|
|
11708
|
+
characterData: true,
|
|
11709
|
+
childList: true,
|
|
11710
|
+
subtree: true,
|
|
11711
|
+
});
|
|
11712
|
+
|
|
11713
|
+
WINDOW.addEventListener('scroll', scrollHandler);
|
|
11714
|
+
|
|
11715
|
+
// Stop listening to scroll timeouts early
|
|
11716
|
+
const scrollTimeout = setTimeout(() => {
|
|
11717
|
+
WINDOW.removeEventListener('scroll', scrollHandler);
|
|
11718
|
+
}, config.scrollTimeout);
|
|
11719
|
+
|
|
11720
|
+
cleanup = () => {
|
|
11721
|
+
clearTimeout(timeout);
|
|
11722
|
+
clearTimeout(scrollTimeout);
|
|
11723
|
+
obs.disconnect();
|
|
11724
|
+
WINDOW.removeEventListener('scroll', scrollHandler);
|
|
11725
|
+
};
|
|
11726
|
+
}
|
|
11727
|
+
|
|
11728
|
+
function maybeHandleSlowClick(
|
|
11729
|
+
replay,
|
|
11730
|
+
clickBreadcrumb,
|
|
11731
|
+
threshold,
|
|
11732
|
+
timeout,
|
|
11733
|
+
endReason,
|
|
11734
|
+
) {
|
|
11735
|
+
const now = Date.now();
|
|
11736
|
+
const timeAfterClickMs = now - clickBreadcrumb.timestamp * 1000;
|
|
11737
|
+
|
|
11738
|
+
if (timeAfterClickMs > threshold) {
|
|
11739
|
+
handleSlowClick(replay, clickBreadcrumb, Math.min(timeAfterClickMs, timeout), endReason);
|
|
11740
|
+
return true;
|
|
11741
|
+
}
|
|
11742
|
+
|
|
11743
|
+
return false;
|
|
11744
|
+
}
|
|
11745
|
+
|
|
11746
|
+
function handleSlowClick(
|
|
11747
|
+
replay,
|
|
11748
|
+
clickBreadcrumb,
|
|
11749
|
+
timeAfterClickMs,
|
|
11750
|
+
endReason,
|
|
11751
|
+
) {
|
|
11752
|
+
const breadcrumb = {
|
|
11753
|
+
message: clickBreadcrumb.message,
|
|
11754
|
+
timestamp: clickBreadcrumb.timestamp,
|
|
11755
|
+
category: 'ui.slowClickDetected',
|
|
11756
|
+
data: {
|
|
11757
|
+
...clickBreadcrumb.data,
|
|
11758
|
+
url: WINDOW.location.href,
|
|
11759
|
+
// TODO FN: add parametrized route, when possible
|
|
11760
|
+
timeAfterClickMs,
|
|
11761
|
+
endReason,
|
|
11762
|
+
},
|
|
11763
|
+
};
|
|
11764
|
+
|
|
11765
|
+
addBreadcrumbEvent(replay, breadcrumb);
|
|
11766
|
+
}
|
|
11767
|
+
|
|
11768
|
+
const SLOW_CLICK_IGNORE_TAGS = ['SELECT', 'OPTION'];
|
|
11769
|
+
|
|
11770
|
+
function ignoreElement(node, config) {
|
|
11771
|
+
// If <input> tag, we only want to consider input[type='submit'] & input[type='button']
|
|
11772
|
+
if (node.tagName === 'INPUT' && !['submit', 'button'].includes(node.getAttribute('type') || '')) {
|
|
11773
|
+
return true;
|
|
11774
|
+
}
|
|
11775
|
+
|
|
11776
|
+
if (SLOW_CLICK_IGNORE_TAGS.includes(node.tagName)) {
|
|
11777
|
+
return true;
|
|
11778
|
+
}
|
|
11779
|
+
|
|
11780
|
+
// If <a> tag, detect special variants that may not lead to an action
|
|
11781
|
+
// If target !== _self, we may open the link somewhere else, which would lead to no action
|
|
11782
|
+
// Also, when downloading a file, we may not leave the page, but still not trigger an action
|
|
11783
|
+
if (
|
|
11784
|
+
node.tagName === 'A' &&
|
|
11785
|
+
(node.hasAttribute('download') || (node.hasAttribute('target') && node.getAttribute('target') !== '_self'))
|
|
11786
|
+
) {
|
|
11787
|
+
return true;
|
|
11788
|
+
}
|
|
11789
|
+
|
|
11790
|
+
if (config.ignoreSelector && node.matches(config.ignoreSelector)) {
|
|
11791
|
+
return true;
|
|
11792
|
+
}
|
|
11793
|
+
|
|
11794
|
+
return false;
|
|
11795
|
+
}
|
|
11796
|
+
|
|
11797
|
+
// Note that these are the serialized attributes and not attributes directly on
|
|
11798
|
+
// the DOM Node. Attributes we are interested in:
|
|
11799
|
+
const ATTRIBUTES_TO_RECORD = new Set([
|
|
11800
|
+
'id',
|
|
11801
|
+
'class',
|
|
11802
|
+
'aria-label',
|
|
11803
|
+
'role',
|
|
11804
|
+
'name',
|
|
11805
|
+
'alt',
|
|
11806
|
+
'title',
|
|
11807
|
+
'data-test-id',
|
|
11808
|
+
'data-testid',
|
|
11809
|
+
]);
|
|
11810
|
+
|
|
11811
|
+
/**
|
|
11812
|
+
* Inclusion list of attributes that we want to record from the DOM element
|
|
11813
|
+
*/
|
|
11814
|
+
function getAttributesToRecord(attributes) {
|
|
11815
|
+
const obj = {};
|
|
11816
|
+
for (const key in attributes) {
|
|
11817
|
+
if (ATTRIBUTES_TO_RECORD.has(key)) {
|
|
11818
|
+
let normalizedKey = key;
|
|
11819
|
+
|
|
11820
|
+
if (key === 'data-testid' || key === 'data-test-id') {
|
|
11821
|
+
normalizedKey = 'testId';
|
|
11822
|
+
}
|
|
11823
|
+
|
|
11824
|
+
obj[normalizedKey] = attributes[key];
|
|
11825
|
+
}
|
|
11826
|
+
}
|
|
11827
|
+
|
|
11828
|
+
return obj;
|
|
11829
|
+
}
|
|
11830
|
+
|
|
11831
|
+
const handleDomListener = (
|
|
11832
|
+
replay,
|
|
11833
|
+
) => {
|
|
11834
|
+
const slowClickExperiment = replay.getOptions()._experiments.slowClicks;
|
|
11835
|
+
|
|
11836
|
+
const slowClickConfig = slowClickExperiment
|
|
11837
|
+
? {
|
|
11838
|
+
threshold: slowClickExperiment.threshold,
|
|
11839
|
+
timeout: slowClickExperiment.timeout,
|
|
11840
|
+
scrollTimeout: slowClickExperiment.scrollTimeout,
|
|
11841
|
+
ignoreSelector: slowClickExperiment.ignoreSelectors ? slowClickExperiment.ignoreSelectors.join(',') : '',
|
|
11842
|
+
}
|
|
11843
|
+
: undefined;
|
|
11844
|
+
|
|
11845
|
+
return (handlerData) => {
|
|
11846
|
+
if (!replay.isEnabled()) {
|
|
11847
|
+
return;
|
|
11848
|
+
}
|
|
11849
|
+
|
|
11850
|
+
const result = handleDom(handlerData);
|
|
11851
|
+
|
|
11852
|
+
if (!result) {
|
|
11853
|
+
return;
|
|
11854
|
+
}
|
|
11855
|
+
|
|
11856
|
+
const isClick = handlerData.name === 'click';
|
|
11857
|
+
const event = isClick && (handlerData.event );
|
|
11858
|
+
// Ignore clicks if ctrl/alt/meta keys are held down as they alter behavior of clicks (e.g. open in new tab)
|
|
11859
|
+
if (isClick && slowClickConfig && event && !event.altKey && !event.metaKey && !event.ctrlKey) {
|
|
11860
|
+
detectSlowClick(
|
|
11861
|
+
replay,
|
|
11862
|
+
slowClickConfig,
|
|
11863
|
+
result ,
|
|
11864
|
+
getClickTargetNode(handlerData.event) ,
|
|
11865
|
+
);
|
|
11866
|
+
}
|
|
11867
|
+
|
|
11868
|
+
addBreadcrumbEvent(replay, result);
|
|
11869
|
+
};
|
|
11870
|
+
};
|
|
11871
|
+
|
|
11872
|
+
/** Get the base DOM breadcrumb. */
|
|
11873
|
+
function getBaseDomBreadcrumb(target, message) {
|
|
11874
|
+
// `__sn` property is the serialized node created by rrweb
|
|
11875
|
+
const serializedNode = target && isRrwebNode(target) && target.__sn.type === NodeType.Element ? target.__sn : null;
|
|
11876
|
+
|
|
11877
|
+
return {
|
|
11878
|
+
message,
|
|
11879
|
+
data: serializedNode
|
|
11880
|
+
? {
|
|
11881
|
+
nodeId: serializedNode.id,
|
|
11882
|
+
node: {
|
|
11883
|
+
id: serializedNode.id,
|
|
11884
|
+
tagName: serializedNode.tagName,
|
|
11885
|
+
textContent: target
|
|
11886
|
+
? Array.from(target.childNodes)
|
|
11887
|
+
.map(
|
|
11888
|
+
(node) => '__sn' in node && node.__sn.type === NodeType.Text && node.__sn.textContent,
|
|
11889
|
+
)
|
|
11890
|
+
.filter(Boolean) // filter out empty values
|
|
11891
|
+
.map(text => (text ).trim())
|
|
11892
|
+
.join('')
|
|
11893
|
+
: '',
|
|
11894
|
+
attributes: getAttributesToRecord(serializedNode.attributes),
|
|
11895
|
+
},
|
|
11896
|
+
}
|
|
11897
|
+
: {},
|
|
11898
|
+
};
|
|
11899
|
+
}
|
|
11900
|
+
|
|
11901
|
+
/**
|
|
11902
|
+
* An event handler to react to DOM events.
|
|
11903
|
+
* Exported for tests.
|
|
11904
|
+
*/
|
|
11905
|
+
function handleDom(handlerData) {
|
|
11906
|
+
const { target, message } = getDomTarget(handlerData);
|
|
11907
|
+
|
|
11908
|
+
return createBreadcrumb({
|
|
11909
|
+
category: `ui.${handlerData.name}`,
|
|
11910
|
+
...getBaseDomBreadcrumb(target, message),
|
|
11911
|
+
});
|
|
11912
|
+
}
|
|
11913
|
+
|
|
11914
|
+
function getDomTarget(handlerData) {
|
|
11915
|
+
const isClick = handlerData.name === 'click';
|
|
11916
|
+
|
|
11917
|
+
let message;
|
|
11918
|
+
let target = null;
|
|
11919
|
+
|
|
11920
|
+
// Accessing event.target can throw (see getsentry/raven-js#838, #768)
|
|
11921
|
+
try {
|
|
11922
|
+
target = isClick ? getClickTargetNode(handlerData.event) : getTargetNode(handlerData.event);
|
|
11923
|
+
message = htmlTreeAsString(target, { maxStringLength: 200 }) || '<unknown>';
|
|
11924
|
+
} catch (e) {
|
|
11925
|
+
message = '<unknown>';
|
|
11926
|
+
}
|
|
11927
|
+
|
|
11928
|
+
return { target, message };
|
|
11929
|
+
}
|
|
11930
|
+
|
|
11931
|
+
function isRrwebNode(node) {
|
|
11932
|
+
return '__sn' in node;
|
|
11933
|
+
}
|
|
11934
|
+
|
|
11935
|
+
function getTargetNode(event) {
|
|
11936
|
+
if (isEventWithTarget(event)) {
|
|
11937
|
+
return event.target ;
|
|
11938
|
+
}
|
|
11939
|
+
|
|
11940
|
+
return event;
|
|
11941
|
+
}
|
|
11942
|
+
|
|
11943
|
+
const INTERACTIVE_SELECTOR = 'button,a';
|
|
11944
|
+
|
|
11945
|
+
// For clicks, we check if the target is inside of a button or link
|
|
11946
|
+
// If so, we use this as the target instead
|
|
11947
|
+
// This is useful because if you click on the image in <button><img></button>,
|
|
11948
|
+
// The target will be the image, not the button, which we don't want here
|
|
11949
|
+
function getClickTargetNode(event) {
|
|
11950
|
+
const target = getTargetNode(event);
|
|
11951
|
+
|
|
11952
|
+
if (!target || !(target instanceof Element)) {
|
|
11953
|
+
return target;
|
|
11954
|
+
}
|
|
11955
|
+
|
|
11956
|
+
const closestInteractive = target.closest(INTERACTIVE_SELECTOR);
|
|
11957
|
+
return closestInteractive || target;
|
|
11958
|
+
}
|
|
11959
|
+
|
|
11960
|
+
function isEventWithTarget(event) {
|
|
11961
|
+
return typeof event === 'object' && !!event && 'target' in event;
|
|
11962
|
+
}
|
|
11963
|
+
|
|
11964
|
+
/** Handle keyboard events & create breadcrumbs. */
|
|
11965
|
+
function handleKeyboardEvent(replay, event) {
|
|
11966
|
+
if (!replay.isEnabled()) {
|
|
11967
|
+
return;
|
|
11968
|
+
}
|
|
11969
|
+
|
|
11970
|
+
replay.triggerUserActivity();
|
|
11971
|
+
|
|
11972
|
+
const breadcrumb = getKeyboardBreadcrumb(event);
|
|
11973
|
+
|
|
11974
|
+
if (!breadcrumb) {
|
|
11975
|
+
return;
|
|
11976
|
+
}
|
|
11977
|
+
|
|
11978
|
+
addBreadcrumbEvent(replay, breadcrumb);
|
|
11979
|
+
}
|
|
11980
|
+
|
|
11981
|
+
/** exported only for tests */
|
|
11982
|
+
function getKeyboardBreadcrumb(event) {
|
|
11983
|
+
const { metaKey, shiftKey, ctrlKey, altKey, key, target } = event;
|
|
11984
|
+
|
|
11985
|
+
// never capture for input fields
|
|
11986
|
+
if (!target || isInputElement(target )) {
|
|
11987
|
+
return null;
|
|
11988
|
+
}
|
|
11989
|
+
|
|
11990
|
+
// Note: We do not consider shift here, as that means "uppercase"
|
|
11991
|
+
const hasModifierKey = metaKey || ctrlKey || altKey;
|
|
11992
|
+
const isCharacterKey = key.length === 1; // other keys like Escape, Tab, etc have a longer length
|
|
11993
|
+
|
|
11994
|
+
// Do not capture breadcrumb if only a word key is pressed
|
|
11995
|
+
// This could leak e.g. user input
|
|
11996
|
+
if (!hasModifierKey && isCharacterKey) {
|
|
11997
|
+
return null;
|
|
11998
|
+
}
|
|
11999
|
+
|
|
12000
|
+
const message = htmlTreeAsString(target, { maxStringLength: 200 }) || '<unknown>';
|
|
12001
|
+
const baseBreadcrumb = getBaseDomBreadcrumb(target , message);
|
|
12002
|
+
|
|
12003
|
+
return createBreadcrumb({
|
|
12004
|
+
category: 'ui.keyDown',
|
|
12005
|
+
message,
|
|
12006
|
+
data: {
|
|
12007
|
+
...baseBreadcrumb.data,
|
|
12008
|
+
metaKey,
|
|
12009
|
+
shiftKey,
|
|
12010
|
+
ctrlKey,
|
|
12011
|
+
altKey,
|
|
12012
|
+
key,
|
|
12013
|
+
},
|
|
12014
|
+
});
|
|
12015
|
+
}
|
|
12016
|
+
|
|
12017
|
+
function isInputElement(target) {
|
|
12018
|
+
return target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable;
|
|
12019
|
+
}
|
|
12020
|
+
|
|
11529
12021
|
const NAVIGATION_ENTRY_KEYS = [
|
|
11530
12022
|
'name',
|
|
11531
12023
|
'type',
|
|
@@ -11679,20 +12171,19 @@ class EventBufferArray {
|
|
|
11679
12171
|
return this.events.length > 0;
|
|
11680
12172
|
}
|
|
11681
12173
|
|
|
12174
|
+
/** @inheritdoc */
|
|
12175
|
+
get type() {
|
|
12176
|
+
return 'sync';
|
|
12177
|
+
}
|
|
12178
|
+
|
|
11682
12179
|
/** @inheritdoc */
|
|
11683
12180
|
destroy() {
|
|
11684
12181
|
this.events = [];
|
|
11685
12182
|
}
|
|
11686
12183
|
|
|
11687
12184
|
/** @inheritdoc */
|
|
11688
|
-
async addEvent(event
|
|
11689
|
-
if (isCheckout) {
|
|
11690
|
-
this.events = [event];
|
|
11691
|
-
return;
|
|
11692
|
-
}
|
|
11693
|
-
|
|
12185
|
+
async addEvent(event) {
|
|
11694
12186
|
this.events.push(event);
|
|
11695
|
-
return;
|
|
11696
12187
|
}
|
|
11697
12188
|
|
|
11698
12189
|
/** @inheritdoc */
|
|
@@ -11706,6 +12197,22 @@ class EventBufferArray {
|
|
|
11706
12197
|
resolve(JSON.stringify(eventsRet));
|
|
11707
12198
|
});
|
|
11708
12199
|
}
|
|
12200
|
+
|
|
12201
|
+
/** @inheritdoc */
|
|
12202
|
+
clear() {
|
|
12203
|
+
this.events = [];
|
|
12204
|
+
}
|
|
12205
|
+
|
|
12206
|
+
/** @inheritdoc */
|
|
12207
|
+
getEarliestTimestamp() {
|
|
12208
|
+
const timestamp = this.events.map(event => event.timestamp).sort()[0];
|
|
12209
|
+
|
|
12210
|
+
if (!timestamp) {
|
|
12211
|
+
return null;
|
|
12212
|
+
}
|
|
12213
|
+
|
|
12214
|
+
return timestampToMs(timestamp);
|
|
12215
|
+
}
|
|
11709
12216
|
}
|
|
11710
12217
|
|
|
11711
12218
|
/**
|
|
@@ -11813,11 +12320,20 @@ class WorkerHandler {
|
|
|
11813
12320
|
* Exported only for testing.
|
|
11814
12321
|
*/
|
|
11815
12322
|
class EventBufferCompressionWorker {
|
|
11816
|
-
/** @inheritdoc */
|
|
11817
12323
|
|
|
11818
12324
|
constructor(worker) {
|
|
11819
12325
|
this._worker = new WorkerHandler(worker);
|
|
11820
|
-
this.
|
|
12326
|
+
this._earliestTimestamp = null;
|
|
12327
|
+
}
|
|
12328
|
+
|
|
12329
|
+
/** @inheritdoc */
|
|
12330
|
+
get hasEvents() {
|
|
12331
|
+
return !!this._earliestTimestamp;
|
|
12332
|
+
}
|
|
12333
|
+
|
|
12334
|
+
/** @inheritdoc */
|
|
12335
|
+
get type() {
|
|
12336
|
+
return 'worker';
|
|
11821
12337
|
}
|
|
11822
12338
|
|
|
11823
12339
|
/**
|
|
@@ -11840,13 +12356,10 @@ class EventBufferCompressionWorker {
|
|
|
11840
12356
|
*
|
|
11841
12357
|
* Returns true if event was successfuly received and processed by worker.
|
|
11842
12358
|
*/
|
|
11843
|
-
|
|
11844
|
-
|
|
11845
|
-
|
|
11846
|
-
|
|
11847
|
-
// This event is a checkout, make sure worker buffer is cleared before
|
|
11848
|
-
// proceeding.
|
|
11849
|
-
await this._clear();
|
|
12359
|
+
addEvent(event) {
|
|
12360
|
+
const timestamp = timestampToMs(event.timestamp);
|
|
12361
|
+
if (!this._earliestTimestamp || timestamp < this._earliestTimestamp) {
|
|
12362
|
+
this._earliestTimestamp = timestamp;
|
|
11850
12363
|
}
|
|
11851
12364
|
|
|
11852
12365
|
return this._sendEventToWorker(event);
|
|
@@ -11859,6 +12372,18 @@ class EventBufferCompressionWorker {
|
|
|
11859
12372
|
return this._finishRequest();
|
|
11860
12373
|
}
|
|
11861
12374
|
|
|
12375
|
+
/** @inheritdoc */
|
|
12376
|
+
clear() {
|
|
12377
|
+
this._earliestTimestamp = null;
|
|
12378
|
+
// We do not wait on this, as we assume the order of messages is consistent for the worker
|
|
12379
|
+
void this._worker.postMessage('clear');
|
|
12380
|
+
}
|
|
12381
|
+
|
|
12382
|
+
/** @inheritdoc */
|
|
12383
|
+
getEarliestTimestamp() {
|
|
12384
|
+
return this._earliestTimestamp;
|
|
12385
|
+
}
|
|
12386
|
+
|
|
11862
12387
|
/**
|
|
11863
12388
|
* Send the event to the worker.
|
|
11864
12389
|
*/
|
|
@@ -11872,15 +12397,10 @@ class EventBufferCompressionWorker {
|
|
|
11872
12397
|
async _finishRequest() {
|
|
11873
12398
|
const response = await this._worker.postMessage('finish');
|
|
11874
12399
|
|
|
11875
|
-
this.
|
|
12400
|
+
this._earliestTimestamp = null;
|
|
11876
12401
|
|
|
11877
12402
|
return response;
|
|
11878
12403
|
}
|
|
11879
|
-
|
|
11880
|
-
/** Clear any pending events from the worker. */
|
|
11881
|
-
_clear() {
|
|
11882
|
-
return this._worker.postMessage('clear');
|
|
11883
|
-
}
|
|
11884
12404
|
}
|
|
11885
12405
|
|
|
11886
12406
|
/**
|
|
@@ -11898,6 +12418,11 @@ class EventBufferProxy {
|
|
|
11898
12418
|
this._ensureWorkerIsLoadedPromise = this._ensureWorkerIsLoaded();
|
|
11899
12419
|
}
|
|
11900
12420
|
|
|
12421
|
+
/** @inheritdoc */
|
|
12422
|
+
get type() {
|
|
12423
|
+
return this._used.type;
|
|
12424
|
+
}
|
|
12425
|
+
|
|
11901
12426
|
/** @inheritDoc */
|
|
11902
12427
|
get hasEvents() {
|
|
11903
12428
|
return this._used.hasEvents;
|
|
@@ -11909,13 +12434,23 @@ class EventBufferProxy {
|
|
|
11909
12434
|
this._compression.destroy();
|
|
11910
12435
|
}
|
|
11911
12436
|
|
|
12437
|
+
/** @inheritdoc */
|
|
12438
|
+
clear() {
|
|
12439
|
+
return this._used.clear();
|
|
12440
|
+
}
|
|
12441
|
+
|
|
12442
|
+
/** @inheritdoc */
|
|
12443
|
+
getEarliestTimestamp() {
|
|
12444
|
+
return this._used.getEarliestTimestamp();
|
|
12445
|
+
}
|
|
12446
|
+
|
|
11912
12447
|
/**
|
|
11913
12448
|
* Add an event to the event buffer.
|
|
11914
12449
|
*
|
|
11915
12450
|
* Returns true if event was successfully added.
|
|
11916
12451
|
*/
|
|
11917
|
-
addEvent(event
|
|
11918
|
-
return this._used.addEvent(event
|
|
12452
|
+
addEvent(event) {
|
|
12453
|
+
return this._used.addEvent(event);
|
|
11919
12454
|
}
|
|
11920
12455
|
|
|
11921
12456
|
/** @inheritDoc */
|
|
@@ -12197,59 +12732,6 @@ function getSession({
|
|
|
12197
12732
|
return { type: 'new', session: newSession };
|
|
12198
12733
|
}
|
|
12199
12734
|
|
|
12200
|
-
/**
|
|
12201
|
-
* Add an event to the event buffer.
|
|
12202
|
-
* `isCheckout` is true if this is either the very first event, or an event triggered by `checkoutEveryNms`.
|
|
12203
|
-
*/
|
|
12204
|
-
async function addEvent(
|
|
12205
|
-
replay,
|
|
12206
|
-
event,
|
|
12207
|
-
isCheckout,
|
|
12208
|
-
) {
|
|
12209
|
-
if (!replay.eventBuffer) {
|
|
12210
|
-
// This implies that `_isEnabled` is false
|
|
12211
|
-
return null;
|
|
12212
|
-
}
|
|
12213
|
-
|
|
12214
|
-
if (replay.isPaused()) {
|
|
12215
|
-
// Do not add to event buffer when recording is paused
|
|
12216
|
-
return null;
|
|
12217
|
-
}
|
|
12218
|
-
|
|
12219
|
-
// TODO: sadness -- we will want to normalize timestamps to be in ms -
|
|
12220
|
-
// requires coordination with frontend
|
|
12221
|
-
const isMs = event.timestamp > 9999999999;
|
|
12222
|
-
const timestampInMs = isMs ? event.timestamp : event.timestamp * 1000;
|
|
12223
|
-
|
|
12224
|
-
// Throw out events that happen more than 5 minutes ago. This can happen if
|
|
12225
|
-
// page has been left open and idle for a long period of time and user
|
|
12226
|
-
// comes back to trigger a new session. The performance entries rely on
|
|
12227
|
-
// `performance.timeOrigin`, which is when the page first opened.
|
|
12228
|
-
if (timestampInMs + replay.timeouts.sessionIdlePause < Date.now()) {
|
|
12229
|
-
return null;
|
|
12230
|
-
}
|
|
12231
|
-
|
|
12232
|
-
// Only record earliest event if a new session was created, otherwise it
|
|
12233
|
-
// shouldn't be relevant
|
|
12234
|
-
const earliestEvent = replay.getContext().earliestEvent;
|
|
12235
|
-
if (replay.session && replay.session.segmentId === 0 && (!earliestEvent || timestampInMs < earliestEvent)) {
|
|
12236
|
-
replay.getContext().earliestEvent = timestampInMs;
|
|
12237
|
-
}
|
|
12238
|
-
|
|
12239
|
-
try {
|
|
12240
|
-
return await replay.eventBuffer.addEvent(event, isCheckout);
|
|
12241
|
-
} catch (error) {
|
|
12242
|
-
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error(error);
|
|
12243
|
-
await replay.stop('addEvent');
|
|
12244
|
-
|
|
12245
|
-
const client = getCurrentHub().getClient();
|
|
12246
|
-
|
|
12247
|
-
if (client) {
|
|
12248
|
-
client.recordDroppedEvent('internal_sdk_error', 'replay');
|
|
12249
|
-
}
|
|
12250
|
-
}
|
|
12251
|
-
}
|
|
12252
|
-
|
|
12253
12735
|
/** If the event is an error event */
|
|
12254
12736
|
function isErrorEvent(event) {
|
|
12255
12737
|
return !event.type;
|
|
@@ -12299,22 +12781,18 @@ function handleAfterSendEvent(replay) {
|
|
|
12299
12781
|
return;
|
|
12300
12782
|
}
|
|
12301
12783
|
|
|
12302
|
-
// Add error to list of errorIds of replay
|
|
12784
|
+
// Add error to list of errorIds of replay. This is ok to do even if not
|
|
12785
|
+
// sampled because context will get reset at next checkout.
|
|
12786
|
+
// XXX: There is also a race condition where it's possible to capture an
|
|
12787
|
+
// error to Sentry before Replay SDK has loaded, but response returns after
|
|
12788
|
+
// it was loaded, and this gets called.
|
|
12303
12789
|
if (event.event_id) {
|
|
12304
12790
|
replay.getContext().errorIds.add(event.event_id);
|
|
12305
12791
|
}
|
|
12306
12792
|
|
|
12307
|
-
//
|
|
12793
|
+
// If error event is tagged with replay id it means it was sampled (when in buffer mode)
|
|
12308
12794
|
// Need to be very careful that this does not cause an infinite loop
|
|
12309
|
-
if (
|
|
12310
|
-
replay.recordingMode === 'buffer' &&
|
|
12311
|
-
event.exception &&
|
|
12312
|
-
event.message !== UNABLE_TO_SEND_REPLAY // ignore this error because otherwise we could loop indefinitely with trying to capture replay and failing
|
|
12313
|
-
) {
|
|
12314
|
-
if (!isSampled(replay.getOptions().errorSampleRate)) {
|
|
12315
|
-
return;
|
|
12316
|
-
}
|
|
12317
|
-
|
|
12795
|
+
if (replay.recordingMode === 'buffer' && event.tags && event.tags.replayId) {
|
|
12318
12796
|
setTimeout(() => {
|
|
12319
12797
|
// Capture current event buffer as new replay
|
|
12320
12798
|
void replay.sendBufferedReplayOrFlush();
|
|
@@ -12339,167 +12817,6 @@ function isBaseTransportSend() {
|
|
|
12339
12817
|
);
|
|
12340
12818
|
}
|
|
12341
12819
|
|
|
12342
|
-
var NodeType;
|
|
12343
|
-
(function (NodeType) {
|
|
12344
|
-
NodeType[NodeType["Document"] = 0] = "Document";
|
|
12345
|
-
NodeType[NodeType["DocumentType"] = 1] = "DocumentType";
|
|
12346
|
-
NodeType[NodeType["Element"] = 2] = "Element";
|
|
12347
|
-
NodeType[NodeType["Text"] = 3] = "Text";
|
|
12348
|
-
NodeType[NodeType["CDATA"] = 4] = "CDATA";
|
|
12349
|
-
NodeType[NodeType["Comment"] = 5] = "Comment";
|
|
12350
|
-
})(NodeType || (NodeType = {}));
|
|
12351
|
-
|
|
12352
|
-
/**
|
|
12353
|
-
* Create a breadcrumb for a replay.
|
|
12354
|
-
*/
|
|
12355
|
-
function createBreadcrumb(
|
|
12356
|
-
breadcrumb,
|
|
12357
|
-
) {
|
|
12358
|
-
return {
|
|
12359
|
-
timestamp: Date.now() / 1000,
|
|
12360
|
-
type: 'default',
|
|
12361
|
-
...breadcrumb,
|
|
12362
|
-
};
|
|
12363
|
-
}
|
|
12364
|
-
|
|
12365
|
-
/**
|
|
12366
|
-
* Add a breadcrumb event to replay.
|
|
12367
|
-
*/
|
|
12368
|
-
function addBreadcrumbEvent(replay, breadcrumb) {
|
|
12369
|
-
if (breadcrumb.category === 'sentry.transaction') {
|
|
12370
|
-
return;
|
|
12371
|
-
}
|
|
12372
|
-
|
|
12373
|
-
if (['ui.click', 'ui.input'].includes(breadcrumb.category )) {
|
|
12374
|
-
replay.triggerUserActivity();
|
|
12375
|
-
} else {
|
|
12376
|
-
replay.checkAndHandleExpiredSession();
|
|
12377
|
-
}
|
|
12378
|
-
|
|
12379
|
-
replay.addUpdate(() => {
|
|
12380
|
-
void addEvent(replay, {
|
|
12381
|
-
type: EventType.Custom,
|
|
12382
|
-
// TODO: We were converting from ms to seconds for breadcrumbs, spans,
|
|
12383
|
-
// but maybe we should just keep them as milliseconds
|
|
12384
|
-
timestamp: (breadcrumb.timestamp || 0) * 1000,
|
|
12385
|
-
data: {
|
|
12386
|
-
tag: 'breadcrumb',
|
|
12387
|
-
// normalize to max. 10 depth and 1_000 properties per object
|
|
12388
|
-
payload: normalize(breadcrumb, 10, 1000),
|
|
12389
|
-
},
|
|
12390
|
-
});
|
|
12391
|
-
|
|
12392
|
-
// Do not flush after console log messages
|
|
12393
|
-
return breadcrumb.category === 'console';
|
|
12394
|
-
});
|
|
12395
|
-
}
|
|
12396
|
-
|
|
12397
|
-
// Note that these are the serialized attributes and not attributes directly on
|
|
12398
|
-
// the DOM Node. Attributes we are interested in:
|
|
12399
|
-
const ATTRIBUTES_TO_RECORD = new Set([
|
|
12400
|
-
'id',
|
|
12401
|
-
'class',
|
|
12402
|
-
'aria-label',
|
|
12403
|
-
'role',
|
|
12404
|
-
'name',
|
|
12405
|
-
'alt',
|
|
12406
|
-
'title',
|
|
12407
|
-
'data-test-id',
|
|
12408
|
-
'data-testid',
|
|
12409
|
-
]);
|
|
12410
|
-
|
|
12411
|
-
/**
|
|
12412
|
-
* Inclusion list of attributes that we want to record from the DOM element
|
|
12413
|
-
*/
|
|
12414
|
-
function getAttributesToRecord(attributes) {
|
|
12415
|
-
const obj = {};
|
|
12416
|
-
for (const key in attributes) {
|
|
12417
|
-
if (ATTRIBUTES_TO_RECORD.has(key)) {
|
|
12418
|
-
let normalizedKey = key;
|
|
12419
|
-
|
|
12420
|
-
if (key === 'data-testid' || key === 'data-test-id') {
|
|
12421
|
-
normalizedKey = 'testId';
|
|
12422
|
-
}
|
|
12423
|
-
|
|
12424
|
-
obj[normalizedKey] = attributes[key];
|
|
12425
|
-
}
|
|
12426
|
-
}
|
|
12427
|
-
|
|
12428
|
-
return obj;
|
|
12429
|
-
}
|
|
12430
|
-
|
|
12431
|
-
const handleDomListener =
|
|
12432
|
-
(replay) =>
|
|
12433
|
-
(handlerData) => {
|
|
12434
|
-
if (!replay.isEnabled()) {
|
|
12435
|
-
return;
|
|
12436
|
-
}
|
|
12437
|
-
|
|
12438
|
-
const result = handleDom(handlerData);
|
|
12439
|
-
|
|
12440
|
-
if (!result) {
|
|
12441
|
-
return;
|
|
12442
|
-
}
|
|
12443
|
-
|
|
12444
|
-
addBreadcrumbEvent(replay, result);
|
|
12445
|
-
};
|
|
12446
|
-
|
|
12447
|
-
/**
|
|
12448
|
-
* An event handler to react to DOM events.
|
|
12449
|
-
*/
|
|
12450
|
-
function handleDom(handlerData) {
|
|
12451
|
-
let target;
|
|
12452
|
-
let targetNode;
|
|
12453
|
-
|
|
12454
|
-
// Accessing event.target can throw (see getsentry/raven-js#838, #768)
|
|
12455
|
-
try {
|
|
12456
|
-
targetNode = getTargetNode(handlerData);
|
|
12457
|
-
target = htmlTreeAsString(targetNode);
|
|
12458
|
-
} catch (e) {
|
|
12459
|
-
target = '<unknown>';
|
|
12460
|
-
}
|
|
12461
|
-
|
|
12462
|
-
// `__sn` property is the serialized node created by rrweb
|
|
12463
|
-
const serializedNode =
|
|
12464
|
-
targetNode && '__sn' in targetNode && targetNode.__sn.type === NodeType.Element ? targetNode.__sn : null;
|
|
12465
|
-
|
|
12466
|
-
return createBreadcrumb({
|
|
12467
|
-
category: `ui.${handlerData.name}`,
|
|
12468
|
-
message: target,
|
|
12469
|
-
data: serializedNode
|
|
12470
|
-
? {
|
|
12471
|
-
nodeId: serializedNode.id,
|
|
12472
|
-
node: {
|
|
12473
|
-
id: serializedNode.id,
|
|
12474
|
-
tagName: serializedNode.tagName,
|
|
12475
|
-
textContent: targetNode
|
|
12476
|
-
? Array.from(targetNode.childNodes)
|
|
12477
|
-
.map(
|
|
12478
|
-
(node) => '__sn' in node && node.__sn.type === NodeType.Text && node.__sn.textContent,
|
|
12479
|
-
)
|
|
12480
|
-
.filter(Boolean) // filter out empty values
|
|
12481
|
-
.map(text => (text ).trim())
|
|
12482
|
-
.join('')
|
|
12483
|
-
: '',
|
|
12484
|
-
attributes: getAttributesToRecord(serializedNode.attributes),
|
|
12485
|
-
},
|
|
12486
|
-
}
|
|
12487
|
-
: {},
|
|
12488
|
-
});
|
|
12489
|
-
}
|
|
12490
|
-
|
|
12491
|
-
function getTargetNode(handlerData) {
|
|
12492
|
-
if (isEventWithTarget(handlerData.event)) {
|
|
12493
|
-
return handlerData.event.target;
|
|
12494
|
-
}
|
|
12495
|
-
|
|
12496
|
-
return handlerData.event;
|
|
12497
|
-
}
|
|
12498
|
-
|
|
12499
|
-
function isEventWithTarget(event) {
|
|
12500
|
-
return !!(event ).target;
|
|
12501
|
-
}
|
|
12502
|
-
|
|
12503
12820
|
/**
|
|
12504
12821
|
* Returns true if we think the given event is an error originating inside of rrweb.
|
|
12505
12822
|
*/
|
|
@@ -12523,6 +12840,30 @@ function isRrwebError(event, hint) {
|
|
|
12523
12840
|
});
|
|
12524
12841
|
}
|
|
12525
12842
|
|
|
12843
|
+
/**
|
|
12844
|
+
* Determine if event should be sampled (only applies in buffer mode).
|
|
12845
|
+
* When an event is captured by `hanldleGlobalEvent`, when in buffer mode
|
|
12846
|
+
* we determine if we want to sample the error or not.
|
|
12847
|
+
*/
|
|
12848
|
+
function shouldSampleForBufferEvent(replay, event) {
|
|
12849
|
+
if (replay.recordingMode !== 'buffer') {
|
|
12850
|
+
return false;
|
|
12851
|
+
}
|
|
12852
|
+
|
|
12853
|
+
// ignore this error because otherwise we could loop indefinitely with
|
|
12854
|
+
// trying to capture replay and failing
|
|
12855
|
+
if (event.message === UNABLE_TO_SEND_REPLAY) {
|
|
12856
|
+
return false;
|
|
12857
|
+
}
|
|
12858
|
+
|
|
12859
|
+
// Require the event to be an error event & to have an exception
|
|
12860
|
+
if (!event.exception || event.type) {
|
|
12861
|
+
return false;
|
|
12862
|
+
}
|
|
12863
|
+
|
|
12864
|
+
return isSampled(replay.getOptions().errorSampleRate);
|
|
12865
|
+
}
|
|
12866
|
+
|
|
12526
12867
|
/**
|
|
12527
12868
|
* Returns a listener to be added to `addGlobalEventProcessor(listener)`.
|
|
12528
12869
|
*/
|
|
@@ -12552,8 +12893,16 @@ function handleGlobalEventListener(
|
|
|
12552
12893
|
return null;
|
|
12553
12894
|
}
|
|
12554
12895
|
|
|
12555
|
-
//
|
|
12556
|
-
if
|
|
12896
|
+
// When in buffer mode, we decide to sample here.
|
|
12897
|
+
// Later, in `handleAfterSendEvent`, if the replayId is set, we know that we sampled
|
|
12898
|
+
// And convert the buffer session to a full session
|
|
12899
|
+
const isErrorEventSampled = shouldSampleForBufferEvent(replay, event);
|
|
12900
|
+
|
|
12901
|
+
// Tag errors if it has been sampled in buffer mode, or if it is session mode
|
|
12902
|
+
// Only tag transactions if in session mode
|
|
12903
|
+
const shouldTagReplayId = isErrorEventSampled || replay.recordingMode === 'session';
|
|
12904
|
+
|
|
12905
|
+
if (shouldTagReplayId) {
|
|
12557
12906
|
event.tags = { ...event.tags, replayId: replay.getSessionId() };
|
|
12558
12907
|
}
|
|
12559
12908
|
|
|
@@ -12603,7 +12952,7 @@ function createPerformanceSpans(
|
|
|
12603
12952
|
) {
|
|
12604
12953
|
return entries.map(({ type, start, end, name, data }) =>
|
|
12605
12954
|
addEvent(replay, {
|
|
12606
|
-
type: EventType.Custom,
|
|
12955
|
+
type: EventType$1.Custom,
|
|
12607
12956
|
timestamp: start,
|
|
12608
12957
|
data: {
|
|
12609
12958
|
tag: 'performanceSpan',
|
|
@@ -13385,7 +13734,32 @@ function _strIsProbablyJson(str) {
|
|
|
13385
13734
|
|
|
13386
13735
|
/** Match an URL against a list of strings/Regex. */
|
|
13387
13736
|
function urlMatches(url, urls) {
|
|
13388
|
-
|
|
13737
|
+
const fullUrl = getFullUrl(url);
|
|
13738
|
+
|
|
13739
|
+
return stringMatchesSomePattern(fullUrl, urls);
|
|
13740
|
+
}
|
|
13741
|
+
|
|
13742
|
+
/** exported for tests */
|
|
13743
|
+
function getFullUrl(url, baseURI = WINDOW.document.baseURI) {
|
|
13744
|
+
// Short circuit for common cases:
|
|
13745
|
+
if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith(WINDOW.location.origin)) {
|
|
13746
|
+
return url;
|
|
13747
|
+
}
|
|
13748
|
+
const fixedUrl = new URL(url, baseURI);
|
|
13749
|
+
|
|
13750
|
+
// If these do not match, we are not dealing with a relative URL, so just return it
|
|
13751
|
+
if (fixedUrl.origin !== new URL(baseURI).origin) {
|
|
13752
|
+
return url;
|
|
13753
|
+
}
|
|
13754
|
+
|
|
13755
|
+
const fullUrl = fixedUrl.href;
|
|
13756
|
+
|
|
13757
|
+
// Remove trailing slashes, if they don't match the original URL
|
|
13758
|
+
if (!url.endsWith('/') && fullUrl.endsWith('/')) {
|
|
13759
|
+
return fullUrl.slice(0, -1);
|
|
13760
|
+
}
|
|
13761
|
+
|
|
13762
|
+
return fullUrl;
|
|
13389
13763
|
}
|
|
13390
13764
|
|
|
13391
13765
|
/**
|
|
@@ -13935,7 +14309,8 @@ function addGlobalListeners(replay) {
|
|
|
13935
14309
|
client.on('afterSendEvent', handleAfterSendEvent(replay));
|
|
13936
14310
|
client.on('createDsc', (dsc) => {
|
|
13937
14311
|
const replayId = replay.getSessionId();
|
|
13938
|
-
|
|
14312
|
+
// We do not want to set the DSC when in buffer mode, as that means the replay has not been sent (yet)
|
|
14313
|
+
if (replayId && replay.isEnabled() && replay.recordingMode === 'session') {
|
|
13939
14314
|
dsc.replay_id = replayId;
|
|
13940
14315
|
}
|
|
13941
14316
|
});
|
|
@@ -14215,6 +14590,23 @@ function debounce(func, wait, options) {
|
|
|
14215
14590
|
return debounced;
|
|
14216
14591
|
}
|
|
14217
14592
|
|
|
14593
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
14594
|
+
|
|
14595
|
+
var EventType; (function (EventType) {
|
|
14596
|
+
const DomContentLoaded = 0; EventType[EventType["DomContentLoaded"] = DomContentLoaded] = "DomContentLoaded";
|
|
14597
|
+
const Load = 1; EventType[EventType["Load"] = Load] = "Load";
|
|
14598
|
+
const FullSnapshot = 2; EventType[EventType["FullSnapshot"] = FullSnapshot] = "FullSnapshot";
|
|
14599
|
+
const IncrementalSnapshot = 3; EventType[EventType["IncrementalSnapshot"] = IncrementalSnapshot] = "IncrementalSnapshot";
|
|
14600
|
+
const Meta = 4; EventType[EventType["Meta"] = Meta] = "Meta";
|
|
14601
|
+
const Custom = 5; EventType[EventType["Custom"] = Custom] = "Custom";
|
|
14602
|
+
const Plugin = 6; EventType[EventType["Plugin"] = Plugin] = "Plugin";
|
|
14603
|
+
})(EventType || (EventType = {}));
|
|
14604
|
+
|
|
14605
|
+
/**
|
|
14606
|
+
* This is a partial copy of rrweb's eventWithTime type which only contains the properties
|
|
14607
|
+
* we specifcally need in the SDK.
|
|
14608
|
+
*/
|
|
14609
|
+
|
|
14218
14610
|
/**
|
|
14219
14611
|
* Handler for recording events.
|
|
14220
14612
|
*
|
|
@@ -14257,6 +14649,14 @@ function getHandleRecordingEmit(replay) {
|
|
|
14257
14649
|
return false;
|
|
14258
14650
|
}
|
|
14259
14651
|
|
|
14652
|
+
// Additionally, create a meta event that will capture certain SDK settings.
|
|
14653
|
+
// In order to handle buffer mode, this needs to either be done when we
|
|
14654
|
+
// receive checkout events or at flush time.
|
|
14655
|
+
//
|
|
14656
|
+
// `isCheckout` is always true, but want to be explicit that it should
|
|
14657
|
+
// only be added for checkouts
|
|
14658
|
+
void addSettingsEvent(replay, isCheckout);
|
|
14659
|
+
|
|
14260
14660
|
// If there is a previousSessionId after a full snapshot occurs, then
|
|
14261
14661
|
// the replay session was started due to session expiration. The new session
|
|
14262
14662
|
// is started before triggering a new checkout and contains the id
|
|
@@ -14267,10 +14667,10 @@ function getHandleRecordingEmit(replay) {
|
|
|
14267
14667
|
return true;
|
|
14268
14668
|
}
|
|
14269
14669
|
|
|
14270
|
-
//
|
|
14271
|
-
// checkout
|
|
14272
|
-
if (replay.recordingMode === 'buffer' && replay.session) {
|
|
14273
|
-
const
|
|
14670
|
+
// When in buffer mode, make sure we adjust the session started date to the current earliest event of the buffer
|
|
14671
|
+
// this should usually be the timestamp of the checkout event, but to be safe...
|
|
14672
|
+
if (replay.recordingMode === 'buffer' && replay.session && replay.eventBuffer) {
|
|
14673
|
+
const earliestEvent = replay.eventBuffer.getEarliestTimestamp();
|
|
14274
14674
|
if (earliestEvent) {
|
|
14275
14675
|
replay.session.started = earliestEvent;
|
|
14276
14676
|
|
|
@@ -14294,6 +14694,46 @@ function getHandleRecordingEmit(replay) {
|
|
|
14294
14694
|
};
|
|
14295
14695
|
}
|
|
14296
14696
|
|
|
14697
|
+
/**
|
|
14698
|
+
* Exported for tests
|
|
14699
|
+
*/
|
|
14700
|
+
function createOptionsEvent(replay) {
|
|
14701
|
+
const options = replay.getOptions();
|
|
14702
|
+
return {
|
|
14703
|
+
type: EventType.Custom,
|
|
14704
|
+
timestamp: Date.now(),
|
|
14705
|
+
data: {
|
|
14706
|
+
tag: 'options',
|
|
14707
|
+
payload: {
|
|
14708
|
+
sessionSampleRate: options.sessionSampleRate,
|
|
14709
|
+
errorSampleRate: options.errorSampleRate,
|
|
14710
|
+
useCompressionOption: options.useCompression,
|
|
14711
|
+
blockAllMedia: options.blockAllMedia,
|
|
14712
|
+
maskAllText: options.maskAllText,
|
|
14713
|
+
maskAllInputs: options.maskAllInputs,
|
|
14714
|
+
useCompression: replay.eventBuffer ? replay.eventBuffer.type === 'worker' : false,
|
|
14715
|
+
networkDetailHasUrls: options.networkDetailAllowUrls.length > 0,
|
|
14716
|
+
networkCaptureBodies: options.networkCaptureBodies,
|
|
14717
|
+
networkRequestHasHeaders: options.networkRequestHeaders.length > 0,
|
|
14718
|
+
networkResponseHasHeaders: options.networkResponseHeaders.length > 0,
|
|
14719
|
+
},
|
|
14720
|
+
},
|
|
14721
|
+
};
|
|
14722
|
+
}
|
|
14723
|
+
|
|
14724
|
+
/**
|
|
14725
|
+
* Add a "meta" event that contains a simplified view on current configuration
|
|
14726
|
+
* options. This should only be included on the first segment of a recording.
|
|
14727
|
+
*/
|
|
14728
|
+
function addSettingsEvent(replay, isCheckout) {
|
|
14729
|
+
// Only need to add this event when sending the first segment
|
|
14730
|
+
if (!isCheckout || !replay.session || replay.session.segmentId !== 0) {
|
|
14731
|
+
return Promise.resolve(null);
|
|
14732
|
+
}
|
|
14733
|
+
|
|
14734
|
+
return addEvent(replay, createOptionsEvent(replay), false);
|
|
14735
|
+
}
|
|
14736
|
+
|
|
14297
14737
|
/**
|
|
14298
14738
|
* Create a replay envelope ready to be sent.
|
|
14299
14739
|
* This includes both the replay event, as well as the recording data.
|
|
@@ -14408,7 +14848,6 @@ async function sendReplayRequest({
|
|
|
14408
14848
|
eventContext,
|
|
14409
14849
|
timestamp,
|
|
14410
14850
|
session,
|
|
14411
|
-
options,
|
|
14412
14851
|
}) {
|
|
14413
14852
|
const preparedRecordingData = prepareRecordingData({
|
|
14414
14853
|
recordingData,
|
|
@@ -14450,15 +14889,6 @@ async function sendReplayRequest({
|
|
|
14450
14889
|
return;
|
|
14451
14890
|
}
|
|
14452
14891
|
|
|
14453
|
-
replayEvent.contexts = {
|
|
14454
|
-
...replayEvent.contexts,
|
|
14455
|
-
replay: {
|
|
14456
|
-
...(replayEvent.contexts && replayEvent.contexts.replay),
|
|
14457
|
-
session_sample_rate: options.sessionSampleRate,
|
|
14458
|
-
error_sample_rate: options.errorSampleRate,
|
|
14459
|
-
},
|
|
14460
|
-
};
|
|
14461
|
-
|
|
14462
14892
|
/*
|
|
14463
14893
|
For reference, the fully built event looks something like this:
|
|
14464
14894
|
{
|
|
@@ -14489,10 +14919,6 @@ async function sendReplayRequest({
|
|
|
14489
14919
|
},
|
|
14490
14920
|
"sdkProcessingMetadata": {},
|
|
14491
14921
|
"contexts": {
|
|
14492
|
-
"replay": {
|
|
14493
|
-
"session_sample_rate": 1,
|
|
14494
|
-
"error_sample_rate": 0,
|
|
14495
|
-
},
|
|
14496
14922
|
},
|
|
14497
14923
|
}
|
|
14498
14924
|
*/
|
|
@@ -14676,7 +15102,6 @@ class ReplayContainer {
|
|
|
14676
15102
|
errorIds: new Set(),
|
|
14677
15103
|
traceIds: new Set(),
|
|
14678
15104
|
urls: [],
|
|
14679
|
-
earliestEvent: null,
|
|
14680
15105
|
initialTimestamp: Date.now(),
|
|
14681
15106
|
initialUrl: '',
|
|
14682
15107
|
};}
|
|
@@ -14686,7 +15111,7 @@ class ReplayContainer {
|
|
|
14686
15111
|
recordingOptions,
|
|
14687
15112
|
}
|
|
14688
15113
|
|
|
14689
|
-
) {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);
|
|
15114
|
+
) {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);
|
|
14690
15115
|
this._recordingOptions = recordingOptions;
|
|
14691
15116
|
this._options = options;
|
|
14692
15117
|
|
|
@@ -15178,6 +15603,7 @@ class ReplayContainer {
|
|
|
15178
15603
|
WINDOW.document.addEventListener('visibilitychange', this._handleVisibilityChange);
|
|
15179
15604
|
WINDOW.addEventListener('blur', this._handleWindowBlur);
|
|
15180
15605
|
WINDOW.addEventListener('focus', this._handleWindowFocus);
|
|
15606
|
+
WINDOW.addEventListener('keydown', this._handleKeyboardEvent);
|
|
15181
15607
|
|
|
15182
15608
|
// There is no way to remove these listeners, so ensure they are only added once
|
|
15183
15609
|
if (!this._hasInitializedCoreListeners) {
|
|
@@ -15206,6 +15632,7 @@ class ReplayContainer {
|
|
|
15206
15632
|
|
|
15207
15633
|
WINDOW.removeEventListener('blur', this._handleWindowBlur);
|
|
15208
15634
|
WINDOW.removeEventListener('focus', this._handleWindowFocus);
|
|
15635
|
+
WINDOW.removeEventListener('keydown', this._handleKeyboardEvent);
|
|
15209
15636
|
|
|
15210
15637
|
if (this._performanceObserver) {
|
|
15211
15638
|
this._performanceObserver.disconnect();
|
|
@@ -15256,6 +15683,11 @@ class ReplayContainer {
|
|
|
15256
15683
|
this._doChangeToForegroundTasks(breadcrumb);
|
|
15257
15684
|
};}
|
|
15258
15685
|
|
|
15686
|
+
/** Ensure page remains active when a key is pressed. */
|
|
15687
|
+
__init16() {this._handleKeyboardEvent = (event) => {
|
|
15688
|
+
handleKeyboardEvent(this, event);
|
|
15689
|
+
};}
|
|
15690
|
+
|
|
15259
15691
|
/**
|
|
15260
15692
|
* Tasks to run when we consider a page to be hidden (via blurring and/or visibility)
|
|
15261
15693
|
*/
|
|
@@ -15335,7 +15767,7 @@ class ReplayContainer {
|
|
|
15335
15767
|
_createCustomBreadcrumb(breadcrumb) {
|
|
15336
15768
|
this.addUpdate(() => {
|
|
15337
15769
|
void addEvent(this, {
|
|
15338
|
-
type: EventType.Custom,
|
|
15770
|
+
type: EventType$1.Custom,
|
|
15339
15771
|
timestamp: breadcrumb.timestamp || 0,
|
|
15340
15772
|
data: {
|
|
15341
15773
|
tag: 'breadcrumb',
|
|
@@ -15376,22 +15808,35 @@ class ReplayContainer {
|
|
|
15376
15808
|
this._context.errorIds.clear();
|
|
15377
15809
|
this._context.traceIds.clear();
|
|
15378
15810
|
this._context.urls = [];
|
|
15379
|
-
|
|
15811
|
+
}
|
|
15812
|
+
|
|
15813
|
+
/** Update the initial timestamp based on the buffer content. */
|
|
15814
|
+
_updateInitialTimestampFromEventBuffer() {
|
|
15815
|
+
const { session, eventBuffer } = this;
|
|
15816
|
+
if (!session || !eventBuffer) {
|
|
15817
|
+
return;
|
|
15818
|
+
}
|
|
15819
|
+
|
|
15820
|
+
// we only ever update this on the initial segment
|
|
15821
|
+
if (session.segmentId) {
|
|
15822
|
+
return;
|
|
15823
|
+
}
|
|
15824
|
+
|
|
15825
|
+
const earliestEvent = eventBuffer.getEarliestTimestamp();
|
|
15826
|
+
if (earliestEvent && earliestEvent < this._context.initialTimestamp) {
|
|
15827
|
+
this._context.initialTimestamp = earliestEvent;
|
|
15828
|
+
}
|
|
15380
15829
|
}
|
|
15381
15830
|
|
|
15382
15831
|
/**
|
|
15383
15832
|
* Return and clear _context
|
|
15384
15833
|
*/
|
|
15385
15834
|
_popEventContext() {
|
|
15386
|
-
if (this._context.earliestEvent && this._context.earliestEvent < this._context.initialTimestamp) {
|
|
15387
|
-
this._context.initialTimestamp = this._context.earliestEvent;
|
|
15388
|
-
}
|
|
15389
|
-
|
|
15390
15835
|
const _context = {
|
|
15391
15836
|
initialTimestamp: this._context.initialTimestamp,
|
|
15392
15837
|
initialUrl: this._context.initialUrl,
|
|
15393
|
-
errorIds: Array.from(this._context.errorIds)
|
|
15394
|
-
traceIds: Array.from(this._context.traceIds)
|
|
15838
|
+
errorIds: Array.from(this._context.errorIds),
|
|
15839
|
+
traceIds: Array.from(this._context.traceIds),
|
|
15395
15840
|
urls: this._context.urls,
|
|
15396
15841
|
};
|
|
15397
15842
|
|
|
@@ -15430,6 +15875,9 @@ class ReplayContainer {
|
|
|
15430
15875
|
}
|
|
15431
15876
|
|
|
15432
15877
|
try {
|
|
15878
|
+
// This uses the data from the eventBuffer, so we need to call this before `finish()
|
|
15879
|
+
this._updateInitialTimestampFromEventBuffer();
|
|
15880
|
+
|
|
15433
15881
|
// Note this empties the event buffer regardless of outcome of sending replay
|
|
15434
15882
|
const recordingData = await this.eventBuffer.finish();
|
|
15435
15883
|
|
|
@@ -15470,7 +15918,7 @@ class ReplayContainer {
|
|
|
15470
15918
|
* Flush recording data to Sentry. Creates a lock so that only a single flush
|
|
15471
15919
|
* can be active at a time. Do not call this directly.
|
|
15472
15920
|
*/
|
|
15473
|
-
|
|
15921
|
+
__init17() {this._flush = async ({
|
|
15474
15922
|
force = false,
|
|
15475
15923
|
}
|
|
15476
15924
|
|
|
@@ -15525,7 +15973,7 @@ class ReplayContainer {
|
|
|
15525
15973
|
}
|
|
15526
15974
|
|
|
15527
15975
|
/** Handler for rrweb.record.onMutation */
|
|
15528
|
-
|
|
15976
|
+
__init18() {this._onMutationHandler = (mutations) => {
|
|
15529
15977
|
const count = mutations.length;
|
|
15530
15978
|
|
|
15531
15979
|
const mutationLimit = this._options._experiments.mutationLimit || 0;
|
|
@@ -15764,6 +16212,8 @@ class Replay {
|
|
|
15764
16212
|
errorSampleRate,
|
|
15765
16213
|
useCompression,
|
|
15766
16214
|
blockAllMedia,
|
|
16215
|
+
maskAllInputs,
|
|
16216
|
+
maskAllText,
|
|
15767
16217
|
networkDetailAllowUrls,
|
|
15768
16218
|
networkCaptureBodies,
|
|
15769
16219
|
networkRequestHeaders: _getMergedNetworkHeaders(networkRequestHeaders),
|
|
@@ -24991,6 +25441,7 @@ var ACTIONS$7;
|
|
|
24991
25441
|
ACTIONS["SET_MEDIA_RECORDER"] = "SET_MEDIA_RECORDER";
|
|
24992
25442
|
ACTIONS["SET_MIC_ERROR"] = "SET_MIC_ERROR";
|
|
24993
25443
|
ACTIONS["SET_PERMISSION_LISTENER"] = "SET_PERMISSION_LISTENER";
|
|
25444
|
+
ACTIONS["SET_ACTIVELY_STOPPED"] = "SET_ACTIVELY_STOPPED";
|
|
24994
25445
|
})(ACTIONS$7 || (ACTIONS$7 = {}));
|
|
24995
25446
|
var EVENTS$6;
|
|
24996
25447
|
(function (EVENTS) {
|
|
@@ -43541,6 +43992,7 @@ const initialState = {
|
|
|
43541
43992
|
isSafeMode: false,
|
|
43542
43993
|
isAutoStart: false,
|
|
43543
43994
|
isMicError: false,
|
|
43995
|
+
isActivelyStopped: false,
|
|
43544
43996
|
};
|
|
43545
43997
|
const recorderMachineV2 = createMachine({
|
|
43546
43998
|
predictableActionArguments: true,
|
|
@@ -43759,6 +44211,7 @@ const recorderMachineV2 = createMachine({
|
|
|
43759
44211
|
on: {
|
|
43760
44212
|
[EVENTS$6.STOP_RECORDING]: {
|
|
43761
44213
|
actions: [
|
|
44214
|
+
ACTIONS$7.SET_ACTIVELY_STOPPED,
|
|
43762
44215
|
ACTIONS$7.STOP_COUNT_DOWN_ACTOR,
|
|
43763
44216
|
ACTIONS$7.STOP_MEDIA_RECORDER,
|
|
43764
44217
|
ACTIONS$7.STOP_MICROPHONE_MACHINE,
|
|
@@ -43971,7 +44424,7 @@ const recorderMachineV2 = createMachine({
|
|
|
43971
44424
|
webm = new File([blob], Date.now().toString(), {
|
|
43972
44425
|
type: mimeType,
|
|
43973
44426
|
});
|
|
43974
|
-
callback({ type:
|
|
44427
|
+
callback({ type: EVENTS$6.SEND_CURRENT_TAKE, data: { webm, videoLength } });
|
|
43975
44428
|
})
|
|
43976
44429
|
.catch((error) => {
|
|
43977
44430
|
console.error('fixVideoMetadata', error);
|
|
@@ -44020,6 +44473,7 @@ const recorderMachineV2 = createMachine({
|
|
|
44020
44473
|
[ACTIONS$7.INIT_COUNT_DOWN_ACTOR]: assign$2({
|
|
44021
44474
|
countDownRef: (context, event, meta) => spawn(counterMachine.withContext(Object.assign(Object.assign({}, counterMachine.context), { callback: meta.action.data.callback })), { name: 'countDownRef' }),
|
|
44022
44475
|
}),
|
|
44476
|
+
[ACTIONS$7.SET_ACTIVELY_STOPPED]: assign$2((context) => ({ isActivelyStopped: true })),
|
|
44023
44477
|
[ACTIONS$7.STOP_COUNT_DOWN_ACTOR]: assign$2((context) => {
|
|
44024
44478
|
context.countDownRef.stop();
|
|
44025
44479
|
return {
|
|
@@ -44090,7 +44544,7 @@ const recorderMachineV2 = createMachine({
|
|
|
44090
44544
|
// send success event to the parent, to be able to proceed to the next step
|
|
44091
44545
|
[ACTIONS$7.SEND_SUCCESS_TO_PARENT]: sendParent$1((context, event) => (Object.assign(Object.assign({}, event), { type: EVENTS$5.READY_TO_START_RECORDING }))),
|
|
44092
44546
|
// on stop recording send webm file to storage machine
|
|
44093
|
-
[ACTIONS$7.SEND_CURRENT_TAKE]: sendParent$1((
|
|
44547
|
+
[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 }) }))),
|
|
44094
44548
|
// send null param to mic machine to be able to stop sound detecting
|
|
44095
44549
|
[ACTIONS$7.STOP_MICROPHONE_MACHINE]: send$2((_, _event) => ({ type: EVENTS$3.ON_SET_MEDIA_STREAM, data: null }), { to: (context) => context.microphoneRef }),
|
|
44096
44550
|
// clean mic error, this is really sensitive, !! a lot of user can't start recording coz of it
|
|
@@ -44619,7 +45073,7 @@ const MAPPED_EVENT_TYPES = {
|
|
|
44619
45073
|
PRACTICE: EVENT_TYPES.TIMES_UP,
|
|
44620
45074
|
},
|
|
44621
45075
|
};
|
|
44622
|
-
const questionEventFormatter = (questionActionType) => pure_1(({ recordingType, questions, currentQuestion, widgetConfig, currentTake }) => {
|
|
45076
|
+
const questionEventFormatter = (questionActionType) => pure_1(({ recordingType, questions, currentQuestion, widgetConfig, currentTake }, event) => {
|
|
44623
45077
|
var _a, _b;
|
|
44624
45078
|
const isQuestionMode = recordingType === TAKE_TYPES.QUESTION;
|
|
44625
45079
|
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;
|
|
@@ -44627,10 +45081,11 @@ const questionEventFormatter = (questionActionType) => pure_1(({ recordingType,
|
|
|
44627
45081
|
const isAssessment = currentQuestionObj.answerType && currentQuestionObj.answerType !== ANSWER_TYPES.VIDEO;
|
|
44628
45082
|
return {
|
|
44629
45083
|
type: ACTIONS$6.EMIT_TRACKING_EVENT,
|
|
44630
|
-
data: Object.assign({ eventType: isQuestionMode ? MAPPED_EVENT_TYPES[questionActionType].QUESTION : MAPPED_EVENT_TYPES[questionActionType].PRACTICE }, (currentQuestionFile && { extraData: Object.assign(Object.assign(
|
|
45084
|
+
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 && {
|
|
44631
45085
|
currentTake,
|
|
44632
45086
|
numberOfTakes: currentQuestionObj.numOfRetakes,
|
|
44633
|
-
|
|
45087
|
+
isActivelyStopped: (event.data || {}).isActivelyStopped,
|
|
45088
|
+
})), { questionType: currentQuestionObj.videoQuestion ? 'video' : 'text', answerType: currentQuestionObj.answerType || ANSWER_TYPES.VIDEO }) })),
|
|
44634
45089
|
};
|
|
44635
45090
|
});
|
|
44636
45091
|
const accWidgetMachine = createMachine({
|