electron-updater 自动更新升级应用

news2025/1/11 19:46:41

electron 内置了 autoUpdater 自动更新功能,但是服务配置有些复杂,最后选择了 electron-updater 工具插件,这里就讲讲如何配置 electron-updater 来自动更新升级应用。

electron-updater

一、项目依赖和 scripts

安装 electron-updaterelectron-log

pnpm add -D electron-updater electron-log

package.json 完整配置如下:

{
  "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"
  }
}

二、配置打包参数

  • 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/

其中最后三行的 public 参数配置是 electron-updater 更新的关键。
http://localhost:3000/ 本地调试用,下载速度很快,上线了可以换成正式域名服务。

  • dev-app-update.yml
provider: generic
url: http://localhost:3000/
updaterCacheDirName: post-tools-updater

三、主要更新逻辑

新建文件 src/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(['更新操作', '关闭更新提醒'])
    }
  }
}

其中 helper.js 是封装的持久化数据相关操作方法。

// 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 中调用封装的 autoUpdateInit 初始化方法

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

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

四、打包调试

因为 electron-updater 在本地开发环境不会去检测更新,所以需要打包后进行操作。
electron-updater 更新检测主要是检测服务端的 latest.yml (Mac软件是生成的是 latest-mac.yml ) 文件中的 version 信息,而这个版本号就是根据 package.jsonversion 生成的。

假定 package.jsonversion: 1.0.0 ,系统为 MacOS。

调试步骤如下:

  1. 执行打包
npm run build:mac
  1. 在生成的安装包文件在 dist 目录,找到 .dmg 后缀的文件
  2. 安装
  3. 修改 package.json ,升级版本号改为 version: 1.1.0
  4. 再次执行打包命令
  5. 启动本地的静态文件服务,这里推荐使用 serve 工具包,全局安装 serve 。
pnpm i -g serve 
  1. 在项目根目录下执行 serve dist 命令,这个命令作用就是把 dist 目录下所有的文件变成静态资源,通过 http 请求拿到对应的资源。
  2. serve 提供的静态服务默认在 3000 端口,如果被占用,会给出一个随机端口,记得修改上边 electron-builder.ymldev-app-update.yml 对应的地址。一切正常的话,访问 http://localhost:3000/latest-mac.yml 就能看到对应资源。
  3. 一切就绪后,启动步骤3安装的应用。等待几秒就能看到上图所示的更新提醒。
  • 注意:MacOS 中需要配置证书,不然检测更新的时候可能会出现问题,这个可以自己生成代码签名证书。可参考:Mac 配置自建证书。

欢迎访问:天问博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/751731.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

5分钟构建电商API接口服务 | python小知识

1. 什么是API 我们经常会使用一些API接口来完成特定的功能,比如查询天气的数据,下载股票的数据,亦或是调用ChatGPT模型的结构等等。 API全称是Application Programming Interface,即应用程序接口,它通常提供了一个功…

数据结构与算法——顺序表(顺序存储结构)及初始化详解

顺序表,全名顺序存储结构,是线性表的一种。通过《什么是线性表》一节的学习我们知道,线性表用于存储逻辑关系为“一对一”的数据,顺序表自然也不例外。 不仅如此,顺序表对数据的物理存储结构也有要求。顺序表存储数据…

Android之Glide图片框架分析

为什么使用Glide? 使用简单,链式调用,API简洁。with、load、into三步走就可以加载图片生命周期自动绑定,根据绑定的Activity或者Fragment生命周期管理图片请求高效处理Bitmap。支持bitmap的复用和主动回收,减少系统回…

查看隐藏文件怎么做?4个简单方法分享

“朋友们!想问问大家如果设置了隐藏文件,想查看的时候应该怎么进行查看呀?有没有朋友可以教教我!” 为了保护电脑的隐私,我们有时候可能会给电脑设置某些隐藏的文件,这些隐藏的文件我们是无法看到的。如果我…

【GitOps系列】从零上手GitOps

文章目录 GitOps 介绍如何将业务代码构建为容器镜像?如何将容器镜像部署到K8s?K8s如何实现自动扩容和自愈?1.传统的扩容和自愈2.k8s自愈机制3.k8s弹性扩容 如何借助GitOps实现应用秒级自动发布和回滚?1.传统 K8s 应用发布流程2.从…

SpringCloudAlibaba:消息驱动之RocketMQ学习

目录 一、MQ简介 (一)什么是MQ (二)MQ的应用场景 1、异步解耦 2、流量削峰 (三)常见的MQ产品 二、RocketMQ入门 (一)RocketMQ安装部署 1、环境要求 2、下载RocketMQ 3、安…

Linux网络综合基础实验 (二十三)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 前言 一、实验目的 二、实验要求 三、实验拓扑 四、实验步骤 1. DHCP 安装 2、DNS 服务器搭建 3、web服务器配置 3.1基础配置 3.2查看IP获得情况 3.3 配置本地yum源 4、…

JDK 7 ConcurrentHashMap

目录 概述 构造器分析 put 流程 get 流程 size 计算流程 概述 JDK1.7中的ConcurrentHashMap间接地实现了Map&#xff0c;并将每一个元素称为分段锁segment&#xff0c;每个segment都是一个HashEntry<K,V>数组&#xff0c;称为table&#xff0c;table的每个元素都是一…

【雕爷学编程】Arduino动手做(149)---MAX9814咪头传感器模块2

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

MyBatis PostgreSQL实现数组类型的操作

我的GitHub&#xff1a;Powerveil GitHub 我的Gitee&#xff1a;Powercs12 (powercs12) - Gitee.com 皮卡丘每天学Java 最近在学习数据库PostgreSQL&#xff0c;遇到如何实现对数组类型的数据操作&#xff0c;试着自己尝试学习实现。 话不多说&#xff0c;直接撸代码。 建表…

linux下一个iic驱动(按键+点灯)-互斥

一、前提&#xff1a; 硬件部分&#xff1a; 1. rk3399开发板&#xff0c;其中的某一路iic&#xff0c;这个作为总线的主控制器 2. gd32单片机&#xff0c;其中的某一路iic&#xff0c;从设备。主要是按键上报和灯的亮灭控制。&#xff08;按键大约30个&#xff0c;灯在键的…

新手杯—easy_base

0x00 前言 CTF 加解密合集&#xff1a;CTF 加解密合集 0x01 题目 0XezFWZfNXafRjNlNXYit3dvh2cmR3Y0x02 Write Up 先倒序 然后base64解码 以上

Self-Attention Cross-Attention

transformer的细节到底是怎么样的&#xff1f;Transformer 连环18问&#xff01; 4.1 从功能角度&#xff0c;Transformer Encoder的核心作用是提取特征&#xff0c;也有使用Transformer Decoder来提取特征。例如&#xff0c;一个人学习跳舞&#xff0c;Encoder是看别人是如何…

智能网卡在分布式 SDN 网络的应用与实践 | 龙蜥技术

编者按&#xff1a;当前智能网卡能够加速数据处理和传输&#xff0c;并能实现网络、存储和安全等功能卸载&#xff0c;在云计算领域得到广泛的应用。今天&#xff0c;浪潮数据云计算网络架构师王培辉带大家了解智能网卡加速原理和以及在浪潮分布式 SDN 网络加速的应用&#xff…

我连夜咨询了30个老同学,学IT上培训班到底有用么?

文章目录 一、背景二、学习IT上培训班的益处2.1 IT行业本身还不错2.2 获取到系统的专业知识2.3 获取到实战经验2.4 获取到网络资源和支持2.5 获取到职业发展指导2.6 建立初步的职业圈子人脉 三、学习IT上培训班的风险3.1 质量风险3.2 课程更新速度风险3.2 缺乏互动与实践机会风…

积分微分电路

积分微分电路 通过写出时域的推导&#xff0c;再到频域&#xff0c;详细介绍了积分微分的频率响应的推导&#xff0c;手绘了bode图&#xff0c;并仿真电路得到对应的结果。积分的频率响应&#xff1a;频率增加10倍&#xff0c;增益下降20db。输出相位超前输入相位90度。微分的…

GPT-4 最强竞争对手,Claude 杀疯了!

公众号关注 “GitHubDaily” 设为 “星标”&#xff0c;每天带你逛 GitHub&#xff01; 在今年早些时候&#xff0c;ChatGPT、Bard、Claude 等大语言模型&#xff0c;在 AI 领域呈三权鼎立之势&#xff0c;无人能出其右&#xff0c;被视为是能力表现最为卓越的 3 款 AI 聊天机器…

阿里云无影云电脑具体价格_云桌面不同配置1元报价

阿里云无影云电脑配置费用&#xff0c;4核8G企业办公型云电脑可以免费使用3个月&#xff0c;无影云电脑地域不同费用不同&#xff0c;无影云电脑是由云桌面配置、云盘、互联网访问带宽、AD Connector、桌面组共用桌面session等费用组成&#xff0c;阿里云百科分享阿里云无影云电…

大模型的“第一性原理”:技术创新与社会价值的接轨

随着时间来到2023年第三季度&#xff0c;国产大模型已经达到100多个&#xff0c;“百模大战”正式开启。 大模型&#xff0c;我们有了很多选择&#xff0c;也开始呈现出某种同质化。除了拼参数、比背景、看榜单&#xff0c;有没有其他方法&#xff0c;让我们更好地判断一个大模…

解决Gson解析json字符串,Integer变为Double类型的问题

直接上代码记录下。我代码里没有Gson包&#xff0c;用的是nacos对Gson的封装&#xff0c;只是包不同&#xff0c;方法都一样 import com.alibaba.nacos.shaded.com.google.common.reflect.TypeToken; import com.alibaba.nacos.shaded.com.google.gson.*;import java.util.Map;…