diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 143815261..2622d294f 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -5423,7 +5423,7 @@ dependencies = [ [[package]] name = "world-monitor" -version = "2.5.19" +version = "2.5.21" dependencies = [ "getrandom 0.2.17", "keyring", diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 01eaee084..f99f8bbb9 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -563,8 +563,8 @@ fn open_settings_window(app: &AppHandle) -> Result<(), String> { let _settings_window = WebviewWindowBuilder::new(app, "settings", WebviewUrl::App("settings.html".into())) .title("World Monitor Settings") - .inner_size(980.0, 760.0) - .min_inner_size(820.0, 620.0) + .inner_size(980.0, 600.0) + .min_inner_size(820.0, 480.0) .resizable(true) .background_color(tauri::webview::Color(26, 28, 30, 255)) .build() diff --git a/src/app/refresh-scheduler.ts b/src/app/refresh-scheduler.ts index cda285503..6dd98dfbf 100644 --- a/src/app/refresh-scheduler.ts +++ b/src/app/refresh-scheduler.ts @@ -85,7 +85,7 @@ export class RefreshScheduler implements AppModule { } } catch (e) { console.error(`[App] Refresh ${name} failed:`, e); - currentMultiplier = 1; + currentMultiplier = Math.min(currentMultiplier * 2, MAX_BACKOFF_MULTIPLIER); } finally { this.ctx.inFlight.delete(name); scheduleNext(computeDelay(intervalMs * currentMultiplier, false)); diff --git a/src/services/runtime.ts b/src/services/runtime.ts index f7094e780..245434691 100644 --- a/src/services/runtime.ts +++ b/src/services/runtime.ts @@ -257,6 +257,7 @@ export function installRuntimeFetchPatch(): void { const nativeFetch = window.fetch.bind(window); let localApiToken: string | null = null; let tokenFetchedAt = 0; + let authRetryCooldownUntil = 0; // suppress 401 retries after consecutive failures window.fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise => { const target = getApiTargetFromRequestInput(input); @@ -333,7 +334,8 @@ export function installRuntimeFetchPatch(): void { if (debug) console.log(`[fetch] ${target} → ${response.status} (${Math.round(performance.now() - t0)}ms)`); // Token may be stale after a sidecar restart — refresh and retry once. - if (response.status === 401 && localApiToken) { + // Skip retry if we recently failed (avoid doubling every request during auth outages). + if (response.status === 401 && localApiToken && Date.now() > authRetryCooldownUntil) { if (debug) console.log(`[fetch] 401 from sidecar, refreshing token and retrying`); try { const { tryInvokeTauri } = await import('@/services/tauri-bridge'); @@ -348,6 +350,12 @@ export function installRuntimeFetchPatch(): void { retryHeaders.set('Authorization', `Bearer ${localApiToken}`); response = await fetchLocalWithStartupRetry(nativeFetch, localUrl, { ...init, headers: retryHeaders }); if (debug) console.log(`[fetch] retry ${target} → ${response.status}`); + if (response.status === 401) { + authRetryCooldownUntil = Date.now() + 60_000; + if (debug) console.log(`[fetch] auth retry failed, suppressing retries for 60s`); + } else { + authRetryCooldownUntil = 0; + } } } diff --git a/src/services/threat-classifier.ts b/src/services/threat-classifier.ts index 5751e030e..6187b0370 100644 --- a/src/services/threat-classifier.ts +++ b/src/services/threat-classifier.ts @@ -462,9 +462,9 @@ function flushBatch(): void { }); job.resolve(toThreat(resp)); } catch (err) { - if (err instanceof ApiError && (err.statusCode === 429 || err.statusCode >= 500)) { + if (err instanceof ApiError && (err.statusCode === 401 || err.statusCode === 429 || err.statusCode >= 500)) { batchPaused = true; - const delay = err.statusCode === 429 ? 60_000 : 30_000; + const delay = err.statusCode === 401 ? 120_000 : err.statusCode === 429 ? 60_000 : 30_000; console.warn(`[Classify] ${err.statusCode} — pausing AI classification for ${delay / 1000}s`); const remaining = batch.slice(i + 1); // Failed job: increment attempts, requeue if under limit