All files / src/contexts AuthContext.tsx

13.63% Statements 6/44
0% Branches 0/13
0% Functions 0/9
14.63% Lines 6/41

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 1044x               4x   4x                                       4x   4x                                                                                                                                   4x          
import React, {
  createContext,
  useContext,
  useState,
  useCallback,
  useEffect,
  useRef,
} from 'react';
import { router, type Href } from 'expo-router';
import { StoredAuthSession, UserSummary } from '@/models/auth';
import {
  clearSession as clearStoredSession,
  hydrateSession,
  setSession as persistSession,
  subscribeToSession,
} from '@/services/sessionManager';
 
interface AuthContextType {
  token: string | null;
  refreshToken: string | null;
  user: UserSummary | null;
  isHydrating?: boolean;
  setSession: (
    accessToken: string,
    refreshToken: string,
    user: UserSummary,
  ) => Promise<void>;
  clearAuth: () => Promise<void>;
}
 
const AuthContext = createContext<AuthContextType | null>(null);
 
export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [token, setToken] = useState<string | null>(null);
  const [refreshToken, setRefreshToken] = useState<string | null>(null);
  const [user, setUser] = useState<UserSummary | null>(null);
  const [isHydrating, setIsHydrating] = useState(true);
  const previousSessionRef = useRef<StoredAuthSession | null>(null);
 
  const applySession = useCallback((session: StoredAuthSession | null) => {
    setToken(session?.access_token ?? null);
    setRefreshToken(session?.refresh_token ?? null);
    setUser(session?.user ?? null);
  }, []);
 
  useEffect(() => {
    let mounted = true;
 
    const unsubscribe = subscribeToSession((session) => {
      Iif (!mounted) return;
 
      const previousSession = previousSessionRef.current;
      previousSessionRef.current = session;
      applySession(session);
 
      Iif (!isHydrating && previousSession && !session) {
        router.replace('/' as Href);
      }
    });
 
    void (async () => {
      const session = await hydrateSession();
      Iif (!mounted) return;
      previousSessionRef.current = session;
      applySession(session);
      setIsHydrating(false);
    })();
 
    return () => {
      mounted = false;
      unsubscribe();
    };
  }, [applySession]);
 
  const setSession = useCallback(
    async (accessToken: string, refresh: string, userSummary: UserSummary) => {
      await persistSession({
        access_token: accessToken,
        refresh_token: refresh,
        user: userSummary,
      });
    },
    [],
  );
 
  const clearAuth = useCallback(() => {
    return clearStoredSession();
  }, []);
 
  return (
    <AuthContext.Provider
      value={{ token, refreshToken, user, isHydrating, setSession, clearAuth }}
    >
      {children}
    </AuthContext.Provider>
  );
}
 
export function useAuth(): AuthContextType {
  const ctx = useContext(AuthContext);
  Iif (!ctx) throw new Error('useAuth must be used within AuthProvider');
  return ctx;
}