posthog-js
Version:
Posthog-js allows you to automatically capture usage and send events to PostHog.
521 lines • 33.5 kB
JavaScript
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
Object.defineProperty(exports, "__esModule", { value: true });
var globals_1 = require("@posthog/browser-common/utils/globals");
var globals_2 = require("../utils/globals");
var core_1 = require("@posthog/core");
var autocapture_utils_1 = require("@posthog/browser-common/utils/autocapture-utils");
var autocapture_1 = require("../autocapture");
var element_utils_1 = require("@posthog/browser-common/utils/element-utils");
var prototype_utils_1 = require("@posthog/browser-common/utils/prototype-utils");
var general_utils_1 = require("@posthog/browser-common/utils/general-utils");
function asCandidate(event, extra) {
var eventTarget = (0, autocapture_utils_1.getEventTarget)(event);
if (eventTarget) {
return __assign({ node: eventTarget, originalEvent: event, timestamp: Date.now() }, extra);
}
return null;
}
function swipeDirection(dx, dy) {
if (Math.abs(dx) >= Math.abs(dy)) {
return dx >= 0 ? 'right' : 'left';
}
return dy >= 0 ? 'down' : 'up';
}
// the event name doubles as the property prefix ($dead_click_* / $dead_swipe_*)
function deadEventName(candidate) {
return candidate.type === 'swipe' ? '$dead_swipe' : '$dead_click';
}
// surfaces that respond to a gesture without anything our observers can see — a canvas or
// WebGL repaint is not a DOM mutation, and native media controls live in a closed UA shadow
// root — so a swipe over them can never be judged dead
var UNOBSERVABLE_SURFACE_SELECTOR = 'canvas,video,audio,embed,object';
// a click within this window of a visibility or window focus/blur change is treated as the click
// that woke/focused the tab (or opened a new tab/window) and suppressed rather than flagged dead.
// wider than the other thresholds because a real "tab back, then click the page" gesture has a
// human-scale gap (refocus, move the mouse, click); still short enough that a genuinely dead click
// a while after refocusing is caught.
var LIVENESS_SUPPRESSION_MS = 1000;
function hasModifierKey(event) {
return event.ctrlKey || event.metaKey || event.altKey || event.shiftKey;
}
function checkTimeout(value, thresholdMs) {
return (0, core_1.isNumber)(value) && value >= thresholdMs;
}
// a liveness transition only counts if it fired within the suppression window on one side of the
// click. this is the single definition of "in window" — it gates every recorded delay, so once a
// delay lands on a candidate `_checkClicks` can trust it is already in range.
function livenessDelayInWindow(delay) {
return delay >= 0 && delay < LIVENESS_SUPPRESSION_MS ? delay : undefined;
}
// a liveness signal (visibility/focus) that fired shortly BEFORE the click — the click that woke
// or refocused the tab. read once when the candidate is queued; only a change inside the
// suppression window counts, so a long-ago transition can neither suppress the click nor (as it
// once wrongly did) mark it dead. the AFTER-the-click direction is recorded separately, as the
// event fires, by `_recordLivenessSignal`.
function priorLivenessDelay(clickTimestamp, lastSeenAt) {
return lastSeenAt ? livenessDelayInWindow(clickTimestamp - lastSeenAt) : undefined;
}
// How dead-click detection works
// ================================
// A click (or swipe) is queued as a candidate, then re-examined ~1s later in `_checkClicks`. It is
// reported as a `$dead_click` only if — after the click — NO liveness/suppression signal fired
// within its window AND at least one timeout signal fired. In short: something-happened-fast wins
// (alive), otherwise nothing-happened-in-time loses (dead).
//
// Gate signals (checked in `_ignore` before the click is ever queued — never even a candidate):
// - element is inside the PostHog toolbar
// - same node clicked again within 1s (dedupe of repeated clicks)
// - target is the <html> node or not an element
// - target matches `css_selector_ignorelist`
// - a modifier key is held (ctrl/meta/alt/shift) unless `capture_clicks_with_modifier_keys`
// - (clicks only) target is an anchor — a legitimate activation; swipes are still candidates
//
// Liveness / suppression signals (any one, within its window after the click => alive, dropped).
// These say "the click did something", so they only ever suppress; none can mark a click dead:
// - mutation: a DOM mutation < mutation_threshold_ms (default 2500)
// - scroll: the page/an element scrolled < scroll_threshold_ms (default 100)
// - selection: a selectionchange < selection_change_threshold_ms (default 100)
// - visibility: a visibilitychange (either direction — the tab going hidden because the click
// opened a new tab, or becoming visible as the click woke/focused it)
// < LIVENESS_SUPPRESSION_MS on either side of the click
// - focus: a window focus/blur (a click that opened a new window/popup may only surface as
// the current window losing focus) < LIVENESS_SUPPRESSION_MS on either side
// visibility/focus are recorded onto each queued candidate the instant they fire (like scroll), not
// read from a single shared timestamp when the click is checked. that matters because a click that
// hides/blurs the tab suspends the ~1s `_checkClicks` timer while hidden; by the time it resumes the
// tab has usually come back, and a shared timestamp would have been overwritten by that later
// transition, losing the click-correlated one and misjudging the click as dead.
//
// Timeout signals (a click with no liveness signal is dead if any one fired). Note visibility and
// focus are deliberately absent here — they are liveness-only and never mark a click dead:
// - mutation timeout: a mutation, but only after mutation_threshold_ms
// - scroll timeout: a scroll, but only after scroll_threshold_ms
// - selection timeout: a selectionchange, but only after selection_change_threshold_ms
// - absolute timeout: nothing at all within mutation_threshold_ms * 1.1 (the catch-all backstop)
var LazyLoadedDeadClicksAutocapture = /** @class */ (function () {
function LazyLoadedDeadClicksAutocapture(instance, config) {
var _this = this;
this.instance = instance;
this._clicks = [];
this._deadSwipesCaptured = 0;
this._defaultConfig = function (defaultOnCapture) { return ({
element_attribute_ignorelist: [],
scroll_threshold_ms: 100,
selection_change_threshold_ms: 100,
mutation_threshold_ms: 2500,
capture_clicks_with_modifier_keys: false,
capture_dead_swipes: true,
swipe_threshold_px: 30,
max_dead_swipes_per_page_load: 10,
__onCapture: defaultOnCapture,
}); };
this._onClick = function (event) {
var click = asCandidate(event, { type: 'click' });
if (!(0, core_1.isNull)(click) && !_this._ignore(click)) {
_this._queueCandidate(click);
}
_this._scheduleCheck();
};
this._onScroll = function () {
var candidateNow = Date.now();
_this._lastScroll = candidateNow;
// very naive throttle
if (candidateNow % 50 === 0) {
// we can see many scrolls between scheduled checks,
// so we update scroll delay as we see them
// to avoid false positives
_this._clicks.forEach(function (click) {
if ((0, core_1.isUndefined)(click.scrollDelayMs)) {
click.scrollDelayMs = candidateNow - click.timestamp;
}
});
}
};
this._onSelectionChange = function () {
_this._lastSelectionChanged = Date.now();
};
this._onVisibilityChange = function () {
// record both directions: a tab going _hidden_ right after a click (the click opened a new
// tab) is as much a liveness signal as it becoming visible (the click that woke the tab).
// stamp queued candidates now, before a hidden tab can suspend `_checkClicks`.
var firedAt = Date.now();
_this._lastVisibilityChange = firedAt;
_this._recordLivenessSignal('visibilityChangedDelayMs', firedAt);
};
this._onFocusChange = function () {
var firedAt = Date.now();
_this._lastFocusChange = firedAt;
_this._recordLivenessSignal('focusChangedDelayMs', firedAt);
};
this._onTouchStart = function (event) {
var touches = event.touches;
// only single-finger gestures are swipes; a second finger (pinch/zoom) is not,
// so a multi-touch start clears any tracked origin rather than measuring against it
var touch = (touches === null || touches === void 0 ? void 0 : touches.length) === 1 ? touches[0] : undefined;
_this._touchStart = touch ? { x: touch.clientX, y: touch.clientY, timestamp: Date.now() } : undefined;
};
this._onTouchCancel = function () {
_this._touchStart = undefined;
};
this._onTouchEnd = function (event) {
var _a, _b, _c;
var start = _this._touchStart;
_this._touchStart = undefined;
if ((0, core_1.isUndefined)(start) || _this._deadSwipesCaptured >= _this._config.max_dead_swipes_per_page_load) {
return;
}
var touchEvent = event;
var touch = (_a = touchEvent.changedTouches) === null || _a === void 0 ? void 0 : _a[0];
if (!touch) {
return;
}
var dx = touch.clientX - start.x;
var dy = touch.clientY - start.y;
var distanceSq = dx * dx + dy * dy;
// compare squared distances to avoid a sqrt on every touch; only the actual swipes need it
var threshold = _this._config.swipe_threshold_px;
if (distanceSq < threshold * threshold) {
// a short movement is a tap or a jitter, not a swipe
return;
}
// a gesture that already did something while the finger was down — dragging scrolled
// the page, a finger-following carousel mutated the DOM, a text selection grew — is not
// dead, so it never becomes a candidate. surviving candidates keep the touchend
// timestamp: the thresholds in _checkClicks measure how quickly the page responds
// after the finger lifts, just as they measure the response to a click
var gestureCausedActivity = [_this._lastScroll, _this._lastMutation, _this._lastSelectionChanged].some(function (lastActivityAt) { return (0, core_1.isNumber)(lastActivityAt) && lastActivityAt >= start.timestamp; });
if (gestureCausedActivity) {
return;
}
// hit-test the stack under where the finger landed rather than the candidate's
// ancestors: canvas apps usually receive their touches through transparent overlay
// elements stacked on top of the canvas, so the surface is beneath, not above.
// this runs at most once per dead-candidate gesture, when the page is provably idle
// (the activity gate above just passed), so a forced layout flush is unlikely
if (_this._pageHasUnobservableSurfaces()) {
var stack = (_c = (_b = globals_1.document === null || globals_1.document === void 0 ? void 0 : globals_1.document.elementsFromPoint) === null || _b === void 0 ? void 0 : _b.call(globals_1.document, start.x, start.y)) !== null && _c !== void 0 ? _c : [];
if (stack.some(function (el) { var _a; return (_a = el.matches) === null || _a === void 0 ? void 0 : _a.call(el, UNOBSERVABLE_SURFACE_SELECTOR); })) {
return;
}
}
var swipe = asCandidate(touchEvent, {
type: 'swipe',
swipeDirection: swipeDirection(dx, dy),
swipeDistancePx: Math.round(Math.sqrt(distanceSq)),
});
if (!(0, core_1.isNull)(swipe) && !_this._ignore(swipe)) {
_this._queueCandidate(swipe);
}
_this._scheduleCheck();
};
this._config = this._asRequiredConfig(config);
this._onCapture = this._config.__onCapture;
}
LazyLoadedDeadClicksAutocapture.prototype._asRequiredConfig = function (providedConfig) {
var _a, _b, _c, _d, _e, _f, _g, _h;
var defaultConfig = this._defaultConfig((providedConfig === null || providedConfig === void 0 ? void 0 : providedConfig.__onCapture) || this._captureDeadClick.bind(this));
return {
element_attribute_ignorelist: (_a = providedConfig === null || providedConfig === void 0 ? void 0 : providedConfig.element_attribute_ignorelist) !== null && _a !== void 0 ? _a : defaultConfig.element_attribute_ignorelist,
scroll_threshold_ms: (_b = providedConfig === null || providedConfig === void 0 ? void 0 : providedConfig.scroll_threshold_ms) !== null && _b !== void 0 ? _b : defaultConfig.scroll_threshold_ms,
selection_change_threshold_ms: (_c = providedConfig === null || providedConfig === void 0 ? void 0 : providedConfig.selection_change_threshold_ms) !== null && _c !== void 0 ? _c : defaultConfig.selection_change_threshold_ms,
mutation_threshold_ms: (_d = providedConfig === null || providedConfig === void 0 ? void 0 : providedConfig.mutation_threshold_ms) !== null && _d !== void 0 ? _d : defaultConfig.mutation_threshold_ms,
capture_clicks_with_modifier_keys: (_e = providedConfig === null || providedConfig === void 0 ? void 0 : providedConfig.capture_clicks_with_modifier_keys) !== null && _e !== void 0 ? _e : defaultConfig.capture_clicks_with_modifier_keys,
capture_dead_swipes: (_f = providedConfig === null || providedConfig === void 0 ? void 0 : providedConfig.capture_dead_swipes) !== null && _f !== void 0 ? _f : defaultConfig.capture_dead_swipes,
swipe_threshold_px: (_g = providedConfig === null || providedConfig === void 0 ? void 0 : providedConfig.swipe_threshold_px) !== null && _g !== void 0 ? _g : defaultConfig.swipe_threshold_px,
max_dead_swipes_per_page_load: (_h = providedConfig === null || providedConfig === void 0 ? void 0 : providedConfig.max_dead_swipes_per_page_load) !== null && _h !== void 0 ? _h : defaultConfig.max_dead_swipes_per_page_load,
css_selector_ignorelist: providedConfig === null || providedConfig === void 0 ? void 0 : providedConfig.css_selector_ignorelist,
__onCapture: defaultConfig.__onCapture,
};
};
LazyLoadedDeadClicksAutocapture.prototype.start = function (observerTarget) {
this._startClickObserver();
this._startScrollObserver();
this._startSelectionChangedObserver();
this._startVisibilityChangeObserver();
this._startFocusChangeObserver();
this._startMutationObserver(observerTarget);
if (this._config.capture_dead_swipes) {
this._startSwipeObserver();
}
};
LazyLoadedDeadClicksAutocapture.prototype._startMutationObserver = function (observerTarget) {
var _this = this;
if (!this._mutationObserver) {
var NativeMutationObserver = (0, prototype_utils_1.getNativeMutationObserverImplementation)(globals_2.assignableWindow);
this._mutationObserver = new NativeMutationObserver(function (mutations) {
_this._onMutation(mutations);
});
this._mutationObserver.observe(observerTarget, {
attributes: true,
characterData: true,
childList: true,
subtree: true,
});
}
};
LazyLoadedDeadClicksAutocapture.prototype.stop = function () {
var _a;
(_a = this._mutationObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
this._mutationObserver = undefined;
globals_2.assignableWindow.removeEventListener('click', this._onClick);
globals_2.assignableWindow.removeEventListener('scroll', this._onScroll, { capture: true });
globals_2.assignableWindow.removeEventListener('selectionchange', this._onSelectionChange);
globals_2.assignableWindow.removeEventListener('touchstart', this._onTouchStart, { capture: true });
globals_2.assignableWindow.removeEventListener('touchend', this._onTouchEnd, { capture: true });
globals_2.assignableWindow.removeEventListener('touchcancel', this._onTouchCancel, { capture: true });
globals_1.document === null || globals_1.document === void 0 ? void 0 : globals_1.document.removeEventListener('visibilitychange', this._onVisibilityChange);
globals_2.assignableWindow.removeEventListener('blur', this._onFocusChange);
globals_2.assignableWindow.removeEventListener('focus', this._onFocusChange);
// so a gesture in flight when we stopped can't pair with a touchend after a restart
this._touchStart = undefined;
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
LazyLoadedDeadClicksAutocapture.prototype._onMutation = function (_mutations) {
// we don't actually care about the content of the mutations, right now
this._lastMutation = Date.now();
};
LazyLoadedDeadClicksAutocapture.prototype._startClickObserver = function () {
(0, general_utils_1.addEventListener)(globals_2.assignableWindow, 'click', this._onClick);
};
// queue a candidate, first recording any liveness signal that fired just BEFORE the click (the
// click that woke/refocused the tab). the AFTER-the-click direction is stamped later, as the
// event fires, by `_recordLivenessSignal`.
LazyLoadedDeadClicksAutocapture.prototype._queueCandidate = function (candidate) {
candidate.visibilityChangedDelayMs = priorLivenessDelay(candidate.timestamp, this._lastVisibilityChange);
candidate.focusChangedDelayMs = priorLivenessDelay(candidate.timestamp, this._lastFocusChange);
this._clicks.push(candidate);
};
LazyLoadedDeadClicksAutocapture.prototype._scheduleCheck = function () {
var _this = this;
if (this._clicks.length && (0, core_1.isUndefined)(this._checkClickTimer)) {
this._checkClickTimer = globals_2.assignableWindow.setTimeout(function () {
_this._checkClicks();
}, 1000);
}
};
// `capture: true` is required to get scroll events for other scrollable elements
// on the page, not just the window
// see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#usecapture
//
// `passive: true` is used to tell the browser that the scroll event handler will not call `preventDefault()`
// This allows the browser to optimize scrolling performance by not waiting for our handling of the scroll event
// see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#passive
LazyLoadedDeadClicksAutocapture.prototype._startScrollObserver = function () {
(0, general_utils_1.addEventListener)(globals_2.assignableWindow, 'scroll', this._onScroll, { capture: true });
};
LazyLoadedDeadClicksAutocapture.prototype._startSelectionChangedObserver = function () {
(0, general_utils_1.addEventListener)(globals_2.assignableWindow, 'selectionchange', this._onSelectionChange);
};
LazyLoadedDeadClicksAutocapture.prototype._startVisibilityChangeObserver = function () {
(0, general_utils_1.addEventListener)(globals_1.document, 'visibilitychange', this._onVisibilityChange);
};
// a click that opens a new window/popup may leave the current tab visible, so its only trace is
// the window losing focus. focus/blur are liveness signals too — they suppress a dead click, and
// like visibility they never cause one.
LazyLoadedDeadClicksAutocapture.prototype._startFocusChangeObserver = function () {
(0, general_utils_1.addEventListener)(globals_2.assignableWindow, 'blur', this._onFocusChange);
(0, general_utils_1.addEventListener)(globals_2.assignableWindow, 'focus', this._onFocusChange);
};
// stamp each queued candidate with how long AFTER its click this liveness signal fired, the
// moment the event arrives. recording it immediately — rather than reading a single shared
// timestamp when `_checkClicks` finally runs — is what makes it robust: a hidden tab suspends
// `_checkClicks`, and by the time it resumes a later transition (the tab coming back) would have
// overwritten the click-correlated one. keeps the closest transition, only within the window.
LazyLoadedDeadClicksAutocapture.prototype._recordLivenessSignal = function (field, firedAt) {
this._clicks.forEach(function (click) {
var delay = livenessDelayInWindow(firedAt - click.timestamp);
if ((0, core_1.isNumber)(delay) && ((0, core_1.isUndefined)(click[field]) || delay < click[field])) {
click[field] = delay;
}
});
};
// `capture: true` mirrors the scroll observer so we see gestures on nested scrollable
// elements too. `passive: true` tells the browser we won't call `preventDefault()`.
LazyLoadedDeadClicksAutocapture.prototype._startSwipeObserver = function () {
(0, general_utils_1.addEventListener)(globals_2.assignableWindow, 'touchstart', this._onTouchStart, { capture: true, passive: true });
(0, general_utils_1.addEventListener)(globals_2.assignableWindow, 'touchend', this._onTouchEnd, { capture: true, passive: true });
// a cancelled gesture (system back-swipe, incoming call, etc.) never fires touchend,
// so clear the start point to avoid measuring the next gesture from a stale origin
(0, general_utils_1.addEventListener)(globals_2.assignableWindow, 'touchcancel', this._onTouchCancel, { capture: true, passive: true });
};
// whether the page contains surfaces whose response to a gesture we cannot observe —
// cached, recomputed lazily and only when the DOM has mutated since the last look
LazyLoadedDeadClicksAutocapture.prototype._pageHasUnobservableSurfaces = function () {
var domChangedSinceCheck = (0, core_1.isUndefined)(this._surfacesCheckedAt) ||
((0, core_1.isNumber)(this._lastMutation) && this._lastMutation > this._surfacesCheckedAt);
if ((0, core_1.isUndefined)(this._hasUnobservableSurfaces) || domChangedSinceCheck) {
this._surfacesCheckedAt = Date.now();
this._hasUnobservableSurfaces = !!(globals_1.document === null || globals_1.document === void 0 ? void 0 : globals_1.document.querySelector(UNOBSERVABLE_SURFACE_SELECTOR));
}
return !!this._hasUnobservableSurfaces;
};
LazyLoadedDeadClicksAutocapture.prototype._ignore = function (candidate) {
if (!candidate) {
return true;
}
// swipes run only the shared gates; clicks layer their own intent gates on top
return candidate.type === 'swipe' ? this._ignoreCommon(candidate) : this._ignoreClick(candidate);
};
// gates that apply to every dead candidate, whatever the gesture
LazyLoadedDeadClicksAutocapture.prototype._ignoreCommon = function (candidate) {
if ((0, element_utils_1.isElementInToolbar)(candidate.node)) {
return true;
}
var alreadySeenInLastSecond = this._clicks.some(function (c) {
return (c.type === candidate.type &&
c.node === candidate.node &&
Math.abs(c.timestamp - candidate.timestamp) < 1000);
});
if (alreadySeenInLastSecond) {
return true;
}
if ((0, element_utils_1.isTag)(candidate.node, 'html') || !(0, element_utils_1.isElementNode)(candidate.node)) {
return true;
}
return !(0, autocapture_utils_1.shouldCaptureDeadClick)(candidate.node, {
css_selector_ignorelist: this._config.css_selector_ignorelist,
});
};
// click-only gates, layered on top of the shared ones
LazyLoadedDeadClicksAutocapture.prototype._ignoreClick = function (candidate) {
// clicks with modifier keys (open in new tab, etc.) are intentional; touch swipes have no modifiers
if (!this._config.capture_clicks_with_modifier_keys && hasModifierKey(candidate.originalEvent)) {
return true;
}
if (this._ignoreCommon(candidate)) {
return true;
}
// an anchor is a legitimate click activation so we skip it, but a swipe that fails to
// navigate is the signal we want — which is why anchors are skipped for clicks only.
// reaching here means _ignoreCommon confirmed an element node, so this is safe to call
return (0, autocapture_utils_1.shouldSkipDeadClick)(candidate.node);
};
LazyLoadedDeadClicksAutocapture.prototype._checkClicks = function () {
var e_1, _a, _b;
var _c;
if (!this._clicks.length) {
return;
}
clearTimeout(this._checkClickTimer);
this._checkClickTimer = undefined;
var clicksToCheck = this._clicks;
this._clicks = [];
try {
for (var clicksToCheck_1 = __values(clicksToCheck), clicksToCheck_1_1 = clicksToCheck_1.next(); !clicksToCheck_1_1.done; clicksToCheck_1_1 = clicksToCheck_1.next()) {
var click = clicksToCheck_1_1.value;
click.mutationDelayMs =
(_c = click.mutationDelayMs) !== null && _c !== void 0 ? _c : (this._lastMutation && click.timestamp <= this._lastMutation
? this._lastMutation - click.timestamp
: undefined);
click.absoluteDelayMs = Date.now() - click.timestamp;
click.selectionChangedDelayMs =
this._lastSelectionChanged && click.timestamp <= this._lastSelectionChanged
? this._lastSelectionChanged - click.timestamp
: undefined;
// visibilityChangedDelayMs / focusChangedDelayMs are already recorded on the candidate as
// the events fire (see `_queueCandidate` for the before-the-click direction and
// `_recordLivenessSignal` for after) — nothing to compute here. both are liveness-only: a
// tab/window transition near a click is evidence the click did something, so they only
// ever _suppress_ a dead click and (unlike mutation/selection) feed no timeout branch.
var scrollTimeout = checkTimeout(click.scrollDelayMs, this._config.scroll_threshold_ms);
var selectionChangedTimeout = checkTimeout(click.selectionChangedDelayMs, this._config.selection_change_threshold_ms);
var mutationTimeout = checkTimeout(click.mutationDelayMs, this._config.mutation_threshold_ms);
// we want to timeout eventually even if nothing else catches it...
// we leave a little longer than the maximum threshold to give the other checks a chance to catch it
var absoluteTimeout = checkTimeout(click.absoluteDelayMs, this._config.mutation_threshold_ms * 1.1);
var hadScroll = (0, core_1.isNumber)(click.scrollDelayMs) && click.scrollDelayMs < this._config.scroll_threshold_ms;
var hadMutation = (0, core_1.isNumber)(click.mutationDelayMs) && click.mutationDelayMs < this._config.mutation_threshold_ms;
var hadSelectionChange = (0, core_1.isNumber)(click.selectionChangedDelayMs) &&
click.selectionChangedDelayMs < this._config.selection_change_threshold_ms;
// visibility/focus delays are only ever recorded when already inside the suppression
// window (see `livenessDelayInWindow`), so their presence alone means "suppress"
var hadVisibilityChange = (0, core_1.isNumber)(click.visibilityChangedDelayMs);
var hadFocusChange = (0, core_1.isNumber)(click.focusChangedDelayMs);
if (hadScroll || hadMutation || hadSelectionChange || hadVisibilityChange || hadFocusChange) {
continue;
}
if (scrollTimeout || mutationTimeout || absoluteTimeout || selectionChangedTimeout) {
var prefix = deadEventName(click);
if (prefix === '$dead_swipe') {
if (this._deadSwipesCaptured >= this._config.max_dead_swipes_per_page_load) {
// the page-load budget for dead swipes is spent — drop, don't requeue
continue;
}
this._deadSwipesCaptured++;
}
this._onCapture(click, (_b = {},
_b["".concat(prefix, "_last_mutation_timestamp")] = this._lastMutation,
_b["".concat(prefix, "_event_timestamp")] = click.timestamp,
_b["".concat(prefix, "_scroll_timeout")] = scrollTimeout,
_b["".concat(prefix, "_mutation_timeout")] = mutationTimeout,
_b["".concat(prefix, "_absolute_timeout")] = absoluteTimeout,
_b["".concat(prefix, "_selection_changed_timeout")] = selectionChangedTimeout,
// visibility changes only ever suppress a dead click, never cause one; kept in the
// payload (always false) so the event shape is unchanged for existing consumers
_b["".concat(prefix, "_visibility_changed_timeout")] = false,
_b));
}
else if (click.absoluteDelayMs < this._config.mutation_threshold_ms) {
// keep waiting until next check
this._clicks.push(click);
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (clicksToCheck_1_1 && !clicksToCheck_1_1.done && (_a = clicksToCheck_1.return)) _a.call(clicksToCheck_1);
}
finally { if (e_1) throw e_1.error; }
}
this._scheduleCheck();
};
LazyLoadedDeadClicksAutocapture.prototype._captureDeadClick = function (click, properties) {
var _a;
// TODO need to check safe and captur-able as with autocapture
// TODO autocaputure config
var prefix = deadEventName(click);
this.instance.capture(prefix, __assign(__assign(__assign({}, properties), (0, autocapture_1.autocapturePropertiesForElement)(click.node, {
e: click.originalEvent,
maskAllElementAttributes: this.instance.config.mask_all_element_attributes,
maskAllText: this.instance.config.mask_all_text,
elementAttributeIgnoreList: this._config.element_attribute_ignorelist,
// TRICKY: it appears that we were moving to elementsChainAsString, but the UI still depends on elements, so :shrug:
elementsChainAsString: false,
disableCaptureUrlHashes: this.instance.config.disable_capture_url_hashes,
}).props), (_a = {}, _a["".concat(prefix, "_scroll_delay_ms")] = click.scrollDelayMs, _a["".concat(prefix, "_mutation_delay_ms")] = click.mutationDelayMs, _a["".concat(prefix, "_absolute_delay_ms")] = click.absoluteDelayMs, _a["".concat(prefix, "_selection_changed_delay_ms")] = click.selectionChangedDelayMs, _a["".concat(prefix, "_visibility_changed_delay_ms")] = click.visibilityChangedDelayMs, _a["".concat(prefix, "_focus_changed_delay_ms")] = click.focusChangedDelayMs,
// undefined for clicks (stripped on serialization), like the delay fields above
_a.$dead_swipe_direction = click.swipeDirection, _a.$dead_swipe_distance_px = click.swipeDistancePx, _a)), {
timestamp: new Date(click.timestamp),
});
};
return LazyLoadedDeadClicksAutocapture;
}());
globals_2.assignableWindow.__PosthogExtensions__ = globals_2.assignableWindow.__PosthogExtensions__ || {};
globals_2.assignableWindow.__PosthogExtensions__.initDeadClicksAutocapture = function (ph, config) {
return new LazyLoadedDeadClicksAutocapture(ph, config);
};
exports.default = LazyLoadedDeadClicksAutocapture;
//# sourceMappingURL=dead-clicks-autocapture.js.map