diff --git a/src/hooks/use-log-data.ts b/src/hooks/use-log-data.ts index 4a6c821ea..e954457f3 100644 --- a/src/hooks/use-log-data.ts +++ b/src/hooks/use-log-data.ts @@ -54,8 +54,7 @@ export const useLogData = () => { const hasLoadedInitialLogsRef = useRef(false) const { response, refresh, setData } = useMihomoWsSubscription({ - storageKey: 'mihomo_logs_date', - buildSubscriptKey: (date) => (enableLog ? `getClashLog-${date}` : null), + subscriptionKey: enableLog ? 'getClashLog' : null, fallbackData: [], connect: () => MihomoWebSocket.connect_logs(logLevel), setupHandlers: ({ next, scheduleReconnect, isMounted }) => { @@ -77,14 +76,13 @@ export const useLogData = () => { } const pendingLogs = buffer.splice(0, buffer.length) flushTimeStr = null - next(null, (current) => appendLogs(current, pendingLogs)) + next((current) => appendLogs(current, pendingLogs)) flushTimer = null } return { handleMessage: (data) => { if (data.startsWith('Websocket error')) { - next(data) void scheduleReconnect() return } @@ -108,9 +106,7 @@ export const useLogData = () => { if (!flushTimer) { flushTimer = setTimeout(flush, FLUSH_DELAY_MS) } - } catch (error) { - next(error) - } + } catch {} }, async onConnected() { if (hasLoadedInitialLogsRef.current) { @@ -119,7 +115,7 @@ export const useLogData = () => { const logs = await getClashLogs() hasLoadedInitialLogsRef.current = true if (isMounted()) { - next(null, (current) => { + next((current) => { if (!current || current.length === 0) { return clampLogs(filterLogsByLevel(logs, allowedTypes)) } diff --git a/src/hooks/use-memory-data.ts b/src/hooks/use-memory-data.ts index db6ea6cb9..f11596aac 100644 --- a/src/hooks/use-memory-data.ts +++ b/src/hooks/use-memory-data.ts @@ -33,15 +33,13 @@ export const useMemoryData = (options?: { enabled?: boolean }) => { const enabled = options?.enabled ?? true const { response, refresh } = useMihomoWsSubscription({ - storageKey: 'mihomo_memory_date', - buildSubscriptKey: (date) => (enabled ? `getClashMemory-${date}` : null), + subscriptionKey: enabled ? 'getClashMemory' : null, fallbackData: FALLBACK_MEMORY_USAGE, connect: () => MihomoWebSocket.connect_memory(), throttleMs: 500, setupHandlers: ({ next, scheduleReconnect }) => ({ handleMessage: (data) => { if (data.startsWith('Websocket error')) { - next(data, FALLBACK_MEMORY_USAGE) void scheduleReconnect() return } @@ -51,10 +49,8 @@ export const useMemoryData = (options?: { enabled?: boolean }) => { if (shouldSkipDuplicateMemory(parsed)) { return } - next(null, parsed) - } catch (error) { - next(error, FALLBACK_MEMORY_USAGE) - } + next(parsed) + } catch {} }, }), }) diff --git a/src/hooks/use-mihomo-ws-subscription.ts b/src/hooks/use-mihomo-ws-subscription.ts index c0a4cea09..ea6858af0 100644 --- a/src/hooks/use-mihomo-ws-subscription.ts +++ b/src/hooks/use-mihomo-ws-subscription.ts @@ -1,12 +1,7 @@ -import { useLocalStorage } from 'foxact/use-local-storage' -import { useCallback, useEffect, useRef } from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import { type Message, type MihomoWebSocket } from 'tauri-plugin-mihomo-api' -import { - removeCacheData, - setCacheData, - useQuery, -} from '@/services/query-client' +import { setCacheData, useQuery } from '@/services/query-client' const RECONNECT_DELAY_MS = 1000 @@ -31,7 +26,6 @@ interface SharedSubscriptionEntry { const sharedSubscriptions = new Map() const subscriptionSnapshots = new Map() -const initialSubscriptionDate = Date.now() const getSubscriptionSnapshot = (key: string) => subscriptionSnapshots.get(key) as T | undefined @@ -136,15 +130,13 @@ const createSharedSubscriptionEntry = ( return entry } +type Updater = (current?: T) => T + /** - * Mirrors SWR's MutatorCallback: consumers can pass either a plain value or a - * functional updater `(current?: T) => T`. The functional form is resolved - * against the current subscription snapshot before updating SWR. + * Pass either a plain value or a functional updater; the updater is resolved + * against the current subscription snapshot before the cache is written. */ -type NextFn = ( - error?: any, - data?: T | ((current?: T) => T | undefined), -) => void +type NextFn = (data: T | Updater) => void interface HandlerContext { next: NextFn @@ -159,8 +151,7 @@ interface HandlerResult { } interface UseMihomoWsSubscriptionOptions { - storageKey: string - buildSubscriptKey: (date: number) => string | null + subscriptionKey: string | null fallbackData: T connect: () => Promise /** @@ -178,18 +169,12 @@ interface UseMihomoWsSubscriptionOptions { export const useMihomoWsSubscription = ( options: UseMihomoWsSubscriptionOptions, ) => { - const { - storageKey, - buildSubscriptKey, - fallbackData, - connect, - throttleMs, - setupHandlers, - } = options + const { subscriptionKey, fallbackData, connect, throttleMs, setupHandlers } = + options - const [date, setDate] = useLocalStorage(storageKey, initialSubscriptionDate) - const subscriptKey = buildSubscriptKey(date) - const subscriptionCacheKey = subscriptKey ? `$sub$${subscriptKey}` : null + const subscriptionCacheKey = subscriptionKey + ? `$sub$${subscriptionKey}` + : null const lastSubscriptionCacheKeyRef = useRef(null) if (subscriptionCacheKey) { lastSubscriptionCacheKeyRef.current = subscriptionCacheKey @@ -197,20 +182,9 @@ export const useMihomoWsSubscription = ( const responseCacheKey = subscriptionCacheKey ?? lastSubscriptionCacheKeyRef.current - const resolveNextData = useCallback( - ( - data: T | ((current?: T) => T | undefined) | undefined, - cacheKey: string, - ): T => { - if (typeof data === 'function') { - const updater = data as (current?: T) => T | undefined - const current = getSubscriptionSnapshot(cacheKey) - return updater(current) ?? fallbackData - } - return data ?? fallbackData - }, - [fallbackData], - ) + // Bumping the epoch re-runs the subscription effect, rebuilding the shared + // entry (and its captured `connect`/handler closures) under the same key. + const [refreshEpoch, setRefreshEpoch] = useState(0) const response = useQuery({ queryKey: responseCacheKey ? [responseCacheKey] : ['$sub$__disabled__'], @@ -238,44 +212,35 @@ export const useMihomoWsSubscription = ( let throttleCleanup: (() => void) | undefined let wrappedNext: NextFn - const baseNext: NextFn = (error, data) => { - if (error !== undefined && error !== null) { - return - } - if (data === undefined) return - const resolved = resolveNextData(data, subscriptionCacheKey) + const baseNext: NextFn = (data) => { + const resolved = + typeof data === 'function' + ? (data as Updater)( + getSubscriptionSnapshot(subscriptionCacheKey), + ) + : (data as T) writeSubscriptionSnapshot(subscriptionCacheKey, resolved) } if (throttleMs && throttleMs > 0) { - let pendingData: T | ((current?: T) => T | undefined) | undefined - let hasPending = false + let pendingData: T | Updater | undefined let timerId: ReturnType | null = null const flush = () => { timerId = null - if (hasPending) { + if (pendingData !== undefined) { const data = pendingData pendingData = undefined - hasPending = false - baseNext(undefined, data) + baseNext(data) } } - wrappedNext = ( - error?: any, - data?: T | ((current?: T) => T | undefined), - ) => { - if (error !== undefined && error !== null) { - baseNext(error, data) - return - } + wrappedNext = (data) => { if (!timerId) { - baseNext(undefined, data) + baseNext(data) timerId = setTimeout(flush, throttleMs) } else { pendingData = data - hasPending = true } } @@ -341,15 +306,14 @@ export const useMihomoWsSubscription = ( } // eslint-disable-next-line react-compiler/react-compiler // eslint-disable-next-line react-hooks/exhaustive-deps, @eslint-react/exhaustive-deps - }, [subscriptionCacheKey]) + }, [subscriptionCacheKey, refreshEpoch]) const refresh = useCallback(() => { if (subscriptionCacheKey) { - subscriptionSnapshots.delete(subscriptionCacheKey) - void removeCacheData([subscriptionCacheKey]) + writeSubscriptionSnapshot(subscriptionCacheKey, fallbackData) } - setDate(Date.now()) - }, [subscriptionCacheKey, setDate]) + setRefreshEpoch((epoch) => epoch + 1) + }, [subscriptionCacheKey, fallbackData]) const setData = useCallback( (data: T) => { diff --git a/src/hooks/use-traffic-data.ts b/src/hooks/use-traffic-data.ts index bd48d09a7..ce20ec723 100644 --- a/src/hooks/use-traffic-data.ts +++ b/src/hooks/use-traffic-data.ts @@ -32,15 +32,13 @@ export const useTrafficData = (options?: { enabled?: boolean }) => { graphData: { appendData }, } = useTrafficMonitorEnhanced({ subscribe: false, enabled }) const { response, refresh } = useMihomoWsSubscription({ - storageKey: 'mihomo_traffic_date', - buildSubscriptKey: (date) => (enabled ? `getClashTraffic-${date}` : null), + subscriptionKey: enabled ? 'getClashTraffic' : null, fallbackData: FALLBACK_TRAFFIC, connect: () => MihomoWebSocket.connect_traffic(), throttleMs: 200, setupHandlers: ({ next, scheduleReconnect }) => ({ handleMessage: (data) => { if (data.startsWith('Websocket error')) { - next(data, FALLBACK_TRAFFIC) void scheduleReconnect() return } @@ -51,10 +49,8 @@ export const useTrafficData = (options?: { enabled?: boolean }) => { return } appendData(parsed) - next(null, parsed) - } catch (error) { - next(error, FALLBACK_TRAFFIC) - } + next(parsed) + } catch {} }, }), })