mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-02 12:52:13 +08:00
Adds src/services/mutate.ts: one path for optimistic apply, services-layer invoke, Busy handling, failure rollback, and the error toast, with busy bookkeeping mirrored into the app store. The patch hooks (verge, clash, profiles) now route through it. Moves the remaining direct invokes (unlock ×3, DNS ×4) into cmds.ts and turns on the invoke-only-in-services architecture gate in CI.
153 lines
3.4 KiB
TypeScript
153 lines
3.4 KiB
TypeScript
import { useLockFn } from 'ahooks'
|
|
import i18n from 'i18next'
|
|
import { getVersion } from 'tauri-plugin-mihomo-api'
|
|
|
|
import {
|
|
getClashInfo,
|
|
getClashMode,
|
|
getRuntimeConfig,
|
|
patchClashConfig,
|
|
} from '@/services/cmds'
|
|
import { mutate } from '@/services/mutate'
|
|
import {
|
|
revalidateQuery,
|
|
setCacheData,
|
|
useQuery,
|
|
} from '@/services/query-client'
|
|
|
|
type MutateClashUpdater =
|
|
| ((old: IConfigData | undefined) => IConfigData | undefined)
|
|
| IConfigData
|
|
| undefined
|
|
|
|
const PORT_KEYS = [
|
|
'port',
|
|
'socks-port',
|
|
'mixed-port',
|
|
'redir-port',
|
|
'tproxy-port',
|
|
] as const
|
|
|
|
type ClashInfoPatch = Partial<
|
|
Pick<
|
|
IConfigData,
|
|
| 'port'
|
|
| 'socks-port'
|
|
| 'mixed-port'
|
|
| 'redir-port'
|
|
| 'tproxy-port'
|
|
| 'external-controller'
|
|
| 'secret'
|
|
>
|
|
>
|
|
|
|
const hasClashInfoPayload = (patch: ClashInfoPatch) =>
|
|
PORT_KEYS.some((key) => patch[key] != null) ||
|
|
patch['external-controller'] != null ||
|
|
patch.secret != null
|
|
|
|
const validatePortRange = (port: number) => {
|
|
if (port < 1000) {
|
|
throw new Error(
|
|
i18n.t(($) => $.settings.modals.clashPort.messages.portTooLow),
|
|
)
|
|
}
|
|
if (port > 65535) {
|
|
throw new Error(
|
|
i18n.t(($) => $.settings.modals.clashPort.messages.portTooHigh),
|
|
)
|
|
}
|
|
}
|
|
|
|
const validatePorts = (patch: ClashInfoPatch) => {
|
|
PORT_KEYS.forEach((key) => {
|
|
const port = patch[key]
|
|
if (!port) return
|
|
validatePortRange(port)
|
|
})
|
|
}
|
|
|
|
export const useRuntimeConfig = (shouldFetch: boolean = true) => {
|
|
return useQuery({
|
|
queryKey: ['getRuntimeConfig'],
|
|
queryFn: getRuntimeConfig,
|
|
enabled: shouldFetch,
|
|
})
|
|
}
|
|
|
|
// Fault-tolerant fallback for the current proxy mode, read straight from the
|
|
// saved clash config on the backend (bypasses the strict BaseConfig path).
|
|
export const useClashMode = (shouldFetch: boolean = true) => {
|
|
return useQuery({
|
|
queryKey: ['getClashMode'],
|
|
queryFn: getClashMode,
|
|
enabled: shouldFetch,
|
|
})
|
|
}
|
|
|
|
export const useClash = () => {
|
|
const { data: clash, refetch } = useRuntimeConfig()
|
|
|
|
const { data: versionData, refetch: mutateVersion } = useQuery({
|
|
queryKey: ['getVersion'],
|
|
queryFn: getVersion,
|
|
})
|
|
|
|
const mutateClash = (updater?: MutateClashUpdater, revalidate?: boolean) => {
|
|
if (updater === undefined) {
|
|
return refetch()
|
|
}
|
|
void setCacheData<IConfigData>(['getRuntimeConfig'], updater)
|
|
if (revalidate !== false) {
|
|
return refetch()
|
|
}
|
|
return Promise.resolve()
|
|
}
|
|
|
|
const patchClash = useLockFn(async (patch: Partial<IConfigData>) => {
|
|
await mutate(() => patchClashConfig(patch), {
|
|
id: 'patch-clash-config',
|
|
revalidate: [['getRuntimeConfig']],
|
|
})
|
|
})
|
|
|
|
const version = versionData?.meta
|
|
? `${versionData.version} Mihomo`
|
|
: versionData?.version || '-'
|
|
|
|
return {
|
|
clash,
|
|
version,
|
|
mutateClash,
|
|
mutateVersion,
|
|
patchClash,
|
|
}
|
|
}
|
|
|
|
export const useClashInfo = () => {
|
|
const { data: clashInfo, refetch: mutateInfo } = useQuery({
|
|
queryKey: ['getClashInfo'],
|
|
queryFn: getClashInfo,
|
|
})
|
|
|
|
const patchInfo = useLockFn(async (patch: ClashInfoPatch) => {
|
|
if (!hasClashInfoPayload(patch)) return
|
|
|
|
validatePorts(patch)
|
|
|
|
await mutate(() => patchClashConfig(patch), {
|
|
id: 'patch-clash-info',
|
|
revalidate: [['getClashInfo'], ['getClashConfig']],
|
|
})
|
|
})
|
|
|
|
const invalidateClashConfig = () => revalidateQuery(['getClashConfig'])
|
|
|
|
return {
|
|
clashInfo,
|
|
mutateInfo,
|
|
patchInfo,
|
|
invalidateClashConfig,
|
|
}
|
|
}
|