refactor: invock mihomo api by use tauri-plugin-mihomo (#4926)

* feat: add tauri-plugin-mihomo

* refactor: invock mihomo api by use tauri-plugin-mihomo

* chore: todo

* chore: update

* chore: update

* chore: update

* chore: update

* fix: incorrect delay status and update pretty config

* chore: update

* chore: remove cache

* chore: update

* chore: update

* fix: app freezed when change group proxy

* chore: update

* chore: update

* chore: add rustfmt.toml to tauri-plugin-mihomo

* chore: happy clippy

* refactor: connect mihomo websocket

* chore: update

* chore: update

* fix: parse bigint to number

* chore: update

* Revert "fix: parse bigint to number"

This reverts commit 74c006522e23aa52cf8979a8fb47d2b1ae0bb043.

* chore: use number instead of bigint

* chore: cleanup

* fix: rule data not refresh when switch profile

* chore: update

* chore: cleanup

* chore: update

* fix: traffic graph data display

* feat: add ipc connection pool

* chore: update

* chore: clippy

* fix: incorrect delay status

* fix: typo

* fix: empty proxies tray menu

* chore: clippy

* chore: import tauri-plugin-mihomo by using git repo

* chore: cleanup

* fix: mihomo api

* fix: incorrect delay status

* chore: update tauri-plugin-mihomo dep

chore: update
This commit is contained in:
oomeow
2025-10-08 12:32:40 +08:00
committed by GitHub
parent 72aa56007c
commit 7fc238c27b
85 changed files with 1780 additions and 3344 deletions

View File

@@ -1,22 +1,28 @@
import { createContext, use } from "react";
import {
BaseConfig,
ProxyProvider,
Rule,
RuleProvider,
} from "tauri-plugin-mihomo-api";
export interface AppDataContextType {
proxies: any;
clashConfig: any;
rules: any[];
clashConfig: BaseConfig;
rules: Rule[];
sysproxy: any;
runningMode?: string;
uptime: number;
proxyProviders: any;
ruleProviders: any;
connections: {
data: ConnectionWithSpeed[];
count: number;
uploadTotal: number;
downloadTotal: number;
};
traffic: { up: number; down: number };
memory: { inuse: number };
proxyProviders: Record<string, ProxyProvider>;
ruleProviders: Record<string, RuleProvider>;
// connections: {
// data: ConnectionWithSpeed[];
// count: number;
// uploadTotal: number;
// downloadTotal: number;
// };
// traffic: { up: number; down: number };
// memory: { inuse: number };
systemProxyAddress: string;
refreshProxy: () => Promise<any>;

View File

@@ -1,30 +1,24 @@
import { listen } from "@tauri-apps/api/event";
import React, { useCallback, useEffect, useMemo, useRef } from "react";
import React, { useCallback, useEffect, useMemo } from "react";
import useSWR from "swr";
import { useClashInfo } from "@/hooks/use-clash";
import { useVerge } from "@/hooks/use-verge";
import { useVisibility } from "@/hooks/use-visibility";
import {
forceRefreshProxies,
getAppUptime,
getClashConfig,
getConnections,
getMemoryData,
getProxies,
getProxyProviders,
getBaseConfig,
getRuleProviders,
getRules,
} from "tauri-plugin-mihomo-api";
// import { useClashInfo } from "@/hooks/use-clash";
import { useVerge } from "@/hooks/use-verge";
// import { useVisibility } from "@/hooks/use-visibility";
import {
calcuProxies,
calcuProxyProviders,
getAppUptime,
getRunningMode,
getSystemProxy,
getTrafficData,
} from "@/services/cmds";
import {
AppDataContext,
type ConnectionSpeedData,
type ConnectionWithSpeed,
} from "./app-data-context";
import { AppDataContext, AppDataContextType } from "./app-data-context";
// 全局数据提供者组件
export const AppDataProvider = ({
@@ -32,60 +26,60 @@ export const AppDataProvider = ({
}: {
children: React.ReactNode;
}) => {
const pageVisible = useVisibility();
const { clashInfo } = useClashInfo();
// const pageVisible = useVisibility();
// const { clashInfo } = useClashInfo();
const { verge } = useVerge();
// 存储上一次连接数据用于速度计算
const previousConnectionsRef = useRef<Map<string, ConnectionSpeedData>>(
new Map(),
);
// const previousConnectionsRef = useRef<Map<string, ConnectionSpeedData>>(
// new Map(),
// );
// 计算连接速度的函数
const calculateConnectionSpeeds = (
currentConnections: IConnectionsItem[],
): ConnectionWithSpeed[] => {
const now = Date.now();
const currentMap = new Map<string, ConnectionSpeedData>();
// const calculateConnectionSpeeds = (
// currentConnections: IConnectionsItem[],
// ): ConnectionWithSpeed[] => {
// const now = Date.now();
// const currentMap = new Map<string, ConnectionSpeedData>();
return currentConnections.map((conn) => {
const connWithSpeed: ConnectionWithSpeed = {
...conn,
curUpload: 0,
curDownload: 0,
};
// return currentConnections.map((conn) => {
// const connWithSpeed: ConnectionWithSpeed = {
// ...conn,
// curUpload: 0,
// curDownload: 0,
// };
const currentData: ConnectionSpeedData = {
id: conn.id,
upload: conn.upload,
download: conn.download,
timestamp: now,
};
// const currentData: ConnectionSpeedData = {
// id: conn.id,
// upload: conn.upload,
// download: conn.download,
// timestamp: now,
// };
currentMap.set(conn.id, currentData);
// currentMap.set(conn.id, currentData);
const previousData = previousConnectionsRef.current.get(conn.id);
if (previousData) {
const timeDiff = (now - previousData.timestamp) / 1000; // 转换为秒
// const previousData = previousConnectionsRef.current.get(conn.id);
// if (previousData) {
// const timeDiff = (now - previousData.timestamp) / 1000; // 转换为秒
if (timeDiff > 0) {
const uploadDiff = conn.upload - previousData.upload;
const downloadDiff = conn.download - previousData.download;
// if (timeDiff > 0) {
// const uploadDiff = conn.upload - previousData.upload;
// const downloadDiff = conn.download - previousData.download;
// 计算每秒速度 (字节/秒)
connWithSpeed.curUpload = Math.max(0, uploadDiff / timeDiff);
connWithSpeed.curDownload = Math.max(0, downloadDiff / timeDiff);
}
}
// // 计算每秒速度 (字节/秒)
// connWithSpeed.curUpload = Math.max(0, uploadDiff / timeDiff);
// connWithSpeed.curDownload = Math.max(0, downloadDiff / timeDiff);
// }
// }
return connWithSpeed;
});
};
// return connWithSpeed;
// });
// };
// 基础数据 - 中频率更新 (5秒)
const { data: proxiesData, mutate: refreshProxy } = useSWR(
"getProxies",
getProxies,
calcuProxies,
{
refreshInterval: 5000,
revalidateOnFocus: true,
@@ -177,19 +171,27 @@ export const AppDataProvider = ({
lastProfileId = newProfileId;
lastUpdateTime = now;
scheduleTimeout(() => {
void forceRefreshProxies()
.catch((error) => {
console.warn("[AppDataProvider] forceRefreshProxies 失败:", error);
})
.finally(() => {
scheduleTimeout(() => {
refreshProxy().catch((error) => {
console.warn("[AppDataProvider] 普通刷新也失败:", error);
});
}, 200);
});
}, 0);
// 刷新规则数据
refreshRules().catch((error) =>
console.warn("[AppDataProvider] 规则刷新失败:", error),
);
refreshRuleProviders().catch((error) =>
console.warn("[AppDataProvider] 规则提供者刷新失败:", error),
);
// scheduleTimeout(() => {
// void forceRefreshProxies()
// .catch((error) => {
// console.warn("[AppDataProvider] forceRefreshProxies 失败:", error);
// })
// .finally(() => {
// scheduleTimeout(() => {
// refreshProxy().catch((error) => {
// console.warn("[AppDataProvider] 普通刷新也失败:", error);
// });
// }, 200);
// });
// }, 0);
};
const handleRefreshClash = () => {
@@ -205,11 +207,11 @@ export const AppDataProvider = ({
scheduleTimeout(async () => {
try {
console.log("[AppDataProvider] Clash刷新 - 强制刷新代理缓存");
await withTimeout(
forceRefreshProxies(),
8000,
"forceRefreshProxies timeout",
);
// await withTimeout(
// forceRefreshProxies(),
// 8000,
// "forceRefreshProxies timeout",
// );
await refreshProxy();
} catch (error) {
console.error(
@@ -240,27 +242,27 @@ export const AppDataProvider = ({
}, 100);
};
const handleForceRefreshProxies = () => {
console.log("[AppDataProvider] 强制代理刷新事件");
// const handleForceRefreshProxies = () => {
// console.log("[AppDataProvider] 强制代理刷新事件");
void forceRefreshProxies()
.then(() => {
console.log("[AppDataProvider] 强制刷新代理缓存完成");
return refreshProxy();
})
.then(() => {
console.log("[AppDataProvider] 前端代理数据刷新完成");
})
.catch((error) => {
console.warn("[AppDataProvider] 强制代理刷新失败:", error);
refreshProxy().catch((fallbackError) => {
console.warn(
"[AppDataProvider] 普通代理刷新也失败:",
fallbackError,
);
});
});
};
// void forceRefreshProxies()
// .then(() => {
// console.log("[AppDataProvider] 强制刷新代理缓存完成");
// return refreshProxy();
// })
// .then(() => {
// console.log("[AppDataProvider] 前端代理数据刷新完成");
// })
// .catch((error) => {
// console.warn("[AppDataProvider] 强制代理刷新失败:", error);
// refreshProxy().catch((fallbackError) => {
// console.warn(
// "[AppDataProvider] 普通代理刷新也失败:",
// fallbackError,
// );
// });
// });
// };
const initializeListeners = async () => {
try {
@@ -282,15 +284,15 @@ export const AppDataProvider = ({
"verge://refresh-proxy-config",
handleRefreshProxy,
);
const unlistenForceRefresh = await listen(
"verge://force-refresh-proxies",
handleForceRefreshProxies,
);
// const unlistenForceRefresh = await listen(
// "verge://force-refresh-proxies",
// handleForceRefreshProxies,
// );
registerCleanup(() => {
unlistenClash();
unlistenProxy();
unlistenForceRefresh();
// unlistenForceRefresh();
});
} catch (error) {
console.warn("[AppDataProvider] 设置 Tauri 事件监听器失败:", error);
@@ -298,7 +300,7 @@ export const AppDataProvider = ({
const fallbackHandlers: Array<[string, EventListener]> = [
["verge://refresh-clash-config", handleRefreshClash],
["verge://refresh-proxy-config", handleRefreshProxy],
["verge://force-refresh-proxies", handleForceRefreshProxies],
// ["verge://force-refresh-proxies", handleForceRefreshProxies],
];
fallbackHandlers.forEach(([eventName, handler]) => {
@@ -322,7 +324,7 @@ export const AppDataProvider = ({
const { data: clashConfig, mutate: refreshClashConfig } = useSWR(
"getClashConfig",
getClashConfig,
getBaseConfig,
{
refreshInterval: 60000, // 60秒刷新间隔减少频繁请求
revalidateOnFocus: false,
@@ -334,7 +336,7 @@ export const AppDataProvider = ({
// 提供者数据
const { data: proxyProviders, mutate: refreshProxyProviders } = useSWR(
"getProxyProviders",
getProxyProviders,
calcuProxyProviders,
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
@@ -384,86 +386,93 @@ export const AppDataProvider = ({
// 高频率更新数据 (2秒)
const { data: uptimeData } = useSWR("appUptime", getAppUptime, {
// TODO: 运行时间
refreshInterval: 2000,
revalidateOnFocus: false,
suspense: false,
});
// 连接数据 - 使用IPC轮询更新并计算速度
const {
data: connectionsData = {
connections: [],
uploadTotal: 0,
downloadTotal: 0,
},
} = useSWR(
clashInfo && pageVisible ? "getConnections" : null,
async () => {
const data = await getConnections();
const rawConnections: IConnectionsItem[] = data.connections || [];
// const {
// data: connectionsData = {
// connections: [],
// uploadTotal: 0,
// downloadTotal: 0,
// },
// } = useSWR(
// clashInfo && pageVisible ? "getConnections" : null,
// async () => {
// const data = await getConnections();
// const rawConnections =
// data.connections?.map((item) => {
// // TODO: transform bigint to number
// return { ...item, upload: 0, download: 0 } as IConnectionsItem;
// }) || [];
// 计算带速度的连接数据
const connectionsWithSpeed = calculateConnectionSpeeds(rawConnections);
// // 计算带速度的连接数据
// const connectionsWithSpeed = calculateConnectionSpeeds(rawConnections);
// 更新上一次数据的引用
const currentMap = new Map<string, ConnectionSpeedData>();
const now = Date.now();
rawConnections.forEach((conn) => {
currentMap.set(conn.id, {
id: conn.id,
upload: conn.upload,
download: conn.download,
timestamp: now,
});
});
previousConnectionsRef.current = currentMap;
// // 更新上一次数据的引用
// const currentMap = new Map<string, ConnectionSpeedData>();
// const now = Date.now();
// rawConnections.forEach((conn) => {
// currentMap.set(conn.id, {
// id: conn.id,
// upload: conn.upload,
// download: conn.download,
// timestamp: now,
// });
// });
// previousConnectionsRef.current = currentMap;
return {
connections: connectionsWithSpeed,
uploadTotal: data.uploadTotal || 0,
downloadTotal: data.downloadTotal || 0,
};
},
{
refreshInterval: 1000, // 1秒刷新一次
fallbackData: { connections: [], uploadTotal: 0, downloadTotal: 0 },
keepPreviousData: true,
onError: (error) => {
console.error("[Connections] IPC 获取数据错误:", error);
},
},
);
// return {
// connections: connectionsWithSpeed,
// uploadTotal: data.uploadTotal || 0,
// downloadTotal: data.downloadTotal || 0,
// };
// },
// {
// refreshInterval: 1000, // 1秒刷新一次
// fallbackData: { connections: [], uploadTotal: 0, downloadTotal: 0 },
// keepPreviousData: true,
// onError: (error) => {
// console.error("[Connections] IPC 获取数据错误:", error);
// },
// },
// );
// 流量数据 - 使用IPC轮询更新
const { data: trafficData = { up: 0, down: 0 } } = useSWR(
clashInfo && pageVisible ? "getTrafficData" : null,
getTrafficData,
{
refreshInterval: 1000, // 1秒刷新一次
fallbackData: { up: 0, down: 0 },
keepPreviousData: true,
onSuccess: () => {
// console.log("[Traffic][AppDataProvider] IPC 获取到流量数据:", data);
},
onError: (error) => {
console.error("[Traffic][AppDataProvider] IPC 获取数据错误:", error);
},
},
);
// const trafficData = { up: 0, down: 0 };
// const { data: trafficData = { up: 0, down: 0 } } = useSWR(
// clashInfo && pageVisible ? "getTrafficData" : null,
// getTrafficData,
// {
// refreshInterval: 1000, // 1秒刷新一次
// fallbackData: { up: 0, down: 0 },
// keepPreviousData: true,
// onSuccess: () => {
// // console.log("[Traffic][AppDataProvider] IPC 获取到流量数据:", data);
// },
// onError: (error) => {
// console.error("[Traffic][AppDataProvider] IPC 获取数据错误:", error);
// },
// },
// );
// 内存数据 - 使用IPC轮询更新
const { data: memoryData = { inuse: 0 } } = useSWR(
clashInfo && pageVisible ? "getMemoryData" : null,
getMemoryData,
{
refreshInterval: 2000, // 2秒刷新一次
fallbackData: { inuse: 0 },
keepPreviousData: true,
onError: (error) => {
console.error("[Memory] IPC 获取数据错误:", error);
},
},
);
// const memoryData = { inuse: 0 };
// const { data: memoryData = { inuse: 0 } } = useSWR(
// clashInfo && pageVisible ? "getMemoryData" : null,
// getMemoryData,
// {
// refreshInterval: 2000, // 2秒刷新一次
// fallbackData: { inuse: 0 },
// keepPreviousData: true,
// onError: (error) => {
// console.error("[Memory] IPC 获取数据错误:", error);
// },
// },
// );
// 提供统一的刷新方法
const refreshAll = useCallback(async () => {
@@ -496,7 +505,7 @@ export const AppDataProvider = ({
// PAC模式显示我们期望设置的代理地址
const proxyHost = verge.proxy_host || "127.0.0.1";
const proxyPort =
verge.verge_mixed_port || clashConfig["mixed-port"] || 7897;
verge.verge_mixed_port || clashConfig.mixedPort || 7897;
return `${proxyHost}:${proxyPort}`;
} else {
// HTTP代理模式优先使用系统地址但如果格式不正确则使用期望地址
@@ -511,7 +520,7 @@ export const AppDataProvider = ({
// 系统地址无效,返回期望的代理地址
const proxyHost = verge.proxy_host || "127.0.0.1";
const proxyPort =
verge.verge_mixed_port || clashConfig["mixed-port"] || 7897;
verge.verge_mixed_port || clashConfig.mixedPort || 7897;
return `${proxyHost}:${proxyPort}`;
}
}
@@ -521,26 +530,26 @@ export const AppDataProvider = ({
// 数据
proxies: proxiesData,
clashConfig,
rules: rulesData || [],
rules: rulesData?.rules || [],
sysproxy,
runningMode,
uptime: uptimeData || 0,
// 提供者数据
proxyProviders: proxyProviders || {},
ruleProviders: ruleProviders || {},
ruleProviders: ruleProviders?.providers || {},
// 连接数据
connections: {
data: connectionsData.connections || [],
count: connectionsData.connections?.length || 0,
uploadTotal: connectionsData.uploadTotal || 0,
downloadTotal: connectionsData.downloadTotal || 0,
},
// connections: {
// data: connectionsData.connections || [],
// count: connectionsData.connections?.length || 0,
// uploadTotal: connectionsData.uploadTotal || 0,
// downloadTotal: connectionsData.downloadTotal || 0,
// },
// 实时流量数据
traffic: trafficData,
memory: memoryData,
// traffic: trafficData,
// memory: memoryData,
systemProxyAddress: calculateSystemProxyAddress(),
@@ -552,7 +561,7 @@ export const AppDataProvider = ({
refreshProxyProviders,
refreshRuleProviders,
refreshAll,
};
} as AppDataContextType;
}, [
proxiesData,
clashConfig,
@@ -560,9 +569,9 @@ export const AppDataProvider = ({
sysproxy,
runningMode,
uptimeData,
connectionsData,
trafficData,
memoryData,
// connectionsData,
// trafficData,
// memoryData,
proxyProviders,
ruleProviders,
verge,