feat(analytics): optional, privacy-first PostHog integration (web + Capacitor) - #156
Draft
majicmaj wants to merge 1 commit into
Draft
feat(analytics): optional, privacy-first PostHog integration (web + Capacitor)#156majicmaj wants to merge 1 commit into
majicmaj wants to merge 1 commit into
Conversation
Adds an opt-in PostHog product-analytics layer that is a complete no-op unless VITE_POSTHOG_KEY is set — self-hosters ship zero analytics and the posthog-js library is eliminated from the bundle entirely (dynamic import behind a build-time-inlined key guard). - src/analytics/analytics.js: no-op-safe wrapper (init/identify/reset/capture) with privacy-first defaults (identified_only, respect_dnt, masked inputs) - Automatic SPA pageviews via defaults:'2025-05-24' (history_change), so react-router navigations are captured without per-route wiring - Identify on profile load (App.jsx hook), reset on logout (ApiClient) - Capacitor-aware host resolution: native requires an absolute ingestion URL since the WebView has no same-origin backend for a relative /ingest proxy - Env vars surfaced via Config.js; documented in docs/analytics.md incl. managed + self-hosted reverse proxy (Go/Caddy) to dodge ad-blockers Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Optional, privacy-first PostHog product analytics
Adds an opt-in PostHog analytics layer for both the web app and the Capacitor mobile app, from a single codebase.
It is disabled by default: with no key set,
initAnalytics()returns immediately and the bundler eliminates theposthog-jsimport entirely (theif (!POSTHOG_KEY) returnguard is inlined at build time), so self-hosters ship zero analytics code and make zero network calls. Enabling is just a couple ofVITE_env vars + a rebuild.What's included
src/analytics/analytics.js— a small, no-op-safe wrapper (initAnalytics/identifyUser/resetAnalytics/captureEvent). Nothing else in the app talks to PostHog directly.defaults: '2025-05-24'(capture_pageview: 'history_change'). React Router v6 navigates through the History API, so every route change is captured — no per-route wiring, no double-counting.src/hooks/useAnalyticsIdentity.js, mounted inApp.jsx) — fires when the authenticated profile loads, covering password login, OAuth, and refreshed sessions uniformly.ApiClient.handleLogout) so a shared device doesn't merge two accounts.src/Config.js; placeholders added to.env,.env.development,.env.selfhosted.docs/analytics.md— full setup, reverse proxy, Capacitor notes, privacy, consent.Privacy-first defaults
person_profiles: 'identified_only'·respect_dnt: true· session-recording inputs masked (maskAllInputs) · replay off on native ·localStoragepersistence on native (cookies are unreliable on thecapacitor://origin).Environment variables
VITE_POSTHOG_KEYphc_...). Absent ⇒ fully disabled.VITE_POSTHOG_HOST/ingest(same-origin proxy), managed-proxy domain, or direct cloud host.VITE_POSTHOG_HOST_NATIVEVITE_POSTHOG_UI_HOSThttps://us.posthog.com.Reverse proxy (avoid ad-blockers)
Ad-blockers drop requests to
*.posthog.com. Two documented options:e.donetick.com, avoidanalytics/ph/tracking) via CNAME. Produces an absolute URL that works for web and mobile → set bothVITE_POSTHOG_HOSTandVITE_POSTHOG_HOST_NATIVEto it.net/http/httputilhandler (mount/ingest/on the backend that already serves the SPA) and a Caddy config in the docs. Rules: split/static+/array→ assets host, everything else → main host; rewrite theHostheader; allow ~64 MB bodies.Capacitor / mobile edge case⚠️
Native loads from
capacitor://localhost/https://localhost— no same-origin backend, so a relative/ingestwon't resolve. The wrapper detectsCapacitor.isNativePlatform()and usesVITE_POSTHOG_HOST_NATIVE(absolute), falling back safely to the direct cloud host if a relative web host is configured. Usesposthog-js(WebView), not the React Native SDK.Verification
npm run build(keyless): posthog-js not emitted (dead-code-eliminated).npm run buildwith a dummy key: posthog-js code-split into a lazy ~75 KB (gzip) chunk, loaded on demand.Follow-ups (out of scope)
/ingestGo reverse-proxy route todonetick/donetick.isAnalyticsEnabledis exported to gate it).🤖 Generated with Claude Code