mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-14 21:20:48 +08:00
fix: stop idle mihomo websocket subscriptions
This commit is contained in:
@@ -147,18 +147,19 @@ export const EnhancedTrafficStats = () => {
|
||||
|
||||
// 是否显示流量图表
|
||||
const trafficGraph = verge?.traffic_graph ?? true
|
||||
const displayMemory = verge?.enable_memory_usage ?? true
|
||||
|
||||
const {
|
||||
response: { data: traffic },
|
||||
} = useTrafficData({ enabled: trafficGraph && pageVisible })
|
||||
} = useTrafficData({ enabled: pageVisible })
|
||||
|
||||
const {
|
||||
response: { data: memory },
|
||||
} = useMemoryData()
|
||||
} = useMemoryData({ enabled: displayMemory && pageVisible })
|
||||
|
||||
const {
|
||||
response: { data: connectionSummary },
|
||||
} = useConnectionSummaryData()
|
||||
} = useConnectionSummaryData({ enabled: pageVisible })
|
||||
|
||||
// Canvas组件现在直接从全局Hook获取数据,无需手动添加数据点
|
||||
|
||||
@@ -211,8 +212,8 @@ export const EnhancedTrafficStats = () => {
|
||||
}, [trafficGraph, pageVisible, theme.palette.divider])
|
||||
|
||||
// 使用useMemo计算统计卡片配置
|
||||
const statCards = useMemo(
|
||||
() => [
|
||||
const statCards = useMemo(() => {
|
||||
const cards: StatCardProps[] = [
|
||||
{
|
||||
icon: <ArrowUpwardRounded fontSize="small" />,
|
||||
title: t('home.components.traffic.metrics.uploadSpeed'),
|
||||
@@ -248,17 +249,20 @@ export const EnhancedTrafficStats = () => {
|
||||
unit: parsedData.downloadTotalUnit,
|
||||
color: 'primary' as const,
|
||||
},
|
||||
{
|
||||
]
|
||||
|
||||
if (displayMemory) {
|
||||
cards.push({
|
||||
icon: <MemoryRounded fontSize="small" />,
|
||||
title: t('home.components.traffic.metrics.memoryUsage'),
|
||||
value: parsedData.inuse,
|
||||
unit: parsedData.inuseUnit,
|
||||
color: 'error' as const,
|
||||
onClick: undefined,
|
||||
},
|
||||
],
|
||||
[t, parsedData],
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return cards
|
||||
}, [t, parsedData, displayMemory])
|
||||
|
||||
return (
|
||||
<TrafficErrorBoundary
|
||||
@@ -276,7 +280,7 @@ export const EnhancedTrafficStats = () => {
|
||||
{/* 统计卡片区域 */}
|
||||
{statCards.map((card) => (
|
||||
<Grid key={card.title} size={4}>
|
||||
<CompactStatCard {...(card as StatCardProps)} />
|
||||
<CompactStatCard {...card} />
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
@@ -24,16 +24,17 @@ export const LayoutTraffic = () => {
|
||||
|
||||
// whether hide traffic graph
|
||||
const trafficGraph = verge?.traffic_graph ?? true
|
||||
const displayMemory = verge?.enable_memory_usage ?? true
|
||||
|
||||
const trafficRef = useRef<TrafficRef>(null)
|
||||
const pageVisible = useVisibility()
|
||||
|
||||
const {
|
||||
response: { data: traffic },
|
||||
} = useTrafficData({ enabled: trafficGraph && pageVisible })
|
||||
} = useTrafficData({ enabled: pageVisible })
|
||||
const {
|
||||
response: { data: memory },
|
||||
} = useMemoryData()
|
||||
} = useMemoryData({ enabled: displayMemory && pageVisible })
|
||||
|
||||
// 监听数据变化,为图表添加数据点
|
||||
useEffect(() => {
|
||||
@@ -47,9 +48,6 @@ export const LayoutTraffic = () => {
|
||||
}
|
||||
}, [traffic])
|
||||
|
||||
// 显示内存使用情况的设置
|
||||
const displayMemory = verge?.enable_memory_usage ?? true
|
||||
|
||||
// 使用parseTraffic统一处理转换,保持与首页一致的显示格式
|
||||
const [up, upUnit] = parseTraffic(traffic?.up || 0)
|
||||
const [down, downUnit] = parseTraffic(traffic?.down || 0)
|
||||
|
||||
@@ -35,7 +35,6 @@ export const initConnSummaryData: ConnectionSummaryData = {
|
||||
let connectionData: ConnectionMonitorData = initConnData
|
||||
let connectionSummary: ConnectionSummaryData = initConnSummaryData
|
||||
let connectionSocket: MihomoWebSocket | null = null
|
||||
let connectionStarted = false
|
||||
let connectionConnecting = false
|
||||
let reconnectTimer: ReturnType<typeof setTimeout> | null = null
|
||||
let flushTimer: ReturnType<typeof setTimeout> | null = null
|
||||
@@ -53,6 +52,9 @@ const notifySummaryListeners = () => {
|
||||
summaryListeners.forEach((listener) => listener())
|
||||
}
|
||||
|
||||
const hasConnectionSubscribers = () =>
|
||||
connectionListeners.size > 0 || summaryListeners.size > 0
|
||||
|
||||
const sameMetadata = (left: ConnectionMetadata, right: ConnectionMetadata) =>
|
||||
metadataValue(left.network) === metadataValue(right.network) &&
|
||||
metadataValue(left.type) === metadataValue(right.type) &&
|
||||
@@ -215,7 +217,7 @@ const flushPendingMessage = () => {
|
||||
flushTimer = null
|
||||
const messageData = pendingMessageData
|
||||
pendingMessageData = null
|
||||
if (!messageData) return
|
||||
if (!messageData || !hasConnectionSubscribers()) return
|
||||
|
||||
let payload: IConnections
|
||||
try {
|
||||
@@ -270,6 +272,7 @@ const closeConnectionSocket = async () => {
|
||||
}
|
||||
|
||||
const scheduleReconnect = () => {
|
||||
if (!hasConnectionSubscribers()) return
|
||||
if (reconnectTimer) return
|
||||
reconnectTimer = window.setTimeout(() => {
|
||||
reconnectTimer = null
|
||||
@@ -278,20 +281,27 @@ const scheduleReconnect = () => {
|
||||
}
|
||||
|
||||
async function reconnectConnectionSocket() {
|
||||
if (!hasConnectionSubscribers()) return
|
||||
await closeConnectionSocket()
|
||||
scheduleReconnect()
|
||||
}
|
||||
|
||||
async function connectConnectionSocket() {
|
||||
if (connectionSocket || connectionConnecting) return
|
||||
if (!hasConnectionSubscribers()) return
|
||||
|
||||
clearReconnectTimer()
|
||||
connectionConnecting = true
|
||||
|
||||
try {
|
||||
const socket = await MihomoWebSocket.connect_connections()
|
||||
if (!hasConnectionSubscribers()) {
|
||||
await socket.close()
|
||||
return
|
||||
}
|
||||
connectionSocket = socket
|
||||
socket.addListener((message) => {
|
||||
if (connectionSocket !== socket) return
|
||||
if (message.type !== 'Text') return
|
||||
if (message.data.startsWith('Websocket error')) {
|
||||
void reconnectConnectionSocket()
|
||||
@@ -308,27 +318,39 @@ async function connectConnectionSocket() {
|
||||
}
|
||||
|
||||
const startConnectionMonitor = () => {
|
||||
if (connectionStarted) return
|
||||
connectionStarted = true
|
||||
void connectConnectionSocket()
|
||||
}
|
||||
|
||||
const stopConnectionMonitorIfIdle = () => {
|
||||
if (hasConnectionSubscribers()) return
|
||||
|
||||
clearReconnectTimer()
|
||||
pendingMessageData = null
|
||||
if (flushTimer) {
|
||||
window.clearTimeout(flushTimer)
|
||||
flushTimer = null
|
||||
}
|
||||
void closeConnectionSocket()
|
||||
}
|
||||
|
||||
const getConnectionSnapshot = () => connectionData
|
||||
const getConnectionSummarySnapshot = () => connectionSummary
|
||||
|
||||
const subscribeConnectionData = (listener: ConnectionListener) => {
|
||||
startConnectionMonitor()
|
||||
connectionListeners.add(listener)
|
||||
startConnectionMonitor()
|
||||
return () => {
|
||||
connectionListeners.delete(listener)
|
||||
stopConnectionMonitorIfIdle()
|
||||
}
|
||||
}
|
||||
|
||||
const subscribeConnectionSummary = (listener: ConnectionListener) => {
|
||||
startConnectionMonitor()
|
||||
summaryListeners.add(listener)
|
||||
startConnectionMonitor()
|
||||
return () => {
|
||||
summaryListeners.delete(listener)
|
||||
stopConnectionMonitorIfIdle()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,9 +373,15 @@ const clearClosedConnectionData = () => {
|
||||
notifyConnectionListeners()
|
||||
}
|
||||
|
||||
export const useConnectionData = () => {
|
||||
export const useConnectionData = (options?: { enabled?: boolean }) => {
|
||||
const enabled = options?.enabled ?? true
|
||||
const subscribe = useCallback(
|
||||
(listener: ConnectionListener) =>
|
||||
enabled ? subscribeConnectionData(listener) : () => {},
|
||||
[enabled],
|
||||
)
|
||||
const data = useSyncExternalStore(
|
||||
subscribeConnectionData,
|
||||
subscribe,
|
||||
getConnectionSnapshot,
|
||||
getConnectionSnapshot,
|
||||
)
|
||||
@@ -372,9 +400,15 @@ export const useConnectionData = () => {
|
||||
}
|
||||
}
|
||||
|
||||
export const useConnectionSummaryData = () => {
|
||||
export const useConnectionSummaryData = (options?: { enabled?: boolean }) => {
|
||||
const enabled = options?.enabled ?? true
|
||||
const subscribe = useCallback(
|
||||
(listener: ConnectionListener) =>
|
||||
enabled ? subscribeConnectionSummary(listener) : () => {},
|
||||
[enabled],
|
||||
)
|
||||
const data = useSyncExternalStore(
|
||||
subscribeConnectionSummary,
|
||||
subscribe,
|
||||
getConnectionSummarySnapshot,
|
||||
getConnectionSummarySnapshot,
|
||||
)
|
||||
|
||||
@@ -29,10 +29,12 @@ const shouldSkipDuplicateMemory = (memory: IMemoryUsageItem) => {
|
||||
return false
|
||||
}
|
||||
|
||||
export const useMemoryData = () => {
|
||||
export const useMemoryData = (options?: { enabled?: boolean }) => {
|
||||
const enabled = options?.enabled ?? true
|
||||
|
||||
const { response, refresh } = useMihomoWsSubscription<IMemoryUsageItem>({
|
||||
storageKey: 'mihomo_memory_date',
|
||||
buildSubscriptKey: (date) => `getClashMemory-${date}`,
|
||||
buildSubscriptKey: (date) => (enabled ? `getClashMemory-${date}` : null),
|
||||
fallbackData: FALLBACK_MEMORY_USAGE,
|
||||
connect: () => MihomoWebSocket.connect_memory(),
|
||||
throttleMs: 500,
|
||||
|
||||
@@ -33,7 +33,7 @@ export const useTrafficData = (options?: { enabled?: boolean }) => {
|
||||
} = useTrafficMonitorEnhanced({ subscribe: false, enabled })
|
||||
const { response, refresh } = useMihomoWsSubscription<ITrafficItem>({
|
||||
storageKey: 'mihomo_traffic_date',
|
||||
buildSubscriptKey: (date) => `getClashTraffic-${date}`,
|
||||
buildSubscriptKey: (date) => (enabled ? `getClashTraffic-${date}` : null),
|
||||
fallbackData: FALLBACK_TRAFFIC,
|
||||
connect: () => MihomoWebSocket.connect_traffic(),
|
||||
throttleMs: 200,
|
||||
|
||||
+52
-12
@@ -1,28 +1,68 @@
|
||||
import { TauriEvent } from '@tauri-apps/api/event'
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
const isDocumentVisible = () =>
|
||||
typeof document === 'undefined' || document.visibilityState === 'visible'
|
||||
|
||||
export const useVisibility = () => {
|
||||
const [visible, setVisible] = useState(() =>
|
||||
typeof document === 'undefined'
|
||||
? true
|
||||
: document.visibilityState === 'visible',
|
||||
)
|
||||
const [visible, setVisible] = useState(isDocumentVisible)
|
||||
|
||||
useEffect(() => {
|
||||
const handleVisibilityChange = () => {
|
||||
setVisible(document.visibilityState === 'visible')
|
||||
let mounted = true
|
||||
let visibilityTimer: ReturnType<typeof setTimeout> | null = null
|
||||
const appWindow = getCurrentWindow()
|
||||
|
||||
const updateVisible = async () => {
|
||||
const windowVisible = await appWindow.isVisible().catch(() => true)
|
||||
if (mounted) {
|
||||
setVisible(isDocumentVisible() && windowVisible)
|
||||
}
|
||||
}
|
||||
|
||||
const updateVisibleSoon = () => {
|
||||
if (visibilityTimer) {
|
||||
window.clearTimeout(visibilityTimer)
|
||||
}
|
||||
visibilityTimer = window.setTimeout(() => {
|
||||
visibilityTimer = null
|
||||
void updateVisible()
|
||||
}, 50)
|
||||
}
|
||||
|
||||
const handleVisibleEvent = () => {
|
||||
void updateVisible()
|
||||
}
|
||||
|
||||
const handleFocus = () => setVisible(true)
|
||||
const handlePointerDown = () => setVisible(true)
|
||||
|
||||
document.addEventListener('focus', handleFocus)
|
||||
document.addEventListener('focus', handleVisibleEvent)
|
||||
document.addEventListener('pointerdown', handlePointerDown)
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange)
|
||||
document.addEventListener('visibilitychange', handleVisibleEvent)
|
||||
window.addEventListener('focus', handleVisibleEvent)
|
||||
|
||||
const unlistenFocusChanged = appWindow.onFocusChanged(updateVisibleSoon)
|
||||
const unlistenCloseRequested = appWindow.listen(
|
||||
TauriEvent.WINDOW_CLOSE_REQUESTED,
|
||||
() => {
|
||||
setVisible(false)
|
||||
updateVisibleSoon()
|
||||
},
|
||||
)
|
||||
void updateVisible()
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('focus', handleFocus)
|
||||
mounted = false
|
||||
if (visibilityTimer) {
|
||||
window.clearTimeout(visibilityTimer)
|
||||
visibilityTimer = null
|
||||
}
|
||||
document.removeEventListener('focus', handleVisibleEvent)
|
||||
document.removeEventListener('pointerdown', handlePointerDown)
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange)
|
||||
document.removeEventListener('visibilitychange', handleVisibleEvent)
|
||||
window.removeEventListener('focus', handleVisibleEvent)
|
||||
void unlistenFocusChanged.then((unlisten) => unlisten())
|
||||
void unlistenCloseRequested.then((unlisten) => unlisten())
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ import { ConnectionTable } from '@/components/connection/connection-table'
|
||||
import { useConnectionData } from '@/hooks/use-connection-data'
|
||||
import { useConnectionSetting } from '@/hooks/use-connection-setting'
|
||||
import { useTrafficData } from '@/hooks/use-traffic-data'
|
||||
import { useVisibility } from '@/hooks/use-visibility'
|
||||
import parseTraffic from '@/utils/parse-traffic'
|
||||
|
||||
type OrderFunc = (list: IConnectionsItem[]) => IConnectionsItem[]
|
||||
@@ -80,6 +81,7 @@ const orderFunctionMap = ORDER_OPTIONS.reduce<Record<OrderKey, OrderFunc>>(
|
||||
const EMPTY_CONNECTIONS: IConnectionsItem[] = []
|
||||
const ConnectionsPage = () => {
|
||||
const { t } = useTranslation()
|
||||
const pageVisible = useVisibility()
|
||||
const [match, setMatch] = useState<(input: string) => boolean>(
|
||||
() => () => true,
|
||||
)
|
||||
@@ -92,10 +94,10 @@ const ConnectionsPage = () => {
|
||||
const {
|
||||
response: { data: connections },
|
||||
clearClosedConnections,
|
||||
} = useConnectionData()
|
||||
} = useConnectionData({ enabled: pageVisible })
|
||||
const {
|
||||
response: { data: traffic },
|
||||
} = useTrafficData()
|
||||
} = useTrafficData({ enabled: pageVisible })
|
||||
|
||||
const [setting, setSetting] = useConnectionSetting()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user