UNPKG

@technoapple/ga4

Version:

TypeScript Node.js library to support GA4 analytics.

91 lines (74 loc) 2.85 kB
import { GA4Plugin, SendFunction, EventTrackerOptions } from '../types/plugins'; import { delegate, DelegateHandle } from '../helpers/delegate'; function kebabToSnake(str: string): string { return str.replace(/-/g, '_'); } function getAttributeParams(element: Element, prefix: string): Record<string, unknown> { const params: Record<string, unknown> = {}; const reservedSuffixes = ['on', 'event-name']; for (let i = 0; i < element.attributes.length; i++) { const attr = element.attributes[i]; if (!attr.name.startsWith(prefix)) continue; const suffix = attr.name.slice(prefix.length); if (reservedSuffixes.includes(suffix)) continue; params[kebabToSnake(suffix)] = attr.value; } return params; } /** * Declarative event tracking via HTML `data-ga4-*` attributes. * * Listens for DOM events on elements with `data-ga4-on` attributes * and sends GA4 events based on attribute values. * * @example * ```html * <button * data-ga4-on="click" * data-ga4-event-name="video_play" * data-ga4-video-title="My Video"> * Play * </button> * ``` */ export class EventTracker implements GA4Plugin { private delegates: DelegateHandle[] = []; private send: SendFunction; private events: string[]; private attributePrefix: string; private hitFilter?: EventTrackerOptions['hitFilter']; constructor(send: SendFunction, options?: EventTrackerOptions) { this.send = send; this.events = options?.events ?? ['click']; this.attributePrefix = options?.attributePrefix ?? 'data-ga4-'; this.hitFilter = options?.hitFilter; const selector = `[${this.attributePrefix}on]`; this.events.forEach((eventType) => { const handle = delegate( document, eventType, selector, (event, element) => this.handleEvent(event, element), { composed: true, useCapture: true } ); this.delegates.push(handle); }); } private handleEvent(event: Event, element: Element): void { const prefix = this.attributePrefix; const onAttr = element.getAttribute(`${prefix}on`); if (onAttr !== event.type) return; const eventName = element.getAttribute(`${prefix}event-name`) || event.type; let params = getAttributeParams(element, prefix); if (this.hitFilter) { const filtered = this.hitFilter(params, element, event); if (filtered === null) return; params = filtered; } this.send(eventName, params); } remove(): void { this.delegates.forEach((d) => d.destroy()); this.delegates = []; } }