Skip to content

Instantly share code, notes, and snippets.

@talyguryn
Last active November 25, 2020 16:10
Show Gist options
  • Save talyguryn/85abb18e93ce6ce928c8a20e6ad4b233 to your computer and use it in GitHub Desktop.
Save talyguryn/85abb18e93ce6ce928c8a20e6ad4b233 to your computer and use it in GitHub Desktop.

Autoupdates for Electron App

This guide will show you how to prepare sample Electron application for publishing from scratch:

  • create a new app
  • build distos
  • publish release to GitHub
  • set up autoupdating

Your app's updates may be stored on GitHub repo's releases page. It is free and user-friendly.

Table of contents

Base demo app

Create a new package.json file.

{
  "name": "self-updating-test-app",
  "version": "0.0.1",
  "main": "src/main.js",
  "scripts": {
    "start": "electron ."
  }
}

And install electon as a dev-dependency

yarn add -D electron

Now you need to create main script file in the sources directory: src/main.js

const { app, BrowserWindow } = require('electron');

/* Init application */
const init = () => {
  /* Create the browser window */
  const mainWindow = new BrowserWindow();

  /* Text content to be shown in the window */
  let content = `${app.getName()} v${app.getVersion()}`;

  /* Load plain text content */
  mainWindow.webContents.loadURL('data:text/plain;charset=utf-8,' + encodeURI(content));
};

app.on('ready', init);

The structure of your project will look like this:

self-updating-test-app
|-- node_modules/
|-- src/
|   |-- main.js 
|-- package.json
|-- yarn.lock

Run the app

yarn start

Now we are going to build a package for distribution

Build

For building I prefer use electron-builder tool.

Add it to your dev-dependencies.

yarn add -D electron-builder

Then add a new node script to your package.json file

{
  "name": "self-updating-test-app",
  "version": "0.0.1",
  "main": "src/main.js",
  "scripts": {
    "start": "electron .",
    "dist": "electron-builder",
  },
  "devDependencies": {
    "electron": "^5.0.1",
    "electron-builder": "^20.40.2"
  }
}

And now you can build a distro file for running OS

yarn dist

electron-builder will create a dist directory and put here release files. You should add dist directory to .gitignore file:

.idea/
dist/
node_modules/
.DS_Store

My dist directory looks like

|-- dist
|   |-- mac/
|   |-- builder-effective-config.yaml
|   |-- self-updating-test-app-0.0.1-mac.zip
|   |-- self-updating-test-app-0.0.1.dmg
|   |-- self-updating-test-app-0.0.1.dmg.blockmap

I can send self-updating-test-app-0.0.1.dmg to my friend and he will be able to install and run my app.

Code signing

Auto-updating will not work if you have not code-sign your app.

Read more about Code Signing.

While an application is under development you can use a free certificate.

Publish

Next step is a release publishing. You can use GitHub repo's releases page for it. electron-builder can ship distros automatically after building.

Get a new GitHub access token

GitHub's guide «Creating a personal access token for the command line».

Open your profile's settings: Settings > Developer settings > Personal access tokens > Generate new token

Select the repo scope

New token will be generated

Save it somewhere and use in console before shipping a release.

export GH_TOKEN=e4b064b76f30966c1347948219a9cc27

You will see an error if you skip the token:

Error: GitHub Personal Access Token is not set, neither programmatically, nor using env "GH_TOKEN"

Shipping

Add repository info and two more scripts to package.json file

yarn ship — for building and publishing release for current OS

yarn ship:all — for building and publishing release for macOS, Windows and Linux

{
  "name": "self-updating-test-app",
  "version": "0.0.1",
  "main": "src/main.js",
  "scripts": {
    "start": "electron .",
    "dist": "electron-builder",
    "ship": "electron-builder -p always",
    "ship:all": "electron-builder -mwl --x64 --ia32 -p always"
  },
  "repository": {
    "type" : "git",
    "url" : "https://github.com/talyguryn/self-updating-test-app.git"
  },
  "devDependencies": {
    "electron": "^5.0.1",
    "electron-builder": "^20.40.2"
  }
}

Run yarn ship. App will be built and sent to release draft.

...
  • building        target=macOS zip arch=x64 file=dist/self-updating-test-app-0.0.1-mac.zip
  • building        target=DMG arch=x64 file=dist/self-updating-test-app-0.0.1.dmg
  • building block map blockMapFile=dist/self-updating-test-app-0.0.1.dmg.blockmap
  • publishing      publisher=Github (owner: talyguryn, project: self-updating-test-app, version: 0.0.1)
  • uploading       file=self-updating-test-app-0.0.1.dmg.blockmap provider=GitHub
  • uploading       file=self-updating-test-app-0.0.1.dmg provider=GitHub
  • creating GitHub release reason=release doesn't exist tag=v0.0.1 version=0.0.1
    [====================] 100% 0.0s | self-updating-test-app-0.0.1.dmg to GitHub
  • building embedded block map file=dist/self-updating-test-app-0.0.1-mac.zip
  • uploading       file=self-updating-test-app-0.0.1-mac.zip provider=GitHub
    [====================] 100% 0.0s | self-updating-test-app-0.0.1-mac.zip to GitHub
✨  Done in 94.48s.

Open releases page and see this draft. You can edit it and publish.

Work with updates

Now you can publish releases. It is time to set up autoupdating.

There are two packages that helps you to check available updates, download and install them:

  • update-electron-app is the simplest method to get updates and notify user.
  • electron-updater allow you define listeners for autoupdates events such as checking-for-update, update-available, update-not-available, download-progress and error.

update-electron-app

Install dependency

yarn add update-electron-app

And add to any place in main process.

require('update-electron-app')();

Our main.js for example

const { app, BrowserWindow } = require('electron');

/* Check for updates */
require('update-electron-app')();

/* Init application */
const init = () => {
  /* Create the browser window */
  const mainWindow = new BrowserWindow();

  /* Text content to be shown in the window */
  let content = `${app.getName()} v${app.getVersion()}`;

  /* Load plain text content */
  mainWindow.webContents.loadURL('data:text/plain;charset=utf-8,' + encodeURI(content));
};

app.on('ready', init);

Testing

Bump version to 0.0.2. Build and publish app with enabled autoupdating. Then install and run it.

It will check update.electronjs.org server for info about updates every 10 minutes.

App send requests to https://update.electronjs.org/[OWNER]/[REPO]/[PLATFORM]-[ARCH]/[VERSION] with app's params.

For example:

https://update.electronjs.org/talyguryn/self-updating-test-app/darwin/0.0.2

Then you can build same app with bumped version 0.0.3 for example. Release it too.

A few minutes later your runned 0.0.2 app will see and start downloading latest update. Then it will notify user and ask to restart app right now to use new version.

electron-updater

Install dependency

yarn add electron-updater

Require autoUpdater from electron-updater package.

const { autoUpdater } = require('electron-updater');

Then call checkForUpdates() function

autoUpdater.checkForUpdates();

Application will check for updates and download them automatically.

If you do not need to download it without asking user then disable autoDownload option.

autoUpdater.autoDownload = false

Then if you want to download available update then call downloadUpdate().

A successfully downloaded update will be applied the next time the application starts.

You can also use checkForUpdatesAndNotify() function to show a notification.

Events

See Events section on docs page for more information.

Here is a list of available events listeners

autoUpdater.on('error', (error) => {});
autoUpdater.on('checking-for-update', () => {});
autoUpdater.on('update-available', (updateInfo) => {});
autoUpdater.on('update-not-available', (updateInfo) => {});
autoUpdater.on('download-progress', (updateInfo) => {});
autoUpdater.on('update-downloaded', (info) => {});

Result

Final version of main.js.

const { app, BrowserWindow } = require('electron');
const { autoUpdater } = require('electron-updater');

/* Init application */
const init = () => {
  /* Create the browser window */
  const mainWindow = new BrowserWindow();

  /* Text content to be shown in the window */
  let content = `${app.getName()} v${app.getVersion()}`;

  /* Load plain text content */
  mainWindow.webContents.loadURL('data:text/plain;charset=utf-8,' + encodeURI(content));
  
  /* Update is ready */
  autoUpdater.on('update-downloaded', (updateInfo) => {
    /* Notify user about ready to be installed update */
    // ...
    
    /* Or force quit app and install update */
    // autoUpdater.quitAndInstall();
  });

  /* Check for updates manually */
  autoUpdater.checkForUpdates();

  /* Check updates every 10 minutes */
  setInterval(() => {
    autoUpdater.checkForUpdates();
  }, 10 * 60 * 1000);
};

app.on('ready', init);

Debug

Use elecron-log package to work with logs easily. See docs.

yarn add electron-log
autoUpdater.logger

Example main.js file with defined autoUpdater.logger

const { app, BrowserWindow } = require('electron');
const { autoUpdater } = require('electron-updater');
const logger = require('electron-log');

/* Init application */
const init = () => {
  /* Create the browser window */
  const mainWindow = new BrowserWindow();

  /* Text content to be shown in the window */
  let content = `${app.getName()} v${app.getVersion()}`;

  /* Load plain text content */
  mainWindow.webContents.loadURL('data:text/plain;charset=utf-8,' + encodeURI(content));
  
  /* Define logger for autoUpdater logs */
  autoUpdater.logger = logger;

  /* Check for updates manually */
  autoUpdater.checkForUpdates();

  /* Check updates every 10 minutes */
  setInterval(() => {
    autoUpdater.checkForUpdates();
  }, 10 * 60 * 1000);
};

app.on('ready', init);
Example log file ~/Library/Logs/self-updating-test-app/log.log
[2019-05-22 13:23:09.761] [info] Checking for update
[2019-05-22 13:23:17.237] [info] Found version 0.0.3 (url: self-updating-test-app-0.0.3-mac.zip, self-updating-test-app-0.0.3.dmg)
[2019-05-22 13:23:17.237] [info] Downloading update from self-updating-test-app-0.0.3-mac.zip, self-updating-test-app-0.0.3.dmg
[2019-05-22 13:23:17.258] [debug] updater cache dir: /Users/taly/Library/Application Support/Caches/self-updating-test-app-updater
[2019-05-22 13:23:17.275] [info] No cached update info available
[2019-05-22 13:24:34.305] [info] New version 0.0.3 has been downloaded to /Users/taly/Library/Application Support/Caches/self-updating-test-app-updater/pending/self-updating-test-app-0.0.3-mac.zip
[2019-05-22 13:24:34.423] [info] / requested
[2019-05-22 13:24:34.432] [info] /1558520674320-6689.zip requested
[2019-05-22 13:24:34.432] [info] /1558520674320-6689.zip requested by Squirrel.Mac, pipe /Users/taly/Library/Application Support/Caches/self-updating-test-app-updater/pending/self-updating-test-app-0.0.3-mac.zip
[2019-05-22 13:24:39.726] [info] Proxy server for native Squirrel.Mac is closed (was started to download https://github.com/talyguryn/self-updating-test-app/releases/download/v0.0.3/self-updating-test-app-0.0.3-mac.zip)
Custom logs

Example main.js file with enabled logs for all events

const { app, BrowserWindow } = require('electron');
const { autoUpdater } = require('electron-updater');
const logger = require('electron-log');

/* Init application */
const init = () => {
  /* Create the browser window */
  const mainWindow = new BrowserWindow();

  /* Text content to be shown in the window */
  let content = `${app.getName()} v${app.getVersion()}`;

  /* Load plain text content */
  mainWindow.webContents.loadURL('data:text/plain;charset=utf-8,' + encodeURI(content));

  /**
   * Autoupdater events
   */

  autoUpdater.on('checking-for-update', () => {
    logger.log('Checking for update');
  });

  autoUpdater.on('error', (error) => {
    logger.error('Error while checking for updates', error);
  });

  autoUpdater.on('update-available', (updateInfo) => {
    logger.log('Update is available:', updateInfo);
  });

  autoUpdater.on('update-not-available', (updateInfo) => {
    logger.log('No updates are available', updateInfo);
  });

  autoUpdater.on('download-progress', (progressInfo) => {
    let logMessage = `speed ${progressInfo.bytesPerSecond} b/s; progress ${progressInfo.percent}%; downloaded ${progressInfo.transferred} out of ${progressInfo.total} bytes`;

    logger.log(logMessage);
  });

  autoUpdater.on('update-downloaded', (updateInfo) => {
    logger.log('Update is ready', updateInfo);

    /* Notify user about ready to be installed update */
    // ...

    /* Or force quit app and install update */
    // autoUpdater.quitAndInstall();
  });

  /* Check for updates manually */
  autoUpdater.checkForUpdates();

  /* Check updates every 10 minutes */
  setInterval(() => {
    autoUpdater.checkForUpdates();
  }, 10 * 60 * 1000);
};

app.on('ready', init);
Example log file ~/Library/Logs/self-updating-test-app/log.log
[2019-05-22 13:08:45.117] [info] Checking for update
[2019-05-22 13:08:52.371] [info] Update is available: { version: '0.0.3',
  files:
   [ { url: 'self-updating-test-app-0.0.3-mac.zip',
       sha512:
        '9I5RdFroVqNgB7fsQL5D2jV1vAxtL/A8pc2YgaCddIowEKB6yg3jLesiTt/g/Hhq4Z4N9A3SP1Fv5oopVcB18w==',
       size: 57172322,
       blockMapSize: 59483 },
     { url: 'self-updating-test-app-0.0.3.dmg',
       sha512:
        'xxRCF93tK+X6PDh8dWgKoXBSA2ias/K9XMNM6Z+fmqrv8yZc5pevkPgnOGIeCPkVTfzmPe2/2IUt1J1b0HtfFA==',
       size: 58915945 } ],
  path: 'self-updating-test-app-0.0.3-mac.zip',
  sha512:
   '9I5RdFroVqNgB7fsQL5D2jV1vAxtL/A8pc2YgaCddIowEKB6yg3jLesiTt/g/Hhq4Z4N9A3SP1Fv5oopVcB18w==',
  releaseDate: '2019-05-22T08:48:27.699Z',
  releaseName: '0.0.3',
  releaseNotes: '' }
[2019-05-22 13:08:53.886] [info] speed 1469207 b/s; progress 2.716265398491249%; downloaded 1552952 out of 57172322 bytes
[2019-05-22 13:08:54.885] [info] speed 2106855 b/s; progress 7.580241362245179%; downloaded 4333800 out of 57172322 bytes
[2019-05-22 13:08:55.891] [info] speed 1873643 b/s; progress 10.03801804656456%; downloaded 5738968 out of 57172322 bytes
[2019-05-22 13:08:56.893] [info] speed 1758388 b/s; progress 12.502287383045244%; downloaded 7147848 out of 57172322 bytes
[2019-05-22 13:08:57.924] [info] speed 1711286 b/s; progress 15.253380822979343%; downloaded 8720712 out of 57172322 bytes
[2019-05-22 13:08:58.926] [info] speed 1685342 b/s; progress 17.975859017935285%; downloaded 10277216 out of 57172322 bytes
[2019-05-22 13:08:59.934] [info] speed 1612534 b/s; progress 20.03951492472179%; downloaded 11457056 out of 57172322 bytes
[2019-05-22 13:09:00.942] [info] speed 1583630 b/s; progress 22.47516901622432%; downloaded 12849576 out of 57172322 bytes
[2019-05-22 13:09:01.950] [info] speed 1543525 b/s; progress 24.62467065794529%; downloaded 14078496 out of 57172322 bytes
[2019-05-22 13:09:02.958] [info] speed 1503133 b/s; progress 26.630424421103626%; downloaded 15225232 out of 57172322 bytes
[2019-05-22 13:09:03.970] [info] speed 1478237 b/s; progress 28.80854130780275%; downloaded 16470512 out of 57172322 bytes
[2019-05-22 13:09:05.009] [info] speed 1455722 b/s; progress 31.015273439480033%; downloaded 17732152 out of 57172322 bytes
[2019-05-22 13:09:06.050] [info] speed 1438975 b/s; progress 33.27856440744177%; downloaded 19026128 out of 57172322 bytes
[2019-05-22 13:09:07.091] [info] speed 1428167 b/s; progress 35.62904441768169%; downloaded 20369952 out of 57172322 bytes
[2019-05-22 13:09:08.130] [info] speed 1421222 b/s; progress 38.03608326420606%; downloaded 21746112 out of 57172322 bytes
[2019-05-22 13:09:09.130] [info] speed 1423388 b/s; progress 40.58619833562121%; downloaded 23204072 out of 57172322 bytes
[2019-05-22 13:09:10.132] [info] speed 1433858 b/s; progress 43.395193919183484%; downloaded 24810040 out of 57172322 bytes
[2019-05-22 13:09:11.144] [info] speed 1469054 b/s; progress 47.06331850576228%; downloaded 26907192 out of 57172322 bytes
[2019-05-22 13:09:12.144] [info] speed 1488212 b/s; progress 50.280091824851894%; downloaded 28746296 out of 57172322 bytes
[2019-05-22 13:09:13.152] [info] speed 1497298 b/s; progress 53.22432767379992%; downloaded 30429584 out of 57172322 bytes
[2019-05-22 13:09:14.164] [info] speed 1497697 b/s; progress 55.88957537879955%; downloaded 31953368 out of 57172322 bytes
[2019-05-22 13:09:15.177] [info] speed 1507442 b/s; progress 58.926821268515205%; downloaded 33689832 out of 57172322 bytes
[2019-05-22 13:09:16.181] [info] speed 1506547 b/s; progress 61.53483848355853%; downloaded 35180896 out of 57172322 bytes
[2019-05-22 13:09:17.198] [info] speed 1483957 b/s; progress 63.2544397969353%; downloaded 36164032 out of 57172322 bytes
[2019-05-22 13:09:18.201] [info] speed 1461507 b/s; progress 64.8589084767276%; downloaded 37081344 out of 57172322 bytes
[2019-05-22 13:09:19.201] [info] speed 1436474 b/s; progress 66.26307044167281%; downloaded 37884136 out of 57172322 bytes
[2019-05-22 13:09:20.211] [info] speed 1414615 b/s; progress 67.75374979522434%; downloaded 38736392 out of 57172322 bytes
[2019-05-22 13:09:21.217] [info] speed 1394493 b/s; progress 69.243757495104%; downloaded 39588264 out of 57172322 bytes
[2019-05-22 13:09:22.225] [info] speed 1376768 b/s; progress 70.79099568494%; downloaded 40472856 out of 57172322 bytes
[2019-05-22 13:09:23.236] [info] speed 1362832 b/s; progress 72.48198175333862%; downloaded 41439632 out of 57172322 bytes
[2019-05-22 13:09:24.244] [info] speed 1351918 b/s; progress 74.28742880164985%; downloaded 42471848 out of 57172322 bytes
[2019-05-22 13:09:25.251] [info] speed 1342270 b/s; progress 76.12149109493926%; downloaded 43520424 out of 57172322 bytes
[2019-05-22 13:09:26.271] [info] speed 1341053 b/s; progress 78.44268420652917%; downloaded 44847504 out of 57172322 bytes
[2019-05-22 13:09:27.273] [info] speed 1348180 b/s; progress 81.22239289144142%; downloaded 46436728 out of 57172322 bytes
[2019-05-22 13:09:28.274] [info] speed 1370619 b/s; progress 84.97636321295468%; downloaded 48582960 out of 57172322 bytes
[2019-05-22 13:09:29.284] [info] speed 1411334 b/s; progress 89.99141927452237%; downloaded 51450184 out of 57172322 bytes
[2019-05-22 13:09:30.299] [info] speed 1447402 b/s; progress 94.8633991111993%; downloaded 54235608 out of 57172322 bytes
[2019-05-22 13:09:31.284] [info] speed 1486733 b/s; progress 100%; downloaded 57172322 out of 57172322 bytes
[2019-05-22 13:09:33.910] [info] Update is ready { version: '0.0.3',
  files:
   [ { url: 'self-updating-test-app-0.0.3-mac.zip',
       sha512:
        '9I5RdFroVqNgB7fsQL5D2jV1vAxtL/A8pc2YgaCddIowEKB6yg3jLesiTt/g/Hhq4Z4N9A3SP1Fv5oopVcB18w==',
       size: 57172322,
       blockMapSize: 59483 },
     { url: 'self-updating-test-app-0.0.3.dmg',
       sha512:
        'xxRCF93tK+X6PDh8dWgKoXBSA2ias/K9XMNM6Z+fmqrv8yZc5pevkPgnOGIeCPkVTfzmPe2/2IUt1J1b0HtfFA==',
       size: 58915945 } ],
  path: 'self-updating-test-app-0.0.3-mac.zip',
  sha512:
   '9I5RdFroVqNgB7fsQL5D2jV1vAxtL/A8pc2YgaCddIowEKB6yg3jLesiTt/g/Hhq4Z4N9A3SP1Fv5oopVcB18w==',
  releaseDate: '2019-05-22T08:48:27.699Z',
  releaseName: '0.0.3',
  releaseNotes: '',
  downloadedFile:
   '/Users/taly/Library/Application Support/Caches/self-updating-test-app-updater/pending/self-updating-test-app-0.0.3-mac.zip' }

Links

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment