@technoapple/ga4
Version:
TypeScript Node.js library to support GA4 analytics.
66 lines (54 loc) • 2.4 kB
text/typescript
import { GA4Plugin, SendFunction, OutboundFormTrackerOptions } from '../types/plugins';
import { delegate, DelegateHandle } from '../helpers/delegate';
import { parseUrl } from '../helpers/parse-url';
function defaultShouldTrack(form: HTMLFormElement, parseUrlFn: (url: string) => { hostname: string; protocol: string; href: string }): boolean {
const action = form.action;
if (!action) return false;
const url = parseUrlFn(action);
return url.hostname !== location.hostname && url.protocol.startsWith('http');
}
/**
* Automatically tracks form submissions to external domains.
*
* Sends an event when a form's `action` attribute points to a
* different hostname than the current page.
*/
export class OutboundFormTracker implements GA4Plugin {
private delegateHandle: DelegateHandle;
private send: SendFunction;
private eventName: string;
private shouldTrackOutboundForm: NonNullable<OutboundFormTrackerOptions['shouldTrackOutboundForm']>;
private hitFilter?: OutboundFormTrackerOptions['hitFilter'];
constructor(send: SendFunction, options?: OutboundFormTrackerOptions) {
this.send = send;
this.eventName = options?.eventName ?? 'outbound_form_submit';
this.shouldTrackOutboundForm = options?.shouldTrackOutboundForm ?? defaultShouldTrack;
this.hitFilter = options?.hitFilter;
const formSelector = options?.formSelector ?? 'form';
this.delegateHandle = delegate(
document,
'submit',
formSelector,
(event, element) => this.handleFormSubmit(event, element as HTMLFormElement),
{ composed: true, useCapture: true }
);
}
private handleFormSubmit(event: Event, form: HTMLFormElement): void {
if (!this.shouldTrackOutboundForm(form, parseUrl)) return;
const url = parseUrl(form.action);
let params: Record<string, unknown> = {
form_action: url.href,
form_domain: url.hostname,
outbound: true,
};
if (this.hitFilter) {
const filtered = this.hitFilter(params, form, event);
if (filtered === null) return;
params = filtered;
}
this.send(this.eventName, params);
}
remove(): void {
this.delegateHandle.destroy();
}
}