How to implement a simple update function in Electron?

insert image description here

foreword

In the previous article "[Fun with Electron] Ultra-Detailed Tutorial", I mentioned the issue of updating. Due to the length of the content, a separate blog was published, so this article came into being.

Regarding the update of Electron, the official has actually provided several very convenient solutions .
But either github is needed, or a server needs to be built, because our scene is small, electron is just a shell, so the need for updates is not strong, it is just a function just in case, so we want to find a simple way to handle it .

Built-in update function

We use electron-forge for packaging. In fact, this comes with an update function, namely autoUpdater. It is also very simple to use and only needs a few steps, as follows:

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

In fact, this is enough, checkForUpdates will check for updates and automatically download and install them, without any perception during the whole process. When restarting the application, it will be the new version.

Of course, this is the simplest step, and we will enrich the functions later.

There are several problems here.

First of all, if you want to update on the mac, it must be a signed application. At present, our mac application is not signed, so it cannot be used, and it will prompt.
Error: Could not get code signature for running application

Secondly, it is to update the url. What does this address correspond to? How can we build an update service conveniently and quickly?

There is no detailed description of what this address corresponds to in the official document, because if you use several official service backgrounds, you can directly add an update through the background interface, and you don’t need to care about the others. But we don't plan to use the official solution, so we have to find out what this url corresponds to? Is it a file? Configuration Data?

something behind the url

After a few days of groping, I consulted relevant documents and source codes, and finally determined what was behind the url. Because we only consider windows at present, the following is based on windows.

We use forge to create a windows installation package through squirrel-maker. After creation, the file path is the project root directory /out/make/squirrel.windows/x64/xxxx.exe.

RELEASESHowever, two other files and files are also generated in the same directory xxx.nupkg. This is the file we need for updating, which is RELEASESequivalent to the configuration file, which records the complete name of the nupkg file, SHA512 (for verification) and file size, as follows :
674802FE0AE3B272F5182E4626893FDB2D8D2107 xxxxxx-0.1.0-full.nupkg 76560314

So we upload these two files to the file server, put them in the same directory (or virtual directory), and then set the address of the directory to feedUrl. In this way, autoUpdater will automatically download the RELEASES file in this directory and read the configuration, then download and verify the update file through the obtained file name, and automatically install it in the background after success.

If we observe the root directory of the application, we will find that there are actually directories named with different version numbers in the root directory of the application. The background installation is actually to download the new version and decompress it into the directory with the new version number in the root directory, and then restart When , the execution file exe will use the files in the directory with the new version number to run, thus completing the update. The old version of the file actually still exists in the root directory. That's why it will be installed without perception, because there is no need to delete and modify files (few configuration files need to be modified).

question

In fact, it did not go so smoothly. The following summarizes some of the problems encountered in the middle.

Error pop-up garbled characters, check the detailed log

If there is an error when the electron is running, a pop-up window will prompt, but in actual operation, if there is Chinese in the error message, it will cause the error message to be garbled. This makes it impossible to see accurate information.

How to deal with it?

In the root directory of the application (installation directory, generally in c:/user/[user name]/AppData/Local/[application name]), a log file of SquirrelSetup.log will be generated, which records the detailed information of the error.

Couldn’t acquire lock, is another instance running

Check SquirrelSetup.log to see the details of this error as follows:

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.Update.Program.<CheckForUpdate>d__8.MoveNext()<---

This error is suspected to be related to the installation package compiled by electron. When the installation package is run, the installation animation will be displayed, but the application has been opened after the installation is complete. The animation has not disappeared, and sometimes it lasts for several minutes.

The application will be updated as soon as it is opened, so there may be a conflict between the application process and the installer process at this time, resulting in the above problem.
At present, this problem has not been well circumvented, but it can be circumvented by registering and handling the error event of autoUpdater, as follows:

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

After adding such code, there will be no more pop-up prompts. But the actual problem still exists, related errors will be recorded in SquirrelSetup.log, and the update will be interrupted.

So this is not a solution. After this treatment, the first startup update will fail with a high probability, but it will be updated normally when it is restarted, so it is acceptable for the time being.

server 403

Check SquirrelSetup.log to see the details of this error as follows:

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()

In fact, the above only tells us that the server returned 403, but it didn't explain why. The url is fine, the file also exists, and can be accessed in the browser, why does 403 appear. Finally, it was found through Charles that the server returned:

{
    "code": "40310011",
    "msg": "invalid User-Agent header"
}

Check the header of this request in charles and find that there is no User-Agent, so it should be the problem here.

Through postman, we simulated the request and found that when the User-Agent was deleted, the above error would occur, and it can be accessed normally by just adding one.

Because the application uses the update that comes with electron, it cannot intervene in this request, so start from the server. After testing, it is found that Qiniu has no such problem, even without User-Agent, it can be accessed normally, so it should be some configuration of upyun.

After the successful replacement of Qiniu, you can visit normally.

full function

The above are just the simplest steps. After opening the app, it will automatically detect the update, and if it is updated, it will be downloaded and installed automatically. Users have no awareness, so they don't know when to update, and the new version will only be used after the user closes and restarts the application. So we need to notify the user.

autoUpdater has a lot of event callbacks. We mentioned error above. We will notify the user by listening to these events, so that the update function is realized. Compared with the official solution, it is simpler and lighter. You only need to update two files on the server in the future. .

The complete source code is as follows:

const {
    
     app, BrowserWindow, globalShortcut, Menu, autoUpdater, dialog } = require('electron')
var win;


if(require('electron-squirrel-startup')){
    
    
    return app.quit();
}


function createWindow () {
    
    
  win = new BrowserWindow({
    
    
    width: 1280,
    height: 744,
    minWidth: 1280,
    minHeight: 744,
    webPreferences: {
    
    
      nodeIntegration: true,
      webSecurity: false
    }
  })


  win.loadURL('https://appd.knowbox.cn/class/#main')
   
  globalShortcut.register('Alt+CommandOrControl+Shift+D', () => {
    
    
    win.webContents.openDevTools({
    
    mode:'detach'}) //开启开发者工具
  })


  //设置url
  autoUpdater.setFeedURL({
    
    url: "https://xxxxxx"});
  //开始检查更新回调
  autoUpdater.on('checking-for-update', () => {
    
    
    //dialog.showMessageBox({message:"check for update"})
    console.log("check for update")
  });
  //有新版本回调
  autoUpdater.on('update-available', () => {
    
    
    //dialog.showMessageBox({message:"update-available"})
    console.log("update-available")
  });
  //已下载完成回调,这里弹窗提示用户
  autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName, releaseDate, updateURL) => {
    
    
    dialog.showMessageBox({
    
    message:"新版本已准备好,是否现在重启程序?", title: "更新应用", buttons: ['重启程序', '暂不重启']}).then((value) => {
    
    
      if(value.response == 0){
    
    
          //重启并安装新版本
        autoUpdater.quitAndInstall()
      }
    })
    console.log("update-downloaded:" + updateURL)
  });
  //没有新版本回调
  autoUpdater.on('update-not-available', () => {
    
    
    //dialog.showMessageBox({message:"update-not-available"})
    console.log("update-not-available")
  });
  //更新出错回调
  autoUpdater.on('error', (error) => {
    
    
    //dialog.showMessageBox({message:"error:" + error.name + "," + error.message + "," + error.stack})
    console.log("error:" + error.name + "," + error.message + "," + error.stack)
  });
  //检查更新
  autoUpdater.checkForUpdates();
  //或者可以设置成定时循环检查,比如10分钟
  // setInterval(() => {
    
    
  //   autoUpdater.checkForUpdates();
  // }, 60000);


}


Menu.setApplicationMenu(null)


app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required');
app.whenReady().then(createWindow)




app.on('window-all-closed', () => {
    
    
  if (process.platform !== 'darwin') {
    
    
    app.quit()
  }
})


app.on('activate', () => {
    
    
  if (BrowserWindow.getAllWindows().length === 0) {
    
    
    createWindow()
  }
})

local update

The official also provides a solution, manually download the update package to the local, and then update it locally, but it is not as simple as the above, but because of the research together, it is also simply recorded.

I won’t talk about downloading this part, just refer to the documents on the Internet. Mainly talk about the local file location and update.
How does electron save some temporary files, and where is the best place to save them? The official website gives a good example, the code is as follows:

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);
}

In this way, we got a temporary directory tmpPath, so where is this directory?

Its location c:/用户/[用户名]/AppData/Local/Temp/AICLASSis actually the cache directory of the browser, where AICLASS is the directory defined by ourselves.

We download the file to this directory, and then we can update it locally through autoUpdater, which is the same as the network update, except that the feedUrl becomes a local directory, as follows:

autoUpdater.setFeedURL({url: global.tmpPath});

Summarize

To sum up, it can be seen that the update of Electron is actually very simple, and there is no need to close the application during the update, the application can still be used normally, and the new version can be used directly after re-opening after the update.

You can know by observing the installation directory. Under the Electron installation directory, there is a directory named after the version. The main files are placed under this. When updating, a new directory will be created with the new version and the file will be downloaded here, so that it will not be modified. The file will not affect the operation of the application. When the application is reopened after the update, the application will automatically find the new version and run the new version of the file, so the update is successful. So Electron can achieve silent updates.

Guess you like

Origin blog.csdn.net/chzphoenix/article/details/121750400