"use client"; import { createContext, useContext } from "react"; import { useSession as useNextAuthSession } from "next-auth/react"; import { signOut, signIn } from "next-auth/react"; import { configureApiAuth } from "./apiClient"; import { assertCustomSession, CustomSession } from "./types"; import { Session } from "next-auth"; import { SessionAutoRefresh } from "./SessionAutoRefresh"; import { REFRESH_ACCESS_TOKEN_ERROR } from "./auth"; type AuthContextType = ( | { status: "loading" } | { status: "refreshing" } | { status: "unauthenticated"; error?: string } | { status: "authenticated"; accessToken: string; accessTokenExpires: number; user: CustomSession["user"]; } ) & { update: () => Promise; signIn: typeof signIn; signOut: typeof signOut; }; const AuthContext = createContext(undefined); export function AuthProvider({ children }: { children: React.ReactNode }) { const { data: session, status, update } = useNextAuthSession(); const customSession = session ? assertCustomSession(session) : null; const contextValue: AuthContextType = { ...(() => { switch (status) { case "loading": { const sessionIsHere = !!customSession; switch (sessionIsHere) { case false: { return { status }; } case true: { return { status: "refreshing" as const }; } default: { const _: never = sessionIsHere; throw new Error("unreachable"); } } } case "authenticated": { if (customSession?.error === REFRESH_ACCESS_TOKEN_ERROR) { // token had expired but next auth still returns "authenticated" so show user unauthenticated state return { status: "unauthenticated" as const, }; } else if (customSession?.accessToken) { return { status, accessToken: customSession.accessToken, accessTokenExpires: customSession.accessTokenExpires, user: customSession.user, }; } else { console.warn( "illegal state: authenticated but have no session/or access token. ignoring", ); return { status: "unauthenticated" as const }; } } case "unauthenticated": { return { status: "unauthenticated" as const }; } default: { const _: never = status; throw new Error("unreachable"); } } })(), update, signIn, signOut, }; // not useEffect, we need it ASAP configureApiAuth( contextValue.status === "authenticated" ? contextValue.accessToken : null, ); return ( {children} ); } export function useAuth() { const context = useContext(AuthContext); if (context === undefined) { throw new Error("useAuth must be used within an AuthProvider"); } return context; }