File size: 7,017 Bytes
cc276cc | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
"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); // 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<boolean> => {
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<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,
};
};
|