Files
hspguard/web/src/api/index.ts

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));
};