110 lines
2.8 KiB
TypeScript
110 lines
2.8 KiB
TypeScript
import { deleteAccount, updateAccountTokens } from "@/repository/account";
|
|
import { useDbStore } from "@/store/db";
|
|
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",
|
|
},
|
|
});
|
|
|
|
let isRefreshing = false;
|
|
let refreshQueue: ((token: string | null) => void)[] = [];
|
|
|
|
const waitForTokenRefresh = () => {
|
|
return new Promise<string | null>((resolve) => {
|
|
refreshQueue.push((token: string | null) => resolve(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) {
|
|
console.log("No database connection available.");
|
|
return Promise.reject("No database connection available.");
|
|
}
|
|
|
|
try {
|
|
const response = await refreshTokenApi(refreshToken);
|
|
|
|
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, accountId);
|
|
requireSignIn?.();
|
|
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;
|
|
}
|
|
|
|
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) => {
|
|
const text =
|
|
response.data?.error ||
|
|
response.data?.toString?.() ||
|
|
"unexpected error happened";
|
|
return new Error(text[0].toUpperCase() + text.slice(1));
|
|
};
|