← back to Homesonspec

apps/web/src/lib/analytics.ts

42 lines

// Fire an analytics event down every available channel, best-effort and non-throwing —
// analytics must never break the page.
//   • gtag       → GA4 (no-op until a GA4 tag is added; lights up the instant it is)
//   • dataLayer  → Google Tag Manager
//   • /api/pwa-event beacon → first-party sink so the funnel is measurable TODAY,
//     with no external analytics account required.
type Params = Record<string, string | number | boolean | undefined>;

export function track(event: string, params: Params = {}): void {
  if (typeof window === "undefined") return;
  const payload = { event, ...params };

  // gtag + dataLayer (dormant-safe)
  try {
    const w = window as unknown as {
      gtag?: (...a: unknown[]) => void;
      dataLayer?: unknown[];
    };
    if (typeof w.gtag === "function") w.gtag("event", event, params);
    (w.dataLayer = w.dataLayer || []).push(payload);
  } catch {
    /* ignore */
  }

  // First-party sink — anonymous funnel counters, no PII.
  try {
    const body = JSON.stringify({ ...payload, t: Date.now(), path: location.pathname });
    if (navigator.sendBeacon) {
      navigator.sendBeacon("/api/pwa-event", new Blob([body], { type: "application/json" }));
    } else {
      void fetch("/api/pwa-event", {
        method: "POST",
        body,
        keepalive: true,
        headers: { "content-type": "application/json" },
      }).catch(() => {});
    }
  } catch {
    /* ignore */
  }
}