fix: provider proxy delay check (#7456)

* refactor(proxy): support delay testing for provider-based proxies

* style: remove debug console logs

* docs: update changelog

* feat(services): merge provider nodes into records in calcuProxies

---------

Co-authored-by: Tunglies <tunglies.dev@outlook.com>
This commit is contained in:
oomeow
2026-07-11 05:24:41 +00:00
committed by GitHub
co-authored by Tunglies
parent e8cefa8b07
commit 562828e14d
9 changed files with 73 additions and 87 deletions
+1
View File
@@ -38,5 +38,6 @@
- 改进切换订阅后激活选中节点的逻辑
- 实现代理组粘性滚动列表
- 完善了配置覆写的相关逻辑
- 支持 provider 节点延迟检测
</details>
+14 -34
View File
@@ -21,7 +21,7 @@ import {
InputLabel,
MenuItem,
Select,
SelectChangeEvent,
type SelectChangeEvent,
Tooltip,
Typography,
alpha,
@@ -31,7 +31,7 @@ import { useLockFn } from 'ahooks'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router'
import { delayGroup, healthcheckProxyProvider } from 'tauri-plugin-mihomo-api'
import { delayGroup } from 'tauri-plugin-mihomo-api'
import { EnhancedCard } from '@/components/home/enhanced-card'
import { useProfiles } from '@/hooks/use-profiles'
@@ -558,11 +558,12 @@ export const CurrentProxyCard = () => {
debugLog(
`[CurrentProxyCard] 自动检测当前节点延迟,组: ${groupName}, 节点: ${proxyName}`,
)
if (proxyRecord.provider) {
await healthcheckProxyProvider(proxyRecord.provider)
} else {
await delayManager.checkDelay(proxyName, groupName, timeout)
}
await delayManager.checkDelay(
proxyRecord.name,
groupName,
timeout,
proxyRecord.provider,
)
} catch (error) {
console.error(
`[CurrentProxyCard] 自动检测当前节点延迟失败,组: ${groupName}, 节点: ${proxyName}`,
@@ -658,8 +659,7 @@ export const CurrentProxyCard = () => {
const timeout = verge?.default_latency_timeout || 10000
// 获取当前组的所有代理
const proxyNames: string[] = []
const providers: Set<string> = new Set()
const delayProxies: IProxyItem[] = []
if (isGlobalMode && proxies?.global) {
// 全局模式
@@ -672,11 +672,7 @@ export const CurrentProxyCard = () => {
allProxies.forEach((name: string) => {
const proxy = state.proxyData.records[name]
if (proxy?.provider) {
providers.add(proxy.provider)
} else {
proxyNames.push(name)
}
delayProxies.push(proxy)
})
} else {
// 规则模式
@@ -684,35 +680,19 @@ export const CurrentProxyCard = () => {
if (group) {
group.all.forEach((name: string) => {
const proxy = state.proxyData.records[name]
if (proxy?.provider) {
providers.add(proxy.provider)
} else {
proxyNames.push(name)
}
delayProxies.push(proxy)
})
}
}
debugLog(
`[CurrentProxyCard] 找到代理数量: ${proxyNames.length}, 提供者数量: ${providers.size}`,
)
// 测试提供者的节点
if (providers.size > 0) {
debugLog(`[CurrentProxyCard] 开始测试提供者节点`)
await Promise.allSettled(
[...providers].map((p) => healthcheckProxyProvider(p)),
)
}
// 测试非提供者的节点
if (proxyNames.length > 0) {
// 测试全部节点
if (delayProxies.length > 0) {
const url = delayManager.getUrl(groupName)
debugLog(`[CurrentProxyCard] 测试URL: ${url}, 超时: ${timeout}ms`)
try {
await Promise.race([
delayManager.checkListDelay(proxyNames, groupName, timeout),
delayManager.checkListDelay(delayProxies, groupName, timeout),
delayGroup(groupName, url, timeout),
])
debugLog(`[CurrentProxyCard] 延迟测试完成,组: ${groupName}`)
+2 -19
View File
@@ -11,7 +11,7 @@ import {
useRef,
useState,
} from 'react'
import { delayGroup, healthcheckProxyProvider } from 'tauri-plugin-mihomo-api'
import { delayGroup } from 'tauri-plugin-mihomo-api'
import {
BaseEmpty,
@@ -97,29 +97,12 @@ function useProxyRenderState(
debugLog(`[ProxyGroups] 找到代理数量: ${proxies.length}`)
const providers = new Set(
proxies.map((p) => p!.provider!).filter(Boolean),
)
if (providers.size) {
debugLog(`[ProxyGroups] 发现提供者,数量: ${providers.size}`)
Promise.allSettled(
[...providers].map((p) => healthcheckProxyProvider(p)),
).then(() => {
debugLog(`[ProxyGroups] 提供者健康检查完成`)
onProxies()
})
}
const names = proxies.filter((p) => !p!.provider).map((p) => p!.name)
debugLog(`[ProxyGroups] 过滤后需要测试的代理数量: ${names.length}`)
const url = delayManager.getUrl(groupName)
debugLog(`[ProxyGroups] 测试URL: ${url}, 超时: ${timeout}ms`)
try {
await Promise.race([
delayManager.checkListDelay(names, groupName, timeout),
delayManager.checkListDelay(proxies, groupName, timeout),
delayGroup(groupName, url, timeout).then((result) => {
debugLog(
`[ProxyGroups] getGroupProxyDelays返回结果数量:`,
+3 -7
View File
@@ -159,8 +159,7 @@ export const ProxyItemMini = (props: Props) => {
<BaseLoading />
</Widget>
)}
{!proxy.provider && delayValue !== -2 && (
// provider 的节点不支持检测
{delayValue !== -2 && (
<Widget
className="the-check"
onClick={(e) => {
@@ -182,16 +181,13 @@ export const ProxyItemMini = (props: Props) => {
<Widget
className="the-delay"
onClick={(e) => {
if (proxy.provider) return
e.preventDefault()
e.stopPropagation()
onDelay()
onDelay(proxy.provider)
}}
sx={({ palette }) => ({
color: delayManager.formatDelayColor(delayValue, timeout),
...(!proxy.provider
? { ':hover': { bgcolor: alpha(palette.primary.main, 0.15) } }
: {}),
':hover': { bgcolor: alpha(palette.primary.main, 0.15) },
})}
>
{delayManager.formatDelay(delayValue, timeout)}
+6 -10
View File
@@ -7,8 +7,8 @@ import {
ListItemIcon,
ListItemText,
styled,
SxProps,
Theme,
type SxProps,
type Theme,
} from '@mui/material'
import { BaseLoading } from '@/components/base'
@@ -125,14 +125,13 @@ export const ProxyItem = (props: Props) => {
</Widget>
)}
{!proxy.provider && delayValue !== -2 && (
// provider 的节点不支持检测
{delayValue !== -2 && (
<Widget
className="the-check"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
onDelay()
onDelay(proxy.provider)
}}
sx={({ palette }) => ({
display: 'none', // hover 时显示
@@ -148,16 +147,13 @@ export const ProxyItem = (props: Props) => {
<Widget
className="the-delay"
onClick={(e) => {
if (proxy.provider) return
e.preventDefault()
e.stopPropagation()
onDelay()
onDelay(proxy.provider)
}}
sx={({ palette }) => ({
color: delayManager.formatDelayColor(delayValue, timeout),
...(!proxy.provider
? { ':hover': { bgcolor: alpha(palette.primary.main, 0.15) } }
: {}),
':hover': { bgcolor: alpha(palette.primary.main, 0.15) },
})}
>
{delayManager.formatDelay(delayValue, timeout)}
+2 -3
View File
@@ -158,12 +158,11 @@ export const useRenderList = (
const calculateDelays = async () => {
try {
const timeout = verge?.default_latency_timeout || 10000
const proxyNames = allProxies.map((proxy) => proxy.name)
debugLog(`[ChainMode] 开始计算 ${proxyNames.length} 个节点的延迟`)
debugLog(`[ChainMode] 开始计算 ${allProxies.length} 个节点的延迟`)
// 使用 delayManager 计算延迟,每个节点计算完成后会自动触发监听器刷新界面
delayManager.checkListDelay(proxyNames, 'chain-mode', timeout)
delayManager.checkListDelay(allProxies, 'chain-mode', timeout)
} catch (error) {
console.error('Failed to calculate delays for chain mode:', error)
}
+10 -3
View File
@@ -21,7 +21,7 @@ export interface UseProxyDelayState {
delayValue: number
isPreset: boolean
timeout: number
onDelay: () => Promise<void>
onDelay: (providerName?: string) => Promise<void>
}
export function useProxyDelayState(
@@ -72,9 +72,16 @@ export function useProxyDelayState(
updateDelay()
}, [updateDelay])
const onDelay = useLockFn(async () => {
const onDelay = useLockFn(async (providerName?: string) => {
setDelayState({ delay: -2, updatedAt: Date.now() })
setDelayState(await delayManager.checkDelay(proxy.name, groupName, timeout))
setDelayState(
await delayManager.checkDelay(
proxy.name,
groupName,
timeout,
providerName,
),
)
})
return {
+5 -2
View File
@@ -217,11 +217,14 @@ export async function calcuProxies(): Promise<{
all: global?.all?.map((item) => generateItem(item)) || [],
}
// 原本的 records 拥有所有节点信息,新版本内核需要将 provider 的节点信息合并到 records 中, 同时兼容旧版本数据
const records = { ...proxyRecord, ...providerMap }
return {
global: _global as IProxyGroupItem,
direct: direct as IProxyItem,
groups,
records: proxyRecord as Record<string, IProxyItem>,
records: records as Record<string, IProxyItem>,
proxies: (proxies as IProxyItem[]) ?? [],
}
}
@@ -505,7 +508,7 @@ export async function saveWebdavConfig(
export async function listWebDavBackup() {
const list: IWebDavFile[] = await invoke<IWebDavFile[]>('list_webdav_backup')
list.map((item) => {
list.forEach((item) => {
item.filename = item.href.split('/').pop() as string
})
return list
+30 -9
View File
@@ -1,4 +1,8 @@
import { delayProxyByName, ProxyDelay } from 'tauri-plugin-mihomo-api'
import {
delayProxyByName,
healthcheckNodeInProvider,
type ProxyDelay,
} from 'tauri-plugin-mihomo-api'
import { debugLog } from '@/utils/debug'
@@ -198,10 +202,23 @@ class DelayManager {
return -1
}
// 统一延迟测试检测
async unifiedDelayCheck(
name: string,
url: string,
timeout: number,
providerName?: string,
) {
if (providerName)
return healthcheckNodeInProvider(providerName, name, url, timeout)
return delayProxyByName(name, url, timeout)
}
async checkDelay(
name: string,
group: string,
timeout: number,
providerName?: string,
): Promise<DelayUpdate> {
debugLog(
`[DelayManager] 开始测试延迟,代理: ${name}, 组: ${group}, 超时: ${timeout}ms`,
@@ -223,7 +240,7 @@ class DelayManager {
// 使用Promise.race来实现超时控制
const result = await Promise.race([
delayProxyByName(name, url, timeout),
this.unifiedDelayCheck(name, url, timeout, providerName),
timeoutPromise,
])
@@ -250,25 +267,29 @@ class DelayManager {
}
async checkListDelay(
nameList: string[],
proxies: IProxyItem[],
group: string,
timeout: number,
concurrency = 36,
) {
debugLog(
`[DelayManager] 批量测试延迟开始,组: ${group}, 数量: ${nameList.length}, 并发数: ${concurrency}`,
`[DelayManager] 批量测试延迟开始,组: ${group}, 数量: ${proxies.length}, 并发数: ${concurrency}`,
)
const names = nameList.filter(Boolean)
const names = proxies.map((p) => p.name)
// 设置正在延迟测试中
names.forEach((name) => this.setDelay(name, group, -2))
names.forEach((name) => {
this.setDelay(name, group, -2)
})
let index = 0
const startTime = Date.now()
const listener = this.groupListenerMap.get(group)
const help = async (): Promise<void> => {
const currName = names[index++]
if (!currName) return
const currProxy = proxies[index++]
if (!currProxy) return
const currName = currProxy.name
const currProviderName = currProxy.provider
try {
// 确保API调用前状态为测试中
@@ -282,7 +303,7 @@ class DelayManager {
)
}
await this.checkDelay(currName, group, timeout)
await this.checkDelay(currName, group, timeout, currProviderName)
if (listener) {
this.queueGroupNotification(group)
}