Files
ChatLab/electron/main/update.ts
T
2025-12-24 22:14:02 +08:00

210 lines
6.4 KiB
TypeScript

import { dialog, app } from 'electron'
import { autoUpdater } from 'electron-updater'
import { platform } from '@electron-toolkit/utils'
import { logger } from './logger'
let isFirstShow = true
let isManualCheck = false
export const setManualCheck = (value: boolean) => {
isManualCheck = value
}
/**
* 比较两个版本号,判断是否为中版本(Minor)或大版本(Major)更新
* @param current 当前版本
* @param latest 最新版本
* @returns true if Major or Minor update, false if Patch update or same/older
*/
const isMajorOrMinorUpdate = (current: string, latest: string): boolean => {
try {
const [cMajor, cMinor] = current.split('.').map(Number)
const [lMajor, lMinor] = latest.split('.').map(Number)
if (lMajor > cMajor) return true
if (lMajor === cMajor && lMinor > cMinor) return true
return false
} catch (e) {
console.error('Version compare error:', e)
return true // Fallback to show update if parse fails
}
}
const checkUpdate = (win) => {
autoUpdater.autoDownload = false // 自动下载
autoUpdater.autoInstallOnAppQuit = true // 应用退出后自动安装
// 开发模式下模拟更新检测(需要创建 dev-app-update.yml 文件)
// 取消下面的注释来启用开发模式更新测试
// if (!app.isPackaged) {
// Object.defineProperty(app, 'isPackaged', {
// get() {
// return true
// },
// })
// }
let showUpdateMessageBox = false
autoUpdater.on('update-available', (info) => {
// win.webContents.send('show-message', 'electron:发现新版本')
if (showUpdateMessageBox) return
// 如果不是手动检查,且不是大版本或中版本更新(即小版本更新),则忽略
if (!isManualCheck && !isMajorOrMinorUpdate(app.getVersion(), info.version)) {
console.log(`[Update] Ignore patch update: ${app.getVersion()} -> ${info.version}`)
isManualCheck = false
return
}
// 重置手动检查标记
isManualCheck = false
showUpdateMessageBox = true
// 解析更新日志
let releaseNotes = ''
if (info.releaseNotes) {
if (typeof info.releaseNotes === 'string') {
releaseNotes = info.releaseNotes
} else if (Array.isArray(info.releaseNotes)) {
releaseNotes = info.releaseNotes.map((note) => note.note || note).join('\n')
}
// 简单清理 HTML 标签,合并连续空行,截断下载说明
releaseNotes = releaseNotes
.replace(/<[^>]*>/g, '')
.replace(/\n{2,}/g, '\n')
.trim()
// 如果包含"下载说明",截断该部分及之后的内容
const downloadGuideIndex = releaseNotes.indexOf('下载说明')
if (downloadGuideIndex > 0) {
releaseNotes = releaseNotes.substring(0, downloadGuideIndex).trim()
}
}
const detail = releaseNotes
? `更新内容:\n${releaseNotes}\n\n是否立即下载并安装新版本?`
: '是否立即下载并安装新版本?'
dialog
.showMessageBox({
title: '发现新版本 v' + info.version,
message: '发现新版本 v' + info.version,
detail,
buttons: ['立即下载', '取消'],
defaultId: 0,
cancelId: 1,
type: 'question',
noLink: true,
})
.then((result) => {
showUpdateMessageBox = false
if (result.response === 0) {
autoUpdater
.downloadUpdate()
.then(() => {
console.log('wait for post download operation')
})
.catch((downloadError) => {
// 下载失败记录到日志,不显示给用户
logger.error(`[Update] 下载更新失败: ${downloadError}`)
})
}
})
})
// 监听下载进度事件
autoUpdater.on('download-progress', (progressObj) => {
console.log(`更新下载进度: ${progressObj.percent}%`)
win.webContents.send('update-download-progress', progressObj.percent)
})
// 下载完成
autoUpdater.on('update-downloaded', () => {
dialog
.showMessageBox({
title: '下载完成',
message: '新版本已准备就绪,是否现在安装?',
buttons: ['安装', platform.isMacOS ? '之后提醒' : '稍后(应用退出后自动安装)'],
defaultId: 1,
cancelId: 2,
type: 'question',
})
.then((result) => {
if (result.response === 0) {
win.webContents.send('begin-install')
// @ts-ignore
app.isQuiting = true
setTimeout(() => {
setImmediate(() => {
autoUpdater.quitAndInstall()
})
}, 100)
}
})
})
// 不需要更新
autoUpdater.on('update-not-available', (info) => {
// 客户端打开会默认弹一次,用isFirstShow来控制不弹
if (isFirstShow) {
isFirstShow = false
} else {
// 只有手动检查才提示"已是最新版本"
if (isManualCheck) {
win.webContents.send('show-message', {
type: 'success',
message: '已是最新版本',
})
}
}
isManualCheck = false
})
// 错误处理(静默处理,记录到日志)
autoUpdater.on('error', (err) => {
// 更新错误记录到日志,不显示给用户
logger.error(`[Update] 更新错误: ${err.message || err}`)
isManualCheck = false
})
// 等待 3 秒再检查更新,确保窗口准备完成,用户进入系统
setTimeout(() => {
autoUpdater.checkForUpdates().catch((err) => {
console.log('[Update] 检查更新失败:', err)
})
}, 3000)
}
/**
* 模拟更新弹窗(仅用于开发测试)
* 控制台通过:window.api.app.simulateUpdate() 测试
*/
const simulateUpdateDialog = (win) => {
const mockInfo = {
version: '9.9.9',
releaseNotes: `## 更新内容\n\n- 🎉 新增聊天记录查看器\n- 🔧 修复已知问题\n- ⚡️ 性能优化`,
}
// 解析更新日志
let releaseNotes = mockInfo.releaseNotes.replace(/<[^>]*>/g, '').trim()
const detail = releaseNotes
? `更新内容:\n${releaseNotes}\n\n是否立即下载并安装新版本?`
: '是否立即下载并安装新版本?'
dialog.showMessageBox({
title: '发现新版本 v' + mockInfo.version,
message: '发现新版本 v' + mockInfo.version,
detail,
buttons: ['立即下载', '取消'],
defaultId: 0,
cancelId: 1,
type: 'question',
noLink: true,
})
}
export { checkUpdate, simulateUpdateDialog }