- All delay test logic, Rust MihomoManager, API, cmds, delay.ts, settings, and group editor now use the Cloudflare 204 test URL - Fixed outdated URLs in docs and comments - Fix Github Actions workflow: unexpected neeeds run
227 lines
6.6 KiB
TypeScript
227 lines
6.6 KiB
TypeScript
import { cmdGetProxyDelay } from "./cmds";
|
||
|
||
const hashKey = (name: string, group: string) => `${group ?? ""}::${name}`;
|
||
|
||
class DelayManager {
|
||
private cache = new Map<string, [number, number]>();
|
||
private urlMap = new Map<string, string>();
|
||
|
||
// 每个item的监听
|
||
private listenerMap = new Map<string, (time: number) => void>();
|
||
|
||
// 每个分组的监听
|
||
private groupListenerMap = new Map<string, () => void>();
|
||
|
||
setUrl(group: string, url: string) {
|
||
console.log(`[DelayManager] 设置测试URL,组: ${group}, URL: ${url}`);
|
||
this.urlMap.set(group, url);
|
||
}
|
||
|
||
getUrl(group: string) {
|
||
const url = this.urlMap.get(group);
|
||
console.log(
|
||
`[DelayManager] 获取测试URL,组: ${group}, URL: ${url || "未设置"}`,
|
||
);
|
||
// 如果未设置URL,返回默认URL
|
||
return url || "https://cp.cloudflare.com/generate_204";
|
||
}
|
||
|
||
setListener(name: string, group: string, listener: (time: number) => void) {
|
||
const key = hashKey(name, group);
|
||
this.listenerMap.set(key, listener);
|
||
}
|
||
|
||
removeListener(name: string, group: string) {
|
||
const key = hashKey(name, group);
|
||
this.listenerMap.delete(key);
|
||
}
|
||
|
||
setGroupListener(group: string, listener: () => void) {
|
||
this.groupListenerMap.set(group, listener);
|
||
}
|
||
|
||
removeGroupListener(group: string) {
|
||
this.groupListenerMap.delete(group);
|
||
}
|
||
|
||
setDelay(name: string, group: string, delay: number) {
|
||
const key = hashKey(name, group);
|
||
console.log(
|
||
`[DelayManager] 设置延迟,代理: ${name}, 组: ${group}, 延迟: ${delay}`,
|
||
);
|
||
|
||
this.cache.set(key, [delay, Date.now()]);
|
||
const listener = this.listenerMap.get(key);
|
||
if (listener) listener(delay);
|
||
}
|
||
|
||
getDelay(name: string, group: string) {
|
||
const key = hashKey(name, group);
|
||
const val = this.cache.get(key);
|
||
if (!val) return -1;
|
||
|
||
// 缓存30分钟
|
||
if (Date.now() - val[1] > 30 * 60 * 1000) {
|
||
return -1;
|
||
}
|
||
return val[0];
|
||
}
|
||
|
||
/// 暂时修复provider的节点延迟排序的问题
|
||
getDelayFix(proxy: IProxyItem, group: string) {
|
||
if (!proxy.provider) {
|
||
const delay = this.getDelay(proxy.name, group);
|
||
if (delay >= 0 || delay === -2) return delay;
|
||
}
|
||
|
||
if (proxy.history.length > 0) {
|
||
// 0ms以error显示
|
||
return proxy.history[proxy.history.length - 1].delay || 1e6;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
async checkDelay(name: string, group: string, timeout: number) {
|
||
console.log(
|
||
`[DelayManager] 开始测试延迟,代理: ${name}, 组: ${group}, 超时: ${timeout}ms`,
|
||
);
|
||
|
||
// 先将状态设置为测试中
|
||
this.setDelay(name, group, -2);
|
||
|
||
let delay = -1;
|
||
|
||
try {
|
||
const url = this.getUrl(group);
|
||
console.log(`[DelayManager] 调用API测试延迟,代理: ${name}, URL: ${url}`);
|
||
|
||
// 记录开始时间,用于计算实际延迟
|
||
const startTime = Date.now();
|
||
|
||
// 设置超时处理
|
||
const timeoutPromise = new Promise<{ delay: number }>((_, reject) => {
|
||
setTimeout(() => reject(new Error("Timeout")), timeout);
|
||
});
|
||
|
||
// 使用Promise.race来实现超时控制
|
||
const result = await Promise.race([
|
||
cmdGetProxyDelay(name, timeout, url),
|
||
timeoutPromise,
|
||
]);
|
||
|
||
// 确保至少显示500ms的加载动画
|
||
const elapsedTime = Date.now() - startTime;
|
||
if (elapsedTime < 500) {
|
||
await new Promise((resolve) => setTimeout(resolve, 500 - elapsedTime));
|
||
}
|
||
|
||
// 检查延迟结果是否为undefined
|
||
if (result && typeof result.delay === "number") {
|
||
delay = result.delay;
|
||
console.log(
|
||
`[DelayManager] 延迟测试完成,代理: ${name}, 结果: ${delay}ms`,
|
||
);
|
||
} else {
|
||
console.error(
|
||
`[DelayManager] 延迟测试返回无效结果,代理: ${name}, 结果:`,
|
||
result,
|
||
);
|
||
delay = 1e6; // 错误情况
|
||
}
|
||
} catch (error) {
|
||
// 确保至少显示500ms的加载动画
|
||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||
|
||
console.error(`[DelayManager] 延迟测试出错,代理: ${name}`, error);
|
||
if (error instanceof Error && error.message === "Timeout") {
|
||
console.log(`[DelayManager] 延迟测试超时,代理: ${name}`);
|
||
}
|
||
delay = 1e6; // error
|
||
}
|
||
|
||
this.setDelay(name, group, delay);
|
||
return delay;
|
||
}
|
||
|
||
async checkListDelay(
|
||
nameList: string[],
|
||
group: string,
|
||
timeout: number,
|
||
concurrency = 36,
|
||
) {
|
||
console.log(
|
||
`[DelayManager] 批量测试延迟开始,组: ${group}, 数量: ${nameList.length}, 并发数: ${concurrency}`,
|
||
);
|
||
const names = nameList.filter(Boolean);
|
||
// 设置正在延迟测试中
|
||
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;
|
||
|
||
try {
|
||
// 确保API调用前状态为测试中
|
||
this.setDelay(currName, group, -2);
|
||
|
||
// 添加一些随机延迟,避免所有请求同时发出和返回
|
||
if (index > 1) {
|
||
// 第一个不延迟,保持响应性
|
||
await new Promise((resolve) =>
|
||
setTimeout(resolve, Math.random() * 200),
|
||
);
|
||
}
|
||
|
||
await this.checkDelay(currName, group, timeout);
|
||
if (listener) listener();
|
||
} catch (error) {
|
||
console.error(
|
||
`[DelayManager] 批量测试单个代理出错,代理: ${currName}`,
|
||
error,
|
||
);
|
||
// 设置为错误状态
|
||
this.setDelay(currName, group, 1e6);
|
||
}
|
||
|
||
return help();
|
||
};
|
||
|
||
// 限制并发数,避免发送太多请求
|
||
const actualConcurrency = Math.min(concurrency, names.length, 10);
|
||
console.log(`[DelayManager] 实际并发数: ${actualConcurrency}`);
|
||
|
||
const promiseList: Promise<void>[] = [];
|
||
for (let i = 0; i < actualConcurrency; i++) {
|
||
promiseList.push(help());
|
||
}
|
||
|
||
await Promise.all(promiseList);
|
||
const totalTime = Date.now() - startTime;
|
||
console.log(
|
||
`[DelayManager] 批量测试延迟完成,组: ${group}, 总耗时: ${totalTime}ms`,
|
||
);
|
||
}
|
||
|
||
formatDelay(delay: number, timeout = 10000) {
|
||
if (delay === -1) return "-";
|
||
if (delay === -2) return "testing";
|
||
if (delay >= timeout) return "timeout";
|
||
return `${delay}`;
|
||
}
|
||
|
||
formatDelayColor(delay: number, timeout = 10000) {
|
||
if (delay < 0) return "";
|
||
if (delay >= timeout) return "error.main";
|
||
if (delay >= 10000) return "error.main";
|
||
if (delay >= 400) return "warning.main";
|
||
if (delay >= 250) return "primary.main";
|
||
return "success.main";
|
||
}
|
||
}
|
||
|
||
export default new DelayManager();
|