Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
inject onCapture behaviour during construction
Browse files Browse the repository at this point in the history
pauldambra committed Nov 8, 2024
1 parent 8d7c417 commit 2a209d2
Showing 4 changed files with 49 additions and 41 deletions.
Original file line number Diff line number Diff line change
@@ -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']()
31 changes: 15 additions & 16 deletions src/entrypoints/dead-clicks-autocapture.ts
Original file line number Diff line number Diff line change
@@ -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<DeadClicksAutoCaptureConfig> = {
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<DeadClicksAutoCaptureConfig>
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<DeadClicksAutoCaptureConfig> {
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) {
27 changes: 4 additions & 23 deletions src/extensions/dead-clicks-autocapture.ts
Original file line number Diff line number Diff line change
@@ -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...`)
}
23 changes: 23 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -122,13 +122,36 @@ 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
// by default if a click is followed by a selection change within 100ms it is not a dead click
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<AutocaptureConfig, 'element_attribute_ignorelist'>

export interface HeatmapConfig {

0 comments on commit 2a209d2

Please sign in to comment.