@embrace-io/web-sdk
Version:
1 lines • 6.45 kB
Source Map (JSON)
{"version":3,"file":"RageClickInstrumentation.cjs","names":["EmbraceInstrumentationBase","RAGE_CLICK_EVENT_NAME","SeverityNumber","KEY_EMB_TYPE","ATTR_RAGE_CLICK_ELEMENT_TYPE","ATTR_RAGE_CLICK_ELEMENT_SELECTOR","getSelector","ATTR_RAGE_CLICK_X","ATTR_RAGE_CLICK_Y","ATTR_RAGE_CLICK_COUNT","ATTR_RAGE_CLICK_INTERACTION_TYPE"],"sources":["../../../../src/instrumentations/rage-click/RageClickInstrumentation/RageClickInstrumentation.ts"],"sourcesContent":["import { SeverityNumber } from '@opentelemetry/api-logs';\nimport { EMB_TYPES, KEY_EMB_TYPE } from '../../../constants/attributes.ts';\nimport { getSelector } from '../../../utils/index.ts';\nimport { EmbraceInstrumentationBase } from '../../EmbraceInstrumentationBase/index.ts';\nimport type { InteractionType } from './constants.ts';\nimport {\n ATTR_RAGE_CLICK_COUNT,\n ATTR_RAGE_CLICK_ELEMENT_SELECTOR,\n ATTR_RAGE_CLICK_ELEMENT_TYPE,\n ATTR_RAGE_CLICK_INTERACTION_TYPE,\n ATTR_RAGE_CLICK_X,\n ATTR_RAGE_CLICK_Y,\n RAGE_CLICK_EVENT_NAME,\n RAGE_CLICK_RADIUS,\n RAGE_CLICK_THRESHOLD,\n RAGE_CLICK_WINDOW_TIME,\n} from './constants.ts';\nimport type { RageClickInstrumentationArgs } from './types.ts';\n\n/**\n * Rage clicks are tracked in an \"event window\" defined by the first click. The\n * window is automatically reset after RAGE_CLICK_WINDOW_TIME.\n */\ninterface RageClickWindow {\n target: Element;\n x: number;\n y: number;\n count: number;\n interactionType: InteractionType;\n timestamp: number;\n timeoutId: number;\n}\n\n/*\n Detects when the user makes multiple rapid clicks (\"rage clicks\") in a target\n area, and emits one log per rage click.\n*/\nexport class RageClickInstrumentation extends EmbraceInstrumentationBase {\n private readonly _onClickHandler: (event: MouseEvent) => void;\n private _eventWindow: RageClickWindow | null = null;\n\n public constructor({ diag, perf }: RageClickInstrumentationArgs = {}) {\n super({\n instrumentationName: 'RageClickInstrumentation',\n instrumentationVersion: '1.0.0',\n diag,\n perf,\n config: {},\n });\n\n this._onClickHandler = (event: MouseEvent): void => {\n try {\n this._handleClick(event);\n } catch (e) {\n this._diag.error('failed to process rage-click candidate', e);\n }\n };\n\n if (this._config.enabled) {\n this.enable();\n }\n }\n\n public override onDisable(): void {\n document.removeEventListener('click', this._onClickHandler);\n this._flush();\n }\n\n public override onEnable(): void {\n document.addEventListener('click', this._onClickHandler);\n }\n\n private _handleClick(event: MouseEvent): void {\n if (!(event.target instanceof Element)) {\n return;\n }\n\n const target = event.target;\n const interactionType: InteractionType =\n event instanceof PointerEvent && event.pointerType === 'touch'\n ? 'tap'\n : 'click';\n\n if (this._eventWindow === null) {\n this._eventWindow = {\n target,\n x: event.clientX,\n y: event.clientY,\n count: 1,\n interactionType,\n timestamp: event.timeStamp,\n timeoutId: window.setTimeout(() => {\n try {\n this._flush();\n } catch (e) {\n this._diag.error('failed to emit rage-click log', e);\n }\n }, RAGE_CLICK_WINDOW_TIME),\n };\n\n return;\n }\n\n const sameTarget = target === this._eventWindow.target;\n const dx = Math.abs(event.clientX - this._eventWindow.x);\n const dy = Math.abs(event.clientY - this._eventWindow.y);\n const withinRadius = dx <= RAGE_CLICK_RADIUS && dy <= RAGE_CLICK_RADIUS;\n\n if (sameTarget || withinRadius) {\n this._eventWindow.count += 1;\n }\n }\n\n private _flush(): void {\n if (this._eventWindow === null) {\n // No active window, probably because the instrumentation was disabled\n return;\n }\n\n const eventWindow = this._eventWindow;\n\n // Ensure the current window is reset, regardless of whether we emit the log or not\n window.clearTimeout(eventWindow.timeoutId);\n this._eventWindow = null;\n\n if (eventWindow.count < RAGE_CLICK_THRESHOLD) {\n // Not enough clicks to be considered a rage click event\n return;\n }\n\n this.logger.emit({\n timestamp: this.perf.epochMillisFromOrigin(eventWindow.timestamp),\n eventName: RAGE_CLICK_EVENT_NAME,\n severityNumber: SeverityNumber.INFO,\n attributes: {\n [KEY_EMB_TYPE]: EMB_TYPES.OTelLog,\n [ATTR_RAGE_CLICK_ELEMENT_TYPE]:\n eventWindow.target.tagName.toLowerCase(),\n [ATTR_RAGE_CLICK_ELEMENT_SELECTOR]: getSelector(eventWindow.target),\n [ATTR_RAGE_CLICK_X]: eventWindow.x,\n [ATTR_RAGE_CLICK_Y]: eventWindow.y,\n [ATTR_RAGE_CLICK_COUNT]: eventWindow.count,\n [ATTR_RAGE_CLICK_INTERACTION_TYPE]: eventWindow.interactionType,\n },\n });\n }\n}\n"],"mappings":";;;;;;;AAqCA,IAAa,2BAAb,cAA8CA,+EAAAA,2BAA2B;CACvE;CACA,eAA+C;CAE/C,YAAmB,EAAE,MAAM,SAAuC,CAAC,GAAG;EACpE,MAAM;GACJ,qBAAqB;GACrB,wBAAwB;GACxB;GACA;GACA,QAAQ,CAAC;EACX,CAAC;EAED,KAAK,mBAAmB,UAA4B;GAClD,IAAI;IACF,KAAK,aAAa,KAAK;GACzB,SAAS,GAAG;IACV,KAAK,MAAM,MAAM,0CAA0C,CAAC;GAC9D;EACF;EAEA,IAAI,KAAK,QAAQ,SACf,KAAK,OAAO;CAEhB;CAEA,YAAkC;EAChC,SAAS,oBAAoB,SAAS,KAAK,eAAe;EAC1D,KAAK,OAAO;CACd;CAEA,WAAiC;EAC/B,SAAS,iBAAiB,SAAS,KAAK,eAAe;CACzD;CAEA,aAAqB,OAAyB;EAC5C,IAAI,EAAE,MAAM,kBAAkB,UAC5B;EAGF,MAAM,SAAS,MAAM;EACrB,MAAM,kBACJ,iBAAiB,gBAAgB,MAAM,gBAAgB,UACnD,QACA;EAEN,IAAI,KAAK,iBAAiB,MAAM;GAC9B,KAAK,eAAe;IAClB;IACA,GAAG,MAAM;IACT,GAAG,MAAM;IACT,OAAO;IACP;IACA,WAAW,MAAM;IACjB,WAAW,OAAO,iBAAiB;KACjC,IAAI;MACF,KAAK,OAAO;KACd,SAAS,GAAG;MACV,KAAK,MAAM,MAAM,iCAAiC,CAAC;KACrD;IACF,GAAA,GAAyB;GAC3B;GAEA;EACF;EAEA,MAAM,aAAa,WAAW,KAAK,aAAa;EAChD,MAAM,KAAK,KAAK,IAAI,MAAM,UAAU,KAAK,aAAa,CAAC;EACvD,MAAM,KAAK,KAAK,IAAI,MAAM,UAAU,KAAK,aAAa,CAAC;EAGvD,IAAI,cAFiB,MAAA,MAA2B,MAAA,IAG9C,KAAK,aAAa,SAAS;CAE/B;CAEA,SAAuB;EACrB,IAAI,KAAK,iBAAiB,MAExB;EAGF,MAAM,cAAc,KAAK;EAGzB,OAAO,aAAa,YAAY,SAAS;EACzC,KAAK,eAAe;EAEpB,IAAI,YAAY,QAAA,GAEd;EAGF,KAAK,OAAO,KAAK;GACf,WAAW,KAAK,KAAK,sBAAsB,YAAY,SAAS;GAChE,WAAWC,uEAAAA;GACX,gBAAgBC,wBAAAA,eAAe;GAC/B,YAAY;KACTC,6BAAAA,eAAAA;KACAC,uEAAAA,+BACC,YAAY,OAAO,QAAQ,YAAY;KACxCC,uEAAAA,mCAAmCC,0BAAAA,YAAY,YAAY,MAAM;KACjEC,uEAAAA,oBAAoB,YAAY;KAChCC,uEAAAA,oBAAoB,YAAY;KAChCC,uEAAAA,wBAAwB,YAAY;KACpCC,uEAAAA,mCAAmC,YAAY;GAClD;EACF,CAAC;CACH;AACF"}