import { useQuery } from '@tanstack/react-query' import { listen } from '@tauri-apps/api/event' import React, { useCallback, useEffect, useMemo } 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 { AppDataContext, AppDataContextType } from './app-data-context' const TQ_MIHOMO = { refetchOnWindowFocus: false, refetchOnReconnect: false, staleTime: 1500, retry: 3, retryDelay: 2000, } as const const TQ_DEFAULTS = { refetchOnWindowFocus: false, refetchOnReconnect: false, staleTime: 5000, retry: 2, } as const // 全局数据提供者组件 export const AppDataProvider = ({ children, }: { children: React.ReactNode }) => { const { verge } = useVerge() const { data: proxiesData, refetch: refreshProxy } = useQuery({ queryKey: ['getProxies'], queryFn: calcuProxies, ...TQ_MIHOMO, }) const { data: clashConfig, refetch: refreshClashConfig } = useQuery({ queryKey: ['getClashConfig'], queryFn: getBaseConfig, ...TQ_MIHOMO, }) const { data: proxyProviders, refetch: refreshProxyProviders } = useQuery({ queryKey: ['getProxyProviders'], queryFn: calcuProxyProviders, ...TQ_MIHOMO, }) const { data: ruleProviders, refetch: refreshRuleProviders } = useQuery({ queryKey: ['getRuleProviders'], queryFn: getRuleProviders, ...TQ_MIHOMO, }) const { data: rulesData, refetch: refreshRules } = useQuery({ queryKey: ['getRules'], queryFn: getRules, ...TQ_MIHOMO, }) useEffect(() => { let lastProfileId: string | null = null let lastUpdateTime = 0 const refreshThrottle = 800 let isUnmounted = false const scheduledTimeouts = new Set() const cleanupFns: Array<() => void> = [] const registerCleanup = (fn: () => void) => { if (isUnmounted) { try { fn() } catch (error) { console.error('[DataProvider] Immediate cleanup failed:', error) } } else { cleanupFns.push(fn) } } const addWindowListener = (eventName: string, handler: EventListener) => { // eslint-disable-next-line @eslint-react/web-api-no-leaked-event-listener window.addEventListener(eventName, handler) return () => window.removeEventListener(eventName, handler) } const scheduleTimeout = ( callback: () => void | Promise, delay: number, ) => { if (isUnmounted) return -1 const timeoutId = window.setTimeout(() => { scheduledTimeouts.delete(timeoutId) if (!isUnmounted) { 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() if ( lastProfileId === newProfileId && now - lastUpdateTime < refreshThrottle ) { return } lastProfileId = newProfileId lastUpdateTime = now scheduleTimeout(() => { refreshRules().catch((error) => console.warn('[DataProvider] Rules refresh failed:', error), ) refreshRuleProviders().catch((error) => console.warn('[DataProvider] Rule providers refresh failed:', error), ) }, 200) } const handleRefreshClash = () => { const now = Date.now() if (now - lastUpdateTime <= refreshThrottle) return lastUpdateTime = now scheduleTimeout(async () => { await Promise.all([ refreshProxy().catch((error) => console.error('[DataProvider] Proxy refresh failed:', error), ), refreshClashConfig().catch((error) => console.error('[DataProvider] Clash config refresh failed:', error), ), ]) }, 200) } const handleRefreshProxy = () => { const now = Date.now() if (now - lastUpdateTime <= refreshThrottle) return lastUpdateTime = now scheduleTimeout(() => { refreshProxy().catch((error) => console.warn('[DataProvider] Proxy refresh failed:', error), ) }, 200) } const initializeListeners = async () => { try { const unlistenProfile = await listen( '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() const errors: Error[] = [] cleanupFns.splice(0).forEach((fn) => { try { fn() } catch (error) { errors.push(error instanceof Error ? error : new Error(String(error))) } }) if (errors.length > 0) { console.error( `[DataProvider] ${errors.length} errors during cleanup:`, errors, ) } } }, [refreshProxy, refreshClashConfig, refreshRules, refreshRuleProviders]) const { data: sysproxy, refetch: refreshSysproxy } = 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 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 {children} }