import { createContext, useCallback, useContext, useMemo, useRef } from "react"; import { defaultTokenData, fetchToken, parseToken, type TokenData } from "./TokenUtils"; export interface TokenState { getToken: () => Promise; } const initialState: TokenState = { getToken: () => new Promise(() => {}) } const TokenContext = createContext(initialState); export default function TokenProvider({ apiUrl, children }: Readonly<{ apiUrl: string; children: React.ReactNode; }>){ const tokenRef = useRef(defaultTokenData); const refreshPromise = useRef>(null); const getToken = useCallback(async () => { if(refreshPromise.current){ return refreshPromise.current; } const { accessToken, expires } = tokenRef.current; const isExpired = Date.now() > (expires - 5000); //Give a 5 second buffer if(!accessToken || isExpired){ refreshPromise.current = (async () => { try { const rawToken = (await fetchToken(apiUrl)).token; const parsedToken = parseToken(rawToken); tokenRef.current = parsedToken; return rawToken; } catch(error){ tokenRef.current = defaultTokenData; throw error; } finally { refreshPromise.current = null; } })(); return refreshPromise.current; } return accessToken; }, [apiUrl]); const value: TokenState = useMemo(() => ({ getToken }), [getToken]); return ( {children} ); } // eslint-disable-next-line react-refresh/only-export-components export function useToken(){ const context = useContext(TokenContext); if(!context){ throw new Error("useToken must be called inside a TokenProvider"); } return context; }