import { Platform } from "react-native"; import Constants from "expo-constants"; // Import default export to ensure namespace registration runs import analytics, { getAnalytics, logEvent as firebaseLogEvent, setUserId as firebaseSetUserId, setUserProperty, setAnalyticsCollectionEnabled, resetAnalyticsData, FirebaseAnalyticsTypes, } from "@react-native-firebase/analytics"; // Force the analytics module to be retained (prevents tree-shaking) const _analyticsModule = analytics; let isInitialized = false; let analyticsInstance: FirebaseAnalyticsTypes.Module | null = null; let analyticsUnavailable = false; let analyticsUnavailableLogged = false; function getErrorMessage(error: unknown): string { if (!error) return ""; if (typeof error === "string") return error; if (error instanceof Error) return error.message; if (typeof (error as { message?: unknown }).message === "string") { return (error as { message?: string }).message || ""; } return ""; } function isAnalyticsSupported(): boolean { if (Platform.OS === "web") return false; if (Constants.appOwnership === "expo") return false; return true; } function logAnalyticsUnavailableOnce(reason: string) { if (analyticsUnavailableLogged) return; analyticsUnavailableLogged = true; if (__DEV__) console.log("[Analytics] " + reason); } function getAnalyticsInstance(): FirebaseAnalyticsTypes.Module | null { if (analyticsUnavailable) { return null; } if (!isAnalyticsSupported()) { analyticsUnavailable = true; logAnalyticsUnavailableOnce("Skipping Firebase Analytics on Expo Go or web."); return null; } if (analyticsInstance) { return analyticsInstance; } try { analyticsInstance = getAnalytics(); return analyticsInstance; } catch (error) { const message = getErrorMessage(error); if (message.includes("module could not be found")) { analyticsUnavailable = true; logAnalyticsUnavailableOnce( "Firebase Analytics native module missing. Rebuild with a dev client or native app." ); return null; } console.warn("[Analytics] Failed to get analytics instance:", error); return null; } } export async function initAnalytics() { if (isInitialized) { return; } // Skip initialization in non-RN environments (e.g., during tsc/Node.js) if (typeof window === "undefined" && typeof global !== "undefined" && !("__BUNDLE_START_TIME__" in global)) { return; } try { const analytics = getAnalyticsInstance(); if (analytics) { await setAnalyticsCollectionEnabled(analytics, true); isInitialized = true; console.log("[Analytics] Firebase Analytics initialized"); } } catch (error) { console.warn("[Analytics] Failed to initialize Firebase Analytics:", error); } } // Log a custom event export function logEvent( name: string, params?: Record ) { try { if (__DEV__) console.log("[Analytics] Event:", name, params); const analytics = getAnalyticsInstance(); if (analytics) { firebaseLogEvent(analytics, name, params); } } catch (error) { console.warn("[Analytics] Failed to log event:", error); } } // Set user ID for tracking export function setUserId(userId: string) { try { if (__DEV__) console.log("[Analytics] Set user ID:", userId); const analytics = getAnalyticsInstance(); if (analytics) { firebaseSetUserId(analytics, userId); } } catch (error) { console.warn("[Analytics] Failed to set user ID:", error); } } // Clear user ID on logout export function clearUserId() { try { if (__DEV__) console.log("[Analytics] Cleared user ID"); const analytics = getAnalyticsInstance(); if (analytics) { firebaseSetUserId(analytics, null); resetAnalyticsData(analytics); } } catch (error) { console.warn("[Analytics] Failed to clear user ID:", error); } } // Set user properties export function setUserProperties( properties: Record ) { try { if (__DEV__) console.log("[Analytics] Set user properties:", properties); const analytics = getAnalyticsInstance(); if (analytics) { // Firebase Analytics requires setting user properties one at a time for (const [key, value] of Object.entries(properties)) { setUserProperty(analytics, key, value); } } } catch (error) { console.warn("[Analytics] Failed to set user properties:", error); } } // Log screen view export function logScreenView(screenName: string, screenClass?: string) { try { if (__DEV__) console.log("[Analytics] Screen view:", screenName); const analytics = getAnalyticsInstance(); if (analytics) { firebaseLogEvent(analytics, "screen_view", { screen_name: screenName, screen_class: screenClass ?? screenName, }); } } catch (error) { console.warn("[Analytics] Failed to log screen view:", error); } } // Flush events (useful before app backgrounding) export function flush() { // Firebase Analytics batches and sends events automatically // No manual flush needed, but we keep the function for API compatibility if (__DEV__) console.log("[Analytics] Flush called (no-op for Firebase)"); } function truncateParam(value: string, maxLength = 100) { if (value.length <= maxLength) return value; return value.slice(0, maxLength - 1) + "…"; } function safeParam(value: unknown, maxLength = 100): string { if (typeof value === "string") return truncateParam(value, maxLength); if (typeof value === "number" || typeof value === "boolean") return String(value); if (value instanceof Error) return truncateParam(value.message, maxLength); return value == null ? "" : truncateParam(String(value), maxLength); } // Pre-defined event helpers for common actions export const Events = { // Auth events signUp: (method: string) => logEvent("sign_up", { method }), signIn: (method: string) => logEvent("login", { method }), signOut: () => logEvent("sign_out"), // Onboarding events onboardingStart: () => logEvent("onboarding_start"), onboardingStageComplete: (stage: string) => logEvent("onboarding_stage_complete", { stage }), onboardingComplete: () => logEvent("onboarding_complete"), // Pairing events coupleCreated: () => logEvent("couple_created"), coupleJoined: () => logEvent("couple_joined"), codeShared: () => logEvent("invite_code_shared"), pairingCancelled: () => logEvent("pairing_cancelled"), // Swipe/question events questionAnswered: (answer: string, packId?: string) => logEvent("question_answered", { answer, pack_id: packId ?? "unknown" }), questionSkipped: () => logEvent("question_skipped"), questionShared: (destination: "instagram" | "messages" | "more") => logEvent("question_shared", { destination }), matchCreated: (matchType: string) => logEvent("match_created", { match_type: matchType }), allQuestionsExhausted: () => logEvent("all_questions_exhausted"), pendingQuestionsPromptViewed: (count: number) => logEvent("pending_questions_prompt_viewed", { question_count: count }), pendingQuestionsPromptTapped: (count: number) => logEvent("pending_questions_prompt_tapped", { question_count: count }), // Chat events matchViewed: () => logEvent("match_viewed"), messageSent: () => logEvent("message_sent"), mediaUploaded: () => logEvent("chat_media_uploaded"), // Subscription events paywallShown: (source: string) => logEvent("paywall_shown", { source }), paywallClosed: (source: string, reason: string) => logEvent("paywall_closed", { source, reason }), purchaseInitiated: (packageType: string) => logEvent("purchase_initiated", { package_type: packageType }), purchaseCancelled: (packageType: string, errorCode?: unknown) => logEvent("purchase_cancelled", { package_type: packageType, error_code: safeParam(errorCode) || "unknown", }), purchaseFailed: (packageType: string, errorCode?: unknown, errorMessage?: unknown) => logEvent("purchase_failed", { package_type: packageType, error_code: safeParam(errorCode) || "unknown", error_message: safeParam(errorMessage), }), purchaseCompleted: (packageType: string) => logEvent("purchase_completed", { package_type: packageType }), purchaseRestored: () => logEvent("purchase_restored"), // Pack events packEnabled: (packId: string) => logEvent("pack_enabled", { pack_id: packId }), packDisabled: (packId: string) => logEvent("pack_disabled", { pack_id: packId }), // App lifecycle events appOpened: (type: "cold" | "warm") => logEvent("app_opened", { type }), // Profile events profileUpdated: (fields: string[]) => logEvent("profile_updated", { fields: fields.join(",") }), avatarUploaded: () => logEvent("avatar_uploaded"), avatarSkipped: () => logEvent("avatar_skipped"), avatarPromptShown: (source: string) => logEvent("avatar_prompt_shown", { source }), avatarPromptAccepted: (source: string) => logEvent("avatar_prompt_accepted", { source }), avatarPromptDismissed: (source: string) => logEvent("avatar_prompt_dismissed", { source }), // Notification events notificationPermissionGranted: () => logEvent("notification_permission_granted"), notificationPermissionDenied: () => logEvent("notification_permission_denied"), // Relationship events relationshipEnded: () => logEvent("relationship_ended"), accountDeleted: () => logEvent("account_deleted"), // Milestone events firstMatch: () => logEvent("first_match"), milestoneMatch: (count: number) => logEvent("milestone_match", { match_count: count }), // Invite events inviteCodeCopied: () => logEvent("invite_code_copied"), inviteLinkOpened: (source: "universal_link" | "scheme" | "clipboard") => logEvent("invite_link_opened", { source }), pairingCodePrefilled: () => logEvent("pairing_code_prefilled"), invitePromptViewed: (surface: "swipe_banner" | "pairing_waiting" | "swipe_no_partner", sealedBucket: string) => logEvent("invite_prompt_viewed", { surface, sealed_bucket: sealedBucket }), inviteShareOpened: (surface: "pairing_waiting", channel: "system" | "sms" | "whatsapp" | "copy_link" | "copy_code") => logEvent("invite_share_opened", { surface, channel }), pairingJoinAttempted: (codeSource: "link" | "stash" | "clipboard" | "manual") => logEvent("pairing_join_attempted", { code_source: codeSource }), pairingJoinFailed: (reason: "invalid_shape" | "code_not_found" | "couple_full" | "already_paired" | "network" | "unknown", codeSource: "link" | "stash" | "clipboard" | "manual") => logEvent("pairing_join_failed", { reason, code_source: codeSource }), pairingCompleted: (role: "inviter" | "joiner", codeSource: "link" | "stash" | "clipboard" | "manual", hadSealed: boolean) => logEvent("pairing_completed", { role, code_source: codeSource, had_sealed: hadSealed }), pairingUnlockViewed: (role: "inviter" | "joiner", unlockedBucket: string) => logEvent("pairing_unlock_viewed", { role, unlocked_bucket: unlockedBucket }), pairingUnlockCtaTapped: (role: "inviter" | "joiner", cta: "view_matches" | "start_session" | "dismiss") => logEvent("pairing_unlock_cta_tapped", { role, cta }), pairingCancelPrompted: (surface: "waiting" | "conflict") => logEvent("pairing_cancel_prompted", { surface }), clipboardInviteOfferShown: () => logEvent("clipboard_invite_offer_shown"), clipboardInviteOfferResolved: (outcome: "accepted" | "dismissed") => logEvent("clipboard_invite_offer_resolved", { outcome }), inviteConflictShown: (state: "already_paired" | "waiting_own_code") => logEvent("invite_conflict_shown", { state }), inviteConflictResolved: (state: "already_paired" | "waiting_own_code", choice: "cancel_and_join" | "keep_own" | "acknowledged") => logEvent("invite_conflict_resolved", { state, choice }), // Quiz events quizStarted: () => logEvent("quiz_started"), quizCompleted: (scorePercent: number) => logEvent("quiz_completed", { score_percent: scorePercent }), quizShared: () => logEvent("quiz_shared"), quizPartnerNudged: () => logEvent("quiz_partner_nudged"), // Tutorial events tutorialStarted: (tutorial: "swipe" | "matches") => logEvent("tutorial_started", { tutorial }), tutorialStepViewed: (tutorial: "swipe" | "matches", step: number, stepName: string) => logEvent("tutorial_step_viewed", { tutorial, step, step_name: stepName }), tutorialCompleted: (tutorial: "swipe" | "matches") => logEvent("tutorial_completed", { tutorial }), tutorialSkipped: (tutorial: "swipe" | "matches", atStep: number) => logEvent("tutorial_skipped", { tutorial, at_step: atStep }), };