"use client"; import { useState, useCallback, useEffect } from 'react'; import { onAuthStateChanged, signInAnonymously, User as FirebaseUser, signOut as firebaseSignOut } from "firebase/auth"; import { doc, getDoc, onSnapshot, updateDoc, arrayUnion, collection, query, where, getDocs, writeBatch } from "firebase/firestore"; import { ref, set, serverTimestamp } from 'firebase/database'; import { cryptoService } from '@/lib/crypto-service'; import { fingerprintService } from '@/lib/fingerprint-service'; import type { User } from '@/lib/types'; import { useFirebase } from '@/contexts/firebase-context'; import { Capacitor } from '@capacitor/core'; import { Device } from '@capacitor/device'; import { useToast } from '@/hooks/use-toast'; export const useAuthCore = () => { const { auth, db, rtdb } = useFirebase(); const { toast } = useToast(); const addToast = useCallback((message: string, options?: { variant?: 'default' | 'destructive' }) => { toast({ title: message, variant: options?.variant }); }, [toast]); const [currentUser, setCurrentUser] = useState(null); const [authStatus, setAuthStatus] = useState<'loading' | 'onboarding' | 'recovery' | 'authenticated'>('loading'); const handleAuthenticatedUser = useCallback(async (user: FirebaseUser) => { const userProfileRef = doc(db, 'users', user.uid); const userSnap = await getDoc(userProfileRef); if (userSnap.exists()) { localStorage.setItem('userId', user.uid); // Save userId for Capacitor await cryptoService.setupUserKeys(user.uid, rtdb); const fingerprint = await fingerprintService.generate(); const existingData = userSnap.data(); if (fingerprint && (!existingData.deviceFingerprints || !existingData.deviceFingerprints.includes(fingerprint))) { await updateDoc(userProfileRef, { deviceFingerprints: arrayUnion(fingerprint) }); } const unsub = onSnapshot(userProfileRef, (doc) => { if (doc.exists()) { setCurrentUser({ uid: doc.id, ...doc.data() } as User); } else { setCurrentUser(null); setAuthStatus('onboarding'); } }); setAuthStatus('authenticated'); return unsub; } else { setAuthStatus('onboarding'); } return () => {}; }, [db, rtdb]); const recoverAccount = useCallback(async (recoveryId: string): Promise => { if (!auth.currentUser) { addToast("Authentication not initialized. Please wait and try again.", { variant: "destructive" }); return false; } const newUid = auth.currentUser.uid; // 1. Find original UID from recoveryId const recoveryDocRef = doc(db, 'recovery', recoveryId); const recoverySnap = await getDoc(recoveryDocRef); if (!recoverySnap.exists()) { addToast("Invalid Recovery ID.", { variant: "destructive" }); return false; } const originalUid = recoverySnap.data().uid; if (originalUid === newUid) { addToast("This account is already active on this device.", { variant: "default" }); await handleAuthenticatedUser(auth.currentUser); return true; } addToast("Account found! Migrating to new device..."); try { const batch = writeBatch(db); const originalProfileRef = doc(db, 'users', originalUid); const originalProfileSnap = await getDoc(originalProfileRef); if (!originalProfileSnap.exists()) { throw new Error("Original user profile not found. The account may be corrupted."); } const originalProfileData = originalProfileSnap.data() as User; const publicId = originalProfileData.publicId; const newProfileData = { ...originalProfileData, uid: newUid }; batch.set(doc(db, 'users', newUid), newProfileData); batch.update(doc(db, 'publicIds', publicId), { uid: newUid }); batch.update(recoveryDocRef, { uid: newUid }); // To be safe, we are not deleting the old user document. // This prevents data loss if the user is logged in on another device. await batch.commit(); addToast("Account recovered successfully!"); await handleAuthenticatedUser(auth.currentUser); return true; } catch (error: any) { console.error("Account migration failed:", error); addToast(`Recovery failed: ${error.message}`, { variant: "destructive" }); return false; } }, [auth, db, addToast, handleAuthenticatedUser]); useEffect(() => { let isMounted = true; const unsubscribe = onAuthStateChanged(auth, async (user) => { if (!isMounted) return; if (user) { await handleAuthenticatedUser(user); } else { try { await signInAnonymously(auth); } catch (error) { console.error("Anonymous sign-in failed:", error); if (isMounted) setAuthStatus('recovery'); // Go to recovery if sign-in fails } } }); return () => { isMounted = false; unsubscribe(); }; }, [auth, handleAuthenticatedUser]); const signOutUser = useCallback(async () => { const userToSignOut = auth.currentUser; if (!userToSignOut && !currentUser) { setAuthStatus('recovery'); window.location.reload(); return; }; const uidToUpdate = currentUser?.uid || userToSignOut?.uid; try { if(uidToUpdate) { const presenceRef = ref(rtdb, `presence/${uidToUpdate}`); await set(presenceRef, { isOnline: false, lastSeen: serverTimestamp() }); } await firebaseSignOut(auth); } catch (error) { console.error("Sign out error:", error); } finally { setCurrentUser(null); setAuthStatus('recovery'); window.location.reload(); } }, [auth, rtdb, currentUser]); const updateUserProfile = useCallback(async (profileData: Partial>) => { if (!currentUser) return; const userRef = doc(db, 'users', currentUser.uid); await updateDoc(userRef, profileData); }, [currentUser, db]); return { currentUser, authStatus, setAuthStatus, signOutUser, recoverAccount, updateUserProfile, auth, handleAuthenticatedUser, }; };