refactor: simplify IPC layer with generic invoke wrapper

This commit is contained in:
xmk23333 2025-12-27 23:41:04 +08:00
parent 54bb819e28
commit 98c8280d48
2 changed files with 470 additions and 740 deletions

View File

@ -92,7 +92,8 @@ import {
webdavDelete,
webdavRestore,
exportLocalBackup,
importLocalBackup
importLocalBackup,
reinitScheduler
} from '../resolve/backup'
import { getInterfaces } from '../sys/interface'
import {
@ -122,24 +123,23 @@ import { startMonitor } from '../resolve/trafficMonitor'
import { closeFloatingWindow, showContextMenu, showFloatingWindow } from '../resolve/floatingWindow'
import i18next from 'i18next'
import { addProfileUpdater, removeProfileUpdater } from '../core/profileUpdater'
import { reinitScheduler } from '../resolve/backup'
import { readFile, writeFile } from 'fs/promises'
function ipcErrorWrapper<T>( // eslint-disable-next-line @typescript-eslint/no-explicit-any
fn: (...args: any[]) => Promise<T> // eslint-disable-next-line @typescript-eslint/no-explicit-any
): (...args: any[]) => Promise<T | { invokeError: unknown }> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return async (...args: any[]) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function wrapAsync<T extends (...args: any[]) => Promise<any>>(
fn: T
): (...args: Parameters<T>) => Promise<ReturnType<T> | { invokeError: unknown }> {
return async (...args) => {
try {
return await fn(...args)
} catch (e) {
if (e && typeof e === 'object') {
if ('message' in e) {
return { invokeError: e.message }
} else {
return { invokeError: JSON.stringify(e) }
}
return { invokeError: JSON.stringify(e) }
}
if (e instanceof Error || typeof e === 'string') {
if (typeof e === 'string') {
return { invokeError: e }
}
return { invokeError: 'Unknown Error' }
@ -147,240 +147,275 @@ function ipcErrorWrapper<T>( // eslint-disable-next-line @typescript-eslint/no-e
}
}
// GitHub 版本管理相关 IPC 处理程序
export async function fetchMihomoTags(
async function fetchMihomoTags(
forceRefresh = false
): Promise<{ name: string; zipball_url: string; tarball_url: string }[]> {
return await getGitHubTags('MetaCubeX', 'mihomo', forceRefresh)
}
export async function installSpecificMihomoCore(version: string): Promise<void> {
// 安装完成后清除缓存,以便下次获取最新的标签列表
async function installSpecificMihomoCore(version: string): Promise<void> {
clearVersionCache('MetaCubeX', 'mihomo')
return await installMihomoCore(version)
}
export async function clearMihomoVersionCache(): Promise<void> {
async function clearMihomoVersionCache(): Promise<void> {
clearVersionCache('MetaCubeX', 'mihomo')
}
export async function getRuleStr(id: string): Promise<string> {
const { readFile } = await import('fs/promises')
const filePath = rulePath(id)
return await readFile(filePath, 'utf-8')
async function getRuleStr(id: string): Promise<string> {
return await readFile(rulePath(id), 'utf-8')
}
export async function setRuleStr(id: string, str: string): Promise<void> {
const { writeFile } = await import('fs/promises')
const filePath = rulePath(id)
await writeFile(filePath, str, 'utf-8')
async function setRuleStr(id: string, str: string): Promise<void> {
await writeFile(rulePath(id), str, 'utf-8')
}
async function getSmartOverrideContent(): Promise<string | null> {
try {
const override = await getOverrideItem('smart-core-override')
return override?.file || null
} catch {
return null
}
}
async function changeLanguage(lng: string): Promise<void> {
await i18next.changeLanguage(lng)
ipcMain.emit('updateTrayMenu')
}
async function setTitleBarOverlay(overlay: Electron.TitleBarOverlayOptions): Promise<void> {
if (mainWindow && typeof mainWindow.setTitleBarOverlay === 'function') {
mainWindow.setTitleBarOverlay(overlay)
}
}
export function registerIpcMainHandlers(): void {
ipcMain.handle('mihomoVersion', ipcErrorWrapper(mihomoVersion))
ipcMain.handle('mihomoCloseConnection', (_e, id) => ipcErrorWrapper(mihomoCloseConnection)(id))
ipcMain.handle('mihomoCloseAllConnections', ipcErrorWrapper(mihomoCloseAllConnections))
ipcMain.handle('mihomoRules', ipcErrorWrapper(mihomoRules))
ipcMain.handle('mihomoProxies', ipcErrorWrapper(mihomoProxies))
ipcMain.handle('mihomoGroups', ipcErrorWrapper(mihomoGroups))
ipcMain.handle('mihomoProxyProviders', ipcErrorWrapper(mihomoProxyProviders))
ipcMain.handle('mihomoUpdateProxyProviders', (_e, name) =>
ipcErrorWrapper(mihomoUpdateProxyProviders)(name)
// Mihomo API
ipcMain.handle('mihomoVersion', wrapAsync(mihomoVersion))
ipcMain.handle('mihomoCloseConnection', (_e, id: string) => wrapAsync(mihomoCloseConnection)(id))
ipcMain.handle('mihomoCloseAllConnections', wrapAsync(mihomoCloseAllConnections))
ipcMain.handle('mihomoRules', wrapAsync(mihomoRules))
ipcMain.handle('mihomoProxies', wrapAsync(mihomoProxies))
ipcMain.handle('mihomoGroups', wrapAsync(mihomoGroups))
ipcMain.handle('mihomoProxyProviders', wrapAsync(mihomoProxyProviders))
ipcMain.handle('mihomoUpdateProxyProviders', (_e, name: string) =>
wrapAsync(mihomoUpdateProxyProviders)(name)
)
ipcMain.handle('mihomoRuleProviders', ipcErrorWrapper(mihomoRuleProviders))
ipcMain.handle('mihomoUpdateRuleProviders', (_e, name) =>
ipcErrorWrapper(mihomoUpdateRuleProviders)(name)
ipcMain.handle('mihomoRuleProviders', wrapAsync(mihomoRuleProviders))
ipcMain.handle('mihomoUpdateRuleProviders', (_e, name: string) =>
wrapAsync(mihomoUpdateRuleProviders)(name)
)
ipcMain.handle('mihomoChangeProxy', (_e, group, proxy) =>
ipcErrorWrapper(mihomoChangeProxy)(group, proxy)
ipcMain.handle('mihomoChangeProxy', (_e, group: string, proxy: string) =>
wrapAsync(mihomoChangeProxy)(group, proxy)
)
ipcMain.handle('mihomoUnfixedProxy', (_e, group) => ipcErrorWrapper(mihomoUnfixedProxy)(group))
ipcMain.handle('mihomoUpgradeGeo', ipcErrorWrapper(mihomoUpgradeGeo))
ipcMain.handle('mihomoUpgrade', ipcErrorWrapper(mihomoUpgrade))
ipcMain.handle('mihomoUpgradeUI', ipcErrorWrapper(mihomoUpgradeUI))
ipcMain.handle('mihomoUpgradeConfig', ipcErrorWrapper(mihomoUpgradeConfig))
ipcMain.handle('mihomoProxyDelay', (_e, proxy, url) =>
ipcErrorWrapper(mihomoProxyDelay)(proxy, url)
ipcMain.handle('mihomoUnfixedProxy', (_e, group: string) => wrapAsync(mihomoUnfixedProxy)(group))
ipcMain.handle('mihomoUpgradeGeo', wrapAsync(mihomoUpgradeGeo))
ipcMain.handle('mihomoUpgrade', wrapAsync(mihomoUpgrade))
ipcMain.handle('mihomoUpgradeUI', wrapAsync(mihomoUpgradeUI))
ipcMain.handle('mihomoUpgradeConfig', wrapAsync(mihomoUpgradeConfig))
ipcMain.handle('mihomoProxyDelay', (_e, proxy: string, url?: string) =>
wrapAsync(mihomoProxyDelay)(proxy, url)
)
ipcMain.handle('mihomoGroupDelay', (_e, group, url) =>
ipcErrorWrapper(mihomoGroupDelay)(group, url)
ipcMain.handle('mihomoGroupDelay', (_e, group: string, url?: string) =>
wrapAsync(mihomoGroupDelay)(group, url)
)
ipcMain.handle('patchMihomoConfig', (_e, patch) => ipcErrorWrapper(patchMihomoConfig)(patch))
// Smart 内核 API
ipcMain.handle('mihomoSmartGroupWeights', (_e, groupName) =>
ipcErrorWrapper(mihomoSmartGroupWeights)(groupName)
ipcMain.handle('patchMihomoConfig', (_e, patch: Partial<IMihomoConfig>) =>
wrapAsync(patchMihomoConfig)(patch)
)
ipcMain.handle('mihomoSmartFlushCache', (_e, configName) =>
ipcErrorWrapper(mihomoSmartFlushCache)(configName)
ipcMain.handle('mihomoSmartGroupWeights', (_e, groupName: string) =>
wrapAsync(mihomoSmartGroupWeights)(groupName)
)
ipcMain.handle('checkAutoRun', ipcErrorWrapper(checkAutoRun))
ipcMain.handle('enableAutoRun', ipcErrorWrapper(enableAutoRun))
ipcMain.handle('disableAutoRun', ipcErrorWrapper(disableAutoRun))
ipcMain.handle('getAppConfig', (_e, force) => ipcErrorWrapper(getAppConfig)(force))
ipcMain.handle('patchAppConfig', (_e, config) => ipcErrorWrapper(patchAppConfig)(config))
ipcMain.handle('getControledMihomoConfig', (_e, force) =>
ipcErrorWrapper(getControledMihomoConfig)(force)
)
ipcMain.handle('patchControledMihomoConfig', (_e, config) =>
ipcErrorWrapper(patchControledMihomoConfig)(config)
)
ipcMain.handle('getProfileConfig', (_e, force) => ipcErrorWrapper(getProfileConfig)(force))
ipcMain.handle('setProfileConfig', (_e, config) => ipcErrorWrapper(setProfileConfig)(config))
ipcMain.handle('getCurrentProfileItem', ipcErrorWrapper(getCurrentProfileItem))
ipcMain.handle('getProfileItem', (_e, id) => ipcErrorWrapper(getProfileItem)(id))
ipcMain.handle('getProfileStr', (_e, id) => ipcErrorWrapper(getProfileStr)(id))
ipcMain.handle('getFileStr', (_e, path) => ipcErrorWrapper(getFileStr)(path))
ipcMain.handle('setFileStr', (_e, path, str) => ipcErrorWrapper(setFileStr)(path, str))
ipcMain.handle('convertMrsRuleset', (_e, path, behavior) =>
ipcErrorWrapper(convertMrsRuleset)(path, behavior)
)
ipcMain.handle('setProfileStr', (_e, id, str) => ipcErrorWrapper(setProfileStr)(id, str))
ipcMain.handle('updateProfileItem', (_e, item) => ipcErrorWrapper(updateProfileItem)(item))
ipcMain.handle('changeCurrentProfile', (_e, id) => ipcErrorWrapper(changeCurrentProfile)(id))
ipcMain.handle('addProfileItem', (_e, item) => ipcErrorWrapper(addProfileItem)(item))
ipcMain.handle('removeProfileItem', (_e, id) => ipcErrorWrapper(removeProfileItem)(id))
ipcMain.handle('addProfileUpdater', (_e, item) => ipcErrorWrapper(addProfileUpdater)(item))
ipcMain.handle('removeProfileUpdater', (_e, id) => ipcErrorWrapper(removeProfileUpdater)(id))
ipcMain.handle('getOverrideConfig', (_e, force) => ipcErrorWrapper(getOverrideConfig)(force))
ipcMain.handle('setOverrideConfig', (_e, config) => ipcErrorWrapper(setOverrideConfig)(config))
ipcMain.handle('getOverrideItem', (_e, id) => ipcErrorWrapper(getOverrideItem)(id))
ipcMain.handle('addOverrideItem', (_e, item) => ipcErrorWrapper(addOverrideItem)(item))
ipcMain.handle('removeOverrideItem', (_e, id) => ipcErrorWrapper(removeOverrideItem)(id))
ipcMain.handle('updateOverrideItem', (_e, item) => ipcErrorWrapper(updateOverrideItem)(item))
ipcMain.handle('getOverride', (_e, id, ext) => ipcErrorWrapper(getOverride)(id, ext))
ipcMain.handle('setOverride', (_e, id, ext, str) => ipcErrorWrapper(setOverride)(id, ext, str))
ipcMain.handle('restartCore', ipcErrorWrapper(restartCore))
ipcMain.handle('startMonitor', (_e, detached) => ipcErrorWrapper(startMonitor)(detached))
ipcMain.handle('triggerSysProxy', (_e, enable) => ipcErrorWrapper(triggerSysProxy)(enable))
ipcMain.handle('manualGrantCorePermition', () => ipcErrorWrapper(manualGrantCorePermition)())
ipcMain.handle('checkAdminPrivileges', () => ipcErrorWrapper(checkAdminPrivileges)())
ipcMain.handle('restartAsAdmin', () => ipcErrorWrapper(restartAsAdmin)())
ipcMain.handle('checkMihomoCorePermissions', () => ipcErrorWrapper(checkMihomoCorePermissions)())
ipcMain.handle('requestTunPermissions', () => ipcErrorWrapper(requestTunPermissions)())
ipcMain.handle('checkHighPrivilegeCore', () => ipcErrorWrapper(checkHighPrivilegeCore)())
ipcMain.handle('showTunPermissionDialog', () => ipcErrorWrapper(showTunPermissionDialog)())
ipcMain.handle('showErrorDialog', (_, title: string, message: string) =>
ipcErrorWrapper(showErrorDialog)(title, message)
ipcMain.handle('mihomoSmartFlushCache', (_e, configName?: string) =>
wrapAsync(mihomoSmartFlushCache)(configName)
)
ipcMain.handle('checkTunPermissions', () => ipcErrorWrapper(checkTunPermissions)())
ipcMain.handle('grantTunPermissions', () => ipcErrorWrapper(grantTunPermissions)())
ipcMain.handle('getFilePath', (_e, ext) => getFilePath(ext))
ipcMain.handle('readTextFile', (_e, filePath) => ipcErrorWrapper(readTextFile)(filePath))
ipcMain.handle('getRuntimeConfigStr', ipcErrorWrapper(getRuntimeConfigStr))
ipcMain.handle('getRuntimeConfig', ipcErrorWrapper(getRuntimeConfig))
ipcMain.handle('downloadAndInstallUpdate', (_e, version) =>
ipcErrorWrapper(downloadAndInstallUpdate)(version)
// AutoRun
ipcMain.handle('checkAutoRun', wrapAsync(checkAutoRun))
ipcMain.handle('enableAutoRun', wrapAsync(enableAutoRun))
ipcMain.handle('disableAutoRun', wrapAsync(disableAutoRun))
// Config
ipcMain.handle('getAppConfig', (_e, force?: boolean) => wrapAsync(getAppConfig)(force))
ipcMain.handle('patchAppConfig', (_e, config: Partial<IAppConfig>) =>
wrapAsync(patchAppConfig)(config)
)
ipcMain.handle('getControledMihomoConfig', (_e, force?: boolean) =>
wrapAsync(getControledMihomoConfig)(force)
)
ipcMain.handle('patchControledMihomoConfig', (_e, config: Partial<IMihomoConfig>) =>
wrapAsync(patchControledMihomoConfig)(config)
)
ipcMain.handle('resetAppConfig', () => resetAppConfig())
// Profile
ipcMain.handle('getProfileConfig', (_e, force?: boolean) => wrapAsync(getProfileConfig)(force))
ipcMain.handle('setProfileConfig', (_e, config: IProfileConfig) =>
wrapAsync(setProfileConfig)(config)
)
ipcMain.handle('getCurrentProfileItem', wrapAsync(getCurrentProfileItem))
ipcMain.handle('getProfileItem', (_e, id?: string) => wrapAsync(getProfileItem)(id))
ipcMain.handle('getProfileStr', (_e, id: string) => wrapAsync(getProfileStr)(id))
ipcMain.handle('setProfileStr', (_e, id: string, str: string) =>
wrapAsync(setProfileStr)(id, str)
)
ipcMain.handle('addProfileItem', (_e, item: Partial<IProfileItem>) =>
wrapAsync(addProfileItem)(item)
)
ipcMain.handle('removeProfileItem', (_e, id: string) => wrapAsync(removeProfileItem)(id))
ipcMain.handle('updateProfileItem', (_e, item: IProfileItem) => wrapAsync(updateProfileItem)(item))
ipcMain.handle('changeCurrentProfile', (_e, id: string) => wrapAsync(changeCurrentProfile)(id))
ipcMain.handle('addProfileUpdater', (_e, item: IProfileItem) => wrapAsync(addProfileUpdater)(item))
ipcMain.handle('removeProfileUpdater', (_e, id: string) => wrapAsync(removeProfileUpdater)(id))
// Override
ipcMain.handle('getOverrideConfig', (_e, force?: boolean) => wrapAsync(getOverrideConfig)(force))
ipcMain.handle('setOverrideConfig', (_e, config: IOverrideConfig) =>
wrapAsync(setOverrideConfig)(config)
)
ipcMain.handle('getOverrideItem', (_e, id: string) => wrapAsync(getOverrideItem)(id))
ipcMain.handle('addOverrideItem', (_e, item: Partial<IOverrideItem>) =>
wrapAsync(addOverrideItem)(item)
)
ipcMain.handle('removeOverrideItem', (_e, id: string) => wrapAsync(removeOverrideItem)(id))
ipcMain.handle('updateOverrideItem', (_e, item: IOverrideItem) =>
wrapAsync(updateOverrideItem)(item)
)
ipcMain.handle('getOverride', (_e, id: string, ext: 'js' | 'yaml' | 'log') =>
wrapAsync(getOverride)(id, ext)
)
ipcMain.handle('setOverride', (_e, id: string, ext: 'js' | 'yaml', str: string) =>
wrapAsync(setOverride)(id, ext, str)
)
// File
ipcMain.handle('getFileStr', (_e, filePath: string) => wrapAsync(getFileStr)(filePath))
ipcMain.handle('setFileStr', (_e, filePath: string, str: string) =>
wrapAsync(setFileStr)(filePath, str)
)
ipcMain.handle('convertMrsRuleset', (_e, filePath: string, behavior: string) =>
wrapAsync(convertMrsRuleset)(filePath, behavior)
)
ipcMain.handle('getRuntimeConfig', wrapAsync(getRuntimeConfig))
ipcMain.handle('getRuntimeConfigStr', wrapAsync(getRuntimeConfigStr))
ipcMain.handle('getSmartOverrideContent', wrapAsync(getSmartOverrideContent))
ipcMain.handle('getRuleStr', (_e, id: string) => wrapAsync(getRuleStr)(id))
ipcMain.handle('setRuleStr', (_e, id: string, str: string) => wrapAsync(setRuleStr)(id, str))
ipcMain.handle('getFilePath', (_e, ext: string[]) => getFilePath(ext))
ipcMain.handle('readTextFile', (_e, filePath: string) => wrapAsync(readTextFile)(filePath))
ipcMain.handle('openFile', (_e, type: 'profile' | 'override', id: string, ext?: 'yaml' | 'js') =>
openFile(type, id, ext)
)
// Core
ipcMain.handle('restartCore', wrapAsync(restartCore))
ipcMain.handle('startMonitor', (_e, detached?: boolean) => wrapAsync(startMonitor)(detached))
ipcMain.handle('quitWithoutCore', wrapAsync(quitWithoutCore))
// System
ipcMain.handle('triggerSysProxy', (_e, enable: boolean) => wrapAsync(triggerSysProxy)(enable))
ipcMain.handle('checkTunPermissions', wrapAsync(checkTunPermissions))
ipcMain.handle('grantTunPermissions', wrapAsync(grantTunPermissions))
ipcMain.handle('manualGrantCorePermition', wrapAsync(manualGrantCorePermition))
ipcMain.handle('checkAdminPrivileges', wrapAsync(checkAdminPrivileges))
ipcMain.handle('restartAsAdmin', (_e, forTun?: boolean) => wrapAsync(restartAsAdmin)(forTun))
ipcMain.handle('checkMihomoCorePermissions', wrapAsync(checkMihomoCorePermissions))
ipcMain.handle('requestTunPermissions', wrapAsync(requestTunPermissions))
ipcMain.handle('checkHighPrivilegeCore', wrapAsync(checkHighPrivilegeCore))
ipcMain.handle('showTunPermissionDialog', wrapAsync(showTunPermissionDialog))
ipcMain.handle('showErrorDialog', (_e, title: string, message: string) =>
wrapAsync(showErrorDialog)(title, message)
)
ipcMain.handle('openUWPTool', wrapAsync(openUWPTool))
ipcMain.handle('setupFirewall', wrapAsync(setupFirewall))
ipcMain.handle('getInterfaces', getInterfaces)
ipcMain.handle('setNativeTheme', (_e, theme: 'system' | 'light' | 'dark') => setNativeTheme(theme))
ipcMain.handle('copyEnv', (_e, type: 'bash' | 'cmd' | 'powershell') => wrapAsync(copyEnv)(type))
// Update
ipcMain.handle('checkUpdate', wrapAsync(checkUpdate))
ipcMain.handle('downloadAndInstallUpdate', (_e, version: string) =>
wrapAsync(downloadAndInstallUpdate)(version)
)
ipcMain.handle('checkUpdate', ipcErrorWrapper(checkUpdate))
ipcMain.handle('getVersion', () => app.getVersion())
ipcMain.handle('platform', () => process.platform)
ipcMain.handle('openUWPTool', ipcErrorWrapper(openUWPTool))
ipcMain.handle('setupFirewall', ipcErrorWrapper(setupFirewall))
ipcMain.handle('getInterfaces', getInterfaces)
ipcMain.handle('webdavBackup', ipcErrorWrapper(webdavBackup))
ipcMain.handle('webdavRestore', (_e, filename) => ipcErrorWrapper(webdavRestore)(filename))
ipcMain.handle('listWebdavBackups', ipcErrorWrapper(listWebdavBackups))
ipcMain.handle('webdavDelete', (_e, filename) => ipcErrorWrapper(webdavDelete)(filename))
ipcMain.handle('reinitWebdavBackupScheduler', ipcErrorWrapper(reinitScheduler))
ipcMain.handle('exportLocalBackup', () => ipcErrorWrapper(exportLocalBackup)())
ipcMain.handle('importLocalBackup', () => ipcErrorWrapper(importLocalBackup)())
ipcMain.handle('registerShortcut', (_e, oldShortcut, newShortcut, action) =>
ipcErrorWrapper(registerShortcut)(oldShortcut, newShortcut, action)
ipcMain.handle('fetchMihomoTags', (_e, forceRefresh?: boolean) =>
wrapAsync(fetchMihomoTags)(forceRefresh)
)
ipcMain.handle('startSubStoreFrontendServer', () =>
ipcErrorWrapper(startSubStoreFrontendServer)()
ipcMain.handle('installSpecificMihomoCore', (_e, version: string) =>
wrapAsync(installSpecificMihomoCore)(version)
)
ipcMain.handle('stopSubStoreFrontendServer', () => ipcErrorWrapper(stopSubStoreFrontendServer)())
ipcMain.handle('startSubStoreBackendServer', () => ipcErrorWrapper(startSubStoreBackendServer)())
ipcMain.handle('stopSubStoreBackendServer', () => ipcErrorWrapper(stopSubStoreBackendServer)())
ipcMain.handle('downloadSubStore', () => ipcErrorWrapper(downloadSubStore)())
ipcMain.handle('clearMihomoVersionCache', wrapAsync(clearMihomoVersionCache))
// Backup
ipcMain.handle('webdavBackup', wrapAsync(webdavBackup))
ipcMain.handle('webdavRestore', (_e, filename: string) => wrapAsync(webdavRestore)(filename))
ipcMain.handle('listWebdavBackups', wrapAsync(listWebdavBackups))
ipcMain.handle('webdavDelete', (_e, filename: string) => wrapAsync(webdavDelete)(filename))
ipcMain.handle('reinitWebdavBackupScheduler', wrapAsync(reinitScheduler))
ipcMain.handle('exportLocalBackup', wrapAsync(exportLocalBackup))
ipcMain.handle('importLocalBackup', wrapAsync(importLocalBackup))
// SubStore
ipcMain.handle('startSubStoreFrontendServer', wrapAsync(startSubStoreFrontendServer))
ipcMain.handle('stopSubStoreFrontendServer', wrapAsync(stopSubStoreFrontendServer))
ipcMain.handle('startSubStoreBackendServer', wrapAsync(startSubStoreBackendServer))
ipcMain.handle('stopSubStoreBackendServer', wrapAsync(stopSubStoreBackendServer))
ipcMain.handle('downloadSubStore', wrapAsync(downloadSubStore))
ipcMain.handle('subStorePort', () => subStorePort)
ipcMain.handle('subStoreFrontendPort', () => subStoreFrontendPort)
ipcMain.handle('subStoreSubs', () => ipcErrorWrapper(subStoreSubs)())
ipcMain.handle('subStoreCollections', () => ipcErrorWrapper(subStoreCollections)())
ipcMain.handle('getGistUrl', ipcErrorWrapper(getGistUrl))
ipcMain.handle('setNativeTheme', (_e, theme) => {
setNativeTheme(theme)
})
ipcMain.handle('setTitleBarOverlay', (_e, overlay) =>
ipcErrorWrapper(async (overlay): Promise<void> => {
if (mainWindow && typeof mainWindow.setTitleBarOverlay === 'function') {
mainWindow.setTitleBarOverlay(overlay)
}
})(overlay)
ipcMain.handle('subStoreSubs', wrapAsync(subStoreSubs))
ipcMain.handle('subStoreCollections', wrapAsync(subStoreCollections))
// Theme
ipcMain.handle('resolveThemes', wrapAsync(resolveThemes))
ipcMain.handle('fetchThemes', wrapAsync(fetchThemes))
ipcMain.handle('importThemes', (_e, files: string[]) => wrapAsync(importThemes)(files))
ipcMain.handle('readTheme', (_e, theme: string) => wrapAsync(readTheme)(theme))
ipcMain.handle('writeTheme', (_e, theme: string, css: string) =>
wrapAsync(writeTheme)(theme, css)
)
ipcMain.handle('setAlwaysOnTop', (_e, alwaysOnTop) => {
mainWindow?.setAlwaysOnTop(alwaysOnTop)
})
ipcMain.handle('isAlwaysOnTop', () => {
return mainWindow?.isAlwaysOnTop()
})
ipcMain.handle('showTrayIcon', () => ipcErrorWrapper(showTrayIcon)())
ipcMain.handle('closeTrayIcon', () => ipcErrorWrapper(closeTrayIcon)())
ipcMain.handle('updateTrayIcon', () => ipcErrorWrapper(updateTrayIcon)())
ipcMain.handle('updateTrayIconImmediate', (_e, sysProxyEnabled, tunEnabled) => {
ipcMain.handle('applyTheme', (_e, theme: string) => wrapAsync(applyTheme)(theme))
// Tray
ipcMain.handle('showTrayIcon', wrapAsync(showTrayIcon))
ipcMain.handle('closeTrayIcon', wrapAsync(closeTrayIcon))
ipcMain.handle('updateTrayIcon', wrapAsync(updateTrayIcon))
ipcMain.handle('updateTrayIconImmediate', (_e, sysProxyEnabled: boolean, tunEnabled: boolean) =>
updateTrayIconImmediate(sysProxyEnabled, tunEnabled)
})
)
// Window
ipcMain.handle('showMainWindow', showMainWindow)
ipcMain.handle('closeMainWindow', closeMainWindow)
ipcMain.handle('triggerMainWindow', (_e, force) => triggerMainWindow(force))
ipcMain.handle('showFloatingWindow', () => ipcErrorWrapper(showFloatingWindow)())
ipcMain.handle('closeFloatingWindow', () => ipcErrorWrapper(closeFloatingWindow)())
ipcMain.handle('showContextMenu', () => ipcErrorWrapper(showContextMenu)())
ipcMain.handle('openFile', (_e, type, id, ext) => openFile(type, id, ext))
ipcMain.handle('openDevTools', () => {
mainWindow?.webContents.openDevTools()
})
ipcMain.handle('createHeapSnapshot', () => {
ipcMain.handle('triggerMainWindow', (_e, force?: boolean) => triggerMainWindow(force))
ipcMain.handle('showFloatingWindow', wrapAsync(showFloatingWindow))
ipcMain.handle('closeFloatingWindow', wrapAsync(closeFloatingWindow))
ipcMain.handle('showContextMenu', wrapAsync(showContextMenu))
ipcMain.handle('setTitleBarOverlay', (_e, overlay: Electron.TitleBarOverlayOptions) =>
wrapAsync(setTitleBarOverlay)(overlay)
)
ipcMain.handle('setAlwaysOnTop', (_e, alwaysOnTop: boolean) =>
mainWindow?.setAlwaysOnTop(alwaysOnTop)
)
ipcMain.handle('isAlwaysOnTop', () => mainWindow?.isAlwaysOnTop())
ipcMain.handle('openDevTools', () => mainWindow?.webContents.openDevTools())
ipcMain.handle('createHeapSnapshot', () =>
v8.writeHeapSnapshot(path.join(logDir(), `${Date.now()}.heapsnapshot`))
})
ipcMain.handle('getImageDataURL', (_e, url) => ipcErrorWrapper(getImageDataURL)(url))
ipcMain.handle('resolveThemes', () => ipcErrorWrapper(resolveThemes)())
ipcMain.handle('fetchThemes', () => ipcErrorWrapper(fetchThemes)())
ipcMain.handle('importThemes', (_e, file) => ipcErrorWrapper(importThemes)(file))
ipcMain.handle('readTheme', (_e, theme) => ipcErrorWrapper(readTheme)(theme))
ipcMain.handle('writeTheme', (_e, theme, css) => ipcErrorWrapper(writeTheme)(theme, css))
ipcMain.handle('applyTheme', (_e, theme) => ipcErrorWrapper(applyTheme)(theme))
ipcMain.handle('copyEnv', (_e, type) => ipcErrorWrapper(copyEnv)(type))
ipcMain.handle('getSmartOverrideContent', async () => {
const { getOverrideItem } = await import('../config')
try {
const override = await getOverrideItem('smart-core-override')
return override?.file || null
} catch (error) {
return null
}
})
ipcMain.handle('resetAppConfig', resetAppConfig)
)
// Shortcut
ipcMain.handle('registerShortcut', (_e, oldShortcut: string, newShortcut: string, action: string) =>
wrapAsync(registerShortcut)(oldShortcut, newShortcut, action)
)
// Misc
ipcMain.handle('getGistUrl', wrapAsync(getGistUrl))
ipcMain.handle('getImageDataURL', (_e, url: string) => wrapAsync(getImageDataURL)(url))
ipcMain.handle('relaunchApp', () => {
app.relaunch()
app.quit()
})
ipcMain.handle('quitWithoutCore', ipcErrorWrapper(quitWithoutCore))
ipcMain.handle('quitApp', () => app.quit())
// Add language change handler
ipcMain.handle('changeLanguage', async (_e, lng) => {
await i18next.changeLanguage(lng)
// 触发托盘菜单更新
ipcMain.emit('updateTrayMenu')
})
// 注册获取 Mihomo 标签的 IPC 处理程序
ipcMain.handle('fetchMihomoTags', (_e, forceRefresh) =>
ipcErrorWrapper(fetchMihomoTags)(forceRefresh)
)
// 注册安装特定版本 Mihomo 核心的 IPC 处理程序
ipcMain.handle('installSpecificMihomoCore', (_e, version) =>
ipcErrorWrapper(installSpecificMihomoCore)(version)
)
// 注册清除版本缓存的 IPC 处理程序
ipcMain.handle('clearMihomoVersionCache', () => ipcErrorWrapper(clearMihomoVersionCache)())
// 规则相关 IPC 处理程序
ipcMain.handle('getRuleStr', (_e, id) => ipcErrorWrapper(getRuleStr)(id))
ipcMain.handle('setRuleStr', (_e, id, str) => ipcErrorWrapper(setRuleStr)(id, str))
ipcMain.handle('changeLanguage', (_e, lng: string) => wrapAsync(changeLanguage)(lng))
}

View File

@ -1,568 +1,263 @@
import { TitleBarOverlayOptions } from 'electron'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function ipcErrorWrapper(response: any): any {
if (typeof response === 'object' && 'invokeError' in response) {
function checkIpcError<T>(response: T | { invokeError: unknown }): T {
if (response && typeof response === 'object' && 'invokeError' in response) {
throw response.invokeError
} else {
return response
}
}
// GitHub 版本管理相关 IPC 调用
export async function fetchMihomoTags(
forceRefresh = false
): Promise<{ name: string; zipball_url: string; tarball_url: string }[]> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('fetchMihomoTags', forceRefresh))
}
export async function installSpecificMihomoCore(version: string): Promise<void> {
return ipcErrorWrapper(
await window.electron.ipcRenderer.invoke('installSpecificMihomoCore', version)
)
}
export async function clearMihomoVersionCache(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('clearMihomoVersionCache'))
}
export async function mihomoVersion(): Promise<IMihomoVersion> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoVersion'))
}
export async function mihomoCloseConnection(id: string): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoCloseConnection', id))
}
export async function mihomoCloseAllConnections(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoCloseAllConnections'))
}
export async function mihomoRules(): Promise<IMihomoRulesInfo> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoRules'))
}
export async function mihomoProxies(): Promise<IMihomoProxies> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoProxies'))
}
export async function mihomoGroups(): Promise<IMihomoMixedGroup[]> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoGroups'))
}
export async function mihomoProxyProviders(): Promise<IMihomoProxyProviders> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoProxyProviders'))
}
export async function mihomoUpdateProxyProviders(name: string): Promise<void> {
return ipcErrorWrapper(
await window.electron.ipcRenderer.invoke('mihomoUpdateProxyProviders', name)
)
}
export async function mihomoRuleProviders(): Promise<IMihomoRuleProviders> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoRuleProviders'))
}
export async function mihomoUpdateRuleProviders(name: string): Promise<void> {
return ipcErrorWrapper(
await window.electron.ipcRenderer.invoke('mihomoUpdateRuleProviders', name)
)
}
export async function mihomoChangeProxy(group: string, proxy: string): Promise<IMihomoProxy> {
return ipcErrorWrapper(
await window.electron.ipcRenderer.invoke('mihomoChangeProxy', group, proxy)
)
}
export async function mihomoUnfixedProxy(group: string): Promise<IMihomoProxy> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoUnfixedProxy', group))
}
export async function mihomoUpgradeGeo(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoUpgradeGeo'))
}
export async function mihomoUpgrade(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoUpgrade'))
}
export async function mihomoUpgradeUI(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoUpgradeUI'))
}
export async function mihomoUpgradeConfig(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoUpgradeConfig'))
}
export async function mihomoProxyDelay(proxy: string, url?: string): Promise<IMihomoDelay> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoProxyDelay', proxy, url))
}
export async function mihomoGroupDelay(group: string, url?: string): Promise<IMihomoGroupDelay> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoGroupDelay', group, url))
}
export async function mihomoSmartGroupWeights(groupName: string): Promise<Record<string, number>> {
return ipcErrorWrapper(
await window.electron.ipcRenderer.invoke('mihomoSmartGroupWeights', groupName)
)
}
export async function mihomoSmartFlushCache(configName?: string): Promise<void> {
return ipcErrorWrapper(
await window.electron.ipcRenderer.invoke('mihomoSmartFlushCache', configName)
)
}
export async function getSmartOverrideContent(): Promise<string | null> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getSmartOverrideContent'))
}
export async function patchMihomoConfig(patch: Partial<IMihomoConfig>): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('patchMihomoConfig', patch))
}
export async function checkAutoRun(): Promise<boolean> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('checkAutoRun'))
}
export async function enableAutoRun(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('enableAutoRun'))
}
export async function disableAutoRun(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('disableAutoRun'))
}
export async function getAppConfig(force = false): Promise<IAppConfig> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getAppConfig', force))
}
export async function patchAppConfig(patch: Partial<IAppConfig>): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('patchAppConfig', patch))
}
export async function getControledMihomoConfig(force = false): Promise<Partial<IMihomoConfig>> {
return ipcErrorWrapper(
await window.electron.ipcRenderer.invoke('getControledMihomoConfig', force)
)
}
export async function patchControledMihomoConfig(patch: Partial<IMihomoConfig>): Promise<void> {
return ipcErrorWrapper(
await window.electron.ipcRenderer.invoke('patchControledMihomoConfig', patch)
)
}
export async function getProfileConfig(force = false): Promise<IProfileConfig> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getProfileConfig', force))
}
export async function setProfileConfig(config: IProfileConfig): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setProfileConfig', config))
}
export async function getCurrentProfileItem(): Promise<IProfileItem> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getCurrentProfileItem'))
}
export async function getProfileItem(id: string | undefined): Promise<IProfileItem> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getProfileItem', id))
}
export async function changeCurrentProfile(id: string): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('changeCurrentProfile', id))
}
export async function addProfileItem(item: Partial<IProfileItem>): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('addProfileItem', item))
}
export async function removeProfileItem(id: string): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('removeProfileItem', id))
}
export async function updateProfileItem(item: IProfileItem): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('updateProfileItem', item))
}
export async function addProfileUpdater(item: IProfileItem): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('addProfileUpdater', item))
}
export async function removeProfileUpdater(id: string): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('removeProfileUpdater', id))
}
export async function getProfileStr(id: string): Promise<string> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getProfileStr', id))
}
export async function getFileStr(id: string): Promise<string> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getFileStr', id))
}
export async function setFileStr(id: string, str: string): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setFileStr', id, str))
}
export async function setProfileStr(id: string, str: string): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setProfileStr', id, str))
}
export async function convertMrsRuleset(path: string, behavior: string): Promise<string> {
return ipcErrorWrapper(
await window.electron.ipcRenderer.invoke('convertMrsRuleset', path, behavior)
)
}
export async function getOverrideConfig(force = false): Promise<IOverrideConfig> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getOverrideConfig', force))
}
export async function setOverrideConfig(config: IOverrideConfig): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setOverrideConfig', config))
}
export async function getOverrideItem(id: string): Promise<IOverrideItem | undefined> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getOverrideItem', id))
}
export async function addOverrideItem(item: Partial<IOverrideItem>): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('addOverrideItem', item))
}
export async function removeOverrideItem(id: string): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('removeOverrideItem', id))
}
export async function updateOverrideItem(item: IOverrideItem): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('updateOverrideItem', item))
}
export async function getOverride(id: string, ext: 'js' | 'yaml' | 'log'): Promise<string> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getOverride', id, ext))
}
export async function setOverride(id: string, ext: 'js' | 'yaml', str: string): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setOverride', id, ext, str))
}
export async function restartCore(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('restartCore'))
}
export async function startMonitor(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('startMonitor'))
}
export async function triggerSysProxy(enable: boolean): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('triggerSysProxy', enable))
}
export async function checkTunPermissions(): Promise<boolean> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('checkTunPermissions'))
}
export async function grantTunPermissions(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('grantTunPermissions'))
}
export async function manualGrantCorePermition(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('manualGrantCorePermition'))
}
export async function checkHighPrivilegeCore(): Promise<boolean> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('checkHighPrivilegeCore'))
}
export async function checkAdminPrivileges(): Promise<boolean> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('checkAdminPrivileges'))
}
export async function restartAsAdmin(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('restartAsAdmin'))
}
export async function showTunPermissionDialog(): Promise<boolean> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('showTunPermissionDialog'))
}
export async function showErrorDialog(title: string, message: string): Promise<void> {
return ipcErrorWrapper(
await window.electron.ipcRenderer.invoke('showErrorDialog', title, message)
)
}
export async function getFilePath(ext: string[]): Promise<string[] | undefined> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getFilePath', ext))
}
export async function readTextFile(filePath: string): Promise<string> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('readTextFile', filePath))
}
export async function getRuntimeConfigStr(): Promise<string> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getRuntimeConfigStr'))
}
export async function getRuntimeConfig(): Promise<IMihomoConfig> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getRuntimeConfig'))
}
export async function checkUpdate(): Promise<IAppVersion | undefined> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('checkUpdate'))
}
export async function downloadAndInstallUpdate(version: string): Promise<void> {
return ipcErrorWrapper(
await window.electron.ipcRenderer.invoke('downloadAndInstallUpdate', version)
)
}
export async function getVersion(): Promise<string> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getVersion'))
}
export async function getPlatform(): Promise<NodeJS.Platform> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('platform'))
}
export async function openUWPTool(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('openUWPTool'))
}
export async function setupFirewall(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setupFirewall'))
}
export async function getInterfaces(): Promise<Record<string, NetworkInterfaceInfo[]>> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getInterfaces'))
}
export async function webdavBackup(): Promise<boolean> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('webdavBackup'))
}
export async function webdavRestore(filename: string): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('webdavRestore', filename))
}
export async function listWebdavBackups(): Promise<string[]> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('listWebdavBackups'))
}
export async function webdavDelete(filename: string): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('webdavDelete', filename))
}
// WebDAV 备份调度器相关 IPC 调用
export async function reinitWebdavBackupScheduler(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('reinitWebdavBackupScheduler'))
}
// 本地备份相关 IPC 调用
export async function exportLocalBackup(): Promise<boolean> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('exportLocalBackup'))
}
export async function importLocalBackup(): Promise<boolean> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('importLocalBackup'))
}
export async function setTitleBarOverlay(overlay: TitleBarOverlayOptions): Promise<void> {
try {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setTitleBarOverlay', overlay))
} catch (error) {
console.debug('setTitleBarOverlay not supported on this platform')
}
}
export async function setAlwaysOnTop(alwaysOnTop: boolean): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setAlwaysOnTop', alwaysOnTop))
}
export async function isAlwaysOnTop(): Promise<boolean> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('isAlwaysOnTop'))
}
export async function relaunchApp(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('relaunchApp'))
}
export async function quitWithoutCore(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('quitWithoutCore'))
}
export async function quitApp(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('quitApp'))
}
export async function setNativeTheme(theme: 'system' | 'light' | 'dark'): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setNativeTheme', theme))
}
export async function getGistUrl(): Promise<string> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getGistUrl'))
}
export async function startSubStoreFrontendServer(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('startSubStoreFrontendServer'))
}
export async function stopSubStoreFrontendServer(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('stopSubStoreFrontendServer'))
}
export async function startSubStoreBackendServer(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('startSubStoreBackendServer'))
}
export async function stopSubStoreBackendServer(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('stopSubStoreBackendServer'))
}
export async function downloadSubStore(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('downloadSubStore'))
}
export async function subStorePort(): Promise<number> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('subStorePort'))
}
export async function subStoreFrontendPort(): Promise<number> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('subStoreFrontendPort'))
}
export async function subStoreSubs(): Promise<ISubStoreSub[]> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('subStoreSubs'))
}
export async function subStoreCollections(): Promise<ISubStoreSub[]> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('subStoreCollections'))
}
export async function showTrayIcon(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('showTrayIcon'))
}
export async function closeTrayIcon(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('closeTrayIcon'))
}
export async function updateTrayIcon(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('updateTrayIcon'))
}
export function updateTrayIconImmediate(sysProxyEnabled: boolean, tunEnabled: boolean): void {
window.electron.ipcRenderer.invoke('updateTrayIconImmediate', sysProxyEnabled, tunEnabled)
}
export async function showMainWindow(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('showMainWindow'))
}
export async function closeMainWindow(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('closeMainWindow'))
}
export async function triggerMainWindow(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('triggerMainWindow'))
}
export async function showFloatingWindow(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('showFloatingWindow'))
}
export async function closeFloatingWindow(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('closeFloatingWindow'))
}
export async function showContextMenu(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('showContextMenu'))
}
export async function openFile(
return response as T
}
async function invoke<T>(channel: string, ...args: unknown[]): Promise<T> {
const response = await window.electron.ipcRenderer.invoke(channel, ...args)
return checkIpcError<T>(response)
}
// Mihomo API
export const mihomoVersion = (): Promise<IMihomoVersion> => invoke('mihomoVersion')
export const mihomoCloseConnection = (id: string): Promise<void> =>
invoke('mihomoCloseConnection', id)
export const mihomoCloseAllConnections = (): Promise<void> => invoke('mihomoCloseAllConnections')
export const mihomoRules = (): Promise<IMihomoRulesInfo> => invoke('mihomoRules')
export const mihomoProxies = (): Promise<IMihomoProxies> => invoke('mihomoProxies')
export const mihomoGroups = (): Promise<IMihomoMixedGroup[]> => invoke('mihomoGroups')
export const mihomoProxyProviders = (): Promise<IMihomoProxyProviders> =>
invoke('mihomoProxyProviders')
export const mihomoUpdateProxyProviders = (name: string): Promise<void> =>
invoke('mihomoUpdateProxyProviders', name)
export const mihomoRuleProviders = (): Promise<IMihomoRuleProviders> =>
invoke('mihomoRuleProviders')
export const mihomoUpdateRuleProviders = (name: string): Promise<void> =>
invoke('mihomoUpdateRuleProviders', name)
export const mihomoChangeProxy = (group: string, proxy: string): Promise<IMihomoProxy> =>
invoke('mihomoChangeProxy', group, proxy)
export const mihomoUnfixedProxy = (group: string): Promise<IMihomoProxy> =>
invoke('mihomoUnfixedProxy', group)
export const mihomoUpgradeGeo = (): Promise<void> => invoke('mihomoUpgradeGeo')
export const mihomoUpgrade = (): Promise<void> => invoke('mihomoUpgrade')
export const mihomoUpgradeUI = (): Promise<void> => invoke('mihomoUpgradeUI')
export const mihomoUpgradeConfig = (): Promise<void> => invoke('mihomoUpgradeConfig')
export const mihomoProxyDelay = (proxy: string, url?: string): Promise<IMihomoDelay> =>
invoke('mihomoProxyDelay', proxy, url)
export const mihomoGroupDelay = (group: string, url?: string): Promise<IMihomoGroupDelay> =>
invoke('mihomoGroupDelay', group, url)
export const patchMihomoConfig = (patch: Partial<IMihomoConfig>): Promise<void> =>
invoke('patchMihomoConfig', patch)
export const mihomoSmartGroupWeights = (groupName: string): Promise<Record<string, number>> =>
invoke('mihomoSmartGroupWeights', groupName)
export const mihomoSmartFlushCache = (configName?: string): Promise<void> =>
invoke('mihomoSmartFlushCache', configName)
export const getSmartOverrideContent = (): Promise<string | null> =>
invoke('getSmartOverrideContent')
// AutoRun
export const checkAutoRun = (): Promise<boolean> => invoke('checkAutoRun')
export const enableAutoRun = (): Promise<void> => invoke('enableAutoRun')
export const disableAutoRun = (): Promise<void> => invoke('disableAutoRun')
// Config
export const getAppConfig = (force = false): Promise<IAppConfig> => invoke('getAppConfig', force)
export const patchAppConfig = (patch: Partial<IAppConfig>): Promise<void> =>
invoke('patchAppConfig', patch)
export const getControledMihomoConfig = (force = false): Promise<Partial<IMihomoConfig>> =>
invoke('getControledMihomoConfig', force)
export const patchControledMihomoConfig = (patch: Partial<IMihomoConfig>): Promise<void> =>
invoke('patchControledMihomoConfig', patch)
export const resetAppConfig = (): Promise<void> => invoke('resetAppConfig')
// Profile
export const getProfileConfig = (force = false): Promise<IProfileConfig> =>
invoke('getProfileConfig', force)
export const setProfileConfig = (config: IProfileConfig): Promise<void> =>
invoke('setProfileConfig', config)
export const getCurrentProfileItem = (): Promise<IProfileItem> => invoke('getCurrentProfileItem')
export const getProfileItem = (id: string | undefined): Promise<IProfileItem> =>
invoke('getProfileItem', id)
export const getProfileStr = (id: string): Promise<string> => invoke('getProfileStr', id)
export const setProfileStr = (id: string, str: string): Promise<void> =>
invoke('setProfileStr', id, str)
export const addProfileItem = (item: Partial<IProfileItem>): Promise<void> =>
invoke('addProfileItem', item)
export const removeProfileItem = (id: string): Promise<void> => invoke('removeProfileItem', id)
export const updateProfileItem = (item: IProfileItem): Promise<void> =>
invoke('updateProfileItem', item)
export const changeCurrentProfile = (id: string): Promise<void> =>
invoke('changeCurrentProfile', id)
export const addProfileUpdater = (item: IProfileItem): Promise<void> =>
invoke('addProfileUpdater', item)
export const removeProfileUpdater = (id: string): Promise<void> =>
invoke('removeProfileUpdater', id)
// Override
export const getOverrideConfig = (force = false): Promise<IOverrideConfig> =>
invoke('getOverrideConfig', force)
export const setOverrideConfig = (config: IOverrideConfig): Promise<void> =>
invoke('setOverrideConfig', config)
export const getOverrideItem = (id: string): Promise<IOverrideItem | undefined> =>
invoke('getOverrideItem', id)
export const addOverrideItem = (item: Partial<IOverrideItem>): Promise<void> =>
invoke('addOverrideItem', item)
export const removeOverrideItem = (id: string): Promise<void> => invoke('removeOverrideItem', id)
export const updateOverrideItem = (item: IOverrideItem): Promise<void> =>
invoke('updateOverrideItem', item)
export const getOverride = (id: string, ext: 'js' | 'yaml' | 'log'): Promise<string> =>
invoke('getOverride', id, ext)
export const setOverride = (id: string, ext: 'js' | 'yaml', str: string): Promise<void> =>
invoke('setOverride', id, ext, str)
// File
export const getFileStr = (path: string): Promise<string> => invoke('getFileStr', path)
export const setFileStr = (path: string, str: string): Promise<void> =>
invoke('setFileStr', path, str)
export const convertMrsRuleset = (path: string, behavior: string): Promise<string> =>
invoke('convertMrsRuleset', path, behavior)
export const getRuntimeConfig = (): Promise<IMihomoConfig> => invoke('getRuntimeConfig')
export const getRuntimeConfigStr = (): Promise<string> => invoke('getRuntimeConfigStr')
export const getRuleStr = (id: string): Promise<string> => invoke('getRuleStr', id)
export const setRuleStr = (id: string, str: string): Promise<void> =>
invoke('setRuleStr', id, str)
export const getFilePath = (ext: string[]): Promise<string[] | undefined> =>
invoke('getFilePath', ext)
export const readTextFile = (filePath: string): Promise<string> => invoke('readTextFile', filePath)
export const openFile = (
type: 'profile' | 'override',
id: string,
ext?: 'yaml' | 'js'
): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('openFile', type, id, ext))
}
): Promise<void> => invoke('openFile', type, id, ext)
export async function openDevTools(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('openDevTools'))
}
// Core
export const restartCore = (): Promise<void> => invoke('restartCore')
export const startMonitor = (): Promise<void> => invoke('startMonitor')
export const quitWithoutCore = (): Promise<void> => invoke('quitWithoutCore')
export async function resetAppConfig(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('resetAppConfig'))
}
// System
export const triggerSysProxy = (enable: boolean): Promise<void> =>
invoke('triggerSysProxy', enable)
export const checkTunPermissions = (): Promise<boolean> => invoke('checkTunPermissions')
export const grantTunPermissions = (): Promise<void> => invoke('grantTunPermissions')
export const manualGrantCorePermition = (): Promise<void> => invoke('manualGrantCorePermition')
export const checkAdminPrivileges = (): Promise<boolean> => invoke('checkAdminPrivileges')
export const restartAsAdmin = (): Promise<void> => invoke('restartAsAdmin')
export const checkMihomoCorePermissions = (): Promise<boolean> =>
invoke('checkMihomoCorePermissions')
export const checkHighPrivilegeCore = (): Promise<boolean> => invoke('checkHighPrivilegeCore')
export const showTunPermissionDialog = (): Promise<boolean> => invoke('showTunPermissionDialog')
export const showErrorDialog = (title: string, message: string): Promise<void> =>
invoke('showErrorDialog', title, message)
export const openUWPTool = (): Promise<void> => invoke('openUWPTool')
export const setupFirewall = (): Promise<void> => invoke('setupFirewall')
export const getInterfaces = (): Promise<Record<string, NetworkInterfaceInfo[]>> =>
invoke('getInterfaces')
export const setNativeTheme = (theme: 'system' | 'light' | 'dark'): Promise<void> =>
invoke('setNativeTheme', theme)
export const copyEnv = (type: 'bash' | 'cmd' | 'powershell'): Promise<void> =>
invoke('copyEnv', type)
export async function createHeapSnapshot(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('createHeapSnapshot'))
}
// Update
export const checkUpdate = (): Promise<IAppVersion | undefined> => invoke('checkUpdate')
export const downloadAndInstallUpdate = (version: string): Promise<void> =>
invoke('downloadAndInstallUpdate', version)
export const getVersion = (): Promise<string> => invoke('getVersion')
export const getPlatform = (): Promise<NodeJS.Platform> => invoke('platform')
export const fetchMihomoTags = (
forceRefresh = false
): Promise<{ name: string; zipball_url: string; tarball_url: string }[]> =>
invoke('fetchMihomoTags', forceRefresh)
export const installSpecificMihomoCore = (version: string): Promise<void> =>
invoke('installSpecificMihomoCore', version)
export const clearMihomoVersionCache = (): Promise<void> => invoke('clearMihomoVersionCache')
export async function getImageDataURL(url: string): Promise<string> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getImageDataURL', url))
}
// Backup
export const webdavBackup = (): Promise<boolean> => invoke('webdavBackup')
export const webdavRestore = (filename: string): Promise<void> =>
invoke('webdavRestore', filename)
export const listWebdavBackups = (): Promise<string[]> => invoke('listWebdavBackups')
export const webdavDelete = (filename: string): Promise<void> => invoke('webdavDelete', filename)
export const reinitWebdavBackupScheduler = (): Promise<void> =>
invoke('reinitWebdavBackupScheduler')
export const exportLocalBackup = (): Promise<boolean> => invoke('exportLocalBackup')
export const importLocalBackup = (): Promise<boolean> => invoke('importLocalBackup')
export async function resolveThemes(): Promise<{ key: string; label: string; content: string }[]> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('resolveThemes'))
}
// SubStore
export const startSubStoreFrontendServer = (): Promise<void> =>
invoke('startSubStoreFrontendServer')
export const stopSubStoreFrontendServer = (): Promise<void> =>
invoke('stopSubStoreFrontendServer')
export const startSubStoreBackendServer = (): Promise<void> =>
invoke('startSubStoreBackendServer')
export const stopSubStoreBackendServer = (): Promise<void> => invoke('stopSubStoreBackendServer')
export const downloadSubStore = (): Promise<void> => invoke('downloadSubStore')
export const subStorePort = (): Promise<number> => invoke('subStorePort')
export const subStoreFrontendPort = (): Promise<number> => invoke('subStoreFrontendPort')
export const subStoreSubs = (): Promise<ISubStoreSub[]> => invoke('subStoreSubs')
export const subStoreCollections = (): Promise<ISubStoreSub[]> => invoke('subStoreCollections')
export async function fetchThemes(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('fetchThemes'))
}
export async function importThemes(files: string[]): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('importThemes', files))
}
export async function readTheme(theme: string): Promise<string> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('readTheme', theme))
}
export async function writeTheme(theme: string, css: string): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('writeTheme', theme, css))
}
// Theme
export const resolveThemes = (): Promise<{ key: string; label: string; content: string }[]> =>
invoke('resolveThemes')
export const fetchThemes = (): Promise<void> => invoke('fetchThemes')
export const importThemes = (files: string[]): Promise<void> => invoke('importThemes', files)
export const readTheme = (theme: string): Promise<string> => invoke('readTheme', theme)
export const writeTheme = (theme: string, css: string): Promise<void> =>
invoke('writeTheme', theme, css)
let applyThemeRunning = false
const waitList: string[] = []
const applyThemeWaitList: string[] = []
export async function applyTheme(theme: string): Promise<void> {
if (applyThemeRunning) {
waitList.push(theme)
applyThemeWaitList.push(theme)
return
}
applyThemeRunning = true
try {
return await ipcErrorWrapper(window.electron.ipcRenderer.invoke('applyTheme', theme))
await invoke<void>('applyTheme', theme)
} finally {
applyThemeRunning = false
if (waitList.length > 0) {
await applyTheme(waitList.shift() || '')
if (applyThemeWaitList.length > 0) {
const nextTheme = applyThemeWaitList.shift()
if (nextTheme) {
await applyTheme(nextTheme)
}
}
}
}
export async function registerShortcut(
// Tray
export const showTrayIcon = (): Promise<void> => invoke('showTrayIcon')
export const closeTrayIcon = (): Promise<void> => invoke('closeTrayIcon')
export const updateTrayIcon = (): Promise<void> => invoke('updateTrayIcon')
export function updateTrayIconImmediate(sysProxyEnabled: boolean, tunEnabled: boolean): void {
window.electron.ipcRenderer.invoke('updateTrayIconImmediate', sysProxyEnabled, tunEnabled)
}
// Window
export const showMainWindow = (): Promise<void> => invoke('showMainWindow')
export const closeMainWindow = (): Promise<void> => invoke('closeMainWindow')
export const triggerMainWindow = (): Promise<void> => invoke('triggerMainWindow')
export const showFloatingWindow = (): Promise<void> => invoke('showFloatingWindow')
export const closeFloatingWindow = (): Promise<void> => invoke('closeFloatingWindow')
export const showContextMenu = (): Promise<void> => invoke('showContextMenu')
export async function setTitleBarOverlay(overlay: TitleBarOverlayOptions): Promise<void> {
try {
await invoke<void>('setTitleBarOverlay', overlay)
} catch {
// Not supported on this platform
}
}
export const setAlwaysOnTop = (alwaysOnTop: boolean): Promise<void> =>
invoke('setAlwaysOnTop', alwaysOnTop)
export const isAlwaysOnTop = (): Promise<boolean> => invoke('isAlwaysOnTop')
export const openDevTools = (): Promise<void> => invoke('openDevTools')
export const createHeapSnapshot = (): Promise<void> => invoke('createHeapSnapshot')
// Shortcut
export const registerShortcut = (
oldShortcut: string,
newShortcut: string,
action: string
): Promise<boolean> {
return ipcErrorWrapper(
await window.electron.ipcRenderer.invoke('registerShortcut', oldShortcut, newShortcut, action)
)
}
): Promise<boolean> => invoke('registerShortcut', oldShortcut, newShortcut, action)
export async function copyEnv(type: 'bash' | 'cmd' | 'powershell'): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('copyEnv', type))
}
export async function getRuleStr(id: string): Promise<string> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getRuleStr', id))
}
export async function setRuleStr(id: string, str: string): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setRuleStr', id, str))
}
// Misc
export const getGistUrl = (): Promise<string> => invoke('getGistUrl')
export const getImageDataURL = (url: string): Promise<string> => invoke('getImageDataURL', url)
export const relaunchApp = (): Promise<void> => invoke('relaunchApp')
export const quitApp = (): Promise<void> => invoke('quitApp')