mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-29 22:23:01 +00:00
- Introduced Sentry for error tracking, initialized in the application to capture errors while respecting user privacy. - Added a new analytics service to manage user consent for data collection, ensuring only non-personal data is sent to Sentry. - Updated Redux state to track user analytics preferences and modified onboarding steps to include analytics consent. - Enhanced the PrivacyPanel component to allow users to toggle analytics sharing. - Removed the AnalyticsStep from onboarding as its functionality is now integrated into the PrivacyPanel. This update improves error reporting capabilities while prioritizing user privacy and consent.
136 lines
4.3 KiB
TypeScript
136 lines
4.3 KiB
TypeScript
/**
|
|
* Analytics & Sentry service
|
|
*
|
|
* Manages Sentry error reporting gated behind user analytics consent.
|
|
* Designed to capture ONLY:
|
|
* - Error message & stack trace
|
|
* - Device / browser metadata (via Sentry's default user-agent parsing)
|
|
* - Source file location (via source maps)
|
|
*
|
|
* Explicitly strips:
|
|
* - All breadcrumbs (console, click, network, etc.)
|
|
* - Redux state / localStorage / sessionStorage
|
|
* - User PII (IP address, cookies)
|
|
* - Request bodies / headers
|
|
* - Session replay
|
|
*/
|
|
|
|
import * as Sentry from "@sentry/react";
|
|
import { store } from "../store";
|
|
|
|
const SENTRY_DSN = import.meta.env.VITE_SENTRY_DSN as string | undefined;
|
|
const IS_DEV = Boolean(import.meta.env.DEV) || import.meta.env.MODE === "development";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/** Check if the current user has opted into analytics. */
|
|
export function isAnalyticsEnabled(): boolean {
|
|
const state = store.getState();
|
|
const userId = state.user?.user?._id;
|
|
if (!userId) return false;
|
|
return state.auth.isAnalyticsEnabledByUser[userId] === true;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Sentry initialisation
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function initSentry(): void {
|
|
if (!SENTRY_DSN) return;
|
|
|
|
Sentry.init({
|
|
dsn: SENTRY_DSN,
|
|
environment: IS_DEV ? "development" : "production",
|
|
enabled: !IS_DEV, // disable in dev builds
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Privacy: disable EVERYTHING that could leak sensitive state
|
|
// -----------------------------------------------------------------------
|
|
|
|
// No session replay
|
|
replaysSessionSampleRate: 0,
|
|
replaysOnErrorSampleRate: 0,
|
|
|
|
// No performance / tracing
|
|
tracesSampleRate: 0,
|
|
|
|
// No breadcrumbs at all (console, clicks, network, etc.)
|
|
defaultIntegrations: false,
|
|
integrations: [
|
|
// Only keep the bare-minimum integrations for stack traces
|
|
Sentry.functionToStringIntegration(),
|
|
Sentry.linkedErrorsIntegration(),
|
|
Sentry.dedupeIntegration(),
|
|
Sentry.browserApiErrorsIntegration(),
|
|
Sentry.globalHandlersIntegration(),
|
|
],
|
|
|
|
// Strip IP address
|
|
sendDefaultPii: false,
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Gate every event behind the user's analytics consent flag
|
|
// -----------------------------------------------------------------------
|
|
beforeSend(event) {
|
|
if (!isAnalyticsEnabled()) return null;
|
|
|
|
// Strip any breadcrumbs that somehow snuck in
|
|
event.breadcrumbs = [];
|
|
|
|
// Strip request data (cookies, headers, body)
|
|
delete event.request;
|
|
|
|
// Strip user PII — keep only a stable anonymous ID
|
|
const userId = store.getState().user?.user?._id;
|
|
event.user = userId ? { id: userId } : undefined;
|
|
|
|
// Strip any extra/contexts that could contain Redux or localStorage data
|
|
delete event.extra;
|
|
event.contexts = {
|
|
// Keep only OS / browser / device metadata
|
|
os: event.contexts?.os,
|
|
browser: event.contexts?.browser,
|
|
device: event.contexts?.device,
|
|
};
|
|
|
|
return event;
|
|
},
|
|
|
|
beforeSendTransaction() {
|
|
// Block all transactions (performance traces)
|
|
return null;
|
|
},
|
|
|
|
// Ignore common non-actionable errors
|
|
ignoreErrors: [
|
|
"ResizeObserver loop",
|
|
"Network request failed",
|
|
"Load failed",
|
|
"AbortError",
|
|
],
|
|
});
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Consent sync — call when the user toggles analytics on/off
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Re-sync Sentry's enabled state after the user changes their consent.
|
|
* Called from onboarding and settings.
|
|
*/
|
|
export function syncAnalyticsConsent(enabled: boolean): void {
|
|
const client = Sentry.getClient();
|
|
if (!client) return;
|
|
|
|
if (enabled) {
|
|
// Client is already initialised; events will pass through beforeSend
|
|
// because isAnalyticsEnabled() will now return true.
|
|
} else {
|
|
// Flush any pending events, then future events will be dropped by beforeSend.
|
|
void Sentry.flush(2000);
|
|
}
|
|
}
|