记为Electron应用添加上自动更新

electron

在最近的一个项目中用Electron包装了一个网站,生成了一个桌面应用。虽说渲染进程内容是被包装的网站,而网站的更新很灵活,但也不排除Electron主进程有时也会有需要更新的时候,因此,为Electron加上自动更新吧。

首先,我们要引入 autoUpdater  。electron里有一个 autoUpdater  但是这里我们使用electron-updater库里的autoUpdater。(PS:和原生autoUpdater的差别

先是把 electron-updater 作用应用依赖去安装。
yarn add electron-updater

配置package.json中的publish设置

...
"build": {
    "productName": "我的Electron应用",
    "copyright": "Copyright © 2019 Lunastudio.cn",
    "appId": "cn.lunastudio.myapp",
    "publish": {
      "provider": "generic",//这里我们使用普通更新提供器,我这里用的是腾讯COS
      "url": "https://upload-123456.cos.ap-chengdu.myqcloud.com/myApp/update/" 
      //这里的地址就是cos的某个存放安装包的文件夹的目录地址,我们后边生成的安装包要扔到这上边来
    },
    "win": {
      "target": "nsis"
    },
    "nsis": {
      "perMachine": true
    }
  },
...

然后再去主程序中来写autoUpdater的相关逻辑

const {app, BrowserWindow, globalShortcut, session, dialog} = require('electron')
const {autoUpdater} = require("electron-updater")
let win;
//保持单窗口模式。只有第一个窗口的应用才会有返回单例锁
const shouldQuit = app.requestSingleInstanceLock();
if (!shouldQuit) {
//没有正常返回的非第一个窗口的应用程序,就直接退出,以免多开
    app.quit()
}

function createWindow() {
    // 创建浏览器窗口。
    win = new BrowserWindow({
        webPreferences: {
            nodeIntegration: true
        }
    })
//使用electron-log,方便查看更新日志。
//日志被存放在
//on Linux: ~/.config/<app name>/log.log
//on macOS: ~/Library/Logs/<app name>/log.log
//on Windows: %USERPROFILE%\AppData\Roaming\<app name>\log.log
 const log = require("electron-log")
    log.transports.file.level = "debug"
    autoUpdater.logger = log
    autoUpdater.allowDowngrade = true;
    autoUpdater.autoInstallOnAppQuit = true;
//窗口一建立,就开始检查是否需要更新
    autoUpdater.checkForUpdatesAndNotify()

win.loadURL('https://mysite.com/index.html')
}
app.on('ready', createWindow)

//可以通过autoUpdater.on()这种事件监听的方式来处理,也可以直接用signals回调的方式,这里我用的是后者
autoUpdater.signals.progress(({total,
    delta,
    transferred,
    percent,
    bytesPerSecond}) => {
//显示加载进度条
    showProgressBar(percent);
})

autoUpdater.signals.updateDownloaded(({version, releaseNotes, releaseName}) => {

//在下载完后,显示一个对话框,提示安装
    const dialogOpts = {
        type: 'info',
        buttons: ['开始安装'],
        title: '程序更新',
        message: `v${version}程序已经下载好了,点击“重启安装”`
    }
    dialog.showMessageBox(dialogOpts, (response) => {
       autoUpdater.quitAndInstall(true)
    })
})

//显示进度条
let progressBar, progressBarReady
function showProgressBar(progress) {
    if (!progressBar) {
        progressBar = new ProgressBar({
            //对于可以指明加载进度值的,要把这里的值设为false
            indeterminate: false,
            title: '正在更新新版本',
            text: '新版本下载中...',
            closeOnComplete: true,
            abortOnError: true,
            style: {
                text: {
                    "overflow-y": 'hidden'
                }
            },
            browserWindow: {
                modal: true,
                parent: null,
                resizable: false,
                closable: false,
                minimizable: false,
                maximizable: false,
                //注意这里的配置,如果不写出来,会导致进度条显示不出来
                //@see https://github.com/AndersonMamede/electron-progressbar/issues/10
                webPreferences: {
                    nodeIntegration: true
                }
            }
        });

        progressBar
            .on('ready', function () {
                progressBarReady = true;
            })
            .on('completed', function () {
                console.info(`completed...`);
                progressBar.text = '下载完成,即将安装!';
                progressBarReady = false;
            }).on('progress', function (value) {
                value = Math.round(value);
                progressBar.text = `新版本下载中...${value}%`;
            });
    }
    //更新进度值
    progressBarReady && (progressBar.value = progress);
}
 

主程序差不多就是这个样子。package.json中写一个生成安装包的运行脚本

...
  "scripts": {
    "start": "electron index.js",
    "installer_build_win32": "electron-builder --win --ia32",
  }
...

当成运行npm run installer_build_win32的时候,就会在dist目录中生成对应版本的安装程序和blockmap文件,以及一个latest.yml文件。这三个文件我们都需要上传到上边提到的腾讯云COS文件夹中。当主程序在启动时执行app.requestSingleInstanceLock方法时,会向package.json中的publish里设定的目录中去找latest.yml文件。
如:https://upload-123456.cos.ap-chengdu.myqcloud.com/myApp/update/latest.yml
对比发现版本号有更新,就会自动开始下载新的安装文件。所以我们这里为了方便一点,需要在生成安装包后,立即把那三个新版本的相关文件上传上去。因为我使用的COS,所以就结合COS的SDK写了一个简单的脚本。

//uploader.js
const COS = require('cos-nodejs-sdk-v5');
const md5File = require('md5-file/promise')
const fs = require('fs');



const config = {
    SecretId: <COS_SECRET_ID>,
    SecretKey: <COS_SECRET_KEY>,
    Bucket: <COS_BUCKET>,
    Region: <COS_REGION>,
    ProgressInterval: 1000
};

async function uploadFile(file) {
    let cos = new COS(config);
    var fileStat = fs.statSync(file);
    var md5str = await md5File(file);
    if (uploadLog[md5str]) {
        return Promise.resolve(file);
    }

    uploadLog[md5str] = 1;
//上传到myApp/update目录下
    let key ='myApp/update/' + path.basename(file)

    return new Promise(resolve => {
        cos.putObject({
            Bucket: config.Bucket, 
            Region: config.Region,
            Method: 'PUT',
            Key: key,
            Body: fs.createReadStream(file),
            ContentLength: fileStat.size,
            onProgress: (progressData) => {
                console.log('uploading...', path.basename(file), Math.round(progressData.percent * 100) + '%')
            }
        }, function (err, data) {
            resolve(file);
        });
    })

}

function uploadFiles(files) {
    (files.map(uploadFile)).forEach(p => p.then(function (file) {
        console.log('uploaded file:', file);
        require("fs").writeFileSync('uploadLog.json', JSON.stringify(uploadLog), {encoding: 'utf-8'})
    }))
}


const {version} = require('./package.json');
const uploadLog = require('./uploadLog.json')

const path = require('path')
let files = [];
//新版本的三个关键文件
files.push(path.resolve(__dirname, './dist/latest.yml'))
files.push(path.resolve(__dirname, `./dist/myApp Setup ${version}.exe`))
files.push(path.resolve(__dirname, `./dist/myApp Setup ${version}.exe.blockmap`))
//开始上传
uploadFiles(files);

最后就把“installer_build_win32”: “electron-builder –win –ia32”改成“installer_build_win32”: “electron-builder –win –ia32″ && node ./uploader.js”
这样就可以很方便地在打包完成就自动上传到COS服务器了。

最后,当程序启动时,发现有新版本时,就会提示

留下评论

Your email address will not be published. Required fields are marked *