@sentry/replay 7.52.0 → 7.52.1
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/cjs/index.js +163 -3
- package/cjs/index.js.map +1 -1
- package/esm/index.js +163 -3
- package/esm/index.js.map +1 -1
- package/package.json +5 -5
- package/types/coreHandlers/handleDom.d.ts.map +1 -1
- package/types/coreHandlers/handleSlowClick.d.ts +12 -0
- package/types/coreHandlers/handleSlowClick.d.ts.map +1 -0
- package/types/types.d.ts +12 -0
- package/types/types.d.ts.map +1 -1
package/cjs/index.js
CHANGED
|
@@ -3374,6 +3374,141 @@ function addBreadcrumbEvent(replay, breadcrumb) {
|
|
|
3374
3374
|
});
|
|
3375
3375
|
}
|
|
3376
3376
|
|
|
3377
|
+
/**
|
|
3378
|
+
* Detect a slow click on a button/a tag,
|
|
3379
|
+
* and potentially create a corresponding breadcrumb.
|
|
3380
|
+
*/
|
|
3381
|
+
function detectSlowClick(
|
|
3382
|
+
replay,
|
|
3383
|
+
config,
|
|
3384
|
+
clickBreadcrumb,
|
|
3385
|
+
node,
|
|
3386
|
+
) {
|
|
3387
|
+
if (ignoreElement(node, config)) {
|
|
3388
|
+
return;
|
|
3389
|
+
}
|
|
3390
|
+
|
|
3391
|
+
/*
|
|
3392
|
+
We consider a slow click a click on a button/a, which does not trigger one of:
|
|
3393
|
+
- DOM mutation
|
|
3394
|
+
- Scroll (within 100ms)
|
|
3395
|
+
Within the given threshold time.
|
|
3396
|
+
After time timeout time, we stop listening and mark it as a slow click anyhow.
|
|
3397
|
+
*/
|
|
3398
|
+
|
|
3399
|
+
let cleanup = () => {
|
|
3400
|
+
// replaced further down
|
|
3401
|
+
};
|
|
3402
|
+
|
|
3403
|
+
// After timeout time, def. consider this a slow click, and stop watching for mutations
|
|
3404
|
+
const timeout = setTimeout(() => {
|
|
3405
|
+
handleSlowClick(replay, clickBreadcrumb, config.timeout, 'timeout');
|
|
3406
|
+
cleanup();
|
|
3407
|
+
}, config.timeout);
|
|
3408
|
+
|
|
3409
|
+
const mutationHandler = () => {
|
|
3410
|
+
maybeHandleSlowClick(replay, clickBreadcrumb, config.threshold, config.timeout, 'mutation');
|
|
3411
|
+
cleanup();
|
|
3412
|
+
};
|
|
3413
|
+
|
|
3414
|
+
const scrollHandler = () => {
|
|
3415
|
+
maybeHandleSlowClick(replay, clickBreadcrumb, config.scrollTimeout, config.timeout, 'scroll');
|
|
3416
|
+
cleanup();
|
|
3417
|
+
};
|
|
3418
|
+
|
|
3419
|
+
const obs = new MutationObserver(mutationHandler);
|
|
3420
|
+
|
|
3421
|
+
obs.observe(WINDOW.document.documentElement, {
|
|
3422
|
+
attributes: true,
|
|
3423
|
+
characterData: true,
|
|
3424
|
+
childList: true,
|
|
3425
|
+
subtree: true,
|
|
3426
|
+
});
|
|
3427
|
+
|
|
3428
|
+
WINDOW.addEventListener('scroll', scrollHandler);
|
|
3429
|
+
|
|
3430
|
+
// Stop listening to scroll timeouts early
|
|
3431
|
+
const scrollTimeout = setTimeout(() => {
|
|
3432
|
+
WINDOW.removeEventListener('scroll', scrollHandler);
|
|
3433
|
+
}, config.scrollTimeout);
|
|
3434
|
+
|
|
3435
|
+
cleanup = () => {
|
|
3436
|
+
clearTimeout(timeout);
|
|
3437
|
+
clearTimeout(scrollTimeout);
|
|
3438
|
+
obs.disconnect();
|
|
3439
|
+
WINDOW.removeEventListener('scroll', scrollHandler);
|
|
3440
|
+
};
|
|
3441
|
+
}
|
|
3442
|
+
|
|
3443
|
+
function maybeHandleSlowClick(
|
|
3444
|
+
replay,
|
|
3445
|
+
clickBreadcrumb,
|
|
3446
|
+
threshold,
|
|
3447
|
+
timeout,
|
|
3448
|
+
endReason,
|
|
3449
|
+
) {
|
|
3450
|
+
const now = Date.now();
|
|
3451
|
+
const timeAfterClickMs = now - clickBreadcrumb.timestamp * 1000;
|
|
3452
|
+
|
|
3453
|
+
if (timeAfterClickMs > threshold) {
|
|
3454
|
+
handleSlowClick(replay, clickBreadcrumb, Math.min(timeAfterClickMs, timeout), endReason);
|
|
3455
|
+
return true;
|
|
3456
|
+
}
|
|
3457
|
+
|
|
3458
|
+
return false;
|
|
3459
|
+
}
|
|
3460
|
+
|
|
3461
|
+
function handleSlowClick(
|
|
3462
|
+
replay,
|
|
3463
|
+
clickBreadcrumb,
|
|
3464
|
+
timeAfterClickMs,
|
|
3465
|
+
endReason,
|
|
3466
|
+
) {
|
|
3467
|
+
const breadcrumb = {
|
|
3468
|
+
message: clickBreadcrumb.message,
|
|
3469
|
+
timestamp: clickBreadcrumb.timestamp,
|
|
3470
|
+
category: 'ui.slowClickDetected',
|
|
3471
|
+
data: {
|
|
3472
|
+
...clickBreadcrumb.data,
|
|
3473
|
+
url: WINDOW.location.href,
|
|
3474
|
+
// TODO FN: add parametrized route, when possible
|
|
3475
|
+
timeAfterClickMs,
|
|
3476
|
+
endReason,
|
|
3477
|
+
},
|
|
3478
|
+
};
|
|
3479
|
+
|
|
3480
|
+
addBreadcrumbEvent(replay, breadcrumb);
|
|
3481
|
+
}
|
|
3482
|
+
|
|
3483
|
+
const SLOW_CLICK_IGNORE_TAGS = ['SELECT', 'OPTION'];
|
|
3484
|
+
|
|
3485
|
+
function ignoreElement(node, config) {
|
|
3486
|
+
// If <input> tag, we only want to consider input[type='submit'] & input[type='button']
|
|
3487
|
+
if (node.tagName === 'INPUT' && !['submit', 'button'].includes(node.getAttribute('type') || '')) {
|
|
3488
|
+
return true;
|
|
3489
|
+
}
|
|
3490
|
+
|
|
3491
|
+
if (SLOW_CLICK_IGNORE_TAGS.includes(node.tagName)) {
|
|
3492
|
+
return true;
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
// If <a> tag, detect special variants that may not lead to an action
|
|
3496
|
+
// If target !== _self, we may open the link somewhere else, which would lead to no action
|
|
3497
|
+
// Also, when downloading a file, we may not leave the page, but still not trigger an action
|
|
3498
|
+
if (
|
|
3499
|
+
node.tagName === 'A' &&
|
|
3500
|
+
(node.hasAttribute('download') || (node.hasAttribute('target') && node.getAttribute('target') !== '_self'))
|
|
3501
|
+
) {
|
|
3502
|
+
return true;
|
|
3503
|
+
}
|
|
3504
|
+
|
|
3505
|
+
if (config.ignoreSelector && node.matches(config.ignoreSelector)) {
|
|
3506
|
+
return true;
|
|
3507
|
+
}
|
|
3508
|
+
|
|
3509
|
+
return false;
|
|
3510
|
+
}
|
|
3511
|
+
|
|
3377
3512
|
// Note that these are the serialized attributes and not attributes directly on
|
|
3378
3513
|
// the DOM Node. Attributes we are interested in:
|
|
3379
3514
|
const ATTRIBUTES_TO_RECORD = new Set([
|
|
@@ -3408,9 +3543,21 @@ function getAttributesToRecord(attributes) {
|
|
|
3408
3543
|
return obj;
|
|
3409
3544
|
}
|
|
3410
3545
|
|
|
3411
|
-
const handleDomListener =
|
|
3412
|
-
|
|
3413
|
-
|
|
3546
|
+
const handleDomListener = (
|
|
3547
|
+
replay,
|
|
3548
|
+
) => {
|
|
3549
|
+
const slowClickExperiment = replay.getOptions()._experiments.slowClicks;
|
|
3550
|
+
|
|
3551
|
+
const slowClickConfig = slowClickExperiment
|
|
3552
|
+
? {
|
|
3553
|
+
threshold: slowClickExperiment.threshold,
|
|
3554
|
+
timeout: slowClickExperiment.timeout,
|
|
3555
|
+
scrollTimeout: slowClickExperiment.scrollTimeout,
|
|
3556
|
+
ignoreSelector: slowClickExperiment.ignoreSelectors ? slowClickExperiment.ignoreSelectors.join(',') : '',
|
|
3557
|
+
}
|
|
3558
|
+
: undefined;
|
|
3559
|
+
|
|
3560
|
+
return (handlerData) => {
|
|
3414
3561
|
if (!replay.isEnabled()) {
|
|
3415
3562
|
return;
|
|
3416
3563
|
}
|
|
@@ -3421,8 +3568,21 @@ const handleDomListener =
|
|
|
3421
3568
|
return;
|
|
3422
3569
|
}
|
|
3423
3570
|
|
|
3571
|
+
const isClick = handlerData.name === 'click';
|
|
3572
|
+
const event = isClick && (handlerData.event );
|
|
3573
|
+
// Ignore clicks if ctrl/alt/meta keys are held down as they alter behavior of clicks (e.g. open in new tab)
|
|
3574
|
+
if (isClick && slowClickConfig && event && !event.altKey && !event.metaKey && !event.ctrlKey) {
|
|
3575
|
+
detectSlowClick(
|
|
3576
|
+
replay,
|
|
3577
|
+
slowClickConfig,
|
|
3578
|
+
result ,
|
|
3579
|
+
getClickTargetNode(handlerData.event) ,
|
|
3580
|
+
);
|
|
3581
|
+
}
|
|
3582
|
+
|
|
3424
3583
|
addBreadcrumbEvent(replay, result);
|
|
3425
3584
|
};
|
|
3585
|
+
};
|
|
3426
3586
|
|
|
3427
3587
|
/** Get the base DOM breadcrumb. */
|
|
3428
3588
|
function getBaseDomBreadcrumb(target, message) {
|