Files
infiplot-web/components/Analytics.tsx
T
yuanzonghao 4bf05f6784 feat(web): add privacy-friendly Umami custom events
Instrument the play flow with 9 content-free custom events (game_start,
art_style_select, style_image_upload, scene_reached, choice_select,
vision_click, tts_toggle, fullscreen_toggle, play_heartbeat) to measure
retention, engagement depth and session duration.

Privacy is enforced by construction, not convention:
- lib/analytics.ts types each event with a discriminated union, so a
  payload has no slot for free text — prompts, world guides, uploaded
  images and vision output can never reach analytics (compile-time
  guarantee, not a comment).
- track() no-ops without window.umami and never throws into the app.
- coarse 30s heartbeat fires only while the tab is visible.
- script stays gated on NEXT_PUBLIC_UMAMI_* env (blank → no script),
  honours Do-Not-Track, and locks to an exact data-domains allowlist.
- one-line on-site disclosure with a link, shown only when tracking is on.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-04 10:14:08 +08:00

26 lines
947 B
TypeScript

import Script from "next/script";
// Privacy-friendly, cookieless analytics (Umami). Both env vars unset →
// render nothing, so local dev and forks never report to our instance.
// - data-do-not-track: honour the visitor's browser Do Not Track setting.
// - data-domains (NEXT_PUBLIC_UMAMI_DOMAINS): extra guard — the tracker only
// fires when the live hostname matches, so even a fork that copied our env
// vars stays silent on a different domain. Unset → run on all hosts.
export function Analytics() {
const src = process.env.NEXT_PUBLIC_UMAMI_SRC;
const websiteId = process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID;
const domains = process.env.NEXT_PUBLIC_UMAMI_DOMAINS;
if (!src || !websiteId) return null;
return (
<Script
src={src}
data-website-id={websiteId}
data-do-not-track="true"
{...(domains ? { "data-domains": domains } : {})}
strategy="afterInteractive"
defer
/>
);
}