22474cdba5
Backend (ruff): - Fix 106 errors: move imports to top of file (E402) - Remove unused imports (F401) - Add missing imports for undefined names (F821) - Remove unused variables (F841) - Fix test_models.py broken RefreshToken test - Fix test_projects_api.py missing TestClient import Frontend (eslint): - Remove unused imports/variables across 10 files - Fix explicit any types in client.ts and sessions.ts - Clean up empty block statements in terminal.tsx Quality gates: ruff (pass), eslint (pass), tsc --noEmit (pass), pytest (98 passed, 4 pre-existing failures)
52 lines
1.6 KiB
TypeScript
52 lines
1.6 KiB
TypeScript
import axios, { type AxiosRequestConfig } from "axios";
|
|
|
|
const BASE_URL = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8000";
|
|
|
|
export const apiClient = axios.create({
|
|
baseURL: BASE_URL,
|
|
withCredentials: true,
|
|
headers: {
|
|
"Content-Type": "application/json"
|
|
}
|
|
});
|
|
|
|
export const shouldSkipAuthRedirect = (path: string): boolean => {
|
|
return path.startsWith("/login") || path.startsWith("/auth");
|
|
};
|
|
|
|
// Retry config for transient network errors
|
|
const MAX_RETRIES = 2;
|
|
const RETRY_DELAY_MS = 1000;
|
|
|
|
// Track retry count per request
|
|
const retryCount = new WeakMap<AxiosRequestConfig, number>();
|
|
|
|
apiClient.interceptors.response.use(
|
|
(response) => response,
|
|
async (error) => {
|
|
const status = error?.response?.status;
|
|
if (status === 401 && !shouldSkipAuthRedirect(window.location.pathname)) {
|
|
window.location.assign(`${BASE_URL}/auth/login`);
|
|
return Promise.reject(error);
|
|
}
|
|
|
|
// Retry on transient network errors (ERR_NETWORK_CHANGED, etc.)
|
|
const isNetworkError = !error.response && error.message?.includes("Network");
|
|
const isRetryable = isNetworkError || status >= 502; // 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout
|
|
|
|
if (isRetryable) {
|
|
const config = error.config;
|
|
const currentRetry = retryCount.get(config) || 0;
|
|
|
|
if (currentRetry < MAX_RETRIES) {
|
|
retryCount.set(config, currentRetry + 1);
|
|
// Wait before retrying
|
|
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS * (currentRetry + 1)));
|
|
return apiClient(config);
|
|
}
|
|
}
|
|
|
|
return Promise.reject(error);
|
|
}
|
|
);
|