Conveyor 15 adds update control API

Our July release is here and adds a much demanded new feature: the ability to check for and control the update process from inside your app.

Out of the box Conveyor provides three update policies that you can assign to the app.updates config key, suitable for most apps:

  1. BACKGROUND. This is similar to how Chrome updates. On Windows your app will update even when it’s not running.
  2. AGGRESSIVE. This is similar to how a web app updates. On startup if the user is online an update check is done, and if they’re behind an update is downloaded and applied before the app is allowed to start. With delta updates this can be very fast.
  3. NONE. The app should be updated by some other mechanism. Support for online updates is not included.

Conveyor 15 adds two new features: an API, accessible from Electron, the JVM and native code, that lets you both check if a new update is available and also trigger the update/restart process. Also a new update mode has been added: MANUAL. In MANUAL support for updating is in the package, but no automatic checks are done. You have to use the API to trigger updates yourself. The API can be combined with any of these modes except NONE.

Because Conveyor uses the native update systems of each OS the UX varies. On Windows if you trigger an update then your app will quit, and the user will see an update dialog with a progress bar. Once the update is applied the app restarts. On macOS the user will see a confirmation dialog, and when the user clicks “Install and relaunch” the update will be applied. On Linux the update API isn’t currently supported, as normally users will prefer to use their package manager to handle updates.

What can be done with these new features?

Staged rollouts

You can make a custom server that decides whether a specific user or install should upgrade, rather than it being left up to the client side. This means you can do staged rollouts of upgrades according to your own schedule.

This is especially useful for apps that speak a complex network protocol to a sharded server. Think games, or something like Slack. Upgrades can be turned on one server at a time, limiting the blast radius if something goes wrong.

Keep an app updated even if it’s always running

Aggressive updates lets you ensure an app is fresh when it starts up, but if the user never restarts it they can still fall behind. By using the Conveyor control API you can monitor a push notification from a server, and then wait until the user is idle before triggering an update.

Trigger a restart once an update has been downloaded

If combined with BACKGROUND mode you can monitor the on-disk locations where updates are downloaded to. Once an update has been downloaded you can show the user a bar or popup suggesting to restart. As the update is already downloaded the apply+restart process will be fast.

How to use

Using the API is simple, and an example can be seen in the apps created by conveyor generate.

Electron apps can depend on the @hydraulic/conveyor-control module, and should also add a dependency on koffi which is used to invoke native code. You can then do this:

const updater = new OnlineUpdater("https://example.com/update-base-url");
let currentVersion = updater.getCurrentVersion()
let latestVersion = await updater.getCurrentVersionFromRepository()
if (currentVersion.compareTo(latestVersion) < 0) {
    updater.triggerUpdateCheckUI();
}

The generated sample apps go further and show how to read the update site from package.json, and communicate the version from the NodeJS backend to the browser frontend.

On the JVM things are equally easy. Add a dependency on dev.hydraulic.conveyor:conveyor-control:1.1 or above, and then:

public static void update() {
    var updateController = SoftwareUpdateController.getInstance();
    if (updateController == null) return;   // Not packaged by Conveyor.

    if (updateController.canTriggerUpdateCheckUI() != SoftwareUpdateController.Availability.AVAILABLE)
        return;  // Not a supported OS or not packaged with built in update support.
    
    if (updateController.getCurrentVersion().compareTo(updateController.getCurrentVersionFromRepository()) >= 0)
        return;  // No update available.
    
    updateController.triggerUpdateCheckUI();
}

In a real app you’d be careful to do error handling and run this code on a background thread, as these operations can block. Also, be aware that in all cases calling triggerUpdateCheckUI restarts the app, even if there’s no update available.

Support for non-Electron/JVM apps is also available, but you’ll need to do more work in those cases to parse the version numbers from the update site. See the user guide for more details.

.