beforeinstallpromptを使って任意のタイミングでPWAアプリをホーム画面へ追加する

この記事は[PWA Advent Calendar 2018](https://qiita.com/advent-calendar/2018/pwa)の10日目の記事です。
6日も遅れてしまいました…すみません。

# 任意のタイミングでホーム画面への追加をしたい

PWAといえばホーム画面に追加で端末にインストールすることが可能ですが、Chrome 68以前は

- メニューを開いて自分でホーム画面に追加する
- 下部に表示されるA2HS bannerバナーから追加する

しかできませんでした。
とはいえ、たまたま訪問したサイトなどで急にバナーが出てきても、中々インストールしようって気持ちにはならないと思います。

できれば、ユーザーにサイト内のボタンなどを押してもらったタイミングなど、任意のタイミングでインストールできるようにしたいところです。

# Chrome68での仕様変更

Chromeのバージョン68で仕様変更があり、

- A2HS bannerバナーがミニインフォバーというコンパクトな表示に変更
- `beforeinstallprompt` を使って任意のタイミングでホーム画面に追加のダイアログを表示可能に

になりました。ミニインフォバーになってサイトの閲覧を邪魔しないような大きさになったのも嬉しいですが、任意のタイミングでダイアログを表示させることが出来るのが良いですね。

https://developers.google.com/web/updates/2018/06/a2hs-updates

# 実際に使ってみる

ホーム画面に追加の条件を満たしていればボタンが活性化して、ボタンを押したらダイアログが表示されるようなものを作ってみました。

![sample.jpg](https://qiita-image-store.s3.amazonaws.com/0/3531/849f247f-31eb-8fc0-98a7-7b7e0b994825.jpeg)

まず、サイトがホーム画面に追加の条件を満たしていると、ミニインフォバーが表示され、 `beforeinstallprompt` というイベントが発生してJavaScriptで制御できるようになります。
ミニインフォバー消してからキャプチャ取ってしまったのでキャプチャには写ってません。。

この時に、発生したイベントを保存しておくことで、任意のタイミングで呼び出せるようになります。

``` JavaScript
let installPromptEvent;

window.addEventListener('beforeinstallprompt', (event) => {
  // Chrome67以前で自動的にプロンプトを表示しないようにする?
  event.preventDefault();

  // イベントを変数に保存する
  installPromptEvent = event;

  // #btnを活性に
  document.querySelector('#btn').disabled = false;
});
```

コードはGoogleの [Changes to Add to Home Screen Behavior](https://developers.google.com/web/updates/2018/06/a2hs-updates) のページを参考にしています。

ちなみにこのイベントは、アプリがすでにインストールされている場合は発生しないようでした。
また、ユーザーによってミニインフォバナーが非表示にされると3ヶ月間は再表示されないらしいですが、非表示にされた場合でも、イベントは発生するようです。

この保存したイベントをユーザーのアクションに紐付ければ、任意のタイミングでダイアログを表示させることができます。

``` JavaScript
// #btnをクリックした時にプロンプトを表示させる
document.querySelector('#btn').addEventListener('click', () => {
  // #btnを非活性に
  document.querySelector('#btn').disabled = true;

  // 　ホーム画面に追加のダイアログを表示させる
  installPromptEvent.prompt();

  // ダイアログの結果をプロミスで受け取る
  installPromptEvent.userChoice.then((choice) => {
    if (choice.outcome === 'accepted') {
      console.log('User accepted the A2HS prompt');
    } else {
      console.log('User dismissed the A2HS prompt');
    }
    // Update the install UI to notify the user app can be installed
    installPromptEvent = null;
  });
});
```

ダイアログで選択した結果をプロミスで受け取ることができます。
最初に結果に関係なくボタンを非活性にしてしまっているのでキャンセルにされた時に、再度ボタン押せなくなって困るんじゃないかと思っていましたが、キャンセルになった場合は、もう一度 `beforeinstallprompt` イベントが発火して最初の処理が行われるので問題ないようでした。

ちなみに[Googleが公表しているホーム画面への追加の条件](https://developers.google.com/web/fundamentals/app-install-banners/#criteria)ですが、

- 次の情報が記述されたマニフェストファイルが存在する。
    - short_name（ホーム画面で使用）
    - name（バナーで使用）
    - 192x192 の png アイコン（アイコンの宣言には MIME タイプ image/png の指定が必要）
    - 読み込み先の start_url
- サイトに Service Worker が登録されている。
- HTTPS 経由で配信されている（Service Worker を使用するための要件）。
- 2回以上のアクセスがあり、そのアクセスに5分以上の間隔がある。

最後の `2回以上のアクセスがあり、そのアクセスに5分以上の間隔がある` だけは、最近は仕様が変わっているような気がします。条件を満たしていれば、大抵は初回のアクセス時に表示されている印象です。

# ローカル環境で動作確認する

<img width="793" alt="Sample App 2018-12-16 07-58-55.png" src="https://qiita-image-store.s3.amazonaws.com/0/3531/35228b1f-e65d-85b6-805f-5237c5bb5f3d.png">

ChromeのデベロッパーツールのApplicationタブのManifestから、 `Add to homescreen` をクリックすると `beforeinstallprompt` が発生した状態を再現できます。

# 将来的にはミニインフォバーも出なくなる？

<img width="496" alt="a2hs-omnibox-cropped.png (500×109) 2018-12-16 08-27-32.png" src="https://qiita-image-store.s3.amazonaws.com/0/3531/2bbcc1aa-3143-4ace-26d5-a2b7d9f02342.png">

将来的には、オムニボックス（Chromeのアドレスバー）にインストール用のアイコンを表示させる予定らしいです。

バナーは正直どうかなと思っていたので、個人的には嬉しいです。
任意のタイミングでこうやってダイアログを出せるのであれば、インストールしてもらうための見せ方の工夫が色々とできそうですね！