From 2a209d22893e7c1b433675942a2e3296225ec5a9 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Fri, 8 Nov 2024 12:16:12 +0100 Subject: [PATCH] inject onCapture behaviour during construction --- ...azy-loaded-dead-clicks-autocapture.test.ts | 9 ++++-- src/entrypoints/dead-clicks-autocapture.ts | 31 +++++++++---------- src/extensions/dead-clicks-autocapture.ts | 27 +++------------- src/types.ts | 23 ++++++++++++++ 4 files changed, 49 insertions(+), 41 deletions(-) diff --git a/src/__tests__/entrypoints/lazy-loaded-dead-clicks-autocapture.test.ts b/src/__tests__/entrypoints/lazy-loaded-dead-clicks-autocapture.test.ts index 4719f9e03..b206acd62 100644 --- a/src/__tests__/entrypoints/lazy-loaded-dead-clicks-autocapture.test.ts +++ b/src/__tests__/entrypoints/lazy-loaded-dead-clicks-autocapture.test.ts @@ -386,14 +386,19 @@ describe('LazyLoadedDeadClicksAutocapture', () => { it('can have alternative behaviour for onCapture', () => { jest.setSystemTime(0) + const replacementCapture = jest.fn() + + lazyLoadedDeadClicksAutocapture = new LazyLoadedDeadClicksAutocapture(fakeInstance, { + __onCapture: replacementCapture, + }) + lazyLoadedDeadClicksAutocapture.start(document) + lazyLoadedDeadClicksAutocapture['_clicks'].push({ node: document.body, originalEvent: { type: 'click' } as MouseEvent, timestamp: 900, }) lazyLoadedDeadClicksAutocapture['_lastMutation'] = undefined - const replacementCapture = jest.fn() - lazyLoadedDeadClicksAutocapture.onCapture = replacementCapture jest.setSystemTime(3001 + 900) lazyLoadedDeadClicksAutocapture['_checkClicks']() diff --git a/src/entrypoints/dead-clicks-autocapture.ts b/src/entrypoints/dead-clicks-autocapture.ts index eb9a832c9..1b5bf480c 100644 --- a/src/entrypoints/dead-clicks-autocapture.ts +++ b/src/entrypoints/dead-clicks-autocapture.ts @@ -6,13 +6,6 @@ import { DeadClickCandidate, DeadClicksAutoCaptureConfig, Properties } from '../ import { autocapturePropertiesForElement } from '../autocapture' import { isElementInToolbar, isElementNode, isTag } from '../utils/element-utils' -const DEFAULT_CONFIG: Required = { - element_attribute_ignorelist: [], - scroll_threshold_ms: 100, - selection_change_threshold_ms: 100, - mutation_threshold_ms: 2500, -} - function asClick(event: MouseEvent): DeadClickCandidate | null { const eventTarget = getEventTarget(event) if (eventTarget) { @@ -30,10 +23,6 @@ function checkTimeout(value: number | undefined, thresholdMs: number) { } class LazyLoadedDeadClicksAutocapture implements LazyLoadedDeadClicksAutocaptureInterface { - set onCapture(value: (click: DeadClickCandidate, properties: Properties) => void) { - this._onCapture = value - } - private _mutationObserver: MutationObserver | undefined private _lastMutation: number | undefined private _lastSelectionChanged: number | undefined @@ -42,20 +31,30 @@ class LazyLoadedDeadClicksAutocapture implements LazyLoadedDeadClicksAutocapture private _config: Required private _onCapture: (click: DeadClickCandidate, properties: Properties) => void + private _defaultConfig = (defaultOnCapture: (click: DeadClickCandidate, properties: Properties) => void) => ({ + element_attribute_ignorelist: [], + scroll_threshold_ms: 100, + selection_change_threshold_ms: 100, + mutation_threshold_ms: 2500, + __onCapture: defaultOnCapture, + }) + private asRequiredConfig(providedConfig?: DeadClicksAutoCaptureConfig): Required { + const defaultConfig = this._defaultConfig(providedConfig?.__onCapture || this._captureDeadClick.bind(this)) return { element_attribute_ignorelist: - providedConfig?.element_attribute_ignorelist ?? DEFAULT_CONFIG.element_attribute_ignorelist, - scroll_threshold_ms: providedConfig?.scroll_threshold_ms ?? DEFAULT_CONFIG.scroll_threshold_ms, + providedConfig?.element_attribute_ignorelist ?? defaultConfig.element_attribute_ignorelist, + scroll_threshold_ms: providedConfig?.scroll_threshold_ms ?? defaultConfig.scroll_threshold_ms, selection_change_threshold_ms: - providedConfig?.selection_change_threshold_ms ?? DEFAULT_CONFIG.selection_change_threshold_ms, - mutation_threshold_ms: providedConfig?.mutation_threshold_ms ?? DEFAULT_CONFIG.mutation_threshold_ms, + providedConfig?.selection_change_threshold_ms ?? defaultConfig.selection_change_threshold_ms, + mutation_threshold_ms: providedConfig?.mutation_threshold_ms ?? defaultConfig.mutation_threshold_ms, + __onCapture: defaultConfig.__onCapture, } } constructor(readonly instance: PostHog, config?: DeadClicksAutoCaptureConfig) { this._config = this.asRequiredConfig(config) - this._onCapture = this._captureDeadClick.bind(this) + this._onCapture = this._config.__onCapture } start(observerTarget: Node) { diff --git a/src/extensions/dead-clicks-autocapture.ts b/src/extensions/dead-clicks-autocapture.ts index 5fd8bf252..67cee29e1 100644 --- a/src/extensions/dead-clicks-autocapture.ts +++ b/src/extensions/dead-clicks-autocapture.ts @@ -3,24 +3,10 @@ import { DEAD_CLICKS_ENABLED_SERVER_SIDE } from '../constants' import { isBoolean, isObject } from '../utils/type-utils' import { assignableWindow, document, LazyLoadedDeadClicksAutocaptureInterface } from '../utils/globals' import { logger } from '../utils/logger' -import { DecideResponse, Properties } from '../types' +import { DecideResponse } from '../types' const LOGGER_PREFIX = '[Dead Clicks]' -interface DeadClickCandidate { - node: Element - originalEvent: MouseEvent - timestamp: number - // time between click and the most recent scroll - scrollDelayMs?: number - // time between click and the most recent mutation - mutationDelayMs?: number - // time between click and the most recent selection changed event - selectionChangedDelayMs?: number - // if neither scroll nor mutation seen before threshold passed - absoluteDelayMs?: number -} - export const isDeadClicksEnabledForHeatmaps = () => { return true } @@ -50,12 +36,10 @@ export class DeadClicksAutocapture { this.startIfEnabled() } - public startIfEnabled({ - onCapture, - }: { force?: boolean; onCapture?: (click: DeadClickCandidate, properties: Properties) => void } = {}) { + public startIfEnabled() { if (this.isEnabled(this)) { this.loadScript(() => { - this.start(onCapture) + this.start() }) } } @@ -78,7 +62,7 @@ export class DeadClicksAutocapture { ) } - private start(onCapture?: (click: DeadClickCandidate, properties: Properties) => void) { + private start() { if (!document) { logger.error(LOGGER_PREFIX + ' `document` not found. Cannot start.') return @@ -94,9 +78,6 @@ export class DeadClicksAutocapture { ? this.instance.config.capture_dead_clicks : undefined ) - if (onCapture) { - this._lazyLoadedDeadClicksAutocapture.onCapture = onCapture - } this._lazyLoadedDeadClicksAutocapture.start(document) logger.info(`${LOGGER_PREFIX} starting...`) } diff --git a/src/types.ts b/src/types.ts index 0f6c5cfc9..e6aa9ac16 100644 --- a/src/types.ts +++ b/src/types.ts @@ -122,6 +122,20 @@ export interface PerformanceCaptureConfig { web_vitals_delayed_flush_ms?: number } +export interface DeadClickCandidate { + node: Element + originalEvent: MouseEvent + timestamp: number + // time between click and the most recent scroll + scrollDelayMs?: number + // time between click and the most recent mutation + mutationDelayMs?: number + // time between click and the most recent selection changed event + selectionChangedDelayMs?: number + // if neither scroll nor mutation seen before threshold passed + absoluteDelayMs?: number +} + export type DeadClicksAutoCaptureConfig = { // by default if a click is followed by a sroll within 100ms it is not a dead click scroll_threshold_ms?: number @@ -129,6 +143,15 @@ export type DeadClicksAutoCaptureConfig = { selection_change_threshold_ms?: number // by default if a click is followed by a mutation within 2500ms it is not a dead click mutation_threshold_ms?: number + /** + * Allows setting behavior for when a dead click is captured. + * For e.g. to support capture to heatmaps + * + * If not provided the default behavior is to auto-capture dead click events + * + * Only intended to be provided by the SDK + */ + __onCapture?: ((click: DeadClickCandidate, properties: Properties) => void) | undefined } & Pick export interface HeatmapConfig {