update.electronjs.orgを使って、アプリの自動更新機能をお気楽に組み込む

ElectronのautoUpdater

Electronには、アプリの新しいバージョンをダウンロードして
更新する機能が用意されています。
https://www.electronjs.org/docs/api/auto-updater


Electronアプリからこの機能を使うには本来、
最新版アプリの情報を返すアップデートサーバーが必要になるのですが、


GitHubのReleasesに最新アプリバイナリを置いているアプリ
については、
Electron公式がアップデートサーバーを用意してくれているので、
アプリ側の修正だけで、簡単に自動アップデート機能を組み込めるようになっています。


(ここだよ、ここ)


update.electronjs.org

こちらが公式が用意しているサーバーで、
update.electronjs.org (GitHubにリダイレクト)

このサーバーは、次のパターンのURLを叩くと、GitHubの最新リリースの情報を取得して返します。

https://update.electronjs.org/update/${process.platform}/${app.getVersion()}


例えば、GitHubのレポジトリが"taku-o/myukkurivoice"で、
現在実行中のアプリのバージョンが"0.13.6"だとすれば、次のURLになりますね。
このURLをブラウザで開くと、JSON形式で最新リリースのデータが返ってくるのが分かります。
https://update.electronjs.org/taku-o/myukkurivoice/darwin-x64/0.13.6

{"name":"0.13.7","notes":"- \"MYukkuriVoice について\"ダイアログの表示を修正。\r\n  - ビルド時の...


対応している環境

update.electronjs.orgで対応している環境はLinux環境以外です。
Linuxは駄目かー。そっかー、残念ですね。


macOSのアプリについては、アプリにDeveloper ID Applicationの署名が必要。
実行中のアプリ、そして、ダウンロードする更新アプリ、両方に署名が必要です。
(アプリに署名がされていないと、自動アップデートがエラーになる。)

Error: Code signature at URL file:///Users/user/Library/Caches/jp.nanasi.myukkurivoice.ShipIt/update.TdmmDN0/
MYukkuriVoice-darwin-x64/MYukkuriVoice.app/ did not pass validation: コードオブジェクトがまったく署名されていません
Error: Could not get code signature for running application


Catalinaでは、アップル公証(Notarization)も必要かも?
調べていません。
わざわざ公証の実装を外してまで検証しないよ。
面倒だからね。仕方ないね(^_^)/


更新用のアプリをReleasesに置く

更新用のアプリは、zipで固めてGitHubのReleasesに置いておきます。 https://github.com/taku-o/myukkurivoice/releases/tag/0.13.8

1. バージョン番号のReleasesタグを作る(0.13.8 とか)
2. 更新用のアプリをzip形式にしてアップロードする
3. zipファイルのファイル名は、例えば、AppName-darwin-x64.zip のような形式にする


update.electronjs.orgは、Windows環境とMac環境に対応していますが、
いくつかアップロードされているzipファイルのうち、
どのzipファイルが、どの環境用のものかは、ファイル名から判断されています。
なので、zipファイル名には、mac, darwin, win32-x64など対応環境を示す文字を入れておきましょう。

mac
darwin
osx
win32-ia32
win32-x64


Electronアプリへの組み込み(自動的に更新処理)

Electronアプリのコードにアプリ自動更新の機能を組み込みます。


npmでupdate-electron-appを入れておいて、

npm install update-electron-app

アプリ側で、このコードを呼び出す。

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

簡単!


Electronアプリへの組み込み(任意のタイミングで更新)

上の実装は簡単なのですが、しかし、ドキュメントによると、
10分毎に更新をチェックしにいくようです。
https://www.electronjs.org/docs/tutorial/updates


なんてこった(>_<)!
そんなにアプリを頻繁に更新するわけないぞ(>_<)!
1ヶ月に1回更新できたらマシな方だ(>_<)!


そういう場合は、checkForUpdates()を手動で叩いて、
好きな頻度で更新するか、メニューから更新できるようにしたら良いでしょうな。

autoUpdater.checkForUpdates();
import {app, dialog, autoUpdater} from 'electron';

// register events
autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName) => {
  const dialogOptions = {
    type: 'info',
    buttons: ['Restart', 'Later'],
    title: 'Application Update',
    message: process.platform === 'win32' ? releaseNotes : releaseName,
    detail: '新しいアプリのダウンロードが完了しました。アプリを再起動して更新を適用します。',
  };
  dialog.showMessageBox(dialogOptions)
  .then((result) => {
    const btnId: number = result.response;
    if (btnId === 0) {
      autoUpdater.quitAndInstall();
    }
  });
});

autoUpdater.on('error', (err: Error) => {
  const dialogOptions = {
    type: 'error',
    title: 'Application Version Check Error.',
    message: 'アプリのアップデート中にエラーが発生しました。',
    buttons: ['OK'],
    defaultId: 0,
    cancelId: 0,
  };
  dialog.showMessageBox(dialogOptions);
});

// setup feed url
const server = 'https://update.electronjs.org';
const feed = `${server}/taku-o/myukkurivoice/${process.platform}-${process.arch}/${app.getVersion()}`;
autoUpdater.setFeedURL({
  url: feed,
  serverType: 'json',
});

// update
autoUpdater.checkForUpdates();


いったんcheckForUpdates()を実行すると、
アプリの更新処理をストップ出来ないらしく、
実行前にユーザーに確認ダイアログを出しておいた方が良さそうです。


あるいは、今動作しているアプリのバージョンが最新かどうか確認しておいてから、
アプリを更新しても良いか聞いておきましょう。


autoUpdaterが間違ったファイルをダウンロードする(1敗)

ElectronのautoUpdaterは、おおよそ次の順番で処理しています。
Mac環境)

1. 実行中のアプリの署名の確認
2. 実行中のアプリが最新版かどうかチェック
3. 最新アプリのダウンロード
4. zipファイルの解凍
5. 最新アプリの署名の確認
6. アプリの入れ替え


update.electronjs.orgはこんな実装(↓)になっているので、
データファイルなどの名前に"mac"の文字が入っていると、
更新アプリでなく、間違ったファイルをダウンロードすることがありますね。
(1敗)

# https://github.com/electron/update.electronjs.org/blob/ed32d4fedca459fb734a5d898d8573f57b605bd4/index.js#L236

const assetPlatform = fileName => {
  if (/.*(mac|darwin|osx).*\.zip/i.test(fileName)) return 'darwin-x64'
  if (/win32-ia32/.test(fileName)) return 'win32-ia32'
  if (/win32-x64|(\.exe$)/.test(fileName)) return 'win32-x64'
  return false
}


ダウンロードされたzipファイルは、 次のような場所に保存された後、解凍され、
その後、アプリの署名の検証が行われます。

/Users/user/Library/Caches/xx.xx.bundleid.ShipIt/update.QCBggxR


なので、誤ったファイルをダウンロードしていて、 署名エラーが発生している時は、
このディレクトリを監視していれば、問題を追えます。
(エラーになった時、すぐ消されちゃうみたいですけど。)


if (/win32-x64|(\.exe$)/.test(fileName)) return 'win32-x64'

試していませんけれど、このコードを見る限り、
Windows環境はexe形式もいけるみたいですね。
自己解凍形式がサポートされているのかな?


終わり

MacのElectronアプリだったらさ、
自動更新機能を組み込まなくても、Mac App Storeに出せば良いじゃない?


そんなふうに考えていた時期が俺にもありました


どうも駄目なレビュアーに当たった気がします。
審査が通らない。
話が通じない。


そろそろ審査アプリのバンドルIDを変更して、
レビューする人を切り替えようかと考え始めています。
(あくまでも自分は悪くないと決めつけるスタイル)