feat(analytics): add consent-gated activation-funnel analytics via PostHog
Installs are up 615% but 7-day retention is ~0% and we had no analytics SDK to see where users drop off. Adds a thin PostHog wrapper (src/lib/analytics.ts) that tracks app_opened, connection_form_submitted, connection_attempted, connection_succeeded/failed (with a coarse error_class, e.g. the known 401 auth bug), message_sent, and response_received. PostHog was chosen over Aptabase for its GMS-free JS-only RN SDK (fine for the F-Droid/no-Firebase build), EU-hosted/self-host option, and generous free tier. Analytics shares the exact same consent flag as Sentry (telemetry.ts now gates both) so zero network calls happen without explicit opt-in. Requires a new EXPO_PUBLIC_POSTHOG_KEY CI secret (wired into build.yml, publish-fdroid.yml, publish-play-store.yml, and documented in publish-app-store.yml alongside the existing Sentry secrets). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NJKAQ6HAikWGQK7PGZ5Y4E
This commit is contained in:
@@ -4,6 +4,7 @@ import * as Crypto from "expo-crypto"
|
||||
import type { ServerConnection, ConnectionType } from "../lib/types"
|
||||
import { createClient, type Client, type Project } from "../lib/sdk"
|
||||
import { addBreadcrumb } from "../lib/sentry"
|
||||
import { AnalyticsEvent, classifyConnectionError, track } from "../lib/analytics"
|
||||
import { buildAuth } from "../lib/auth"
|
||||
|
||||
const CONNECTIONS_KEY = "opencode_connections"
|
||||
@@ -243,6 +244,7 @@ export const useConnections = create<ConnectionsState>((set, get) => ({
|
||||
},
|
||||
|
||||
testConnection: async (connection, password) => {
|
||||
track(AnalyticsEvent.ConnectionAttempted)
|
||||
try {
|
||||
const client = createClient({
|
||||
baseUrl: connection.url,
|
||||
@@ -251,9 +253,12 @@ export const useConnections = create<ConnectionsState>((set, get) => ({
|
||||
})
|
||||
|
||||
await client.global.health()
|
||||
track(AnalyticsEvent.ConnectionSucceeded)
|
||||
return { ok: true }
|
||||
} catch (error) {
|
||||
return { ok: false, error: error instanceof Error ? error.message : String(error) }
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
track(AnalyticsEvent.ConnectionFailed, { error_class: classifyConnectionError(message) })
|
||||
return { ok: false, error: message }
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user