Electron 版本自动更新

3,006 阅读4分钟

1. 安装 electron-updater

在项目中安装 electron-updater 包

npm i electron-updater -S

2. 配置package.json

在构建electron项目的build配置项下增加publish相关配置,provider设定为genericurl为新版本文件存放的地址。这个最终会用来生成latest.yml版本描述文件,auto-update工作时,会查询该文件中的版本信息来判断是否有新版本可供下载。

{
  "name": "electron_upgrade_test",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "pack": "electron-builder --dir",
    "dist": "electron-builder",
    "release": "build"
  },
  "author": "",
  "license": "ISC",
  "build": {
    "appId": "your.id",
    "mac": {
      "category": "penghk.app.category.type"
    },
    "publish": [
      {
        "provider": "generic",
        "url": "http://127.0.0.1:3002/download/"
      }
    ]
  },
  "devDependencies": {
    "electron": "^7.1.7",
    "electron-builder": "^21.2.0"
  },
  "dependencies": {
    "electron-log": "^4.0.2",
    "electron-updater": "^4.2.0"
  }
}

3. 创建 main.js 主进程文件

updateHandle函数用于监听版本更新相关的事件

const { app, BrowserWindow, ipcMain } = require('electron')
const { autoUpdater } = require("electron-updater")

function updateHandle() {
  autoUpdater.on('error', function (error) {
    sendUpdateMessage('检查更新出错 %s', error)
  });
  autoUpdater.on('checking-for-update', function () {
    sendUpdateMessage('正在检查更新…')
  });
  autoUpdater.on('update-available', function (info) {
    sendUpdateMessage('检测到新版本,正在下载…')
  });
  autoUpdater.on('update-not-available', function (info) {
    sendUpdateMessage('现在使用的已经是最新版本')
  });

  // 更新下载进度事件
  autoUpdater.on('download-progress', function (progressObj) {
    win.webContents.send('downloadProgress', progressObj)
  });
  
  autoUpdater.on('update-downloaded', function (event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) {
    // 渲染层回复立即更新,则自动退出当前程序,然后进行程序更新
    ipcMain.on('updateNow', (e, arg) => {
      console.log("开始更新");
      autoUpdater.quitAndInstall();
    });

    // 询问渲染层是否立即更新
    win.webContents.send('isUpdateNow')
  });

  ipcMain.on("checkForUpdate",()=> {
    // 执行自动更新检查
    // 这里有两种"checkForUpdates"和"checkForUpdatesAndNotify"
    // 区别在于checkForUpdatesAndNotify检查到新版本时会调用系统的通知组件通知用户有新版本可供下载
    // 大家可以根据实际需要自行选择
    // autoUpdater.checkForUpdates();
    autoUpdater.checkForUpdatesAndNotify();
  });
}

// 通过main进程发送事件给renderer进程,提示更新信息
function sendUpdateMessage(text) {
  win.webContents.send('message', text)
}

// 保持对window对象的全局引用,如果不这么做的话,当JavaScript对象被
// 垃圾回收的时候,window对象将会自动的关闭
let win

function createWindow () {
  // 创建浏览器窗口。
  win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  })

  // 加载index.html文件,拼接版本信息到URL的hash中,便于渲染层获取当前版本
  win.loadURL(`file://${__dirname}/index.html#v${app.getVersion()}`);

  // 打开开发者工具
  win.webContents.openDevTools()

  // 当 window 被关闭,这个事件会被触发。
  win.on('closed', () => {
    // 取消引用 window 对象,如果你的应用支持多窗口的话,
    // 通常会把多个 window 对象存放在一个数组里面,
    // 与此同时,你应该删除相应的元素。
    win = null
  });

  // 监听更新相关事件
  updateHandle();
}

// Electron 会在初始化后并准备
// 创建浏览器窗口时,调用这个函数。
// 部分 API 在 ready 事件触发后才能使用。
app.on('ready', createWindow)

// 当全部窗口关闭时退出。
app.on('window-all-closed', () => {
  // 在 macOS 上,除非用户用 Cmd + Q 确定地退出,
  // 否则绝大部分应用及其菜单栏会保持激活。
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', () => {
  // 在macOS上,当单击dock图标并且没有其他窗口打开时,
  // 通常在应用程序中重新创建一个窗口。
  if (win === null) {
    createWindow()
  }
})

4. 创建 index.html 渲染层视图文件

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta charset="UTF-8">
    <title>Version Auto Upgrade Demo</title>
</head>
<body>
    <h1>Version <span id="version"></span></h1>
    <div id="container"></div>
    <script>
        // 更新版本信息
        const version = window.location.hash.substring(1);
        document.getElementById('version').innerText = version;

        // 消息容器
        const container = document.getElementById('container');

        const { ipcRenderer } = require('electron');
        ipcRenderer.send("checkForUpdate");
        ipcRenderer.on("message", (event, text) => {
            const message = document.createElement('div');
            message.innerText = text;
            container.appendChild(message);
        });
        
        // 注意:“downloadProgress”事件可能存在无法触发的问题,
        // 因为如果安装文件过小的话,很快就下载完成,导致没能达到触发条件。
        ipcRenderer.on("downloadProgress", (event, { percent })=> {
            console.log(percent);
        });

        // 接收到主进程有新的版本已经下载完成,询问是否更新。
        ipcRenderer.on("isUpdateNow", () => {
            if(confirm('确定要现在升级吗?')){
                ipcRenderer.send("updateNow");
            }
        });
    </script>
</body>
</html>

附录1:快速搭建本地静态文件服务器

搭建本地静态文件服务器,方便本地调试。

  • 在项目目录下创建静态文件存放目录mkdir -p update_server/download
  • 全局安装http-servernpm i http-server -g
  • 启动文件服务器http-server ./update_server -p 3002
  • 将打包好的新版本文件放入download目录
  • 检查是否可以正常访问 http://127.0.0.1:3002/download,如下图

附录2:latest-mac.yml 示例

version为版本字段,来源于打包时项目中的package.json文件的 version字段,如果用户当前程序版本低于该版本,则提示用户进行更新。

sha512字段为对应文件的Hash值,用于校对文件的完整合法性。

version: 1.0.1
files:
  - url: electron_upgrade_test-1.0.1-mac.zip
    sha512: w5kTV0E2duVr1zw2ZPhJZncWXXwY6Muu6gCGpb3CNQWwqtM/WlCfKMsIIMCZH/FHeoDxF3hJhZR6yUIJSc02fg==
    size: 62878761
    blockMapSize: 67301
  - url: electron_upgrade_test-1.0.1.dmg
    sha512: x6ySqvUl8NXZ88rAnn64J36ra9H+Tgecp/QG3MtaNZgFAsp8BEl4UJT8HIXTpFmmMLxpcfVAdhE7Aibe8xQc9Q==
    size: 64808426
path: electron_upgrade_test-1.0.1-mac.zip
sha512: w5kTV0E2duVr1zw2ZPhJZncWXXwY6Muu6gCGpb3CNQWwqtM/WlCfKMsIIMCZH/FHeoDxF3hJhZR6yUIJSc02fg==
releaseDate: '2020-01-03T06:36:08.231Z'

最后,文章写得有点仓促,不免会出现不恰当的地方,欢迎大家评论区加以指正,谢谢!