Electron
ElectronDay 17

Electron アプリを Mac App Store に登録する手順

More than 3 years have passed since last update.

Electron アプリの Mac App Store 事例

tweetman.png
最強Twitterクライアント「Tweetman」をリリースしました - 9mのブログ
Tweetman for Twitter https://tweetman.kksg.net/
Mac App Store (以下、MAS)で Electron のアプリをリリースしたところ、なんと有料アプリランキング1位に食い込む事ができました。公式の方法でMASに登録できるようになったのはここ2ヶ月の話なので、まだまだ事例は少ないという状態ですが、Electron で開発したアプリをMASで配布というのは今後有望な方法なのではないでしょうか。知っている限りだと Wire も Electron App on the MAS のようです。こちらの手順でリリース & アップデート(審査中)までできたので、どんどん MAS にアプリ上げていきましょう。

必要な要件

有用なドキュメント

Electron Mac App Store Submission Guide
Mac App Store (MAS) Submission Guideline · nwjs/nw.js Wiki
Enabling App Sandbox

Electron 公式の Submission Guideline はコードサイニングについてしか書いていないので、nw.js の wiki を参考にしてアプリを登録していきます。この2つのドキュメントを読むだけでもゴールには到達できますが、つまずきポイントもなかなかあるので手順を解説していきます。

Electron アプリを Mac App Store に登録する手順

  1. 証明書を作成
  2. iTunes Connect でアプリを作成
  3. アプリを asar にアーカイブ
  4. MASディストリビューションのElectronリリースに app.asar をコピー
  5. アイコンをパッケージに含める
  6. Info.plist を編集
  7. Electron Frameworks に含まれる子アプリを編集
  8. 1で作成した証明書でコードサイニング
  9. Application Loader でパッケージをアップロード
  10. レビューに提出

そこそこ手順が必要です。順を追って注意点など解説していきます。

証明書を作成

Code signing is a security technology, used in OS X, that allows you to certify that an app was created by you. Once an app is signed, the system can detect any change to the app—whether the change is introduced accidentally or by malicious code.

MASでアプリを配布するには、配布者が作成した証明書でアプリをサイニングする必要があります。これをすることによって、MASからダウンロードされたアプリが第三者によって変更されていないかOSが検知することができます。

証明書を要求

キーチェーンアクセス → 証明書アシスタント → 認証局に証明書を要求
ここで作成された .csr ファイルを使って、Apple Member Center から証明書をダウンロードします。
スクリーンショット付きの手順は MAS: Requesting certificates の通りにやればOKです。ほぼ同じ手順を2回繰り返して Mac App Distribution certificate と Mac Installer Distribution certificate の2つの証明書を取得する必要があります。

アプリを asar にアーカイブ

MASで配布しない通常のアプリを作成する場合は electron-packager で一発ですが、現時点では mas ディストリビューションに対応していません。Electronのリリースページから *-mas-x64.zip と書いてあるディストリビューションをダウンロードして、中身のアプリケーションを差し替えてパッケージにするという方法で作成します。とりあえず動かしてみる場合はつくったアプリを app というディレクトリ名にして、Electron.app/Contents/Resources/app に配置するだけで動く .app ファイルになります。ただし、これではファイルサイズがムダに大きくなってしまうので、asar という形式に固める必要があります。

asar コマンドを使う場合

ベーシックに asar コマンドを使って固める方法です。
Application Packaging

グローバルに asar コマンドをインストール

$ npm install -g asar

src ディレクトリ以外を app.asar に固める例

$ asar pack your-app app.asar --unpack src
electron-packeger を使う場合

darwin パッケージを作成して、そこから app.asar を抜き取る方法です。electron-packeger を使うと prune オプション(devDependencies の node_modules をパッケージに含めない)が使えて便利なので、自分はこっちを使ってます。

グローバルに electron-packager をインストール

$ npm install electron-packager -g

YourApp.app として固める

electron-packager . YourApp --platform=darwin --out=./ --arch=x64 --version=0.36.0 --ignore='src|ignore-*' --prune=true --asar=true

app.asar を取り出す

cp YourApp.app/Contents/Resources/app.asar ./

MASディストリビューションのElectronリリースに app.asar をコピー

前で説明したとおり、MAS ディストリビューションのアプリを作成するには、Github のリリースページから MAS ディストリビューションのElectron アプリをダウンロードして、app.asar コピーします。ちなみに MAS ディストリビューション以外を使って MAS に登録しようとしても、auto updater が入ってるとかでバリデーションに引っかかり、iTunes Connect にアップロードすらできません。

Githubのリリースページからなるべく最新のMASディストリビューションをダウンロード

wget https://github.com/atom/electron/releases/download/v0.36.0/electron-v0.36.0-mas-x64.zip

app.asar を入れる

unzip electron-v0.36.0-mas-x64.zip
cp app.asar Electron.app/Contents/Resources/

これで、Electron.app を起動すると自分のアプリが動きます。

アイコンをパッケージに含める

このままではアイコンがデフォルトのままなので、アイコンを差し替えます。アイコンは .icns という拡張子のファイルに複数の画像をまとめます。最大で 1024px x 1024px のかなり大きい画像を用意する必要があります。コマンドでも .icons ファイルを作ることはできますが、たくさんリサイズしなくてはならないため、フリーソフトを使うのが楽で良かったです。自分は Icon Tool for Developersというソフトを使いました。このソフトで作成した .icns ファイルを app.icns という名前にリネームして、 Electron.app/Contents/Resources/ に配置します。

Info.plist を編集

アプリのメタデータは Electron.app/Contents/Info.plist を編集することで変更できます。見てみるとなんとなく書き換えが必要そうな項目がわかると思いますが、以下の項目を変えるとよいでしょう。

  • CFBundleDisplayName
  • CFBundleName
  • CFBundleIdentifier
  • CFBundleShortVersionString
  • CFBundleVersion

CFBundleURLTypes という項目を追加すると、独自の URL Scheme でアプリを起動できるようになります。Tweetman ではこの URL Scheme を使い、 tweetman://post?text=foo というスキームでアプリの外からツイートボックスにテキストを挿入できるようになっています。

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLName</key>
        <string>YourApp URL</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>yourapp</string>
        </array>
    </dict>
</array>

(本当は CFBundleExecutable も Electron から自分の名前に変えるもんだと思うんだけど、この値と Executable ファイルの名前変えたらアプリが起動できなかった。Electron という名前でハードコーディングされてるっぽい(?))

Electron Frameworks に含まれる子アプリを編集

アプリを起動すると、Electron Helper というプロセスも同時に立ち上がります。このままではアクティビティモニターにもそのまま Electron Helper という名前が表示されてしまうので、Electron Frameworks に含まれる子アプリの名前を編集します。

Electron Helper (EH|NP) を YourApp Helper (EH|NP) にリネーム

#!/bin/bash
FRAMEWORKS=YourApp.app/Contents/Frameworks
APP_NAME=YourApp

mv "$FRAMEWORKS/Electron Helper.app/Contents/MacOS/Electron Helper" "$FRAMEWORKS/Electron Helper.app/Contents/MacOS/$APP_NAME Helper"
mv "$FRAMEWORKS/Electron Helper EH.app/Contents/MacOS/Electron Helper EH" "$FRAMEWORKS/Electron Helper EH.app/Contents/MacOS/$APP_NAME Helper EH"
mv "$FRAMEWORKS/Electron Helper NP.app/Contents/MacOS/Electron Helper NP" "$FRAMEWORKS/Electron Helper NP.app/Contents/MacOS/$APP_NAME Helper NP"

Electron Helper.app に含まれる Info.plist は「CFBundleIdentifier, CFBundleName」Electron Heler (EH|NP).app に含まれる Info.plist は 「CFBundleDisplayName, CFBundleExecutable, CFBundleName, CFBundleIdentifier」をそれぞれ編集します。CFBundleExecutable は上でリネームした YourApp Helper (EH|NP) の名前に合わせる必要があります。

コードサイニング

Mac App Store Submission Guideで説明されている箇所です。まず child.plist と parent.plist を作成します。

child.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>com.apple.security.app-sandbox</key>
    <true/>
    <key>com.apple.security.inherit</key>
    <true/>
  </dict>
</plist>
parent.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>com.apple.security.app-sandbox</key>
    <true/>
  </dict>
</plist>

ここで注意点ですが、この .plist ではサンドボックスをONにしているだけで、ネット通信やファイルを扱う権限がありません。インターネットをする場合は com.apple.security.network.client を、input type="file" を使うような場合はさらに com.apple.security.files.user-selected.read-only を追加する必要があります。その他の権限については Enabling App Sandbox から必要な権限を探して追加します。以下はhttpのアウトバウンドを許可する場合の例です。child.plistcom.apple.security.inheritparent.plist から権限を引き継いているので、このままで大丈夫です。

parent.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>com.apple.security.app-sandbox</key>
    <true/>
    <key>com.apple.security.network.client</key>
    <true/>
  </dict>
</plist>

最後に下のスクリプトでサイニングを実行します。APP_KEY と INSTALLER_KEY は 1 で作成した鍵を、キーチェーンに表示されている名前をそのままの通りに差し替えます。

sign.sh
#!/bin/bash

# Name of your app.
APP="YourApp"
# The path of you app to sign.
APP_PATH="/path/to/YouApp.app"
# The path to the location you want to put the signed package.
RESULT_PATH="~/Desktop/$APP.pkg"
# The name of certificates you requested.
APP_KEY="3rd Party Mac Developer Application: Company Name (APPIDENTITY)"
INSTALLER_KEY="3rd Party Mac Developer Installer: Company Name (APPIDENTITY)"

FRAMEWORKS_PATH="$APP_PATH/Contents/Frameworks"

codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Libraries/libnode.dylib"
codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Electron Framework"
codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/"
codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper.app/"
codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper EH.app/"
codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper NP.app/"
codesign  -fs "$APP_KEY" --entitlements parent.plist "$APP_PATH"
productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RESULT_PATH"
$ bash sign.sh

これが通ればパッケージ作成完了です。YourApp.pkg というファイルが作成されます。サイニングした YourApp.app ファイルはサンドボックス版になっているので、まずこれが使えるか確認しましょう。parent.plist で設定する権限に漏れがあると固まったりする場合があります。

これらの作業をアップデートの度にやってくのは大変なので、自分は以下のように一連の作業をまとめた雑なスクリプトを作成してやってます。

#!/bin/bash
ELECTRON_ZIP=electron-v0.36.0-mas-x64.zip
rm -rf solution
mkdir solution
cp $ELECTRON_ZIP solution/
cd solution
unzip $ELECTRON_ZIP
cd ../

APP_NAME=YourApp
BASE_DIR=$(pwd)
SOURCE_APP=$BASE_DIR/YourApp-darwin-x64/$APP_NAME.app
TARGET_DIR=$BASE_DIR/solution
TARGET_APP=$TARGET_DIR/Electron.app
FRAMEWORKS=$TARGET_APP/Contents/Frameworks
INFO_PLIST=$BASE_DIR/plists/Info.plist
HELPER_PLIST=$BASE_DIR/plists/helper-Info.plist
HELPER_EH_PLIST=$BASE_DIR/plists/helper-eh-Info.plist
HELPER_NP_PLIST=$BASE_DIR/plists/helper-np-Info.plist
SOLUTION_PATH=$BASE_DIR/solutions

cp $SOURCE_APP/Contents/Resources/app.asar $TARGET_APP/Contents/Resources/
cp $SOURCE_APP/Contents/Resources/atom.icns $TARGET_APP/Contents/Resources/
cp $INFO_PLIST $TARGET_APP/Contents/Info.plist
cp $HELPER_PLIST "$FRAMEWORKS/Electron Helper.app/Contents/Info.plist"
cp $HELPER_EH_PLIST "$FRAMEWORKS/Electron Helper EH.app/Contents/Info.plist"
cp $HELPER_NP_PLIST "$FRAMEWORKS/Electron Helper NP.app/Contents/Info.plist"
#mv $TARGET_APP/Contents/MacOS/Electron $TARGET_APP/Contents/MacOS/$APP_NAME
mv "$FRAMEWORKS/Electron Helper.app/Contents/MacOS/Electron Helper" "$FRAMEWORKS/Electron Helper.app/Contents/MacOS/$APP_NAME Helper"
mv "$FRAMEWORKS/Electron Helper EH.app/Contents/MacOS/Electron Helper EH" "$FRAMEWORKS/Electron Helper EH.app/Contents/MacOS/$APP_NAME Helper EH"
mv "$FRAMEWORKS/Electron Helper NP.app/Contents/MacOS/Electron Helper NP" "$FRAMEWORKS/Electron Helper NP.app/Contents/MacOS/$APP_NAME Helper NP"
mv "$FRAMEWORKS/Electron Helper.app" "$FRAMEWORKS/$APP_NAME Helper.app"
mv "$FRAMEWORKS/Electron Helper EH.app" "$FRAMEWORKS/$APP_NAME Helper EH.app"
mv "$FRAMEWORKS/Electron Helper NP.app" "$FRAMEWORKS/$APP_NAME Helper NP.app"
mv $TARGET_APP $TARGET_DIR/$APP_NAME.app

APP_NAME="YourApp"
APP_PATH="/path/to/YouApp.app"
RESULT_PATH="~/Desktop/$APP.pkg"
APP_KEY="3rd Party Mac Developer Application: Company Name (APPIDENTITY)"
INSTALLER_KEY="3rd Party Mac Developer Installer: Company Name (APPIDENTITY)"

FRAMEWORKS_PATH="$APP_PATH/Contents/Frameworks"

codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Libraries/libnode.dylib"
codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Electron Framework"
codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/"
codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Tweetman Helper.app/"
codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Tweetman Helper EH.app/"
codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Tweetman Helper NP.app/"
codesign  -fs "$APP_KEY" --entitlements parent.plist "$APP_PATH"
productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RESULT_PATH"

Application Loader でパッケージをアップロード

Electronのように、開発環境が Xcode でない場合は Application Loader というソフトを使ってパッケージをアップロードします。Application Loader は iTunes Connect のリソースとヘルプの一番下、もしくは iTunes Connect の「マイ App」のアプリケーション情報登録画面にダウンロードリンクがあります。
直接リンクはこれ https://itunesconnect.apple.com/apploader/ApplicationLoader_3.1.dmg

applicationloader.png

「App をデリバリ」を選択して、先ほど作成した .pkg ファイルを選択するといろいろ検証がはじまって、上手くいくとアップロードが完了します。iTunes Connect の「マイ App」でアップロードしたビルドを選択できるようになります。

レビューに提出

「マイ App」で画面にしたがってアプリケーションの情報を記入し、審査に提出します。自分の場合は7日で審査が完了しました。レビューが通ったら #macreviewtime タグを付けて Average App Store Review Times にコントリビュートしましょう。

アップデートについて

アップデートは登録と同じ手順で Application Loader を使ってまたパッケージをアップロードするだけです。「マイ App」でアップデートの内容を記述して、また審査に出します。このサイクルが遅くて手間なのがMASを使うデメリットなわけですが、アプリケーションを有償で配布するのであれば、これほど整ったプラットフォームは他にないですし、"Appleの審査を通った"お墨付きアプリケーションとして配布できます。みなさんもどんどんアプリをマネーに変えてやっていきましょう。

Electron Advent Calendar 2015 明日は @khrtz さんです。なんかお願いします!