diff --git a/src/components/layout/layout-traffic.tsx b/src/components/layout/layout-traffic.tsx index 96082103..fce7967e 100644 --- a/src/components/layout/layout-traffic.tsx +++ b/src/components/layout/layout-traffic.tsx @@ -29,21 +29,23 @@ export const LayoutTraffic = () => { // setup log ws during layout useLogSetup(); - const { connect, disconnect } = useWebsocket((event) => { - const data = JSON.parse(event.data) as ITrafficItem; - trafficRef.current?.appendData(data); - setTraffic(data); - }); + const trafficWs = useWebsocket( + (event) => { + const data = JSON.parse(event.data) as ITrafficItem; + trafficRef.current?.appendData(data); + setTraffic(data); + }, + { onError: () => setTraffic({ up: 0, down: 0 }), errorCount: 10 } + ); useEffect(() => { if (!clashInfo || !pageVisible) return; const { server = "", secret = "" } = clashInfo; - connect(`ws://${server}/traffic?token=${encodeURIComponent(secret)}`); - - return () => { - disconnect(); - }; + trafficWs.connect( + `ws://${server}/traffic?token=${encodeURIComponent(secret)}` + ); + return () => trafficWs.disconnect(); }, [clashInfo, pageVisible]); /* --------- meta memory information --------- */ @@ -54,7 +56,7 @@ export const LayoutTraffic = () => { (event) => { setMemory(JSON.parse(event.data)); }, - { onError: () => setMemory({ inuse: 0 }) } + { onError: () => setMemory({ inuse: 0 }), errorCount: 10 } ); useEffect(() => { diff --git a/src/components/proxy/proxy-groups.tsx b/src/components/proxy/proxy-groups.tsx index e53e01df..ab747515 100644 --- a/src/components/proxy/proxy-groups.tsx +++ b/src/components/proxy/proxy-groups.tsx @@ -6,6 +6,7 @@ import { providerHealthCheck, updateProxy, deleteConnection, + getGroupProxyDelays, } from "@/services/api"; import { Box } from "@mui/material"; import { useProfiles } from "@/hooks/use-profiles"; @@ -33,7 +34,7 @@ export const ProxyGroups = (props: Props) => { // 切换分组的节点代理 const handleChangeProxy = useLockFn( async (group: IProxyGroupItem, proxy: IProxyItem) => { - if (group.type !== "Selector" && group.type !== "Fallback") return; + if (!["Selector", "URLTest", "Fallback"].includes(group.type)) return; const { name, now } = group; await updateProxy(name, proxy.name); @@ -85,7 +86,11 @@ export const ProxyGroups = (props: Props) => { } const names = proxies.filter((p) => !p!.provider).map((p) => p!.name); - await delayManager.checkListDelay(names, groupName, timeout); + + await Promise.race([ + delayManager.checkListDelay(names, groupName, timeout), + getGroupProxyDelays(groupName, delayManager.getUrl(groupName), timeout), // 查询group delays 将清除fixed(不关注调用结果) + ]); onProxies(); }); diff --git a/src/components/proxy/proxy-item-mini.tsx b/src/components/proxy/proxy-item-mini.tsx index 6e099d12..e510d832 100644 --- a/src/components/proxy/proxy-item-mini.tsx +++ b/src/components/proxy/proxy-item-mini.tsx @@ -7,7 +7,7 @@ import delayManager from "@/services/delay"; import { useVerge } from "@/hooks/use-verge"; interface Props { - groupName: string; + group: IProxyGroupItem; proxy: IProxyItem; selected: boolean; showType?: boolean; @@ -16,7 +16,7 @@ interface Props { // 多列布局 export const ProxyItemMini = (props: Props) => { - const { groupName, proxy, selected, showType = true, onClick } = props; + const { group, proxy, selected, showType = true, onClick } = props; // -1/<=0 为 不显示 // -2 为 loading @@ -25,21 +25,21 @@ export const ProxyItemMini = (props: Props) => { const timeout = verge?.default_latency_timeout || 10000; useEffect(() => { - delayManager.setListener(proxy.name, groupName, setDelay); + delayManager.setListener(proxy.name, group.name, setDelay); return () => { - delayManager.removeListener(proxy.name, groupName); + delayManager.removeListener(proxy.name, group.name); }; - }, [proxy.name, groupName]); + }, [proxy.name, group.name]); useEffect(() => { if (!proxy) return; - setDelay(delayManager.getDelayFix(proxy, groupName)); + setDelay(delayManager.getDelayFix(proxy, group.name)); }, [proxy]); const onDelay = useLockFn(async () => { setDelay(-2); - setDelay(await delayManager.checkDelay(proxy.name, groupName, timeout)); + setDelay(await delayManager.checkDelay(proxy.name, group.name, timeout)); }); return ( @@ -65,6 +65,12 @@ export const ProxyItemMini = (props: Props) => { "&:hover .the-check": { display: !showDelay ? "block" : "none" }, "&:hover .the-delay": { display: showDelay ? "block" : "none" }, "&:hover .the-icon": { display: "none" }, + "& .the-pin, & .the-unpin": { + position: "absolute", + top: "-8px", + right: "-8px", + }, + "& .the-unpin": { filter: "grayscale(1)" }, "&.Mui-selected": { width: `calc(100% + 3px)`, marginLeft: `-3px`, @@ -147,14 +153,12 @@ export const ProxyItemMini = (props: Props) => { )} - {delay === -2 && ( )} - {!proxy.provider && delay !== -2 && ( // provider的节点不支持检测 { {delayManager.formatDelay(delay, timeout)} )} - {delay !== -2 && delay <= 0 && selected && ( // 展示已选择的icon { /> )} + + {group.fixed && group.fixed === proxy.name && ( + // 展示fixed状态 + + 📌 + + )} ); }; diff --git a/src/components/proxy/proxy-item.tsx b/src/components/proxy/proxy-item.tsx index ee3d5006..b1879b5a 100644 --- a/src/components/proxy/proxy-item.tsx +++ b/src/components/proxy/proxy-item.tsx @@ -17,7 +17,7 @@ import delayManager from "@/services/delay"; import { useVerge } from "@/hooks/use-verge"; interface Props { - groupName: string; + group: IProxyGroupItem; proxy: IProxyItem; selected: boolean; showType?: boolean; @@ -44,7 +44,7 @@ const TypeBox = styled(Box)(({ theme }) => ({ })); export const ProxyItem = (props: Props) => { - const { groupName, proxy, selected, showType = true, sx, onClick } = props; + const { group, proxy, selected, showType = true, sx, onClick } = props; // -1/<=0 为 不显示 // -2 为 loading @@ -52,21 +52,21 @@ export const ProxyItem = (props: Props) => { const { verge } = useVerge(); const timeout = verge?.default_latency_timeout || 10000; useEffect(() => { - delayManager.setListener(proxy.name, groupName, setDelay); + delayManager.setListener(proxy.name, group.name, setDelay); return () => { - delayManager.removeListener(proxy.name, groupName); + delayManager.removeListener(proxy.name, group.name); }; - }, [proxy.name, groupName]); + }, [proxy.name, group.name]); useEffect(() => { if (!proxy) return; - setDelay(delayManager.getDelayFix(proxy, groupName)); + setDelay(delayManager.getDelayFix(proxy, group.name)); }, [proxy]); const onDelay = useLockFn(async () => { setDelay(-2); - setDelay(await delayManager.checkDelay(proxy.name, groupName, timeout)); + setDelay(await delayManager.checkDelay(proxy.name, group.name, timeout)); }); return ( diff --git a/src/components/proxy/proxy-render.tsx b/src/components/proxy/proxy-render.tsx index eefb41e8..b5423a25 100644 --- a/src/components/proxy/proxy-render.tsx +++ b/src/components/proxy/proxy-render.tsx @@ -142,7 +142,7 @@ export const ProxyRender = (props: RenderProps) => { if (type === 2 && !group.hidden) { return ( { {proxyCol?.map((proxy) => ( ) => void; export interface WsOptions { errorCount?: number; // default is 5 retryInterval?: number; // default is 2500 - onError?: () => void; + onError?: (event: Event) => void; + onClose?: (event: CloseEvent) => void; } export const useWebsocket = (onMessage: WsMsgFn, options?: WsOptions) => { @@ -33,17 +34,23 @@ export const useWebsocket = (onMessage: WsMsgFn, options?: WsOptions) => { const ws = new WebSocket(url); wsRef.current = ws; - ws.addEventListener("message", onMessage); - ws.addEventListener("error", () => { + ws.addEventListener("message", (event) => { + errorCount = 0; // reset counter + onMessage(event); + }); + ws.addEventListener("error", (event) => { errorCount -= 1; if (errorCount >= 0) { timerRef.current = setTimeout(connectHelper, 2500); } else { disconnect(); - options?.onError?.(); + options?.onError?.(event); } }); + ws.addEventListener("close", (event) => { + options?.onClose?.(event); + }); }; connectHelper(); diff --git a/src/services/api.ts b/src/services/api.ts index b8c42a92..bded6e32 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -75,9 +75,13 @@ export const getRules = async () => { }; /// Get Proxy delay -export const getProxyDelay = async (name: string, url?: string) => { +export const getProxyDelay = async ( + name: string, + url?: string, + timeout?: number +) => { const params = { - timeout: 10000, + timeout: timeout || 10000, url: url || "http://1.1.1.1", }; const instance = await getAxios(); @@ -237,3 +241,21 @@ export const closeAllConnections = async () => { const instance = await getAxios(); await instance.delete(`/connections`); }; + +// Get Group Proxy Delays +export const getGroupProxyDelays = async ( + groupName: string, + url?: string, + timeout?: number +) => { + const params = { + timeout: timeout || 10000, + url: url || "http://1.1.1.1", + }; + const instance = await getAxios(); + const result = await instance.get( + `/group/${encodeURIComponent(groupName)}/delay`, + { params } + ); + return result as any as Record; +}; diff --git a/src/services/types.d.ts b/src/services/types.d.ts index 3de493f1..0d9537ec 100644 --- a/src/services/types.d.ts +++ b/src/services/types.d.ts @@ -64,6 +64,7 @@ interface IProxyItem { hidden?: boolean; icon?: string; provider?: string; // 记录是否来自provider + fixed?: string; // 记录固定(优先)的节点 } type IProxyGroupItem = Omit & {