155 lines
4.6 KiB
TypeScript
155 lines
4.6 KiB
TypeScript
import axios, { AxiosResponse, Method, ResponseType } from "axios";
|
|
import { getAuthToken, setAuthToken } from "../stores/auth";
|
|
|
|
export interface MakeRequestConfig {
|
|
getAuthToken: () => string | undefined;
|
|
setAuthToken: (token: string) => void;
|
|
refreshUrl: string;
|
|
logoutFn: () => void;
|
|
handleComponentError?: (error: Error, info?: any) => void;
|
|
clearAuthDataFn?: () => void;
|
|
clearErrorHandlingConfig?: () => void;
|
|
allowedDomains?: string[];
|
|
debugSecretKey?: string;
|
|
logErrorFn?: (message: string, error?: any) => void;
|
|
}
|
|
|
|
let makeRequestConfig: MakeRequestConfig | null = null;
|
|
|
|
|
|
export function createMakeRequestConfig(
|
|
getAuthToken: () => string | undefined,
|
|
setAuthToken: (token: string) => void,
|
|
refreshUrl: string,
|
|
logoutFn?: () => void,
|
|
handleComponentError?: (error: Error, info?: any) => void,
|
|
clearAuthDataFn?: () => void,
|
|
clearErrorHandlingConfig?: () => void,
|
|
allowedDomains?: string[],
|
|
debugSecretKey?: string,
|
|
logErrorFn?: (message: string, error?: any) => void,
|
|
) {
|
|
makeRequestConfig = {
|
|
getAuthToken,
|
|
setAuthToken,
|
|
refreshUrl,
|
|
logoutFn: () => {
|
|
clearMakeRequestConfig();
|
|
if (typeof clearErrorHandlingConfig === 'function') clearErrorHandlingConfig();
|
|
if (logoutFn) logoutFn();
|
|
},
|
|
handleComponentError,
|
|
clearAuthDataFn,
|
|
clearErrorHandlingConfig,
|
|
allowedDomains,
|
|
debugSecretKey,
|
|
logErrorFn,
|
|
};
|
|
}
|
|
|
|
export function getMakeRequestConfig(): MakeRequestConfig | null {
|
|
return makeRequestConfig;
|
|
}
|
|
|
|
export function clearMakeRequestConfig() {
|
|
makeRequestConfig = null;
|
|
}
|
|
|
|
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;
|
|
config.handleComponentError(httpError, { componentStack: null });
|
|
}
|
|
// refreshToken is empty
|
|
if (
|
|
error.response?.status === 400 &&
|
|
error.response?.data?.message === "refreshToken is empty" &&
|
|
config?.clearAuthDataFn
|
|
) {
|
|
config.clearAuthDataFn();
|
|
}
|
|
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"
|
|
});
|
|
} |