| |
|
| | "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<User | null>(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); |
| | 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<boolean> => { |
| | if (!auth.currentUser) { |
| | addToast("Authentication not initialized. Please wait and try again.", { variant: "destructive" }); |
| | return false; |
| | } |
| | const newUid = auth.currentUser.uid; |
| |
|
| | |
| | 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 }); |
| |
|
| | |
| | |
| | |
| | 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'); |
| | } |
| | } |
| | }); |
| |
|
| | 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<Pick<User, 'displayName' | 'photoURL' | 'bio' | 'status' | 'privacySettings'>>) => { |
| | 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, |
| | }; |
| | }; |
| |
|