Files
clash-verge-rev/src/hooks/use-proxy-selection.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

183 lines
5.0 KiB
TypeScript

import { useCallback, useRef } from 'react'
import {
closeConnection,
getConnections,
selectNodeForGroup,
unfixedProxy,
} from 'tauri-plugin-mihomo-api'
import {
useForgetSelection,
useRecordSelection,
} from '@/hooks/use-record-selection'
import { useVerge } from '@/hooks/use-verge'
import { syncTrayProxySelection } from '@/services/cmds'
import { mutate } from '@/services/mutate'
import { debugLog } from '@/utils/debug'
// 缓存连接清理
const cleanupConnections = async (previousProxy: string) => {
try {
const { connections } = await getConnections()
const cleanupPromises = (connections ?? [])
.filter((conn) => conn.chains.includes(previousProxy))
.map((conn) => closeConnection(conn.id))
if (cleanupPromises.length > 0) {
await Promise.allSettled(cleanupPromises)
debugLog(`[ProxySelection] 清理了 ${cleanupPromises.length} 个连接`)
}
} catch (error) {
console.warn('[ProxySelection] 连接清理失败:', error)
}
}
interface ProxySelectionOptions {
onSuccess?: () => void
onError?: (error: any) => void
enableConnectionCleanup?: boolean
}
interface ProxyChangeRequest {
groupName: string
proxyName: string
previousProxy?: string
fixed?: string
}
// 代理选择 Hook
export const useProxySelection = (options: ProxySelectionOptions = {}) => {
const recordSelection = useRecordSelection()
const forgetSelection = useForgetSelection()
const { verge } = useVerge()
const pendingRequestRef = useRef<ProxyChangeRequest | null>(null)
const isProcessingRef = useRef(false)
const { onSuccess, onError, enableConnectionCleanup = true } = options
const autoCloseConnection = verge?.auto_close_connection ?? false
// 切换节点
const syncTraySelection = useCallback(() => {
mutate(() => syncTrayProxySelection(), {
id: 'sync-tray-selection',
errorNotice: false,
}).catch((error) => {
console.error('[ProxySelection] 托盘状态同步失败:', error)
})
}, [])
const executeChange = useCallback(
async (request: ProxyChangeRequest) => {
const { groupName, proxyName, previousProxy, fixed } = request
const isFixedProxy = fixed === proxyName
if (isFixedProxy) {
debugLog(`[ProxySelection] 代理取消固定: ${groupName} -> ${proxyName}`)
} else {
debugLog(`[ProxySelection] 代理切换: ${groupName} -> ${proxyName}`)
}
try {
const result = await mutate(
() =>
isFixedProxy
? unfixedProxy(groupName)
: selectNodeForGroup(groupName, proxyName),
{ id: `select-proxy-node:${groupName}`, errorNotice: false },
)
if (!result.ok) return
onSuccess?.()
syncTraySelection()
if (isFixedProxy) {
await forgetSelection(groupName)
debugLog(`[ProxySelection] 代理和状态同步完成: ${groupName}`)
} else {
await recordSelection(groupName, proxyName)
debugLog(
`[ProxySelection] 代理和状态同步完成: ${groupName} -> ${proxyName}`,
)
}
if (enableConnectionCleanup && autoCloseConnection && previousProxy) {
void cleanupConnections(previousProxy)
}
} catch (error) {
console.error(
`[ProxySelection] 代理切换失败: ${groupName} -> ${proxyName}`,
error,
)
onError?.(error)
}
},
[
autoCloseConnection,
forgetSelection,
enableConnectionCleanup,
onError,
onSuccess,
recordSelection,
syncTraySelection,
],
)
const flushChangeQueue = useCallback(async () => {
if (isProcessingRef.current) return
isProcessingRef.current = true
try {
while (pendingRequestRef.current) {
const request = pendingRequestRef.current
pendingRequestRef.current = null
await executeChange(request)
}
} finally {
isProcessingRef.current = false
if (pendingRequestRef.current) {
void flushChangeQueue()
}
}
}, [executeChange])
const changeProxy = useCallback(
(
groupName: string,
proxyName: string,
previousProxy?: string,
fixed?: string,
) => {
pendingRequestRef.current = {
groupName,
proxyName,
previousProxy,
fixed,
}
void flushChangeQueue()
},
[flushChangeQueue],
)
const handleSelectChange = useCallback(
(groupName: string, previousProxy?: string, fixed?: string) =>
(event: { target: { value: string } }) => {
changeProxy(groupName, event.target.value, previousProxy, fixed)
},
[changeProxy],
)
const handleProxyGroupChange = useCallback(
(
group: { name: string; now?: string | null; fixed?: string | null },
proxy: { name: string },
) => {
changeProxy(group.name, proxy.name, group.now!, group.fixed!)
},
[changeProxy],
)
return {
changeProxy,
handleSelectChange,
handleProxyGroupChange,
}
}