Files
koring-launcher/electron/main.ts
T
dream_pep 4d3e43394c feat(auto-update): 实现全流程自动更新功能与配套界面
- 新增主进程更新服务,支持GitHub官方源与加速源兜底的检查、下载、安装全流程
- 新增独立更新日志页面,支持查看更新说明与更新全流程管理
- 实现更新状态持久化,重启应用可恢复上次未完成的下载进度
- 完善版本管理与构建元数据脚本,优化CI发布流水线
- 补充VersionCard的构建信息展示与更新页跳转逻辑
- 新增更新相关IPC接口、类型定义与全局状态管理
- 清理旧的无用配置项,升级相关依赖包
2026-08-30 20:19:15 +08:00

244 lines
7.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import electron from 'electron';
import path from 'path';
import fs from 'fs';
import { registerConfigHandlers } from './handlers/config';
import { registerAuthHandlers } from './handlers/auth';
import { registerInstallHandlers } from './handlers/install';
import { registerLaunchHandlers } from './handlers/launch';
import { registerModsHandlers } from './handlers/mods';
import { registerInstanceHandlers } from './handlers/instance';
import { registerBackgroundHandlers } from './handlers/background';
import { registerTaskHandlers } from './handlers/task';
import { registerSystemHandlers } from './handlers/system';
import { registerWindowHandlers } from './handlers/window';
import { registerCrashHandlers, setupCrashListeners, testCrashDialog } from './handlers/crash-monitor';
import { registerKoringAuthHandlers } from './handlers/koring-auth';
import { registerJavaHandlers } from './handlers/java';
import { registerUpdateHandlers } from './handlers/update';
import { updateService } from './updater';
import { saveConfig, configExists, getConfig, flushConfig, configPath } from './config';
import { authPath } from './auth';
const { app } = electron;
const isDev = !app.isPackaged;
// GPU acceleration flags
app.commandLine.appendSwitch('enable-gpu-rasterization');
app.commandLine.appendSwitch('enable-zero-copy');
const win: { mainWindow: electron.BrowserWindow | null; splashWindow: electron.BrowserWindow | null } = {
mainWindow: null,
splashWindow: null,
};
// 迁移旧版「可执行文件旁」存储 → userData(仅打包模式)。
// 复制而非移动,避免破坏用户已有文件;userData 已有目标文件则跳过。
function migrateLegacyFiles(): void {
if (!app.isPackaged) return;
const exeDir = path.dirname(app.getPath('exe'));
const pairs: { name: string; dest: string }[] = [
{ name: 'Koring.yml', dest: configPath() },
{ name: 'koring-auth.json', dest: authPath() },
];
for (const { name, dest } of pairs) {
if (fs.existsSync(dest)) continue;
const src = path.join(exeDir, name);
if (!fs.existsSync(src)) continue;
try {
fs.copyFileSync(src, dest);
console.log(`[migrate] copied ${name}${dest}`);
} catch (e) {
console.error(`[migrate] failed to copy ${name}:`, e);
}
}
}
// Startup checks: .minecraft dir + config file + first launch detection
function runStartupChecks(): { isFirstLaunch: boolean; config: ReturnType<typeof getConfig> } {
const dataPath = app.isPackaged
? path.dirname(app.getPath('exe'))
: path.join(__dirname, '..');
// 1. Ensure .minecraft directory exists
const minecraftDir = path.join(dataPath, '.minecraft');
if (!fs.existsSync(minecraftDir)) {
fs.mkdirSync(minecraftDir, { recursive: true });
}
// 2. Check if config exists, if not create with defaults
const hasConfig = configExists();
const isFirstLaunch = !hasConfig;
// 3. Load (or create) configgetConfig 会缓存到主进程内存,成为唯一权威)
const config = getConfig();
if (isFirstLaunch) {
saveConfig(config);
}
return { isFirstLaunch, config };
}
// 根据调试/运行状态自动选择窗口图标:
// - 开发调试(未打包):直接使用 public/icons/dev/ 下的 dev 图标
// - 打包运行:electron-builder 构建时 switch-icon 已把对应模式图标复制到 build/
function getWindowIconPath(): string | undefined {
if (!app.isPackaged) {
return path.join(__dirname, '../public/icons/dev/icon.ico');
}
const packagedIcon = path.join(__dirname, '../build/icon.ico');
return fs.existsSync(packagedIcon) ? packagedIcon : undefined;
}
function createSplashWindow(): electron.BrowserWindow {
const iconPath = getWindowIconPath();
const splash = new electron.BrowserWindow({
width: 480,
height: 320,
transparent: true,
frame: false,
resizable: false,
skipTaskbar: true,
alwaysOnTop: true,
icon: iconPath,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
},
});
if (isDev) {
splash.loadURL('http://localhost:1420/splash.html');
} else {
splash.loadFile(path.join(__dirname, '../dist/splash.html'));
}
return splash;
}
function createMainWindow(): electron.BrowserWindow {
const iconPath = getWindowIconPath();
const main = new electron.BrowserWindow({
width: 1000,
height: 700,
minWidth: 800,
minHeight: 600,
frame: false,
transparent: false,
resizable: true,
show: false,
icon: iconPath,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false,
contextIsolation: true,
sandbox: false,
},
});
if (isDev) {
main.loadURL('http://localhost:1420');
} else {
main.loadFile(path.join(__dirname, '../dist/index.html'));
}
main.on('maximize', () => {
main.webContents.send('window:resized');
});
main.on('unmaximize', () => {
main.webContents.send('window:resized');
});
return main;
}
function registerAllHandlers() {
registerConfigHandlers(win);
registerAuthHandlers();
registerInstallHandlers(win);
registerLaunchHandlers(win);
registerModsHandlers();
registerInstanceHandlers(win);
registerBackgroundHandlers();
registerTaskHandlers(win);
registerSystemHandlers();
registerWindowHandlers(win);
registerCrashHandlers();
registerKoringAuthHandlers();
registerJavaHandlers();
registerUpdateHandlers();
}
app.whenReady().then(() => {
registerAllHandlers();
// Migrate legacy exe-dir config/auth to userData before anything reads them
migrateLegacyFiles();
// Run startup checks before creating windows
const { isFirstLaunch, config } = runStartupChecks();
// 1. Show splash immediately
win.splashWindow = createSplashWindow();
// 2. Create main window in background
win.mainWindow = createMainWindow();
// 3. Preload config into renderer before it renders
win.mainWindow.webContents.on('did-finish-load', () => {
win.mainWindow?.webContents.send('config:preload', { config, isFirstLaunch });
});
// 4. When main window finishes loading, wait a minimum time then transition
let mainReady = false;
let splashMinTimeDone = false;
const tryTransition = () => {
if (mainReady && splashMinTimeDone) {
if (win.mainWindow && !win.mainWindow.isDestroyed()) {
win.mainWindow.show();
win.mainWindow.focus();
}
if (win.splashWindow && !win.splashWindow.isDestroyed()) {
win.splashWindow.close();
win.splashWindow = null;
}
}
};
win.mainWindow.once('ready-to-show', () => {
mainReady = true;
tryTransition();
});
// Setup crash listeners on main window
setupCrashListeners(win.mainWindow);
// Minimum splash display time (1.5s)
setTimeout(() => {
splashMinTimeDone = true;
tryTransition();
}, 1500);
// 延迟静默检查更新(避开启动加载,不抢带宽;开发模式在 updater.init 内自动跳过)
setTimeout(() => {
updateService.check(false).catch((e) => {
console.error('[updater] 启动静默检查失败:', e);
});
}, 12000);
});
app.on('window-all-closed', () => {
flushConfig();
app.quit();
});
app.on('activate', () => {
if (electron.BrowserWindow.getAllWindows().length === 0) {
win.mainWindow = createMainWindow();
}
});