refactor(client): use generated API client and hooks
Signed-off-by: Noa Virellia <noa@requiem.garden>
This commit is contained in:
@@ -1,67 +0,0 @@
|
||||
import type { AxiosError, AxiosRequestConfig } from 'axios';
|
||||
import type { JsonValue } from 'type-fest';
|
||||
import axios from 'axios';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { router } from '@/lib/router';
|
||||
import { clearTokens, doRefreshToken, getRefreshToken, getToken, setRefreshToken, setToken } from './token';
|
||||
|
||||
export const HEADER_API_VERSION = {
|
||||
'X-Api-Version': 'latest',
|
||||
};
|
||||
|
||||
export const axiosClient = axios.create({
|
||||
baseURL: '/api/v1/',
|
||||
headers: HEADER_API_VERSION,
|
||||
});
|
||||
|
||||
axiosClient.interceptors.request.use((config) => {
|
||||
const token = getToken();
|
||||
if (token !== null) {
|
||||
config.headers = config.headers ?? {};
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
type RetryConfig = AxiosRequestConfig & { _retry?: boolean };
|
||||
|
||||
interface ResponseData {
|
||||
code: number;
|
||||
error_id: string;
|
||||
status: string;
|
||||
data: JsonValue;
|
||||
}
|
||||
|
||||
axiosClient.interceptors.response.use(async (response) => {
|
||||
const data = response.data as ResponseData;
|
||||
if (data.code !== 200) {
|
||||
return Promise.reject(data);
|
||||
}
|
||||
response.data = data.data;
|
||||
return response;
|
||||
}, async (error: AxiosError) => {
|
||||
const originalRequest = error.config as RetryConfig | undefined;
|
||||
if (!error.response || error.response.status !== 401 || !originalRequest) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
if (error.response.status === 401 && originalRequest.url !== '/auth/refresh' && !isNil(getRefreshToken())) {
|
||||
try {
|
||||
const maybeRefreshTokenResponse = await doRefreshToken();
|
||||
if (maybeRefreshTokenResponse.status !== 200) {
|
||||
throw new Error('Failed to refresh token');
|
||||
}
|
||||
const { access_token, refresh_token } = maybeRefreshTokenResponse.data;
|
||||
originalRequest.headers = originalRequest.headers ?? {};
|
||||
originalRequest.headers.Authorization = `Bearer ${access_token}`;
|
||||
setToken(access_token);
|
||||
setRefreshToken(refresh_token);
|
||||
return await axiosClient(originalRequest);
|
||||
}
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||
catch (e) {
|
||||
// Should remove token (tokens are out of date)
|
||||
clearTokens();
|
||||
await router.navigate({ to: '/authorize' });
|
||||
}
|
||||
}
|
||||
});
|
||||
63
client/cms/src/lib/client.ts
Normal file
63
client/cms/src/lib/client.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import {
|
||||
getToken,
|
||||
getRefreshToken,
|
||||
setToken,
|
||||
setRefreshToken,
|
||||
clearTokens,
|
||||
doRefreshToken
|
||||
} from "./token";
|
||||
import { router } from "./router";
|
||||
import { isEmpty,
|
||||
isNil } from "lodash-es";
|
||||
import { client } from "@/client/client.gen";
|
||||
|
||||
export function configInternalApiClient() {
|
||||
client.setConfig({
|
||||
baseUrl: '/api/v1/',
|
||||
headers: {
|
||||
'X-Api-Version': 'latest',
|
||||
},
|
||||
});
|
||||
|
||||
client.interceptors.request.use((request) => {
|
||||
const token = getToken();
|
||||
if (token) {
|
||||
request.headers.set('Authorization', `Bearer ${token}`);
|
||||
}
|
||||
return request;
|
||||
});
|
||||
|
||||
client.interceptors.response.use(async (response, request, options) => {
|
||||
if (response.status === 401) {
|
||||
const refreshToken = getRefreshToken();
|
||||
// Avoid infinite loop if the refresh token request itself fails
|
||||
if (!request.url.includes('/auth/refresh') && !isNil(refreshToken)) {
|
||||
try {
|
||||
const refreshResponse = await doRefreshToken();
|
||||
if (!isEmpty(refreshResponse)) {
|
||||
const { access_token, refresh_token } = refreshResponse;
|
||||
setToken(access_token!);
|
||||
setRefreshToken(refresh_token!);
|
||||
|
||||
const fetchFn = options.fetch ?? globalThis.fetch;
|
||||
const headers = new Headers(request.headers);
|
||||
headers.set('Authorization', `Bearer ${access_token}`);
|
||||
|
||||
return fetchFn(request.url, {
|
||||
method: request.method,
|
||||
headers,
|
||||
body: (options.serializedBody ?? options.body) as BodyInit | null | undefined,
|
||||
signal: request.signal,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
clearTokens();
|
||||
await router.navigate({ to: '/authorize' });
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
return response;
|
||||
});
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { axiosClient, HEADER_API_VERSION } from './axios';
|
||||
import { postAuthRefresh, type ServiceAuthTokenResponse } from '@/client';
|
||||
|
||||
export function setToken(token: string) {
|
||||
localStorage.setItem('token', token);
|
||||
@@ -29,18 +29,11 @@ export function clearTokens() {
|
||||
setRefreshToken('');
|
||||
}
|
||||
|
||||
export async function doSetTokenByCode(code: string) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
axiosClient.post<{ access_token: string; refresh_token: string }>('/auth/token', { code }, { headers: HEADER_API_VERSION }).then(({ data }) => {
|
||||
setToken(data.access_token);
|
||||
setRefreshToken(data.refresh_token);
|
||||
resolve();
|
||||
}).catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
export async function doRefreshToken(): Promise<ServiceAuthTokenResponse | undefined> {
|
||||
const { data } = await postAuthRefresh({
|
||||
body: {
|
||||
refresh_token: getRefreshToken()!
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function doRefreshToken() {
|
||||
return axiosClient.post<{ access_token: string; refresh_token: string }>('/auth/refresh', { refresh_token: getRefreshToken() }, { headers: HEADER_API_VERSION });
|
||||
return data?.data;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user