mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-04-16 23:40:32 +08:00
refactor(app-data): split monolithic context into focused SWR hooks (#5576)
* refactor(app-data): split monolithic context into focused SWR hooks * refactor(swr): unify polling and consolidate proxy/config/provider data flow
This commit is contained in:
parent
871881c460
commit
8e8182f707
@ -3,8 +3,14 @@ import { Divider, Stack, Typography } from "@mui/material";
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import {
|
||||||
|
useAppUptime,
|
||||||
|
useClashConfig,
|
||||||
|
useRulesData,
|
||||||
|
useSystemProxyAddress,
|
||||||
|
useSystemProxyData,
|
||||||
|
} from "@/hooks/app-data";
|
||||||
import { useClash } from "@/hooks/use-clash";
|
import { useClash } from "@/hooks/use-clash";
|
||||||
import { useAppData } from "@/providers/app-data-context";
|
|
||||||
|
|
||||||
import { EnhancedCard } from "./enhanced-card";
|
import { EnhancedCard } from "./enhanced-card";
|
||||||
|
|
||||||
@ -19,7 +25,14 @@ const formatUptime = (uptimeMs: number) => {
|
|||||||
export const ClashInfoCard = () => {
|
export const ClashInfoCard = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { version: clashVersion } = useClash();
|
const { version: clashVersion } = useClash();
|
||||||
const { clashConfig, rules, uptime, systemProxyAddress } = useAppData();
|
const { clashConfig } = useClashConfig();
|
||||||
|
const { sysproxy } = useSystemProxyData();
|
||||||
|
const { rules } = useRulesData();
|
||||||
|
const { uptime } = useAppUptime();
|
||||||
|
const systemProxyAddress = useSystemProxyAddress({
|
||||||
|
clashConfig,
|
||||||
|
sysproxy,
|
||||||
|
});
|
||||||
|
|
||||||
// 使用useMemo缓存格式化后的uptime,避免频繁计算
|
// 使用useMemo缓存格式化后的uptime,避免频繁计算
|
||||||
const formattedUptime = useMemo(() => formatUptime(uptime), [uptime]);
|
const formattedUptime = useMemo(() => formatUptime(uptime), [uptime]);
|
||||||
|
|||||||
@ -9,8 +9,8 @@ import { useMemo } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { closeAllConnections } from "tauri-plugin-mihomo-api";
|
import { closeAllConnections } from "tauri-plugin-mihomo-api";
|
||||||
|
|
||||||
|
import { useClashConfig } from "@/hooks/app-data";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { useAppData } from "@/providers/app-data-context";
|
|
||||||
import { patchClashMode } from "@/services/cmds";
|
import { patchClashMode } from "@/services/cmds";
|
||||||
import type { TranslationKey } from "@/types/generated/i18n-keys";
|
import type { TranslationKey } from "@/types/generated/i18n-keys";
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ const MODE_META: Record<
|
|||||||
export const ClashModeCard = () => {
|
export const ClashModeCard = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { verge } = useVerge();
|
const { verge } = useVerge();
|
||||||
const { clashConfig, refreshClashConfig } = useAppData();
|
const { clashConfig, refreshClashConfig } = useClashConfig();
|
||||||
|
|
||||||
// 支持的模式列表
|
// 支持的模式列表
|
||||||
const modeList = CLASH_MODES;
|
const modeList = CLASH_MODES;
|
||||||
|
|||||||
@ -34,10 +34,10 @@ import { useNavigate } from "react-router";
|
|||||||
import { delayGroup, healthcheckProxyProvider } from "tauri-plugin-mihomo-api";
|
import { delayGroup, healthcheckProxyProvider } from "tauri-plugin-mihomo-api";
|
||||||
|
|
||||||
import { EnhancedCard } from "@/components/home/enhanced-card";
|
import { EnhancedCard } from "@/components/home/enhanced-card";
|
||||||
|
import { useClashConfig, useProxiesData, useRulesData } from "@/hooks/app-data";
|
||||||
import { useProfiles } from "@/hooks/use-profiles";
|
import { useProfiles } from "@/hooks/use-profiles";
|
||||||
import { useProxySelection } from "@/hooks/use-proxy-selection";
|
import { useProxySelection } from "@/hooks/use-proxy-selection";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { useAppData } from "@/providers/app-data-context";
|
|
||||||
import delayManager from "@/services/delay";
|
import delayManager from "@/services/delay";
|
||||||
|
|
||||||
// 本地存储的键名
|
// 本地存储的键名
|
||||||
@ -100,7 +100,9 @@ export const CurrentProxyCard = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { proxies, clashConfig, refreshProxy, rules } = useAppData();
|
const { proxies, refreshProxy } = useProxiesData();
|
||||||
|
const { clashConfig } = useClashConfig();
|
||||||
|
const { rules } = useRulesData();
|
||||||
const { verge } = useVerge();
|
const { verge } = useVerge();
|
||||||
const { current: currentProfile } = useProfiles();
|
const { current: currentProfile } = useProfiles();
|
||||||
const autoDelayEnabled = verge?.enable_auto_delay_detection ?? false;
|
const autoDelayEnabled = verge?.enable_auto_delay_detection ?? false;
|
||||||
|
|||||||
@ -24,7 +24,7 @@ import { useCallback, useMemo, useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
|
|
||||||
import { useAppData } from "@/providers/app-data-context";
|
import { useRefreshAll } from "@/hooks/app-data";
|
||||||
import { openWebUrl, updateProfile } from "@/services/cmds";
|
import { openWebUrl, updateProfile } from "@/services/cmds";
|
||||||
import { showNotice } from "@/services/noticeService";
|
import { showNotice } from "@/services/noticeService";
|
||||||
import parseTraffic from "@/utils/parse-traffic";
|
import parseTraffic from "@/utils/parse-traffic";
|
||||||
@ -281,7 +281,7 @@ export const HomeProfileCard = ({
|
|||||||
}: HomeProfileCardProps) => {
|
}: HomeProfileCardProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { refreshAll } = useAppData();
|
const refreshAll = useRefreshAll();
|
||||||
|
|
||||||
// 更新当前订阅
|
// 更新当前订阅
|
||||||
const [updating, setUpdating] = useState(false);
|
const [updating, setUpdating] = useState(false);
|
||||||
|
|||||||
@ -22,7 +22,7 @@ import { useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { updateProxyProvider } from "tauri-plugin-mihomo-api";
|
import { updateProxyProvider } from "tauri-plugin-mihomo-api";
|
||||||
|
|
||||||
import { useAppData } from "@/providers/app-data-context";
|
import { useProxiesData, useProxyProvidersData } from "@/hooks/app-data";
|
||||||
import { showNotice } from "@/services/noticeService";
|
import { showNotice } from "@/services/noticeService";
|
||||||
import parseTraffic from "@/utils/parse-traffic";
|
import parseTraffic from "@/utils/parse-traffic";
|
||||||
|
|
||||||
@ -48,7 +48,8 @@ const parseExpire = (expire?: number) => {
|
|||||||
export const ProviderButton = () => {
|
export const ProviderButton = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const { proxyProviders, refreshProxy, refreshProxyProviders } = useAppData();
|
const { proxyProviders, refreshProxyProviders } = useProxyProvidersData();
|
||||||
|
const { refreshProxy } = useProxiesData();
|
||||||
const [updating, setUpdating] = useState<Record<string, boolean>>({});
|
const [updating, setUpdating] = useState<Record<string, boolean>>({});
|
||||||
|
|
||||||
// 检查是否有提供者
|
// 检查是否有提供者
|
||||||
@ -175,8 +176,8 @@ export const ProviderButton = () => {
|
|||||||
<List sx={{ py: 0, minHeight: 250 }}>
|
<List sx={{ py: 0, minHeight: 250 }}>
|
||||||
{Object.entries(proxyProviders || {})
|
{Object.entries(proxyProviders || {})
|
||||||
.sort()
|
.sort()
|
||||||
.map(([key, item]) => {
|
.map(([key, provider]) => {
|
||||||
const provider = item;
|
if (!provider) return null;
|
||||||
const time = dayjs(provider.updatedAt);
|
const time = dayjs(provider.updatedAt);
|
||||||
const isUpdating = updating[key];
|
const isUpdating = updating[key];
|
||||||
|
|
||||||
|
|||||||
@ -39,7 +39,7 @@ import {
|
|||||||
selectNodeForGroup,
|
selectNodeForGroup,
|
||||||
} from "tauri-plugin-mihomo-api";
|
} from "tauri-plugin-mihomo-api";
|
||||||
|
|
||||||
import { useAppData } from "@/providers/app-data-context";
|
import { useProxiesData } from "@/hooks/app-data";
|
||||||
import { calcuProxies, updateProxyChainConfigInRuntime } from "@/services/cmds";
|
import { calcuProxies, updateProxyChainConfigInRuntime } from "@/services/cmds";
|
||||||
|
|
||||||
interface ProxyChainItem {
|
interface ProxyChainItem {
|
||||||
@ -199,7 +199,7 @@ export const ProxyChain = ({
|
|||||||
}: ProxyChainProps) => {
|
}: ProxyChainProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { proxies } = useAppData();
|
const { proxies } = useProxiesData();
|
||||||
const [isConnecting, setIsConnecting] = useState(false);
|
const [isConnecting, setIsConnecting] = useState(false);
|
||||||
const markUnsavedChanges = useCallback(() => {
|
const markUnsavedChanges = useCallback(() => {
|
||||||
onMarkUnsavedChanges?.();
|
onMarkUnsavedChanges?.();
|
||||||
|
|||||||
@ -15,9 +15,9 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { Virtuoso, type VirtuosoHandle } from "react-virtuoso";
|
import { Virtuoso, type VirtuosoHandle } from "react-virtuoso";
|
||||||
import { delayGroup, healthcheckProxyProvider } from "tauri-plugin-mihomo-api";
|
import { delayGroup, healthcheckProxyProvider } from "tauri-plugin-mihomo-api";
|
||||||
|
|
||||||
|
import { useProxiesData } from "@/hooks/app-data";
|
||||||
import { useProxySelection } from "@/hooks/use-proxy-selection";
|
import { useProxySelection } from "@/hooks/use-proxy-selection";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { useAppData } from "@/providers/app-data-context";
|
|
||||||
import { updateProxyChainConfigInRuntime } from "@/services/cmds";
|
import { updateProxyChainConfigInRuntime } from "@/services/cmds";
|
||||||
import delayManager from "@/services/delay";
|
import delayManager from "@/services/delay";
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ export const ProxyGroups = (props: Props) => {
|
|||||||
}>({ open: false, message: "" });
|
}>({ open: false, message: "" });
|
||||||
|
|
||||||
const { verge } = useVerge();
|
const { verge } = useVerge();
|
||||||
const { proxies: proxiesData } = useAppData();
|
const { proxies: proxiesData } = useProxiesData();
|
||||||
const groups = proxiesData?.groups;
|
const groups = proxiesData?.groups;
|
||||||
const availableGroups = useMemo(() => groups ?? [], [groups]);
|
const availableGroups = useMemo(() => groups ?? [], [groups]);
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
|
import { useProxiesData } from "@/hooks/app-data";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { useAppData } from "@/providers/app-data-context";
|
|
||||||
import { getRuntimeConfig } from "@/services/cmds";
|
import { getRuntimeConfig } from "@/services/cmds";
|
||||||
import delayManager from "@/services/delay";
|
import delayManager from "@/services/delay";
|
||||||
|
|
||||||
@ -33,24 +33,8 @@ interface IProxyItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 代理组类型
|
// 代理组类型
|
||||||
type ProxyGroup = {
|
type ProxyGroup = IProxyGroupItem & {
|
||||||
name: string;
|
now?: string;
|
||||||
type: string;
|
|
||||||
udp: boolean;
|
|
||||||
xudp: boolean;
|
|
||||||
tfo: boolean;
|
|
||||||
mptcp: boolean;
|
|
||||||
smux: boolean;
|
|
||||||
history: {
|
|
||||||
time: string;
|
|
||||||
delay: number;
|
|
||||||
}[];
|
|
||||||
now: string;
|
|
||||||
all: IProxyItem[];
|
|
||||||
hidden?: boolean;
|
|
||||||
icon?: string;
|
|
||||||
testUrl?: string;
|
|
||||||
provider?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IRenderItem {
|
export interface IRenderItem {
|
||||||
@ -99,7 +83,7 @@ export const useRenderList = (
|
|||||||
selectedGroup?: string | null,
|
selectedGroup?: string | null,
|
||||||
) => {
|
) => {
|
||||||
// 使用全局数据提供者
|
// 使用全局数据提供者
|
||||||
const { proxies: proxiesData, refreshProxy } = useAppData();
|
const { proxies: proxiesData, refreshProxy } = useProxiesData();
|
||||||
const { verge } = useVerge();
|
const { verge } = useVerge();
|
||||||
const { width } = useWindowWidth();
|
const { width } = useWindowWidth();
|
||||||
const [headStates, setHeadState] = useHeadStateNew();
|
const [headStates, setHeadState] = useHeadStateNew();
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import { useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { updateRuleProvider } from "tauri-plugin-mihomo-api";
|
import { updateRuleProvider } from "tauri-plugin-mihomo-api";
|
||||||
|
|
||||||
import { useAppData } from "@/providers/app-data-context";
|
import type { useRuleProvidersData, useRulesData } from "@/hooks/app-data";
|
||||||
import { showNotice } from "@/services/noticeService";
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
|
||||||
// 辅助组件 - 类型框
|
// 辅助组件 - 类型框
|
||||||
@ -37,10 +37,22 @@ const TypeBox = styled(Box)<{ component?: React.ElementType }>(({ theme }) => ({
|
|||||||
lineHeight: 1.25,
|
lineHeight: 1.25,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const ProviderButton = () => {
|
type RuleProvidersHook = ReturnType<typeof useRuleProvidersData>;
|
||||||
|
type RulesHook = ReturnType<typeof useRulesData>;
|
||||||
|
|
||||||
|
interface ProviderButtonProps {
|
||||||
|
ruleProviders: RuleProvidersHook["ruleProviders"];
|
||||||
|
refreshRuleProviders: RuleProvidersHook["refreshRuleProviders"];
|
||||||
|
refreshRules: RulesHook["refreshRules"];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProviderButton = ({
|
||||||
|
ruleProviders,
|
||||||
|
refreshRuleProviders,
|
||||||
|
refreshRules,
|
||||||
|
}: ProviderButtonProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const { ruleProviders, refreshRules, refreshRuleProviders } = useAppData();
|
|
||||||
const [updating, setUpdating] = useState<Record<string, boolean>>({});
|
const [updating, setUpdating] = useState<Record<string, boolean>>({});
|
||||||
|
|
||||||
// 检查是否有提供者
|
// 检查是否有提供者
|
||||||
@ -163,8 +175,8 @@ export const ProviderButton = () => {
|
|||||||
<List sx={{ py: 0, minHeight: 250 }}>
|
<List sx={{ py: 0, minHeight: 250 }}>
|
||||||
{Object.entries(ruleProviders || {})
|
{Object.entries(ruleProviders || {})
|
||||||
.sort()
|
.sort()
|
||||||
.map(([key, item]) => {
|
.map(([key, provider]) => {
|
||||||
const provider = item;
|
if (!provider) return null;
|
||||||
const time = dayjs(provider.updatedAt);
|
const time = dayjs(provider.updatedAt);
|
||||||
const isUpdating = updating[key];
|
const isUpdating = updating[key];
|
||||||
|
|
||||||
|
|||||||
@ -20,15 +20,18 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import useSWR, { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
import { getBaseConfig } from "tauri-plugin-mihomo-api";
|
|
||||||
|
|
||||||
import { BaseDialog, DialogRef, Switch } from "@/components/base";
|
import { BaseDialog, DialogRef, Switch } from "@/components/base";
|
||||||
import { BaseFieldset } from "@/components/base/base-fieldset";
|
import { BaseFieldset } from "@/components/base/base-fieldset";
|
||||||
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
||||||
import { EditorViewer } from "@/components/profile/editor-viewer";
|
import { EditorViewer } from "@/components/profile/editor-viewer";
|
||||||
|
import {
|
||||||
|
useClashConfig,
|
||||||
|
useSystemProxyAddress,
|
||||||
|
useSystemProxyData,
|
||||||
|
} from "@/hooks/app-data";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { useAppData } from "@/providers/app-data-context";
|
|
||||||
import {
|
import {
|
||||||
getAutotemProxy,
|
getAutotemProxy,
|
||||||
getNetworkInterfacesInfo,
|
getNetworkInterfacesInfo,
|
||||||
@ -92,9 +95,6 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
const { verge, patchVerge, mutateVerge } = useVerge();
|
const { verge, patchVerge, mutateVerge } = useVerge();
|
||||||
const [hostOptions, setHostOptions] = useState<string[]>([]);
|
const [hostOptions, setHostOptions] = useState<string[]>([]);
|
||||||
|
|
||||||
type SysProxy = Awaited<ReturnType<typeof getSystemProxy>>;
|
|
||||||
const [sysproxy, setSysproxy] = useState<SysProxy>();
|
|
||||||
|
|
||||||
type AutoProxy = Awaited<ReturnType<typeof getAutotemProxy>>;
|
type AutoProxy = Awaited<ReturnType<typeof getAutotemProxy>>;
|
||||||
const [autoproxy, setAutoproxy] = useState<AutoProxy>();
|
const [autoproxy, setAutoproxy] = useState<AutoProxy>();
|
||||||
|
|
||||||
@ -129,12 +129,8 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
return "127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,localhost,*.local,*.crashlytics.com,<local>";
|
return "127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,localhost,*.local,*.crashlytics.com,<local>";
|
||||||
};
|
};
|
||||||
|
|
||||||
const { data: clashConfig } = useSWR("getClashConfig", getBaseConfig, {
|
const { clashConfig } = useClashConfig();
|
||||||
revalidateOnFocus: false,
|
const { sysproxy, refreshSysproxy } = useSystemProxyData();
|
||||||
revalidateIfStale: true,
|
|
||||||
dedupingInterval: 1000,
|
|
||||||
errorRetryInterval: 5000,
|
|
||||||
});
|
|
||||||
|
|
||||||
const prevMixedPortRef = useRef(clashConfig?.mixedPort);
|
const prevMixedPortRef = useRef(clashConfig?.mixedPort);
|
||||||
|
|
||||||
@ -168,7 +164,10 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
updateProxy();
|
updateProxy();
|
||||||
}, [clashConfig?.mixedPort, value.pac]);
|
}, [clashConfig?.mixedPort, value.pac]);
|
||||||
|
|
||||||
const { systemProxyAddress } = useAppData();
|
const systemProxyAddress = useSystemProxyAddress({
|
||||||
|
clashConfig,
|
||||||
|
sysproxy,
|
||||||
|
});
|
||||||
|
|
||||||
// 为当前状态计算系统代理地址
|
// 为当前状态计算系统代理地址
|
||||||
const getSystemProxyAddress = useMemo(() => {
|
const getSystemProxyAddress = useMemo(() => {
|
||||||
@ -209,7 +208,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
pac_content: pac_file_content ?? DEFAULT_PAC,
|
pac_content: pac_file_content ?? DEFAULT_PAC,
|
||||||
proxy_host: proxy_host ?? "127.0.0.1",
|
proxy_host: proxy_host ?? "127.0.0.1",
|
||||||
});
|
});
|
||||||
getSystemProxy().then((p) => setSysproxy(p));
|
void refreshSysproxy();
|
||||||
getAutotemProxy().then((p) => setAutoproxy(p));
|
getAutotemProxy().then((p) => setAutoproxy(p));
|
||||||
fetchNetworkInterfaces();
|
fetchNetworkInterfaces();
|
||||||
},
|
},
|
||||||
|
|||||||
206
src/hooks/app-data.ts
Normal file
206
src/hooks/app-data.ts
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
import { useCallback, useMemo } from "react";
|
||||||
|
import useSWR, { useSWRConfig } from "swr";
|
||||||
|
import {
|
||||||
|
getBaseConfig,
|
||||||
|
getRuleProviders,
|
||||||
|
getRules,
|
||||||
|
} from "tauri-plugin-mihomo-api";
|
||||||
|
|
||||||
|
import {
|
||||||
|
calcuProxies,
|
||||||
|
calcuProxyProviders,
|
||||||
|
getAppUptime,
|
||||||
|
getSystemProxy,
|
||||||
|
} from "@/services/cmds";
|
||||||
|
import { SWR_DEFAULTS, SWR_REALTIME, SWR_SLOW_POLL } from "@/services/config";
|
||||||
|
|
||||||
|
import { useSharedSWRPoller } from "./use-shared-swr-poller";
|
||||||
|
import { useVerge } from "./use-verge";
|
||||||
|
|
||||||
|
export const useProxiesData = () => {
|
||||||
|
const { mutate: globalMutate } = useSWRConfig();
|
||||||
|
const { data, error, isLoading } = useSWR("getProxies", calcuProxies, {
|
||||||
|
...SWR_REALTIME,
|
||||||
|
refreshInterval: 0,
|
||||||
|
onError: (err) => console.warn("[AppData] Proxy fetch failed:", err),
|
||||||
|
});
|
||||||
|
|
||||||
|
const refreshProxy = useCallback(
|
||||||
|
() => globalMutate("getProxies"),
|
||||||
|
[globalMutate],
|
||||||
|
);
|
||||||
|
const pollerRefresh = useCallback(() => {
|
||||||
|
void globalMutate("getProxies");
|
||||||
|
}, [globalMutate]);
|
||||||
|
|
||||||
|
useSharedSWRPoller("getProxies", SWR_REALTIME.refreshInterval, pollerRefresh);
|
||||||
|
|
||||||
|
return {
|
||||||
|
proxies: data,
|
||||||
|
refreshProxy,
|
||||||
|
isLoading,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useClashConfig = () => {
|
||||||
|
const { mutate: globalMutate } = useSWRConfig();
|
||||||
|
const { data, error, isLoading } = useSWR("getClashConfig", getBaseConfig, {
|
||||||
|
...SWR_SLOW_POLL,
|
||||||
|
refreshInterval: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const refreshClashConfig = useCallback(
|
||||||
|
() => globalMutate("getClashConfig"),
|
||||||
|
[globalMutate],
|
||||||
|
);
|
||||||
|
const pollerRefresh = useCallback(() => {
|
||||||
|
void globalMutate("getClashConfig");
|
||||||
|
}, [globalMutate]);
|
||||||
|
|
||||||
|
useSharedSWRPoller(
|
||||||
|
"getClashConfig",
|
||||||
|
SWR_SLOW_POLL.refreshInterval,
|
||||||
|
pollerRefresh,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
clashConfig: data,
|
||||||
|
refreshClashConfig,
|
||||||
|
isLoading,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useProxyProvidersData = () => {
|
||||||
|
const { data, error, isLoading, mutate } = useSWR(
|
||||||
|
"getProxyProviders",
|
||||||
|
calcuProxyProviders,
|
||||||
|
SWR_DEFAULTS,
|
||||||
|
);
|
||||||
|
|
||||||
|
const refreshProxyProviders = useCallback(() => mutate(), [mutate]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
proxyProviders: data || {},
|
||||||
|
refreshProxyProviders,
|
||||||
|
isLoading,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useRuleProvidersData = () => {
|
||||||
|
const { data, error, isLoading, mutate } = useSWR(
|
||||||
|
"getRuleProviders",
|
||||||
|
getRuleProviders,
|
||||||
|
SWR_DEFAULTS,
|
||||||
|
);
|
||||||
|
|
||||||
|
const refreshRuleProviders = useCallback(() => mutate(), [mutate]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
ruleProviders: data?.providers || {},
|
||||||
|
refreshRuleProviders,
|
||||||
|
isLoading,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useRulesData = () => {
|
||||||
|
const { data, error, isLoading, mutate } = useSWR(
|
||||||
|
"getRules",
|
||||||
|
getRules,
|
||||||
|
SWR_DEFAULTS,
|
||||||
|
);
|
||||||
|
|
||||||
|
const refreshRules = useCallback(() => mutate(), [mutate]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
rules: data?.rules || [],
|
||||||
|
refreshRules,
|
||||||
|
isLoading,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSystemProxyData = () => {
|
||||||
|
const { data, error, isLoading, mutate } = useSWR(
|
||||||
|
"getSystemProxy",
|
||||||
|
getSystemProxy,
|
||||||
|
SWR_DEFAULTS,
|
||||||
|
);
|
||||||
|
|
||||||
|
const refreshSysproxy = useCallback(() => mutate(), [mutate]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
sysproxy: data,
|
||||||
|
refreshSysproxy,
|
||||||
|
isLoading,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type ClashConfig = Awaited<ReturnType<typeof getBaseConfig>>;
|
||||||
|
type SystemProxy = Awaited<ReturnType<typeof getSystemProxy>>;
|
||||||
|
|
||||||
|
interface SystemProxyAddressParams {
|
||||||
|
clashConfig?: ClashConfig | null;
|
||||||
|
sysproxy?: SystemProxy | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSystemProxyAddress = ({
|
||||||
|
clashConfig,
|
||||||
|
sysproxy,
|
||||||
|
}: SystemProxyAddressParams) => {
|
||||||
|
const { verge } = useVerge();
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
if (!verge || !clashConfig) return "-";
|
||||||
|
|
||||||
|
const isPacMode = verge.proxy_auto_config ?? false;
|
||||||
|
|
||||||
|
if (isPacMode) {
|
||||||
|
const proxyHost = verge.proxy_host || "127.0.0.1";
|
||||||
|
const proxyPort = verge.verge_mixed_port || clashConfig.mixedPort || 7897;
|
||||||
|
return [proxyHost, proxyPort].join(":");
|
||||||
|
}
|
||||||
|
|
||||||
|
const systemServer = sysproxy?.server;
|
||||||
|
if (systemServer && systemServer !== "-" && !systemServer.startsWith(":")) {
|
||||||
|
return systemServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxyHost = verge.proxy_host || "127.0.0.1";
|
||||||
|
const proxyPort = verge.verge_mixed_port || clashConfig.mixedPort || 7897;
|
||||||
|
return [proxyHost, proxyPort].join(":");
|
||||||
|
}, [clashConfig, sysproxy, verge]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useAppUptime = () => {
|
||||||
|
const { data, error, isLoading } = useSWR("appUptime", getAppUptime, {
|
||||||
|
...SWR_DEFAULTS,
|
||||||
|
refreshInterval: 3000,
|
||||||
|
errorRetryCount: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
uptime: data || 0,
|
||||||
|
error,
|
||||||
|
isLoading,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useRefreshAll = () => {
|
||||||
|
const { mutate } = useSWRConfig();
|
||||||
|
|
||||||
|
return useCallback(async () => {
|
||||||
|
await Promise.all([
|
||||||
|
mutate("getProxies"),
|
||||||
|
mutate("getClashConfig"),
|
||||||
|
mutate("getRules"),
|
||||||
|
mutate("getSystemProxy"),
|
||||||
|
mutate("getProxyProviders"),
|
||||||
|
mutate("getRuleProviders"),
|
||||||
|
]);
|
||||||
|
}, [mutate]);
|
||||||
|
};
|
||||||
@ -1,17 +1,11 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
import { useAppData } from "@/providers/app-data-context";
|
import { useClashConfig, useProxiesData } from "@/hooks/app-data";
|
||||||
|
|
||||||
// 定义代理组类型
|
|
||||||
interface ProxyGroup {
|
|
||||||
name: string;
|
|
||||||
now: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取当前代理节点信息的自定义Hook
|
// 获取当前代理节点信息的自定义Hook
|
||||||
export const useCurrentProxy = () => {
|
export const useCurrentProxy = () => {
|
||||||
// 从AppDataProvider获取数据
|
const { proxies, refreshProxy } = useProxiesData();
|
||||||
const { proxies, clashConfig, refreshProxy } = useAppData();
|
const { clashConfig } = useClashConfig();
|
||||||
|
|
||||||
// 获取当前模式
|
// 获取当前模式
|
||||||
const currentMode = clashConfig?.mode?.toLowerCase() || "rule";
|
const currentMode = clashConfig?.mode?.toLowerCase() || "rule";
|
||||||
@ -20,11 +14,15 @@ export const useCurrentProxy = () => {
|
|||||||
const currentProxyInfo = useMemo(() => {
|
const currentProxyInfo = useMemo(() => {
|
||||||
if (!proxies) return { currentProxy: null, primaryGroupName: null };
|
if (!proxies) return { currentProxy: null, primaryGroupName: null };
|
||||||
|
|
||||||
const { global, groups, records } = proxies;
|
const globalGroup = proxies.global as IProxyGroupItem | undefined;
|
||||||
|
const groups: IProxyGroupItem[] = Array.isArray(proxies.groups)
|
||||||
|
? (proxies.groups as IProxyGroupItem[])
|
||||||
|
: [];
|
||||||
|
const records = (proxies.records || {}) as Record<string, IProxyItem>;
|
||||||
|
|
||||||
// 默认信息
|
// 默认信息
|
||||||
let primaryGroupName = "GLOBAL";
|
let primaryGroupName = "GLOBAL";
|
||||||
let currentName = global?.now;
|
let currentName = globalGroup?.now;
|
||||||
|
|
||||||
// 在规则模式下,寻找主要代理组(通常是第一个或者名字包含特定关键词的组)
|
// 在规则模式下,寻找主要代理组(通常是第一个或者名字包含特定关键词的组)
|
||||||
if (currentMode === "rule" && groups.length > 0) {
|
if (currentMode === "rule" && groups.length > 0) {
|
||||||
@ -37,11 +35,11 @@ export const useCurrentProxy = () => {
|
|||||||
"自动选择",
|
"自动选择",
|
||||||
];
|
];
|
||||||
const primaryGroup =
|
const primaryGroup =
|
||||||
groups.find((group: ProxyGroup) =>
|
groups.find((group) =>
|
||||||
primaryKeywords.some((keyword) =>
|
primaryKeywords.some((keyword) =>
|
||||||
group.name.toLowerCase().includes(keyword.toLowerCase()),
|
group.name.toLowerCase().includes(keyword.toLowerCase()),
|
||||||
),
|
),
|
||||||
) || groups.filter((g: ProxyGroup) => g.name !== "GLOBAL")[0];
|
) || groups.filter((g) => g.name !== "GLOBAL")[0];
|
||||||
|
|
||||||
if (primaryGroup) {
|
if (primaryGroup) {
|
||||||
primaryGroupName = primaryGroup.name;
|
primaryGroupName = primaryGroup.name;
|
||||||
|
|||||||
121
src/hooks/use-shared-swr-poller.ts
Normal file
121
src/hooks/use-shared-swr-poller.ts
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import type { Key } from "swr";
|
||||||
|
|
||||||
|
type SharedPollerEntry = {
|
||||||
|
subscribers: number;
|
||||||
|
timer: number | null;
|
||||||
|
interval: number;
|
||||||
|
callback: (() => void) | null;
|
||||||
|
refreshWhenHidden: boolean;
|
||||||
|
refreshWhenOffline: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sharedPollers = new Map<string, SharedPollerEntry>();
|
||||||
|
|
||||||
|
const isDocumentHidden = () => {
|
||||||
|
if (typeof document === "undefined") return false;
|
||||||
|
return document.visibilityState === "hidden";
|
||||||
|
};
|
||||||
|
|
||||||
|
const isOffline = () => {
|
||||||
|
if (typeof navigator === "undefined") return false;
|
||||||
|
return navigator.onLine === false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ensureTimer = (key: string, entry: SharedPollerEntry) => {
|
||||||
|
if (typeof window === "undefined") return;
|
||||||
|
|
||||||
|
if (entry.timer !== null) {
|
||||||
|
clearInterval(entry.timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.timer = window.setInterval(() => {
|
||||||
|
if (!entry.refreshWhenHidden && isDocumentHidden()) return;
|
||||||
|
if (!entry.refreshWhenOffline && isOffline()) return;
|
||||||
|
entry.callback?.();
|
||||||
|
}, entry.interval);
|
||||||
|
};
|
||||||
|
|
||||||
|
const registerSharedPoller = (
|
||||||
|
key: string,
|
||||||
|
interval: number,
|
||||||
|
callback: () => void,
|
||||||
|
options: { refreshWhenHidden: boolean; refreshWhenOffline: boolean },
|
||||||
|
) => {
|
||||||
|
let entry = sharedPollers.get(key);
|
||||||
|
|
||||||
|
if (!entry) {
|
||||||
|
entry = {
|
||||||
|
subscribers: 0,
|
||||||
|
timer: null,
|
||||||
|
interval,
|
||||||
|
callback,
|
||||||
|
refreshWhenHidden: options.refreshWhenHidden,
|
||||||
|
refreshWhenOffline: options.refreshWhenOffline,
|
||||||
|
};
|
||||||
|
sharedPollers.set(key, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.subscribers += 1;
|
||||||
|
entry.callback = callback;
|
||||||
|
entry.interval = Math.min(entry.interval, interval);
|
||||||
|
entry.refreshWhenHidden =
|
||||||
|
entry.refreshWhenHidden || options.refreshWhenHidden;
|
||||||
|
entry.refreshWhenOffline =
|
||||||
|
entry.refreshWhenOffline || options.refreshWhenOffline;
|
||||||
|
|
||||||
|
ensureTimer(key, entry);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const current = sharedPollers.get(key);
|
||||||
|
if (!current) return;
|
||||||
|
|
||||||
|
current.subscribers -= 1;
|
||||||
|
if (current.subscribers <= 0) {
|
||||||
|
if (current.timer !== null) {
|
||||||
|
clearInterval(current.timer);
|
||||||
|
}
|
||||||
|
sharedPollers.delete(key);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizeKey = (key: Key): string | null => {
|
||||||
|
if (typeof key === "string") return key;
|
||||||
|
if (typeof key === "number" || typeof key === "boolean") return String(key);
|
||||||
|
if (Array.isArray(key)) {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(key);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface SharedSWRPollerOptions {
|
||||||
|
refreshWhenHidden?: boolean;
|
||||||
|
refreshWhenOffline?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSharedSWRPoller = (
|
||||||
|
key: Key,
|
||||||
|
interval?: number,
|
||||||
|
callback?: () => void,
|
||||||
|
options?: SharedSWRPollerOptions,
|
||||||
|
) => {
|
||||||
|
const refreshWhenHidden = options?.refreshWhenHidden ?? false;
|
||||||
|
const refreshWhenOffline = options?.refreshWhenOffline ?? false;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!key || !interval || interval <= 0 || !callback) return;
|
||||||
|
|
||||||
|
const serializedKey = normalizeKey(key);
|
||||||
|
if (!serializedKey) return;
|
||||||
|
|
||||||
|
return registerSharedPoller(serializedKey, interval, callback, {
|
||||||
|
refreshWhenHidden,
|
||||||
|
refreshWhenOffline,
|
||||||
|
});
|
||||||
|
}, [key, interval, callback, refreshWhenHidden, refreshWhenOffline]);
|
||||||
|
};
|
||||||
@ -2,14 +2,14 @@ import { useLockFn } from "ahooks";
|
|||||||
import useSWR, { mutate } from "swr";
|
import useSWR, { mutate } from "swr";
|
||||||
import { closeAllConnections } from "tauri-plugin-mihomo-api";
|
import { closeAllConnections } from "tauri-plugin-mihomo-api";
|
||||||
|
|
||||||
|
import { useSystemProxyData } from "@/hooks/app-data";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { useAppData } from "@/providers/app-data-context";
|
|
||||||
import { getAutotemProxy } from "@/services/cmds";
|
import { getAutotemProxy } from "@/services/cmds";
|
||||||
|
|
||||||
// 系统代理状态检测统一逻辑
|
// 系统代理状态检测统一逻辑
|
||||||
export const useSystemProxyState = () => {
|
export const useSystemProxyState = () => {
|
||||||
const { verge, mutateVerge, patchVerge } = useVerge();
|
const { verge, mutateVerge, patchVerge } = useVerge();
|
||||||
const { sysproxy } = useAppData();
|
const { sysproxy } = useSystemProxyData();
|
||||||
const { data: autoproxy } = useSWR("getAutotemProxy", getAutotemProxy, {
|
const { data: autoproxy } = useSWR("getAutotemProxy", getAutotemProxy, {
|
||||||
revalidateOnFocus: true,
|
revalidateOnFocus: true,
|
||||||
revalidateOnReconnect: true,
|
revalidateOnReconnect: true,
|
||||||
|
|||||||
@ -8,12 +8,13 @@ import { BaseSearchBox } from "@/components/base/base-search-box";
|
|||||||
import { ScrollTopButton } from "@/components/layout/scroll-top-button";
|
import { ScrollTopButton } from "@/components/layout/scroll-top-button";
|
||||||
import { ProviderButton } from "@/components/rule/provider-button";
|
import { ProviderButton } from "@/components/rule/provider-button";
|
||||||
import RuleItem from "@/components/rule/rule-item";
|
import RuleItem from "@/components/rule/rule-item";
|
||||||
|
import { useRuleProvidersData, useRulesData } from "@/hooks/app-data";
|
||||||
import { useVisibility } from "@/hooks/use-visibility";
|
import { useVisibility } from "@/hooks/use-visibility";
|
||||||
import { useAppData } from "@/providers/app-data-context";
|
|
||||||
|
|
||||||
const RulesPage = () => {
|
const RulesPage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { rules = [], refreshRules, refreshRuleProviders } = useAppData();
|
const { rules = [], refreshRules } = useRulesData();
|
||||||
|
const { ruleProviders, refreshRuleProviders } = useRuleProvidersData();
|
||||||
const [match, setMatch] = useState(() => (_: string) => true);
|
const [match, setMatch] = useState(() => (_: string) => true);
|
||||||
const virtuosoRef = useRef<VirtuosoHandle>(null);
|
const virtuosoRef = useRef<VirtuosoHandle>(null);
|
||||||
const [showScrollTop, setShowScrollTop] = useState(false);
|
const [showScrollTop, setShowScrollTop] = useState(false);
|
||||||
@ -57,7 +58,11 @@ const RulesPage = () => {
|
|||||||
}}
|
}}
|
||||||
header={
|
header={
|
||||||
<Box display="flex" alignItems="center" gap={1}>
|
<Box display="flex" alignItems="center" gap={1}>
|
||||||
<ProviderButton />
|
<ProviderButton
|
||||||
|
ruleProviders={ruleProviders}
|
||||||
|
refreshRuleProviders={refreshRuleProviders}
|
||||||
|
refreshRules={refreshRules}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,51 +0,0 @@
|
|||||||
import { createContext, use } from "react";
|
|
||||||
import {
|
|
||||||
BaseConfig,
|
|
||||||
ProxyProvider,
|
|
||||||
Rule,
|
|
||||||
RuleProvider,
|
|
||||||
} from "tauri-plugin-mihomo-api";
|
|
||||||
|
|
||||||
export interface AppDataContextType {
|
|
||||||
proxies: any;
|
|
||||||
clashConfig: BaseConfig;
|
|
||||||
rules: Rule[];
|
|
||||||
sysproxy: any;
|
|
||||||
runningMode?: string;
|
|
||||||
uptime: number;
|
|
||||||
proxyProviders: Record<string, ProxyProvider>;
|
|
||||||
ruleProviders: Record<string, RuleProvider>;
|
|
||||||
systemProxyAddress: string;
|
|
||||||
|
|
||||||
refreshProxy: () => Promise<any>;
|
|
||||||
refreshClashConfig: () => Promise<any>;
|
|
||||||
refreshRules: () => Promise<any>;
|
|
||||||
refreshSysproxy: () => Promise<any>;
|
|
||||||
refreshProxyProviders: () => Promise<any>;
|
|
||||||
refreshRuleProviders: () => Promise<any>;
|
|
||||||
refreshAll: () => Promise<any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConnectionWithSpeed extends IConnectionsItem {
|
|
||||||
curUpload: number;
|
|
||||||
curDownload: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConnectionSpeedData {
|
|
||||||
id: string;
|
|
||||||
upload: number;
|
|
||||||
download: number;
|
|
||||||
timestamp: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AppDataContext = createContext<AppDataContextType | null>(null);
|
|
||||||
|
|
||||||
export const useAppData = () => {
|
|
||||||
const context = use(AppDataContext);
|
|
||||||
|
|
||||||
if (!context) {
|
|
||||||
throw new Error("useAppData必须在AppDataProvider内使用");
|
|
||||||
}
|
|
||||||
|
|
||||||
return context;
|
|
||||||
};
|
|
||||||
@ -1,63 +1,25 @@
|
|||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import React, { useCallback, useEffect, useMemo } from "react";
|
import { PropsWithChildren, useCallback, useEffect } from "react";
|
||||||
import useSWR from "swr";
|
import { useSWRConfig } from "swr";
|
||||||
import {
|
|
||||||
getBaseConfig,
|
|
||||||
getRuleProviders,
|
|
||||||
getRules,
|
|
||||||
} from "tauri-plugin-mihomo-api";
|
|
||||||
|
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
// 负责监听全局事件并驱动 SWR 刷新,避免包裹全局 context 带来的额外渲染
|
||||||
import {
|
export const AppDataProvider = ({ children }: PropsWithChildren) => {
|
||||||
calcuProxies,
|
useAppDataEventBridge();
|
||||||
calcuProxyProviders,
|
return <>{children}</>;
|
||||||
getAppUptime,
|
};
|
||||||
getRunningMode,
|
|
||||||
getSystemProxy,
|
|
||||||
} from "@/services/cmds";
|
|
||||||
import { SWR_DEFAULTS, SWR_REALTIME, SWR_SLOW_POLL } from "@/services/config";
|
|
||||||
|
|
||||||
import { AppDataContext, AppDataContextType } from "./app-data-context";
|
const useAppDataEventBridge = () => {
|
||||||
|
const { mutate } = useSWRConfig();
|
||||||
|
|
||||||
// 全局数据提供者组件
|
const refreshProxy = useCallback(() => mutate("getProxies"), [mutate]);
|
||||||
export const AppDataProvider = ({
|
const refreshClashConfig = useCallback(
|
||||||
children,
|
() => mutate("getClashConfig"),
|
||||||
}: {
|
[mutate],
|
||||||
children: React.ReactNode;
|
|
||||||
}) => {
|
|
||||||
const { verge } = useVerge();
|
|
||||||
|
|
||||||
const { data: proxiesData, mutate: refreshProxy } = useSWR(
|
|
||||||
"getProxies",
|
|
||||||
calcuProxies,
|
|
||||||
{
|
|
||||||
...SWR_REALTIME,
|
|
||||||
onError: (err) => console.warn("[DataProvider] Proxy fetch failed:", err),
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
const refreshRules = useCallback(() => mutate("getRules"), [mutate]);
|
||||||
const { data: clashConfig, mutate: refreshClashConfig } = useSWR(
|
const refreshRuleProviders = useCallback(
|
||||||
"getClashConfig",
|
() => mutate("getRuleProviders"),
|
||||||
getBaseConfig,
|
[mutate],
|
||||||
SWR_SLOW_POLL,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: proxyProviders, mutate: refreshProxyProviders } = useSWR(
|
|
||||||
"getProxyProviders",
|
|
||||||
calcuProxyProviders,
|
|
||||||
SWR_DEFAULTS,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: ruleProviders, mutate: refreshRuleProviders } = useSWR(
|
|
||||||
"getRuleProviders",
|
|
||||||
getRuleProviders,
|
|
||||||
SWR_DEFAULTS,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: rulesData, mutate: refreshRules } = useSWR(
|
|
||||||
"getRules",
|
|
||||||
getRules,
|
|
||||||
SWR_DEFAULTS,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -220,125 +182,10 @@ export const AppDataProvider = ({
|
|||||||
|
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
console.error(
|
console.error(
|
||||||
`[DataProvider] ${errors.length} errors during cleanup:`,
|
"[DataProvider] " + errors.length + " errors during cleanup:",
|
||||||
errors,
|
errors,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [refreshProxy, refreshClashConfig, refreshRules, refreshRuleProviders]);
|
}, [refreshProxy, refreshClashConfig, refreshRules, refreshRuleProviders]);
|
||||||
|
|
||||||
const { data: sysproxy, mutate: refreshSysproxy } = useSWR(
|
|
||||||
"getSystemProxy",
|
|
||||||
getSystemProxy,
|
|
||||||
SWR_DEFAULTS,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: runningMode } = useSWR(
|
|
||||||
"getRunningMode",
|
|
||||||
getRunningMode,
|
|
||||||
SWR_DEFAULTS,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: uptimeData } = useSWR("appUptime", getAppUptime, {
|
|
||||||
...SWR_DEFAULTS,
|
|
||||||
refreshInterval: 3000,
|
|
||||||
errorRetryCount: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 提供统一的刷新方法
|
|
||||||
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>;
|
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user