import { create } from 'zustand'; import { signInWithCustomToken, signOut as firebaseSignOut, } from 'firebase/auth'; import { apiClient } from '@/api/client'; import type { Human } from '@/api/types'; import { firebaseAuth } from '@/firebase'; import { logError, ApiError } from '@/lib/errors'; import { useSessionStore } from './session-store'; async function signInToFirebase() { try { const { token } = await apiClient.getFirebaseToken(); await signInWithCustomToken(firebaseAuth, token); } catch (err) { // Firestore subscriptions will fail until the next successful sign-in; the // rest of the app keeps working against Orion. logError(err, { scope: 'auth.firebase' }); } } type AuthStatus = 'idle' | 'restoring' | 'unauthenticated' | 'authenticated'; interface AuthState { status: AuthStatus; user: Human | null; isRequestingCode: boolean; isSigningIn: boolean; isSigningOut: boolean; error: string | null; restoreSession: () => Promise; refreshUser: () => Promise; requestCode: (email: string) => Promise; signIn: (email: string, code: string) => Promise; signOut: () => Promise; clearError: () => void; } export const useAuthStore = create((set) => ({ status: 'idle', user: null, isRequestingCode: false, isSigningIn: false, isSigningOut: false, error: null, restoreSession: async () => { const token = useSessionStore.getState().token; if (!token) { set({ status: 'unauthenticated' }); return; } set({ status: 'restoring' }); try { const user = await apiClient.me(); await signInToFirebase(); set({ status: 'authenticated', user }); } catch (err) { // Expected on expired/invalid tokens — fall back to the login screen. logError(err, { scope: 'auth.restore' }); useSessionStore.getState().clearToken(); set({ status: 'unauthenticated', user: null }); } }, refreshUser: async () => { const user = await apiClient.me(); set({ user }); }, requestCode: async (email: string) => { set({ isRequestingCode: true, error: null }); try { await apiClient.requestCode({ email }); } catch (e) { const message = e instanceof ApiError ? e.message : 'Failed to send code'; set({ error: message }); throw e; } finally { set({ isRequestingCode: false }); } }, signIn: async (email: string, code: string) => { set({ isSigningIn: true, error: null }); try { const { human, token } = await apiClient.signIn({ email, code }); useSessionStore.getState().setToken(token); await signInToFirebase(); set({ status: 'authenticated', user: human }); } catch (e) { const message = e instanceof ApiError ? e.message : 'Failed to sign in'; set({ error: message }); throw e; } finally { set({ isSigningIn: false }); } }, signOut: async () => { set({ isSigningOut: true }); try { await apiClient.signOut(); } catch (err) { // Best-effort — sign out locally regardless. logError(err, { scope: 'auth.signOut' }); } finally { await firebaseSignOut(firebaseAuth).catch((err) => logError(err, { scope: 'auth.firebaseSignOut' }), ); useSessionStore.getState().clearToken(); set({ status: 'unauthenticated', user: null, isSigningOut: false, error: null, }); } }, clearError: () => set({ error: null }), })); // React to token being cleared externally (e.g. 401 from API client) useSessionStore.subscribe((state, prevState) => { if (prevState.token && !state.token) { const authState = useAuthStore.getState(); if (authState.status === 'authenticated') { useAuthStore.setState({ status: 'unauthenticated', user: null, error: null, }); } } });