cleanup
This commit is contained in:
@@ -12,7 +12,7 @@ import type {
|
||||
TenantSnippet,
|
||||
TagResponse,
|
||||
} from './apiTypes';
|
||||
import type { AxiosInstance } from 'axios';
|
||||
import type { AxiosError, AxiosInstance, InternalAxiosRequestConfig, AxiosRequestConfig } from 'axios';
|
||||
|
||||
export const httpClient: Pick<AxiosInstance, 'get' | 'post' | 'patch' | 'delete' | 'defaults'> = {
|
||||
get: api.get.bind(api),
|
||||
@@ -22,6 +22,22 @@ export const httpClient: Pick<AxiosInstance, 'get' | 'post' | 'patch' | 'delete'
|
||||
defaults: api.defaults,
|
||||
};
|
||||
|
||||
type AuthAwareRequestConfig = InternalAxiosRequestConfig & {
|
||||
_retry?: boolean;
|
||||
skipAuthRefresh?: boolean;
|
||||
};
|
||||
|
||||
type AuthRequestConfig = AxiosRequestConfig & {
|
||||
skipAuthRefresh?: boolean;
|
||||
};
|
||||
type AuthRefreshHandlers = {
|
||||
onRefreshSuccess?: (token: string, payload?: { tenant?: unknown }) => void;
|
||||
onRefreshFailure?: (error: unknown) => void;
|
||||
};
|
||||
|
||||
let refreshPromise: Promise<string> | null = null;
|
||||
let authRefreshHandlers: AuthRefreshHandlers = {};
|
||||
|
||||
const normalizeNumber = (value: unknown): number | undefined => {
|
||||
const n = Number(value);
|
||||
return Number.isFinite(n) ? n : undefined;
|
||||
@@ -168,7 +184,7 @@ export const performLogin = async (payload: Record<string, unknown>): Promise<un
|
||||
};
|
||||
|
||||
export const refreshSession = async (): Promise<{ access_token?: string; tenant?: unknown }> => {
|
||||
const { data } = await api.post('/auth/refresh');
|
||||
const { data } = await api.post('/auth/refresh', undefined, { skipAuthRefresh: true } as AuthRequestConfig);
|
||||
return data as { access_token?: string; tenant?: unknown };
|
||||
};
|
||||
|
||||
@@ -277,6 +293,59 @@ export const clearAuthToken = () => {
|
||||
delete api.defaults.headers.common.Authorization;
|
||||
};
|
||||
|
||||
export const setAuthRefreshHandlers = (handlers: AuthRefreshHandlers) => {
|
||||
authRefreshHandlers = handlers;
|
||||
};
|
||||
|
||||
const performTokenRefresh = async (): Promise<string> => {
|
||||
if (refreshPromise) {
|
||||
return refreshPromise;
|
||||
}
|
||||
|
||||
refreshPromise = refreshSession()
|
||||
.then((data) => {
|
||||
const token = data?.access_token;
|
||||
if (!token) {
|
||||
throw new Error('Missing access token in refresh response');
|
||||
}
|
||||
setAuthToken(token);
|
||||
authRefreshHandlers.onRefreshSuccess?.(token, { tenant: data?.tenant });
|
||||
return token;
|
||||
})
|
||||
.catch((error) => {
|
||||
authRefreshHandlers.onRefreshFailure?.(error);
|
||||
throw error;
|
||||
})
|
||||
.finally(() => {
|
||||
refreshPromise = null;
|
||||
});
|
||||
|
||||
return refreshPromise;
|
||||
};
|
||||
|
||||
api.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error: AxiosError) => {
|
||||
const response = error.response;
|
||||
const config = (error.config || {}) as AuthAwareRequestConfig;
|
||||
if (!response || response.status !== 401 || config._retry || config.skipAuthRefresh) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
config._retry = true;
|
||||
|
||||
try {
|
||||
const token = await performTokenRefresh();
|
||||
const headers = (config.headers ?? {}) as Record<string, unknown>;
|
||||
headers.Authorization = `Bearer ${token}`;
|
||||
config.headers = headers as AuthAwareRequestConfig['headers'];
|
||||
return api(config);
|
||||
} catch (refreshError) {
|
||||
return Promise.reject(refreshError);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export type {
|
||||
DownloadLink,
|
||||
DocumentResponse,
|
||||
|
||||
Reference in New Issue
Block a user