Files
PolyWeather/frontend/lib/app-analytics.ts
T
2026-05-31 03:21:58 +08:00

118 lines
3.0 KiB
TypeScript

"use client";
const ANALYTICS_ENABLED =
process.env.NEXT_PUBLIC_POLYWEATHER_APP_ANALYTICS !== "false";
type TrackableAnalyticsEvent =
| "landing_view"
| "enter_terminal"
| "login_start"
| "signup_success"
| "trial_created"
| "payment_start"
| "payment_success"
| "degraded_auth_profile"
| "signup_completed"
| "dashboard_active"
| "paywall_feature_clicked"
| "paywall_viewed"
| "checkout_started"
| "checkout_succeeded";
const CLIENT_ID_KEY = "polyweather:analytics:client-id";
const SESSION_ID_KEY = "polyweather:analytics:session-id";
function isClient() {
return typeof window !== "undefined";
}
function randomId() {
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
return crypto.randomUUID();
}
return `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
}
function getStoredId(storage: Storage, key: string) {
let value = storage.getItem(key);
if (!value) {
value = randomId();
storage.setItem(key, value);
}
return value;
}
function getDeviceType() {
if (!isClient()) return "unknown";
const width = window.innerWidth || 0;
const ua = navigator.userAgent || "";
if (/ipad|tablet/i.test(ua) || (width >= 768 && width < 1100)) return "tablet";
if (/mobi|android|iphone/i.test(ua) || width < 768) return "mobile";
return "desktop";
}
export function getAnalyticsClientId() {
if (!isClient()) return "";
try {
return getStoredId(window.localStorage, CLIENT_ID_KEY);
} catch {
return "";
}
}
export function getAnalyticsSessionId() {
if (!isClient()) return "";
try {
return getStoredId(window.sessionStorage, SESSION_ID_KEY);
} catch {
return "";
}
}
export function markAnalyticsOnce(key: string, scope: "local" | "session" = "session") {
if (!isClient()) return false;
const normalizedKey = `polyweather:analytics:once:${key}`;
try {
const storage = scope === "local" ? window.localStorage : window.sessionStorage;
if (!storage) return true;
if (storage.getItem(normalizedKey) === "1") {
return false;
}
storage.setItem(normalizedKey, "1");
return true;
} catch {
return true;
}
}
export function trackAppEvent(
eventType: TrackableAnalyticsEvent,
payload: Record<string, unknown> = {},
) {
if (!isClient() || !ANALYTICS_ENABLED) return;
const body = {
event_type: eventType,
client_id: getAnalyticsClientId() || undefined,
session_id: getAnalyticsSessionId() || undefined,
payload: {
...payload,
path: window.location.pathname,
href: window.location.href,
referrer: document.referrer || "",
language: navigator.language || "",
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "",
device_type: getDeviceType(),
viewport: `${window.innerWidth || 0}x${window.innerHeight || 0}`,
captured_at: new Date().toISOString(),
},
};
void fetch("/api/analytics/events", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
keepalive: true,
}).catch(() => {});
}