Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 4 additions & 9 deletions app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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 (
Expand Down
6 changes: 6 additions & 0 deletions lib/analytics-proxy.ts
Original file line number Diff line number Diff line change
@@ -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<void> => Promise.resolve();
export const setAnalyticsUserProperties = async (properties: { [key: string]: any }): Promise<void> => Promise.resolve();
46 changes: 46 additions & 0 deletions lib/analytics-real.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { getFirebaseApp } from './firebase';
import type { Analytics } from 'firebase/analytics';

let analyticsInstance: Analytics | null = null;
let initializationPromise: Promise<Analytics | null> | null = null;

const getAnalyticsInstance = (): Promise<Analytics | null> => {
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<void> => {
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<void> => {
const instance = await getAnalyticsInstance();
if (!instance) return;
const { setUserProperties } = await import('firebase/analytics');
setUserProperties(instance, properties);
};
65 changes: 37 additions & 28 deletions lib/analytics.ts
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down Expand Up @@ -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<string, any>) => {
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<void>;
type SetPropertiesFunction = (properties: { [key: string]: any }) => Promise<void>;

// 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<string, any>) => {
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<string, any>): Promise<void> => {
return await _logAnalyticsEvent(eventName, parameters);
};

export const setAnalyticsUserProperties = async (properties: Record<string, any>): Promise<void> => {
return await _setAnalyticsUserProperties(properties);
};

// Helper function to set personalization user properties from profile
Expand Down
33 changes: 5 additions & 28 deletions lib/firebase.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -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...');
Expand All @@ -40,6 +38,10 @@ const getFirebaseApp = (): FirebaseApp => {
return app;
};

export const getFullFirebaseConfig = () => {
return firebaseConfig;
};

export const getAuthInstance = (): Auth => {
if (!authInstance) {
try {
Expand Down Expand Up @@ -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, {
Expand Down