mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-08 19:22:44 +08:00
refactor: fix startup init chain — resolve_done semantics, dedupe events, cleanup
Backend: - Move resolve_done() from sync setup() to async task after futures::join! so Timer waits for actual init completion instead of firing immediately - Replace std::thread::sleep(50ms) with tokio::time::sleep in async context - Remove duplicate refresh_tray_menu in tray_init (keep post-join call only) - Delete dead code reset_resolve_done (process restarts, static is destroyed) - Rename create_window(is_show) → create_window(should_create) for clarity Frontend: - Remove duplicate verge://refresh-clash-config listener from AppDataProvider (useLayoutEvents handles it via invalidateQueries — single consumer path) - Stabilize useEffect deps with useRef for TQ refetch references - Simplify AppDataProvider event listener setup (profile-changed + proxy only)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import React, { useCallback, useEffect, useMemo } from 'react'
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||
import {
|
||||
getBaseConfig,
|
||||
getRuleProviders,
|
||||
@@ -79,106 +79,45 @@ export const AppDataProvider = ({
|
||||
...TQ_MIHOMO,
|
||||
})
|
||||
|
||||
const refreshProxyRef = useRef(refreshProxy)
|
||||
const refreshRulesRef = useRef(refreshRules)
|
||||
const refreshRuleProvidersRef = useRef(refreshRuleProviders)
|
||||
useEffect(() => {
|
||||
refreshProxyRef.current = refreshProxy
|
||||
}, [refreshProxy])
|
||||
useEffect(() => {
|
||||
refreshRulesRef.current = refreshRules
|
||||
}, [refreshRules])
|
||||
useEffect(() => {
|
||||
refreshRuleProvidersRef.current = refreshRuleProviders
|
||||
}, [refreshRuleProviders])
|
||||
|
||||
useEffect(() => {
|
||||
let lastProfileId: string | null = null
|
||||
let lastUpdateTime = 0
|
||||
const refreshThrottle = 800
|
||||
|
||||
let isUnmounted = false
|
||||
const scheduledTimeouts = new Set<number>()
|
||||
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<void>,
|
||||
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)
|
||||
refreshRulesRef.current().catch(() => {})
|
||||
refreshRuleProvidersRef.current().catch(() => {})
|
||||
}
|
||||
|
||||
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)
|
||||
refreshProxyRef.current().catch(() => {})
|
||||
}
|
||||
|
||||
const initializeListeners = async () => {
|
||||
@@ -187,62 +126,34 @@ export const AppDataProvider = ({
|
||||
'profile-changed',
|
||||
handleProfileChanged,
|
||||
)
|
||||
registerCleanup(unlistenProfile)
|
||||
cleanupFns.push(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()
|
||||
})
|
||||
cleanupFns.push(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) => {
|
||||
cleanupFns.forEach((fn) => {
|
||||
try {
|
||||
fn()
|
||||
} catch (error) {
|
||||
errors.push(error instanceof Error ? error : new Error(String(error)))
|
||||
console.error('[DataProvider] Cleanup error:', 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'],
|
||||
|
||||
Reference in New Issue
Block a user