アプリを起動すると自動的に最新版があるか確認し、もし更新されていれば自動的にバージョンアップしてくれる機能を実装します。
いくつか方法はあるのですが今回は
- electron-builderの機能を使う
- ビルドしたアプリはAWS S3へアップ
- 更新があるとユーザーの確認無しでダウンロードしちゃう
という方向でまとめていきます。
最終的なファイル
GitHubにこのページのファイルの一覧をアップしました。必要に応じてご利用ください。 github.com
準備
macOS用アプリはコード署名が必要
自動アップデートに対応するために、macOS用のアプリはコード署名が必須です。先に以下のページを参考に設定を行ってください。初めての方は正直ちょっと大変です(;´∀`) Windowsではコード署名は必須ではありません。 blog.katsubemakito.net
AWS側の準備
今回はAWS上のS3へアップロードします。専用のIAMとS3のバケットを作成します。
IAM
electron-builderからAWS S3を利用するためのIAMを作成します。IAMは情報が漏れた場合などを考え、使い回すさずに環境ごとに作成するのが定石です。
AWSのコンソールにあるIAMにアクセスし、左側メニュー「ユーザー」をクリック。
「ユーザーを追加」ボタンをクリック。
ユーザー名には適当な文字列を入力します。個人的には「ユーザー名@利用場所(端末やサーバ名)」的な感じでつけることが多いです。アクセスの種類は「プログラムによるアクセス」にチェック。コンソールにはログインしません。
最後にこのIAMに付与する権限を設定します。今回はS3が使えれば良いので「既存のポリシーを直接アタッチ」をクリックし、「AmazonS3FullAccess」にチェックします。
最終的に作成が完了したら、「アクセスキーID」と「シークレットアクセスキー」を適当な場所にメモします。
S3のバケット
こちらもIAMと同様にAWSのコンソールにあるS3へアクセスし、右側にある「バケットを作成」ボタンから画面の指示に従って作成します。
注意点としては「パブリックアクセスをすべてブロック」のチェックを外してください。
作成するリージョンやバケットの名前、バージョニングの有無などその他の項目は自由に設定してください。ただ一般に公開しますので専用のバケットを作成し(他と流用しない)、アクセスログを記録する設定を行うのが万が一の時のためにおすすめです。
IAMの情報をセットする
先ほど作成したIAMの情報をホームディレクトリに「.aws」という名前のフォルダを作成、その中に「credentials」という名前のファイルに以下のようなフォーマットで記述し保存します。
$ cat ~/.aws/credentials [default] aws_access_key_id=AAAAAAAAAAAAAAAAAAAA aws_secret_access_key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
詳細はAWSのドキュメントを参照してください。 docs.aws.amazon.com
モジュールを追加
electron本体の機能ではなく、electron-builderの機能を利用する関係で「electron-updater」と、ログを記録するための「electron-log」を追加でインストールします。
$ npm install electron-updater electron-log
electron-builderの自動アップデートはよく出来ており、ビルドが終わると自動的にS3へアップロードしてくれる機能が付いています。後述しますがアップデートに必要な情報をまとめたファイル(latest.yml, latest-mac.yml)も自動生成されるので、コマンドを一発叩けばあとは全自動ですべてが終わります。
これで準備が整いました。ではここから具体的なコードを書いていきましょう。
ソースコード
自動アップデートに必要な登場人物は2人。 メインプロセス用の「JavaScript(index.js)」と「package.json」です。今回はアプリを起動したらバージョン情報だけを表示するレンダラープロセス側のHTML(index.html)も用意しました。
メインプロセス
ポイントはElectronから提供されるautoUpdaterではなく、electron-updaterのautoUpdaterを利用している点です。利用方法も変わりますので別物と考えてください。
ここではアプリの実行準備が整った段階で autoUpdater.checkForUpdatesAndNotify()
を呼び、アップデートがあれば自動的にダウンロードしています。ダウンロードが完了するとupdate-downloaded
イベントが発生しますのですぐに適用するかダイアログでユーザーに確認します。なおダイアログでキャンセル(「あとで」をクリック)した場合、次回の起動時に反映されます。
どこにどういった方法でアップロードするかはpackage.jsonで指定するため、ここではロジックのみを記述します。コード量もそれほど多くないので手軽に利用できますね。
const { app, dialog, BrowserWindow } = require('electron'); const { autoUpdater } = require('electron-updater'); const log = require('electron-log'); // アップデートに関する情報をログファイルへ出力 autoUpdater.logger = log; autoUpdater.logger.transports.file.level = 'info'; // ウィンドウ管理用 let mainWin; /** * ウィンドウを作成 */ function createWindow () { mainWin = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: true, enableRemoteModule: true } }) mainWin.loadFile('index.html') } // Electronの準備が完了 app.whenReady().then(()=>{ // ウィンドウを作成 createWindow(); // アップデートをチェック autoUpdater.checkForUpdatesAndNotify(); }) // ウィンドウがすべて閉じられたら app.on('window-all-closed', () => { // macOS以外は終了 if (process.platform !== 'darwin') { app.quit() } }) // ウィンドウが0個の状態でアクティブになったら(macOS用) app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow() } }) //------------------------------------------- // 自動アップデート関連のイベント処理 //------------------------------------------- // アップデートをチェック開始 autoUpdater.on('checking-for-update', () => { log.info(process.pid, 'checking-for-update...'); }) // アップデートが見つかった autoUpdater.on('update-available', (ev, info) => { log.info(process.pid, 'Update available.'); }) // アップデートがなかった(最新版だった) autoUpdater.on('update-not-available', (ev, info) => { log.info(process.pid, 'Update not available.'); }) // アップデートのダウンロードが完了 autoUpdater.on('update-downloaded', (info) => { const dialogOpts = { type: 'info', buttons: ['更新して再起動', 'あとで'], message: 'アップデート', detail: '新しいバージョンをダウンロードしました。再起動して更新を適用しますか?' } // ダイアログを表示しすぐに再起動するか確認 dialog.showMessageBox(mainWin, dialogOpts).then((returnValue) => { if (returnValue.response === 0){ autoUpdater.quitAndInstall() } }) }); // エラーが発生 autoUpdater.on('error', (err) => { log.error(process.pid, err); })
レンダラープロセス
メインプロセスから呼び出されるHTML(index.html)です。バージョン情報を取り出してH1タグに挿入しているだけのもの。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" /> <title>autoUpdate sample</title> <style> body{background-color: white;} </style> </head> <body> <h1></h1> <script> const {app} = require('electron').remote; const version = app.getVersion(); // 現在のバージョン情報を取得(package.json) // h1タグにバージョンを入れる document.querySelector("h1").textContent = version; </script> </body> </html>
package.json
次の内容を既存のpackage.jsonに追加してください。
{ "scripts": { "build-mac-publish":"electron-builder --mac --x64 --publish always", "build-win-publish": "electron-builder --win --x64 --publish always" }, "build": { "mac": { "target": ["zip", "dmg"], "publish": [ { "provider": "s3", "bucket": "electron.blog.katsubemakito.net", "region": "ap-northeast-1", "acl":"public-read", "storageClass": "STANDARD", "path": "/darwin/" } ] }, "win": { "publish": [ { "provider": "s3", "bucket": "electron.blog.katsubemakito.net", "region": "ap-northeast-1", "acl":"public-read", "storageClass": "STANDARD", "path": "/win32/" } ] } } }
- scripts
- electron-builderコマンドに
--publish always
オプションを付けると、S3へ自動的にアップロードしてくれます。逆にこれを付けないとローカルでビルドした後に終了します。 - target
- macOSのtargetには「dmg」や「pkg」などのインストーラーを指定することが多いと思いますが、zip形式がないとアップデート時にエラー終了してしまうため、ここではdmgとzipの2つの形式を同時に指定しています(2種類作成されます)
- publish
-
providerでサーバに公開する際の設定を行います。今回はAWS S3を指定しますが、他にも「github」「spaces」「generic」が利用できます。
- githubはGitHub上の指定したリポジトリのReleaseを利用するもの
- spacesはDigitalOcean社が提供するサービスで、雑に言うとS3のような物です。
- genericは自分で用意したサーバを使う場合に指定します
- genericを指定した場合は自動でアップロードされません。
S3を利用する際に指定できる設定項目には以下のような物があります。
項目 | 説明 | 例 |
---|---|---|
region | バケットが存在するリージョン | ap-northeast-1 |
bucket | S3バケット名 | electron.blog.katsubemakito.net |
path | バケット内のサブディレクトリ | /darwin/ |
acl | アクセス権限。privateかpublic-readを指定 | public-read |
storageClass | ストレージタイプ。 STANDARD, REDUCED_REDUNDANCY, STANDARD_IAのいずれかを指定 | STANDARD |
ビルドする
ビルド実行
ビルドはこれまでと変わりません。今回はpackage.jsonのscriptsに定義した以下のコマンドを実行すれば、ビルドからS3へのアップロードまですべて自動で行ってくれます。
$ npm run build-mac-publish $ npm run build-win-publish
実際に実行するとこれまでの表示に加え、最後にuploadingが加わっていますね。
$ npm run build-mac-publish > sample-autoupdate@1.0.0 build-mac-publish /Users/katsube/Develop/electron-sample-autoupdate > electron-builder --mac --x64 --publish always • electron-builder version=22.9.1 os=19.6.0 • loaded configuration file=package.json ("build" field) • writing effective config file=dist/builder-effective-config.yaml • packaging platform=darwin arch=x64 electron=10.2.0 appOutDir=dist/mac • default Electron icon is used reason=application icon is not set • signing file=dist/mac/sample-autoupdate.app identityName=Developer ID Application: Makito Katsube (AAAAAAAAAA) identityHash=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA provisioningProfile=none • building target=DMG arch=x64 file=dist/sample-autoupdate-1.0.0.dmg • building block map blockMapFile=dist/sample-autoupdate-1.0.0.dmg.blockmap • publishing publisher=S3 (bucket: electron.blog.katsubemakito.net) • uploading file=sample-autoupdate-1.0.0.dmg.blockmap provider=S3 • uploading file=sample-autoupdate-1.0.0.dmg provider=S3 • uploading file=latest-mac.yml provider=S3
Windowsはそれほど時間はかからないのですが、macOSの場合はビルドする度に「公証」で数分止まることもあり正直ちょっと億劫ですねw
S3の状態を確認
マネジメントコンソールか、CLIからバケットを確認すると確かにアップロードされているのがわかります。
$ aws s3 ls s3://electron.blog.katsubemakito.net/darwin/ 2020-12-31 19:47:12 539 latest-mac.yml 2020-12-31 19:47:00 75910960 sample-autoupdate-1.0.0-mac.zip 2020-12-31 19:46:35 78163579 sample-autoupdate-1.0.0.dmg 2020-12-31 19:46:35 82960 sample-autoupdate-1.0.0.dmg.blockmap $ aws s3 ls s3://electron.blog.katsubemakito.net/win32/ 2020-12-31 01:15:33 362 latest.yml 2020-12-31 00:46:10 54567528 sample-autoupdate Setup 1.0.0.exe 2020-12-31 00:46:10 58506 sample-autoupdate Setup 1.0.0.exe.blockmap
electron-updaterの「autoUpdater」は、Windowsなら「latest.yml」、macOSは「latest-mac.yml」を参照し、最新のバージョンはいくつか、アプリのファイルがどこにあるかなどを確認しています。
latest.yml, latest-mac.ymlの中身
実際の中身は以下のようになっています。Windows用もmacOS用もフォーマットは同じです。
version: 1.0.0 files: - url: sample-autoupdate-1.0.0-mac.zip sha512: ZjA+AWAcl9e8xqU1y4wydG+jFYYqu9A2Iep4sXsdeYcvNOvmrE2rSdNe+metIUsZmzTaNgGSH2u1ozc78U52dA== size: 75910554 blockMapSize: 81375 - url: sample-autoupdate-1.0.0.dmg sha512: 2qyhbVj0nSK1ECz8SSDhl8loBD+fNlYRtqesxdWzdbT2hCE/YBf+2XbYDgYJ4ZcmFwBh7gdAN2RfNy2HSUHm9g== size: 78166321 path: sample-autoupdate-1.0.0-mac.zip sha512: ZjA+AWAcl9e8xqU1y4wydG+jFYYqu9A2Iep4sXsdeYcvNOvmrE2rSdNe+metIUsZmzTaNgGSH2u1ozc78U52dA== releaseDate: '2020-12-30T13:18:34.817Z'
実際にバージョンアップしてみる
現在のバージョンをインストール
忘れないようにインストールしておきます。注意すべきはnpm start
ではautoUpdateが実行されないので、自動アップデートを試す際には必ずexeやdmgなどからインストールしておく必要があります。
試しにnpm start
すると以下のようなメッセージが表示され、アップデートのチェックがスキップされます。
$ npm start 20:55:28.021 › Skip checkForUpdatesAndNotify because application is not packed
package.jsonを修正
ではバージョンアップしていきます。 package.jsonのversionの箇所を先ほどよりも大きな数値にします。
{ "name": "sample-autoupdate", "version": "1.1.0", // 以下略 }
以上ですw 実際にはコードやアセット(画像等の素材)の修正を行った後に上記の作業を行います。
ビルドする
あとは先ほどと同様にビルド&パブリッシュすれば完了です。
$ npm run build-mac-publish $ npm run build-win-publish
(設定がうまく終わっていれば)驚くほど簡単!
自動アップデートする
Windows
旧バージョンのアプリを起動してしばらく待つと、裏側でS3上にあるlatest.ymlを取得し、現在インストールされているものより新しいバージョンがあるかチェックします。もし新しいバージョンがある場合は自動的にダウンロードし次のようなダイアログを表示します。
「更新して再起動」を押すか、次回起動時に最新のバージョンが適用されます。
macOS
こちらもWindowsと同様です。 旧バージョンのアプリを起動してしばらく待つと、裏側でS3上にあるlatest-mac.ymlを取得し、現在インストールされているものより新しいバージョンがあるかチェック、もし新しいバージョンがある場合は自動的にダウンロードし次のようなダイアログを表示します。
「更新して再起動」を押すか、次回起動時に最新のバージョンが適用されます。
ログを確認する
今回のようにうまく更新できればよいのですが、一発でうまく行かないことも多いと思います。そんな時にはログを確認します。特に広く一般に配布するexeやdmgだとconsole.log
でデバッグ内容を確認できないため、最初に設定したelectron-logが活躍してくれます。
例えばmacOSの場合は以下のファイルにどこで何が起こったかが記録されます。
$ cat ~/Library/Logs/sample-autoupdate/main.log [2020-12-30 21:56:50.846] [info] Checking for update [2020-12-30 21:56:50.861] [info] 42155 checking-for-update... [2020-12-30 21:56:51.102] [info] Generated new staging user ID: ac7e80a1-7159-593b-9519-06b55d99735a [2020-12-30 21:56:51.530] [info] Update for version 1.0.0 is not available (latest version: 1.0.0, downgrade is disallowed). [2020-12-30 21:56:51.532] [info] 42155 Update not available.
Windowsの場合は以下です。
PS C:\Users\katsube> type '.\AppData\Roaming\sample-autoupdate\logs\main.log' [2020-12-31 21:27:29.509] [info] Checking for update [2020-12-31 21:27:29.526] [info] 2196 checking-for-update... [2020-12-31 21:27:30.866] [info] Update for version 1.1.0 is not available (latest version: 1.1.0, downgrade is disallowed). [2020-12-31 21:27:30.879] [info] 2196 Update not available.
electron-logについて詳しい利用方法は以下のページをご覧ください。 blog.katsubemakito.net
トラブルシューティング
Error: ZIP file not provided
macOSでアップデートができないと思ってログを確認すると、以下のようなエラーが出ていた場合は、package.jsonの指定をミスっています。targetにzipを指定すると解消します。
$ cat ~/Library/Logs/sample-autoupdate/main.log [2020-12-30 22:02:37.493] [error] 42565 Error: ZIP file not provided: [ { "url": "https://s3-ap-northeast-1.amazonaws.com/electron.blog.katsubemakito.net/sample-autoupdate-1.1.0.dmg", "info": { "url": "sample-autoupdate-1.1.0.dmg", "sha512": "5gp0AdyD5efK8gt0i7An1rUmOqx0eSiyDjxY1ZlvonLh4w8U2A2N7/Gl7c+nrzrTadf2TpT23xmrHuEq3rx2dQ==", "size": 78166291 } } ]
GitHubのelectron-builderのIssueでも報告されていますが、どうもメンテナーの方が面倒なようでZipを不要な仕様に改修はしない方針のようです(寄付があれば別とも書かれていますがw) github.com
Windowsでアンインストールできない
WindowsにインストールしたElectronアプリを削除しようとすると以下のようなダイアログが表示され、アンインストールできない場合があります。
NSIS Error Installer integrity check has failed. Common causes include incomplete download and damaged media. Contact the installer's author to obtain a new copy. More information at: http://nsis.sf.net/NSIS_Error
これはmacOSでビルドした物をWindowsに入れた場合に発生するようです。試しにWindows用のEXEはWindowsでビルドすると発生しなくなりました。
こちらもGitHubのelectron-builderのIssueでも報告されていますが、この記事を書いている時点ではまだ解決していないようです。 github.com