mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-22 19:52:14 +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
183 lines
5.0 KiB
TypeScript
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,
|
|
}
|
|
}
|