mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2026-02-11 04:00:32 +08:00
refactor: improve preload security with IPC channel whitelist and fix dependencies
This commit is contained in:
parent
2467306903
commit
a159974142
@ -40,7 +40,10 @@ module.exports = [
|
|||||||
{
|
{
|
||||||
files: ['**/*.{ts,tsx}'],
|
files: ['**/*.{ts,tsx}'],
|
||||||
rules: {
|
rules: {
|
||||||
'@typescript-eslint/no-unused-vars': 0,
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'warn',
|
||||||
|
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' }
|
||||||
|
],
|
||||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
'@typescript-eslint/no-explicit-any': 'warn'
|
'@typescript-eslint/no-explicit-any': 'warn'
|
||||||
}
|
}
|
||||||
|
|||||||
13
package.json
13
package.json
@ -30,23 +30,16 @@
|
|||||||
"build:linux:dev": "npm run prepare:dev && electron-vite build && electron-builder --publish never --linux"
|
"build:linux:dev": "npm run prepare:dev && electron-vite build && electron-builder --publish never --linux"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron-toolkit/preload": "^3.0.2",
|
|
||||||
"@electron-toolkit/utils": "^4.0.0",
|
"@electron-toolkit/utils": "^4.0.0",
|
||||||
"@heroui/react": "^2.8.5",
|
|
||||||
"@mihomo-party/sysproxy": "^2.0.8",
|
"@mihomo-party/sysproxy": "^2.0.8",
|
||||||
"@types/crypto-js": "^4.2.2",
|
|
||||||
"adm-zip": "^0.5.16",
|
"adm-zip": "^0.5.16",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
"chart.js": "^4.5.1",
|
|
||||||
"chokidar": "^4.0.3",
|
"chokidar": "^4.0.3",
|
||||||
"croner": "^9.1.0",
|
"croner": "^9.1.0",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"dayjs": "^1.11.19",
|
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"i18next": "^25.6.2",
|
"i18next": "^25.6.2",
|
||||||
"iconv-lite": "^0.6.3",
|
"iconv-lite": "^0.6.3",
|
||||||
"react-chartjs-2": "^5.3.1",
|
|
||||||
"react-i18next": "^15.7.4",
|
|
||||||
"webdav": "^5.8.0",
|
"webdav": "^5.8.0",
|
||||||
"ws": "^8.18.3",
|
"ws": "^8.18.3",
|
||||||
"yaml": "^2.8.1"
|
"yaml": "^2.8.1"
|
||||||
@ -58,8 +51,10 @@
|
|||||||
"@electron-toolkit/eslint-config-prettier": "^3.0.0",
|
"@electron-toolkit/eslint-config-prettier": "^3.0.0",
|
||||||
"@electron-toolkit/eslint-config-ts": "^3.1.0",
|
"@electron-toolkit/eslint-config-ts": "^3.1.0",
|
||||||
"@electron-toolkit/tsconfig": "^1.0.1",
|
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||||
|
"@heroui/react": "^2.8.5",
|
||||||
"@tailwindcss/vite": "^4.1.17",
|
"@tailwindcss/vite": "^4.1.17",
|
||||||
"@types/adm-zip": "^0.5.7",
|
"@types/adm-zip": "^0.5.7",
|
||||||
|
"@types/crypto-js": "^4.2.2",
|
||||||
"@types/express": "^5.0.5",
|
"@types/express": "^5.0.5",
|
||||||
"@types/node": "^24.10.1",
|
"@types/node": "^24.10.1",
|
||||||
"@types/pubsub-js": "^1.8.6",
|
"@types/pubsub-js": "^1.8.6",
|
||||||
@ -67,7 +62,9 @@
|
|||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
"@vitejs/plugin-react": "^4.7.0",
|
"@vitejs/plugin-react": "^4.7.0",
|
||||||
|
"chart.js": "^4.5.1",
|
||||||
"cron-validator": "^1.4.0",
|
"cron-validator": "^1.4.0",
|
||||||
|
"dayjs": "^1.11.19",
|
||||||
"driver.js": "^1.3.6",
|
"driver.js": "^1.3.6",
|
||||||
"electron": "^37.10.0",
|
"electron": "^37.10.0",
|
||||||
"electron-builder": "26.0.12",
|
"electron-builder": "26.0.12",
|
||||||
@ -86,8 +83,10 @@
|
|||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"pubsub-js": "^1.9.5",
|
"pubsub-js": "^1.9.5",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
|
"react-chartjs-2": "^5.3.1",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"react-error-boundary": "^6.0.0",
|
"react-error-boundary": "^6.0.0",
|
||||||
|
"react-i18next": "^15.7.4",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"react-monaco-editor": "^0.59.0",
|
"react-monaco-editor": "^0.59.0",
|
||||||
|
|||||||
@ -562,7 +562,7 @@ async function waitForCoreReady(): Promise<void> {
|
|||||||
await axios.get('/')
|
await axios.get('/')
|
||||||
await managerLogger.info(`Core ready after ${i + 1} attempts (${(i + 1) * retryInterval}ms)`)
|
await managerLogger.info(`Core ready after ${i + 1} attempts (${(i + 1) * retryInterval}ms)`)
|
||||||
return
|
return
|
||||||
} catch (error) {
|
} catch {
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
await managerLogger.info('Waiting for core to be ready...')
|
await managerLogger.info('Waiting for core to be ready...')
|
||||||
}
|
}
|
||||||
@ -791,7 +791,7 @@ async function checkHighPrivilegeMihomoProcess(): Promise<boolean> {
|
|||||||
if (processJson.Name.includes('mihomo') && processJson.Path === null) {
|
if (processJson.Name.includes('mihomo') && processJson.Path === null) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
await managerLogger.info(
|
await managerLogger.info(
|
||||||
`Cannot get info for process ${pid}, might be high privilege`
|
`Cannot get info for process ${pid}, might be high privilege`
|
||||||
)
|
)
|
||||||
@ -835,7 +835,7 @@ async function checkHighPrivilegeMihomoProcess(): Promise<boolean> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ export async function initProfileUpdater(): Promise<void> {
|
|||||||
async () => {
|
async () => {
|
||||||
try {
|
try {
|
||||||
await addProfileItem(item)
|
await addProfileItem(item)
|
||||||
} catch (e) {
|
} catch {
|
||||||
/* ignore */
|
/* ignore */
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -26,7 +26,7 @@ export async function initProfileUpdater(): Promise<void> {
|
|||||||
intervalPool[item.id] = new Cron(item.interval, async () => {
|
intervalPool[item.id] = new Cron(item.interval, async () => {
|
||||||
try {
|
try {
|
||||||
await addProfileItem(item)
|
await addProfileItem(item)
|
||||||
} catch (e) {
|
} catch {
|
||||||
/* ignore */
|
/* ignore */
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -34,7 +34,7 @@ export async function initProfileUpdater(): Promise<void> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await addProfileItem(item)
|
await addProfileItem(item)
|
||||||
} catch (e) {
|
} catch {
|
||||||
/* ignore */
|
/* ignore */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -46,7 +46,7 @@ export async function initProfileUpdater(): Promise<void> {
|
|||||||
async () => {
|
async () => {
|
||||||
try {
|
try {
|
||||||
await addProfileItem(currentItem)
|
await addProfileItem(currentItem)
|
||||||
} catch (e) {
|
} catch {
|
||||||
/* ignore */
|
/* ignore */
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -57,7 +57,7 @@ export async function initProfileUpdater(): Promise<void> {
|
|||||||
async () => {
|
async () => {
|
||||||
try {
|
try {
|
||||||
await addProfileItem(currentItem)
|
await addProfileItem(currentItem)
|
||||||
} catch (e) {
|
} catch {
|
||||||
/* ignore */
|
/* ignore */
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -67,7 +67,7 @@ export async function initProfileUpdater(): Promise<void> {
|
|||||||
intervalPool[currentItem.id] = new Cron(currentItem.interval, async () => {
|
intervalPool[currentItem.id] = new Cron(currentItem.interval, async () => {
|
||||||
try {
|
try {
|
||||||
await addProfileItem(currentItem)
|
await addProfileItem(currentItem)
|
||||||
} catch (e) {
|
} catch {
|
||||||
/* ignore */
|
/* ignore */
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -75,7 +75,7 @@ export async function initProfileUpdater(): Promise<void> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await addProfileItem(currentItem)
|
await addProfileItem(currentItem)
|
||||||
} catch (e) {
|
} catch {
|
||||||
/* ignore */
|
/* ignore */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,7 +96,7 @@ export async function addProfileUpdater(item: IProfileItem): Promise<void> {
|
|||||||
async () => {
|
async () => {
|
||||||
try {
|
try {
|
||||||
await addProfileItem(item)
|
await addProfileItem(item)
|
||||||
} catch (e) {
|
} catch {
|
||||||
/* ignore */
|
/* ignore */
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -106,7 +106,7 @@ export async function addProfileUpdater(item: IProfileItem): Promise<void> {
|
|||||||
intervalPool[item.id] = new Cron(item.interval, async () => {
|
intervalPool[item.id] = new Cron(item.interval, async () => {
|
||||||
try {
|
try {
|
||||||
await addProfileItem(item)
|
await addProfileItem(item)
|
||||||
} catch (e) {
|
} catch {
|
||||||
/* ignore */
|
/* ignore */
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -120,7 +120,7 @@ export const buildContextMenu = async (): Promise<Menu> => {
|
|||||||
groupsMenu = groupItems
|
groupsMenu = groupItems
|
||||||
groupsMenu.unshift({ type: 'separator' })
|
groupsMenu.unshift({ type: 'separator' })
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
// 避免出错时无法创建托盘菜单
|
// 避免出错时无法创建托盘菜单
|
||||||
}
|
}
|
||||||
@ -206,7 +206,7 @@ export const buildContextMenu = async (): Promise<Menu> => {
|
|||||||
await patchAppConfig({ sysProxy: { enable } })
|
await patchAppConfig({ sysProxy: { enable } })
|
||||||
mainWindow?.webContents.send('appConfigUpdated')
|
mainWindow?.webContents.send('appConfigUpdated')
|
||||||
floatingWindow?.webContents.send('appConfigUpdated')
|
floatingWindow?.webContents.send('appConfigUpdated')
|
||||||
} catch (e) {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
} finally {
|
} finally {
|
||||||
ipcMain.emit('updateTrayMenu')
|
ipcMain.emit('updateTrayMenu')
|
||||||
|
|||||||
@ -61,7 +61,7 @@ export async function checkAutoRun(): Promise<boolean> {
|
|||||||
`chcp 437 && %SystemRoot%\\System32\\schtasks.exe /query /tn "${appName}"`
|
`chcp 437 && %SystemRoot%\\System32\\schtasks.exe /query /tn "${appName}"`
|
||||||
)
|
)
|
||||||
return stdout.includes(appName)
|
return stdout.includes(appName)
|
||||||
} catch (e) {
|
} catch {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,7 +96,7 @@ export async function enableAutoRun(): Promise<void> {
|
|||||||
await execPromise(
|
await execPromise(
|
||||||
`powershell -NoProfile -Command "Start-Process schtasks -Verb RunAs -ArgumentList '/create', '/tn', '${appName}', '/xml', '${taskFilePath}', '/f' -WindowStyle Hidden"`
|
`powershell -NoProfile -Command "Start-Process schtasks -Verb RunAs -ArgumentList '/create', '/tn', '${appName}', '/xml', '${taskFilePath}', '/f' -WindowStyle Hidden"`
|
||||||
)
|
)
|
||||||
} catch (e) {
|
} catch {
|
||||||
await managerLogger.info('Maybe the user rejected the UAC dialog?')
|
await managerLogger.info('Maybe the user rejected the UAC dialog?')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -144,7 +144,7 @@ export async function disableAutoRun(): Promise<void> {
|
|||||||
await execPromise(
|
await execPromise(
|
||||||
`powershell -NoProfile -Command "Start-Process schtasks -Verb RunAs -ArgumentList '/delete', '/tn', '${appName}', '/f' -WindowStyle Hidden"`
|
`powershell -NoProfile -Command "Start-Process schtasks -Verb RunAs -ArgumentList '/delete', '/tn', '${appName}', '/f' -WindowStyle Hidden"`
|
||||||
)
|
)
|
||||||
} catch (e) {
|
} catch {
|
||||||
await managerLogger.info('Maybe the user rejected the UAC dialog?')
|
await managerLogger.info('Maybe the user rejected the UAC dialog?')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
src/preload/index.d.ts
vendored
18
src/preload/index.d.ts
vendored
@ -1,6 +1,22 @@
|
|||||||
import { ElectronAPI } from '@electron-toolkit/preload'
|
|
||||||
import { webUtils } from 'electron'
|
import { webUtils } from 'electron'
|
||||||
|
|
||||||
|
type IpcListener = (event: Electron.IpcRendererEvent, ...args: unknown[]) => void
|
||||||
|
|
||||||
|
interface SafeIpcRenderer {
|
||||||
|
invoke: (channel: string, ...args: unknown[]) => Promise<unknown>
|
||||||
|
send: (channel: string, ...args: unknown[]) => void
|
||||||
|
on: (channel: string, listener: IpcListener) => void
|
||||||
|
removeListener: (channel: string, listener: IpcListener) => void
|
||||||
|
removeAllListeners: (channel: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ElectronAPI {
|
||||||
|
ipcRenderer: SafeIpcRenderer
|
||||||
|
process: {
|
||||||
|
platform: NodeJS.Platform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
electron: ElectronAPI
|
electron: ElectronAPI
|
||||||
|
|||||||
@ -1,13 +1,231 @@
|
|||||||
import { contextBridge, webUtils } from 'electron'
|
import { contextBridge, ipcRenderer, webUtils } from 'electron'
|
||||||
import { electronAPI } from '@electron-toolkit/preload'
|
|
||||||
|
// 允许的 invoke channels 白名单
|
||||||
|
const validInvokeChannels = [
|
||||||
|
// Mihomo API
|
||||||
|
'mihomoVersion',
|
||||||
|
'mihomoCloseConnection',
|
||||||
|
'mihomoCloseAllConnections',
|
||||||
|
'mihomoRules',
|
||||||
|
'mihomoProxies',
|
||||||
|
'mihomoGroups',
|
||||||
|
'mihomoProxyProviders',
|
||||||
|
'mihomoUpdateProxyProviders',
|
||||||
|
'mihomoRuleProviders',
|
||||||
|
'mihomoUpdateRuleProviders',
|
||||||
|
'mihomoChangeProxy',
|
||||||
|
'mihomoUnfixedProxy',
|
||||||
|
'mihomoUpgradeGeo',
|
||||||
|
'mihomoUpgrade',
|
||||||
|
'mihomoUpgradeUI',
|
||||||
|
'mihomoUpgradeConfig',
|
||||||
|
'mihomoProxyDelay',
|
||||||
|
'mihomoGroupDelay',
|
||||||
|
'patchMihomoConfig',
|
||||||
|
'mihomoSmartGroupWeights',
|
||||||
|
'mihomoSmartFlushCache',
|
||||||
|
// AutoRun
|
||||||
|
'checkAutoRun',
|
||||||
|
'enableAutoRun',
|
||||||
|
'disableAutoRun',
|
||||||
|
// Config
|
||||||
|
'getAppConfig',
|
||||||
|
'patchAppConfig',
|
||||||
|
'getControledMihomoConfig',
|
||||||
|
'patchControledMihomoConfig',
|
||||||
|
'resetAppConfig',
|
||||||
|
// Profile
|
||||||
|
'getProfileConfig',
|
||||||
|
'setProfileConfig',
|
||||||
|
'getCurrentProfileItem',
|
||||||
|
'getProfileItem',
|
||||||
|
'getProfileStr',
|
||||||
|
'setProfileStr',
|
||||||
|
'addProfileItem',
|
||||||
|
'removeProfileItem',
|
||||||
|
'updateProfileItem',
|
||||||
|
'changeCurrentProfile',
|
||||||
|
'addProfileUpdater',
|
||||||
|
'removeProfileUpdater',
|
||||||
|
// Override
|
||||||
|
'getOverrideConfig',
|
||||||
|
'setOverrideConfig',
|
||||||
|
'getOverrideItem',
|
||||||
|
'addOverrideItem',
|
||||||
|
'removeOverrideItem',
|
||||||
|
'updateOverrideItem',
|
||||||
|
'getOverride',
|
||||||
|
'setOverride',
|
||||||
|
// File
|
||||||
|
'getFileStr',
|
||||||
|
'setFileStr',
|
||||||
|
'convertMrsRuleset',
|
||||||
|
'getRuntimeConfig',
|
||||||
|
'getRuntimeConfigStr',
|
||||||
|
'getSmartOverrideContent',
|
||||||
|
'getRuleStr',
|
||||||
|
'setRuleStr',
|
||||||
|
'getFilePath',
|
||||||
|
'readTextFile',
|
||||||
|
'openFile',
|
||||||
|
// Core
|
||||||
|
'restartCore',
|
||||||
|
'startMonitor',
|
||||||
|
'quitWithoutCore',
|
||||||
|
// System
|
||||||
|
'triggerSysProxy',
|
||||||
|
'checkTunPermissions',
|
||||||
|
'grantTunPermissions',
|
||||||
|
'manualGrantCorePermition',
|
||||||
|
'checkAdminPrivileges',
|
||||||
|
'restartAsAdmin',
|
||||||
|
'checkMihomoCorePermissions',
|
||||||
|
'requestTunPermissions',
|
||||||
|
'checkHighPrivilegeCore',
|
||||||
|
'showTunPermissionDialog',
|
||||||
|
'showErrorDialog',
|
||||||
|
'openUWPTool',
|
||||||
|
'setupFirewall',
|
||||||
|
'getInterfaces',
|
||||||
|
'setNativeTheme',
|
||||||
|
'copyEnv',
|
||||||
|
// Update
|
||||||
|
'checkUpdate',
|
||||||
|
'downloadAndInstallUpdate',
|
||||||
|
'getVersion',
|
||||||
|
'platform',
|
||||||
|
'fetchMihomoTags',
|
||||||
|
'installSpecificMihomoCore',
|
||||||
|
'clearMihomoVersionCache',
|
||||||
|
// Backup
|
||||||
|
'webdavBackup',
|
||||||
|
'webdavRestore',
|
||||||
|
'listWebdavBackups',
|
||||||
|
'webdavDelete',
|
||||||
|
'reinitWebdavBackupScheduler',
|
||||||
|
'exportLocalBackup',
|
||||||
|
'importLocalBackup',
|
||||||
|
// SubStore
|
||||||
|
'startSubStoreFrontendServer',
|
||||||
|
'stopSubStoreFrontendServer',
|
||||||
|
'startSubStoreBackendServer',
|
||||||
|
'stopSubStoreBackendServer',
|
||||||
|
'downloadSubStore',
|
||||||
|
'subStorePort',
|
||||||
|
'subStoreFrontendPort',
|
||||||
|
'subStoreSubs',
|
||||||
|
'subStoreCollections',
|
||||||
|
// Theme
|
||||||
|
'resolveThemes',
|
||||||
|
'fetchThemes',
|
||||||
|
'importThemes',
|
||||||
|
'readTheme',
|
||||||
|
'writeTheme',
|
||||||
|
'applyTheme',
|
||||||
|
// Tray
|
||||||
|
'showTrayIcon',
|
||||||
|
'closeTrayIcon',
|
||||||
|
'updateTrayIcon',
|
||||||
|
'updateTrayIconImmediate',
|
||||||
|
// Window
|
||||||
|
'showMainWindow',
|
||||||
|
'closeMainWindow',
|
||||||
|
'triggerMainWindow',
|
||||||
|
'showFloatingWindow',
|
||||||
|
'closeFloatingWindow',
|
||||||
|
'showContextMenu',
|
||||||
|
'setTitleBarOverlay',
|
||||||
|
'setAlwaysOnTop',
|
||||||
|
'isAlwaysOnTop',
|
||||||
|
'openDevTools',
|
||||||
|
'createHeapSnapshot',
|
||||||
|
'relaunchApp',
|
||||||
|
'quitApp',
|
||||||
|
// Shortcut
|
||||||
|
'registerShortcut',
|
||||||
|
// Misc
|
||||||
|
'getGistUrl',
|
||||||
|
'getImageDataURL',
|
||||||
|
'changeLanguage'
|
||||||
|
] as const
|
||||||
|
|
||||||
|
// 允许的 on/removeListener channels 白名单
|
||||||
|
const validListenChannels = [
|
||||||
|
'mihomoLogs',
|
||||||
|
'mihomoConnections',
|
||||||
|
'mihomoTraffic',
|
||||||
|
'mihomoMemory',
|
||||||
|
'appConfigUpdated',
|
||||||
|
'controledMihomoConfigUpdated',
|
||||||
|
'profileConfigUpdated',
|
||||||
|
'groupsUpdated',
|
||||||
|
'rulesUpdated'
|
||||||
|
] as const
|
||||||
|
|
||||||
|
// 允许的 send channels 白名单
|
||||||
|
const validSendChannels = [
|
||||||
|
'updateTrayMenu',
|
||||||
|
'updateFloatingWindow',
|
||||||
|
'trayIconUpdate'
|
||||||
|
] as const
|
||||||
|
|
||||||
|
type InvokeChannel = (typeof validInvokeChannels)[number]
|
||||||
|
type ListenChannel = (typeof validListenChannels)[number]
|
||||||
|
type SendChannel = (typeof validSendChannels)[number]
|
||||||
|
|
||||||
|
type IpcListener = (event: Electron.IpcRendererEvent, ...args: unknown[]) => void
|
||||||
|
const listenerMap = new Map<ListenChannel, Set<IpcListener>>()
|
||||||
|
|
||||||
|
// 安全的 IPC API,只暴露白名单内的 channels
|
||||||
|
const electronAPI = {
|
||||||
|
ipcRenderer: {
|
||||||
|
invoke: (channel: InvokeChannel, ...args: unknown[]): Promise<unknown> => {
|
||||||
|
if (validInvokeChannels.includes(channel)) {
|
||||||
|
return ipcRenderer.invoke(channel, ...args)
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(`Invalid invoke channel: ${channel}`))
|
||||||
|
},
|
||||||
|
send: (channel: SendChannel, ...args: unknown[]): void => {
|
||||||
|
if (validSendChannels.includes(channel)) {
|
||||||
|
ipcRenderer.send(channel, ...args)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
on: (channel: ListenChannel, listener: IpcListener): void => {
|
||||||
|
if (validListenChannels.includes(channel)) {
|
||||||
|
if (!listenerMap.has(channel)) {
|
||||||
|
listenerMap.set(channel, new Set())
|
||||||
|
}
|
||||||
|
listenerMap.get(channel)!.add(listener)
|
||||||
|
ipcRenderer.on(channel, listener)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeListener: (channel: ListenChannel, listener: IpcListener): void => {
|
||||||
|
if (validListenChannels.includes(channel)) {
|
||||||
|
listenerMap.get(channel)?.delete(listener)
|
||||||
|
ipcRenderer.removeListener(channel, listener)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeAllListeners: (channel: ListenChannel): void => {
|
||||||
|
if (validListenChannels.includes(channel)) {
|
||||||
|
const listeners = listenerMap.get(channel)
|
||||||
|
if (listeners) {
|
||||||
|
listeners.forEach((listener) => {
|
||||||
|
ipcRenderer.removeListener(channel, listener)
|
||||||
|
})
|
||||||
|
listeners.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
process: {
|
||||||
|
platform: process.platform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Custom APIs for renderer
|
|
||||||
const api = {
|
const api = {
|
||||||
webUtils: webUtils
|
webUtils: webUtils
|
||||||
}
|
}
|
||||||
// Use `contextBridge` APIs to expose Electron APIs to
|
|
||||||
// renderer only if context isolation is enabled, otherwise
|
|
||||||
// just add to the DOM global.
|
|
||||||
if (process.contextIsolated) {
|
if (process.contextIsolated) {
|
||||||
try {
|
try {
|
||||||
contextBridge.exposeInMainWorld('electron', electronAPI)
|
contextBridge.exposeInMainWorld('electron', electronAPI)
|
||||||
|
|||||||
@ -86,7 +86,7 @@ const App: React.FC = () => {
|
|||||||
options.color = window.getComputedStyle(document.documentElement).backgroundColor
|
options.color = window.getComputedStyle(document.documentElement).backgroundColor
|
||||||
options.symbolColor = window.getComputedStyle(document.documentElement).color
|
options.symbolColor = window.getComputedStyle(document.documentElement).color
|
||||||
setTitleBarOverlay(options)
|
setTitleBarOverlay(options)
|
||||||
} catch (e) {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,7 +49,8 @@ const FloatingApp: React.FC = () => {
|
|||||||
}, [spinSpeed, spinFloatingIcon])
|
}, [spinSpeed, spinFloatingIcon])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.electron.ipcRenderer.on('mihomoTraffic', async (_e, info: IMihomoTrafficInfo) => {
|
window.electron.ipcRenderer.on('mihomoTraffic', async (_e, ...args) => {
|
||||||
|
const info = args[0] as IMihomoTrafficInfo
|
||||||
setUpload(info.up)
|
setUpload(info.up)
|
||||||
setDownload(info.down)
|
setDownload(info.down)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -32,7 +32,7 @@ const BasePage = forwardRef<HTMLDivElement, Props>((props, ref) => {
|
|||||||
// @ts-ignore windowControlsOverlay
|
// @ts-ignore windowControlsOverlay
|
||||||
const windowControlsOverlay = window.navigator.windowControlsOverlay
|
const windowControlsOverlay = window.navigator.windowControlsOverlay
|
||||||
setOverlayWidth(window.innerWidth - windowControlsOverlay.getTitlebarAreaRect().width)
|
setOverlayWidth(window.innerWidth - windowControlsOverlay.getTitlebarAreaRect().width)
|
||||||
} catch (e) {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -149,7 +149,7 @@ const EditInfoModal: React.FC<Props> = (props) => {
|
|||||||
// 非纯数字
|
// 非纯数字
|
||||||
try {
|
try {
|
||||||
setValues({ ...values, interval: v })
|
setValues({ ...values, interval: v })
|
||||||
} catch (e) {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,7 +66,7 @@ const Viewer: React.FC<Props> = (props) => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
setCurrData(fileContent)
|
setCurrData(fileContent)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -126,7 +126,8 @@ const ConnCard: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null
|
const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.electron.ipcRenderer.on('mihomoTraffic', async (_e, info: IMihomoTrafficInfo) => {
|
window.electron.ipcRenderer.on('mihomoTraffic', async (_e, ...args) => {
|
||||||
|
const info = args[0] as IMihomoTrafficInfo
|
||||||
setUpload(info.up)
|
setUpload(info.up)
|
||||||
setDownload(info.down)
|
setDownload(info.down)
|
||||||
const data = series
|
const data = series
|
||||||
|
|||||||
@ -43,7 +43,8 @@ const MihomoCoreCard: React.FC<Props> = (props) => {
|
|||||||
const token = PubSub.subscribe('mihomo-core-changed', () => {
|
const token = PubSub.subscribe('mihomo-core-changed', () => {
|
||||||
mutate()
|
mutate()
|
||||||
})
|
})
|
||||||
window.electron.ipcRenderer.on('mihomoMemory', (_e, info: IMihomoMemoryInfo) => {
|
window.electron.ipcRenderer.on('mihomoMemory', (_e, ...args) => {
|
||||||
|
const info = args[0] as IMihomoMemoryInfo
|
||||||
setMem(info.inuse)
|
setMem(info.inuse)
|
||||||
})
|
})
|
||||||
return (): void => {
|
return (): void => {
|
||||||
|
|||||||
@ -170,7 +170,8 @@ const Connections: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = (_e: unknown, info: IMihomoConnectionsInfo): void => {
|
const handler = (_e: unknown, ...args: unknown[]): void => {
|
||||||
|
const info = args[0] as IMihomoConnectionsInfo
|
||||||
setConnectionsInfo(info)
|
setConnectionsInfo(info)
|
||||||
|
|
||||||
if (!info.connections) return
|
if (!info.connections) return
|
||||||
|
|||||||
@ -26,7 +26,8 @@ const cachedLogs: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.electron.ipcRenderer.on('mihomoLogs', (_e, log: IMihomoLogInfo) => {
|
window.electron.ipcRenderer.on('mihomoLogs', (_e, ...args) => {
|
||||||
|
const log = args[0] as IMihomoLogInfo
|
||||||
log.time = new Date().toLocaleString()
|
log.time = new Date().toLocaleString()
|
||||||
cachedLogs.log.push(log)
|
cachedLogs.log.push(log)
|
||||||
if (cachedLogs.log.length >= 500) {
|
if (cachedLogs.log.length >= 500) {
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { TitleBarOverlayOptions } from 'electron'
|
import { TitleBarOverlayOptions } from 'electron'
|
||||||
|
|
||||||
function checkIpcError<T>(response: T | { invokeError: unknown }): T {
|
function checkIpcError<T>(response: unknown): T {
|
||||||
if (response && typeof response === 'object' && 'invokeError' in response) {
|
if (response && typeof response === 'object' && 'invokeError' in response) {
|
||||||
throw response.invokeError
|
throw (response as { invokeError: unknown }).invokeError
|
||||||
}
|
}
|
||||||
return response as T
|
return response as T
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user