mirror of
https://github.com/apricote/Listory.git
synced 2026-01-13 21:21:02 +00:00
95 lines
2.5 KiB
TypeScript
95 lines
2.5 KiB
TypeScript
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
|
|
import React, {
|
|
createContext,
|
|
useContext,
|
|
useEffect,
|
|
useRef,
|
|
useState,
|
|
} from "react";
|
|
import { useAuth } from "./use-auth";
|
|
|
|
interface ApiClientContext {
|
|
client: AxiosInstance;
|
|
}
|
|
|
|
const apiClientContext = createContext<ApiClientContext>(
|
|
(undefined as any) as ApiClientContext
|
|
);
|
|
|
|
export const ProvideApiClient: React.FC = ({ children }) => {
|
|
const auth = useProvideApiClient();
|
|
|
|
return (
|
|
<apiClientContext.Provider value={auth}>
|
|
{children}
|
|
</apiClientContext.Provider>
|
|
);
|
|
};
|
|
|
|
export function useApiClient() {
|
|
return useContext(apiClientContext);
|
|
}
|
|
|
|
function useProvideApiClient(): ApiClientContext {
|
|
const { accessToken, refreshAccessToken } = useAuth();
|
|
|
|
// Wrap value to immediatly update when refreshing access token
|
|
// and always having access to newest access token in interceptor
|
|
const localAccessToken = useRef(accessToken);
|
|
|
|
// initialState must be passed as function as return value of axios.create()
|
|
// is also callable and react will call it and then use that result (promise)
|
|
// as the initial state instead of the axios instace.
|
|
const [client] = useState<AxiosInstance>(() => axios.create());
|
|
|
|
useEffect(() => {
|
|
localAccessToken.current = accessToken;
|
|
}, [localAccessToken, accessToken]);
|
|
|
|
// TODO Implement lock to avoid multiple parallel refreshes, maybe in useAuth?
|
|
|
|
useEffect(() => {
|
|
// Setup Axios Interceptors
|
|
const requestInterceptor = client.interceptors.request.use(
|
|
(config) => {
|
|
if (!config.headers) {
|
|
config.headers = {};
|
|
}
|
|
|
|
config.headers.Authorization = `Bearer ${localAccessToken.current}`;
|
|
|
|
return config;
|
|
},
|
|
(err) => Promise.reject(err)
|
|
);
|
|
const responseInterceptor = client.interceptors.response.use(
|
|
(data) => data,
|
|
async (err: any) => {
|
|
if (!err.response || !err.config) {
|
|
throw err;
|
|
}
|
|
|
|
const { response, config } = err as {
|
|
response: AxiosResponse;
|
|
config: AxiosRequestConfig;
|
|
};
|
|
|
|
if (response && response.status !== 401) {
|
|
throw err;
|
|
}
|
|
|
|
// TODO error handling
|
|
localAccessToken.current = await refreshAccessToken();
|
|
|
|
return client.request(config);
|
|
}
|
|
);
|
|
|
|
return () => {
|
|
client.interceptors.request.eject(requestInterceptor);
|
|
client.interceptors.response.eject(responseInterceptor);
|
|
};
|
|
}, [client, localAccessToken, refreshAccessToken]);
|
|
|
|
return { client };
|
|
}
|