サーバサイドエンジニアの古谷です。

ウェブブラウザを用いたサービスの欠点であったオフライン時の動作やプッシュ通知など、従来ネイティブアプリにしかできなかったことができるようになってきました。

なかでもWeb Push通知は、ユーザのエンゲージメント向上やメールに変わる情報発信の手段として注目をあつめています。

本記事では、Service Workerを利用したWeb Push通知環境の構築について、コードを交えながら解説します。

Service Workerとは

Service WorkerとはWebブラウザとWebサーバの間にある、クライアントPC側のプロキシのようなものです。

Service Workerを使えば、オフライン時でもWebサービスを提供できたり、Web Push通知を受け取ることができます。

Service WorkerにはHTTPSでしか利用できない制約がありますが、近年GoogleがHTTPSページを標準でインデックスするなど、ウェブのHTTPS化は急速に進んでいます。

Webサイトにプッシュ通知機構を搭載するには?

Webサイトにプッシュ通知を提供する方法はいくつかありますが、今回はGCM(Google Cloud Messaging)を用いたプッシュ機構の構築について解説いたします。

まず、Google Cloud Consoleでプロジェクトを作成し、プロジェクト番号とAPIキーのサーバーキーを作成してください。

そのプロジェクト番号を記載したmanifest.jsonを用意します。

{
  "gcm_sender_id": "000000000000"
}

manifest.jsonはHTML側から読み込む必要がありますので、<head>内にlink要素を設置します。

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

次に、JavaScriptを記述します。
このコードが呼ばれるとブラウザのポップアップが出現するので、DOMContentLoadedイベント発生後の処理を記述します。

var registerSubscription = function(subscription){
  if(subscription){
    $.post("/api/register", {endpoint: subscription.endpoint})
  }
};

navigator.serviceWorker.ready.then(function(sw){
  sw.pushManager.getSubscription().then(function(subscription){
    if(subscription === null){
      sw.pushManager.subscribe({userVisibleOnly: true}).then(registerSubscription);
    }
  });
});
navigator.serviceWorker.register("/sw.js").then();

最後に、Service Workerのコードを書きます。
このJavaScriptファイルは先程のJavaScriptで登録した/sw.jsに配置します。

self.addEventListener("push", function(event){
  event.waitUntil().then(function(res){
    self.registration.showNotification("プッシュタイトル", {
      body: "プッシュ通知",
      icon: "/path/to.png",
      tag: "tag"
    });
  });
});
self.addEventListener("notificationclick", function(event) {
  event.notification.close();
  clients.openWindow("/");
}, false);

これで固定文言でのお知らせ機能が実装できます。
しかし、実際のWebサービスで運用する際には、先ほど実装したものだけでは機能が足りません。

  • ユーザによって文言や画像の出し分けをしたい
  • 実際にどのプッシュ通知がクリックされたか統計をとりたい

このような要望は当然ありますし、サービス運用には欠かせないものです。

Web Push通知でユーザ固有の文言を表示させるには

チャット通知や、ユーザへのお知らせ機能としてWeb Push通知を用いるには、文言の出し分けが必要です。

Web Push通知で文言を出し分けるには、プッシュイベントが来た際にメッセージをサーバに問いあわせます。
Service Worker内ではXMLHttpRequestは使えません。
fetch()を使うことで同等のことができます。

fetch()に、オプションcredentials: 'include'を渡し、Cookieをつけてリクエストをします。
次に、Cookieの情報からユーザを判断して、適切なJSONを返すサーバのコードを書きます。

今回は例として、/api/message.jsonにアクセスをすると、Cookie情報を元に適切なプッシュ文言・画像を以下の形式で返すAPIがあるものとして、Service Workerの実装を紹介します。

{
  title: "Title",
  body: "Message",
  iamge_path: "/path/to.png"
}

fetch()のJSON形式であれば、json()でJavaScriptのオブジェクトが取得できます。

(function(){
  self.addEventListener("push", function(event){

    event.waitUntil(
      fetch("/api/fetch_message", {
        credentials: "include"
      }).then(function(res){
        res.json().then(function(data){
          self.registration.showNotification(data.title, {
            body: data.body,
            icon: data.icon
          });
        });
     })
    )
  });
  self.addEventListener("notificationclick", function(event) {
    event.notification.close();
    clients.openWindow("/");
  }, false);
})();

通知のクリック数を取得する

さきほどのAPIが返すJSONにトークン付きのリンク先を含むことで、プッシュ通知の効果を測定できます。

{
  title: "Title",
  body: "Message",
  iamge_path: "/path/to.png",
  link_url: "/path/foo?code=12345678"
}

showNotificationの第2引数のオブジェクトにdataというキーで渡したデータは、notificationclickイベントevent.notification.dataで取得することができます。

self.registration.showNotification(data.title, {
  body: data.body,
  icon: data.icon,
  data: {
    link_to: data.link_to
  }
});
self.addEventListener("notificationclick", function(event) {
  event.notification.close();
  clients.openWindow(event.notification.data.link_to);
}, false);

Service Workerの進化

Web Push通知という技術は2015年春の時点で利用できましたが、運用しているサービスで実際に利用するためにはこのような工夫が必要でした。

ウェブブラウザは日々進化していて、ユーザのために必要な機能は次々と実装されています。

2016年4月にリリースされたGoogle Chrome 50では、これらの要望を解決できる仕組みが備わりました。

  • 通知に任意のメッセージが送れること
  • 通知画面のクローズイベントが取得ができること
  • 以前のコードから大きな変更点なく、これらの機能が利用できること

次回はChrome 50を用いて、プッシュ通知のメッセージを暗号化して送信することについて記事を書きたいと考えております。