Electron 自动更新
electron-updater 简介
electron-updater 是 Electron 应用实现自动更新的最佳方案,支持 Windows、macOS 和 Linux。
特点
- 自动下载更新
- 后台静默更新
- 增量更新支持
- 多种发布平台支持(GitHub、S3、通用服务器)
- 代码签名验证
基础配置
安装依赖
yarn add electron-updater
package.json 配置
{
"name": "my-app",
"version": "1.0.0",
"main": "electron/main.js",
"build": {
"appId": "com.example.myapp",
"productName": "我的应用",
"publish": [
{
"provider": "github",
"owner": "your-username",
"repo": "your-repo"
}
]
}
}
主进程实现
基础更新逻辑
// electron/main.js
const { app, BrowserWindow } = require('electron')
const { autoUpdater } = require('electron-updater')
let mainWindow
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false,
contextIsolation: true
}
})
mainWindow.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
// 应用启动后检查更新
autoUpdater.checkForUpdatesAndNotify()
})
// 更新事件监听
autoUpdater.on('checking-for-update', () => {
console.log('检查更新中...')
})
autoUpdater.on('update-available', (info) => {
console.log('发现新版本:', info.version)
})
autoUpdater.on('update-not-available', () => {
console.log('当前已是最新版本')
})
autoUpdater.on('download-progress', (progress) => {
console.log(`下载进度: ${progress.percent.toFixed(2)}%`)
})
autoUpdater.on('update-downloaded', (info) => {
console.log('更新下载完成')
// 5秒后自动安装
setTimeout(() => {
autoUpdater.quitAndInstall()
}, 5000)
})
autoUpdater.on('error', (err) => {
console.error('更新错误:', err)
})
完整更新管理器
// electron/updater.js
const { autoUpdater } = require('electron-updater')
const { dialog } = require('electron')
class UpdateManager {
constructor(mainWindow) {
this.mainWindow = mainWindow
this.setupAutoUpdater()
}
setupAutoUpdater() {
// 配置更新服务器
autoUpdater.setFeedURL({
provider: 'github',
owner: 'your-username',
repo: 'your-repo'
})
// 禁用自动下载
autoUpdater.autoDownload = false
// 禁用自动安装
autoUpdater.autoInstallOnAppQuit = false
this.bindEvents()
}
bindEvents() {
autoUpdater.on('checking-for-update', () => {
this.sendStatusToWindow('checking-for-update')
})
autoUpdater.on('update-available', (info) => {
this.sendStatusToWindow('update-available', info)
this.showUpdateDialog(info)
})
autoUpdater.on('update-not-available', () => {
this.sendStatusToWindow('update-not-available')
})
autoUpdater.on('download-progress', (progress) => {
this.sendStatusToWindow('download-progress', progress)
})
autoUpdater.on('update-downloaded', (info) => {
this.sendStatusToWindow('update-downloaded', info)
this.showInstallDialog()
})
autoUpdater.on('error', (err) => {
this.sendStatusToWindow('error', err.message)
dialog.showErrorBox('更新错误', err.message)
})
}
sendStatusToWindow(status, data = null) {
if (this.mainWindow) {
this.mainWindow.webContents.send('update-status', { status, data })
}
}
async showUpdateDialog(info) {
const result = await dialog.showMessageBox(this.mainWindow, {
type: 'info',
title: '发现新版本',
message: `发现新版本 ${info.version}`,
detail: info.releaseNotes || '是否立即下载更新?',
buttons: ['稍后', '立即更新'],
defaultId: 1,
cancelId: 0
})
if (result.response === 1) {
autoUpdater.downloadUpdate()
}
}
async showInstallDialog() {
const result = await dialog.showMessageBox(this.mainWindow, {
type: 'info',
title: '更新已下载',
message: '更新已下载完成',
detail: '是否立即重启应用安装更新?',
buttons: ['稍后', '立即重启'],
defaultId: 1,
cancelId: 0
})
if (result.response === 1) {
autoUpdater.quitAndInstall(false, true)
}
}
checkForUpdates() {
autoUpdater.checkForUpdates()
}
downloadUpdate() {
autoUpdater.downloadUpdate()
}
quitAndInstall() {
autoUpdater.quitAndInstall(false, true)
}
}
module.exports = UpdateManager
在主进程中使用
// electron/main.js
const { app, BrowserWindow, ipcMain } = require('electron')
const UpdateManager = require('./updater')
let mainWindow
let updateManager
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false,
contextIsolation: true
}
})
mainWindow.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
// 初始化更新管理器
updateManager = new UpdateManager(mainWindow)
// 延迟检查更新(避免启动时卡顿)
setTimeout(() => {
updateManager.checkForUpdates()
}, 3000)
})
// IPC 通信
ipcMain.handle('check-for-updates', () => {
updateManager.checkForUpdates()
})
ipcMain.handle('download-update', () => {
updateManager.downloadUpdate()
})
ipcMain.handle('quit-and-install', () => {
updateManager.quitAndInstall()
})
渲染进程集成
Preload 脚本
// electron/preload.js
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('updaterAPI', {
checkForUpdates: () => ipcRenderer.invoke('check-for-updates'),
downloadUpdate: () => ipcRenderer.invoke('download-update'),
quitAndInstall: () => ipcRenderer.invoke('quit-and-install'),
onUpdateStatus: (callback) => {
ipcRenderer.on('update-status', (event, data) => callback(data))
}
})
React 更新组件
// src/components/UpdateNotification.jsx
import { useState, useEffect } from 'react'
import './UpdateNotification.css'
function UpdateNotification() {
const [updateInfo, setUpdateInfo] = useState(null)
const [status, setStatus] = useState('')
const [progress, setProgress] = useState(0)
useEffect(() => {
if (!window.updaterAPI) return
window.updaterAPI.onUpdateStatus((data) => {
setStatus(data.status)
switch (data.status) {
case 'update-available':
setUpdateInfo(data.data)
break
case 'download-progress':
setProgress(data.data.percent)
break
case 'update-downloaded':
setUpdateInfo(data.data)
break
}
})
}, [])
const handleCheckUpdate = () => {
window.updaterAPI.checkForUpdates()
}
const handleDownload = () => {
window.updaterAPI.downloadUpdate()
}
const handleInstall = () => {
window.updaterAPI.quitAndInstall()
}
if (status === 'checking-for-update') {
return (
<div className="update-notification">
<p>正在检查更新...</p>
</div>
)
}
if (status === 'update-available' && updateInfo) {
return (
<div className="update-notification">
<h3>发现新版本 {updateInfo.version}</h3>
<p>{updateInfo.releaseNotes}</p>
<button onClick={handleDownload}>立即下载</button>
</div>
)
}
if (status === 'download-progress') {
return (
<div className="update-notification">
<p>正在下载更新...</p>
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${progress}%` }}
/>
</div>
<p>{progress.toFixed(2)}%</p>
</div>
)
}
if (status === 'update-downloaded') {
return (
<div className="update-notification">
<h3>更新已下载完成</h3>
<p>重启应用以安装更新</p>
<button onClick={handleInstall}>立即重启</button>
</div>
)
}
return (
<div className="update-notification">
<button onClick={handleCheckUpdate}>检查更新</button>
</div>
)
}
export default UpdateNotification
Vue 更新组件
<!-- src/components/UpdateNotification.vue -->
<template>
<div class="update-notification">
<div v-if="status === 'checking-for-update'">
<p>正在检查更新...</p>
</div>
<div v-else-if="status === 'update-available' && updateInfo">
<h3>发现新版本 {{ updateInfo.version }}</h3>
<p>{{ updateInfo.releaseNotes }}</p>
<button @click="handleDownload">立即下载</button>
</div>
<div v-else-if="status === 'download-progress'">
<p>正在下载更新...</p>
<div class="progress-bar">
<div
class="progress-fill"
:style="{ width: `${progress}%` }"
/>
</div>
<p>{{ progress.toFixed(2) }}%</p>
</div>
<div v-else-if="status === 'update-downloaded'">
<h3>更新已下载完成</h3>
<p>重启应用以安装更新</p>
<button @click="handleInstall">立即重启</button>
</div>
<div v-else>
<button @click="handleCheckUpdate">检查更新</button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const status = ref('')
const updateInfo = ref(null)
const progress = ref(0)
onMounted(() => {
if (!window.updaterAPI) return
window.updaterAPI.onUpdateStatus((data) => {
status.value = data.status
switch (data.status) {
case 'update-available':
updateInfo.value = data.data
break
case 'download-progress':
progress.value = data.data.percent
break
case 'update-downloaded':
updateInfo.value = data.data
break
}
})
})
const handleCheckUpdate = () => {
window.updaterAPI.checkForUpdates()
}
const handleDownload = () => {
window.updaterAPI.downloadUpdate()
}
const handleInstall = () => {
window.updaterAPI.quitAndInstall()
}
</script>
<style scoped>
.update-notification {
padding: 20px;
background: #f5f5f5;
border-radius: 8px;
}
.progress-bar {
width: 100%;
height: 20px;
background: #e0e0e0;
border-radius: 10px;
overflow: hidden;
margin: 10px 0;
}
.progress-fill {
height: 100%;
background: #4caf50;
transition: width 0.3s;
}
button {
padding: 8px 16px;
background: #2196f3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #1976d2;
}
</style>
发布配置
GitHub Releases
{
"build": {
"publish": [
{
"provider": "github",
"owner": "your-username",
"repo": "your-repo",
"releaseType": "release"
}
]
}
}
私有服务器
{
"build": {
"publish": [
{
"provider": "generic",
"url": "https://your-server.com/updates"
}
]
}
}
Amazon S3
{
"build": {
"publish": [
{
"provider": "s3",
"bucket": "your-bucket",
"region": "us-east-1"
}
]
}
}
发布流程
1. 更新版本号
{
"version": "1.0.1"
}
2. 构建应用
yarn electron:build
3. 发布到 GitHub
# 使用 GitHub Token
export GH_TOKEN="your-github-token"
yarn electron:build --publish always
4. 创建 Release
在 GitHub Releases 页面创建新版本,上传构建产物:
app-1.0.1.exe(Windows)app-1.0.1.dmg(macOS)app-1.0.1.AppImage(Linux)latest.yml/latest-mac.yml(更新配置文件)
增量更新
Windows NSIS 配置
{
"build": {
"win": {
"target": [
{
"target": "nsis",
"arch": ["x64"]
}
]
},
"nsis": {
"differentialPackage": true
}
}
}
macOS 配置
{
"build": {
"mac": {
"target": ["dmg", "zip"]
}
}
}
代码签名
Windows 签名
{
"build": {
"win": {
"certificateFile": "cert.pfx",
"certificatePassword": "password",
"signingHashAlgorithms": ["sha256"],
"sign": "./sign.js"
}
}
}
macOS 签名
{
"build": {
"mac": {
"identity": "Developer ID Application: Your Name (TEAM_ID)",
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "build/entitlements.mac.plist",
"entitlementsInherit": "build/entitlements.mac.plist"
}
}
}
更新策略
强制更新
autoUpdater.on('update-available', async (info) => {
const result = await dialog.showMessageBox({
type: 'info',
title: '必须更新',
message: '发现新版本,必须更新后才能继续使用',
buttons: ['立即更新'],
defaultId: 0
})
autoUpdater.downloadUpdate()
})
autoUpdater.on('update-downloaded', () => {
autoUpdater.quitAndInstall(false, true)
})
静默更新
autoUpdater.autoDownload = true
autoUpdater.autoInstallOnAppQuit = true
app.whenReady().then(() => {
autoUpdater.checkForUpdatesAndNotify()
})
定时检查
// 每小时检查一次
setInterval(() => {
autoUpdater.checkForUpdates()
}, 60 * 60 * 1000)
版本比较
const semver = require('semver')
autoUpdater.on('update-available', (info) => {
const currentVersion = app.getVersion()
const newVersion = info.version
// 主版本更新(强制)
if (semver.major(newVersion) > semver.major(currentVersion)) {
// 强制更新
autoUpdater.downloadUpdate()
}
// 次版本更新(推荐)
else if (semver.minor(newVersion) > semver.minor(currentVersion)) {
// 提示用户更新
showUpdateDialog(info)
}
// 补丁更新(可选)
else {
// 静默下载
autoUpdater.downloadUpdate()
}
})
测试更新
本地测试
// electron/main.js
if (process.env.NODE_ENV === 'development') {
autoUpdater.updateConfigPath = path.join(__dirname, 'dev-app-update.yml')
}
# dev-app-update.yml
provider: generic
url: http://localhost:8080/updates
启动本地服务器
# 在 dist 目录启动服务器
cd dist
python -m http.server 8080
错误处理
autoUpdater.on('error', (error) => {
console.error('更新错误:', error)
// 网络错误
if (error.message.includes('net::')) {
dialog.showErrorBox('网络错误', '无法连接到更新服务器')
}
// 签名验证失败
else if (error.message.includes('signature')) {
dialog.showErrorBox('安全错误', '更新包签名验证失败')
}
// 其他错误
else {
dialog.showErrorBox('更新错误', error.message)
}
})
日志记录
const log = require('electron-log')
autoUpdater.logger = log
autoUpdater.logger.transports.file.level = 'info'
// 日志位置
// macOS: ~/Library/Logs/your-app/main.log
// Windows: %USERPROFILE%\AppData\Roaming\your-app\logs\main.log
// Linux: ~/.config/your-app/logs/main.log
完整示例
查看完整的自动更新示例: