← back to Nineoh Guide

apps/mobile/analytics.ts

70 lines

// analytics.ts — Firebase Analytics (→ GA4) + Crashlytics for the 90210 guide.
//
// Screen views + custom events flow to GA4 (viewable in the Firebase / GA4 iOS
// apps); Crashlytics auto-captures native + unhandled-JS crashes and lets us
// record handled errors. All calls are safe no-ops if the native Firebase
// module isn't present (e.g. Expo Go / a build without GoogleService-Info.plist),
// so the app never crashes because analytics isn't wired.
import analytics from "@react-native-firebase/analytics";
import crashlytics from "@react-native-firebase/crashlytics";

let ready = true;
try {
  analytics();
  // Firebase's iOS default is collection OFF (GoogleService-Info.plist ships
  // IS_ANALYTICS_ENABLED=false). We intentionally DO NOT enable it here at
  // module-import time: that let a screen_view transmit before the App Tracking
  // Transparency prompt was answered (an App Store 5.1.2 flag). Collection is
  // now enabled only AFTER ATT resolves, via enableCollectionAfterATT() below,
  // called from App.tsx. Until then Firebase stays at its default (OFF), so the
  // early logScreen effect transmits nothing pre-consent.
} catch {
  ready = false;
}

/**
 * Enable Firebase Analytics + Crashlytics collection. MUST be called ONLY after
 * the ATT prompt has resolved (see App.tsx), so nothing is transmitted
 * pre-consent. Safe no-op if the native Firebase module isn't present.
 */
export async function enableCollectionAfterATT(): Promise<void> {
  if (!ready) return;
  try {
    await analytics().setAnalyticsCollectionEnabled(true);
    await crashlytics().setCrashlyticsCollectionEnabled(true);
  } catch {
    /* non-fatal */
  }
}

/** Log a screen view (call whenever the visible tab/screen changes). */
export async function logScreen(screen: string): Promise<void> {
  if (!ready) return;
  try {
    await analytics().logScreenView({ screen_name: screen, screen_class: screen });
  } catch {
    /* non-fatal */
  }
}

/** Log a custom event (e.g. episode_open, watchlist_add). */
export async function logEvent(name: string, params?: Record<string, unknown>): Promise<void> {
  if (!ready) return;
  try {
    await analytics().logEvent(name, params as Record<string, string | number | boolean>);
  } catch {
    /* non-fatal */
  }
}

/** Record a handled error to Crashlytics (unhandled crashes are auto-captured). */
export function recordError(err: unknown, context?: string): void {
  if (!ready) return;
  try {
    if (context) crashlytics().log(context);
    crashlytics().recordError(err instanceof Error ? err : new Error(String(err)));
  } catch {
    /* non-fatal */
  }
}