Files
shop/utils/trackEvent.ts
T
libretech bf3b8ea3da
build / build (pull_request) Successful in 1m10s
Analytics: queued events, once-per-order revenue, heatmap on product pages
trackEvent queues events until the deferred Umami script exists (they
were dropped when fired in onMounted) and drops them silently after 10 s.
The result page sends purchase-completed with revenue/currency once per
order (localStorage guard, set only after sending). The plugin honours
Do Not Track and loads recorder.js only on /details/* for Umami heatmaps.
bun test utils/trackEvent.test.ts: 4 pass. Netcup #115.
2026-10-15 04:16:42 +02:00

57 lines
1.5 KiB
TypeScript

declare global {
interface Window {
umami?: {
track: (event: string, data?: Record<string, unknown>) => void;
};
}
}
type Pending = { event: string; data?: Record<string, unknown>; onSent?: () => void };
// Events fired during page setup (onMounted) often run before the deferred
// Umami script has loaded; they used to be dropped silently. Queue them and
// flush once the tracker exists. If it never appears (blocked, offline),
// they are discarded after ~10 s without throwing.
const queue: Pending[] = [];
let timer: ReturnType<typeof setInterval> | null = null;
let attempts = 0;
const MAX_ATTEMPTS = 40;
const INTERVAL_MS = 250;
function send(item: Pending) {
try {
window.umami!.track(item.event, item.data);
item.onSent?.();
} catch {
// tracking must never break the shop
}
}
function flush() {
if (window.umami) {
while (queue.length) send(queue.shift()!);
}
if (window.umami || ++attempts >= MAX_ATTEMPTS) {
queue.length = 0;
if (timer) clearInterval(timer);
timer = null;
attempts = 0;
}
}
export const trackEvent = (event: string, data?: Record<string, unknown>, onSent?: () => void) => {
if (typeof window === "undefined") return;
const item = { event, data, onSent };
if (window.umami) return send(item);
queue.push(item);
if (!timer) timer = setInterval(flush, INTERVAL_MS);
};
// Test hook: reset module state between cases.
export const __resetTrackQueue = () => {
queue.length = 0;
if (timer) clearInterval(timer);
timer = null;
attempts = 0;
};