如何为Electron应用实现一个简易的更新功能

简介: 官方其实已经提供了几种很便捷的方案:www.electronjs.org/docs/tutori…但是不是需要github,就是需要搭建一个服务端,因为我们的场景很小,electron只是一个壳,所以更新的需求不强烈,只是一个以防万一的功能,所以我们想寻求一个简单的方式来处理。

官方其实已经提供了几种很便捷的方案:www.electronjs.org/docs/tutori…但是不是需要github,就是需要搭建一个服务端,因为我们的场景很小,electron只是一个壳,所以更新的需求不强烈,只是一个以防万一的功能,所以我们想寻求一个简单的方式来处理。


autoUpdater


我们用electron-forge进行打包,其实这就自带了更新功能,即autoUpdater。使用也很简单只需要几步,如下:


const { autoUpdater } = require('electron')
//先设置更新的url
autoUpdater.setFeedURL({url: "https://xxxxxx"});
//在合适的时机检查更新
autoUpdater.checkForUpdates();
复制代码


其实这样就可以了,checkForUpdates会检查更新并自动下载安装,全程无感知。当重启应用的时候就会是新版本的了。


当然这是最简单的步骤,我们后面会丰富一下功能。

这里有几个问题。


首先,mac上如果想更新,那么必须是签名的应用,目前我们的mac应用未签名,所以不能使用,会提示。

Error: Could not get code signature for running application


其次,就是更新url,这地址对应的是什么?我们如何方便快捷的构建出一个更新服务?

在官方文档中没有详细的描述这个地址对应的是什么,因为如果使用官方提供的几种服务后台,可以通过后台界面直接添加一个更新即可,其他的无需关心。但是我们又不打算使用官方提供的方案,那么我们就必须自己研究出这个url对应的是什么?是文件?配置数据?


更新服务


经过我几天的摸索,查阅相关文档和源码,最终确定了url背后的东西。因为我们目前只考虑windows,所以下面都是以windows为准。


我们用forge通过squirrel-maker来创建windows安装包,创建后文件路径是项目根目录/out/make/squirrel.windows/x64/xxxx.exe。


但是同目录下还同时生成了另外两个文件RELEASESxxx.nupkg,这就是我们更新所需要的文件,其中RELEASES相当于配置文件,里面记录着nupkg文件的完整名称、SHA512(用于校验)和文件大小,如下:


674802FE0AE3B272F5182E4626893FDB2D8D2107 xxxxxx-0.1.0-full.nupkg 76560314


所以我们将这两个文件上传到文件服务器上,放在同一个目录下(或虚拟目录),然后将目录的地址设置为feedUrl即可。这样autoUpdater会自动下载该目录下的RELEASES文件并读取配置,然后通过拿到的文件名下载更新文件并校验,成功后即自动后台安装。

如果我们观察应用的根目录就会发现,实际上在应用根目录有以不同版本号命名的目录,后台安装实际上就是将新版本下载后解压到根目录中新版本号的目录中,然后重启的时候,执行文件exe就会使用新版本号的目录中的文件运行,这样就完成了更新。而旧版本的文件实际上还存在根目录中。所以才会无感知的进行安装,因为不需要删除修改文件(需要修改很少的配置文件)。


问题


其实并没有这么顺利,下面总结了中间遇到的几个问题。


出错弹窗乱码,查看详细日志


如果electron运行时出错,那么就会弹窗提示,但是在实际运行中发现,如果错误信息中有中文,那么就会导致错误信息乱码。这样就无法看到准确的信息。

如何处理呢?


在应用的根目录(安装目录,一般在c:/用户/[用户名]/AppData/Local/[应用名])会生成一个SquirrelSetup.log的日志文件,这里面就记录着错误的详细信息。


System.Exception: Couldn't acquire lock, is another instance running


查看SquirrelSetup.log看到这个错误的详细信息如下:


2021-04-25 15:09:13> SingleGlobalInstance: Failed to grab lockfile, will retry: C:\Users\guozh\AppData\Local\Temp\.squirrel-lock-68CEC12091756AFBF3BF0445D48359FFDABDAB12: System.IO.IOException: 文件“C:\Users\guozh\AppData\Local\Temp\.squirrel-lock-68CEC12091756AFBF3BF0445D48359FFDABDAB12”正由另一进程使用,因此该进程无法访问此文件。
   在 System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
   在 System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
   在 System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)
   在 Squirrel.SingleGlobalInstance..ctor(String key, TimeSpan timeOut)
2021-04-25 15:09:14> Unhandled exception: System.AggregateException: 发生一个或多个错误。 ---> System.Exception: Couldn't acquire lock, is another instance running
   在 Squirrel.SingleGlobalInstance..ctor(String key, TimeSpan timeOut)
   在 Squirrel.UpdateManager.<acquireUpdateLock>b__32_0()
   在 System.Threading.Tasks.Task`1.InnerInvoke()
   在 System.Threading.Tasks.Task.Execute()
--- 引发异常的上一位置中堆栈跟踪的末尾 ---
   在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   在 Squirrel.UpdateManager.<CheckForUpdate>d__7.MoveNext()
--- 引发异常的上一位置中堆栈跟踪的末尾 ---
   在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   在 Squirrel.Update.Program.<CheckForUpdate>d__8.MoveNext()
   --- 内部异常堆栈跟踪的结尾 ---
   在 System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
   在 System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
   在 System.Threading.Tasks.Task`1.get_Result()
   在 Squirrel.Update.Program.executeCommandLine(String[] args)
   在 Squirrel.Update.Program.main(String[] args)
---> (内部异常 #0) System.Exception: Couldn't acquire lock, is another instance running
   在 Squirrel.SingleGlobalInstance..ctor(String key, TimeSpan timeOut)
   在 Squirrel.UpdateManager.<acquireUpdateLock>b__32_0()
   在 System.Threading.Tasks.Task`1.InnerInvoke()
   在 System.Threading.Tasks.Task.Execute()
--- 引发异常的上一位置中堆栈跟踪的末尾 ---
   在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   在 Squirrel.UpdateManager.<CheckForUpdate>d__7.MoveNext()
--- 引发异常的上一位置中堆栈跟踪的末尾 ---
   在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   在 Squirrel.Update.Program.<CheckForUpdate>d__8.MoveNext()<---
复制代码


出现这个错误怀疑是与electron编译的安装包有关,运行安装包的时候会展示安装动画,但是安装完成已经打开应用了,动画还没有消失,有时候甚至持续几分钟。


应用一打开的时候就会进行更新,所以这时候有可能应用进程和安装器进程有冲突,导致上面的问题。 目前这个问题还没有很好的规避,但是可以通过注册处理autoUpdater的error事件进行规避,如下:


autoUpdater.on('error', (error) => {
    //dialog.showMessageBox({message:"error:" + error.name + "," + error.message + "," + error.stack})
    console.log("error:" + error.name + "," + error.message + "," + error.stack)
  });
复制代码


在添加了这样的代码后,就不会再弹窗提示了。但是实际问题还存在,在SquirrelSetup.log中还会记录相关错误,而且更新中断。


所以这并不是解决办法,这样处理后会导致第一次启动更新大概率失败,不过再次启动的时候就会正常更新了,所以暂时可以接受。


服务器403


查看SquirrelSetup.log看到这个错误的详细信息如下:


2021-04-25 14:51:42> IEnableLogger: Failed to download url: https://appd.knowbox.cn/aiclass-pc-update/dev/RELEASES?id=aiclass&localVersion=0.1.0&arch=amd64: System.Net.WebException: 远程服务器返回错误: (403) 已禁止。
   在 System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
   在 System.Net.WebClient.GetWebResponse(WebRequest request, IAsyncResult result)
   在 System.Net.WebClient.DownloadBitsResponseCallback(IAsyncResult result)
--- 引发异常的上一位置中堆栈跟踪的末尾 ---
   在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   在 Squirrel.Utility.<LogIfThrows>d__43`1.MoveNext()
2021-04-25 14:51:42> FileDownloader: Downloading url: https://appd.knowbox.cn/aiclass-pc-update/dev/releases?id=aiclass&localversion=0.1.0&arch=amd64
2021-04-25 14:51:43> IEnableLogger: Failed to download url: https://appd.knowbox.cn/aiclass-pc-update/dev/releases?id=aiclass&localversion=0.1.0&arch=amd64: System.Net.WebException: 远程服务器返回错误: (403) 已禁止。
   在 System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
   在 System.Net.WebClient.GetWebResponse(WebRequest request, IAsyncResult result)
   在 System.Net.WebClient.DownloadBitsResponseCallback(IAsyncResult result)
--- 引发异常的上一位置中堆栈跟踪的末尾 ---
   在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   在 Squirrel.Utility.<LogIfThrows>d__43`1.MoveNext()
2021-04-25 14:51:43> CheckForUpdateImpl: Download resulted in WebException (returning blank release list): System.Net.WebException: 远程服务器返回错误: (403) 已禁止。
   在 System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
   在 System.Net.WebClient.GetWebResponse(WebRequest request, IAsyncResult result)
   在 System.Net.WebClient.DownloadBitsResponseCallback(IAsyncResult result)
--- 引发异常的上一位置中堆栈跟踪的末尾 ---
   在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   在 Squirrel.Utility.<LogIfThrows>d__43`1.MoveNext()
--- 引发异常的上一位置中堆栈跟踪的末尾 ---
   在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   在 Squirrel.FileDownloader.<DownloadUrl>d__3.MoveNext()
--- 引发异常的上一位置中堆栈跟踪的末尾 ---
   在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   在 System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
   在 Squirrel.UpdateManager.CheckForUpdateImpl.<CheckForUpdate>d__2.MoveNext()
复制代码


其实上面只是告诉我们服务端返回了403,至于为什么并没有说明。url是没问题的,文件也存在,在浏览器中也可以访问,为什么会出现403。最后通过charles抓包发现,服务器返回的是:


{
    "code": "40310011",
    "msg": "invalid User-Agent header"
}
复制代码


在charles中查看这个请求的header发现没有User-Agent,所以应该就是这里出现的问题。

通过postman我们模拟请求,发现当删除User-Agent就会出现上面的错误,随便添加一个就可以正常访问。


因为应用用的是electron自带的更新,所以无法干预这个请求,那么就从服务器这边入手。经过测试发现七牛没有这样的问题,即使没有User-Agent也可以正常访问,所以应该是upyun有什么配置。

替换成功七牛后就可以正常访问了。


完善


上面只是最简单的步骤,打开应用后就会自动检测更新,又更新就自动下载安装。用户无感知,所以不知道何时更新,只有用户关闭重启应用后才会使用新版本。所以我们需要通知用户。


autoUpdater有很多事件回调,我们上面提到了error,我们就通过监听这些事件来通知用户,这样就实现了更新功能,相对于官方的方案更简单轻量,后续只要更新服务器上的两个文件即可。


本地更新


官方还提供了一个方案,手动下载更新包到本地,然后通过本地更新,但是没有上面的简单,但是因为一起调研了一下,所以也简单记录一下。

下载这部分就不说了,参考网上的文档即可。主要说一下本地文件位置和更新。 electron如何保存一些临时文件,在哪里保存比较好?官网的给了一个很好的例子,代码如下:


var path = require('path');
var fs = require('fs');
global.tmpPath = path.join( app.getPath("temp"), "AICLASS");
if( !fs.existsSync(global.tmpPath)){
    fs.mkdirSync(global.tmpPath);
}
复制代码


这样我们得到了一个临时目录tmpPath,那么这个目录在哪里呢?

它的位置在c:/用户/[用户名]/AppData/Local/Temp/AICLASS,其实就是浏览器的缓存目录,其中AICLASS是我们自己定义的目录。

我们将文件下载到这个目录中,就可以通过autoUpdater进行本地更新了,与网络更新一样,只不过feedUrl变成了本地目录而已,如下:


autoUpdater.setFeedURL({url: global.tmpPath});
复制代码


通过zip解压的应用


因为win7可能缺少某些必要的库,所以electron的安装包实际上并不能成功运行。这部分用户我们提供的是zip包,自行解压即可。

但是这部分用户就不能使用autoUpdater了,因为这个是依赖于squirrel安装器的,如果是通过zip解压的则没有,所以无法使用。这部分用户目前只能通过手动下载新的zip解压覆盖来实现更新。


目录
相关文章
|
1月前
|
安全 前端开发 Windows
Windows Electron 应用更新的原理是什么?揭秘 NsisUpdater
本文介绍了 Electron 应用在 Windows 中的更新原理,重点分析了 `NsisUpdater` 类的实现。该类利用 NSIS 脚本,通过初始化、检查更新、下载更新、验证签名和安装更新等步骤,确保应用的更新过程安全可靠。核心功能包括差异下载、签名验证和管理员权限处理,确保更新高效且安全。
33 4
Windows Electron 应用更新的原理是什么?揭秘 NsisUpdater
|
2月前
|
存储 搜索推荐 API
Electron-store本地存储功能
【10月更文挑战第18天】Electron-store 无疑为我们的 Electron 应用开发提供了强大的支持。它的本地存储功能不仅方便实用,而且性能优异,为我们打造高质量的应用提供了坚实的基础。
|
2月前
|
安全 前端开发 iOS开发
揭秘 electron-builder:macOS 应用打包背后到底发生了什么?
本文详细介绍了 Electron 应用在 macOS 平台上的打包流程,涵盖配置文件、打包步骤、签名及 notarization 等关键环节。通过剖析 `electron-builder` 的源码,展示了如何处理多架构应用、执行签名,并解决常见问题。适合希望深入了解 macOS 打包细节的开发者。
92 2
|
2月前
|
监控 前端开发 安全
谈谈我做 Electron 应用的这一两年
本文首发于微信公众号“前端徐徐”,作者徐徐分享了过去一两年间开发Electron桌面应用的经验与心得。文章详细介绍了从项目启动、技术选型到具体实施的过程,并探讨了桌面端开发面临的挑战及解决方案,如软件更新、任务队列设计、性能优化等。此外,还列举了一些特殊需求的实现方法,如静默安装、进程禁用等。作者认为,尽管桌面端开发有其独特性,但通过不断探索与实践,仍能显著提升用户体验和技术水平。
165 0
谈谈我做 Electron 应用的这一两年
|
2月前
|
XML 缓存 前端开发
Electron-builder 是如何打包 Windows 应用的?
本文首发于微信公众号“前端徐徐”,作者徐徐深入解析了 electron-builder 在 Windows 平台上的打包流程。文章详细介绍了 `winPackager.ts`、`AppxTarget.ts`、`MsiTarget.ts` 和 `NsisTarget.ts` 等核心文件,涵盖了目标创建、图标处理、代码签名、资源编辑、应用签名、性能优化等内容,并分别讲解了 AppX/MSIX、MSI 和 NSIS 安装程序的生成过程。通过这些内容,读者可以更好地理解和使用 electron-builder 进行 Windows 应用的打包和发布。
170 0
|
4月前
|
容器 iOS开发 Linux
震惊!Uno Platform 响应式 UI 构建秘籍大公开!从布局容器到自适应设计,带你轻松打造跨平台完美界面
【8月更文挑战第31天】Uno Platform 是一款强大的跨平台应用开发框架,支持 Web、桌面(Windows、macOS、Linux)及移动(iOS、Android)等平台,仅需单一代码库。本文分享了四个构建响应式用户界面的最佳实践:利用布局容器(如 Grid)适配不同屏幕尺寸;采用自适应布局调整 UI;使用媒体查询定制样式;遵循响应式设计原则确保 UI 元素自适应调整。通过这些方法,开发者可以为用户提供一致且优秀的多设备体验。
183 0
|
4月前
|
前端开发 JavaScript API
强强联手打造桌面应用新标杆:Angular与Electron的完美融合——从环境搭建到通信机制,全面解析构建跨平台应用的最佳实践与技巧
【8月更文挑战第31天】随着Web技术的进步,开发者们越来越多地采用Web技术来构建桌面应用程序。通过结合使用开源框架Electron及前沿的前端框架Angular,开发者能充分利用JavaScript、HTML和CSS打造出高性能且易维护的跨平台桌面应用。本文将详细介绍如何搭建基于Angular与Electron的开发环境,包括创建Angular项目、安装Electron及相关依赖、配置Electron主进程以及实现Angular应用与Electron间的通信等关键步骤,并最终将应用打包成多平台可执行文件,为读者提供了一套完整的解决方案以快速入门并实践这一强大技术组合。
138 0
|
7月前
|
JavaScript 前端开发 API
如何利用JavaScript和Electron构建具有丰富功能的桌面应用
【4月更文挑战第30天】如何利用JavaScript和Electron构建具有丰富功能的桌面应用
38 0
|
Web App开发 前端开发 JavaScript
用electron打包前端应用初体验
用electron打包开发桌面应用遇到的各种问题和解决办法
218 1
|
JavaScript Shell
electron使用child_process打开外部应用
electron使用child_process打开外部应用