{"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"}