import * as SecureStore from "expo-secure-store"; import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode, } from "react"; import { setAuthToken, clearAuthToken } from "./fetch"; const TOKEN_KEY = "waseel_auth_token"; const USER_KEY = "waseel_auth_user"; const DEFAULT_TOKEN_TTL_SECONDS = 30 * 24 * 60 * 60; export type SessionUser = { id: string; name: string; email: string; role?: string | null; avatarUrl?: string | null; }; type AuthResult = { token: string; user: SessionUser }; type SessionContextValue = { isLoaded: boolean; isSignedIn: boolean; userId: string | null; user: SessionUser | null; setSession: (result: AuthResult) => Promise; setUserRole: (role: string) => void; signOut: () => Promise; }; const SessionContext = createContext(null); // Mirrors lib/jwt.ts payload decoding (no signature check needed client-side). export const decodeJwtExp = (token: string): number | null => { try { const claims = JSON.parse( atob(token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/")), ); return typeof claims.exp === "number" ? claims.exp : null; } catch { return null; } }; export const SessionProvider = ({ children }: { children: ReactNode }) => { const [isLoaded, setIsLoaded] = useState(false); const [user, setUser] = useState(null); useEffect(() => { let cancelled = false; const restore = async () => { try { const [token, storedUser] = await Promise.all([ SecureStore.getItemAsync(TOKEN_KEY), SecureStore.getItemAsync(USER_KEY), ]); if (!token || !storedUser) return; const exp = decodeJwtExp(token) ?? 0; if (exp * 1000 < Date.now()) { await SecureStore.deleteItemAsync(TOKEN_KEY); await SecureStore.deleteItemAsync(USER_KEY); return; } if (cancelled) return; setAuthToken(token); setUser(JSON.parse(storedUser) as SessionUser); } catch (error) { console.error("[SESSION_RESTORE]: ", error); } finally { if (!cancelled) setIsLoaded(true); } }; void restore(); return () => { cancelled = true; }; }, []); const setSession = useCallback(async (result: AuthResult) => { setAuthToken(result.token); setUser(result.user); await SecureStore.setItemAsync(TOKEN_KEY, result.token); await SecureStore.setItemAsync(USER_KEY, JSON.stringify(result.user)); }, []); const setUserRole = useCallback( (role: string) => { setUser((currentUser) => { if (!currentUser) return currentUser; const updated = { ...currentUser, role }; void SecureStore.setItemAsync(USER_KEY, JSON.stringify(updated)); return updated; }); }, [], ); const signOut = useCallback(async () => { clearAuthToken(); setUser(null); await SecureStore.deleteItemAsync(TOKEN_KEY); await SecureStore.deleteItemAsync(USER_KEY); }, []); const value = useMemo( () => ({ isLoaded, isSignedIn: user !== null, userId: user?.id ?? null, user, setSession, setUserRole, signOut, }), [isLoaded, user, setSession, setUserRole, signOut], ); return ( {children} ); }; export const useSession = (): SessionContextValue => { const context = useContext(SessionContext); if (!context) { throw new Error("useSession must be used within a SessionProvider."); } return context; }; // Alias kept for parity with the previous auth API. export const useAuth = useSession; export const TOKEN_TTL_SECONDS = DEFAULT_TOKEN_TTL_SECONDS;