Files
MattrixwvReactComponents/lib/provider/token/TokenProvider.tsx

78 lines
1.8 KiB
TypeScript

import { createContext, useCallback, useContext, useMemo, useRef } from "react";
import { defaultTokenData, fetchToken, parseToken, type TokenData } from "./TokenUtils";
export interface TokenState {
getToken: () => Promise<string | null | undefined>;
}
const initialState: TokenState = {
getToken: () => new Promise(() => {})
}
const TokenContext = createContext<TokenState>(initialState);
export default function TokenProvider({
apiUrl,
children
}: Readonly<{
apiUrl: string;
children: React.ReactNode;
}>){
const tokenRef = useRef<TokenData>(defaultTokenData);
const refreshPromise = useRef<Promise<string | null | undefined>>(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 (
<TokenContext.Provider value={value}>
{children}
</TokenContext.Provider>
);
}
// 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;
}