first commit

This commit is contained in:
Adrian Zürcher
2025-10-12 14:56:18 +02:00
parent a9f2e11fe6
commit a908db4f38
92 changed files with 13273 additions and 0 deletions

125
src/boot/axios.ts Normal file
View File

@@ -0,0 +1,125 @@
import { boot } from 'quasar/wrappers';
import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
import axios from 'axios';
import { useLogin } from 'src/vueLib/login/useLogin';
const host = window.location.hostname;
export const portApp = 9500;
// Create axios instance
export const appApi: AxiosInstance = axios.create({
baseURL: `http://${host}:${portApp}/api`,
timeout: 10000,
withCredentials: true,
});
interface RetryRequestConfig extends AxiosRequestConfig {
_retry?: boolean;
}
const noRefreshEndpoints = ['/login', '/secure/login/refresh', '/logout'];
// ========= Refresh Queue Handling ========= //
let isRefreshing = false;
interface FailedRequest {
resolve: (value?: unknown) => void;
reject: (reason?: Error) => void;
}
let failedQueue: FailedRequest[] = [];
const processQueue = (error: Error | null): void => {
failedQueue.forEach((prom) => {
if (error) prom.reject(error);
else prom.resolve();
});
failedQueue = [];
};
// ========================================= //
appApi.interceptors.response.use(
(response: AxiosResponse) => response,
async (error: AxiosError<unknown, RetryRequestConfig>): Promise<AxiosResponse> => {
const { refresh, logout } = useLogin();
const originalRequest = error.config as RetryRequestConfig | undefined;
// Skip refresh for login/logout endpoints
if (
!originalRequest ||
noRefreshEndpoints.some((url) => originalRequest.url?.includes(url ?? ''))
) {
const message = error instanceof Error ? error.message : JSON.stringify(error);
throw new Error(message);
}
// Handle unauthorized responses
if (error.response?.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
// Wait until refresh completes
return new Promise<AxiosResponse>((resolve, reject) => {
failedQueue.push({
resolve: () => {
void appApi(originalRequest).then(resolve).catch(reject);
},
reject,
});
});
}
originalRequest._retry = true;
isRefreshing = true;
try {
const refreshed = await refresh().catch(() => false);
processQueue(null);
if (refreshed) {
// Token refreshed successfully → retry request
return appApi(originalRequest);
}
// Refresh returned false → logout
console.warn('[Axios] Refresh returned false, logging out');
await logout();
throw new Error('Token refresh failed');
} catch (err) {
const e = err instanceof Error ? err : new Error(String(err));
console.error('[Axios] Token refresh failed:', e.message);
// Always logout, even if refresh throws
try {
await logout();
} catch (logoutErr) {
console.error('[Axios] Logout failed after token refresh error:', logoutErr);
}
processQueue(e);
throw e;
} finally {
isRefreshing = false;
}
}
// Not a 401 — rethrow as Error
let msg = '';
if (error && (error as AxiosError).isAxiosError) {
const axiosError = error as AxiosError<{ message?: string }>;
msg = axiosError.response?.data?.message ?? axiosError.message;
} else {
msg = error instanceof Error ? error.message : JSON.stringify(error);
}
throw new Error(msg);
},
);
// ======== Boot registration for Quasar ======== //
export default boot(({ app }) => {
app.config.globalProperties.$axios = axios;
app.config.globalProperties.$appApi = appApi;
});
export { axios };