Electron 自動(dòng)更新,繞過(guò) latest.yml 使用自定義接口
大家好,我是楊成功。
在上一篇文章《手?jǐn)] Electron 自動(dòng)更新,再繁瑣也要搞懂它》中,我們?cè)敿?xì)介紹了 Electron 自動(dòng)更新的全流程。
簡(jiǎn)單來(lái)說(shuō),就是打包生成 latest.yml 文件和安裝包,并上傳到服務(wù)器??蛻?hù)端打開(kāi)時(shí)訪(fǎng)問(wèn)服務(wù)端的 latest.yml 地址,判斷是否有新版本,有則自動(dòng)下載新包并更新。
對(duì)于以上的更新流程,很多朋友在評(píng)論區(qū)留言表示不夠靈活,比如:
- 只能自動(dòng)更新嗎?能不能通過(guò)后端接口來(lái)判斷是否更新?
- 必須要用 latest.yml 嗎?能不能繞開(kāi)它?
- 如何區(qū)分 Window、Mac、Mac M1 三種安裝包?
下面我們一一解答這些問(wèn)題,并手?jǐn)]一個(gè)更靈活的自動(dòng)更新接口。
如何本地測(cè)試更新?
在開(kāi)發(fā)階段想測(cè)試一下檢測(cè)更新的流程走沒(méi)走通,可能不太好測(cè)試,因?yàn)?Electron 默認(rèn)在開(kāi)發(fā)環(huán)境下會(huì)繞過(guò)更新檢測(cè)。
開(kāi)發(fā)環(huán)境下 Electron 啟動(dòng)后,如果接入了自動(dòng)更新,主進(jìn)程控制臺(tái)會(huì)打印下面的信息:
Skip checkForUpdates because application is not packed and dev update config is not forced
意思是當(dāng)前是開(kāi)發(fā)環(huán)境,未打包,所以繞過(guò)檢測(cè)。Electron 通過(guò) app.isPackaged
的值來(lái)判斷是否打包,那么在開(kāi)發(fā)環(huán)境下,我們可以修改一下這個(gè)值:
import { app } from 'electron';
// 未打包時(shí)是開(kāi)發(fā)環(huán)境
if (!app.isPackaged) {
Object.defineProperty(app, 'isPackaged', {
get: () => true,
});
}
重新運(yùn)行,大概率會(huì)看到第二個(gè)錯(cuò)誤:
Error: ENOENT: no such file or directory /xxxx/app-update.yml
因?yàn)闆](méi)有打包嘛,所以找不到 app-update.yml
這個(gè)文件,索性我們就創(chuàng)建一個(gè)。
在根目錄下創(chuàng)建一個(gè) dev-update.yml
文件(文件名可自定義),寫(xiě)入配置:
provider: generic
updaterCacheDirName: demo-updater # 下載目錄
然后在開(kāi)發(fā)環(huán)境指定這個(gè)配置文件地址:
import { app } from 'electron';
import path from "path";
if(!app.isPackaged) {
...
autoUpdater.updateConfigPath = path.join(__dirname, "../../dev-update.yml");
}
重新運(yùn)行項(xiàng)目,會(huì)發(fā)現(xiàn)檢測(cè)更新的邏輯可以正常執(zhí)行了。
能不能繞開(kāi) latest.yml,走后端接口?
可能大家希望的檢查更新流程是這樣:
調(diào)用后端的 API 接口,接口返回 JSON 格式數(shù)據(jù),包含最新的版本號(hào)和安裝包下載地址。將該版本號(hào)與本地版本號(hào)做對(duì)比,如果不一樣則表示有更新,并執(zhí)行下載。
然而 electron-updater
是通過(guò) latest.yml
文件來(lái)獲取版本號(hào)等信息。latest.yml 是一個(gè)配置文件,內(nèi)容如下:
version: 1.0.2
files:
- url: elapp_1.0.2.exe
sha512: xxxxxx
size: 72716511
path: elapp_1.0.2.exe
sha512: xxxxxx
releaseDate: '2023-11-29T02:28:28.032Z'
大家想繞過(guò)它,可能是因?yàn)?YAML 文件的內(nèi)容看不太懂,或者與接口格式不匹配。其實(shí)它就是一個(gè)普通的配置文件,轉(zhuǎn)換成 JSON 格式如下:
{
"version": "1.0.2",
"files": [
{
"url": "elapp_1.0.2.exe",
"sha512": "xxxxxx",
"size": "72716511"
}
],
"path": "elapp_1.0.2.exe"
}
可能有人會(huì)問(wèn):配置文件可以改成latest.json
嗎,這樣后端就可以動(dòng)態(tài)返回了。
我仔細(xì)查閱過(guò)文檔,目前只支持 YAML 文件,不支持 JSON,所以 latest.yml 無(wú)法繞開(kāi)。但這只是官方說(shuō)法,咱還是有辦法滴。
經(jīng)過(guò)大量測(cè)試,我發(fā)現(xiàn)把 latest.yml 文件的內(nèi)容手動(dòng)改成 JSON 格式也是可以的。這樣的話(huà),就可以寫(xiě)一個(gè)接口來(lái)模擬 latest.yml 的地址。
假設(shè) latest.yml 的訪(fǎng)問(wèn)路由是/ele-app/latest.yml
,那么寫(xiě)一個(gè)接口如下:
const app = require('express')()
app.get('/ele-app/latest.yml', (req, res, next) => {
let resinfo = {
version: "1.0.25",
path: "xxx_1.0.25.exe"
sha512: "xxxxxx"
}
res.send(resinfo)
})
該接口就是自定義的檢測(cè)更新接口。接口返回值中至少要包含version、path、sha512
三個(gè)屬性(與 latest.yml 中的配置保持一致)。這樣我們不需要上傳 latest.yml 文件了,用該接口替代即可。
基于該檢測(cè)更新接口,接下來(lái)我們逐步實(shí)現(xiàn)自定義更新流程。
如何區(qū)分 Windows 和 Mac 系統(tǒng)
對(duì)于 Windows 和 Mac 兩個(gè)系統(tǒng)的更新,electron-updater 使用不同的配置文件,分別是 latest.yml
和 latest-mac.yml
。
從上一步的檢測(cè)更新接口來(lái)看,不同的配置文件就是不同的路由,我們改造接口如下:
app.get('/ele-app/:platform', (req, res, next) => {
let { platform } = req.params
let resinfo = null
// 返回 Windows 配置
if(platform == 'latest.yml') {
resinfo = {
version: "1.0.2",
path: "xxx_1.0.2.exe"
sha512: "xxxxxx"
}
}
// 返回 Mac 配置
if(platform == 'latest-mac.yml') {
resinfo = {
version: "1.0.3",
path: "xxx_1.0.3.dmg"
sha512: "xxxxxx"
}
}
if(!resinfo) {
resinfo = { code: 400, msg: '參數(shù)錯(cuò)誤' }
}
res.send(resinfo)
})
上面代碼中,使用動(dòng)態(tài)路由返回 Windows 和 Mac 的配置,同時(shí)兼容了兩個(gè)平臺(tái)的更新檢測(cè)。
完整的自定義更新流程
經(jīng)過(guò)上面的介紹,自定義檢測(cè)更新的關(guān)鍵思路已經(jīng)講清楚了。完整的更新流程如下:
(1)打包各個(gè)平臺(tái)的安裝包,上傳服務(wù)器。
如何打包在上一篇介紹過(guò),就不展開(kāi)說(shuō)了。注意的是:現(xiàn)在你只需要上傳安裝包,不需要上傳 latest.yml 文件。
(2)在主進(jìn)程中設(shè)置更新地址,并手動(dòng)控制更新。
假設(shè)我們編寫(xiě)的檢測(cè)更新接口已經(jīng)部署,設(shè)置方法如下:
import { autoUpdater } from 'electron-updater';
// 設(shè)置檢測(cè)更新的地址
autoUpdater.setFeedURL('http://[xxx]/ele-app');
// 不自動(dòng)下載
autoUpdater.autoDownload = false;
// 觸發(fā)檢測(cè)
autoUpdater.checkForUpdatesAndNotify().catch();
// 監(jiān)聽(tīng)到可更新
autoUpdater.on('update-available', (info) => {
// info 是檢測(cè)更新接口返回的數(shù)據(jù)
if (info.can_download) {
// can_download 是自定義屬性
autoUpdater.downloadUpdate();
}
});
(3)編寫(xiě)檢測(cè)更新接口,返回配置。
返回的配置我們可以自定義,假設(shè)返回結(jié)果如下:
{
"version": "1.0.2",
"path": "http://xxx/xxx_1.0.2.exe",
"sha512": "xxxxxx",
"can_download": false
}
上述示例中,接口返回了自定義屬性 can_download
,我們?cè)诘冢?)步中使用了該屬性,用于判斷是否執(zhí)行下載更新。
通過(guò)這種方式,即便我們更新了安裝包,也可以自由決定是否要下載安裝。
這里有一個(gè)小驚喜:path
屬性的值可以是一個(gè)完整的安裝包地址,這樣可以把安裝包上傳到任意地方。如果值是一個(gè)文件名,那么會(huì)以第(2)步中 setFeedURL()
方法設(shè)置的地址為前綴。
提醒:sha512 屬性的值必須從打包生成的 latest.yml 中獲取,不可以隨意寫(xiě),否則在安裝時(shí)不能通過(guò)檢驗(yàn),會(huì)報(bào)這個(gè)錯(cuò):
Error: sha512 checksum mismatch
特別篇:Mac 如何區(qū)分 Intel 和 M1?
Mac 系統(tǒng)有兩種軟件包,分別對(duì)應(yīng) M1 芯片和 Intel 芯片,兩者不兼容。一般打包時(shí)我們也會(huì)構(gòu)建兩種安裝包。
那么在檢測(cè)更新時(shí),我們就需要返回適配當(dāng)前系統(tǒng)的安裝包。但不管是 M1 還是 Intel 都使用 latest-mac.yml
這一個(gè)配置文件,該如何區(qū)分呢?
這個(gè)時(shí)候就要從主進(jìn)程中獲取參數(shù),然后傳給接口了。步驟如下:
(1)在主進(jìn)程中,獲取系統(tǒng)架構(gòu),并通過(guò)請(qǐng)求頭傳給檢測(cè)更新接口。
import { autoUpdater } from 'electron-updater';
// 添加請(qǐng)求頭
autoUpdater.requestHeaders = {
elearch: process.arch,
};
(2)在接口中接收參數(shù),并在返回 Mac 配置中判斷:
app.get('/ele-app/:platform', (req, res, next) => {
let { platform } = req.params
let { elearch } = req.headers
if(platform == 'latest-mac.yml') {
if(elearch == 'arm64') {
// M1
return {...}
} else {
// Intel
return {...}
}
}
})
有了上面的邏輯,我們?cè)诖虬鼤r(shí)就可以單獨(dú)打兩個(gè)包,分別上傳,然后在檢測(cè)時(shí)返回不同的下載地址。
總結(jié)
本文使用自定義接口的方式,繞過(guò)了 latest.yml 配置文件,使自動(dòng)更新的靈活性更高,也更符合我們的常規(guī)習(xí)慣,但是需要維護(hù)一個(gè)更新接口。
如果不想維護(hù)接口,那么上傳 latest.yml 文件的方式更適合你。