mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-19 16:10:46 +08:00
* refactor(ipc): generate bindings with Specta * fix(profile): project IPC inputs to consumed fields * fix(events): remove unused refresh payloads * fix(webdav): return only consumed backup metadata * fix(network): trim unused interface metadata * fix(sysinfo): return only displayed system fields * chore(knip): check generated runtime exports * fix(proxy): confirm runtime chain updates before UI changes * fix(clipboard): propagate proxy environment copy failures * refactor(dns): remove ignored runtime patch * refactor(core): trim unused upgrade response fields * refactor(ipc): avoid redundant response copies * refactor(proxy): clarify automatic proxy query naming * chore(tests): remove added IPC regression tests * refactor(ipc): enforce typed command boundaries * chore(contract): check generated bindings before push * refactor(ipc): remove unused proxy liveness payloads
127 lines
3.9 KiB
TypeScript
127 lines
3.9 KiB
TypeScript
import { useRef } from 'react'
|
|
import { closeAllConnections } from 'tauri-plugin-mihomo-api'
|
|
|
|
import { useDisplayedMixedPort } from '@/hooks/use-displayed-mixed-port'
|
|
import { useVerge } from '@/hooks/use-verge'
|
|
import { useSystemData } from '@/providers/app-data-context'
|
|
import {
|
|
getAutoProxy,
|
|
getEmbeddedServerPort,
|
|
patchVergeConfig,
|
|
} from '@/services/cmds'
|
|
import { mutate } from '@/services/mutate'
|
|
import {
|
|
removeCacheData,
|
|
revalidateQueries,
|
|
useQuery,
|
|
} from '@/services/query-client'
|
|
|
|
export const useSystemProxyState = () => {
|
|
const { verge, mutateVerge } = useVerge()
|
|
const { sysproxy } = useSystemData()
|
|
const displayedMixedPort = useDisplayedMixedPort()
|
|
const { data: autoproxy } = useQuery({
|
|
queryKey: ['getAutoProxy'],
|
|
queryFn: getAutoProxy,
|
|
refetchOnWindowFocus: true,
|
|
refetchOnReconnect: true,
|
|
})
|
|
const { data: pacPort } = useQuery({
|
|
queryKey: ['getEmbeddedServerPort'],
|
|
queryFn: getEmbeddedServerPort,
|
|
})
|
|
|
|
const { proxy_auto_config, proxy_host } = verge ?? {}
|
|
|
|
const indicator = (() => {
|
|
const host = proxy_host || '127.0.0.1'
|
|
if (proxy_auto_config) {
|
|
if (!autoproxy?.enable) return false
|
|
if (!pacPort) return false
|
|
return autoproxy.url === `http://${host}:${pacPort}/commands/pac`
|
|
} else {
|
|
if (!sysproxy?.enable) return false
|
|
return sysproxy.server === `${host}:${displayedMixedPort}`
|
|
}
|
|
})()
|
|
|
|
// Coalesce rapid clicks so only the latest requested state is applied.
|
|
const pendingRef = useRef<boolean | null>(null)
|
|
const busyRef = useRef(false)
|
|
|
|
const toggleSystemProxy = async (enabled: boolean) => {
|
|
// Roll failed optimistic writes back to the latest confirmed state.
|
|
let confirmed = verge?.enable_system_proxy ?? false
|
|
mutateVerge(
|
|
(prev) => (prev ? { ...prev, enable_system_proxy: enabled } : prev),
|
|
false,
|
|
)
|
|
pendingRef.current = enabled
|
|
|
|
if (busyRef.current) return
|
|
busyRef.current = true
|
|
|
|
try {
|
|
while (pendingRef.current !== null) {
|
|
const target = pendingRef.current
|
|
pendingRef.current = null
|
|
await mutate(() => patchVergeConfig({ enable_system_proxy: target }), {
|
|
id: 'toggle-system-proxy',
|
|
errorNotice: false,
|
|
})
|
|
confirmed = target
|
|
if (!target && verge?.auto_close_connection) {
|
|
await mutate(() => closeAllConnections(), {
|
|
id: 'close-all-connections',
|
|
errorNotice: false,
|
|
}).catch(() => {})
|
|
}
|
|
}
|
|
} catch (error) {
|
|
mutateVerge(
|
|
(prev) => (prev ? { ...prev, enable_system_proxy: confirmed } : prev),
|
|
false,
|
|
)
|
|
// Queued requests were based on a state that never landed.
|
|
pendingRef.current = null
|
|
throw error
|
|
} finally {
|
|
busyRef.current = false
|
|
// Refreshing cached state is not part of the toggle's result: a failed read must not
|
|
// turn a write that landed into a reported failure.
|
|
try {
|
|
await revalidateQueries([['getVergeConfig']])
|
|
} catch (error) {
|
|
console.warn(
|
|
'[system-proxy] rereading the config after a toggle failed:',
|
|
error,
|
|
)
|
|
}
|
|
// Kept separate so an unreadable config cannot discard OS state that did read.
|
|
try {
|
|
await revalidateQueries([['getSystemProxy'], ['getAutoProxy']])
|
|
} catch (error) {
|
|
console.warn(
|
|
'[system-proxy] rereading the OS proxy after a toggle failed:',
|
|
error,
|
|
)
|
|
// The indicator reports observed OS state, so an unreadable one must read as inactive
|
|
// rather than stay live from a stale cache.
|
|
await Promise.all([
|
|
removeCacheData(['getSystemProxy']),
|
|
removeCacheData(['getAutoProxy']),
|
|
])
|
|
}
|
|
}
|
|
}
|
|
|
|
const invalidateProxyState = () =>
|
|
revalidateQueries([['getSystemProxy'], ['getAutoProxy']])
|
|
|
|
return {
|
|
indicator,
|
|
toggleSystemProxy,
|
|
invalidateProxyState,
|
|
}
|
|
}
|