import type { ClientOptions, InferSessionFromClient, InferUserFromClient } from 'better-auth/client' import type { RuntimeAuthConfig } from '~/types/auth' import { defu } from 'defu' import { useAuthClient } from '~/composables/auth/useAuthClient' // Global state for auth const session = ref | null>(null) const user = ref | null>(null) const sessionFetching = import.meta.server ? ref(false) : ref(false) const jwt = ref(null) const organizations = ref< { id: string name: string createdAt: Date slug: string metadata?: Record logo?: string | null }[] >([]) const selectedOrganization = ref<{ id: string name: string createdAt: Date slug: string metadata?: Record logo?: string | null } | null>(null) const activeMember = ref<{ role: string } | null>(null) export function useAuthState() { const { client } = useAuthClient() const route = useRoute() const options = defu(useRuntimeConfig().public.auth as Partial, { redirectUserTo: '/', redirectGuestTo: '/login' }) const headers = import.meta.server ? useRequestHeaders() : undefined async function fetchSession(targetPath?: string) { if (sessionFetching.value) { console.log('already fetching session') return } sessionFetching.value = true const { data } = await client.getSession({ fetchOptions: { headers } }) session.value = data?.session || null user.value = data?.user || null sessionFetching.value = false // Only fetch JWT and organizations if we have a session and not on public routes if (session.value && !isPublicPath(targetPath)) { await fetchJwtAndOrganizations() } return data } async function fetchJwtAndOrganizations() { // Fetch JWT const tokenResult = await client.token() jwt.value = tokenResult.data?.token ?? null // Fetch organization const orgResult = await client.organization.list({ fetchOptions: { headers } }) organizations.value = orgResult.data ?? [] if (!selectedOrganization.value && organizations.value.length > 0) { selectedOrganization.value = organizations.value[0] } // Fetch active member const activeMemberResult = await client.organization.getActiveMember({ fetchOptions: { headers } }) activeMember.value = activeMemberResult.data || null } function isPublicPath(path?: string) { const finalPath = path ?? route.path const publicRoutes = ['/login', '/signup', '/accept-invitation'] return publicRoutes.some((path) => finalPath.startsWith(path)) } // Watch organization changes watch( () => selectedOrganization.value, async (newValue) => { if (newValue) { await client.organization.setActive({ organizationId: newValue?.id }) } } ) // Client-side session listening if (import.meta.client) { client.$store.listen('$sessionSignal', async (signal) => { if (!signal) return await fetchSession() }) } return { session, user, sessionFetching, jwt, organizations, selectedOrganization, activeMember, options, fetchSession, isPublicPath, loggedIn: computed(() => !!session.value) } }