import { useGetTutorialsStatus, useUpdateTutorialsStatus } from "@/hooks/AccountHooks"; import { AccountPermission } from "@/interface/AccountPermission"; import { AccountTutorialStatus } from "@/interface/AccountTutorialStatus"; import { GamePermission } from "@/interface/GamePermission"; import { RaidGroupPermission } from "@/interface/RaidGroupPermission"; import { RaidGroupRequest } from "@/interface/RaidGroupRequest"; import { api } from "@/util/AxiosUtil"; import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useState } from "react"; import { Navigate, Outlet } from "react-router"; type AuthProviderProps = { children: React.ReactNode; jwtStorageKey?: string; refreshTokenStorageKey?: string; } type AuthProviderState = { jwt: string | null; setJwt: (token: string | null) => void; expiration: Date | null; setExpiration: (expiration: Date | null) => void; accountId: string | null; accountPermissions: AccountPermission[]; raidGroupPermissions: RaidGroupPermission[]; gamePermissions: GamePermission[]; raidGroupRequests: RaidGroupRequest[]; tutorialsStatus: AccountTutorialStatus; setTutorialsStatus: (tutorialsStatus: AccountTutorialStatus) => void; } const initialState: AuthProviderState = { jwt: null, setJwt: () => null, expiration: null, setExpiration: () => null, accountId: null, accountPermissions: [], raidGroupPermissions: [], gamePermissions: [], raidGroupRequests: [], tutorialsStatus: {} as AccountTutorialStatus, setTutorialsStatus: () => null } const AuthContext = createContext(initialState); export function AuthProvider({ children }: AuthProviderProps){ const [ jwt, setJwt ] = useState(null); const [ expiration, setExpiration ] = useState(null); const [ firstFetch, setFirstFetch ] = useState(true); const [ accountId, setAccountId ] = useState(null); const [ accountPermissions, setAccountPermissions ] = useState([]); const [ raidGroupPermissions, setRaidGroupPermissions ] = useState([]); const [ gamePermissions, setGamePermissions ] = useState([]); const [ raidGroupRequests, setRaidGroupRequests ] = useState([]); const [ tutorialsStatus, setTutorialsStatus ] = useState({} as AccountTutorialStatus); const tutorialsStatusQuery = useGetTutorialsStatus(accountId); const { mutate: tutorialsStatusMutation } = useUpdateTutorialsStatus(); const fetchToken = useCallback(async () => { //console.log("Fetching token"); try{ const response = await api.get("/auth/refresh"); //If the token is retrieved if((response.status === 200) && (!response.data.errors)){ setJwt(response.data.token); const decodedToken = JSON.parse(atob(response.data.token.split(".")[1])); //console.log("decodedToken = "); //console.log(decodedToken); setExpiration(new Date(decodedToken.exp * 1000)); setFirstFetch(false); setAccountId(decodedToken.accountId); setAccountPermissions(JSON.parse(decodedToken.accountPermissions)); setRaidGroupPermissions(JSON.parse(decodedToken.raidGroupPermissions)); setGamePermissions(JSON.parse(decodedToken.gamePermissions)); setRaidGroupRequests(JSON.parse(decodedToken.raidGroupRequests)); return response.data.token; } //If the token cannot be retrieved else{ setJwt(null); setExpiration(null); setFirstFetch(false); } } //If the token cannot be retrieved catch{ setJwt(null); setExpiration(null); setFirstFetch(false); } }, [ setJwt, setExpiration, setFirstFetch ]); //Add the token to all queries useLayoutEffect(() => { const authInterceptor = api.interceptors.request.use(async (config) => { if(config.url?.endsWith("/auth/refresh")){ return config; } let currentJwt = jwt; if((expiration) && (expiration < new Date()) && (!config.url?.endsWith("/auth/refresh"))){ currentJwt = await fetchToken(); config.headers.Authorization = jwt ? `Bearer ${currentJwt}` : config.headers.Authorization; } config.headers.Authorization = jwt ? `Bearer ${currentJwt}` : config.headers.Authorization; return config; }); return () => { api.interceptors.request.eject(authInterceptor); }; }, [ jwt, expiration, fetchToken ]); //Try to get the token on page load useEffect(() => { fetchToken(); }, [ fetchToken ]); //Update the tutorial status when fetched useEffect(() => { if(tutorialsStatusQuery.status === "success"){ setTutorialsStatus(tutorialsStatusQuery.data); } }, [ tutorialsStatusQuery.status, tutorialsStatusQuery.data ]); const updateTutorialsStatus = useCallback((newTutorialsStatus: AccountTutorialStatus) => { setTutorialsStatus(newTutorialsStatus); tutorialsStatusMutation(newTutorialsStatus); }, [ setTutorialsStatus, tutorialsStatusMutation ]); const currentTokens = useMemo(() => ({ jwt, setJwt, expiration, setExpiration, accountId, accountPermissions, raidGroupPermissions, gamePermissions, raidGroupRequests, tutorialsStatus, setTutorialsStatus: updateTutorialsStatus }), [ jwt, expiration, accountId, accountPermissions, raidGroupPermissions, gamePermissions, raidGroupRequests, tutorialsStatus, updateTutorialsStatus ]); //TODO: Return a spinner while the first token is being fetched if(firstFetch){ return (
Loading...
); } return ( {children} ); } export function ProtectedRoute(){ const { jwt } = useAuth(); if(!jwt){ return ; } return ; } export const useAuth = () => { const context = useContext(AuthContext); if(context === undefined){ throw new Error("useAuth must be used within an AuthProvider"); } return context; }