fix(proxy): sync system proxy switch state with background highlight #5240, #5439, #5421, #4651, #4536

Fixed race condition where button state and background color would desync
during rapid toggles or slow backend responses.

Root causes:
- Button used actualState while background used configState
- toggleSystemProxy returned immediately via setTimeout(0), releasing lock
  before backend operation completed
- 100ms delay in updateProxyStatus caused visual flash

Changes:
- Unified state source: both button and background now use actualState
- Made toggleSystemProxy truly async with proper await chain
- Wrapped toggle function with useLockFn to serialize operations
- Reduced state refresh delay from 100ms to 50ms
- Fixed GuardState lock release timing for async operations
- Added proper error rollback with state refresh on failure

Resolves issue where proxy toggle button would show as off while
background remained green, or vice versa.
This commit is contained in:
Tunglies 2025-11-23 12:14:04 +08:00
parent ddd24eb3ac
commit 44adf55975
No known key found for this signature in database
GPG Key ID: B9B01B389469B3E8
3 changed files with 24 additions and 21 deletions

View File

@ -60,6 +60,7 @@ export function GuardState<T>(props: Props<T>) {
if (waitTime <= 0) { if (waitTime <= 0) {
await onGuard(newValue, value!); await onGuard(newValue, value!);
lockRef.current = false;
} else { } else {
// debounce guard // debounce guard
clearTimeout(timeRef.current); clearTimeout(timeRef.current);
@ -71,6 +72,8 @@ export function GuardState<T>(props: Props<T>) {
// 状态回退 // 状态回退
onChange(saveRef.current!); onChange(saveRef.current!);
onCatch(err); onCatch(err);
} finally {
lockRef.current = false;
} }
}, waitTime); }, waitTime);
} }
@ -78,8 +81,8 @@ export function GuardState<T>(props: Props<T>) {
// 状态回退 // 状态回退
onChange(saveRef.current!); onChange(saveRef.current!);
onCatch(err); onCatch(err);
}
lockRef.current = false; lockRef.current = false;
}
}; };
const { children: nestedChildren, ...restProps } = childProps; const { children: nestedChildren, ...restProps } = childProps;

View File

@ -123,7 +123,7 @@ const ProxyControlSwitches = ({
const sysproxyRef = useRef<DialogRef>(null); const sysproxyRef = useRef<DialogRef>(null);
const tunRef = useRef<DialogRef>(null); const tunRef = useRef<DialogRef>(null);
const { enable_tun_mode, enable_system_proxy } = verge ?? {}; const { enable_tun_mode } = verge ?? {};
const showErrorNotice = useCallback( const showErrorNotice = useCallback(
(msg: string) => showNotice.error(msg), (msg: string) => showNotice.error(msg),
@ -175,7 +175,7 @@ const ProxyControlSwitches = ({
onInfoClick={() => sysproxyRef.current?.open()} onInfoClick={() => sysproxyRef.current?.open()}
onToggle={(value) => toggleSystemProxy(value)} onToggle={(value) => toggleSystemProxy(value)}
onError={onError} onError={onError}
highlight={enable_system_proxy} highlight={systemProxyActualState}
/> />
)} )}

View File

@ -1,3 +1,4 @@
import { useLockFn } from "ahooks";
import useSWR, { mutate } from "swr"; import useSWR, { mutate } from "swr";
import { closeAllConnections } from "tauri-plugin-mihomo-api"; import { closeAllConnections } from "tauri-plugin-mihomo-api";
@ -41,31 +42,30 @@ export const useSystemProxyState = () => {
} }
}; };
const updateProxyStatus = async () => { const updateProxyStatus = async (isEnabling: boolean) => {
await new Promise((resolve) => setTimeout(resolve, 100)); // 关闭时更快响应,开启时等待系统确认
const delay = isEnabling ? 20 : 10;
await new Promise((resolve) => setTimeout(resolve, delay));
await mutate("getSystemProxy"); await mutate("getSystemProxy");
await mutate("getAutotemProxy"); await mutate("getAutotemProxy");
}; };
const toggleSystemProxy = (enabled: boolean) => { const toggleSystemProxy = useLockFn(async (enabled: boolean) => {
mutateVerge({ ...verge, enable_system_proxy: enabled }, false); mutateVerge({ ...verge, enable_system_proxy: enabled }, false);
setTimeout(async () => {
try { try {
if (!enabled && verge?.auto_close_connection) { if (!enabled && verge?.auto_close_connection) {
closeAllConnections(); await closeAllConnections();
} }
await patchVerge({ enable_system_proxy: enabled }); await patchVerge({ enable_system_proxy: enabled });
await updateProxyStatus(enabled);
updateProxyStatus();
} catch (error) { } catch (error) {
console.warn("[useSystemProxyState] toggleSystemProxy failed:", error); console.warn("[useSystemProxyState] toggleSystemProxy failed:", error);
mutateVerge({ ...verge, enable_system_proxy: !enabled }, false); mutateVerge({ ...verge, enable_system_proxy: !enabled }, false);
await updateProxyStatus(!enabled);
throw error;
} }
}, 0); });
return Promise.resolve();
};
return { return {
actualState: getSystemProxyActualState(), actualState: getSystemProxyActualState(),