PWA（Progressive Web Apps／プログレッシブ ウェブ アプリ）でpush通知するための手順

最近巷で話題になっているPWAことProgressive Web Apps（プログレッシブ ウェブ アプリ）
今回は、PWAでpush通知をするための手順を記載します。

# まずはPWAとはなにか？
以下、Googleの公式サイトからの引用です。

>[引用] https://developers.google.com/web/fundamentals/getting-started/codelabs/your-first-pwapp/?hl=ja

>> プログレッシブ ウェブアプリはウェブとアプリの両方の利点を兼ね備えたアプリです。ブラウザのタブで表示してすぐに利用することができ、インストールの必要はありません。使い続けてユーザーとの関係性が構築されていくにつれ、より強力なアプリとなります。不安定なネットワークでも迅速に起動し、関連性の高いプッシュ通知を送信することができます。また、ホーム画面にアイコンを表示することができ、トップレベルの全画面表示で読み込むことができます。

要は、これまでにプロダクトされている各種技術を使って、Webサイト内でスマホアプリと同様の機能を実現する。その為の技術の総称をPWAと呼びますよ。という解釈で良いのではないかと思われます。
GoogleはこのPWAをAMPと併せて世界中に存在するWebサイトに普及するよう広めていきたいようで、2016年には「Progressive Web App Summit 2016」も開催しています。興味のある方は以下よりご覧ください。

[[参考] Progressive Web App Summit 2016](https://www.youtube.com/playlist?list=PLNYkxOF6rcIAWWNR_Q6eLPhsyx6VvYjVb)

# push通知実装までの流れ
1. SSLの用意
2. Firebase ConsoleよりAPIキーとプロジェクト番号を取得
3. ServiceWorkerの設定
    1. manifest.jsonの作成
    2. notification.jsonの作成
    3. sw.jsの作成
    4. endpointの取得
4. push通知テスト
    1. Chromeのdeveloperツールを使ってテスト
    2. curlコマンドを使ってpush通知

# 1. SSLの用意
push通知を行う為にはSSL接続が必須となります。
（いわゆるオレオレ証明書では実現できません）

SSL接続を行う為に必要な設定は以下にまとめてあります。ご確認下さい。
[[参考] 無料で使えるSSL - 「Let's Encrypt」をh2oに設定してhttp2.0に対応してみた](http://qiita.com/keki/items/dd5a447a88f7dd45be63)

# 2. Firebase ConsoleよりAPIキーとプロジェクト番号を取得
元々はGoogle Developers ConsoleからAPIキーを取得していましたが、試してみたところ、**Google Developers Consoleで取得したAPIキーではpush通知が出来ませんでした。**必ず以下のサイトより、Firebase ConsoleからAPIキーを取得してください。

> Firebase Console
[https://console.firebase.google.com/](https://console.firebase.google.com/)

Firebase ConsoleのUIは結構かわりますので細かな取得方法は参考程度にご確認下さい。
必要な情報は「ウェブ API キー」と「プロジェクト番号」の２つになります。

[取得方法]
　*ウェブ API キー*
　　「プロジェクト」 → 「プロジェクトの設定」 → 「ウェブ API キー」から取得できます。

　*プロジェクト番号*
　　「プロジェクト」 → 「権限」を選択し、「IAMと管理」画面を開き、
　　「設定」 → 「プロジェクト番号」から取得できます。

# 3. ServiceWorkerの設定
今回、push通知を実現する為に、Service Workerを使用します。Service Workerとは、ブラウザが Webページとは別にバックグラウンドで実行するスクリプトで、プッシュ通知やバックグラウンド同期と行った機能が提供されています。
Service Workerについて詳しく知りたい方は以下のサイトをご確認下さい。

> https://developers.google.com/web/fundamentals/getting-started/primers/service-workers?hl=ja

以下より各ファイルの作成手順を説明しますが、作成したファイルはサーバのDocumentRoot配下に設置してください。

## 1. manifest.jsonの作成
push通知を実現するためであれば、以下の1行が記入されていればOKです。
gcm_sender_idには、手順2.で取得した「プロジェクト番号」を記載してください。

```manifest.json
{
  "gcm_sender_id": "851639706594"
}
```

manifest.jsonを作成したら、push通知を行いたいサイト内の`<head></head>`の中に以下の1行を追加してください。

```
<link rel="manifest" href="/manifest.json">
```

## 2. notification.jsonの作成
通知メッセージをjson形式で記載します。

```notification.json
{
  "title": "引越し侍",
  "body": "皆さんこんにちはでござる。拙者、引越し侍でござるよ。"
}
```

## 3. sw.jsの作成
push通知要求があった場合に起動するスクリプトです。ここで、先ほど作成したnotification.jsonからメッセージの取得や、アイコンの画像を指定を行います。

```sw.js
self.addEventListener("push", function(event) {
  event.waitUntil(
    self.registration.pushManager.getSubscription()
      .then(function(subscription) {
        if (subscription) {
          return subscription.endpoint
        }
        throw new Error('User not subscribed')
    })
    .then(function(res) {
      return fetch('/notifications.json')
    })
    .then(function(res) {
      if (res.status === 200) {
        return res.json()
      }
      throw new Error('notification api response error')
    })
    .then(function(res) {
      return self.registration.showNotification(res.title, {
        icon: '/icon.png',
        body: res.body
      })
    })
  )
})
```

`icon: `部分にアイコン画像を指定します。

# 4. endpointの取得
push通知を行いたいサイト内の`<head></head>`の中に以下のスクリプトを追加してください。
ここではテスト用にconsole.logでendpointの値を出力しています。

```
<script>
window.addEventListener('load', function() {
  if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register("/sw.js")
      .then(function(registration) {
        return registration.pushManager.getSubscription().then(function(subscription) {
          if (subscription) {
            return subscription
          }
          return registration.pushManager.subscribe({
            userVisibleOnly: true
          })
        })
      }).then(function(subscription) {
        console.log("pushManager endpoint:", subscription.endpoint) 
      }).catch(function(error) {
        console.warn("serviceWorker error:", error)
      })
  }
});
</script>
```

# 4. push通知テスト

## 1. Chromeのdeveloper toolを使ってテスト
まずはChromeのdeveloper toolを使ってpush通知のテストを行います。
developer toolを開き、「Application」タブから「Service Worker」を選択してください。

![スクリーンショット 2017-02-26 18.39.53.png](https://qiita-image-store.s3.amazonaws.com/0/117041/e259d995-8bec-0437-4524-5f0d7912519b.png)

設定が全てうまく出来ていれば、status欄が緑で「running」と表示されます。うまく動いていない場合はConsoleタブにエラーが出ているはずなので、内容を確認して修正してください。

ステータスに問題が無ければ、右上の「push」をクリックしてみましょう。
実際push通知が届くはずです。

![スクリーンショット 2017-02-26 18.43.27.png](https://qiita-image-store.s3.amazonaws.com/0/117041/407856ca-4ec0-42b9-40ec-e203720687b2.png)

## 2. curlコマンドを使ってpush通知
次に、別の端末にpush通知を送ってみます。
以下のcurlコマンドを使います。

```
curl --header "Authorization:key=*******" --header Content-Type:"application/json" https://android.googleapis.com/gcm/send -d "{\"registration_ids\":[\"************************************************************\"]}"
```

keyの値には、上記手順2.で取得した「APIキー」を指定します。registration_idsの値には、上記手順3-4. で取得したendpointの値を設定します。push通知を行いたい端末のconsoleに出力されるendpointの値を記述してください。尚、endpointの値は「https://android.googleapis.com/gcm/send/****」の形式で出力されますので、/send/以降の値を指定してください。

# まとめ
いかがでしたでしょうか。今回は取り急ぎテスト用に端末のendpointを手動で取得し、curlコマンド内に指定してpush通知を行いましたが、jqueryを使ってendpointをサーバに送信して保存した上で、バッチ等を使ってcurlコマンドを実行してpush通知を行えば、実運用ベースで使える仕組みが作れると思います。ご興味ある方は是非お試し下さい。