72 lines · 2.5 KB
import { env, waitUntil } from "cloudflare:workers";

/**
 * Product analytics (PostHog), optional like Stripe: off unless POSTHOG_KEY (or the VITE_POSTHOG_PROJECT_TOKEN
 * alias) is set. Server events carry no PII: distinct ids are opaque account ids, and properties are enums
 * and counts only, never emails, names, private repository names or paths.
 */
export type AnalyticsConfig = { key: string; host: string };

const DEFAULT_HOST = "https://us.i.posthog.com";
const CAPTURE_TIMEOUT_MS = 3000;

export function analyticsConfig(): AnalyticsConfig | null {
  const key = (env.POSTHOG_KEY || env.VITE_POSTHOG_PROJECT_TOKEN || "").trim();
  if (!/^phc_[A-Za-z0-9]+$/.test(key)) return null;
  const raw = (env.POSTHOG_HOST || env.VITE_POSTHOG_HOST || DEFAULT_HOST).trim();
  try {
    const url = new URL(raw);
    if (url.protocol !== "https:" || url.username || url.password) return null;
    return { key, host: url.origin };
  } catch {
    return null;
  }
}

/** What the browser needs to start PostHog; null when analytics is off. */
export function clientAnalyticsConfig(): { key: string; host: string; origin: string; personEmail: boolean } | null {
  const config = analyticsConfig();
  if (!config) return null;
  try {
    return { ...config, origin: new URL(env.PUBLIC_URL).origin, personEmail: env.POSTHOG_PERSON_EMAIL === "true" };
  } catch {
    return null;
  }
}

type Value = string | number | boolean | null;

/**
 * Fire-and-forget server event. Runs under waitUntil with a short timeout and swallows every error, so it can