diff --git a/src-tauri/src/proxy_manager.rs b/src-tauri/src/proxy_manager.rs index ae2c9013..ff432b38 100644 --- a/src-tauri/src/proxy_manager.rs +++ b/src-tauri/src/proxy_manager.rs @@ -619,6 +619,11 @@ impl ProxyManager { }; let content = serde_json::to_string_pretty(&cache)?; crate::app_dirs::write_owner_only(&cache_file, content.as_bytes())?; + // The page reads a receipt once; a check run by the API or an agent + // reaches it only through this event. + if let Err(e) = events::emit("proxy-check-saved", proxy_id) { + log::warn!("Event emit failed event=proxy-check-saved err=\"{e}\""); + } Ok(()) } diff --git a/src/lib/proxy-check-store.ts b/src/lib/proxy-check-store.ts index 5bc0d88f..d3607aa3 100644 --- a/src/lib/proxy-check-store.ts +++ b/src/lib/proxy-check-store.ts @@ -1,4 +1,5 @@ import { invoke } from "@tauri-apps/api/core"; +import { listen } from "@tauri-apps/api/event"; import { useCallback, useEffect, useMemo, useSyncExternalStore } from "react"; import type { ProxyCheckResult, StoredProxy } from "@/types"; @@ -7,8 +8,8 @@ import type { ProxyCheckResult, StoredProxy } from "@/types"; * * The profile table renders a check button per row, so two profiles on the * same proxy are two buttons. A check run from one row is a fact about the - * proxy, not the row, and the other row must show it too; the backend cache - * is only re-read on mount, so it cannot carry that update by itself. + * proxy, not the row, and the other row must show it too. The backend cache + * is read on mount and again when the backend saves a check it ran itself. */ export interface ProxyCheckEntry { /** The route the receipt and the in-flight check belong to. */ @@ -33,6 +34,50 @@ const anyListeners = new Set<() => void>(); let version = 0; /** Cache reads in flight, keyed by route, so N rows cost one invoke. */ const cacheReads = new Map>(); +/** The route each proxy was last shown with, where a saved check goes. */ +const routes = new Map(); +/** Checks saved per proxy; only the read after the newest one publishes. */ +const savedChecks = new Map(); +let watchingSaves = false; + +/** + * A check the backend saved (the API, an agent, a launch): read its receipt + * again. A check this store runs publishes its own result. + */ +function onCheckSaved(proxyId: string) { + const routeKey = routes.get(proxyId); + if (!routeKey || entries.get(proxyId)?.checking) return; + const saved = nextSave(proxyId); + invoke("get_cached_proxy_check", { proxyId }) + .then((result) => { + const current = entries.get(proxyId); + if (savedChecks.get(proxyId) !== saved || current?.checking) return; + // A failed receipt keeps the words of the failure this store saw. + const failure = + !result?.is_valid && current?.routeKey === routeKey + ? current.failure + : null; + publish(proxyId, { routeKey, result, checking: false, failure }); + }) + .catch(() => {}); +} + +/** Counts a saved check; reads of the receipt from before it are dropped. */ +function nextSave(proxyId: string): number { + const saved = (savedChecks.get(proxyId) ?? 0) + 1; + savedChecks.set(proxyId, saved); + return saved; +} + +function watchSavedChecks() { + if (watchingSaves) return; + watchingSaves = true; + listen("proxy-check-saved", (event) => + onCheckSaved(event.payload), + ).catch(() => { + watchingSaves = false; + }); +} /** Credentials and the endpoint decide whether an old receipt still applies. */ export function proxyRouteKey(proxy: StoredProxy): string { @@ -47,6 +92,7 @@ function publish(proxyId: string, next: ProxyCheckEntry) { } function subscribeAny(listener: () => void) { + watchSavedChecks(); anyListeners.add(listener); return () => { anyListeners.delete(listener); @@ -94,6 +140,7 @@ function entryFor(proxyId: string, routeKey: string): ProxyCheckEntry { } function subscribe(proxyId: string, listener: () => void) { + watchSavedChecks(); const set = listeners.get(proxyId) ?? new Set(); set.add(listener); listeners.set(proxyId, set); @@ -104,6 +151,7 @@ function subscribe(proxyId: string, listener: () => void) { } async function loadCached(proxyId: string, routeKey: string) { + routes.set(proxyId, routeKey); const known = entries.get(proxyId); if (known?.routeKey === routeKey) return; const pending = cacheReads.get(routeKey); @@ -149,9 +197,11 @@ export async function runProxyCheck( proxyId: proxy.id, proxySettings: proxy.proxy_settings, }); + nextSave(proxy.id); publish(proxy.id, { routeKey, result, checking: false, failure: null }); return result; } catch (error) { + nextSave(proxy.id); publish(proxy.id, { routeKey, result: {