mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2026-02-10 11:40:28 +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}'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-vars': 0,
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'warn',
|
||||
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' }
|
||||
],
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@electron-toolkit/preload": "^3.0.2",
|
||||
"@electron-toolkit/utils": "^4.0.0",
|
||||
"@heroui/react": "^2.8.5",
|
||||
"@mihomo-party/sysproxy": "^2.0.8",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"adm-zip": "^0.5.16",
|
||||
"axios": "^1.13.2",
|
||||
"chart.js": "^4.5.1",
|
||||
"chokidar": "^4.0.3",
|
||||
"croner": "^9.1.0",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.19",
|
||||
"express": "^5.1.0",
|
||||
"i18next": "^25.6.2",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"react-chartjs-2": "^5.3.1",
|
||||
"react-i18next": "^15.7.4",
|
||||
"webdav": "^5.8.0",
|
||||
"ws": "^8.18.3",
|
||||
"yaml": "^2.8.1"
|
||||
@ -58,8 +51,10 @@
|
||||
"@electron-toolkit/eslint-config-prettier": "^3.0.0",
|
||||
"@electron-toolkit/eslint-config-ts": "^3.1.0",
|
||||
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||
"@heroui/react": "^2.8.5",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"@types/adm-zip": "^0.5.7",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/express": "^5.0.5",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/pubsub-js": "^1.8.6",
|
||||
@ -67,7 +62,9 @@
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"chart.js": "^4.5.1",
|
||||
"cron-validator": "^1.4.0",
|
||||
"dayjs": "^1.11.19",
|
||||
"driver.js": "^1.3.6",
|
||||
"electron": "^37.10.0",
|
||||
"electron-builder": "26.0.12",
|
||||
@ -86,8 +83,10 @@
|
||||
"prettier": "^3.6.2",
|
||||
"pubsub-js": "^1.9.5",
|
||||
"react": "^19.2.0",
|
||||
"react-chartjs-2": "^5.3.1",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-error-boundary": "^6.0.0",
|
||||
"react-i18next": "^15.7.4",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-monaco-editor": "^0.59.0",
|
||||
|
||||
@ -562,7 +562,7 @@ async function waitForCoreReady(): Promise<void> {
|
||||
await axios.get('/')
|
||||
await managerLogger.info(`Core ready after ${i + 1} attempts (${(i + 1) * retryInterval}ms)`)
|
||||
return
|
||||
} catch (error) {
|
||||
} catch {
|
||||
if (i === 0) {
|
||||
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) {
|
||||
return true
|
||||
}
|
||||
} catch (error) {
|
||||
} catch {
|
||||
await managerLogger.info(
|
||||
`Cannot get info for process ${pid}, might be high privilege`
|
||||
)
|
||||
@ -835,7 +835,7 @@ async function checkHighPrivilegeMihomoProcess(): Promise<boolean> {
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ export async function initProfileUpdater(): Promise<void> {
|
||||
async () => {
|
||||
try {
|
||||
await addProfileItem(item)
|
||||
} catch (e) {
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
},
|
||||
@ -26,7 +26,7 @@ export async function initProfileUpdater(): Promise<void> {
|
||||
intervalPool[item.id] = new Cron(item.interval, async () => {
|
||||
try {
|
||||
await addProfileItem(item)
|
||||
} catch (e) {
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
})
|
||||
@ -34,7 +34,7 @@ export async function initProfileUpdater(): Promise<void> {
|
||||
|
||||
try {
|
||||
await addProfileItem(item)
|
||||
} catch (e) {
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
@ -46,7 +46,7 @@ export async function initProfileUpdater(): Promise<void> {
|
||||
async () => {
|
||||
try {
|
||||
await addProfileItem(currentItem)
|
||||
} catch (e) {
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
},
|
||||
@ -57,7 +57,7 @@ export async function initProfileUpdater(): Promise<void> {
|
||||
async () => {
|
||||
try {
|
||||
await addProfileItem(currentItem)
|
||||
} catch (e) {
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
},
|
||||
@ -67,7 +67,7 @@ export async function initProfileUpdater(): Promise<void> {
|
||||
intervalPool[currentItem.id] = new Cron(currentItem.interval, async () => {
|
||||
try {
|
||||
await addProfileItem(currentItem)
|
||||
} catch (e) {
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
})
|
||||
@ -75,7 +75,7 @@ export async function initProfileUpdater(): Promise<void> {
|
||||
|
||||
try {
|
||||
await addProfileItem(currentItem)
|
||||
} catch (e) {
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
@ -96,7 +96,7 @@ export async function addProfileUpdater(item: IProfileItem): Promise<void> {
|
||||
async () => {
|
||||
try {
|
||||
await addProfileItem(item)
|
||||
} catch (e) {
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
},
|
||||
@ -106,7 +106,7 @@ export async function addProfileUpdater(item: IProfileItem): Promise<void> {
|
||||
intervalPool[item.id] = new Cron(item.interval, async () => {
|
||||
try {
|
||||
await addProfileItem(item)
|
||||
} catch (e) {
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
})
|
||||
|
||||
@ -120,7 +120,7 @@ export const buildContextMenu = async (): Promise<Menu> => {
|
||||
groupsMenu = groupItems
|
||||
groupsMenu.unshift({ type: 'separator' })
|
||||
}
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// ignore
|
||||
// 避免出错时无法创建托盘菜单
|
||||
}
|
||||
@ -206,7 +206,7 @@ export const buildContextMenu = async (): Promise<Menu> => {
|
||||
await patchAppConfig({ sysProxy: { enable } })
|
||||
mainWindow?.webContents.send('appConfigUpdated')
|
||||
floatingWindow?.webContents.send('appConfigUpdated')
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// ignore
|
||||
} finally {
|
||||
ipcMain.emit('updateTrayMenu')
|
||||
|
||||
@ -61,7 +61,7 @@ export async function checkAutoRun(): Promise<boolean> {
|
||||
`chcp 437 && %SystemRoot%\\System32\\schtasks.exe /query /tn "${appName}"`
|
||||
)
|
||||
return stdout.includes(appName)
|
||||
} catch (e) {
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -96,7 +96,7 @@ export async function enableAutoRun(): Promise<void> {
|
||||
await execPromise(
|
||||
`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?')
|
||||
}
|
||||
}
|
||||
@ -144,7 +144,7 @@ export async function disableAutoRun(): Promise<void> {
|
||||
await execPromise(
|
||||
`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?')
|
||||
}
|
||||
}
|
||||
|
||||
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'
|
||||
|
||||
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 {
|
||||
interface Window {
|
||||
electron: ElectronAPI
|
||||
|
||||
@ -1,13 +1,231 @@
|
||||
import { contextBridge, webUtils } from 'electron'
|
||||
import { electronAPI } from '@electron-toolkit/preload'
|
||||
import { contextBridge, ipcRenderer, webUtils } from 'electron'
|
||||
|
||||
// 允许的 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 = {
|
||||
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) {
|
||||
try {
|
||||
contextBridge.exposeInMainWorld('electron', electronAPI)
|
||||
|
||||
@ -86,7 +86,7 @@ const App: React.FC = () => {
|
||||
options.color = window.getComputedStyle(document.documentElement).backgroundColor
|
||||
options.symbolColor = window.getComputedStyle(document.documentElement).color
|
||||
setTitleBarOverlay(options)
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,7 +49,8 @@ const FloatingApp: React.FC = () => {
|
||||
}, [spinSpeed, spinFloatingIcon])
|
||||
|
||||
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)
|
||||
setDownload(info.down)
|
||||
})
|
||||
|
||||
@ -32,7 +32,7 @@ const BasePage = forwardRef<HTMLDivElement, Props>((props, ref) => {
|
||||
// @ts-ignore windowControlsOverlay
|
||||
const windowControlsOverlay = window.navigator.windowControlsOverlay
|
||||
setOverlayWidth(window.innerWidth - windowControlsOverlay.getTitlebarAreaRect().width)
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,7 +149,7 @@ const EditInfoModal: React.FC<Props> = (props) => {
|
||||
// 非纯数字
|
||||
try {
|
||||
setValues({ ...values, interval: v })
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,7 +66,7 @@ const Viewer: React.FC<Props> = (props) => {
|
||||
})
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
} catch {
|
||||
setCurrData(fileContent)
|
||||
}
|
||||
} 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
|
||||
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)
|
||||
setDownload(info.down)
|
||||
const data = series
|
||||
|
||||
@ -43,7 +43,8 @@ const MihomoCoreCard: React.FC<Props> = (props) => {
|
||||
const token = PubSub.subscribe('mihomo-core-changed', () => {
|
||||
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)
|
||||
})
|
||||
return (): void => {
|
||||
|
||||
@ -170,7 +170,8 @@ const Connections: React.FC = () => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (_e: unknown, info: IMihomoConnectionsInfo): void => {
|
||||
const handler = (_e: unknown, ...args: unknown[]): void => {
|
||||
const info = args[0] as IMihomoConnectionsInfo
|
||||
setConnectionsInfo(info)
|
||||
|
||||
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()
|
||||
cachedLogs.log.push(log)
|
||||
if (cachedLogs.log.length >= 500) {
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
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) {
|
||||
throw response.invokeError
|
||||
throw (response as { invokeError: unknown }).invokeError
|
||||
}
|
||||
return response as T
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user