@@ -11,23 +11,28 @@ import {
1111 useState ,
1212} from 'react' ;
1313
14+ import { subscribeToAuthUpdates } from '@/lib/auth-sync' ;
15+
1416type AuthApiUser = {
1517 id : string ;
1618 role : 'user' | 'admin' ;
17- username : string ;
1819} | null ;
1920
2021type AuthContextValue = {
2122 user : AuthApiUser ;
2223 userExists : boolean ;
2324 userId : string | null ;
2425 isAdmin : boolean ;
25- username : string | null ;
2626 loading : boolean ;
2727 refresh : ( ) => Promise < void > ;
2828} ;
2929
3030const AuthContext = createContext < AuthContextValue | undefined > ( undefined ) ;
31+ let authUserCache : AuthApiUser | undefined ;
32+ let authUserCacheAt = 0 ;
33+ let inFlightAuthPromise : Promise < AuthApiUser > | null = null ;
34+
35+ const AUTH_CACHE_TTL_MS = 60_000 ;
3136
3237async function fetchAuth ( signal ?: AbortSignal ) : Promise < AuthApiUser > {
3338 const response = await fetch ( '/api/auth/me' , {
@@ -44,22 +49,49 @@ async function fetchAuth(signal?: AbortSignal): Promise<AuthApiUser> {
4449 return ( await response . json ( ) ) as AuthApiUser ;
4550}
4651
52+ function isCacheFresh ( ) : boolean {
53+ if ( authUserCache === undefined ) return false ;
54+ return Date . now ( ) - authUserCacheAt < AUTH_CACHE_TTL_MS ;
55+ }
56+
57+ function fetchAuthDeduped ( ) : Promise < AuthApiUser > {
58+ if ( inFlightAuthPromise ) {
59+ return inFlightAuthPromise ;
60+ }
61+
62+ inFlightAuthPromise = fetchAuth ( ) . finally ( ( ) => {
63+ inFlightAuthPromise = null ;
64+ } ) ;
65+
66+ return inFlightAuthPromise ;
67+ }
68+
4769export function AuthProvider ( { children } : { children : ReactNode } ) {
48- const [ user , setUser ] = useState < AuthApiUser > ( null ) ;
49- const [ loading , setLoading ] = useState ( true ) ;
70+ const [ user , setUser ] = useState < AuthApiUser > ( authUserCache ?? null ) ;
71+ const [ loading , setLoading ] = useState ( authUserCache === undefined ) ;
5072 const latestRequestIdRef = useRef ( 0 ) ;
5173
52- const runAuthRequest = useCallback ( async ( signal ?: AbortSignal ) => {
74+ const runAuthRequest = useCallback ( async ( options ?: { force ?: boolean } ) => {
75+ const force = options ?. force ?? false ;
76+
77+ if ( ! force && isCacheFresh ( ) ) {
78+ setUser ( authUserCache ?? null ) ;
79+ setLoading ( false ) ;
80+ return ;
81+ }
82+
5383 const requestId = ++ latestRequestIdRef . current ;
54- setLoading ( true ) ;
84+ setLoading ( authUserCache === undefined || force ) ;
5585
5686 try {
57- const nextUser = await fetchAuth ( signal ) ;
87+ const nextUser = await fetchAuthDeduped ( ) ;
5888
5989 if ( latestRequestIdRef . current !== requestId ) {
6090 return ;
6191 }
6292
93+ authUserCache = nextUser ;
94+ authUserCacheAt = Date . now ( ) ;
6395 setUser ( nextUser ) ;
6496 } catch ( error ) {
6597 if ( error instanceof DOMException && error . name === 'AbortError' ) {
@@ -70,6 +102,8 @@ export function AuthProvider({ children }: { children: ReactNode }) {
70102 return ;
71103 }
72104
105+ authUserCache = null ;
106+ authUserCacheAt = Date . now ( ) ;
73107 setUser ( null ) ;
74108 } finally {
75109 if ( latestRequestIdRef . current === requestId ) {
@@ -79,26 +113,36 @@ export function AuthProvider({ children }: { children: ReactNode }) {
79113 } , [ ] ) ;
80114
81115 const refresh = useCallback ( async ( ) => {
82- await runAuthRequest ( ) ;
116+ await runAuthRequest ( { force : true } ) ;
83117 } , [ runAuthRequest ] ) ;
84118
85119 useEffect ( ( ) => {
86- const controller = new AbortController ( ) ;
120+ void runAuthRequest ( ) ;
121+ } , [ runAuthRequest ] ) ;
87122
88- void runAuthRequest ( controller . signal ) ;
123+ useEffect ( ( ) => {
124+ const handleFocus = ( ) => {
125+ void runAuthRequest ( ) ;
126+ } ;
89127
128+ window . addEventListener ( 'focus' , handleFocus ) ;
90129 return ( ) => {
91- controller . abort ( ) ;
130+ window . removeEventListener ( 'focus' , handleFocus ) ;
92131 } ;
93132 } , [ runAuthRequest ] ) ;
94133
134+ useEffect ( ( ) => {
135+ return subscribeToAuthUpdates ( ( ) => {
136+ void runAuthRequest ( { force : true } ) ;
137+ } ) ;
138+ } , [ runAuthRequest ] ) ;
139+
95140 const value = useMemo < AuthContextValue > (
96141 ( ) => ( {
97142 user,
98143 userExists : Boolean ( user ) ,
99144 userId : user ?. id ?? null ,
100145 isAdmin : user ?. role === 'admin' ,
101- username : user ?. username ?? null ,
102146 loading,
103147 refresh,
104148 } ) ,
0 commit comments