初めてのService Worker （Push API編）

# はじめに
この記事は[Gizumoエンジニア Advent Calendar 2015](http://www.adventar.org/calendars/919)の17日目の記事です。

今回は、ServiceWorkerのPushAPIを使ってブラウザにPush通知を表示させてみたいと思います！
このような記事はすでにあるので、今回は、

1. **サンプルプログラムを用いて、より簡単にPush通知を体験してみる**
2. **どんな流れでPush通知がくるのか流れを把握する**

この2点を中心に進めて行きます。

そして以下の点には触れません

1. **Service Worker 及び Push APIの詳しい仕様**

今回はService WorkerのPush通知を体験し、おおまかな流れを掴むことを目的としています。


まだあまりJSに触れたことがない方も是非Push通知を体験してみてください！！


それでは始めます！

#開発環境
Chrome 42以上
MAMP（MAMPのインストールは[こちら](https://www.mamp.info/en/)）


上記が使えれば準備OKです。

# Service Workerとは
Service Workerとは**ブラウザではなく、バックグラウンドで動作するJavaScript**のことを言います。
Service Workerを使えば、オフラインでwebサイトが見れたり、ブラウザにpush通知を送ることができます。

↓詳しくはこちら↓
http://www.html5rocks.com/ja/tutorials/service-worker/introduction/

# Push通知の準備
それではPush通知の準備をしていきます。
Push通知を体験するには以下の設定が必要になります。

## サンプルプログラムのダウンロード
[こちらのサイト](https://github.com/GoogleChrome/samples/tree/gh-pages/push-messaging-and-notifications)から「push-messaging-and-notifications」をダウンロードしてください。

今回は下記4ファイルを使用します。
- index.html
- main.js
- service-worker.js
- manifest.sample.json


## Google Developer Consoleでプロジェクトの作成
今回のサンプルではPush通知にGoogleのAPIであるGCM(Google Cloud Messaging)を使用します。


1. [Google Developer Console](https://console.developers.google.com/project)ページの「プロジェクトを作成」を押下し、「プロジェクト名」は好きなプロジェクト名を、「プロジェクトID」はデフォルトのままにし、「作成」ボタンをクリックしてください。
![スクリーンショット 2015-12-16 22.44.25.png](https://qiita-image-store.s3.amazonaws.com/0/102556/da979278-56d6-5c8c-62ce-4f81a287d256.png)

2. 左のメニューボタンからAPIManagerを選択し検索欄に「Google Cloud Messaging for Android」のAPIを有効にしてください。
![スクリーンショット 2015-12-16 22.48.02.png](https://qiita-image-store.s3.amazonaws.com/0/102556/9dd2d336-7e0a-a06c-74f0-98a5edd7e19f.png)

3. 「認証情報」→「新しい認証情報」→「APIkey」→「サーバーキー」の順に押下し、作成したAPIkeyをコピーしておいて下さい。
![スクリーンショット 2015-12-16 22.50.31.png](https://qiita-image-store.s3.amazonaws.com/0/102556/8130cd76-9572-a41b-1f85-9ff6eb72bf8f.png)





## サンプルソースを修正する
サンプルソースに以下の設定を反映させてください。

```lang:manifest.sample.json
manifest.sample.json → manifest.json（ファイル名の変更）
```

```js:main.js
'use strict';

var API_KEY = 'ここにAPIkeyを入れてください';
var GCM_ENDPOINT = 'https://android.googleapis.com/gcm/send';

var curlCommandDiv = document.querySelector('.js-curl-command');
var isPushEnabled = false;
```


```json:manifest.json
{
  "name": "Push Demo",
  "short_name": "Push Demo",
  "icons": [{
        "src": "images/icon-192x192.png",
        "sizes": "192x192"
      }],
  "start_url": "./index.html?homescreen=1",
  "display": "standalone",
  "gcm_sender_id": "ここにプロジェクト番号を入れてください",
  
  "//": "gcm_user_visible_only is only needed until Chrome 44 is in stable ",<img width="1266" alt="スクリーンショット 2015-12-14 21.59.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/102556/6c4613d5-d907-cc4c-6189-d863008e50b2.png">

  "gcm_user_visible_only": true
}
```

## いざPush通知
それではPush通知をおこなってみましょう！

今回はlocalhostでPush通知を行うのでMAMPのルートディレクトリに今回のサンプルを設定してください。
こんな感じです

![スクリーンショット 2015-12-14 21.53.16（2）.png](https://qiita-image-store.s3.amazonaws.com/0/102556/0fddeb71-8f96-0a6c-faad-bca5989cfe19.png)



準備ができたらlocalhostにアクセスし、ブラウザのPush通知を許可にします。
「cURL Command to Send Push」に表示されるコマンドをコピーし、ターミナル(winの場合はコマンドプロンプト)に貼って 　**Enter！！**　

すると・・・


![スクリーンショット 2015-12-14 21.59.05.png](https://qiita-image-store.s3.amazonaws.com/0/102556/446a215c-5eab-7b17-e578-203e463d5daa.png)

見事Push通知を受け取ることができました！
ブラウザを閉じて再度ターミナルにコマンドを打ってもPush通知を受け取ることができます！

# サンプルプログラムの解説
それではどんな順番でPush通知されたのか、簡単に見ていきたいと思います。

## 各JSファイルの役割
今回使用したmain.jsとservice-worker.jsの役割を紹介します。
### **main.js**
・ブラウザがPush通知を許可しているか、Service Workerに対応しているかを判断する。
・対応していれば、Service Workerを常駐させ、Push通知を送るためのURLみたいなもの(endpoint)を取得し、画面に表示する。

### **service-worker.js**
・GCMからのPush通知を受け取り、ブラウザにPush通知を表示させる。

それぞれはこんな感じの内容になっています！

## 各JSファイルの内容
それではmain.jsとservice-worker.jsの内容を少しずつですが解説していきます。

### main.js

まずブラウザにアクセスした際に以下の関数が実行されます。

```js:main.js
// 203行目〜221行目

window.addEventListener('load', function() {
  // ...省略

  if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('./service-worker.js')
    .then(initialiseState);
  } else {
    window.Demo.debug.log('Service workers aren\'t supported in this browser.');
  }
});
```

1. `if ('serviceWorker' in navigator)`
でnavigatorオブジェクトにserviceWorkerがあったら、service-worker.jsを常駐させ、initialiseState関数を実行します。


次にinitialiseState関数です。

```js:main.js
// 203行目〜221行目

function initialiseState() {

  navigator.serviceWorker.ready.then(function(serviceWorkerRegistration) {
    
    serviceWorkerRegistration.pushManager.getSubscription()
      .then(function(subscription) {
        
        var pushButton = document.querySelector('.js-push-button');
        pushButton.disabled = false;

        if (!subscription) {
          return;
        }

        sendSubscriptionToServer(subscription);

        pushButton.textContent = 'Disable Push Messages';
        isPushEnabled = true;
      })
      .catch(function(err) {
        window.Demo.debug.log('Error during getSubscription()', err);
      });
  });
}
```

1. `navigator.serviceWorker.ready`
PromiseオブジェクトとserviceWorkerRegistrationを返し、serviceWorkerRegistrationオブジェクトを引数に関数を実行します。

2. `serviceWorkerRegistration.pushManager.getSubscription()`
serviceWorkerRegistrationのpushManagerを経由してgetSubscriptionを実行するとsubscriptionオブジェクトが返ってきます。このなかにendpointが格納されています。

3. `sendSubscriptionToServer(subscription);`
そして先ほどのsubscriptionオブジェクトを引数に設定し、sendSubscriptionToServerを実行！


```js:main.js
// 30行目〜44行目

function sendSubscriptionToServer(subscription) {
  var mergedEndpoint = endpointWorkaround(subscription);
  showCurlCommand(mergedEndpoint);
}
```

1. `var mergedEndpoint = endpointWorkaround(subscription)`
subscriptionオブジェクトのendpointを取得してmergedEndpointに代入します。
subscription.endpointと同義ですね！

2. `showCurlCommand(mergedEndpoint)`
mergedEndpointを引数にshowCurlCommandを実行！！


```js:main.js
// 49行目〜65行目

function showCurlCommand(mergedEndpoint) {
  // ...省略

  var curlCommand = 'curl --header "Authorization: key=' + API_KEY +
    '" --header Content-Type:"application/json" ' + GCM_ENDPOINT +
    ' -d "{\\"registration_ids\\":[\\"' + subscriptionId + '\\"]}"';

  curlCommandDiv.textContent = curlCommand;
}
```

あとはendpointを代入してcurlCommandを作成し、テキストとして出力させます。
サンプルではターミナルにコマンドを貼り付けただけでしたが、
実際はたくさんのユーザのendpointを保存しておかないといけないので、DBに持っておく必要がありそうです。

以上でmain.jsはおしまいです！

### service-worker.js

こちらは比較的読みやすいのでさくっと見ます！


```js:service-worker.js
// 203行目〜221行目

self.addEventListener('push', function(event) {
  var title = 'Yay a message.';
  var body = 'We have received a push message.';
  var icon = '/images/icon-192x192.png';
  var tag = 'simple-push-demo-notification-tag';

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

1. `self.addEventListener('push', function(event){}`
これでPush通知のイベントを取得できます。

2.

```js:
var title = 'Yay a message.';
var body = 'We have received a push message.';
var icon = '/images/icon-192x192.png';
```

このようにクライアント側にPush通知の内容を書くので、サーバー側からPush通知の内容を動的に指定することはできないようです。
(実はできるみたいです。[Web Pushでブラウザにプッシュ通知を送ってみる](http://qiita.com/tomoyukilabs/items/217915676603fda73b0a))

あとは`self.registration.showNotification()`を通じて変数を代入していきます！


かなりざっくりでしたが、
以上でservice-worker.jsの解説を終わります！

# 終わりに
いかがでしたでしょうか？
ブラウザにPush通知を送れるこの技術は、有効に使えばかなり強力なツールになると思います。

今回はPCで実装しましたが、AndroidのChromeでもService WorkerのPush APIを使用することができるので、サーバーに上げて是非試してみてください。



以上になります！
最後までご覧頂きありがとうございました！