perf(delay): cache latency updates and smooth proxy list refresh
- track delay as structured updates with TTL-backed cache - batch listener notifications to avoid render storms during checks - surface cached latency in proxy items for quicker, steadier UI feedback
This commit is contained in:
@@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
|
|
||||||
import { BaseLoading } from "@/components/base";
|
import { BaseLoading } from "@/components/base";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import delayManager from "@/services/delay";
|
import delayManager, { DelayUpdate } from "@/services/delay";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
group: IProxyGroupItem;
|
group: IProxyGroupItem;
|
||||||
@@ -24,15 +24,17 @@ export const ProxyItemMini = (props: Props) => {
|
|||||||
|
|
||||||
const presetList = ["DIRECT", "REJECT", "REJECT-DROP", "PASS", "COMPATIBLE"];
|
const presetList = ["DIRECT", "REJECT", "REJECT-DROP", "PASS", "COMPATIBLE"];
|
||||||
const isPreset = presetList.includes(proxy.name);
|
const isPreset = presetList.includes(proxy.name);
|
||||||
// -1/<=0 为 不显示
|
// -1/<=0 为不显示,-2 为 loading
|
||||||
// -2 为 loading
|
const [delayState, setDelayState] = useReducer(
|
||||||
const [delay, setDelay] = useReducer((_: number, value: number) => value, -1);
|
(_: DelayUpdate, next: DelayUpdate) => next,
|
||||||
|
{ delay: -1, updatedAt: 0 },
|
||||||
|
);
|
||||||
const { verge } = useVerge();
|
const { verge } = useVerge();
|
||||||
const timeout = verge?.default_latency_timeout || 10000;
|
const timeout = verge?.default_latency_timeout || 10000;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isPreset) return;
|
if (isPreset) return;
|
||||||
delayManager.setListener(proxy.name, group.name, setDelay);
|
delayManager.setListener(proxy.name, group.name, setDelayState);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
delayManager.removeListener(proxy.name, group.name);
|
delayManager.removeListener(proxy.name, group.name);
|
||||||
@@ -41,7 +43,32 @@ export const ProxyItemMini = (props: Props) => {
|
|||||||
|
|
||||||
const updateDelay = useCallback(() => {
|
const updateDelay = useCallback(() => {
|
||||||
if (!proxy) return;
|
if (!proxy) return;
|
||||||
setDelay(delayManager.getDelayFix(proxy, group.name));
|
const cachedUpdate = delayManager.getDelayUpdate(proxy.name, group.name);
|
||||||
|
if (cachedUpdate) {
|
||||||
|
setDelayState({ ...cachedUpdate });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fallbackDelay = delayManager.getDelayFix(proxy, group.name);
|
||||||
|
if (fallbackDelay === -1) {
|
||||||
|
setDelayState({ delay: -1, updatedAt: 0 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let updatedAt = 0;
|
||||||
|
const history = proxy.history;
|
||||||
|
if (history && history.length > 0) {
|
||||||
|
const lastRecord = history[history.length - 1];
|
||||||
|
const parsed = Date.parse(lastRecord.time);
|
||||||
|
if (!Number.isNaN(parsed)) {
|
||||||
|
updatedAt = parsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setDelayState({
|
||||||
|
delay: fallbackDelay,
|
||||||
|
updatedAt,
|
||||||
|
});
|
||||||
}, [proxy, group.name]);
|
}, [proxy, group.name]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -49,10 +76,14 @@ export const ProxyItemMini = (props: Props) => {
|
|||||||
}, [updateDelay]);
|
}, [updateDelay]);
|
||||||
|
|
||||||
const onDelay = useLockFn(async () => {
|
const onDelay = useLockFn(async () => {
|
||||||
setDelay(-2);
|
setDelayState({ delay: -2, updatedAt: Date.now() });
|
||||||
setDelay(await delayManager.checkDelay(proxy.name, group.name, timeout));
|
setDelayState(
|
||||||
|
await delayManager.checkDelay(proxy.name, group.name, timeout),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const delayValue = delayState.delay;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
dense
|
dense
|
||||||
@@ -69,7 +100,7 @@ export const ProxyItemMini = (props: Props) => {
|
|||||||
},
|
},
|
||||||
({ palette: { mode, primary } }) => {
|
({ palette: { mode, primary } }) => {
|
||||||
const bgcolor = mode === "light" ? "#ffffff" : "#24252f";
|
const bgcolor = mode === "light" ? "#ffffff" : "#24252f";
|
||||||
const showDelay = delay > 0;
|
const showDelay = delayValue > 0;
|
||||||
const selectColor = mode === "light" ? primary.main : primary.light;
|
const selectColor = mode === "light" ? primary.main : primary.light;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -181,13 +212,13 @@ export const ProxyItemMini = (props: Props) => {
|
|||||||
<Box
|
<Box
|
||||||
sx={{ ml: 0.5, color: "primary.main", display: isPreset ? "none" : "" }}
|
sx={{ ml: 0.5, color: "primary.main", display: isPreset ? "none" : "" }}
|
||||||
>
|
>
|
||||||
{delay === -2 && (
|
{delayValue === -2 && (
|
||||||
<Widget>
|
<Widget>
|
||||||
<BaseLoading />
|
<BaseLoading />
|
||||||
</Widget>
|
</Widget>
|
||||||
)}
|
)}
|
||||||
{!proxy.provider && delay !== -2 && (
|
{!proxy.provider && delayValue !== -2 && (
|
||||||
// provider的节点不支持检测
|
// provider 的节点不支持检测
|
||||||
<Widget
|
<Widget
|
||||||
className="the-check"
|
className="the-check"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@@ -196,7 +227,7 @@ export const ProxyItemMini = (props: Props) => {
|
|||||||
onDelay();
|
onDelay();
|
||||||
}}
|
}}
|
||||||
sx={({ palette }) => ({
|
sx={({ palette }) => ({
|
||||||
display: "none", // hover才显示
|
display: "none", // hover 时显示
|
||||||
":hover": { bgcolor: alpha(palette.primary.main, 0.15) },
|
":hover": { bgcolor: alpha(palette.primary.main, 0.15) },
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
@@ -204,7 +235,7 @@ export const ProxyItemMini = (props: Props) => {
|
|||||||
</Widget>
|
</Widget>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{delay >= 0 && (
|
{delayValue >= 0 && (
|
||||||
// 显示延迟
|
// 显示延迟
|
||||||
<Widget
|
<Widget
|
||||||
className="the-delay"
|
className="the-delay"
|
||||||
@@ -214,26 +245,29 @@ export const ProxyItemMini = (props: Props) => {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onDelay();
|
onDelay();
|
||||||
}}
|
}}
|
||||||
color={delayManager.formatDelayColor(delay, timeout)}
|
color={delayManager.formatDelayColor(delayValue, timeout)}
|
||||||
sx={({ palette }) =>
|
sx={({ palette }) =>
|
||||||
!proxy.provider
|
!proxy.provider
|
||||||
? { ":hover": { bgcolor: alpha(palette.primary.main, 0.15) } }
|
? { ":hover": { bgcolor: alpha(palette.primary.main, 0.15) } }
|
||||||
: {}
|
: {}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{delayManager.formatDelay(delay, timeout)}
|
{delayManager.formatDelay(delayValue, timeout)}
|
||||||
</Widget>
|
</Widget>
|
||||||
)}
|
)}
|
||||||
{proxy.type !== "Direct" && delay !== -2 && delay < 0 && selected && (
|
{proxy.type !== "Direct" &&
|
||||||
// 展示已选择的icon
|
delayValue !== -2 &&
|
||||||
<CheckCircleOutlineRounded
|
delayValue < 0 &&
|
||||||
className="the-icon"
|
selected && (
|
||||||
sx={{ fontSize: 16, mr: 0.5, display: "block" }}
|
// 展示已选择的 icon
|
||||||
/>
|
<CheckCircleOutlineRounded
|
||||||
)}
|
className="the-icon"
|
||||||
|
sx={{ fontSize: 16, mr: 0.5, display: "block" }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
{group.fixed && group.fixed === proxy.name && (
|
{group.fixed && group.fixed === proxy.name && (
|
||||||
// 展示fixed状态
|
// 展示 fixed 状态
|
||||||
<span
|
<span
|
||||||
className={proxy.name === group.now ? "the-pin" : "the-unpin"}
|
className={proxy.name === group.now ? "the-pin" : "the-unpin"}
|
||||||
title={
|
title={
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { useCallback, useEffect, useReducer } from "react";
|
|||||||
|
|
||||||
import { BaseLoading } from "@/components/base";
|
import { BaseLoading } from "@/components/base";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import delayManager from "@/services/delay";
|
import delayManager, { DelayUpdate } from "@/services/delay";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
group: IProxyGroupItem;
|
group: IProxyGroupItem;
|
||||||
@@ -49,14 +49,17 @@ export const ProxyItem = (props: Props) => {
|
|||||||
|
|
||||||
const presetList = ["DIRECT", "REJECT", "REJECT-DROP", "PASS", "COMPATIBLE"];
|
const presetList = ["DIRECT", "REJECT", "REJECT-DROP", "PASS", "COMPATIBLE"];
|
||||||
const isPreset = presetList.includes(proxy.name);
|
const isPreset = presetList.includes(proxy.name);
|
||||||
// -1/<=0 为 不显示
|
// -1/<=0 为不显示,-2 为 loading
|
||||||
// -2 为 loading
|
const [delayState, setDelayState] = useReducer(
|
||||||
const [delay, setDelay] = useReducer((_: number, value: number) => value, -1);
|
(_: DelayUpdate, next: DelayUpdate) => next,
|
||||||
|
{ delay: -1, updatedAt: 0 },
|
||||||
|
);
|
||||||
const { verge } = useVerge();
|
const { verge } = useVerge();
|
||||||
const timeout = verge?.default_latency_timeout || 10000;
|
const timeout = verge?.default_latency_timeout || 10000;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isPreset) return;
|
if (isPreset) return;
|
||||||
delayManager.setListener(proxy.name, group.name, setDelay);
|
delayManager.setListener(proxy.name, group.name, setDelayState);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
delayManager.removeListener(proxy.name, group.name);
|
delayManager.removeListener(proxy.name, group.name);
|
||||||
@@ -65,7 +68,32 @@ export const ProxyItem = (props: Props) => {
|
|||||||
|
|
||||||
const updateDelay = useCallback(() => {
|
const updateDelay = useCallback(() => {
|
||||||
if (!proxy) return;
|
if (!proxy) return;
|
||||||
setDelay(delayManager.getDelayFix(proxy, group.name));
|
const cachedUpdate = delayManager.getDelayUpdate(proxy.name, group.name);
|
||||||
|
if (cachedUpdate) {
|
||||||
|
setDelayState({ ...cachedUpdate });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fallbackDelay = delayManager.getDelayFix(proxy, group.name);
|
||||||
|
if (fallbackDelay === -1) {
|
||||||
|
setDelayState({ delay: -1, updatedAt: 0 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let updatedAt = 0;
|
||||||
|
const history = proxy.history;
|
||||||
|
if (history && history.length > 0) {
|
||||||
|
const lastRecord = history[history.length - 1];
|
||||||
|
const parsed = Date.parse(lastRecord.time);
|
||||||
|
if (!Number.isNaN(parsed)) {
|
||||||
|
updatedAt = parsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setDelayState({
|
||||||
|
delay: fallbackDelay,
|
||||||
|
updatedAt,
|
||||||
|
});
|
||||||
}, [proxy, group.name]);
|
}, [proxy, group.name]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -73,10 +101,14 @@ export const ProxyItem = (props: Props) => {
|
|||||||
}, [updateDelay]);
|
}, [updateDelay]);
|
||||||
|
|
||||||
const onDelay = useLockFn(async () => {
|
const onDelay = useLockFn(async () => {
|
||||||
setDelay(-2);
|
setDelayState({ delay: -2, updatedAt: Date.now() });
|
||||||
setDelay(await delayManager.checkDelay(proxy.name, group.name, timeout));
|
setDelayState(
|
||||||
|
await delayManager.checkDelay(proxy.name, group.name, timeout),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const delayValue = delayState.delay;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListItem sx={sx}>
|
<ListItem sx={sx}>
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
@@ -88,7 +120,7 @@ export const ProxyItem = (props: Props) => {
|
|||||||
({ palette: { mode, primary } }) => {
|
({ palette: { mode, primary } }) => {
|
||||||
const bgcolor = mode === "light" ? "#ffffff" : "#24252f";
|
const bgcolor = mode === "light" ? "#ffffff" : "#24252f";
|
||||||
const selectColor = mode === "light" ? primary.main : primary.light;
|
const selectColor = mode === "light" ? primary.main : primary.light;
|
||||||
const showDelay = delay > 0;
|
const showDelay = delayValue > 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"&:hover .the-check": { display: !showDelay ? "block" : "none" },
|
"&:hover .the-check": { display: !showDelay ? "block" : "none" },
|
||||||
@@ -145,14 +177,14 @@ export const ProxyItem = (props: Props) => {
|
|||||||
display: isPreset ? "none" : "",
|
display: isPreset ? "none" : "",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{delay === -2 && (
|
{delayValue === -2 && (
|
||||||
<Widget>
|
<Widget>
|
||||||
<BaseLoading />
|
<BaseLoading />
|
||||||
</Widget>
|
</Widget>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!proxy.provider && delay !== -2 && (
|
{!proxy.provider && delayValue !== -2 && (
|
||||||
// provider的节点不支持检测
|
// provider 的节点不支持检测
|
||||||
<Widget
|
<Widget
|
||||||
className="the-check"
|
className="the-check"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@@ -161,7 +193,7 @@ export const ProxyItem = (props: Props) => {
|
|||||||
onDelay();
|
onDelay();
|
||||||
}}
|
}}
|
||||||
sx={({ palette }) => ({
|
sx={({ palette }) => ({
|
||||||
display: "none", // hover才显示
|
display: "none", // hover 时显示
|
||||||
":hover": { bgcolor: alpha(palette.primary.main, 0.15) },
|
":hover": { bgcolor: alpha(palette.primary.main, 0.15) },
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
@@ -169,7 +201,7 @@ export const ProxyItem = (props: Props) => {
|
|||||||
</Widget>
|
</Widget>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{delay > 0 && (
|
{delayValue > 0 && (
|
||||||
// 显示延迟
|
// 显示延迟
|
||||||
<Widget
|
<Widget
|
||||||
className="the-delay"
|
className="the-delay"
|
||||||
@@ -179,19 +211,19 @@ export const ProxyItem = (props: Props) => {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onDelay();
|
onDelay();
|
||||||
}}
|
}}
|
||||||
color={delayManager.formatDelayColor(delay, timeout)}
|
color={delayManager.formatDelayColor(delayValue, timeout)}
|
||||||
sx={({ palette }) =>
|
sx={({ palette }) =>
|
||||||
!proxy.provider
|
!proxy.provider
|
||||||
? { ":hover": { bgcolor: alpha(palette.primary.main, 0.15) } }
|
? { ":hover": { bgcolor: alpha(palette.primary.main, 0.15) } }
|
||||||
: {}
|
: {}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{delayManager.formatDelay(delay, timeout)}
|
{delayManager.formatDelay(delayValue, timeout)}
|
||||||
</Widget>
|
</Widget>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{delay !== -2 && delay <= 0 && selected && (
|
{delayValue !== -2 && delayValue <= 0 && selected && (
|
||||||
// 展示已选择的icon
|
// 展示已选择的 icon
|
||||||
<CheckCircleOutlineRounded
|
<CheckCircleOutlineRounded
|
||||||
className="the-icon"
|
className="the-icon"
|
||||||
sx={{ fontSize: 16 }}
|
sx={{ fontSize: 16 }}
|
||||||
|
|||||||
@@ -2,16 +2,111 @@ import { delayProxyByName, ProxyDelay } from "tauri-plugin-mihomo-api";
|
|||||||
|
|
||||||
const hashKey = (name: string, group: string) => `${group ?? ""}::${name}`;
|
const hashKey = (name: string, group: string) => `${group ?? ""}::${name}`;
|
||||||
|
|
||||||
|
export interface DelayUpdate {
|
||||||
|
delay: number;
|
||||||
|
elapsed?: number;
|
||||||
|
updatedAt: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CACHE_TTL = 30 * 60 * 1000;
|
||||||
|
|
||||||
class DelayManager {
|
class DelayManager {
|
||||||
private cache = new Map<string, [number, number]>();
|
private cache = new Map<string, DelayUpdate>();
|
||||||
private urlMap = new Map<string, string>();
|
private urlMap = new Map<string, string>();
|
||||||
|
|
||||||
// 每个item的监听
|
// 每个节点的监听
|
||||||
private listenerMap = new Map<string, (time: number) => void>();
|
private listenerMap = new Map<string, (update: DelayUpdate) => void>();
|
||||||
|
|
||||||
// 每个分组的监听
|
// 每个分组的监听
|
||||||
private groupListenerMap = new Map<string, () => void>();
|
private groupListenerMap = new Map<string, () => void>();
|
||||||
|
|
||||||
|
private pendingItemUpdates = new Map<string, DelayUpdate[]>();
|
||||||
|
private pendingGroupUpdates = new Set<string>();
|
||||||
|
private itemFlushScheduled = false;
|
||||||
|
private groupFlushScheduled = false;
|
||||||
|
|
||||||
|
private scheduleItemFlush() {
|
||||||
|
if (this.itemFlushScheduled) return;
|
||||||
|
this.itemFlushScheduled = true;
|
||||||
|
|
||||||
|
const run = () => {
|
||||||
|
this.itemFlushScheduled = false;
|
||||||
|
const updates = this.pendingItemUpdates;
|
||||||
|
this.pendingItemUpdates = new Map();
|
||||||
|
|
||||||
|
updates.forEach((queue, key) => {
|
||||||
|
const listener = this.listenerMap.get(key);
|
||||||
|
if (!listener) return;
|
||||||
|
|
||||||
|
queue.forEach((update) => {
|
||||||
|
try {
|
||||||
|
listener(update);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
`[DelayManager] 通知节点延迟监听器失败: ${key}`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
if (typeof window.requestAnimationFrame === "function") {
|
||||||
|
window.requestAnimationFrame(run);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof window.setTimeout === "function") {
|
||||||
|
window.setTimeout(run, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise.resolve().then(run);
|
||||||
|
}
|
||||||
|
|
||||||
|
private scheduleGroupFlush() {
|
||||||
|
if (this.groupFlushScheduled) return;
|
||||||
|
this.groupFlushScheduled = true;
|
||||||
|
|
||||||
|
const run = () => {
|
||||||
|
this.groupFlushScheduled = false;
|
||||||
|
const groups = this.pendingGroupUpdates;
|
||||||
|
this.pendingGroupUpdates = new Set();
|
||||||
|
|
||||||
|
groups.forEach((group) => {
|
||||||
|
const listener = this.groupListenerMap.get(group);
|
||||||
|
if (!listener) return;
|
||||||
|
try {
|
||||||
|
listener();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
`[DelayManager] 通知分组延迟监听器失败: ${group}`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
if (typeof window.requestAnimationFrame === "function") {
|
||||||
|
window.requestAnimationFrame(run);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof window.setTimeout === "function") {
|
||||||
|
window.setTimeout(run, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise.resolve().then(run);
|
||||||
|
}
|
||||||
|
|
||||||
|
private queueGroupNotification(group: string) {
|
||||||
|
this.pendingGroupUpdates.add(group);
|
||||||
|
this.scheduleGroupFlush();
|
||||||
|
}
|
||||||
|
|
||||||
setUrl(group: string, url: string) {
|
setUrl(group: string, url: string) {
|
||||||
console.log(`[DelayManager] 设置测试URL,组: ${group}, URL: ${url}`);
|
console.log(`[DelayManager] 设置测试URL,组: ${group}, URL: ${url}`);
|
||||||
this.urlMap.set(group, url);
|
this.urlMap.set(group, url);
|
||||||
@@ -26,7 +121,11 @@ class DelayManager {
|
|||||||
return url || "https://cp.cloudflare.com/generate_204";
|
return url || "https://cp.cloudflare.com/generate_204";
|
||||||
}
|
}
|
||||||
|
|
||||||
setListener(name: string, group: string, listener: (time: number) => void) {
|
setListener(
|
||||||
|
name: string,
|
||||||
|
group: string,
|
||||||
|
listener: (update: DelayUpdate) => void,
|
||||||
|
) {
|
||||||
const key = hashKey(name, group);
|
const key = hashKey(name, group);
|
||||||
this.listenerMap.set(key, listener);
|
this.listenerMap.set(key, listener);
|
||||||
}
|
}
|
||||||
@@ -44,34 +143,60 @@ class DelayManager {
|
|||||||
this.groupListenerMap.delete(group);
|
this.groupListenerMap.delete(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
setDelay(name: string, group: string, delay: number) {
|
setDelay(
|
||||||
|
name: string,
|
||||||
|
group: string,
|
||||||
|
delay: number,
|
||||||
|
meta?: { elapsed?: number },
|
||||||
|
): DelayUpdate {
|
||||||
const key = hashKey(name, group);
|
const key = hashKey(name, group);
|
||||||
console.log(
|
console.log(
|
||||||
`[DelayManager] 设置延迟,代理: ${name}, 组: ${group}, 延迟: ${delay}`,
|
`[DelayManager] 设置延迟,代理: ${name}, 组: ${group}, 延迟: ${delay}`,
|
||||||
);
|
);
|
||||||
|
const update: DelayUpdate = {
|
||||||
|
delay,
|
||||||
|
elapsed: meta?.elapsed,
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
this.cache.set(key, [delay, Date.now()]);
|
this.cache.set(key, update);
|
||||||
const listener = this.listenerMap.get(key);
|
|
||||||
if (listener) listener(delay);
|
const queue = this.pendingItemUpdates.get(key);
|
||||||
|
if (queue) {
|
||||||
|
queue.push(update);
|
||||||
|
} else {
|
||||||
|
this.pendingItemUpdates.set(key, [update]);
|
||||||
|
}
|
||||||
|
this.scheduleItemFlush();
|
||||||
|
|
||||||
|
return update;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDelayUpdate(name: string, group: string) {
|
||||||
|
const key = hashKey(name, group);
|
||||||
|
const entry = this.cache.get(key);
|
||||||
|
if (!entry) return undefined;
|
||||||
|
|
||||||
|
if (Date.now() - entry.updatedAt > CACHE_TTL) {
|
||||||
|
this.cache.delete(key);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...entry };
|
||||||
}
|
}
|
||||||
|
|
||||||
getDelay(name: string, group: string) {
|
getDelay(name: string, group: string) {
|
||||||
const key = hashKey(name, group);
|
const update = this.getDelayUpdate(name, group);
|
||||||
const val = this.cache.get(key);
|
return update ? update.delay : -1;
|
||||||
if (!val) return -1;
|
|
||||||
|
|
||||||
// 缓存30分钟
|
|
||||||
if (Date.now() - val[1] > 30 * 60 * 1000) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return val[0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 暂时修复provider的节点延迟排序的问题
|
/// 暂时修复provider的节点延迟排序的问题
|
||||||
getDelayFix(proxy: IProxyItem, group: string) {
|
getDelayFix(proxy: IProxyItem, group: string) {
|
||||||
if (!proxy.provider) {
|
if (!proxy.provider) {
|
||||||
const delay = this.getDelay(proxy.name, group);
|
const update = this.getDelayUpdate(proxy.name, group);
|
||||||
if (delay >= 0 || delay === -2) return delay;
|
if (update && (update.delay >= 0 || update.delay === -2)) {
|
||||||
|
return update.delay;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加 history 属性的安全检查
|
// 添加 history 属性的安全检查
|
||||||
@@ -82,7 +207,11 @@ class DelayManager {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkDelay(name: string, group: string, timeout: number) {
|
async checkDelay(
|
||||||
|
name: string,
|
||||||
|
group: string,
|
||||||
|
timeout: number,
|
||||||
|
): Promise<DelayUpdate> {
|
||||||
console.log(
|
console.log(
|
||||||
`[DelayManager] 开始测试延迟,代理: ${name}, 组: ${group}, 超时: ${timeout}ms`,
|
`[DelayManager] 开始测试延迟,代理: ${name}, 组: ${group}, 超时: ${timeout}ms`,
|
||||||
);
|
);
|
||||||
@@ -91,16 +220,16 @@ class DelayManager {
|
|||||||
this.setDelay(name, group, -2);
|
this.setDelay(name, group, -2);
|
||||||
|
|
||||||
let delay = -1;
|
let delay = -1;
|
||||||
|
let elapsed = 0;
|
||||||
|
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const url = this.getUrl(group);
|
const url = this.getUrl(group);
|
||||||
console.log(`[DelayManager] 调用API测试延迟,代理: ${name}, URL: ${url}`);
|
console.log(`[DelayManager] 调用API测试延迟,代理: ${name}, URL: ${url}`);
|
||||||
|
|
||||||
// 记录开始时间,用于计算实际延迟
|
|
||||||
const startTime = Date.now();
|
|
||||||
|
|
||||||
// 设置超时处理, delay = 0 为超时
|
// 设置超时处理, delay = 0 为超时
|
||||||
const timeoutPromise = new Promise<ProxyDelay>((resolve, _) => {
|
const timeoutPromise = new Promise<ProxyDelay>((resolve) => {
|
||||||
setTimeout(() => resolve({ delay: 0 }), timeout);
|
setTimeout(() => resolve({ delay: 0 }), timeout);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -117,6 +246,7 @@ class DelayManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
delay = result.delay;
|
delay = result.delay;
|
||||||
|
elapsed = elapsedTime;
|
||||||
console.log(
|
console.log(
|
||||||
`[DelayManager] 延迟测试完成,代理: ${name}, 结果: ${delay}ms`,
|
`[DelayManager] 延迟测试完成,代理: ${name}, 结果: ${delay}ms`,
|
||||||
);
|
);
|
||||||
@@ -125,10 +255,10 @@ class DelayManager {
|
|||||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||||
console.error(`[DelayManager] 延迟测试出错,代理: ${name}`, error);
|
console.error(`[DelayManager] 延迟测试出错,代理: ${name}`, error);
|
||||||
delay = 1e6; // error
|
delay = 1e6; // error
|
||||||
|
elapsed = Date.now() - startTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setDelay(name, group, delay);
|
return this.setDelay(name, group, delay, { elapsed });
|
||||||
return delay;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkListDelay(
|
async checkListDelay(
|
||||||
@@ -165,7 +295,9 @@ class DelayManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.checkDelay(currName, group, timeout);
|
await this.checkDelay(currName, group, timeout);
|
||||||
if (listener) listener();
|
if (listener) {
|
||||||
|
this.queueGroupNotification(group);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
`[DelayManager] 批量测试单个代理出错,代理: ${currName}`,
|
`[DelayManager] 批量测试单个代理出错,代理: ${currName}`,
|
||||||
|
|||||||
Reference in New Issue
Block a user