ServiceWorker を使ってプッシュ通知実装  

#<i class="fa fa-file-text-o"></i> はじめに  
[前回の投稿](http://qiita.com/OMOIKANESAN/items/72c17bf96099adde7787) で 「Service Worker ってなんぞや？」について調べたことを共有しました。  
今回は Service Worker を使って Web アプリでプッシュ通知機能を実装してみようと思います。  
使用イメージとしては次のとおりです。  
1. ユーザーがサイトにアクセスして ServiceWorker が登録される
2. ServiceWorker 登録時にユーザーの端末上にエンドポイントが生成される
3. プッシュ通知送信者がエンドポイントを指定してプッシュを送信する
4. Google の BaaS である Firebase のプッシュ送信サービス **Firebase Cloud Messaging(FCM)** がエンドポイントに対してプッシュ通知を送信する
5. ServiceWorker が push イベントを検知して、ユーザーの端末上にプッシュ内容を表示する　


ざっくりと下のようなイメージのものができあがります！  

![ServiceWorkerOutline.png](https://qiita-image-store.s3.amazonaws.com/0/153290/16be2802-7254-08a2-fb43-c1fd8802ef8f.png)

# <i class="fa fa-file-text-o"></i> プッシュ通知実現のための準備  
## <i class="fa fa-google"></i> プロジェクト登録  
プッシュ通知を送信する場合は、Google Developer Console か Firebase Console にプロジェクトを作成する必要があります。
今回は推奨されている Firebase でプロジェクトの新規作成を説明します。  

1. [Firebase Console](https://console.firebase.google.com/) にアクセスして、「新規プロジェクト作成」をクリック  
![Firebase登録1.PNG](https://qiita-image-store.s3.amazonaws.com/0/153290/d4a0b255-08f6-3feb-a77b-88ba33caa718.png)  
<br />

2. プロジェクト名、国/地域を設定して、プロジェクトを作成  
![Firebase登録2.PNG](https://qiita-image-store.s3.amazonaws.com/0/153290/05f3db3e-7666-acca-9d24-0999f23edc92.png)  
<br />

3. 通知送信に必要な送信者 ID と API キーが作成されているので調べる  
![Firebase登録3.PNG](https://qiita-image-store.s3.amazonaws.com/0/153290/1e2fcb0b-cb2f-ed66-0a6a-f0b8cc0d88c1.png)  

## manifest.json
プッシュ通知に必要な manifest.json を用意します。  
先ほど Firebase で作成した際に調べた送信者 ID を ``` gcm_sender_id ``` に指定します。  

``` js
{
  "name": "ServiceWorker Push",
  "short_name": "ServiceWorker Push",
  "icons": [{
    "src": "プッシュ通知で表示する画像のURL",
    "sizes": "192x192",
    "type": "image/jpg"
  }],
  "start_url": "./RegistServiceWorker.html",
  "display": "standalone",
  "gcm_sender_id": "Firebase で調べた送信者ID"
}
```

また、 ``` start_url ``` で指定したページ内で、 manifest.json を読み込んでください。  

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

# <i class="fa fa-file-code-o"></i> 実装  
## RegistServiceWorker.html  
ServiceWorkerを登録するページです。
ServiceWorkerの登録と、プッシュ通知に必要な情報を表示します。

``` html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <link rel="manifest" href="/manifest.json"/>
    <title></title>
</head>
<body>
    サービスワーカーを試すページです!<br>
    まだサービスワーカーは動いていません！
</body>
<script type="text/javascript">
    if ('serviceWorker' in navigator) {
        navigator.serviceWorker.register('/serviceworker.js');

        navigator.serviceWorker.ready
            .then(function (registration) {
                return registration.pushManager.subscribe({ userVisibleOnly: true });
            })
            .then(function (subscription) {
                console.log('GCM EndPoint is:' + subscription.endpoint);
                var auth = subscription.getKey('auth') ? btoa(String.fromCharCode.apply(null, new Uint8Array(subscription.getKey('auth')))) : '';
                console.log('User Auth is:' + auth);
                var publicKey = subscription.getKey('p256dh') ? btoa(String.fromCharCode.apply(null, new Uint8Array(subscription.getKey('p256dh')))) : '';
                console.log('User PublicKey is:' + publicKey);
            })
            .catch(console.error.bind(console));
    }
</script>
</html>
```

ServiceWorker 登録部分で ``` registration.pushManager.subscribe({ userVisibleOnly: true }) ``` のように subscribe することで、プッシュ通知を受け取ることが可能になります。
subscribe が終わったら、 プッシュを送る時に必要なエンドポイント、鍵生成に使用する乱数、公開鍵を取得しています。

## serviceworker.js
ServiceWorker のイベントを設定しています。

``` js
self.addEventListener('fetch', function (event) {
    event.respondWith(
      new Response('サービスワーカーが動いています！')
    );
});

self.addEventListener('push', function (event) {
    console.log('Received a push message', event);
    var title = "プッシュ通知です！";
    var body = "プッシュ通知はこのようにして送られるのです";

    event.waitUntil(
        self.registration.showNotification(title, {
            body: body,
            icon: 'http://free-images.gatag.net/images/201108090000.jpg',
            tag: 'push-notification-tag'
        })
    );
});
self.addEventListener('notificationclick', function (event) {
    event.notification.close();
    clients.openWindow("/");
}, false);


```

* fetch  
ServiceWorker が動いているブラウザでリソースのリクエストが発生すると fetch イベントが呼ばれます。  
今回は「サービスワーカーが動いています！」というレスポンスを返しています。
* push  
プッシュ通知を受け取った時の挙動を設定します。  
通知に表示するタイトル、メッセージ、アイコン等を設定できます。
* notificationclick  
表示されたプッシュ通知を開いた（クリックした）ときの挙動を設定します。

## push.js  
プッシュ通知を送信するスクリプトです。  
ServiceWorker 登録時に取得したエンドポイント、乱数、公開鍵を使用します。

``` js
'use strict';

const push = require('web-push');

const GCM_API_KEY = '**Firebase で調べた API キー**';
push.setGCMAPIKey(GCM_API_KEY);

const data = {
    'endpoint': '**エンドポイントを指定**',
    'userAuth': '**鍵生成に使用する乱数指定**',
    'userPublicKey': '**公開鍵指定**'
};

const pushSubscription = {
	endpoint: data.endpoint,
	keys: {
		auth: data.userAuth,
		p256dh: data.userPublicKey
	}
}

push.sendNotification(pushSubscription,'Hi! How are you?')
    .then(function(result) {
    console.log("success!");
        console.log(result);
    })
    .catch(function(err) {
    console.log("fail!");    
        console.error(err);
    });
```
push.js を実行するためには Node.js/npm 環境が必要になります。  

Node.js/npm 環境は下記の記事を参考にすれば初めてでも問題なくできると思います。  

* [Node.js / npmをインストールする（for Windows）](http://qiita.com/taipon_rock/items/9001ae194571feb63a5e)

# <i class="fa fa-tasks"></i> 実際にプッシュ通知を送信してみる  
## ServiceWorker の登録  

※ブラウザは Google Chrome を使用しています。  
1. **RegistServiceWorker.html にアクセスします**  
![localhost.PNG](https://qiita-image-store.s3.amazonaws.com/0/153290/a4a3dd89-0ea2-ce24-c04e-a0664042e917.png)　　
2. **ServiceWorker が登録されたか確認してみましょう**
(chrome://serviceworker-internals/ と入力)  
![serviceworker.PNG](https://qiita-image-store.s3.amazonaws.com/0/153290/f68364cc-ce96-b477-2544-994a339fb138.png)  
無事登録されているようです。  
3. **登録されたエンドポイント、乱数、公開鍵の情報を取得しておきましょう**
(F12 で Chrome の開発者モードに切り替え、console 出力を確認)  
![endpoint.PNG](https://qiita-image-store.s3.amazonaws.com/0/153290/46a538cd-e4f5-d800-cd2c-71b6e83e8f98.png)
※オレンジで塗りつぶしている部分の値を push.js に書き込んでください

## プッシュ通知送信スクリプトを実行  
ターミナル上で ``node push.js`` と入力  

![success.PNG](https://qiita-image-store.s3.amazonaws.com/0/153290/d6bbdff8-41cd-20ad-97ba-da969c77f927.png)

ターミナルに "success!" の文字が表示されるや否や、プッシュ通知が表示されました！

#### push.js 実行時に気をつけること  
push.js を node.js 環境で実行したときに push.js の文字コードが原因で syntax error になって、結構ハマりました。  
``node push.js`` を実行する際の push.js の文字コードは UTF-8 が無難だと思います。  
![error.PNG](https://qiita-image-store.s3.amazonaws.com/0/153290/6f1c86c6-4635-8068-80dc-8a9428485121.png)

# <i class="fa fa-tag"></i> まとめ  
* 今回は ServiceWorker を使ったプッシュ通知の送信を実装してみました  
* 感想としては  
    * ServiceWorker のスコープ指定が意外に融通がきかない(cross domain とかで怒られてしまう)    
    * 一斉に複数のユーザーに同一のプッシュを送信する機構は自分で作らなれければならない  
    * プッシュが初めて届いたときの感動はすごい。是非皆さんにも味わってほしい  

ServiceWorker ができることはプッシュ通知以外にもいろいろあるので、是非試してみてください。