Files
readest/apps/readest-app/src/utils/telemetry.ts
T
Huang Xin 48d52ea898 feat(telemetry): opt-out by default for new users; consent prompt for 10% (#4340)
Fixes #4339. PostHog telemetry was previously enabled by default for every
install, which surprised privacy-conscious users on self-hosted setups.

Behavior for new users only (existing users are migrated to a decision that
preserves their current `telemetryEnabled` setting):
  - 90% are silently opted out on first launch.
  - 10% see a one-time consent prompt; accepting opts in, declining opts out.
Decision is persisted via a new `readest-telemetry-decision` localStorage key
so subsequent boots don't re-roll. PostHog now inits with
`opt_out_capturing_by_default` so brand-new users never ping before the
decision is finalized.

New `TelemetryConsentDialog` uses the project's `btn-contrast` (theme-neutral)
CTA and `eink-bordered` chassis so it renders correctly under `[data-eink]`
without color-mode-only assumptions. Strings translated across all 33 locales.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 15:19:05 +02:00

47 lines
1.6 KiB
TypeScript

import posthog from 'posthog-js';
export const TELEMETRY_OPT_OUT_KEY = 'readest-telemetry-opt-out';
export const TELEMETRY_DECISION_KEY = 'readest-telemetry-decision';
export type TelemetryDecision = 'opt-in' | 'opt-out' | 'pending';
/** Fraction of new users shown the consent prompt; the rest are opted out silently. */
export const TELEMETRY_PROMPT_BUCKET_RATE = 0.1;
export const hasOptedOutTelemetry = () => {
return localStorage.getItem(TELEMETRY_OPT_OUT_KEY) === 'true';
};
export const getTelemetryDecision = (): TelemetryDecision | null => {
if (typeof window === 'undefined') return null;
const value = localStorage.getItem(TELEMETRY_DECISION_KEY);
if (value === 'opt-in' || value === 'opt-out' || value === 'pending') return value;
return null;
};
export const setTelemetryDecision = (decision: TelemetryDecision) => {
localStorage.setItem(TELEMETRY_DECISION_KEY, decision);
};
/** Returns true with probability TELEMETRY_PROMPT_BUCKET_RATE. */
export const rollIntoTelemetryPromptBucket = (rng: () => number = Math.random) => {
return rng() < TELEMETRY_PROMPT_BUCKET_RATE;
};
export const captureEvent = (event: string, properties?: Record<string, unknown>) => {
if (!hasOptedOutTelemetry()) {
posthog.capture(event, properties);
}
};
export const optInTelemetry = () => {
localStorage.setItem(TELEMETRY_OPT_OUT_KEY, 'false');
setTelemetryDecision('opt-in');
posthog.opt_in_capturing();
};
export const optOutTelemetry = () => {
localStorage.setItem(TELEMETRY_OPT_OUT_KEY, 'true');
setTelemetryDecision('opt-out');
posthog.opt_out_capturing();
};