electron-updater automatically updates and upgrades applications

Electron has a built-in autoUpdaterautomatic update function, but the service configuration is a bit complicated. In the end, I chose electron-updatera tool plug-in. Here I will talk about how to configure it electron-updaterto automatically update and upgrade applications.

electron-updater

1. Project dependencies and scripts

Install electron-updaterandelectron-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 publicis electron-updaterthe 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.jsare 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.whenReadyCall the encapsulated autoUpdateInitinitialization method in the main process

app.whenReady().then(() => {
    
    
    // ...

    // 版本更新初始化
    autoUpdateInit()
})

4. Packaging and debugging

Because electron-updaterthe local development environment will not detect updates, it needs to be packaged and operated.
electron-updaterUpdate 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.ymlpackage.jsonversion

Assume package.jsonthat version: 1.0.0the system is MacOS.

The debugging steps are as follows:

  1. Execute packaging
npm run build:mac
  1. In the generated installation package file in distthe directory, find .dmgthe file with the suffix
  2. Install
  3. Modify package.json and change the upgrade version number to version: 1.1.0.
  4. Execute the packaging command again
  5. Start the local static file service. It is recommended to use servethe toolkit and install serve globally.
pnpm i -g serve 
  1. 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 and httpobtain the corresponding resources through requests.
  2. The static service provided by serve is on 3000the port by default. If it is occupied, a random port will be given. Remember to modify the addresses corresponding to electron-builder.ymland above. dev-app-update.ymlIf everything is normal, you can see the corresponding resources by visiting http://localhost:3000/latest-mac.yml.
  3. 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

Guess you like

Origin blog.csdn.net/tiven_/article/details/131713981