自动更新

深入理解 Electron 自动更新机制(electron-updater)、更新服务器配置、以及更新流程的完整实现。


自动更新概述

Electron 应用使用 electron-updater 库实现自动更新,它支持:

  • 增量更新(下载差量包)
  • 静默下载,后台更新
  • 用户确认后再安装
  • 更新回滚

核心概念

  • Publisher:发布更新包的服务器
  • Update Server:托管更新包的服务器(GitHub、AWS S3、Nexus 等)
  • Client:应用内置的更新检查和下载逻辑

安装 electron-updater

npm install electron-updater

基本配置

const { autoUpdater } = require('electron-updater');
const { ipcMain } = require('electron');

// 配置日志
autoUpdater.logger = require('electron-log');

// 开发环境禁用自动更新
if (process.env.NODE_ENV === 'development') {
  autoUpdater.autoUpdate = false;
}

更新服务器配置

GitHub Releases

最常用的更新服务器,免费且配置简单:

const { autoUpdater } = require('electron-updater');

autoUpdater.configure({
  provider: 'github',
  owner: 'your-username',
  repo: 'your-repo'
});

通用服务器(S3、Nexus、自建)

autoUpdater.configure({
  provider: 'generic',
  url: 'https://update.example.com/'  // 更新包托管地址
});

配合 electron-builder 使用

package.json 中配置:

{
  "build": {
    "publish": [
      {
        "provider": "github",
        "owner": "your-username",
        "repo": "your-repo"
      }
    ]
  }
}

更新流程

1. 检查更新

// main.js
const { autoUpdater } = require('electron-updater');

function checkForUpdates() {
  // 返回一个 Promise
  return autoUpdater.checkForUpdates();
}

// 检查并下载
function checkAndDownload() {
  return autoUpdater.checkForUpdates()
    .then(result => {
      console.log('Update info:', result);
      return result;
    })
    .catch(err => {
      console.error('Update check failed:', err);
    });
}

2. 监听更新事件

// 监听检查更新事件
autoUpdater.on('checking-for-update', () => {
  console.log('Checking for updates...');
  mainWindow.webContents.send('update:checking');
});

// 监听有可用更新
autoUpdater.on('update-available', (info) => {
  console.log('Update available:', info.version);
  mainWindow.webContents.send('update:available', info);
});

// 监听没有可用更新
autoUpdater.on('update-not-available', (info) => {
  console.log('Update not available. Current version is latest.');
  mainWindow.webContents.send('update:not-available', info);
});

// 监听下载进度
autoUpdater.on('download-progress', (progress) => {
  const percent = Math.round(progress.percent);
  console.log(`Downloaded ${percent}%`);

  mainWindow.webContents.send('update:progress', {
    percent,
    bytesPerSecond: progress.bytesPerSecond,
    transferred: progress.transferred,
    total: progress.total
  });
});

// 监听下载完成
autoUpdater.on('update-downloaded', (info) => {
  console.log('Update downloaded:', info.version);
  mainWindow.webContents.send('update:downloaded', info);
});

// 监听错误
autoUpdater.on('error', (err) => {
  console.error('Update error:', err);
  mainWindow.webContents.send('update:error', err.message);
});

3. 下载并安装更新

// 下载更新
async function downloadUpdate() {
  try {
    const result = await autoUpdater.downloadUpdate();
    return result;
  } catch (err) {
    console.error('Download failed:', err);
    throw err;
  }
}

// 安装更新并重启
function installUpdate() {
  autoUpdater.quitAndInstall();
}

渲染进程更新 UI

IPC 通信

// main.js - 处理渲染进程的更新请求
ipcMain.handle('update:check', async () => {
  try {
    const result = await autoUpdater.checkForUpdates();
    return { success: true, info: result?.updateInfo };
  } catch (err) {
    return { success: false, error: err.message };
  }
});

ipcMain.handle('update:download', async () => {
  try {
    await autoUpdater.downloadUpdate();
    return { success: true };
  } catch (err) {
    return { success: false, error: err.message };
  }
});

ipcMain.handle('update:install', () => {
  autoUpdater.quitAndInstall();
});

Preload API

// preload.js
contextBridge.exposeInMainWorld('electronAPI', {
  checkForUpdates: () => ipcRenderer.invoke('update:check'),
  downloadUpdate: () => ipcRenderer.invoke('update:download'),
  installUpdate: () => ipcRenderer.invoke('update:install'),

  // 监听更新事件
  onUpdateAvailable: (callback) => {
    ipcRenderer.on('update:available', (event, info) => callback(info));
  },
  onUpdateProgress: (callback) => {
    ipcRenderer.on('update:progress', (event, progress) => callback(progress));
  },
  onUpdateDownloaded: (callback) => {
    ipcRenderer.on('update:downloaded', (event, info) => callback(info));
  }
});

React 组件示例

// UpdateButton.jsx
import React, { useState, useEffect } from 'react';

function UpdateButton() {
  const [status, setStatus] = useState('idle');  // idle, checking, available, downloading, ready
  const [progress, setProgress] = useState(0);
  const [updateInfo, setUpdateInfo] = useState(null);

  useEffect(() => {
    const api = window.electronAPI;

    api.onUpdateAvailable((info) => {
      setStatus('available');
      setUpdateInfo(info);
    });

    api.onUpdateProgress((progress) => {
      setStatus('downloading');
      setProgress(progress.percent);
    });

    api.onUpdateDownloaded((info) => {
      setStatus('ready');
    });
  }, []);

  const handleCheck = async () => {
    setStatus('checking');
    await window.electronAPI.checkForUpdates();
  };

  const handleDownload = async () => {
    await window.electronAPI.downloadUpdate();
  };

  const handleInstall = () => {
    window.electronAPI.installUpdate();
  };

  return (
    <div>
      {status === 'idle' && <button onClick={handleCheck}>检查更新</button>}

      {status === 'checking' && <span>检查中...</span>}

      {status === 'available' && (
        <div>
          <span>发现新版本 {updateInfo?.version}</span>
          <button onClick={handleDownload}>下载更新</button>
        </div>
      )}

      {status === 'downloading' && (
        <div>
          <span>下载中 {progress}%</span>
          <progress value={progress} max="100" />
        </div>
      )}

      {status === 'ready' && (
        <div>
          <span>下载完成</span>
          <button onClick={handleInstall}>安装并重启</button>
        </div>
      )}
    </div>
  );
}

签名与安全

macOS 代码签名

// electron-builder 配置
{
  "build": {
    "mac": {
      "identity": "Developer ID Application: Your Name (TEAM_ID)",
      "hardened-runtime": true,
      "entitlements": "entitlements.plist",
      "gatekeeper-assess": false
    }
  }
}

Windows 代码签名

{
  "build": {
    "win": {
      "certificateFile": "./certificate.pfx",
      "certificatePassword": "your-password"
    }
  }
}

更新包签名验证

electron-updater 会自动验证更新包的签名:

const { autoUpdater } = require('electron-updater');

// 确保签名验证
autoUpdater.autoDownload = false;  // 先下载,验证后再安装

高级配置

差量更新

只下载变化的部分,减少更新包大小:

autoUpdater.fullChangelog = false;  // 使用差量更新

限制更新范围

// 只检查 beta 版本
autoUpdater.allowPrerelease = true;

// 或者指定版本范围
autoUpdater.versionInfoToReturn = {
  version: '2.0.0-beta.1',
  releaseDate: new Date(),
  releaseNotes: 'Beta release'
};

自定义更新检查

// 使用私有 API 进行自定义检查
async function customUpdateCheck() {
  const response = await fetch('https://api.example.com/latest-version');
  const data = await response.json();

  const currentVersion = app.getVersion();

  if (compareVersions(data.version, currentVersion) > 0) {
    // 有新版本,手动设置更新信息
    autoUpdater.updateInfo = {
      version: data.version,
      releaseDate: data.releaseDate,
      releaseNotes: data.releaseNotes,
      downloadUrl: data.downloadUrl
    };
  }
}

调试更新功能

开发环境禁用自动更新

if (process.env.NODE_ENV === 'development' || !app.isPackaged) {
  autoUpdater.autoDownload = false;
  autoUpdater.autoInstallOnAppQuit = false;
}

强制更新

// 检查更新前先清除缓存
autoUpdater.cache = null;

模拟更新流程

// 在开发环境中模拟更新事件
if (process.env.NODE_ENV === 'development') {
  setTimeout(() => {
    mainWindow.webContents.send('update:available', {
      version: '2.0.0',
      releaseNotes: 'New features'
    });
  }, 3000);
}

常见问题与解决方案

1. 更新检查失败

  • 检查网络连接
  • 确认 GitHub Releases 格式正确
  • 查看 electron-updater 日志

2. 下载后无法安装

  • Windows:确保应用有写入权限
  • macOS:确保代码签名正确

3. 版本号格式

推荐使用 semver 格式:1.0.02.1.0-beta.1

4. 更新回滚

electron-updater 不直接支持回滚,可以通过以下方式实现:

// 在安装前备份当前版本
function backupCurrentVersion() {
  const currentExe = process.execPath;
  const backupPath = path.join(app.getPath('userData'), 'previous-version.exe');
  fs.copyFileSync(currentExe, backupPath);
}

这一章想说的

Electron 自动更新是一个完整的系统工程:

  1. 更新服务器:GitHub Releases 是最简单选择
  2. 更新流程:检查 → 下载 → 安装
  3. 用户界面:通过 IPC 让渲染进程控制更新
  4. 安全:代码签名和包验证
  5. 调试:开发环境需要特殊处理

完善的自动更新机制能提升用户体验,确保用户始终使用最新版本。


延展阅读