Files
clash-verge-rev/src/hooks/use-system-proxy-state.ts
T
Tunglies 6e08a77a84 refactor(backend): specta ipc bindings (#8195)
* 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
2026-10-12 06:16:05 +00:00

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,
}
}