Electron has a built-in autoUpdater
automatic update function, but the service configuration is a bit complicated. In the end, I chose electron-updater
a tool plug-in. Here I will talk about how to configure it electron-updater
to automatically update and upgrade applications.
1. Project dependencies and scripts
Install electron-updater
andelectron-log
pnpm add -D electron-updater electron-log
The complete configuration of package.json is as follows:
{
"name": "post-tools",
"productName": "Post Tools",
"version": "3.0.0",
"description": "一个基于electron和node开发,用于http/https接口测试的工具",
"main": "./out/main/index.js",
"author": "Tiven",
"homepage": "https://tiven.cn",
"scripts": {
"format": "prettier --write .",
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
"start": "electron-vite preview",
"dev": "electron-vite dev",
"dev:debug": "nodemon --watch ./src/main/index.js --exec \" electron-vite dev\" ",
"build": "electron-vite build",
"postinstall": "electron-builder install-app-deps",
"build:win": "npm run build && electron-builder --win --config",
"build:mac": "npm run build && electron-builder --mac --config",
"build:linux": "npm run build && electron-builder --linux --config",
"git": "tive git -c tive.git.config.cjs"
},
"dependencies": {
"@electron-toolkit/preload": "^2.0.0",
"@electron-toolkit/utils": "^1.0.2"
},
"devDependencies": {
"@ant-design/icons": "4.0.0",
"@electron/notarize": "^1.2.3",
"@vitejs/plugin-react": "^4.0.0",
"about-window": "^1.15.2",
"ahooks": "^3.7.7",
"antd": "^5.6.2",
"axios": "^1.4.0",
"electron": "^24.4.1",
"electron-builder": "^23.6.0",
"electron-builder-squirrel-windows": "^24.5.0",
"electron-log": "^4.4.8",
"electron-updater": "^5.3.0",
"electron-vite": "^1.0.23",
"eslint": "^8.42.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.32.2",
"jsoneditor": "8",
"prettier": "^2.8.8",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"vite": "^4.3.9"
}
}
2. Configure packaging parameters
- electron-builder.yml
appId: cn.tiven.app
productName: Post Tools
copyright: Copyright © 2023 ${
author}
directories:
buildResources: build
output: dist
files:
- '!**/.vscode/*'
- '!src/*'
- '!dist2/*'
- '!node_modules/*'
- '!electron.vite.config.{js,ts,mjs,cjs}'
- '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
asarUnpack:
- resources/**
afterSign: build/notarize.js
win:
executableName: Post Tools
icon: resources/icon.ico
publisherName: tiven
verifyUpdateCodeSignature: false
target:
- nsis
- squirrel
nsis:
oneClick: false
artifactName: ${
name}-${
version}-setup.${
ext}
shortcutName: ${
productName}
uninstallDisplayName: ${
productName}
createDesktopShortcut: always
perMachine: true
allowToChangeInstallationDirectory: true
guid: 2cf313e9-0f05-xxxx-1006-e278272e9b2a
squirrelWindows:
loadingGif: resources/loading.gif
iconUrl: https://tiven.cn/static/img/net-stats.ico
mac:
category: public.app-category.developer-tools
entitlementsInherit: build/entitlements.mac.plist
extendInfo:
- NSCameraUsageDescription: Application requests access to the device's camera.
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
dmg:
artifactName: ${
name}-${
version}.${
ext}
linux:
target:
- AppImage
- snap
- deb
maintainer: electronjs.org
category: Utility
appImage:
artifactName: ${
name}-${
version}.${
ext}
npmRebuild: false
publish:
provider: generic
url: http://localhost:3000/
The parameter configuration in the last three lines public
is electron-updater
the key to the update.
http://localhost:3000/
Used for local debugging, the download speed is very fast, and it can be replaced with a formal domain name service after it goes online.
- dev-app-update.yml
provider: generic
url: http://localhost:3000/
updaterCacheDirName: post-tools-updater
3. Main update logic
create a new filesrc/main/autoUpdater.js
// src/main/autoUpdater.js
import {
app, dialog } from 'electron'
import {
join } from 'path'
import {
autoUpdater } from 'electron-updater'
import logger from 'electron-log'
import {
getLocalData, setLocalData, sleep } from './helper'
import {
productName } from '@package'
export async function autoUpdateInit() {
//打印log到本地
logger.transports.file.maxSize = 1002430 // 10M
logger.transports.file.format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}]{scope} {text}'
logger.transports.file.resolvePath = () => join(app.getPath('appData'), 'logs/main.log')
await sleep(5000)
//每次启动自动更新检查 更新版本 --可以根据自己方式更新,定时或者什么
autoUpdater.checkForUpdates()
autoUpdater.logger = logger
autoUpdater.disableWebInstaller = false
autoUpdater.autoDownload = false //这个必须写成false,写成true时,我这会报没权限更新,也没清楚什么原因
autoUpdater.on('error', (error) => {
logger.error(['检查更新失败', error])
})
//当有可用更新的时候触发。 更新将自动下载。
autoUpdater.on('update-available', (info) => {
logger.info('检查到有更新,开始下载新版本')
logger.info(info)
const {
version } = info
askUpdate(version)
})
//当没有可用更新的时候触发。
autoUpdater.on('update-not-available', () => {
logger.info('没有可用更新')
})
// 在应用程序启动时设置差分下载逻辑
autoUpdater.on('download-progress', async (progress) => {
logger.info(progress)
})
//在更新下载完成的时候触发。
autoUpdater.on('update-downloaded', (res) => {
logger.info('下载完毕!提示安装更新')
logger.info(res)
//dialog 想要使用,必须在BrowserWindow创建之后
dialog
.showMessageBox({
title: '升级提示!',
message: '已为您下载最新应用,点击确定马上替换为最新版本!',
})
.then(() => {
logger.info('退出应用,安装开始!')
//重启应用并在下载后安装更新。 它只应在发出 update-downloaded 后方可被调用。
autoUpdater.quitAndInstall()
})
})
}
async function askUpdate(version) {
logger.info(`最新版本 ${
version}`)
let {
updater } = getLocalData()
let {
auto, version: ver, skip } = updater || {
}
logger.info(
JSON.stringify({
...updater,
ver: ver,
})
)
if (skip && version === ver) return
if (auto) {
// 不再询问 直接下载更新
autoUpdater.downloadUpdate()
} else {
const {
response, checkboxChecked } = await dialog.showMessageBox({
type: 'info',
buttons: ['关闭', '跳过这个版本', '安装更新'],
title: '软件更新提醒',
message: `${
productName} 最新版本是 ${
version},您现在的版本是 ${
app.getVersion()},现在要下载更新吗?`,
defaultId: 2,
cancelId: -1,
checkboxLabel: '以后自动下载并安装更新',
checkboxChecked: false,
textWidth: 300,
})
if ([1, 2].includes(response)) {
let updaterData = {
version: version,
skip: response === 1,
auto: checkboxChecked,
}
setLocalData({
updater: {
...updaterData,
},
})
if (response === 2) autoUpdater.downloadUpdate()
logger.info(['更新操作', JSON.stringify(updaterData)])
} else {
logger.info(['更新操作', '关闭更新提醒'])
}
}
}
Among them helper.js
are the encapsulated persistent data related operation methods.
// src/main/helper.js
import {
join } from 'path'
import fs from 'fs'
import {
app } from 'electron'
const dataPath = join(app.getPath('userData'), 'data.json')
export function getLocalData(key) {
if (!fs.existsSync(dataPath)) {
fs.writeFileSync(dataPath, JSON.stringify({
}), {
encoding: 'utf-8' })
}
let data = fs.readFileSync(dataPath, {
encoding: 'utf-8' })
let json = JSON.parse(data)
return key ? json[key] : json
}
export function setLocalData(key, value) {
let args = [...arguments]
let data = fs.readFileSync(dataPath, {
encoding: 'utf-8' })
let json = JSON.parse(data)
if (args.length === 0 || args[0] === null) {
json = {
}
} else if (args.length === 1 && typeof key === 'object' && key) {
json = {
...json,
...args[0],
}
} else {
json[key] = value
}
fs.writeFileSync(dataPath, JSON.stringify(json), {
encoding: 'utf-8' })
}
export async function sleep(ms) {
return new Promise((resolve) => {
const timer = setTimeout(() => {
resolve()
clearTimeout(timer)
}, ms)
})
}
app.whenReady
Call the encapsulated autoUpdateInit
initialization method in the main process
app.whenReady().then(() => {
// ...
// 版本更新初始化
autoUpdateInit()
})
4. Packaging and debugging
Because electron-updater
the local development environment will not detect updates, it needs to be packaged and operated.
electron-updater
Update detection mainly detects the version information in the file on the server side latest.yml
(the Mac software generates it ), and this version number is generated based on .latest-mac.yml
package.json
version
Assume
package.json
thatversion: 1.0.0
the system is MacOS.
The debugging steps are as follows:
- Execute packaging
npm run build:mac
- In the generated installation package file in
dist
the directory, find.dmg
the file with the suffix - Install
- Modify package.json and change the upgrade version number to
version: 1.1.0
. - Execute the packaging command again
- Start the local static file service. It is recommended to use
serve
the toolkit and install serve globally.
pnpm i -g serve
- Execute the command in the project root directory
serve dist
. The function of this command is to turn all files in the dist directory into static resources andhttp
obtain the corresponding resources through requests. - The static service provided by serve is on
3000
the port by default. If it is occupied, a random port will be given. Remember to modify the addresses corresponding toelectron-builder.yml
and above.dev-app-update.yml
If everything is normal, you can see the corresponding resources by visiting http://localhost:3000/latest-mac.yml. - Once everything is ready, launch the application installed in step 3. Wait a few seconds to see the update reminder shown above.
- Note: A certificate needs to be configured in MacOS, otherwise problems may occur when detecting updates. You can generate a code signing certificate yourself. Please refer to: Mac configuration self-created certificate .
Welcome to: Tianwen Blog