94 lines
3.1 KiB
TypeScript
94 lines
3.1 KiB
TypeScript
|
|
// http service hub
|
|
// handles interceptors and such
|
|
|
|
import axios from "axios";
|
|
|
|
const baseUrl: string = import.meta.env.DEV ? import.meta.env.VITE_DEV_API_URL : "https://app.vxbard.net/api"
|
|
export const api = axios.create({
|
|
baseURL: baseUrl
|
|
});
|
|
|
|
type FailedRequest = { resolve: (token: string) => void, reject: (error: unknown) => void}
|
|
let isRefreshing: boolean = false;
|
|
let failedQueue: FailedRequest[] = [];
|
|
|
|
export const authStorage = {
|
|
getAccessToken: () => localStorage.getItem("accessToken"),
|
|
getRefreshToken: () => localStorage.getItem("refreshToken"),
|
|
|
|
setTokens: ({ accessToken, refreshToken } : { accessToken: string, refreshToken: string }) => {
|
|
localStorage.setItem("accessToken", accessToken)
|
|
localStorage.setItem("refreshToken", refreshToken)
|
|
},
|
|
|
|
clear: () => {
|
|
localStorage.removeItem("accessToken")
|
|
localStorage.removeItem("refreshToken")
|
|
}
|
|
}
|
|
|
|
const processQueue = (error: unknown, token: string | null = null): void => {
|
|
failedQueue.forEach(prom => {
|
|
if (error) prom.reject(error);
|
|
else prom.resolve(token as string);
|
|
})
|
|
failedQueue = [];
|
|
}
|
|
|
|
// intercept on each request
|
|
api.interceptors.request.use(config => { // add access token to request headers
|
|
|
|
const token = authStorage.getAccessToken();
|
|
|
|
if (token) {
|
|
config.headers.Authorization = `Bearer ${token}`;
|
|
}
|
|
|
|
return config;
|
|
|
|
});
|
|
|
|
// intercept on each response
|
|
api.interceptors.response.use(response => response, async error => { // mainly for authentication refreshTokens
|
|
const originalRequest = error.config;
|
|
|
|
// if un authorized then refresh the token
|
|
if(error.response?.status === 401 && !originalRequest._retry) {
|
|
if(isRefreshing) {
|
|
return new Promise((resolve, reject) => {
|
|
failedQueue.push({ resolve, reject })
|
|
}).then(token => {
|
|
originalRequest.headers.Authorization = `Bearer ${token}`;
|
|
return api(originalRequest);
|
|
}).catch(err => Promise.reject(err));
|
|
}
|
|
|
|
originalRequest._retry = true;
|
|
isRefreshing = true;
|
|
const refreshToken = authStorage.getRefreshToken();
|
|
try {
|
|
// request refresh endpoint get back a new accessToken
|
|
const res = await axios.post(`${baseUrl}/auth/refresh`, { refreshToken });
|
|
const { accessToken, refreshToken: newRefresh } = res.data;
|
|
authStorage.setTokens({ accessToken, refreshToken: newRefresh });
|
|
processQueue(null, accessToken);
|
|
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
|
|
return api(originalRequest);
|
|
} catch (err) {
|
|
processQueue(err, null);
|
|
authStorage.clear()
|
|
window.location.href = "/login";
|
|
return Promise.reject(err);
|
|
} finally {
|
|
isRefreshing = false;
|
|
}
|
|
}
|
|
return Promise.reject(error);
|
|
})
|
|
|
|
// TODO: if you get a 403 while navigating then redirect to the last authenticated page
|
|
// if you gert a 403 on a form submissio nthen do like an unauthorized popup (message: stale session <login link>) (or redirect to login like i said elsewhere)
|
|
|
|
export default api;
|