Files
clash-verge-rev/src/providers/app-data-provider.tsx
2025-10-18 16:11:42 +08:00

364 lines
9.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { listen } from "@tauri-apps/api/event";
import React, { useCallback, useEffect, useMemo } from "react";
import useSWR from "swr";
import {
getBaseConfig,
getRuleProviders,
getRules,
} from "tauri-plugin-mihomo-api";
import { useVerge } from "@/hooks/use-verge";
import {
calcuProxies,
calcuProxyProviders,
getAppUptime,
getRunningMode,
getSystemProxy,
} from "@/services/cmds";
import { AppDataContext, AppDataContextType } from "./app-data-context";
// 全局数据提供者组件
export const AppDataProvider = ({
children,
}: {
children: React.ReactNode;
}) => {
const { verge } = useVerge();
// 基础数据 - 中频率更新 (5秒)
const { data: proxiesData, mutate: refreshProxy } = useSWR(
"getProxies",
calcuProxies,
{
refreshInterval: 5000,
revalidateOnFocus: true,
suspense: false,
errorRetryCount: 3,
},
);
const { data: clashConfig, mutate: refreshClashConfig } = useSWR(
"getClashConfig",
getBaseConfig,
{
refreshInterval: 60000, // 60秒刷新间隔减少频繁请求
revalidateOnFocus: false,
suspense: false,
errorRetryCount: 3,
},
);
// 提供者数据
const { data: proxyProviders, mutate: refreshProxyProviders } = useSWR(
"getProxyProviders",
calcuProxyProviders,
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
dedupingInterval: 3000,
suspense: false,
errorRetryCount: 3,
},
);
const { data: ruleProviders, mutate: refreshRuleProviders } = useSWR(
"getRuleProviders",
getRuleProviders,
{
revalidateOnFocus: false,
suspense: false,
errorRetryCount: 3,
},
);
// 低频率更新数据
const { data: rulesData, mutate: refreshRules } = useSWR(
"getRules",
getRules,
{
revalidateOnFocus: false,
suspense: false,
errorRetryCount: 3,
},
);
// 监听profile和clash配置变更事件
useEffect(() => {
let lastProfileId: string | null = null;
let lastUpdateTime = 0;
const refreshThrottle = 500;
let isUnmounted = false;
const scheduledTimeouts = new Set<number>();
const cleanupFns: Array<() => void> = [];
const registerCleanup = (fn: () => void) => {
if (isUnmounted) {
fn();
} else {
cleanupFns.push(fn);
}
};
const addWindowListener = (eventName: string, handler: EventListener) => {
// eslint-disable-next-line @eslint-react/web-api/no-leaked-event-listener -- cleanup is returned by this helper
window.addEventListener(eventName, handler);
return () => window.removeEventListener(eventName, handler);
};
const scheduleTimeout = (
callback: () => void | Promise<void>,
delay: number,
) => {
const timeoutId = window.setTimeout(() => {
scheduledTimeouts.delete(timeoutId);
void callback();
}, delay);
scheduledTimeouts.add(timeoutId);
return timeoutId;
};
const clearAllTimeouts = () => {
scheduledTimeouts.forEach((timeoutId) => clearTimeout(timeoutId));
scheduledTimeouts.clear();
};
const handleProfileChanged = (event: { payload: string }) => {
const newProfileId = event.payload;
const now = Date.now();
console.log(`[AppDataProvider] Profile切换事件: ${newProfileId}`);
if (
lastProfileId === newProfileId &&
now - lastUpdateTime < refreshThrottle
) {
console.log("[AppDataProvider] 重复事件被防抖,跳过");
return;
}
lastProfileId = newProfileId;
lastUpdateTime = now;
// 刷新规则数据
refreshRules().catch((error) =>
console.warn("[AppDataProvider] 规则刷新失败:", error),
);
refreshRuleProviders().catch((error) =>
console.warn("[AppDataProvider] 规则提供者刷新失败:", error),
);
};
const handleRefreshClash = () => {
const now = Date.now();
console.log("[AppDataProvider] Clash配置刷新事件");
if (now - lastUpdateTime <= refreshThrottle) {
return;
}
lastUpdateTime = now;
scheduleTimeout(async () => {
try {
console.log("[AppDataProvider] Clash刷新 - 刷新代理数据");
await refreshProxy();
} catch (error) {
console.error(
"[AppDataProvider] Clash刷新时刷新代理数据失败:",
error,
);
}
}, 0);
};
const handleRefreshProxy = () => {
const now = Date.now();
console.log("[AppDataProvider] 代理配置刷新事件");
if (now - lastUpdateTime <= refreshThrottle) {
return;
}
lastUpdateTime = now;
scheduleTimeout(() => {
refreshProxy().catch((error) =>
console.warn("[AppDataProvider] 代理刷新失败:", error),
);
}, 100);
};
const initializeListeners = async () => {
try {
const unlistenProfile = await listen<string>(
"profile-changed",
handleProfileChanged,
);
registerCleanup(unlistenProfile);
} catch (error) {
console.error("[AppDataProvider] 监听 Profile 事件失败:", error);
}
try {
const unlistenClash = await listen(
"verge://refresh-clash-config",
handleRefreshClash,
);
const unlistenProxy = await listen(
"verge://refresh-proxy-config",
handleRefreshProxy,
);
registerCleanup(() => {
unlistenClash();
unlistenProxy();
});
} catch (error) {
console.warn("[AppDataProvider] 设置 Tauri 事件监听器失败:", error);
const fallbackHandlers: Array<[string, EventListener]> = [
["verge://refresh-clash-config", handleRefreshClash],
["verge://refresh-proxy-config", handleRefreshProxy],
];
fallbackHandlers.forEach(([eventName, handler]) => {
registerCleanup(addWindowListener(eventName, handler));
});
}
};
void initializeListeners();
return () => {
isUnmounted = true;
clearAllTimeouts();
cleanupFns.splice(0).forEach((fn) => fn());
};
}, [refreshProxy, refreshRules, refreshRuleProviders]);
const { data: sysproxy, mutate: refreshSysproxy } = useSWR(
"getSystemProxy",
getSystemProxy,
{
revalidateOnFocus: true,
revalidateOnReconnect: true,
suspense: false,
errorRetryCount: 3,
},
);
const { data: runningMode } = useSWR("getRunningMode", getRunningMode, {
revalidateOnFocus: false,
suspense: false,
errorRetryCount: 3,
});
// 高频率更新数据 (2秒)
const { data: uptimeData } = useSWR("appUptime", getAppUptime, {
// TODO: 运行时间
refreshInterval: 2000,
revalidateOnFocus: false,
suspense: false,
});
// 提供统一的刷新方法
const refreshAll = useCallback(async () => {
await Promise.all([
refreshProxy(),
refreshClashConfig(),
refreshRules(),
refreshSysproxy(),
refreshProxyProviders(),
refreshRuleProviders(),
]);
}, [
refreshProxy,
refreshClashConfig,
refreshRules,
refreshSysproxy,
refreshProxyProviders,
refreshRuleProviders,
]);
// 聚合所有数据
const value = useMemo(() => {
// 计算系统代理地址
const calculateSystemProxyAddress = () => {
if (!verge || !clashConfig) return "-";
const isPacMode = verge.proxy_auto_config ?? false;
if (isPacMode) {
// PAC模式显示我们期望设置的代理地址
const proxyHost = verge.proxy_host || "127.0.0.1";
const proxyPort =
verge.verge_mixed_port || clashConfig.mixedPort || 7897;
return `${proxyHost}:${proxyPort}`;
} else {
// HTTP代理模式优先使用系统地址但如果格式不正确则使用期望地址
const systemServer = sysproxy?.server;
if (
systemServer &&
systemServer !== "-" &&
!systemServer.startsWith(":")
) {
return systemServer;
} else {
// 系统地址无效,返回期望的代理地址
const proxyHost = verge.proxy_host || "127.0.0.1";
const proxyPort =
verge.verge_mixed_port || clashConfig.mixedPort || 7897;
return `${proxyHost}:${proxyPort}`;
}
}
};
return {
// 数据
proxies: proxiesData,
clashConfig,
rules: rulesData?.rules || [],
sysproxy,
runningMode,
uptime: uptimeData || 0,
// 提供者数据
proxyProviders: proxyProviders || {},
ruleProviders: ruleProviders?.providers || {},
systemProxyAddress: calculateSystemProxyAddress(),
// 刷新方法
refreshProxy,
refreshClashConfig,
refreshRules,
refreshSysproxy,
refreshProxyProviders,
refreshRuleProviders,
refreshAll,
} as AppDataContextType;
}, [
proxiesData,
clashConfig,
rulesData,
sysproxy,
runningMode,
uptimeData,
proxyProviders,
ruleProviders,
verge,
refreshProxy,
refreshClashConfig,
refreshRules,
refreshSysproxy,
refreshProxyProviders,
refreshRuleProviders,
refreshAll,
]);
return <AppDataContext value={value}>{children}</AppDataContext>;
};