Files
clash-verge-rev/src/hooks/use-profiles.ts
T
Tunglies f38ceae3fb fix(profiles): restore single switch error toast and refetch after busy
patchProfiles entered the mutate funnel without errorNotice:false, so a
failed switch toasted twice. On busy the cached current profile was left
untouched, so a discarded switch draft could poison later clicks on the
target profile. Refetch on every non-valid outcome.
2026-10-17 08:10:16 +08:00

103 lines
2.8 KiB
TypeScript

import { useCallback, useEffect, useRef } from 'react'
import type { ProfilePatch, ProfileSelection } from '@/services/bindings'
import { getProfiles, patchProfile, patchProfilesConfig } from '@/services/cmds'
import { mutate } from '@/services/mutate'
import { migrateProfileTestUrls } from '@/services/proxy-test-url'
import { fetchCacheData, setCacheData, useQuery } from '@/services/query-client'
import { debugLog } from '@/utils/debug'
const profilesQueryKey = ['getProfiles'] as const
export const fetchProfilesIntoCache = () =>
fetchCacheData(profilesQueryKey, getProfiles)
export const useProfiles = () => {
const {
data: profiles,
refetch,
error,
isFetching: isValidating,
} = useQuery({
queryKey: profilesQueryKey,
queryFn: async () => {
const data = await getProfiles()
debugLog(
'[useProfiles] 配置数据更新成功,配置数量:',
data?.items?.length || 0,
)
return data
},
refetchOnWindowFocus: false,
refetchOnReconnect: false,
staleTime: 500,
retry: 3,
retryDelay: 1000,
refetchInterval: false,
})
useEffect(() => {
if (profiles) migrateProfileTestUrls(profiles)
}, [profiles])
const refetchRef = useRef(refetch)
refetchRef.current = refetch
const mutateProfiles = useCallback(async () => {
await refetchRef.current()
}, [])
const patchProfiles = useCallback(
async (value: ProfileSelection) => {
try {
const result = await mutate(() => patchProfilesConfig(value), {
id: 'patch-profiles-config',
errorNotice: false,
onFulfilled: (outcome) => {
if (outcome.status === 'valid') {
void setCacheData<IProfilesConfig>(profilesQueryKey, (current) =>
current ? { ...current, ...value } : current,
)
}
},
})
const outcome = result.value
if (outcome.status !== 'valid') {
await mutateProfiles()
}
return outcome
} catch (error) {
await mutateProfiles()
throw error
}
},
[mutateProfiles],
)
const patchCurrent = useCallback(
async (value: ProfilePatch) => {
if (profiles?.current) {
const uid = profiles.current
await mutate(() => patchProfile(uid, value), {
id: `patch-profile:${uid}`,
errorNotice: false,
})
void mutateProfiles()
}
},
[mutateProfiles, profiles],
)
return {
profiles,
current: profiles?.items?.find((p) => p && p.uid === profiles.current),
patchProfiles,
patchCurrent,
mutateProfiles,
// 新增故障检测状态
isLoading: isValidating,
error,
isStale: !profiles && !error && !isValidating, // 检测是否处于异常状态
}
}