Files
lgp-admin-plus/apps/desktop/src/main/updater.ts
2026-08-13 19:04:28 +08:00

155 lines
5.3 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.
/**
* 自动更新:electron-updater(generic provider,消费静态托管的 latest*.yml + 安装包)
* Windows 消费 latest.yml + exe;mac 消费 latest-mac.yml + zip
* mac 未签名包无法通过更新签名校验:捕获后降级为「前往下载页手动安装」提示
*/
import type { UpdateInfo } from 'electron-updater';
import { app, dialog, shell } from 'electron';
import log from 'electron-log/main';
import electronUpdater from 'electron-updater';
import { DOWNLOAD_PAGE_URL, PRODUCT_NAME } from './config';
import { appState } from './state';
import { getMainWindow } from './window';
// electron-updater 是 CJS 包,ESM 下先整体导入再解构(named import 在部分打包场景会失败)
const { autoUpdater } = electronUpdater;
/** 是否用户手动触发的检查:手动才弹「已是最新/失败」反馈,启动时的静默检查不打扰用户 */
let manualCheck = false;
/** 防止重复初始化事件监听 */
let initialized = false;
/** 把更新流程事件转发给渲染层(web 侧可用 desktopAPI.onUpdateEvent 展示下载进度) */
function sendUpdateEvent(type: string, payload?: unknown): void {
getMainWindow()?.webContents.send('desktop:update-event', { payload, type });
}
/**
* 初始化更新器:注册事件 + 启动延时静默检查
* 只在打包环境生效(开发态没有 app-update.yml,检查必然报错)
*/
export function initUpdater(): void {
if (!app.isPackaged || initialized) return;
initialized = true;
autoUpdater.logger = log;
autoUpdater.autoDownload = true;
// 用户选「退出时安装」时靠这个兜底自动装
autoUpdater.autoInstallOnAppQuit = true;
autoUpdater.on('checking-for-update', () => sendUpdateEvent('checking'));
autoUpdater.on('update-available', (info: UpdateInfo) =>
sendUpdateEvent('available', { version: info.version }),
);
autoUpdater.on('update-not-available', () => {
sendUpdateEvent('not-available');
if (manualCheck) {
dialog.showMessageBox({
message: '当前已是最新版本',
title: PRODUCT_NAME,
type: 'info',
});
}
manualCheck = false;
});
autoUpdater.on('download-progress', (progress) =>
sendUpdateEvent('progress', {
percent: progress.percent,
transferred: progress.transferred,
total: progress.total,
}),
);
autoUpdater.on('update-downloaded', (info: UpdateInfo) => {
sendUpdateEvent('downloaded', { version: info.version });
void promptInstall(info);
});
autoUpdater.on('error', (error) => {
const message = String(error?.message ?? error);
sendUpdateEvent('error', { message });
log.error('[updater] 更新出错:', message);
// mac 未签名包的签名校验失败:降级为手动下载引导
const isMacSignatureIssue =
process.platform === 'darwin' &&
/code signature|codesign|not signed/i.test(message);
if (isMacSignatureIssue) {
void promptManualDownload();
} else if (manualCheck) {
dialog.showMessageBox({
detail: message,
message: '检查更新失败',
title: PRODUCT_NAME,
type: 'warning',
});
}
manualCheck = false;
});
// 启动 8 秒后静默检查:等窗口/协议就绪,也避免抢启动期资源
setTimeout(() => {
autoUpdater
.checkForUpdates()
.catch((error) => log.warn('[updater] 启动静默检查失败:', error));
}, 8000);
}
/** 手动检查更新(托盘菜单 / desktopAPI.checkUpdate 共用入口) */
export function checkUpdateManually(): void {
if (!app.isPackaged) {
dialog.showMessageBox({
message: '开发模式不支持检查更新',
title: PRODUCT_NAME,
type: 'info',
});
return;
}
manualCheck = true;
autoUpdater
.checkForUpdates()
.catch((error) => log.warn('[updater] 手动检查失败:', error));
}
/** 立即重启安装(更新属于真正退出,先置 quitting 绕过「关闭进托盘」拦截) */
export function quitAndInstallNow(): void {
appState.quitting = true;
// isSilent=false 走正常安装界面;isForceRunAfter=true 装完自动拉起新版本
autoUpdater.quitAndInstall(false, true);
}
/** 下载完成后的安装确认弹窗 */
async function promptInstall(info: UpdateInfo): Promise<void> {
const options = {
buttons: ['立即重启安装', '退出时安装'],
cancelId: 1,
defaultId: 0,
detail: '选择「退出时安装」则下次退出应用时自动完成升级。',
message: `新版本 ${info.version} 已下载完成,是否立即重启安装?`,
title: PRODUCT_NAME,
type: 'info' as const,
};
const win = getMainWindow();
const { response } = win
? await dialog.showMessageBox(win, options)
: await dialog.showMessageBox(options);
if (response === 0) {
quitAndInstallNow();
}
}
/** mac 未签名降级:引导用户去下载页手动装新版本 */
async function promptManualDownload(): Promise<void> {
const { response } = await dialog.showMessageBox({
buttons: ['打开下载页', '取消'],
cancelId: 1,
defaultId: 0,
detail: '当前安装包未进行代码签名,无法自动更新,请前往下载页手动安装最新版本。',
message: '检测到新版本',
title: PRODUCT_NAME,
type: 'info',
});
if (response === 0) {
void shell.openExternal(DOWNLOAD_PAGE_URL);
}
}