mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-01 12:02:07 +08:00
317 lines
8.1 KiB
TypeScript
317 lines
8.1 KiB
TypeScript
import { listen } from '@tauri-apps/api/event'
|
|
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
|
|
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 { useQuery } from '@/services/query-client'
|
|
import { queryClient } from '@/services/query-client'
|
|
|
|
import {
|
|
ClashConfigContext,
|
|
CoreDataStatusContext,
|
|
ProxiesContext,
|
|
RefreshersContext,
|
|
RulesContext,
|
|
SystemContext,
|
|
UptimeContext,
|
|
} from './app-data-context'
|
|
|
|
const TQ_MIHOMO = {
|
|
refetchOnWindowFocus: false,
|
|
refetchOnReconnect: false,
|
|
staleTime: 1500,
|
|
retry: 3,
|
|
retryDelay: (attempt: number) => Math.min(200 * 2 ** attempt, 3000),
|
|
} as const
|
|
|
|
const TQ_DEFAULTS = {
|
|
refetchOnWindowFocus: false,
|
|
refetchOnReconnect: false,
|
|
staleTime: 5000,
|
|
retry: 2,
|
|
} as const
|
|
|
|
function useStableFn<T extends (...args: any[]) => any>(fn: T): T {
|
|
const ref = useRef(fn)
|
|
ref.current = fn
|
|
return useCallback((...args: Parameters<T>) => ref.current(...args), []) as T
|
|
}
|
|
|
|
// 全局数据提供者组件
|
|
export const AppDataProvider = ({
|
|
children,
|
|
}: {
|
|
children: React.ReactNode
|
|
}) => {
|
|
const { verge } = useVerge()
|
|
|
|
const {
|
|
data: proxiesData,
|
|
isPending: isProxiesPending,
|
|
refetch: _refetchProxy,
|
|
} = useQuery({
|
|
queryKey: ['getProxies'],
|
|
queryFn: calcuProxies,
|
|
...TQ_MIHOMO,
|
|
})
|
|
|
|
const {
|
|
data: clashConfig,
|
|
isPending: isClashConfigPending,
|
|
refetch: _refetchClashConfig,
|
|
} = useQuery({
|
|
queryKey: ['getClashConfig'],
|
|
queryFn: getBaseConfig,
|
|
...TQ_MIHOMO,
|
|
})
|
|
|
|
const { data: proxyProviders, refetch: _refetchProxyProviders } = useQuery({
|
|
queryKey: ['getProxyProviders'],
|
|
queryFn: calcuProxyProviders,
|
|
...TQ_MIHOMO,
|
|
})
|
|
|
|
const { data: ruleProviders, refetch: _refetchRuleProviders } = useQuery({
|
|
queryKey: ['getRuleProviders'],
|
|
queryFn: getRuleProviders,
|
|
...TQ_MIHOMO,
|
|
})
|
|
|
|
const { data: rulesData, refetch: _refetchRules } = useQuery({
|
|
queryKey: ['getRules'],
|
|
queryFn: getRules,
|
|
...TQ_MIHOMO,
|
|
})
|
|
|
|
const { data: sysproxy, refetch: _refetchSysproxy } = useQuery({
|
|
queryKey: ['getSystemProxy'],
|
|
queryFn: getSystemProxy,
|
|
...TQ_DEFAULTS,
|
|
})
|
|
|
|
const { data: runningMode } = useQuery({
|
|
queryKey: ['getRunningMode'],
|
|
queryFn: getRunningMode,
|
|
...TQ_DEFAULTS,
|
|
})
|
|
|
|
const { data: uptimeData } = useQuery({
|
|
queryKey: ['appUptime'],
|
|
queryFn: getAppUptime,
|
|
...TQ_DEFAULTS,
|
|
refetchInterval: 3000,
|
|
retry: 1,
|
|
})
|
|
|
|
const refreshProxy = useStableFn(_refetchProxy)
|
|
const refreshClashConfig = useStableFn(_refetchClashConfig)
|
|
const refreshRules = useStableFn(_refetchRules)
|
|
const refreshSysproxy = useStableFn(_refetchSysproxy)
|
|
const refreshProxyProviders = useStableFn(_refetchProxyProviders)
|
|
const refreshRuleProviders = useStableFn(_refetchRuleProviders)
|
|
|
|
useEffect(() => {
|
|
let lastProfileId: string | null = null
|
|
let lastUpdateTime = 0
|
|
const refreshThrottle = 800
|
|
const cleanupFns: Array<() => void> = []
|
|
|
|
const handleProfileChanged = (event: { payload: string }) => {
|
|
const newProfileId = event.payload
|
|
const now = Date.now()
|
|
if (
|
|
lastProfileId === newProfileId &&
|
|
now - lastUpdateTime < refreshThrottle
|
|
) {
|
|
return
|
|
}
|
|
lastProfileId = newProfileId
|
|
lastUpdateTime = now
|
|
void queryClient.invalidateQueries({ queryKey: ['getProfiles'] })
|
|
refreshProxy().catch(() => {})
|
|
refreshProxyProviders().catch(() => {})
|
|
refreshRules().catch(() => {})
|
|
refreshRuleProviders().catch(() => {})
|
|
}
|
|
|
|
const handleRefreshProxy = () => {
|
|
const now = Date.now()
|
|
if (now - lastUpdateTime <= refreshThrottle) return
|
|
lastUpdateTime = now
|
|
refreshProxy().catch(() => {})
|
|
}
|
|
|
|
const initializeListeners = async () => {
|
|
try {
|
|
const unlistenProfile = await listen<string>(
|
|
'profile-changed',
|
|
handleProfileChanged,
|
|
)
|
|
cleanupFns.push(unlistenProfile)
|
|
} catch (error) {
|
|
console.error('[AppDataProvider] 监听 Profile 事件失败:', error)
|
|
}
|
|
|
|
try {
|
|
const unlistenProxy = await listen(
|
|
'verge://refresh-proxy-config',
|
|
handleRefreshProxy,
|
|
)
|
|
cleanupFns.push(unlistenProxy)
|
|
} catch (error) {
|
|
console.warn('[AppDataProvider] 设置 Tauri 事件监听器失败:', error)
|
|
}
|
|
}
|
|
|
|
void initializeListeners()
|
|
|
|
return () => {
|
|
cleanupFns.forEach((fn) => {
|
|
try {
|
|
fn()
|
|
} catch (error) {
|
|
console.error('[DataProvider] Cleanup error:', error)
|
|
}
|
|
})
|
|
}
|
|
}, [refreshProxy, refreshProxyProviders, refreshRules, refreshRuleProviders])
|
|
|
|
const refreshAll = useCallback(async () => {
|
|
await Promise.all([
|
|
refreshProxy(),
|
|
refreshClashConfig(),
|
|
refreshRules(),
|
|
refreshSysproxy(),
|
|
refreshProxyProviders(),
|
|
refreshRuleProviders(),
|
|
])
|
|
}, [
|
|
refreshProxy,
|
|
refreshClashConfig,
|
|
refreshRules,
|
|
refreshSysproxy,
|
|
refreshProxyProviders,
|
|
refreshRuleProviders,
|
|
])
|
|
|
|
const proxiesValue = useMemo(
|
|
() => ({
|
|
proxies: proxiesData,
|
|
proxyProviders: proxyProviders || {},
|
|
isProxiesPending,
|
|
}),
|
|
[proxiesData, proxyProviders, isProxiesPending],
|
|
)
|
|
|
|
const rulesValue = useMemo(
|
|
() => ({
|
|
rules: rulesData?.rules ?? [],
|
|
ruleProviders: ruleProviders?.providers || {},
|
|
}),
|
|
[rulesData, ruleProviders],
|
|
)
|
|
|
|
const clashConfigValue = useMemo(
|
|
() => ({
|
|
clashConfig,
|
|
isClashConfigPending,
|
|
}),
|
|
[clashConfig, isClashConfigPending],
|
|
)
|
|
|
|
const systemValue = 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 {
|
|
sysproxy,
|
|
runningMode,
|
|
systemProxyAddress: calculateSystemProxyAddress(),
|
|
}
|
|
}, [sysproxy, runningMode, verge, clashConfig])
|
|
|
|
const uptimeValue = useMemo(() => ({ uptime: uptimeData || 0 }), [uptimeData])
|
|
|
|
const coreDataStatusValue = useMemo(
|
|
() => ({ isCoreDataPending: isProxiesPending || isClashConfigPending }),
|
|
[isProxiesPending, isClashConfigPending],
|
|
)
|
|
|
|
const refreshersValue = useMemo(
|
|
() => ({
|
|
refreshProxy,
|
|
refreshClashConfig,
|
|
refreshRules,
|
|
refreshSysproxy,
|
|
refreshProxyProviders,
|
|
refreshRuleProviders,
|
|
refreshAll,
|
|
}),
|
|
[
|
|
refreshProxy,
|
|
refreshClashConfig,
|
|
refreshRules,
|
|
refreshSysproxy,
|
|
refreshProxyProviders,
|
|
refreshRuleProviders,
|
|
refreshAll,
|
|
],
|
|
)
|
|
|
|
return (
|
|
<ProxiesContext value={proxiesValue}>
|
|
<RulesContext value={rulesValue}>
|
|
<ClashConfigContext value={clashConfigValue}>
|
|
<SystemContext value={systemValue}>
|
|
<UptimeContext value={uptimeValue}>
|
|
<CoreDataStatusContext value={coreDataStatusValue}>
|
|
<RefreshersContext value={refreshersValue}>
|
|
{children}
|
|
</RefreshersContext>
|
|
</CoreDataStatusContext>
|
|
</UptimeContext>
|
|
</SystemContext>
|
|
</ClashConfigContext>
|
|
</RulesContext>
|
|
</ProxiesContext>
|
|
)
|
|
}
|