import type { User, Session } from 'better-auth'; import type { Result } from '~~/types/result'; import { Ok, Err } from '~~/types/result'; import { createAuthClient } from 'better-auth/vue'; export enum AuthError { NotAuthenticated = 'NOT_AUTHENTICATED', SignInFailed = 'SIGN_IN_FAILED', SignUpFailed = 'SIGN_UP_FAILED', SignOutFailed = 'SIGN_OUT_FAILED', NetworkError = 'NETWORK_ERROR', } export interface AuthState { user: User | null; session: Session | null; isLoading: boolean; } export const useAuth = () => { const url = useRequestURL(); const headers = useRequestHeaders(); const client = createAuthClient({ baseURL: url.origin, fetchOptions: { headers } }); const state = useState('auth:state', () => ({ user: null, session: null, isLoading: false, })); const isAuthenticated = computed(() => !!state.value.session); const userId = computed(() => state.value.user?.id ?? null); const fetchSession = async (): Promise> => { state.value.isLoading = true; try { const { data } = await client.getSession(); if (data) { state.value.session = data.session; state.value.user = data.user; return Ok({ session: data.session, user: data.user }); } state.value.session = null; state.value.user = null; return Ok({ session: null, user: null }); } catch (error) { console.error('Failed to fetch session:', error); return Err(AuthError.NetworkError); } finally { state.value.isLoading = false; } }; const signIn = async ( email: string, password: string ): Promise> => { state.value.isLoading = true; try { const { data, error } = await client.signIn.email({ email, password, }); if (error) { console.error('Sign in failed:', error); return Err({ error: AuthError.SignInFailed, data: error }); } if (!data) { return Err({ error: AuthError.SignInFailed }); } state.value.user = data.user; const triplit = useTriplitClient(); if ('startSession' in triplit && data.token) { await triplit.startSession(data.token); } clearNuxtData(); return Ok({ user: data.user, token: data.token }); } catch (err) { console.error('Sign in error:', err); return Err({ error: AuthError.NetworkError }); } finally { state.value.isLoading = false; } }; const signUp = async ( email: string, password: string, name: string ): Promise> => { state.value.isLoading = true; try { const { data, error } = await client.signUp.email({ email, password, name, }); if (error) { console.error('Sign up failed:', error); return Err({ error: AuthError.SignUpFailed, data: error }); } if (!data || !data.token) { return Err({ error: AuthError.SignUpFailed }); } state.value.user = data.user; const triplit = useTriplitClient(); if ('startSession' in triplit) { await triplit.startSession(data.token); } clearNuxtData(); return Ok({ user: data.user, token: data.token }); } catch (err) { console.error('Sign up error:', err); return Err({ error: AuthError.NetworkError }); } finally { state.value.isLoading = false; } }; const signOut = async (): Promise> => { state.value.isLoading = true; try { const { error } = await client.signOut(); if (error) { console.error('Sign out failed:', error); return Err(AuthError.SignOutFailed); } state.value.user = null; state.value.session = null; const triplit = useTriplitClient(); if ('disconnect' in triplit) { triplit.disconnect(); } clearNuxtData(); return Ok(undefined); } catch (err) { console.error('Sign out error:', err); return Err(AuthError.NetworkError); } finally { state.value.isLoading = false; } }; return { client, user: computed(() => state.value.user), session: computed(() => state.value.session), isLoading: computed(() => state.value.isLoading), isAuthenticated, userId, fetchSession, signIn, signUp, signOut, }; };