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 & {