diff --git a/app/_layout.tsx b/app/_layout.tsx index 8e60189e..05249d46 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -3,7 +3,7 @@ import { Slot, SplashScreen } from 'expo-router'; import { useEffect } from 'react'; import { AuthProvider } from '../contexts/AuthContext'; import { UserProfileProvider } from '../contexts/UserProfileContext'; -import { getAnalyticsInstance } from '../lib/firebase'; +import { activateAnalytics } from '../lib/analytics'; import "../global.css"; export default function RootLayout() { @@ -13,14 +13,9 @@ export default function RootLayout() { console.log('[RootLayout] Root layout effect running - hiding splash screen'); SplashScreen.hideAsync(); - // Initialize Firebase Analytics lazily - const analytics = getAnalyticsInstance(); - if (analytics) { - console.log('[Analytics] Firebase Analytics initialized'); - } else { - console.log('[Analytics] Firebase Analytics not available (likely not web platform)'); - } - }, []); + // Kick off the safe, asynchronous activation of the real analytics. + activateAnalytics(); + }, []); // Run only once console.log('[RootLayout] Rendering providers and slot'); return ( diff --git a/lib/analytics-proxy.ts b/lib/analytics-proxy.ts new file mode 100644 index 00000000..ceb455b3 --- /dev/null +++ b/lib/analytics-proxy.ts @@ -0,0 +1,6 @@ +/** + * A completely safe, inert analytics proxy. + * These functions do nothing and are used during app startup to prevent crashes. + */ +export const logAnalyticsEvent = async (eventName: string, eventParams?: { [key: string]: any }): Promise => Promise.resolve(); +export const setAnalyticsUserProperties = async (properties: { [key: string]: any }): Promise => Promise.resolve(); \ No newline at end of file diff --git a/lib/analytics-real.ts b/lib/analytics-real.ts new file mode 100644 index 00000000..39f1ae0f --- /dev/null +++ b/lib/analytics-real.ts @@ -0,0 +1,46 @@ +import { getFirebaseApp } from './firebase'; +import type { Analytics } from 'firebase/analytics'; + +let analyticsInstance: Analytics | null = null; +let initializationPromise: Promise | null = null; + +const getAnalyticsInstance = (): Promise => { + if (initializationPromise) return initializationPromise; + + initializationPromise = (async () => { + if (analyticsInstance) return analyticsInstance; + if (typeof window === 'undefined') return null; + + try { + const { getAnalytics, isSupported } = await import('firebase/analytics'); + if (!(await isSupported())) return null; + + const app = await getFirebaseApp(); + if (!app) return null; + + const instance = getAnalytics(app); + + analyticsInstance = instance; + return instance; + } catch (error) { + console.error('[Analytics REAL] CRITICAL: Real initialization failed.', error); + return null; + } + })(); + + return initializationPromise; +}; + +export const logAnalyticsEvent = async (eventName: string, eventParams?: { [key: string]: any }): Promise => { + const instance = await getAnalyticsInstance(); + if (!instance) return; + const { logEvent } = await import('firebase/analytics'); + logEvent(instance, eventName, eventParams); +}; + +export const setAnalyticsUserProperties = async (properties: { [key: string]: any }): Promise => { + const instance = await getAnalyticsInstance(); + if (!instance) return; + const { setUserProperties } = await import('firebase/analytics'); + setUserProperties(instance, properties); +}; \ No newline at end of file diff --git a/lib/analytics.ts b/lib/analytics.ts index c326a513..9ae19817 100644 --- a/lib/analytics.ts +++ b/lib/analytics.ts @@ -1,5 +1,4 @@ -import { logEvent, setUserProperties } from 'firebase/analytics'; -import { getAnalyticsInstance } from './firebase'; +import * as Decoy from './analytics-proxy'; // Custom event names as defined in the issue export const ANALYTICS_EVENTS = { @@ -58,34 +57,44 @@ export const USER_PROPERTIES = { USER_SEGMENT: 'user_segment', // Derived from profile settings } as const; -// Helper function to safely log analytics events -export const logAnalyticsEvent = (eventName: string, parameters?: Record) => { - const analytics = getAnalyticsInstance(); - if (analytics) { - try { - logEvent(analytics, eventName, parameters); - console.log(`[Analytics] Event logged: ${eventName}`, parameters); - } catch (error) { - console.error(`[Analytics] Failed to log event ${eventName}:`, error); - } - } else { - console.log(`[Analytics] Analytics not available, would log: ${eventName}`, parameters); - } +let isActivationStarted = false; + +// Function type definitions +type LogEventFunction = (eventName: string, eventParams?: { [key: string]: any }) => Promise; +type SetPropertiesFunction = (properties: { [key: string]: any }) => Promise; + +// By default, all exports point to the safe decoy functions. +let _logAnalyticsEvent: LogEventFunction = Decoy.logAnalyticsEvent; +let _setAnalyticsUserProperties: SetPropertiesFunction = Decoy.setAnalyticsUserProperties; + +/** + * Call this ONCE from the root of the app after it has stabilized. + * It will load the real analytics module and hot-swap the functions. + */ +export const activateAnalytics = () => { + if (isActivationStarted) return; + isActivationStarted = true; + + setTimeout(() => { + import('./analytics-real') + .then(RealAnalytics => { + _logAnalyticsEvent = RealAnalytics.logAnalyticsEvent; + _setAnalyticsUserProperties = RealAnalytics.setAnalyticsUserProperties; + console.log('[Analytics Controller] ✅ Analytics ACTIVATED. Real functions are now live.'); + }) + .catch(error => { + console.error('[Analytics Controller] ❌ FAILED TO ACTIVATE ANALYTICS. App will continue safely with decoy.', error); + }); + }, 500); // Generous delay to ensure app is stable }; -// Helper function to safely set user properties -export const setAnalyticsUserProperties = (properties: Record) => { - const analytics = getAnalyticsInstance(); - if (analytics) { - try { - setUserProperties(analytics, properties); - console.log(`[Analytics] User properties set:`, properties); - } catch (error) { - console.error(`[Analytics] Failed to set user properties:`, error); - } - } else { - console.log(`[Analytics] Analytics not available, would set properties:`, properties); - } +// Export wrapper functions that call the swappable implementations. +export const logAnalyticsEvent = async (eventName: string, parameters?: Record): Promise => { + return await _logAnalyticsEvent(eventName, parameters); +}; + +export const setAnalyticsUserProperties = async (properties: Record): Promise => { + return await _setAnalyticsUserProperties(properties); }; // Helper function to set personalization user properties from profile diff --git a/lib/firebase.ts b/lib/firebase.ts index 868fd008..9589d67e 100644 --- a/lib/firebase.ts +++ b/lib/firebase.ts @@ -1,6 +1,5 @@ import { initializeApp, FirebaseApp } from "firebase/app"; import { getAuth, Auth } from "firebase/auth"; -import { getAnalytics, Analytics } from "firebase/analytics"; import { getFirestore, Firestore } from "firebase/firestore"; import Constants from "expo-constants"; @@ -19,9 +18,8 @@ const firebaseConfig = Constants.expoConfig?.extra?.firebase || { let app: FirebaseApp | undefined = undefined; let authInstance: Auth | undefined = undefined; let dbInstance: Firestore | undefined = undefined; -let analyticsInstance: Analytics | undefined = undefined; -const getFirebaseApp = (): FirebaseApp => { +export const getFirebaseApp = (): FirebaseApp => { if (!app) { try { console.log('[Firebase] Initializing Firebase app...'); @@ -40,6 +38,10 @@ const getFirebaseApp = (): FirebaseApp => { return app; }; +export const getFullFirebaseConfig = () => { + return firebaseConfig; +}; + export const getAuthInstance = (): Auth => { if (!authInstance) { try { @@ -68,31 +70,6 @@ export const getDbInstance = (): Firestore => { return dbInstance; }; -export const getAnalyticsInstance = (): Analytics | undefined => { - if (typeof window === "undefined") { - console.log('[Firebase] Analytics not available - not in browser environment'); - return undefined; - } - - if (!analyticsInstance) { - try { - console.log('[Firebase] Initializing Firebase Analytics...'); - analyticsInstance = getAnalytics(getFirebaseApp()); - console.log('[Firebase] Firebase Analytics initialized successfully'); - } catch (error) { - console.error('[Firebase] Analytics initialization failed:', error); - console.error('[Firebase] Analytics error details:', { - message: error?.message, - stack: error?.stack, - name: error?.name - }); - return undefined; - } - } - - return analyticsInstance; -}; - // For backward compatibility, provide auth and db as getters // These will initialize on first access rather than at module load time export const auth = new Proxy({} as Auth, {