fix: handle ERR_NETWORK_CHANGED from Docker network changes
- Add retry logic for transient network errors in API client - Retry up to 2 times with exponential backoff on network errors - Reduce session polling from 10s to 30s to decrease error frequency - Handle 502/503/504 gateway errors with retries as well
This commit is contained in:
@@ -14,13 +14,38 @@ export const shouldSkipAuthRedirect = (path: string): boolean => {
|
|||||||
return path.startsWith("/login") || path.startsWith("/auth");
|
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<any, number>();
|
||||||
|
|
||||||
apiClient.interceptors.response.use(
|
apiClient.interceptors.response.use(
|
||||||
(response) => response,
|
(response) => response,
|
||||||
(error) => {
|
async (error) => {
|
||||||
const status = error?.response?.status;
|
const status = error?.response?.status;
|
||||||
if (status === 401 && !shouldSkipAuthRedirect(window.location.pathname)) {
|
if (status === 401 && !shouldSkipAuthRedirect(window.location.pathname)) {
|
||||||
window.location.assign(`${BASE_URL}/auth/login`);
|
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);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -58,10 +58,10 @@ export const AppShell = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void loadSessions();
|
void loadSessions();
|
||||||
// Poll every 10 seconds
|
// Poll every 30 seconds (reduced from 10s to avoid ERR_NETWORK_CHANGED from Docker network changes)
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
void loadSessions();
|
void loadSessions();
|
||||||
}, 10000);
|
}, 30000);
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [loadSessions]);
|
}, [loadSessions]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user