fix: proper token refreshing BEFORE sending req
This commit is contained in:
@ -4,65 +4,100 @@ import { useAuth } from "@/store/auth";
|
||||
import Axios, { type AxiosResponse } from "axios";
|
||||
import { refreshTokenApi } from "./refresh";
|
||||
|
||||
import { isExpired } from "react-jwt";
|
||||
|
||||
export const axios = Axios.create({
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
axios.interceptors.request.use(
|
||||
(request) => {
|
||||
const account = useAuth.getState().activeAccount;
|
||||
if (account?.access) {
|
||||
request.headers["Authorization"] = `Bearer ${account.access}`;
|
||||
}
|
||||
let isRefreshing = false;
|
||||
let refreshQueue: ((token: string | null) => void)[] = [];
|
||||
|
||||
return request;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
const waitForTokenRefresh = () => {
|
||||
return new Promise<string | null>((resolve) => {
|
||||
refreshQueue.push((token: string | null) => resolve(token));
|
||||
});
|
||||
};
|
||||
|
||||
axios.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error) => {
|
||||
const originalRequest = error.config;
|
||||
|
||||
if (error.response.status === 401 && !originalRequest._retry) {
|
||||
originalRequest._retry = true;
|
||||
|
||||
const account = useAuth.getState().activeAccount;
|
||||
if (!account?.refresh) {
|
||||
return Promise.reject(new Error("Unauthorized. No refresh token"));
|
||||
}
|
||||
const processRefreshQueue = async (token: string | null) => {
|
||||
refreshQueue.forEach((resolve) => resolve(token));
|
||||
refreshQueue = [];
|
||||
};
|
||||
|
||||
const refreshToken = async (
|
||||
accountId: string,
|
||||
refreshToken: string
|
||||
): Promise<{ access: string; refresh: string }> => {
|
||||
const db = useDbStore.getState().db;
|
||||
const loadAccounts = useAuth.getState().loadAccounts;
|
||||
const requireSignIn = useAuth.getState().requireSignIn;
|
||||
|
||||
if (!db) {
|
||||
return Promise.reject(new Error("No database connection"));
|
||||
console.log("No database connection available.");
|
||||
return Promise.reject("No database connection available.");
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await refreshTokenApi(account.refresh);
|
||||
const response = await refreshTokenApi(refreshToken);
|
||||
|
||||
updateAccountTokens(db, {
|
||||
accountId: account.accountId,
|
||||
await updateAccountTokens(db, {
|
||||
accountId: accountId,
|
||||
access: response.access,
|
||||
refresh: response.refresh,
|
||||
});
|
||||
|
||||
processRefreshQueue(response.access);
|
||||
|
||||
return { access: response.access, refresh: response.refresh };
|
||||
} catch (err) {
|
||||
console.error("token refresh failed:", err);
|
||||
await deleteAccount(db, account.accountId);
|
||||
const loadAccounts = useAuth.getState().loadAccounts;
|
||||
loadAccounts?.();
|
||||
const requireSignIn = useAuth.getState().requireSignIn;
|
||||
console.error("Token refresh failed:", err);
|
||||
await deleteAccount(db, accountId);
|
||||
requireSignIn?.();
|
||||
return Promise.reject(err);
|
||||
processRefreshQueue(null);
|
||||
throw err;
|
||||
} finally {
|
||||
localStorage.removeItem("refreshing");
|
||||
loadAccounts?.();
|
||||
isRefreshing = false;
|
||||
}
|
||||
};
|
||||
|
||||
axios.interceptors.request.use(
|
||||
async (request) => {
|
||||
const account = useAuth.getState().activeAccount;
|
||||
let token: string | null = account?.access ?? null;
|
||||
|
||||
if (!token || !isExpired(token)) {
|
||||
request.headers["Authorization"] = `Bearer ${token}`;
|
||||
return request;
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
if (!isRefreshing) {
|
||||
isRefreshing = true;
|
||||
try {
|
||||
const { access } = await refreshToken(
|
||||
account!.accountId,
|
||||
account!.refresh
|
||||
);
|
||||
token = access;
|
||||
} catch (err) {
|
||||
console.error("Token refresh failed:", err);
|
||||
throw err;
|
||||
}
|
||||
} else {
|
||||
token = await waitForTokenRefresh();
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
throw new Error("No token available");
|
||||
}
|
||||
|
||||
request.headers["Authorization"] = `Bearer ${token}`;
|
||||
return request;
|
||||
},
|
||||
(error) => Promise.reject(error)
|
||||
);
|
||||
|
||||
export const handleApiError = async (response: AxiosResponse) => {
|
||||
|
Reference in New Issue
Block a user