// ============================================================ // updater/index.ts — 自动更新主入口 // // 流程图:调用自有 API → 获取版本信息 → electron-updater 差分下载 → SHA512 校验 → 安装 // ============================================================ import { BrowserWindow, ipcMain } from 'electron'; import type { NsisUpdater, MacUpdater, AppImageUpdater } from 'electron-updater'; import type { UpdateInfo } from 'builder-util-runtime'; import { APP_VERSION } from '../../../shared/constants/version'; import { MAIN_TO_RENDERER_UPDATER, RENDERER_TO_MAIN_UPDATER } from '../../../shared/constants/ipc/updater'; import { logger } from '../logger'; import { HeiXiuProvider, getApiBaseUrl, getDeviceFingerprint, BUILD_CHANNEL, BUILD_EDITION } from './provider'; import { createPlatformUpdater } from './platform-updaters'; // ---------- 状态 ---------- let updateWindow: BrowserWindow | null = null; let provider: HeiXiuProvider | null = null; let platformUpdater: NsisUpdater | MacUpdater | AppImageUpdater | null = null; // ============================================================ // 初始化 // ============================================================ export function initUpdater(mainWindow: BrowserWindow): void { updateWindow = mainWindow; console.log('[updater] initUpdater 被调用'); console.log(`[updater] API URL: ${getApiBaseUrl()}`); console.log(`[updater] 渠道: ${BUILD_CHANNEL}`); console.log(`[updater] 版本类型: ${BUILD_EDITION}`); console.log(`[updater] 当前版本: ${APP_VERSION}`); console.log(`[updater] 设备指纹: ${getDeviceFingerprint()}`); // 创建自定义 Provider provider = new HeiXiuProvider({ isUseMultipleRangeRequest: true, platform: process.platform as 'win32' | 'darwin' | 'linux', executor: null as never, }); // 创建平台 Updater platformUpdater = createPlatformUpdater(provider); // 将 electron-updater 事件桥接到现有 IPC 通道 platformUpdater.on('checking-for-update', () => { logger.debug('updater', '正在检查更新'); }); platformUpdater.on('update-available', (info: UpdateInfo) => { // releaseNotes 可能是 string | ReleaseNoteInfo[] | null,统一转为 string let notes = ''; if (typeof info.releaseNotes === 'string') { notes = info.releaseNotes; } else if (Array.isArray(info.releaseNotes)) { notes = info.releaseNotes.map((n) => n.note).filter(Boolean).join('\n'); } updateWindow?.webContents.send(MAIN_TO_RENDERER_UPDATER.UPDATE_AVAILABLE, { version: info.version, releaseDate: info.releaseDate, releaseNotes: notes, forceUpdate: false, }); logger.info('updater', '发现新版本', { version: info.version, releaseNotes: notes ? `${notes.slice(0, 50)}...` : '(空)', }); }); platformUpdater.on('update-not-available', (info: UpdateInfo) => { updateWindow?.webContents.send(MAIN_TO_RENDERER_UPDATER.UPDATE_NOT_AVAILABLE); logger.info('updater', '当前已是最新版本', { currentVersion: info.version }); }); platformUpdater.on('download-progress', (progress: { percent: number; bytesPerSecond: number; transferred: number; total: number; }) => { updateWindow?.webContents.send(MAIN_TO_RENDERER_UPDATER.UPDATE_PROGRESS, { percent: Math.round(progress.percent), bytesPerSecond: progress.bytesPerSecond, transferred: progress.transferred, total: progress.total, }); }); platformUpdater.on('update-downloaded', () => { updateWindow?.webContents.send(MAIN_TO_RENDERER_UPDATER.UPDATE_DOWNLOADED); logger.info('updater', '更新已下载完成'); }); platformUpdater.on('error', (error: Error) => { logger.error('updater', '更新流程出错', error); updateWindow?.webContents.send(MAIN_TO_RENDERER_UPDATER.UPDATE_ERROR, { message: error.message || '更新失败', }); }); // 启动 5 秒后静默检查 setTimeout(() => { checkForUpdates(); }, 5000); } // ============================================================ // 操作方法 // ============================================================ let updateChecked = false; /** 静默检查更新(自动下载) */ export async function checkForUpdates(): Promise { if (updateChecked) { logger.debug('updater', '已检查过更新,跳过'); return; } if (!platformUpdater || !provider) return; try { updateChecked = true; // ① 先自行检查有无更新(不经过 electron-updater) const info = await provider.getLatestVersion(); if (info.version === APP_VERSION) { // 无更新 → 直接通知 UI,不触发 electron-updater 的任何事件 updateWindow?.webContents.send(MAIN_TO_RENDERER_UPDATER.UPDATE_NOT_AVAILABLE); logger.debug('updater', '当前已是最新版本', { version: APP_VERSION }); return; } // ② 有新版本 → 交给 electron-updater 触发 'update-available' 事件(不自动下载) await platformUpdater.checkForUpdates(); } catch (error) { updateChecked = false; logger.error('updater', '更新流程失败', error as Error, { apiUrl: getApiBaseUrl() }); updateWindow?.webContents.send(MAIN_TO_RENDERER_UPDATER.UPDATE_ERROR, { message: (error as Error).message || '更新检查失败', }); } } /** 手动检查更新(用户点击触发) */ export async function checkForUpdatesManual(): Promise { if (!platformUpdater || !provider) return; try { updateChecked = false; // ① 先自行检查有无更新 const info = await provider.getLatestVersion(); if (info.version === APP_VERSION) { // 无更新 → 直接通知 UI updateWindow?.webContents.send(MAIN_TO_RENDERER_UPDATER.UPDATE_NOT_AVAILABLE); logger.debug('updater', '手动检查:当前已是最新版本', { version: APP_VERSION }); return; } // ② 有新版本 → 交给 electron-updater 触发 'update-available' 事件(不自动下载) await platformUpdater.checkForUpdates(); } catch (error) { logger.error('updater', '手动更新失败', error as Error); updateWindow?.webContents.send(MAIN_TO_RENDERER_UPDATER.UPDATE_ERROR, { message: (error as Error).message || '检查更新失败', }); } } /** 用户确认后开始下载更新 */ export async function downloadUpdate(): Promise { if (!platformUpdater) { logger.warn('updater', 'downloadUpdate: Updater 未初始化'); return; } try { await platformUpdater.downloadUpdate(); } catch (error) { logger.error('updater', '下载更新失败', error as Error); updateWindow?.webContents.send(MAIN_TO_RENDERER_UPDATER.UPDATE_ERROR, { message: (error as Error).message || '下载更新失败', }); } } /** 安装已下载的更新并退出应用 */ export function installUpdateNow(): void { if (!platformUpdater) { logger.warn('updater', 'Updater 未初始化'); return; } logger.info('updater', '开始安装更新并退出应用'); setImmediate(() => { platformUpdater!.quitAndInstall(false, true); }); } // ============================================================ // IPC 注册 // ============================================================ export function registerUpdateIpcHandlers(): void { ipcMain.handle(RENDERER_TO_MAIN_UPDATER.CHECK_FOR_UPDATE, async () => { await checkForUpdatesManual(); }); ipcMain.handle(RENDERER_TO_MAIN_UPDATER.DOWNLOAD_UPDATE, async () => { await downloadUpdate(); }); ipcMain.on(RENDERER_TO_MAIN_UPDATER.INSTALL_UPDATE, () => { installUpdateNow(); }); }