114 lines
2.7 KiB
TypeScript
114 lines
2.7 KiB
TypeScript
import axios, { AxiosError, AxiosInstance } from "axios";
|
|
|
|
let cachedToken: string | null = null;
|
|
const LOGIN_PATH = "/login";
|
|
|
|
export const setAccessToken = (token: string | null) => {
|
|
cachedToken = token;
|
|
};
|
|
|
|
export const getAccessToken = (): string | null => {
|
|
return cachedToken;
|
|
};
|
|
|
|
const handleTokenError = () => {
|
|
cachedToken = null;
|
|
};
|
|
|
|
const redirectToLogin = () => {
|
|
if (typeof window !== "undefined") {
|
|
window.location.href = LOGIN_PATH;
|
|
}
|
|
};
|
|
|
|
const refreshAccessToken = async () => {
|
|
const refreshResponse = await fetch("/api/auth/refresh", {
|
|
method: "POST",
|
|
credentials: "include",
|
|
});
|
|
|
|
if (!refreshResponse.ok) {
|
|
return null;
|
|
}
|
|
|
|
const data = await refreshResponse.json();
|
|
setAccessToken(data.access_token);
|
|
return data.access_token as string;
|
|
};
|
|
|
|
const createApi = (): AxiosInstance & {
|
|
postStream: (url: string, data: unknown) => Promise<Response>;
|
|
} => {
|
|
const api = axios.create({
|
|
baseURL: process.env.NEXT_PUBLIC_API_URL,
|
|
});
|
|
|
|
api.interceptors.request.use(
|
|
async (config) => {
|
|
if (cachedToken) {
|
|
config.headers = config.headers ?? {};
|
|
config.headers.Authorization = `Bearer ${cachedToken}`;
|
|
}
|
|
return config;
|
|
},
|
|
(error) => {
|
|
return Promise.reject(error);
|
|
},
|
|
);
|
|
|
|
api.interceptors.response.use(
|
|
(response) => response,
|
|
async (error: AxiosError<{ error?: string }>) => {
|
|
if (error.response?.status === 401) {
|
|
handleTokenError();
|
|
|
|
try {
|
|
const nextToken = await refreshAccessToken();
|
|
if (nextToken && error.config) {
|
|
error.config.headers = error.config.headers ?? {};
|
|
error.config.headers.Authorization = `Bearer ${nextToken}`;
|
|
return api.request(error.config);
|
|
}
|
|
} catch {}
|
|
|
|
handleTokenError();
|
|
redirectToLogin();
|
|
}
|
|
|
|
if (error.response?.data?.error) {
|
|
return Promise.reject(error.response.data.error);
|
|
}
|
|
|
|
return Promise.reject(error);
|
|
},
|
|
);
|
|
|
|
const postStream = async (url: string, data: unknown): Promise<Response> => {
|
|
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}${url}`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: cachedToken ? `Bearer ${cachedToken}` : "",
|
|
},
|
|
body: JSON.stringify(data),
|
|
});
|
|
|
|
if (response.status === 401) {
|
|
handleTokenError();
|
|
redirectToLogin();
|
|
throw new Error("Unauthorized");
|
|
}
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
throw new Error(errorData.error || "Request failed");
|
|
}
|
|
|
|
return response;
|
|
};
|
|
|
|
return Object.assign(api, { postStream });
|
|
};
|
|
|
|
export const api = createApi();
|