GitHub用のIssue Reader「Jasper」の開発を振り返ってみる

この記事はElectron Advent Calendar 2016の11日目の記事です。

この記事では僕がプライベートで開発しているJasperというGitHub用のIssue Readerについて書きました。 JasperはElectronで作られており、どういうものかを一行で説明すると「任意の条件にマッチしたIssueが流れてくるIssue Reader」です。

f:id:h13i32maru:20161211175016p:plain:w800

今回はそのJasperの開発初期、リリース期、運用期での出来事を時系列でまとめました。 なので、技術的な話はあまり多くありません。 どちらかというと僕自身の備忘録としてJasperの開発史をまとめたものになっており、すごく長い記事になっています。 ご注意ください。

目次

  • 開発初期
    • コンセプト
    • 初期実装
    • フィードバックループ
    • 技術的に面白かったこと
  • リリース期
    • Mac App Storeへの登録
    • アイコン作成
    • Electronのサイトに掲載
  • 運用期
    • Mac App Store撤退
    • ライセンス販売への変更
    • Gumroadのダウン
    • NodeFest2016
    • 社内で使えなくなった
    • エゴサーチ
    • ベータバージョン
    • GitHubの新機能

開発初期

2016年2月から2016年5月ごろの開発時の話です。

コンセプト

会社ではGitHub Enterpriseを使って仕事をしているので、非常に多くの人が毎日issueを更新したり、pull requestを投げたりします。 なので、IssueやPull Requestの通知メールやサイト上での通知が大量に流れてきます。 それらを処理するのがすごくすごく大変でこれをなんとかしたいなとずっと思っていました。

そこで、GitHubのIssueを効率的に処理できるデスクトップアプリケーションを作ろうと思いコンセプトを考え始めました。 最初はGitHubの通知APIやIssue API、Pull Request APIを組み合わせていい感じに通知を受け取るというのを考えていました。 ですが、これらのAPIではちょっと汎用性が足らないなと気づきました。

というのも、GitHubのIssueやPull Requestを中心に仕事をしていると、各自それぞれが自分なりの仕事のスタイルやユースケースを持っています。 例えば、自分がメンションされたもの、チームメンションされたもの、特定のリポジトリ、特定のOrganization、特定の単語が入っているものなどなど。 各自はこれらのユースケースを組み合わせて仕事をしているので、アプリでそれらを解決できる必要があります。 しかもわかりやすい操作(=わかりやすい概念)で使える必要もあります。

ここで、ちょっと煮詰まってしまったのですが、あるときGitHubのIssue検索APIが超強力だということに気づきました。 先程あげた様々なユースケースに対応できるだけの十分な検索機能を持っています。 そこで、ユーザが「自分がみたいIssueの条件を自由に設定する」と、内部的には「Issue検索APIをポーリング」するというアイデアを思いつきました。 そして、この概念を「自分が興味のあるIssueが流れてくる」と捉え、「Stream」と呼ぶことにしました。 例えば以下のようなStreamを作ることになります。

  • repo:electron/electron label:beginner
    • electronのリポジトリでbeginnerラベルが付いているissue
  • is:pr author:alice author:bob
    • aliceもしくはbobが出したpull request

その後ElectronやIssue検索APIの技術的な検証をし、このコンセプトを実現できることを確認しました。 コンセプトを考え始めてからここまでたしか1ヶ月くらいだったと思います。

初期実装

初期の実装は土日の二日間でいっきに作り上げました。 コード行数にして700行程度です。 そして翌日の月曜から会社で使い始めました。 これだけ短時間で作れたのも僕がNode.jsやブラウザ上でのJSの使い方、HTMLやCSSになれており、それをそのままElectron上で活かすことができたからです。

といってもまだまだ荒削りで、アクセストークンやStreamの設定画面はなくて直接アプリに組み込む必要がありましたし、デザインも全然されていない本当にミニマム実装というものでした。 ですが、一刻も早く実践で使うことによって改善点を得たいというのがあり、結果としてこれは大成功でした。 そして、この初期実装で作ったコードは今もJasperのコアなコードとして使われています。

フィードバックループ

初期実装から少しして、知り合いのエンジニアやデザイナーにテストに参加してもらうようになりました。 まだこの頃は設定画面もなくて、僕が個別にアプリに設定を書き込んで配布したり、生の設定ファイルを直接編集してもらうなどで対応していました。 その後、ユーザ自身で設定できるようにUIを作り込んだり、フィードバックをもらい改善や機能追加を行っていきました。 このフィードバックループを20回ほど、期間にして2ヶ月くらい行っていました。

技術的に面白かったこと

フィードバックループを回してる中で面白かった技術的な話を少し紹介したいと思います。

SQLite

JasperではデータのストレージにSQLiteを使っています。これはNode.jsのネイティブモジュールとして実装されており、当初はnpm installしたものをElectronでもそのまま使えていました。 ですが、ある時から急に専用にビルドし直さないといけなくなり、少し苦労した覚えがあります。 何故かと言うと当時はelectron-rebuildが僕の手元ではうまく動かずモジュールをビルドすることができませんでした。 また、僕自身ネイティブモジュールの経験もなかったため、対応方法もよくわかりませんでした。 色々探し回ったり試行錯誤したあげく、node-gypで自分でビルドしなおすことで解決できました。

# for Mac
node-gyp configure  --module_name=node_sqlite3 --module_path=../lib/binding/electron-v1.4-darwin-x64
node-gyp rebuild  --target=1.4.2 --arch=x64 --target_platform=darwin --dist-url=https://atom.io/download/electron --module_name=node_sqlite3 --module_path=../lib/binding/electron-v1.4-darwin-x64

# for Windows
node-gyp configure  --module_name=node_sqlite3 --module_path=../lib/binding/electron-v1.4-win32-x64
node-gyp rebuild  --msvs_version=2015 --target=1.4.2 --arch=x64 --target_platform=win32 --dist-url=https://atom.io/download/electron --module_name=node_sqlite3 --module_path=../lib/binding/electron-v1.4-win32-x64

# for Linux
node-gyp configure --module_name=node_sqlite3 --module_path=../lib/binding/electron-v1.4-linux-x64
node-gyp rebuild --target=1.4.2 --arch=x64 --target_platform=linux --dist-url=https://atom.io/download/electron --module_name=node_sqlite3 --module_path=../lib/binding/electron-v1.4-linux-x64


async/await

JasperではSQLiteの読み書き、GitHubのAPI呼び出し、ポーリング、などで非常にたくさんの非同期処理を実装する必要がありました。 ここですごく役に立ったのがES2017に入る予定のasync/awaitという言語組み込みの非同期処理機構です。 一言で言うと非同期処理を同期的に書くことができるすごく便利なものです。 Jasperではこのasync/awaitをがっつり使っており、アプリ全体で全350メソッドのうち120メソッドでasync/awaitが使われています。

たとえば一定間隔で処理をするポーリングのようなものはasync/awaitを使ってこのように書くことができます。

async function polling() {
  while(1) {
    const res = await fetch(url);
    doSomething(res);
    await sleep(10000);
    if (someCondition) break;
  }

  doFinish();
}


Photon

僕はデザイン(ビジュアル)の力は殆ど無いので、いい感じの見た目をお手軽に作るためにPhotonというElectronようのUIツールキットを使っています。 Photonを使えばMacのようなUIを簡単に作ることができます。 ターゲットがElectronだけの単一プラットフォームなので、Flexboxでレイアウトを組まれているのもモダンで良いです。 あとは組み込みのアイコンフォントも数は多くないのですが、使い勝手が良いものが揃っており便利です。

Photon導入前と後

f:id:h13i32maru:20161211180712p:plain:left:w400 f:id:h13i32maru:20161211180814p:plain:w400


Windows対応

当初Windows対応はあまり考えていなかったのですが、テストに参加してくれた人が自力でWindows対応をしてくれました。 というのもElectronはもともとマルチプラットフォーム対応をしているので、Jasperから必要な部分を取り出し、Windows用のElectronバイナリに載せ替えれば動くからです。 ただし、ネイティブモジュールを使っているところは各プラットフォームごとにビルドする必要がありそこがやや面倒ではあります。 この方はその部分も自力で解決してWindows環境でテストしてくださいました。

こういう経緯もあり、これは僕自身でちゃんと対応しておいたほうが良いなと思い直し、Windows版も作ることになりました。 そして後にLinux版も作りました。 といっても、コードはほぼ変更する必要がなくて、ファイルパスの扱いとネイティブモジュールのビルドだけ手を加えただけです。 Electronがマルチプラットフォーム対応しているの本当に便利で最高です。

リリース期

2016年5月から2016年6月のリリース作業中の話です。

Mac App Storeへの登録

JasperはGumroadとMac App Store(以下MASと言います)の両方にリリースすることにしていました。 Gumroadは審査があるわけではないので簡単に登録できるのですが、MASの方はAppleによる審査があります。 その審査にJasperが通らずリリースすることができない状態が1ヶ月近く続きました。 リジェクトされたのはJasperの実装に問題があったからではなく、Electron自体にいくつかの問題があったためです。

MASバイナリのクラッシュ

Electron v0.35からv0.36でChromeのバージョンがあがり、MAS用のサンドボックスビルドでElectronがクラッシュするようになってしまいました。 この問題により、審査どころかサブミットさえできませんでした。 僕がこの問題に遭遇してから1, 2週間ほどしてcom.apple.security.temporary-exception.sbplという権限をアプリに付与することでクラッシュする問題は解決されました。


Temporary Exception Entitlements

これでようやく審査に出せると思ったのですが、今度は審査でリジェクトされてしまいました。 理由はクラッシュする問題で付与したcom.apple.security.temporary-exception.sbplの設定値が適切ではないということでした。 古いバージョンのElectronではこの権限が不要だったので使用しているElectronを古いバージョンにダウングレードしたりもしたのですが、動作が不安定になるのであきらめました。 リジェクトされてから3週間後、Electron側での対応がすすみcom.apple.security.temporary-exception.sbplがそもそも不要になり、この問題は解決されました。


Non-Public API

ようやくこれでMASでリリースできると思ったのですが、今度はElectronのChromeがMacのnon-publicを使っているということでリジェクトされてしまいました。 こちらは問題が発覚してからすぐにElectron側で対応してもらえました。

このようにJasper自体の実装ではなくElectron側の問題によるMASにリリースできずにつらい思いをしていました。 最後の方はちょっと心がおれかけて、MASでのリリースは諦めようかと思っていました。

ちなみに、MASでリリースする場合はサンドボックス環境でアプリを動かすために、アプリが必要とする権限を明示的に設定する必要があります。 例えばネットワーク使用権限、アドレス帳へのアクセス権限などです。詳しくは以下のURLを参照してください。

アイコン作成

アプリのアイコンを同僚のtransit_kixさんにお願いして作ってもらいました。 最初にアイコンのラフアイデアを手書きで10個くらい作っていただきました。 そこから気に入ったものを選ばせていただいて、ブラッシュアップしていくという流れになりました。 このラフアイデアを見せてもらったときに驚いたのが、transit_kixさんの思うJasperのイメージと僕の思うイメージがすごく近かったことです。 transit_kixさんにもJasperのプロトタイプを業務で使ってもらっていたというのもあると思うのですが、すごい汲み取り力だなーと感動しました。

それとブラッシュアップのときにもすごいと思ったことがあります。 それは「僕の要望を最大限くみとりつつ、バリエーションをいくつも提案してくれたこと」「transit_kixさん自身の意見の押しつけみたいなのは一切なかったこと」です。 特に後者は、絶対に本職のtransit_kixさんのほうがセンスは良いので、多分言いたいことをぐっと我慢してくれたんだろうなーと思います。 勝手な想像ですが、僕がアイコンを気に入るというのを重要事項におきつつ、デザイン的にも良くなるようにというバランスを取りつつ作ってくれたのかなと思います。 さすがプロという感じで尊敬しています。

Electronのサイトに掲載

ElectronのサイトにはElectronで作ったアプリを紹介するページがあります。

このページに自分のアプリを載せるにはpull requestを送ると中の人がレビューをしてくれて、問題なければマージして載せてくれます。 僕の場合はちょっとアプリの説明に問題があったので、指摘が入りましたがそれ以外は特に問題なくマージされました。 (GitHub Issue ReaderだとGitHubオフィシャルぽいので、Issue Reader for GitHubとかに変えてねという内容)

Electronでアプリを作った際はここに掲載するのを絶対におすすめします。 ここから結構な数がJasperのサイトへ流入しているので、アプリのよい宣伝になると思います。

運用期期

2016年6月からしばらくJasperの開発は休んでいました。 私生活が少し忙しくなったり、ESDocの開発をしていたためです。 なので、ここでは開発を再開した2016年10月から現在に至る話です。

Mac App Store撤退

Jasperは当初GumroadとMac App Store(以下MASと言います)で販売をしていました。 しかし、2016年10月末ごろにMASから撤退しました。 これは幾つか理由があります。

  1. 各国の税金手続きが僕にはさっぱりわからなかったこと
    • カナダとオーストラリアだったかの税金手続きを当該国のサイトから行う必要がでてきて、全然わからずに諦めました
  2. GumroadとMASで販売数はほとんど変わらなかったこと
    • サイト上ではGumroadを推していたのが要因かもしれません
    • それと、手数料やTierの関係上、MASのほうが高かったのも要因かもしれません
  3. Windows版やLinux版の販売を考えるとGumroadで統一するのが楽そうだったこと
  4. また審査でリジェクトされるかもしれないという不安があったこと
    • リリース期にもあったような僕ではコントロールできない理由によりリジェクトされる恐れが今後もあったためです
    • AtomがMASで配布されていないため、Electron自体に問題があり、リジェクトされた場合の対応が遅れがちになってしまいます
    • ただし、MASでElectronのデモアプリが配布されるようになったようなので今後は改善されていくかもしれません https://itunes.apple.com/jp/app/electron-apis/id1119345146?mt=12
  5. MASではクーポンコードやベータアプリの配布ができないこと
    • クーポンコードはイベントなどで配れるとマーケティング上すごく便利です
    • ベータアプリは開発段階の機能についてユーザからフィードバックをもらえるため非常に有益です

このようにMASには様々な制限や不便な点があるので、MASでアプリを配布する場合は十分に検討したほうがよいと思います。 特に有料アプリ、Electron(非ネイティブ)アプリ、マルチプラットフォームアプリを開発している場合には。

ライセンス販売への変更

MASから移行と同時に、それまでは「有料バイナリアプリを販売する」という形をとっていたのを「ライセンスキーを販売する」という形に変更しました。 具体的にはGumroadのライセンス機能を使って、ライセンスキーを販売し、アプリに登録してもらうというものです。

なぜライセンスキーの販売にしたかというと、有料バイナリを販売している状態では自動アップデート機能を提供することが難しいからです。 というのも、自動アップデート機能はネット上から最新のアプリをダウンロードしてくる形になるので、有料バイナリをネット上のパブリックなスペースに置く必要があるからです。 有料バイナリ自体に認証機構を持たせて有料バイナリからしかダウンロードできないようにするという方法もあります。 しかし運用を極力楽にしたいのでできればやりたくありません。

さらに、ライセンス販売にするとフリートライアル版と有料版のバイナリを同じにできるので運用が楽になるのと、フリートライアルから有料版へのアプリの更新をしなくてよくなります。

Gumroadのライセンス機能はシンプルで簡単にアプリに組み込むことができるので、有料アプリを開発する場合に検討してみると良いと思います。 ただし、その手軽さゆえにハックされやすい形になっています。 まあElectron自体が簡単にアプリの中身を見れるものなので、そういうことを気にしなければならない場合、Electron自体が選択肢から外れるとは思いますが。

1つ注意点として、有料バイナリ販売からライセンスキー販売にする場合、ユーザに移行作業をしてもらう必要があります。 特にMAS版のアプリを使っていたユーザには大きな負担をかけることになり、非常に申し訳なかったなと思っています。 今後有料アプリを販売される方は、どのような形で販売するかしっかり事前に検討したほうが良いと思います。 ちなみにJasperでの移行手順はここに書いてある方法をとりました。

Gumroadのダウン

MASを撤退し、Gumroadに統一して平和な日々を送っていました。 NodeFest2016での発表を控え資料の作成などをしていたときです、急にGumroadがサイトごと落ちてしまいました。 しかもGumroadのライセンスAPIも落ちてしまい、Jasperのライセンス認証が失敗するようになりました。 GumroadのTwitterアカウントでも特に反応がなくて、「このままGumroad無くなるとかないよな!?」とビクビクしていました。 その後、2~3日Gumroadが不安定な状態が続きましたが、なんとか復旧しました。 この期間は非常に辛く不安だったのを覚えています。

NodeFest2016

2016年11月13日に行われたNodeFest2016(Node学園祭2016)でJasperの話をLTでしてきました。 それとJasperのフライヤーを作り、100%オフのクーポンコードを載せて会場のノベルティグッズエリアに置かせていただきました。 またオーガナイザーのyosuke_furukawaさんにオープニング前に宣伝も少ししていただきました。

そのかいあって、多くの方にダウンロードして使っていただけました。ありがとうございました。 有料アプリはOSSと違ってやはり使ってもらうハードルが高いので、こうやって使ってもらえるだけで非常にありがたいものです。 そしてJasperを知ってくれた方々が、ご自身の会社内でJasperを広めて頂けるとさらに嬉しいので、みなさま何卒おねがいします 🙏

f:id:h13i32maru:20161211183510p:plain:w400

社内で使えなくなった

数々の困難(?)や発表を乗り越えようやく落ち着いた頃、会社に出社するとJasperが使えなくなっていました。 業務の傍ら原因を調べていたら、どうやらSSL(HTTPS)の証明書周りということがわかりました。 これは社内だけの話で、github.comや他社では問題ないようでした。 とはいえ、Jasperが無いと仕事の効率がものすごく悪くなります。 その時、横に座ってるhokacchaさんから「最新のElectronだとHTTPS通りますね」と教えてもらいました。 なので、その日のお昼休みに家に帰って、最新のElectronにアップデートしてアプリをビルドしてなんとか事なきをえました。

ElectronのSSL証明書周り、issueをみてるとちょいちょいトラブルがあるようなのでSSL周りで困っていることがあればissueを調べたり最新のElectronにアップデートすると良いかもしれません。

エゴサーチ

Jasperをリリースしてからというもの、承認欲求を満たすため、日々エゴサーチをしております。 GitHubの中の人が褒めてくれているツイートを見かけたときはすごく嬉しかったですし、Jasperについてのブログを書いてくださった方々もいて、非常にありがたいです。

ベータバージョン

Jasperは有料アプリなのでプライベートリポジトリで開発をしています。 なので、開発の進捗をユーザに公開することができずに少し困っていました。 そこで、リリース前の開発中の機能をのせたアプリをベータバージョンとして配布することにしました。 こうすることで開発の進捗もある程度早く公開でき、ユーザからのフィードバックを早くにもらうことができます。

GitHubの新機能

ここ最近GitHubに新しい機能がどんどん実装されています。 その中でも「新しいレビュースタイル(これ名前何ていうんだろう?)」と「GitHub Projects」はJasperとも連携させたいと思っています。 ですが、これらの機能はGitHubのIssue Search APIと統合されていないようなので、Jasperとの連携ができません (JasperはIssue Search APIを使ってる)。

GitHubにIssue Search APIと統合してもらえるようにリクエストしてみようかとは思っています。 けど、やっぱり他人の土俵でアプリを作るのは大変だなーという印象です。 しかもGitHub社内で新しいGitHubアプリ(フィードアプリ?)を作っているという噂も・・・。 まあその時はその時ですね。




Electronで作ったGitHub用のIssue Reader「Jasper」について、開発初期・リリース期・運用期にわけて紹介しました。 こうやって振り返ると沢山の人に協力してもらってるんだなーというのがわかりました。 みなさんありがとうございます。

というわけでElectron(に限らないですが)でアプリを作るのは面白くもあり、ときには大変でもあるのですが、色々と経験できるものがあるので是非おすすめです。