WEBプッシュ通知の実装方法について

この記事は [ウェブクルー Advent Calendar 2017](https://qiita.com/advent-calendar/2017/webcrew)の8日目の記事です。
昨日は@wchikarusatoさんの「[知ってる様で知らないless](https://qiita.com/wchikarusato/private/7c068078014fcb563e6b)」でした。

# WEBプッシュ通知とは
スマホアプリで馴染み深いプッシュ通知のWEB版。
アプリを使わずにブラウザの機能で通知を行えるため、アプリのインストールは不要ですが、
通知を受け取って貰うためには、利用者に通知を受け取る「許可」をして貰う必要があります。
スマホだけではなくPCでも使用できます。

# 実装を始める前に
Service Workerは、HTTPS環境とlocalhostでのみ動作します。

## Service Workerとは
ブラウザが Web ページとは別にバックグラウンドで実行するスクリプト。
プッシュ通知だけではなく、リソースのキャッシュ、バックグラウンド同期などもできます。

参考：[Service Workerの紹介](https://developers.google.com/web/fundamentals/primers/service-workers/?hl=ja)


# 実装方法
## サーバーキーの確認
Firebase consoleにアクセス
https://console.firebase.google.com/

プロジェクトを追加をクリック
![1.jpg](https://qiita-image-store.s3.amazonaws.com/0/210852/3bab2817-d899-5ba2-5b89-b8cd17dcede3.jpeg)

プロジェクト名を入力、国／地域を選択してプロジェクト作成
今回は「sample-webpush」を作成

Overviewの右側にある歯車みたいなマークをクリック
![2.jpg](https://qiita-image-store.s3.amazonaws.com/0/210852/84064c09-b1b2-81cb-5933-24a6cb15329e.jpeg)

プロジェクトの設定をクリックすると下記のような画面が表示される。
![3.jpg](https://qiita-image-store.s3.amazonaws.com/0/210852/b5d76b7e-c287-4e73-ad57-95253e2423b3.jpeg)

「クラウドメッセージングタブ」を選択すると、
「サーバーキー」「以前のサーバーキー」「送信者ID」が表示される。
ここに表示されている「サーバーキー」を後記する＊＊＊に記述、
「送信者ID」を`manifest.json`に記述する。

## manifest.jsonの作成
```json:manifest.json
{
  "name": "プッシュ通知（サンプル）",
  "short_name": "sample Push",
  "gcm_sender_id": "送信者IDを記述する"
}
```

#通知の許可を求めるページの作成
```html:permission.html
<html>
<head>
<!-- 上に記載したmanifest.jsonを読み込む -->
<link rel="manifest" href="manifest.json">
<!-- ココから下は、Firebaseの設定「全般」タブの「ウェブアプリにFirebaseを追加」をクリックした時に表示される内容を張り付ける-->
<script src="https://www.gstatic.com/firebasejs/4.5.0/firebase.js"></script>
<script>
  // Initialize Firebase
  var config = {
    apiKey: "***",
    authDomain: "***",
    databaseURL: "***",
    projectId: "***",
    storageBucket: "***",
    messagingSenderId: "***"
  };
  firebase.initializeApp(config);
</script>
<!-- ココまで-->
</head>
<body>
</body>
</html>
```

#Service Workerの登録処理を作成

```javascript:register.js
if ('serviceWorker' in navigator) {
  //serviceworker.jsをSerice Workerとして登録
  navigator.serviceWorker.register('serviceworker.js');
  //navigator.serviceWorker.readyはresolveなPromiseと、
  //serviceWorkerRegistrationオブジェクトを渡す。
  navigator.serviceWorker.ready.then((serviceWorkerRegistration) => {
    initialize(serviceWorkerRegistration);
  }).catch(function(err) {
    console.log(err);
  });
}
```

# エンドポイントとトークンを表示させる処理を作成
以下のメソッドを`push_notify.js`というファイルに記述。
##initializeメソッドの作成
`register.js`から呼び出されています。

```javascript:push_notify.js
function initialize(serviceWorkerRegistration) {
  switch (Notification.permission) {
    case 'granted':
      console.log("permission = granted");
      break;
    case 'denied':
      console.log("permission = denied");
      break;
    case 'default':
      Notification.requestPermission(function (permission) {
        // ユーザが許可した場合は、通知を作成する
        if (permission === "granted") {
          serviceWorkerRegistration.pushManager.subscribe({userVisibleOnly: true})
          .then(subscription => {
            showEndpoint(subscription);
          })
          .catch(e => console.log(e));
        }
      });
      break;
    default:
      break;
  }
}
```

## showEndpointメソッド追加
```javascript:push_notify.js
function showEndpoint(subscription) {
  console.log("endpoint:" + subscription.endpoint);
  console.log("token   :" + getToken(subscription.endpoint))
}
```

## getTokenメソッド追加
```javascript:push_notify.js
function getToken(subscription) {
  let token = subscription.split("/");
  return token[token.length - 1];
}
```

# serviceworker.jsの作成
```javascript:serviceworker.js
self.addEventListener('push', function (event) {
    let title = "プッシュ通知（サンプル）";
    let body = "ココに本文を設定します。\rプッシュ通知のサンプルです。";

    event.waitUntil(
        self.registration.showNotification(title, {
            body: body,
        })
    );
});
```

# 構造
今回は、下記のように全て同一階層に配置します。

```
root/
　┣━　manifest.json
　┃
　┣━　permission.html
　┃
　┣━　register.js
　┃
　┣━　push_notify.js
　┃
　┗━　serviceworker.js
```

# 動作確認
(1) ブラウザを起動しデベロッパーツールを起動します。
(2) permission.htmlにアクセスします。
(3) ブラウザの右上あたりに下記のようなポップアップ画面が現れるので「許可」を押下します。
![5.jpg](https://qiita-image-store.s3.amazonaws.com/0/210852/53d06139-77ed-f044-2188-3756127112c9.jpeg)
(4) デベロッパーツールのコンソール下記2つのログが出力されている事を確認します。

```
endpoint:https://android.googleapis.com/gcm/send/*****
token   :*****
```
(5) (4)の`token`と`ウェブ API キー`を下記CURLコマンドに埋め込みます。
※`###ウェブ API キー###`は、Firebase consoleで作成したプロジェクトに表示されている`ウェブ API キー`を設定

```
curl --header "Authorization: key=###ウェブ API キー###" --header Content-Type:"application/json" https://fcm.googleapis.com/fcm/send -d "{\"registration_ids\":[\"###(4)で取得したtokenを埋め込む###\"]}"
```

(6) CURLコマンドを実行すると、(3)を実施したブラウザに下記のような通知が表示されます。
![4.jpg](https://qiita-image-store.s3.amazonaws.com/0/210852/12bc79be-be8a-1c84-cdd3-7803a4f78102.jpeg)

# 通知が表示されない場合の確認ポイント
- Service WorkerはHTTPS通信でのみ動作するため、動作環境がHTTPSかどうか確認する
- CURLに正しい`ウェブ API キー`を設定しているかどうか。サーバーキーを設定していませんか？
- CURLに正しい`token`が設定されているかどうか。途切れていませんか？
- CURLに`token`と`ウェブ API キー`を埋め込んだときに、必要な文字を消していないかどうか。

# まとめ
今回は、固定のメッセージが記述されたポップアップを表示させる方法について記述しましたが、
利用者の属性に合わせた通知（利用者ごとに異なるメッセージなど）を行うことも出来ます。
メッセージ付きプッシュ通知を送る場合は、メッセージの暗号化などを行う必要があり、
少しだけ手間が増えてしまいますが、用途は格段に広がると思います。

明日は、@keigo_magami さんです。よろしくお願いします。

# 最後に
ウェブクルーでは一緒に働いていただける方を随時募集しております。
お気軽にエントリーくださいませ。

[開発エンジニアの募集](https://hrmos.co/pages/1004681658307198976/jobs/0000005)
[フロントエンドエンジニアの募集](http://www.webcrew.co.jp/recruit/creative.html)
[データベースエンジニアの募集](https://hrmos.co/pages/1004681658307198976/jobs/0000023)