mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-14 21:20:48 +08:00
refactor(frontend): consolidate query state ownership
Consolidate ordinary query snapshots on SWR and make Profiles own its query key, cache mutation, and invalidation policy. Impact: - remove the generic synchronous query Map while keeping dedicated Mihomo WebSocket snapshots - eliminate duplicate Profiles refreshes while preserving retries and the 800 ms event throttle - preserve preload-backed Verge updates and system proxy rollback behavior - scope uptime polling to the Home Clash info card instead of the app lifetime - leave RunState, Connections, Traffic, backend, and IPC contracts unchanged - reduce frontend code by 72 net lines without adding dependencies
This commit is contained in:
@@ -5,11 +5,11 @@ import { useTranslation } from 'react-i18next'
|
|||||||
|
|
||||||
import { useClash } from '@/hooks/use-clash'
|
import { useClash } from '@/hooks/use-clash'
|
||||||
import { useDisplayedMixedPort } from '@/hooks/use-displayed-mixed-port'
|
import { useDisplayedMixedPort } from '@/hooks/use-displayed-mixed-port'
|
||||||
|
import { useAppUptime } from '@/hooks/use-system-state'
|
||||||
import {
|
import {
|
||||||
useClashConfigData,
|
useClashConfigData,
|
||||||
useRulesData,
|
useRulesData,
|
||||||
useSystemData,
|
useSystemData,
|
||||||
useUptimeData,
|
|
||||||
} from '@/providers/app-data-context'
|
} from '@/providers/app-data-context'
|
||||||
|
|
||||||
import { EnhancedCard } from './enhanced-card'
|
import { EnhancedCard } from './enhanced-card'
|
||||||
@@ -28,7 +28,7 @@ export const ClashInfoCard = () => {
|
|||||||
const { clashConfig } = useClashConfigData()
|
const { clashConfig } = useClashConfigData()
|
||||||
const displayedMixedPort = useDisplayedMixedPort()
|
const displayedMixedPort = useDisplayedMixedPort()
|
||||||
const { rules } = useRulesData()
|
const { rules } = useRulesData()
|
||||||
const { uptime } = useUptimeData()
|
const uptime = useAppUptime()
|
||||||
const { systemProxyAddress } = useSystemData()
|
const { systemProxyAddress } = useSystemData()
|
||||||
|
|
||||||
// 使用useMemo缓存格式化后的uptime,避免频繁计算
|
// 使用useMemo缓存格式化后的uptime,避免频繁计算
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
type RunState,
|
type RunState,
|
||||||
} from '@/services/cmds'
|
} from '@/services/cmds'
|
||||||
import { showNotice } from '@/services/notice-service'
|
import { showNotice } from '@/services/notice-service'
|
||||||
import { setCacheDataAsync, useQuery } from '@/services/query-client'
|
import { setCacheData, useQuery } from '@/services/query-client'
|
||||||
|
|
||||||
export const ServiceMigrationDialog = () => {
|
export const ServiceMigrationDialog = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -50,7 +50,7 @@ export const ServiceMigrationDialog = () => {
|
|||||||
const refreshRunState = async () => {
|
const refreshRunState = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await getRuntimeState()
|
const data = await getRuntimeState()
|
||||||
await setCacheDataAsync<RunState>(runStateQueryKey, data)
|
await setCacheData<RunState>(runStateQueryKey, data)
|
||||||
setStateRefreshFailed(false)
|
setStateRefreshFailed(false)
|
||||||
return data
|
return data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
patchClashConfig,
|
patchClashConfig,
|
||||||
} from '@/services/cmds'
|
} from '@/services/cmds'
|
||||||
import {
|
import {
|
||||||
getCacheData,
|
|
||||||
revalidateQuery,
|
revalidateQuery,
|
||||||
setCacheData,
|
setCacheData,
|
||||||
useQuery,
|
useQuery,
|
||||||
@@ -97,11 +96,7 @@ export const useClash = () => {
|
|||||||
if (updater === undefined) {
|
if (updater === undefined) {
|
||||||
return refetch()
|
return refetch()
|
||||||
}
|
}
|
||||||
const next =
|
void setCacheData<IConfigData>(['getRuntimeConfig'], updater)
|
||||||
typeof updater === 'function'
|
|
||||||
? updater(getCacheData<IConfigData>(['getRuntimeConfig']))
|
|
||||||
: updater
|
|
||||||
setCacheData(['getRuntimeConfig'], next)
|
|
||||||
if (revalidate !== false) {
|
if (revalidate !== false) {
|
||||||
return refetch()
|
return refetch()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { useEffect, useRef } from 'react'
|
|||||||
import { MihomoWebSocket, type LogLevel } from 'tauri-plugin-mihomo-api'
|
import { MihomoWebSocket, type LogLevel } from 'tauri-plugin-mihomo-api'
|
||||||
|
|
||||||
import { getClashLogs } from '@/services/cmds'
|
import { getClashLogs } from '@/services/cmds'
|
||||||
import { setCacheData } from '@/services/query-client'
|
|
||||||
|
|
||||||
import { useClashLog } from './use-clash-log'
|
import { useClashLog } from './use-clash-log'
|
||||||
import { useMihomoWsSubscription } from './use-mihomo-ws-subscription'
|
import { useMihomoWsSubscription } from './use-mihomo-ws-subscription'
|
||||||
@@ -54,9 +53,7 @@ export const useLogData = () => {
|
|||||||
const allowedTypes = LOG_LEVEL_FILTERS[logLevel] ?? DEFAULT_LOG_TYPES
|
const allowedTypes = LOG_LEVEL_FILTERS[logLevel] ?? DEFAULT_LOG_TYPES
|
||||||
const hasLoadedInitialLogsRef = useRef(false)
|
const hasLoadedInitialLogsRef = useRef(false)
|
||||||
|
|
||||||
const { response, refresh, subscriptionCacheKey } = useMihomoWsSubscription<
|
const { response, refresh, setData } = useMihomoWsSubscription<ILogItem[]>({
|
||||||
ILogItem[]
|
|
||||||
>({
|
|
||||||
storageKey: 'mihomo_logs_date',
|
storageKey: 'mihomo_logs_date',
|
||||||
buildSubscriptKey: (date) => (enableLog ? `getClashLog-${date}` : null),
|
buildSubscriptKey: (date) => (enableLog ? `getClashLog-${date}` : null),
|
||||||
fallbackData: [],
|
fallbackData: [],
|
||||||
@@ -154,9 +151,7 @@ export const useLogData = () => {
|
|||||||
|
|
||||||
const refreshGetClashLog = (clear = false) => {
|
const refreshGetClashLog = (clear = false) => {
|
||||||
if (clear) {
|
if (clear) {
|
||||||
if (subscriptionCacheKey) {
|
setData([])
|
||||||
setCacheData<ILogItem[]>([subscriptionCacheKey], [])
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
hasLoadedInitialLogsRef.current = false
|
hasLoadedInitialLogsRef.current = false
|
||||||
refresh()
|
refresh()
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { type MutableRefObject, useCallback, useEffect, useRef } from 'react'
|
|||||||
import { type Message, type MihomoWebSocket } from 'tauri-plugin-mihomo-api'
|
import { type Message, type MihomoWebSocket } from 'tauri-plugin-mihomo-api'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getCacheData,
|
|
||||||
removeCacheData,
|
removeCacheData,
|
||||||
setCacheData,
|
setCacheData,
|
||||||
useQuery,
|
useQuery,
|
||||||
@@ -32,8 +31,17 @@ interface SharedSubscriptionEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sharedSubscriptions = new Map<string, SharedSubscriptionEntry>()
|
const sharedSubscriptions = new Map<string, SharedSubscriptionEntry>()
|
||||||
|
const subscriptionSnapshots = new Map<string, unknown>()
|
||||||
const initialSubscriptionDate = Date.now()
|
const initialSubscriptionDate = Date.now()
|
||||||
|
|
||||||
|
const getSubscriptionSnapshot = <T>(key: string) =>
|
||||||
|
subscriptionSnapshots.get(key) as T | undefined
|
||||||
|
|
||||||
|
const writeSubscriptionSnapshot = <T>(key: string, data: T) => {
|
||||||
|
subscriptionSnapshots.set(key, data)
|
||||||
|
void setCacheData<T>([key], data)
|
||||||
|
}
|
||||||
|
|
||||||
const syncSharedWsRefs = (entry: SharedSubscriptionEntry) => {
|
const syncSharedWsRefs = (entry: SharedSubscriptionEntry) => {
|
||||||
entry.refHolders.forEach((ref) => {
|
entry.refHolders.forEach((ref) => {
|
||||||
ref.current = entry.ws
|
ref.current = entry.ws
|
||||||
@@ -141,7 +149,7 @@ const createSharedSubscriptionEntry = (
|
|||||||
/**
|
/**
|
||||||
* Mirrors SWR's MutatorCallback: consumers can pass either a plain value or a
|
* Mirrors SWR's MutatorCallback: consumers can pass either a plain value or a
|
||||||
* functional updater `(current?: T) => T`. The functional form is resolved
|
* functional updater `(current?: T) => T`. The functional form is resolved
|
||||||
* against the current cache entry before calling `setCacheData`.
|
* against the current subscription snapshot before updating SWR.
|
||||||
*/
|
*/
|
||||||
type NextFn<T> = (
|
type NextFn<T> = (
|
||||||
error?: any,
|
error?: any,
|
||||||
@@ -208,7 +216,7 @@ export const useMihomoWsSubscription = <T>(
|
|||||||
): T => {
|
): T => {
|
||||||
if (typeof data === 'function') {
|
if (typeof data === 'function') {
|
||||||
const updater = data as (current?: T) => T | undefined
|
const updater = data as (current?: T) => T | undefined
|
||||||
const current = getCacheData<T>([cacheKey])
|
const current = getSubscriptionSnapshot<T>(cacheKey)
|
||||||
return updater(current) ?? fallbackData
|
return updater(current) ?? fallbackData
|
||||||
}
|
}
|
||||||
return data ?? fallbackData
|
return data ?? fallbackData
|
||||||
@@ -218,9 +226,10 @@ export const useMihomoWsSubscription = <T>(
|
|||||||
|
|
||||||
const response = useQuery<T>({
|
const response = useQuery<T>({
|
||||||
queryKey: responseCacheKey ? [responseCacheKey] : ['$sub$__disabled__'],
|
queryKey: responseCacheKey ? [responseCacheKey] : ['$sub$__disabled__'],
|
||||||
queryFn: () => getCacheData<T>([responseCacheKey!]) ?? fallbackData,
|
queryFn: () =>
|
||||||
|
getSubscriptionSnapshot<T>(responseCacheKey!) ?? fallbackData,
|
||||||
initialData: () =>
|
initialData: () =>
|
||||||
getCacheData<T>([responseCacheKey ?? '$sub$__disabled__']) ??
|
getSubscriptionSnapshot<T>(responseCacheKey ?? '$sub$__disabled__') ??
|
||||||
fallbackData,
|
fallbackData,
|
||||||
staleTime: Infinity,
|
staleTime: Infinity,
|
||||||
enabled: subscriptionCacheKey !== null,
|
enabled: subscriptionCacheKey !== null,
|
||||||
@@ -249,7 +258,7 @@ export const useMihomoWsSubscription = <T>(
|
|||||||
}
|
}
|
||||||
if (data === undefined) return
|
if (data === undefined) return
|
||||||
const resolved = resolveNextData(data, subscriptionCacheKey)
|
const resolved = resolveNextData(data, subscriptionCacheKey)
|
||||||
setCacheData<T>([subscriptionCacheKey], resolved)
|
writeSubscriptionSnapshot(subscriptionCacheKey, resolved)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (throttleMs && throttleMs > 0) {
|
if (throttleMs && throttleMs > 0) {
|
||||||
@@ -352,10 +361,20 @@ export const useMihomoWsSubscription = <T>(
|
|||||||
|
|
||||||
const refresh = useCallback(() => {
|
const refresh = useCallback(() => {
|
||||||
if (subscriptionCacheKey) {
|
if (subscriptionCacheKey) {
|
||||||
removeCacheData([subscriptionCacheKey])
|
subscriptionSnapshots.delete(subscriptionCacheKey)
|
||||||
|
void removeCacheData([subscriptionCacheKey])
|
||||||
}
|
}
|
||||||
setDate(Date.now())
|
setDate(Date.now())
|
||||||
}, [subscriptionCacheKey, setDate])
|
}, [subscriptionCacheKey, setDate])
|
||||||
|
|
||||||
return { response, refresh, subscriptionCacheKey: responseCacheKey, wsRef }
|
const setData = useCallback(
|
||||||
|
(data: T) => {
|
||||||
|
if (responseCacheKey) {
|
||||||
|
writeSubscriptionSnapshot(responseCacheKey, data)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[responseCacheKey],
|
||||||
|
)
|
||||||
|
|
||||||
|
return { response, refresh, setData, wsRef }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,21 @@
|
|||||||
import { useCallback, useRef } from 'react'
|
import { useCallback, useRef } from 'react'
|
||||||
|
|
||||||
import { getProfiles, patchProfile, patchProfilesConfig } from '@/services/cmds'
|
import { getProfiles, patchProfile, patchProfilesConfig } from '@/services/cmds'
|
||||||
import { setCacheDataAsync, useQuery } from '@/services/query-client'
|
import {
|
||||||
|
fetchCacheData,
|
||||||
|
revalidateQuery,
|
||||||
|
setCacheData,
|
||||||
|
useQuery,
|
||||||
|
} from '@/services/query-client'
|
||||||
import { debugLog } from '@/utils/debug'
|
import { debugLog } from '@/utils/debug'
|
||||||
|
|
||||||
|
const profilesQueryKey = ['getProfiles'] as const
|
||||||
|
|
||||||
|
export const revalidateProfiles = () => revalidateQuery(profilesQueryKey)
|
||||||
|
|
||||||
|
export const fetchProfilesIntoCache = () =>
|
||||||
|
fetchCacheData(profilesQueryKey, getProfiles)
|
||||||
|
|
||||||
export const useProfiles = () => {
|
export const useProfiles = () => {
|
||||||
const {
|
const {
|
||||||
data: profiles,
|
data: profiles,
|
||||||
@@ -11,7 +23,7 @@ export const useProfiles = () => {
|
|||||||
error,
|
error,
|
||||||
isFetching: isValidating,
|
isFetching: isValidating,
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ['getProfiles'],
|
queryKey: profilesQueryKey,
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const data = await getProfiles()
|
const data = await getProfiles()
|
||||||
debugLog(
|
debugLog(
|
||||||
@@ -40,9 +52,8 @@ export const useProfiles = () => {
|
|||||||
const outcome = await patchProfilesConfig(value)
|
const outcome = await patchProfilesConfig(value)
|
||||||
|
|
||||||
if (outcome.status === 'valid') {
|
if (outcome.status === 'valid') {
|
||||||
await setCacheDataAsync<IProfilesConfig>(
|
await setCacheData<IProfilesConfig>(profilesQueryKey, (current) =>
|
||||||
['getProfiles'],
|
current ? { ...current, ...value } : current,
|
||||||
(current) => (current ? { ...current, ...value } : current),
|
|
||||||
)
|
)
|
||||||
} else if (outcome.status !== 'busy') {
|
} else if (outcome.status !== 'busy') {
|
||||||
await mutateProfiles()
|
await mutateProfiles()
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
patchVergeConfig,
|
patchVergeConfig,
|
||||||
} from '@/services/cmds'
|
} from '@/services/cmds'
|
||||||
import {
|
import {
|
||||||
getCacheData,
|
|
||||||
removeCacheData,
|
removeCacheData,
|
||||||
revalidateQueries,
|
revalidateQueries,
|
||||||
useQuery,
|
useQuery,
|
||||||
@@ -51,9 +50,7 @@ export const useSystemProxyState = () => {
|
|||||||
|
|
||||||
const toggleSystemProxy = async (enabled: boolean) => {
|
const toggleSystemProxy = async (enabled: boolean) => {
|
||||||
// Roll failed optimistic writes back to the latest confirmed state.
|
// Roll failed optimistic writes back to the latest confirmed state.
|
||||||
let confirmed =
|
let confirmed = verge?.enable_system_proxy ?? false
|
||||||
getCacheData<IVergeConfig>(['getVergeConfig'])?.enable_system_proxy ??
|
|
||||||
false
|
|
||||||
mutateVerge(
|
mutateVerge(
|
||||||
(prev) => (prev ? { ...prev, enable_system_proxy: enabled } : prev),
|
(prev) => (prev ? { ...prev, enable_system_proxy: enabled } : prev),
|
||||||
false,
|
false,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
getAppUptime,
|
||||||
getRuntimeState,
|
getRuntimeState,
|
||||||
type RunState,
|
type RunState,
|
||||||
type RunningMode,
|
type RunningMode,
|
||||||
@@ -8,6 +9,7 @@ import { useQuery } from '@/services/query-client'
|
|||||||
import { useVisibility } from './use-visibility'
|
import { useVisibility } from './use-visibility'
|
||||||
|
|
||||||
export const runStateQueryKey = ['getRuntimeState'] as const
|
export const runStateQueryKey = ['getRuntimeState'] as const
|
||||||
|
const appUptimeQueryKey = ['appUptime'] as const
|
||||||
|
|
||||||
/** Fail closed until the first snapshot so TUN never flashes as available. */
|
/** Fail closed until the first snapshot so TUN never flashes as available. */
|
||||||
const unknownRunState: RunState = {
|
const unknownRunState: RunState = {
|
||||||
@@ -52,3 +54,17 @@ export function useSystemState() {
|
|||||||
isLoading,
|
isLoading,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useAppUptime() {
|
||||||
|
const { data: uptime = 0 } = useQuery({
|
||||||
|
queryKey: appUptimeQueryKey,
|
||||||
|
queryFn: getAppUptime,
|
||||||
|
staleTime: 5000,
|
||||||
|
refetchInterval: 3000,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
refetchOnReconnect: false,
|
||||||
|
retry: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
return uptime
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useCallback } from 'react'
|
|||||||
|
|
||||||
import { getVergeConfig, patchVergeConfig } from '@/services/cmds'
|
import { getVergeConfig, patchVergeConfig } from '@/services/cmds'
|
||||||
import { getPreloadConfig, setPreloadConfig } from '@/services/preload'
|
import { getPreloadConfig, setPreloadConfig } from '@/services/preload'
|
||||||
import { getCacheData, setCacheData, useQuery } from '@/services/query-client'
|
import { setCacheData, useQuery } from '@/services/query-client'
|
||||||
|
|
||||||
export const useVerge = () => {
|
export const useVerge = () => {
|
||||||
const initialVergeConfig = getPreloadConfig()
|
const initialVergeConfig = getPreloadConfig()
|
||||||
@@ -30,13 +30,12 @@ export const useVerge = () => {
|
|||||||
void refetch()
|
void refetch()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (typeof updaterOrData === 'function') {
|
void setCacheData<IVergeConfig>(
|
||||||
const prev = getCacheData<IVergeConfig>(['getVergeConfig'])
|
['getVergeConfig'],
|
||||||
const next = updaterOrData(prev)
|
typeof updaterOrData === 'function'
|
||||||
setCacheData(['getVergeConfig'], next)
|
? (current) => updaterOrData(current ?? verge)
|
||||||
} else {
|
: updaterOrData,
|
||||||
setCacheData(['getVergeConfig'], updaterOrData)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const patchVerge = useCallback(
|
const patchVerge = useCallback(
|
||||||
|
|||||||
@@ -1,20 +1,40 @@
|
|||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
|
import { revalidateProfiles } from '@/hooks/use-profiles'
|
||||||
import { runStateQueryKey } from '@/hooks/use-system-state'
|
import { runStateQueryKey } from '@/hooks/use-system-state'
|
||||||
import type { RunState } from '@/services/cmds'
|
import type { RunState } from '@/services/cmds'
|
||||||
import { subscribeVergeEvents } from '@/services/events'
|
import { subscribeVergeEvents } from '@/services/events'
|
||||||
import { revalidateQueries, setCacheDataAsync } from '@/services/query-client'
|
import { revalidateQueries, setCacheData } from '@/services/query-client'
|
||||||
|
|
||||||
export const useLayoutEvents = (
|
export const useLayoutEvents = (
|
||||||
handleNotice: (payload: [string, string]) => void,
|
handleNotice: (payload: [string, string]) => void,
|
||||||
) => {
|
) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
let lastProfileId: string | null = null
|
||||||
|
let lastProfileUpdateTime = 0
|
||||||
|
const refreshThrottle = 800
|
||||||
|
|
||||||
const revalidateKeys = (keys: readonly string[]) => {
|
const revalidateKeys = (keys: readonly string[]) => {
|
||||||
void revalidateQueries(keys.map((key) => [key]))
|
void revalidateQueries(keys.map((key) => [key]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleProfileChanged = (newProfileId: string) => {
|
||||||
|
const now = Date.now()
|
||||||
|
if (
|
||||||
|
lastProfileId === newProfileId &&
|
||||||
|
now - lastProfileUpdateTime < refreshThrottle
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lastProfileId = newProfileId
|
||||||
|
lastProfileUpdateTime = now
|
||||||
|
void revalidateProfiles()
|
||||||
|
}
|
||||||
|
|
||||||
return subscribeVergeEvents(
|
return subscribeVergeEvents(
|
||||||
{
|
{
|
||||||
|
'profile-changed': handleProfileChanged,
|
||||||
|
'verge://refresh-profiles': () => void revalidateProfiles(),
|
||||||
'verge://refresh-clash-config': () => {
|
'verge://refresh-clash-config': () => {
|
||||||
revalidateKeys([
|
revalidateKeys([
|
||||||
'getProxyView',
|
'getProxyView',
|
||||||
@@ -36,7 +56,7 @@ export const useLayoutEvents = (
|
|||||||
},
|
},
|
||||||
// Transitions carry the full run-state snapshot, so write it directly to cache.
|
// Transitions carry the full run-state snapshot, so write it directly to cache.
|
||||||
'verge://run-state-changed': (payload) => {
|
'verge://run-state-changed': (payload) => {
|
||||||
void setCacheDataAsync<RunState>(runStateQueryKey, payload)
|
void setCacheData<RunState>(runStateQueryKey, payload)
|
||||||
},
|
},
|
||||||
'verge://notice-message': handleNotice,
|
'verge://notice-message': handleNotice,
|
||||||
},
|
},
|
||||||
|
|||||||
+4
-11
@@ -40,12 +40,11 @@ import {
|
|||||||
} from '@/components/profile/profile-viewer'
|
} from '@/components/profile/profile-viewer'
|
||||||
import { ConfigViewer } from '@/components/setting/mods/config-viewer'
|
import { ConfigViewer } from '@/components/setting/mods/config-viewer'
|
||||||
import { useListen } from '@/hooks/use-listen'
|
import { useListen } from '@/hooks/use-listen'
|
||||||
import { useProfiles } from '@/hooks/use-profiles'
|
import { fetchProfilesIntoCache, useProfiles } from '@/hooks/use-profiles'
|
||||||
import {
|
import {
|
||||||
createProfile,
|
createProfile,
|
||||||
deleteProfile,
|
deleteProfile,
|
||||||
enhanceProfiles,
|
enhanceProfiles,
|
||||||
getProfiles,
|
|
||||||
getRuntimeLogs,
|
getRuntimeLogs,
|
||||||
importProfile,
|
importProfile,
|
||||||
reorderProfile,
|
reorderProfile,
|
||||||
@@ -53,11 +52,7 @@ import {
|
|||||||
} from '@/services/cmds'
|
} from '@/services/cmds'
|
||||||
import { subscribeVergeEvents } from '@/services/events'
|
import { subscribeVergeEvents } from '@/services/events'
|
||||||
import { errorDetail, showNotice } from '@/services/notice-service'
|
import { errorDetail, showNotice } from '@/services/notice-service'
|
||||||
import {
|
import { revalidateQuery, useQuery } from '@/services/query-client'
|
||||||
fetchCacheData,
|
|
||||||
revalidateQueries,
|
|
||||||
useQuery,
|
|
||||||
} from '@/services/query-client'
|
|
||||||
import {
|
import {
|
||||||
useLoadingCache,
|
useLoadingCache,
|
||||||
useSetLoadingCache,
|
useSetLoadingCache,
|
||||||
@@ -175,9 +170,7 @@ const ProfilePage = () => {
|
|||||||
debugLog('[紧急刷新] 开始强制刷新所有数据')
|
debugLog('[紧急刷新] 开始强制刷新所有数据')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await revalidateQueries([['getProfiles'], ['getRuntimeLogs']])
|
await Promise.all([revalidateQuery(['getRuntimeLogs']), mutateProfiles()])
|
||||||
|
|
||||||
await mutateProfiles()
|
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||||
await onEnhance(false)
|
await onEnhance(false)
|
||||||
@@ -294,7 +287,7 @@ const ProfilePage = () => {
|
|||||||
|
|
||||||
console.warn(`[导入刷新] 常规刷新失败,尝试清除缓存重新获取`)
|
console.warn(`[导入刷新] 常规刷新失败,尝试清除缓存重新获取`)
|
||||||
try {
|
try {
|
||||||
await fetchCacheData(['getProfiles'], getProfiles)
|
await fetchProfilesIntoCache()
|
||||||
await onEnhance(false)
|
await onEnhance(false)
|
||||||
showNotice.error(
|
showNotice.error(
|
||||||
'profiles.page.feedback.notifications.importNeedsRefresh',
|
'profiles.page.feedback.notifications.importNeedsRefresh',
|
||||||
|
|||||||
@@ -26,10 +26,6 @@ export interface SystemContextType {
|
|||||||
systemProxyAddress: string
|
systemProxyAddress: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UptimeContextType {
|
|
||||||
uptime: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CoreDataStatusContextType {
|
export interface CoreDataStatusContextType {
|
||||||
isCoreDataPending: boolean
|
isCoreDataPending: boolean
|
||||||
}
|
}
|
||||||
@@ -49,7 +45,6 @@ export const ClashConfigContext = createContext<ClashConfigContextType | null>(
|
|||||||
null,
|
null,
|
||||||
)
|
)
|
||||||
export const SystemContext = createContext<SystemContextType | null>(null)
|
export const SystemContext = createContext<SystemContextType | null>(null)
|
||||||
export const UptimeContext = createContext<UptimeContextType | null>(null)
|
|
||||||
export const CoreDataStatusContext =
|
export const CoreDataStatusContext =
|
||||||
createContext<CoreDataStatusContextType | null>(null)
|
createContext<CoreDataStatusContextType | null>(null)
|
||||||
export const RefreshersContext = createContext<RefreshersContextType | null>(
|
export const RefreshersContext = createContext<RefreshersContextType | null>(
|
||||||
@@ -80,9 +75,6 @@ export const useClashConfigData = (): ClashConfigContextType =>
|
|||||||
export const useSystemData = (): SystemContextType =>
|
export const useSystemData = (): SystemContextType =>
|
||||||
useCtx(SystemContext, 'useSystemData')
|
useCtx(SystemContext, 'useSystemData')
|
||||||
|
|
||||||
export const useUptimeData = (): UptimeContextType =>
|
|
||||||
useCtx(UptimeContext, 'useUptimeData')
|
|
||||||
|
|
||||||
export const useAppRefreshers = (): RefreshersContextType =>
|
export const useAppRefreshers = (): RefreshersContextType =>
|
||||||
useCtx(RefreshersContext, 'useAppRefreshers')
|
useCtx(RefreshersContext, 'useAppRefreshers')
|
||||||
|
|
||||||
|
|||||||
@@ -8,14 +8,9 @@ import {
|
|||||||
import { useClashInfo, useRuntimeConfig } from '@/hooks/use-clash'
|
import { useClashInfo, useRuntimeConfig } from '@/hooks/use-clash'
|
||||||
import { runStateQueryKey } from '@/hooks/use-system-state'
|
import { runStateQueryKey } from '@/hooks/use-system-state'
|
||||||
import { useVerge } from '@/hooks/use-verge'
|
import { useVerge } from '@/hooks/use-verge'
|
||||||
import {
|
import { getProxyView, getRuntimeState, getSystemProxy } from '@/services/cmds'
|
||||||
getAppUptime,
|
|
||||||
getProxyView,
|
|
||||||
getRuntimeState,
|
|
||||||
getSystemProxy,
|
|
||||||
} from '@/services/cmds'
|
|
||||||
import { subscribeVergeEvents } from '@/services/events'
|
import { subscribeVergeEvents } from '@/services/events'
|
||||||
import { revalidateQueries, useQuery } from '@/services/query-client'
|
import { useQuery } from '@/services/query-client'
|
||||||
import { resolveDisplayedMixedPort } from '@/utils/mixed-port'
|
import { resolveDisplayedMixedPort } from '@/utils/mixed-port'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -25,7 +20,6 @@ import {
|
|||||||
RefreshersContext,
|
RefreshersContext,
|
||||||
RulesContext,
|
RulesContext,
|
||||||
SystemContext,
|
SystemContext,
|
||||||
UptimeContext,
|
|
||||||
} from './app-data-context'
|
} from './app-data-context'
|
||||||
|
|
||||||
const TQ_MIHOMO = {
|
const TQ_MIHOMO = {
|
||||||
@@ -109,14 +103,6 @@ export const AppDataProvider = ({
|
|||||||
})
|
})
|
||||||
const runningMode = runState?.mode
|
const runningMode = runState?.mode
|
||||||
|
|
||||||
const { data: uptimeData } = useQuery({
|
|
||||||
queryKey: ['appUptime'],
|
|
||||||
queryFn: getAppUptime,
|
|
||||||
...TQ_DEFAULTS,
|
|
||||||
refetchInterval: 3000,
|
|
||||||
retry: 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
const refreshProxy = useStableFn(_refetchProxyView)
|
const refreshProxy = useStableFn(_refetchProxyView)
|
||||||
const refreshClashConfig = useStableFn(_refetchClashConfig)
|
const refreshClashConfig = useStableFn(_refetchClashConfig)
|
||||||
const refreshRules = useStableFn(_refetchRules)
|
const refreshRules = useStableFn(_refetchRules)
|
||||||
@@ -124,22 +110,8 @@ export const AppDataProvider = ({
|
|||||||
const refreshRuleProviders = useStableFn(_refetchRuleProviders)
|
const refreshRuleProviders = useStableFn(_refetchRuleProviders)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let lastProfileId: string | null = null
|
|
||||||
let lastProfileUpdateTime = 0
|
|
||||||
let lastProxyUpdateTime = 0
|
let lastProxyUpdateTime = 0
|
||||||
const refreshThrottle = 800
|
const refreshThrottle = 800
|
||||||
const handleProfileChanged = (newProfileId: string) => {
|
|
||||||
const now = Date.now()
|
|
||||||
if (
|
|
||||||
lastProfileId === newProfileId &&
|
|
||||||
now - lastProfileUpdateTime < refreshThrottle
|
|
||||||
) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
lastProfileId = newProfileId
|
|
||||||
lastProfileUpdateTime = now
|
|
||||||
void revalidateQueries([['getProfiles']])
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleRefreshProxy = () => {
|
const handleRefreshProxy = () => {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
@@ -148,13 +120,7 @@ export const AppDataProvider = ({
|
|||||||
refreshProxy().catch(() => {})
|
refreshProxy().catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRefreshProfiles = () => {
|
|
||||||
void revalidateQueries([['getProfiles']])
|
|
||||||
}
|
|
||||||
|
|
||||||
return subscribeVergeEvents({
|
return subscribeVergeEvents({
|
||||||
'profile-changed': handleProfileChanged,
|
|
||||||
'verge://refresh-profiles': handleRefreshProfiles,
|
|
||||||
'verge://refresh-proxy-config': handleRefreshProxy,
|
'verge://refresh-proxy-config': handleRefreshProxy,
|
||||||
})
|
})
|
||||||
}, [refreshProxy])
|
}, [refreshProxy])
|
||||||
@@ -244,8 +210,6 @@ export const AppDataProvider = ({
|
|||||||
}
|
}
|
||||||
}, [sysproxy, runningMode, isRunningModePending, verge, displayedMixedPort])
|
}, [sysproxy, runningMode, isRunningModePending, verge, displayedMixedPort])
|
||||||
|
|
||||||
const uptimeValue = useMemo(() => ({ uptime: uptimeData || 0 }), [uptimeData])
|
|
||||||
|
|
||||||
const coreDataStatusValue = useMemo(
|
const coreDataStatusValue = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
isCoreDataPending: isProxyViewPending || isClashConfigPending,
|
isCoreDataPending: isProxyViewPending || isClashConfigPending,
|
||||||
@@ -277,13 +241,11 @@ export const AppDataProvider = ({
|
|||||||
<RulesContext value={rulesValue}>
|
<RulesContext value={rulesValue}>
|
||||||
<ClashConfigContext value={clashConfigValue}>
|
<ClashConfigContext value={clashConfigValue}>
|
||||||
<SystemContext value={systemValue}>
|
<SystemContext value={systemValue}>
|
||||||
<UptimeContext value={uptimeValue}>
|
|
||||||
<CoreDataStatusContext value={coreDataStatusValue}>
|
<CoreDataStatusContext value={coreDataStatusValue}>
|
||||||
<RefreshersContext value={refreshersValue}>
|
<RefreshersContext value={refreshersValue}>
|
||||||
{children}
|
{children}
|
||||||
</RefreshersContext>
|
</RefreshersContext>
|
||||||
</CoreDataStatusContext>
|
</CoreDataStatusContext>
|
||||||
</UptimeContext>
|
|
||||||
</SystemContext>
|
</SystemContext>
|
||||||
</ClashConfigContext>
|
</ClashConfigContext>
|
||||||
</RulesContext>
|
</RulesContext>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { unstable_serialize } from 'swr'
|
|
||||||
import useSWR, {
|
import useSWR, {
|
||||||
type SWRConfiguration,
|
type SWRConfiguration,
|
||||||
type SWRResponse,
|
type SWRResponse,
|
||||||
@@ -33,19 +32,6 @@ type QueryResult<T> = SWRResponse<T> & {
|
|||||||
refetch: () => Promise<{ data: T | undefined }>
|
refetch: () => Promise<{ data: T | undefined }>
|
||||||
}
|
}
|
||||||
|
|
||||||
const serializeQueryKey = (queryKey: QueryKey) => unstable_serialize(queryKey)
|
|
||||||
|
|
||||||
const queryCache = new Map<string, unknown>()
|
|
||||||
|
|
||||||
const setCachedData = <T>(queryKey: QueryKey, data: T | undefined) => {
|
|
||||||
const cacheKey = serializeQueryKey(queryKey)
|
|
||||||
if (data === undefined) {
|
|
||||||
queryCache.delete(cacheKey)
|
|
||||||
} else {
|
|
||||||
queryCache.set(cacheKey, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const swrConfig: SWRConfiguration = {
|
export const swrConfig: SWRConfiguration = {
|
||||||
dedupingInterval: 2000,
|
dedupingInterval: 2000,
|
||||||
errorRetryCount: 3,
|
errorRetryCount: 3,
|
||||||
@@ -53,72 +39,29 @@ export const swrConfig: SWRConfiguration = {
|
|||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getCacheData = <T>(queryKey: QueryKey): T | undefined => {
|
|
||||||
return queryCache.get(serializeQueryKey(queryKey)) as T | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateCachedData = <T>(
|
|
||||||
queryKey: QueryKey,
|
|
||||||
updaterOrData: QueryDataUpdater<T>,
|
|
||||||
) => {
|
|
||||||
const current = getCacheData<T>(queryKey)
|
|
||||||
const next =
|
|
||||||
typeof updaterOrData === 'function'
|
|
||||||
? (updaterOrData as (current: T | undefined) => T | undefined)(current)
|
|
||||||
: updaterOrData
|
|
||||||
setCachedData(queryKey, next)
|
|
||||||
return next
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setCacheData = <T>(
|
export const setCacheData = <T>(
|
||||||
queryKey: QueryKey,
|
queryKey: QueryKey,
|
||||||
updaterOrData: QueryDataUpdater<T>,
|
updaterOrData: QueryDataUpdater<T>,
|
||||||
) => {
|
) =>
|
||||||
const next = updateCachedData(queryKey, updaterOrData)
|
swrMutate<T>(queryKey, updaterOrData, {
|
||||||
void swrMutate(queryKey, next, {
|
|
||||||
populateCache: true,
|
populateCache: true,
|
||||||
revalidate: false,
|
revalidate: false,
|
||||||
})
|
})
|
||||||
return next
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setCacheDataAsync = async <T>(
|
export const revalidateQuery = (queryKey: QueryKey) => swrMutate(queryKey)
|
||||||
queryKey: QueryKey,
|
|
||||||
updaterOrData: QueryDataUpdater<T>,
|
|
||||||
) => {
|
|
||||||
const next = updateCachedData(queryKey, updaterOrData)
|
|
||||||
await swrMutate(queryKey, next, {
|
|
||||||
populateCache: true,
|
|
||||||
revalidate: false,
|
|
||||||
})
|
|
||||||
return next
|
|
||||||
}
|
|
||||||
|
|
||||||
export const revalidateQuery = async (queryKey: QueryKey) => {
|
|
||||||
const data = await swrMutate(queryKey)
|
|
||||||
if (data !== undefined) {
|
|
||||||
setCachedData(queryKey, data)
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
export const revalidateQueries = (queryKeys: readonly QueryKey[]) =>
|
export const revalidateQueries = (queryKeys: readonly QueryKey[]) =>
|
||||||
Promise.all(queryKeys.map(revalidateQuery))
|
Promise.all(queryKeys.map(revalidateQuery))
|
||||||
|
|
||||||
export const removeCacheData = (queryKey: QueryKey) => {
|
export const removeCacheData = (queryKey: QueryKey) =>
|
||||||
setCachedData(queryKey, undefined)
|
setCacheData(queryKey, undefined)
|
||||||
return swrMutate(queryKey, undefined, {
|
|
||||||
populateCache: true,
|
|
||||||
revalidate: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fetchCacheData = async <T>(
|
export const fetchCacheData = async <T>(
|
||||||
queryKey: QueryKey,
|
queryKey: QueryKey,
|
||||||
queryFn: () => Promise<T> | T,
|
queryFn: () => Promise<T> | T,
|
||||||
) => {
|
) => {
|
||||||
const data = await queryFn()
|
const data = await queryFn()
|
||||||
setCacheData(queryKey, data)
|
await setCacheData(queryKey, data)
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,11 +87,6 @@ export function useQuery<T>(options: QueryOptions<T>): QueryResult<T> {
|
|||||||
typeof fallbackDataSource === 'function'
|
typeof fallbackDataSource === 'function'
|
||||||
? (fallbackDataSource as () => T | undefined)()
|
? (fallbackDataSource as () => T | undefined)()
|
||||||
: fallbackDataSource
|
: fallbackDataSource
|
||||||
const serializedKey = serializeQueryKey(queryKey)
|
|
||||||
if (enabled && fallbackData !== undefined && !queryCache.has(serializedKey)) {
|
|
||||||
setCachedData(queryKey, fallbackData)
|
|
||||||
}
|
|
||||||
|
|
||||||
const swr = useSWR<T>(enabled ? queryKey : null, queryFn, {
|
const swr = useSWR<T>(enabled ? queryKey : null, queryFn, {
|
||||||
dedupingInterval: staleTime,
|
dedupingInterval: staleTime,
|
||||||
errorRetryCount: retry === false ? 0 : retry,
|
errorRetryCount: retry === false ? 0 : retry,
|
||||||
@@ -176,21 +114,12 @@ export function useQuery<T>(options: QueryOptions<T>): QueryResult<T> {
|
|||||||
revalidateOnReconnect: refetchOnReconnect,
|
revalidateOnReconnect: refetchOnReconnect,
|
||||||
refreshInterval: refetchInterval || 0,
|
refreshInterval: refetchInterval || 0,
|
||||||
refreshWhenHidden: refetchIntervalInBackground ?? false,
|
refreshWhenHidden: refetchIntervalInBackground ?? false,
|
||||||
onSuccess: (data) => {
|
|
||||||
setCachedData(queryKey, data)
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...swr,
|
...swr,
|
||||||
isFetching: swr.isValidating,
|
isFetching: swr.isValidating,
|
||||||
isPending: swr.isLoading,
|
isPending: swr.isLoading,
|
||||||
refetch: async () => {
|
refetch: async () => ({ data: await swr.mutate() }),
|
||||||
const data = await swr.mutate()
|
|
||||||
if (data !== undefined) {
|
|
||||||
setCachedData(queryKey, data)
|
|
||||||
}
|
|
||||||
return { data }
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user