fix: windows first startup issue

This commit is contained in:
ezequielnick 2025-08-06 10:32:09 +08:00
parent d6f0d30f9a
commit 0a064bdbb8
3 changed files with 109 additions and 54 deletions

View File

@ -39,6 +39,7 @@ import os from 'os'
import { createWriteStream, existsSync } from 'fs'
import { uploadRuntimeConfig } from '../resolve/gistApi'
import { startMonitor } from '../resolve/trafficMonitor'
import { safeShowErrorBox } from '../utils/init'
import i18next from '../../shared/i18n'
chokidar.watch(path.join(mihomoCoreDir(), 'meta-update'), {}).on('unlinkDir', async () => {
@ -46,7 +47,7 @@ chokidar.watch(path.join(mihomoCoreDir(), 'meta-update'), {}).on('unlinkDir', as
await stopCore(true)
await startCore()
} catch (e) {
dialog.showErrorBox(i18next.t('mihomo.error.coreStartFailed'), `${e}`)
safeShowErrorBox('mihomo.error.coreStartFailed', `${e}`)
}
})
@ -228,7 +229,7 @@ export async function keepCoreAlive(): Promise<void> {
await writeFile(path.join(dataDir(), 'core.pid'), child.pid.toString())
}
} catch (e) {
dialog.showErrorBox(i18next.t('mihomo.error.coreStartFailed'), `${e}`)
safeShowErrorBox('mihomo.error.coreStartFailed', `${e}`)
}
}

View File

@ -18,12 +18,31 @@ import { initProfileUpdater } from './core/profileUpdater'
import { existsSync, writeFileSync } from 'fs'
import { exePath, taskDir } from './utils/dirs'
import path from 'path'
import iconv from 'iconv-lite'
import { startMonitor } from './resolve/trafficMonitor'
import { showFloatingWindow } from './resolve/floatingWindow'
import iconv from 'iconv-lite'
import { initI18n } from '../shared/i18n'
import i18next from 'i18next'
// 错误处理
function showSafeErrorBox(titleKey: string, message: string): void {
let title: string
try {
title = i18next.t(titleKey)
if (!title || title === titleKey) throw new Error('Translation not ready')
} catch {
const isZh = app.getLocale().startsWith('zh')
const fallbacks: Record<string, { zh: string; en: string }> = {
'common.error.initFailed': { zh: '应用初始化失败', en: 'Application initialization failed' },
'mihomo.error.coreStartFailed': { zh: '内核启动出错', en: 'Core start failed' },
'profiles.error.importFailed': { zh: '配置导入失败', en: 'Profile import failed' },
'common.error.adminRequired': { zh: '需要管理员权限', en: 'Administrator privileges required' }
}
title = fallbacks[titleKey] ? (isZh ? fallbacks[titleKey].zh : fallbacks[titleKey].en) : (isZh ? '错误' : 'Error')
}
dialog.showErrorBox(title, message)
}
async function fixUserDataPermissions(): Promise<void> {
if (process.platform !== 'darwin') return
@ -50,6 +69,7 @@ async function fixUserDataPermissions(): Promise<void> {
let quitTimeout: NodeJS.Timeout | null = null
export let mainWindow: BrowserWindow | null = null
// Windows 管理员权限检查(仅在生产模式下)
if (process.platform === 'win32' && !is.dev && !process.argv.includes('noadmin')) {
try {
createElevateTask()
@ -74,10 +94,7 @@ if (process.platform === 'win32' && !is.dev && !process.argv.includes('noadmin')
} catch {
// ignore
}
dialog.showErrorBox(
i18next.t('common.error.adminRequired'),
`${i18next.t('common.error.adminRequired')}\n${createErrorStr}\n${eStr}`
)
showSafeErrorBox('common.error.adminRequired', `${createErrorStr}\n${eStr}`)
} finally {
app.exit()
}
@ -171,6 +188,9 @@ app.whenReady().then(async () => {
electronApp.setAppUserModelId('party.mihomo.app')
try {
// 首先等待初始化完成,确保配置文件和目录都已创建
await initPromise
const appConfig = await getAppConfig()
// 如果配置中没有语言设置,则使用系统语言
if (!appConfig.language) {
@ -179,9 +199,8 @@ app.whenReady().then(async () => {
appConfig.language = systemLanguage
}
await initI18n({ lng: appConfig.language })
await initPromise
} catch (e) {
dialog.showErrorBox(i18next.t('common.error.initFailed'), `${e}`)
showSafeErrorBox('common.error.initFailed', `${e}`)
app.quit()
}
try {
@ -190,7 +209,7 @@ app.whenReady().then(async () => {
await initProfileUpdater()
})
} catch (e) {
dialog.showErrorBox(i18next.t('mihomo.error.coreStartFailed'), `${e}`)
showSafeErrorBox('mihomo.error.coreStartFailed', `${e}`)
}
try {
await startMonitor()
@ -242,7 +261,7 @@ async function handleDeepLink(url: string): Promise<void> {
new Notification({ title: i18next.t('profiles.notification.importSuccess') }).show()
break
} catch (e) {
dialog.showErrorBox(i18next.t('profiles.error.importFailed'), `${url}\n${e}`)
showSafeErrorBox('profiles.error.importFailed', `${url}\n${e}`)
}
}
}

View File

@ -39,8 +39,25 @@ import {
patchAppConfig,
patchControledMihomoConfig
} from '../config'
import { app } from 'electron'
import { app, dialog } from 'electron'
import { startSSIDCheck } from '../sys/ssid'
import i18next from '../../shared/i18n'
// 安全错误处理
export function safeShowErrorBox(titleKey: string, message: string): void {
let title: string
try {
title = i18next.t(titleKey)
if (!title || title === titleKey) throw new Error('Translation not ready')
} catch {
const isZh = process.env.LANG?.startsWith('zh') || process.env.LC_ALL?.startsWith('zh')
const fallbacks: Record<string, { zh: string; en: string }> = {
'mihomo.error.coreStartFailed': { zh: '内核启动出错', en: 'Core start failed' }
}
title = fallbacks[titleKey] ? (isZh ? fallbacks[titleKey].zh : fallbacks[titleKey].en) : (isZh ? '错误' : 'Error')
}
dialog.showErrorBox(title, message)
}
async function fixDataDirPermissions(): Promise<void> {
if (process.platform !== 'darwin') return
@ -68,47 +85,48 @@ async function fixDataDirPermissions(): Promise<void> {
async function initDirs(): Promise<void> {
await fixDataDirPermissions()
if (!existsSync(dataDir())) {
await mkdir(dataDir())
}
if (!existsSync(themesDir())) {
await mkdir(themesDir())
}
if (!existsSync(profilesDir())) {
await mkdir(profilesDir())
}
if (!existsSync(overrideDir())) {
await mkdir(overrideDir())
}
if (!existsSync(mihomoWorkDir())) {
await mkdir(mihomoWorkDir())
}
if (!existsSync(logDir())) {
await mkdir(logDir())
}
if (!existsSync(mihomoTestDir())) {
await mkdir(mihomoTestDir())
}
if (!existsSync(subStoreDir())) {
await mkdir(subStoreDir())
// 按依赖顺序创建目录
const dirsToCreate = [
dataDir(),
themesDir(),
profilesDir(),
overrideDir(),
mihomoWorkDir(),
logDir(),
mihomoTestDir(),
subStoreDir()
]
for (const dir of dirsToCreate) {
try {
if (!existsSync(dir)) {
await mkdir(dir, { recursive: true })
}
} catch (error) {
console.error(`Failed to create directory ${dir}:`, error)
throw new Error(`Failed to create directory ${dir}: ${error}`)
}
}
}
async function initConfig(): Promise<void> {
if (!existsSync(appConfigPath())) {
await writeFile(appConfigPath(), yaml.stringify(defaultConfig))
}
if (!existsSync(profileConfigPath())) {
await writeFile(profileConfigPath(), yaml.stringify(defaultProfileConfig))
}
if (!existsSync(overrideConfigPath())) {
await writeFile(overrideConfigPath(), yaml.stringify(defaultOverrideConfig))
}
if (!existsSync(profilePath('default'))) {
await writeFile(profilePath('default'), yaml.stringify(defaultProfile))
}
if (!existsSync(controledMihomoConfigPath())) {
await writeFile(controledMihomoConfigPath(), yaml.stringify(defaultControledMihomoConfig))
const configs = [
{ path: appConfigPath(), content: defaultConfig, name: 'app config' },
{ path: profileConfigPath(), content: defaultProfileConfig, name: 'profile config' },
{ path: overrideConfigPath(), content: defaultOverrideConfig, name: 'override config' },
{ path: profilePath('default'), content: defaultProfile, name: 'default profile' },
{ path: controledMihomoConfigPath(), content: defaultControledMihomoConfig, name: 'mihomo config' }
]
for (const config of configs) {
try {
if (!existsSync(config.path)) {
await writeFile(config.path, yaml.stringify(config.content))
}
} catch (error) {
console.error(`Failed to create ${config.name} at ${config.path}:`, error)
throw new Error(`Failed to create ${config.name}: ${error}`)
}
}
}
@ -117,13 +135,30 @@ async function initFiles(): Promise<void> {
const targetPath = path.join(mihomoWorkDir(), file)
const testTargetPath = path.join(mihomoTestDir(), file)
const sourcePath = path.join(resourcesFilesDir(), file)
if (!existsSync(targetPath) && existsSync(sourcePath)) {
await cp(sourcePath, targetPath, { recursive: true })
}
if (!existsSync(testTargetPath) && existsSync(sourcePath)) {
await cp(sourcePath, testTargetPath, { recursive: true })
try {
if (!existsSync(targetPath) && existsSync(sourcePath)) {
await cp(sourcePath, targetPath, { recursive: true })
}
if (!existsSync(testTargetPath) && existsSync(sourcePath)) {
await cp(sourcePath, testTargetPath, { recursive: true })
}
} catch (error) {
console.error(`Failed to copy ${file}:`, error)
if (['country.mmdb', 'geoip.dat', 'geosite.dat'].includes(file)) {
throw new Error(`Failed to copy critical file ${file}: ${error}`)
}
}
}
// 确保工作目录存在
if (!existsSync(mihomoWorkDir())) {
await mkdir(mihomoWorkDir(), { recursive: true })
}
if (!existsSync(mihomoTestDir())) {
await mkdir(mihomoTestDir(), { recursive: true })
}
await Promise.all([
copy('country.mmdb'),
copy('geoip.metadb'),