107 lines
2.6 KiB
TypeScript
107 lines
2.6 KiB
TypeScript
import axios, { AxiosResponse } from "axios";
|
||
import { create } from "zustand";
|
||
import { persist } from "zustand/middleware";
|
||
|
||
type Token = string;
|
||
|
||
interface AuthStore {
|
||
token: Token;
|
||
makeRequest: <TRequest, TResponse>(props: FirstRequest<TRequest>) => Promise<TResponse>;
|
||
clearToken: () => void;
|
||
}
|
||
|
||
interface FirstRequest<T> {
|
||
method?: string;
|
||
url: string;
|
||
body?: T;
|
||
/** Send access token */
|
||
useToken?: boolean;
|
||
contentType?: boolean;
|
||
signal?: AbortSignal;
|
||
/** Send refresh token */
|
||
withCredentials?: boolean;
|
||
}
|
||
|
||
export const authStore = create<AuthStore>()(
|
||
persist(
|
||
(set, get) => ({
|
||
token: "",
|
||
makeRequest: <TRequest, TResponse>(props: FirstRequest<TRequest>): Promise<TResponse> => {
|
||
const newProps = { ...props, HC: (newToken: Token) => set({ token: newToken }), token: get().token };
|
||
|
||
return makeRequest<TRequest, TResponse>(newProps);
|
||
},
|
||
clearToken: () => set({ token: "" }),
|
||
}),
|
||
{
|
||
name: "token",
|
||
}
|
||
)
|
||
);
|
||
|
||
interface MakeRequest<T> extends FirstRequest<T> {
|
||
HC: (newToken: Token) => void;
|
||
token: Token;
|
||
}
|
||
|
||
async function makeRequest<TRequest, TResponse>({
|
||
method = "post",
|
||
url,
|
||
body,
|
||
useToken = true,
|
||
contentType = false,
|
||
HC,
|
||
token,
|
||
signal,
|
||
withCredentials,
|
||
}: MakeRequest<TRequest>) {
|
||
//В случае 401 рефреш должен попробовать вызваться 1 раз
|
||
let headers: Record<string, string> = {};
|
||
if (useToken) headers["Authorization"] = `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,
|
||
withCredentials,
|
||
});
|
||
|
||
if (response.data?.accessToken) {
|
||
HC(response.data.accessToken);
|
||
}
|
||
|
||
return response.data;
|
||
} catch (error) {
|
||
if (axios.isAxiosError(error) && error.response?.status === 401 && !withCredentials) {
|
||
const refreshResponse = await refresh(token);
|
||
if (refreshResponse.data?.accessToken) HC(refreshResponse.data.accessToken);
|
||
|
||
headers["Authorization"] = refreshResponse.data.accessToken;
|
||
const response = await axios.request<TRequest, AxiosResponse<TResponse>>({
|
||
url,
|
||
method,
|
||
headers,
|
||
data: body,
|
||
signal,
|
||
});
|
||
|
||
return response.data;
|
||
}
|
||
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
function refresh(token: Token) {
|
||
return axios<never, AxiosResponse<{ accessToken: string }>>("https://admin.pena.digital/auth/refresh", {
|
||
headers: {
|
||
Authorization: token,
|
||
"Content-Type": "application/json",
|
||
},
|
||
});
|
||
}
|