Ship updates to your Electron app with `autoUpdater` (Mac)

Cameron Nokes
InstructorCameron Nokes

Share this video with your friends

Send Tweet
Published 5 years ago
Updated 2 years ago

Once users are using your app, you need a way to ship new features and fixes to them. In this lesson, we’ll learn how to implement auto-updates in your app, which allows you to distribute updates to your application seamlessly. We'll go over how to integrate with Electron's autoUpdater module to check for an update and display a prompt to update once it's been downloaded. For our server side implementation we'll just use a plain file server to keep things simple.

[00:01] My Hello World application here is basically complete. I have fully functioning main and renderer process code set up. In my main process, I'm requiring in another file, auto-updater. Then it exports an init method, and I'm calling that init method an application ready.

[00:15] My auto-updater file wraps Electron's auto-updater module. You can see I've already set it up a little bit. I'm bringing in a few dependencies. I'm getting the application version. I'm exporting the init method.

[00:31] In my init method here, I'm just logging some events. Electron's auto-updater module emits several events that form the auto-update life cycle. I'm logging those events out, and then I have a prompt update function here that's incomplete.

[00:47] The first thing we need to do is set up our update URL. Let's go ahead and do that. For this demonstration, I'm just going to use a localhost server to serve my updates. Because I'm doing a very basic server side implementation for my auto-update server, I'm going to have separate URLs for Windows and Mac.

[01:06] We're going to check if it's Mac. In here, we'll say if isMac, Mac is going to request a mac.json file from our update server. Windows doesn't need to do anything. On Mac, the native update framework that Electron wraps calls a URL that returns a specific HTTP status and JSON object, which it'll use to check if there's an update for it to download.

[01:32] Because we're doing a very basic server side implementation, we're just going to call an actual JSON file, and then decide on the client if there needs to be an update. Let's look at what our update server looks like.

[01:43] I'm just using a simple file server to host these. I'm using HTTP-server, which I've installed globally. The mac.json file here is pretty simple. The only required field here is the URL field. You can see I've already set it to the next version of my application, which will be Hello World, version 1.01.

[02:01] The client will check this version against its local version to see if there needs to be an update. Before we can check for update, we need to tell Electron auto-updater what the update URL is. We're going to call setFeedURL, and we'll pass our update URL.

[02:16] Now, we need to tell it to check for updates. Let's implement checkForUpdates. If it's Mac, we're going to call checkForMacUpdate, which we haven't implemented yet. That'll return a promise and a Boolean that tells if there is an update for us to download.

[02:35] If hasUpdate is true, we will tell the auto-updater to check for updates. Basically, because we're doing the version checking logic in our Electron application is the reason we have to do this. Let's implement checkForMacUpdate.

[02:51] I'm going to use Axios here to make the HTTP request. The update URL, it's going to get our mac.json file. I'm going to say if the status is 200, and another dependency I added was Semver. Semver is used by NPM internally to compare those version strings.

[03:16] We're going to call Semver greater than, and we're going to see if the mac.json file, if that version is greater than our local version. You'll remember version here is coming straight from our package.json up here.

[03:28] Back up here in our checkForUpdates function, we'll add an else and handle Windows. Windows is easy. We don't actually have to have any special handling for Windows. We can just tell Windows to check for updates without anything else.

[03:42] One last thing we want to add is, when an update is downloaded, we'll pass our callback handler. This is when we want to prompt the user to update. Basically, what'll happen is our application will start. We'll check for updates, and these events will get fired.

[03:58] We'll do checking for update, update available, and then the update downloaded. If there is an update, Electron will download the ZIP file. When it's done, the update downloaded will be emitted. At this point, we're safe to close the application, install the update, and then restart it.

[04:15] That's why we'll call prompt.update. prompt.update is mostly implemented down here. I'm just using the dialogue module to show a message box with two buttons, update and close. If they click update, we're going to call auto-updater, quit and install.

[04:35] Like I said, this will quit the application, apply the update, and then restart it. Then one last thing we want to do up here. Right now, if we tried to run this in development mode, it wouldn't work. For auto-updates on Mac to work, you have to have a valid code sign. We don't have that in development ever.

[04:52] Let's go up here, and we'll say isDev. We're going to check the process arguments. We're going to check if there is a flag passed to our process called dev. Then in our init function, we'll just say if isDev, just return.

[05:07] Then over in our package.json, we have our start script here. I'm just going to pass the dev flag here, so that way, our auto-updater code won't be run in development. Let's go ahead and build our application. I have a build script already set up.

[05:24] This build script just tells Electron builder where my code signing certificate is, and then just builds the application. I'm going to run this. Our build script completed successfully. We already have our auto-update server running.

[05:39] It's serving up the next version of our application, 1.01. I'm going to go ahead and start my 1.00application. I'm going to start it from the terminal. That way, I can see all those console.logs that set up in the auto-updater file.

[05:54] I'm going to start that there. It'll start my application like normal. You can see it's already running. I checked for update. The update was available. It went ahead and downloaded it. You can see it downloaded 1.01. Then it's prompting me to update the application.

[06:10] If I hit update here, it closes the application and then restarts it. We can see our new application version here is 1.01. Awesome, it worked. If we jump over here into our mock update server again, we can just glance at the server logs to see just what happened a little bit.

[06:28] First thing that happened was our application started. Axios, which our HTTP requests library, it got our mac.json file. In our client, it got a 200 response, and then Semver determined that the update server's version was greater than our local version.

[06:46] Then it returned true here. hasUpdate was true. Then we told the Electron auto-updater to go ahead and check for updates. The reason we did this is because the native Mac OS auto-update framework called scroll.mac, it checks for specific HTTP statuses to see if it needs an update.

[07:02] Any time scroll.mac gets a 200, it assumes there's an update, and will download and apply it. If there's no update, you're supposed to return a 204. Because it requires some server side implementation, I opted to just do that checking in my Electron application to make the server side easier.

[07:17] That's why here, our Electron JavaScript code determined that there was an update. We told Electron to download the update. It got mac.json again, and the user agent here is CF Network. That's scroll.mac making the request.

[07:31] It saw that there was an update, then it downloaded our ZIP file. At that point, our application restarted, and we can see that it again requested mac.json. This is happening in our JavaScript from Axios. Then it determined that the versions were equal. No update was available, so you can see that no other requests happened here.