UIKit/lib/api/makeRequest.ts

131 lines
4.0 KiB
TypeScript

import axios, { AxiosResponse, Method, ResponseType } from "axios";
import { getAuthToken, setAuthToken } from "../stores/auth";
import { Ticket, clearErrorHandlingConfig } from "..";
let makeRequestConfig: MakeRequestConfig | null = null;
export interface MakeRequestConfig {
logoutFn: () => void;
handleComponentError?: (error: Error, info: any, getTickets: () => Ticket[]) => void;
getTickets?: () => Ticket[];
}
export function createMakeRequestConfig(
logoutFn?: () => void,
handleComponentError?: (error: Error, info: any, getTickets: () => Ticket[]) => void,
getTickets?: () => Ticket[]
) {
makeRequestConfig = {
logoutFn: () => {
clearErrorHandlingConfig();
if (logoutFn) logoutFn();
},
handleComponentError,
getTickets,
};
}
export function getMakeRequestConfig(): MakeRequestConfig | null {
return makeRequestConfig;
}
export async function makeRequest<TRequest = unknown, TResponse = unknown>({
method = "post",
url,
body,
useToken = true,
contentType = false,
responseType = "json",
signal,
withCredentials,
}: {
method?: Method;
url: string;
body?: TRequest;
useToken?: boolean;
contentType?: boolean;
responseType?: ResponseType;
signal?: AbortSignal;
withCredentials?: boolean;
}): Promise<TResponse> {
const config = getMakeRequestConfig();
const headers: Record<string, string> = {};
if (useToken) {
const token = getAuthToken();
headers["Authorization"] = token ? `Bearer ${token}` : "";
}
if (contentType) headers["Content-Type"] = "application/json";
try {
const response = await axios<TRequest, AxiosResponse<TResponse & { accessToken?: string; }>>({
url,
method,
headers,
data: body,
signal,
responseType,
withCredentials,
});
if (response.data?.accessToken) {
setAuthToken(response.data.accessToken);
}
return response.data;
} catch (error: any) {
if (axios.isAxiosError(error) && error.response?.status === 401 && !withCredentials) {
const refreshResponse = await refresh(getAuthToken());
if (axios.isAxiosError(refreshResponse) && error.response?.status === 401 && config !== null) {
//токен так сильно сдох, что восстановлению не подлежит
config.logoutFn();
throw new Error("Пожалуйста, войдите в свой профиль.");
}
if (refreshResponse.data?.accessToken) {
setAuthToken(refreshResponse.data.accessToken);
}
headers["Authorization"] = refreshResponse.data.accessToken ? `Bearer ${refreshResponse.data.accessToken}` : "";
const response = await axios.request<TRequest, AxiosResponse<TResponse>>({
url,
method,
headers,
data: body,
signal,
});
return response.data;
}
// Централизованная обработка ошибок (400/500+)
if (
error.response?.status &&
(error.response.status === 400 || error.response.status >= 500) &&
config?.handleComponentError
) {
const errorMessage = `HTTP ${error.response.status}: ${error.response?.data?.message || error.message}`;
const httpError = new Error(errorMessage);
httpError.stack = error.stack;
// Передаем getTickets как callback
config.handleComponentError(httpError, { componentStack: null }, config.getTickets || (() => []));
}
// refreshToken is empty
if (
error.response?.status === 400 &&
error.response?.data?.message === "refreshToken is empty" &&
config?.logoutFn
) {
config.logoutFn();
}
throw error;
}
}
export async function refresh(token?: string): Promise<AxiosResponse<{ accessToken: string }>> {
if (!token) throw new Error("No refresh token provided");
return axios<never, AxiosResponse<{ accessToken: string }>>(process.env.REACT_APP_DOMAIN + "/auth/refresh", {
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json",
},
method: "post"
});
}