Files
clash-verge-rev/src/services/cmds.ts
T
wonfen ff1ce92fac feat(dns): remember override state per profile
Save the DNS override switch and session confirmation per profile so automatic protection only disables the affected profile. Preserve the legacy switch as the initial preference and show the profile scope beside the settings gear and in the DNS editor.

Refs #7993
2026-09-22 10:54:55 +08:00

526 lines
13 KiB
TypeScript

import { invoke } from '@tauri-apps/api/core'
import dayjs from 'dayjs'
import type { CommandFailure } from '@/services/notice-service'
import { showNotice } from '@/services/notice-service'
import type { ProxyViewV1 } from '@/types/proxy-view'
import { debugLog } from '@/utils/debug'
export async function copyClashEnv() {
return invoke<void>('copy_clash_env')
}
export async function getProfiles() {
return invoke<IProfilesConfig>('get_profiles')
}
export async function enhanceProfiles() {
return (
(await invoke<ValidationOutcome>('enhance_profiles')).status === 'valid'
)
}
export async function patchProfilesConfig(profiles: IProfilesConfig) {
return invoke<ValidationOutcome>('patch_profiles_config', { profiles })
}
export async function createProfile(
item: Partial<IProfileItem>,
fileData?: string | null,
) {
return invoke<void>('create_profile', { item, fileData })
}
export async function viewProfile(index: string) {
return invoke<void>('view_profile', { index })
}
export async function readProfileFile(index: string) {
return invoke<string>('read_profile_file', { index })
}
export async function saveProfileFile(index: string, fileData: string) {
return (
(
await invoke<ValidationOutcome>('save_profile_file', {
index,
fileData,
})
).status === 'valid'
)
}
export async function importProfile(url: string, option?: IProfileOption) {
return invoke<void>('import_profile', {
url,
option: option || { with_proxy: true },
})
}
export async function reorderProfile(activeId: string, overId: string) {
return invoke<void>('reorder_profile', {
activeId,
overId,
})
}
export async function updateProfile(index: string, option?: IProfileOption) {
return invoke<void>('update_profile', { index, option })
}
export async function deleteProfile(index: string) {
return invoke<void>('delete_profile', { index })
}
export async function patchProfile(
index: string,
profile: Partial<IProfileItem>,
) {
return invoke<void>('patch_profile', { index, profile })
}
export async function getClashInfo() {
return invoke<IClashInfo | null>('get_clash_info')
}
// Fallback mode read independent of strict mihomo `/configs` deserialization.
export async function getClashMode() {
return invoke<string | null>('get_clash_mode')
}
export async function getRuntimeConfig() {
return invoke<IConfigData | null>('get_runtime_config')
}
export async function getRuntimeYaml() {
return invoke<string | null>('get_runtime_yaml')
}
export async function getRuntimeLogs() {
return invoke<Record<string, [string, string][]>>('get_runtime_logs')
}
export async function getRuntimeProxyChainConfig(proxyChainExitNode: string) {
return invoke<string>('get_runtime_proxy_chain_config', {
proxyChainExitNode,
})
}
export async function updateProxyChainConfigInRuntime(proxyChainConfig: any) {
return invoke<void>('update_proxy_chain_config_in_runtime', {
proxyChainConfig,
})
}
export async function patchClashConfig(payload: Partial<IConfigData>) {
return invoke<void>('patch_clash_config', { payload })
}
export async function patchClashMode(payload: string) {
return invoke<void>('patch_clash_mode', { payload })
}
export async function syncTrayProxySelection() {
return invoke<void>('sync_tray_proxy_selection')
}
/** Sends one selection pair so the backend can merge against current profile state. */
export async function recordSelectedNode(groupName: string, node: string) {
return invoke<void>('record_selected_node', { groupName, node })
}
export async function forgetSelectedNode(groupName: string) {
return invoke<void>('forget_selected_node', { groupName })
}
export async function getProxyView(): Promise<ProxyViewV1> {
const view = await invoke<ProxyViewV1>('get_proxy_view')
if (view.schemaVersion !== 1) {
throw new Error('Unsupported proxy view schema: ' + view.schemaVersion)
}
return view
}
export async function getClashLogs() {
const regex = /time="(.+?)"\s+level=(.+?)\s+msg="(.+?)"/
const newRegex = /(.+?)\s+(.+?)\s+(.+)/
const logs = await invoke<string[]>('get_clash_logs')
return logs.reduce<ILogItem[]>((acc, log) => {
const result = log.match(regex)
if (result) {
const [_, _time, type, payload] = result
const time = dayjs(_time).format('MM-DD HH:mm:ss')
acc.push({ time, type, payload })
return acc
}
const result2 = log.match(newRegex)
if (result2) {
const [_, time, type, payload] = result2
acc.push({ time, type, payload })
}
return acc
}, [])
}
export async function getVergeConfig() {
return invoke<IVergeConfig>('get_verge_config')
}
export async function patchVergeConfig(payload: IVergeConfig) {
return invoke<void>('patch_verge_config', { payload })
}
export async function setDnsOverride(
profileUid: string,
enabled: boolean,
confirmation?: string,
) {
return invoke<
{ status: 'applied' } | { status: 'confirmation_required'; source: string }
>('set_dns_override', { profileUid, enabled, confirmation })
}
export async function takeDnsOverrideNotice() {
return invoke<boolean>('take_dns_override_notice')
}
export async function takeServiceFallbackNotice() {
return invoke<boolean>('take_service_fallback_notice')
}
export async function takeServiceRepairNotice() {
return invoke<boolean>('take_service_repair_notice')
}
export async function takeDiscardedKeysNotice() {
return invoke<string | null>('take_discarded_keys_notice')
}
export async function getSystemProxy() {
return invoke<{
enable: boolean
server: string
bypass: string
}>('get_sys_proxy')
}
export async function getAutotemProxy() {
try {
debugLog('[API] 开始调用 get_auto_proxy')
const result = await invoke<{
enable: boolean
url: string
}>('get_auto_proxy')
debugLog('[API] get_auto_proxy 调用成功:', result)
return result
} catch (error) {
console.error('[API] get_auto_proxy 调用失败:', error)
return {
enable: false,
url: '',
}
}
}
export async function getEmbeddedServerPort() {
return invoke<number>('get_embedded_server_port')
}
export async function changeClashCore(clashCore: string) {
return invoke<CommandFailure | null>('change_clash_core', { clashCore })
}
export async function restartCore() {
return invoke<void>('restart_core')
}
export interface CoreUpgradeReport {
/** False when the managed core was already at the latest version. */
upgraded: boolean
from: string
to: string
}
export async function upgradeClashCore(force = false) {
return invoke<CoreUpgradeReport>('upgrade_clash_core', { force })
}
export async function restartApp() {
return invoke<void>('restart_app')
}
export async function getAppDir() {
return invoke<string>('get_app_dir')
}
export async function openAppDir() {
return invoke<void>('open_app_dir').catch((err) => showNotice.error(err))
}
export async function openCoreDir() {
return invoke<void>('open_core_dir').catch((err) => showNotice.error(err))
}
export async function openLogsDir() {
return invoke<void>('open_logs_dir').catch((err) => showNotice.error(err))
}
export async function syncRuntimeProviders() {
return invoke<void>('sync_runtime_providers').catch((err) => {
console.warn('failed to queue the provider cache sync', err)
})
}
export async function cmdTestDelay(url: string) {
return invoke<number>('test_delay', { url })
}
export async function invoke_uwp_tool() {
return invoke<void>('invoke_uwp_tool').catch((err) =>
showNotice.error(err, 1500),
)
}
export async function openDevTools() {
return invoke('open_devtools')
}
export async function exitApp() {
return invoke('exit_app')
}
export async function exportDiagnosticInfo() {
return invoke('export_diagnostic_info')
}
interface SystemInfo {
system_name: string
system_version: string
system_kernel_version: string
system_arch: string
app_version: string
app_core_mode: string
app_is_admin: boolean
}
export async function getSystemInfo() {
return invoke<SystemInfo>('get_system_info')
}
export async function copyIconFile(
path: string,
name: 'common' | 'sysproxy' | 'tun',
) {
const key = `icon_${name}_update_time`
const previousTime = localStorage.getItem(key) || ''
const currentTime = String(Date.now())
localStorage.setItem(key, currentTime)
const iconInfo = {
name,
previous_t: previousTime,
current_t: currentTime,
}
return invoke<void>('copy_icon_file', { path, iconInfo })
}
export async function downloadIconCache(url: string, name: string) {
return invoke<string>('download_icon_cache', { url, name })
}
export async function getNetworkInterfaces() {
return invoke<string[]>('get_network_interfaces')
}
export async function getSystemHostname() {
return invoke<string>('get_system_hostname')
}
export async function getNetworkInterfacesInfo() {
return invoke<INetworkInterface[]>('get_network_interfaces_info')
}
export async function createWebdavBackup() {
return invoke<void>('create_webdav_backup')
}
export async function createLocalBackup() {
return invoke<void>('create_local_backup')
}
export async function deleteWebdavBackup(filename: string) {
return invoke<void>('delete_webdav_backup', { filename })
}
export async function deleteLocalBackup(filename: string) {
return invoke<void>('delete_local_backup', { filename })
}
export async function restoreWebDavBackup(filename: string) {
return invoke<void>('restore_webdav_backup', { filename })
}
export async function restoreLocalBackup(filename: string) {
return invoke<void>('restore_local_backup', { filename })
}
export async function importLocalBackup(source: string) {
return invoke<string>('import_local_backup', { source })
}
export async function exportLocalBackup(filename: string, destination: string) {
return invoke<void>('export_local_backup', { filename, destination })
}
export async function saveWebdavConfig(
url: string,
username: string,
password: string,
) {
return invoke<void>('save_webdav_config', {
url,
username,
password,
})
}
export async function listWebDavBackup() {
const list: IWebDavFile[] = await invoke<IWebDavFile[]>('list_webdav_backup')
list.forEach((item) => {
item.filename = item.href.split('/').pop() as string
})
return list
}
export async function listLocalBackup() {
return invoke<ILocalBackupFile[]>('list_local_backup')
}
export type RunningMode = 'Service' | 'Sidecar' | 'NotRunning'
type ServiceHealth =
| 'unknown'
| 'ready'
| 'notInstalled'
| 'versionMismatch'
| 'unavailable'
type PendingServiceAction =
| 'install'
| 'uninstall'
| 'reinstall'
| 'forceReinstall'
/** Consistent core/service snapshot with backend-derived availability flags. */
export interface RunState {
mode: RunningMode
service: ServiceHealth
serviceUnavailableReason: string | null
pendingAction: PendingServiceAction | null
sidecarAllowed: boolean
isAdmin: boolean
opInFlight: boolean
serviceUsable: boolean
tunCapable: boolean
serviceNeedsAttention: boolean
}
export const getRuntimeState = async () => {
return invoke<RunState>('get_runtime_state')
}
export type FailedOperation =
| 'systemProxyEnable'
| 'systemProxyDisable'
| 'systemProxyRestore'
| 'systemProxyGuard'
export interface PendingFailure {
code: string
detail: string
operation: FailedOperation
sequence: number
}
export const getPendingFailures = async () => {
return invoke<PendingFailure[]>('get_pending_failures')
}
export const getAppUptime = async () => {
return invoke<number>('get_app_uptime')
}
export const installService = async () => {
return invoke<void>('install_service')
}
export const uninstallService = async () => {
return invoke<void>('uninstall_service')
}
export const reinstallService = async () => {
return invoke<void>('reinstall_service')
}
export const repairService = async () => {
return invoke<void>('repair_service')
}
export const continueWithSidecar = async () => {
return invoke<void>('continue_with_sidecar')
}
export const entry_lightweight_mode = async () => {
return invoke<void>('entry_lightweight_mode')
}
export async function getNextUpdateTime(uid: string) {
return invoke<number | null>('get_next_update_time', { uid })
}
interface ToggleableProxyPort {
enabled: boolean
port: number
}
export interface ProxyPortSettings {
mixedPort: number
socks: ToggleableProxyPort
http: ToggleableProxyPort
redir: ToggleableProxyPort
tproxy: ToggleableProxyPort
}
export type ListenerTransport = 'tcp' | 'udp'
export interface ListenerProbe {
address: string
transports: ListenerTransport[]
}
export type ListenerProbeOutcome =
| { status: 'available' }
| {
status: 'conflict'
port: number
transport: ListenerTransport
}
| { status: 'invalid'; message: string }
| { status: 'indeterminate'; message: string }
export type SaveProxyPortsOutcome =
| { status: 'saved' }
| { status: 'conflict'; port: number; transport: ListenerTransport }
export const probeListener = async (request: ListenerProbe) => {
return invoke<ListenerProbeOutcome>('probe_listener', { request })
}
export const saveProxyPorts = async (settings: ProxyPortSettings) => {
return invoke<SaveProxyPortsOutcome>('save_proxy_ports', { settings })
}