import * as WebBrowser from 'expo-web-browser'
import * as AuthSession from 'expo-auth-session'
import { create } from 'zustand'
import { createSelectors } from '../utils'
import { fetchUserSession, getAuthUrl, logout } from './api'
import { type OAuthProvider, type TokenType, type UserSession } from './types'
import { getToken, removeToken, setToken } from './utils'
import { router } from 'expo-router'
interface AuthState {
token: TokenType | null
status: 'unauthenticated' | 'loading' | 'authenticated'
userSession: UserSession | null
provider: OAuthProvider | null
initiateOAuth: (provider: OAuthProvider) => Promise<void>
signOut: () => void
hydrate: () => Promise<void>
refreshUserSession: () => Promise<void>
}
const _useAuth = create<AuthState>((set, get) => ({
status: 'unauthenticated',
token: null,
userSession: null,
provider: null,
initiateOAuth: async (provider: OAuthProvider) => {
try {
set({ status: 'loading' })
const redirectUrl = AuthSession.makeRedirectUri({
path: 'auth',
})
const authUrl = await getAuthUrl(provider, redirectUrl)
const result = await WebBrowser.openAuthSessionAsync(authUrl, redirectUrl)
if (result.type === 'success') {
const url = new URL(result.url)
const accessToken = url.searchParams.get('access_token')
const refreshToken = url.searchParams.get('refresh_token')
if (accessToken && refreshToken) {
set({ token: { accessToken, refreshToken } })
setToken({ accessToken, refreshToken })
await get().refreshUserSession()
const user = get().userSession
console.log('user', user)
if (!user?.isRegistered) {
router.replace('/register')
} else {
router.replace('/')
}
}
}
} catch (error) {
console.error(`Error during ${provider} auth:`, error)
}
},
signOut: async () => {
const token = get().token
if (token) {
await logout(token.accessToken)
}
removeToken()
set({
status: 'unauthenticated',
token: null,
userSession: null,
})
router.replace('/login')
},
hydrate: async () => {
try {
const userToken = getToken()
if (userToken !== null) {
set({ token: userToken })
await get().refreshUserSession()
} else {
get().signOut()
}
} catch (e) {
console.error('Error during hydration:', e)
get().signOut()
}
},
refreshUserSession: async () => {
try {
const token = get().token
if (!token) throw new Error('No token available')
const userSession = await fetchUserSession(token.accessToken)
if (userSession) {
set({ userSession, status: 'authenticated' })
}
} catch (e) {
console.error('Error refreshing user session:', e)
get().signOut()
}
},
}))
export const useAuth = createSelectors(_useAuth)
export const signOut = () => _useAuth.getState().signOut()
export const initiateOAuth = (provider: OAuthProvider) =>
_useAuth.getState().initiateOAuth(provider)
export const hydrateAuth = () => _useAuth.getState().hydrate()
export const refreshUserSession = () => _useAuth.getState().refreshUserSession()