LoginSignup
27
34

More than 5 years have passed since last update.

VAPIDでWebPushを実装してみた

Last updated at Posted at 2017-11-16

WebPush

VAPIDと呼ばれる仕組みを使ってWebPushを実装する。VAPIDの仕組みそのものは下記記事を参照。
https://qiita.com/tomoyukilabs/items/9346eb44b5a48b294762

処理の流れ

大雑把な流れは

  • クライアントでアプリケーションサーバから公開鍵の取得
  • サービスワーカーをブラウザへ登録
  • アプリケーションサーバの公開鍵をPushサーバに登録して、クライアント公開鍵・クライアント秘密乱数・エンドポイントを取得
  • クライアント公開鍵・クライアント秘密乱数・エンドポイントをサーバに登録
  • アプリケーションサーバで任意のタイミングでエンドポイントに向けてリクエストを送信
  • PushサーバからブラウザにPush送信
  • サービスワーカーでpushイベントをハンドルして通知

設計

VAPIDでPushを行うに当たって、下記の値が必要になる。

  • アプリケーションサーバ秘密鍵
  • アプリケーションサーバ公開鍵
  • クライアント公開鍵
  • クライアント秘密乱数
  • エンドポイント

上記のうち、サーバの鍵はコマンドで生成しておく。

$ openssl ecparam -genkey -name prime256v1 -out private_key.pem
$ openssl ec -in private_key.pem -pubout -outform DER|tail -c 65|base64|tr -d '=' |tr '/+' '_-' >> public_key.txt
$ openssl ec -in private_key.pem -outform DER|tail -c +8|head -c 32|base64|tr -d '=' |tr '/+' '_-' >> private_key.txt

テーブル作成

クライアント公開鍵、クライアント秘密乱数、エンドポイントの3つはクライアントを区別するために必要なため、DBに保存する必要がある。
アプリケーションのユーザIDと紐づけておく。

カラム名 説明
id int(11) pkey
user_id int(11) ユーザのID
endpoint varchar(2048) エンドポイント
key varchar(255) クライアント公開鍵
token varchar(255) クライアント秘密乱数

web-push-php

アプリケーションサーバからPushサーバへのリクエストはweb-push-libs/web-push-phpを使う。

$auth = array(
    'VAPID' => array(
        'subject' => 'https://hoge.com',
        'publicKey' => $public_key,
        'privateKey' => $private_key,
    ),
);

$webpush = new \Minishlink\WebPush\WebPush($auth);
$payload = [
    'title' => '通知があります。',
    'message' => $message,
    'url' => 'https://hoge.com/user/'.$user_id
];

$res = $webpush->sendNotification(
    $endpoint,
    json_encode( $payload ), // jsonで送ると、pushイベントでjsonでデータを取得できる。
    $key,
    $token,
    true
);

サービスワーカー

Pushサーバから送られたPushはサービスワーカーで処理する。

登録

サーバから公開鍵を取得して、サービスワーカーを登録する。また、サービスワーカーの登録に成功したら、Pushサーバからsubscriptionの取得を行う。取得できなかった場合はサーバ公開鍵と一緒に登録を行う。

var serverKey = urlB64ToUint8Array(data.result.server_key); // Util function from https://github.com/GoogleChrome/push-notifications/blob/master/app/scripts/main.js#L31-L44

navigator.serviceWorker.register('serviceworker.js', { scope: './' }).then(function (registration) {
    // getSubscription()で登録済みのsubscriptionを取得してみる
    registration.pushManager.getSubscription().then(function (subscription) {
        if (subscription) {
            registerSubscription(subscription);
            return;
        }

        // 登録済みのsubscriptionがない場合は新たに登録する
        registration.pushManager.subscribe({
            userVisibleOnly: true,
            applicationServerKey: serverKey
        }).then(function (subscription) {
            // Pushサーバに登録できたらsubscriptionをアプリケーションサーバにも登録する
            registerSubscription(subscription);
            return;
        })
    });
});

イベントハンドリング

よく使うと思われるイベントは下記の2つ。

  • pushイベント:送られてきたPushイベントをハンドルして、registration.showNotificationで通知する
  • notificationclickイベント:通知をクリックしたときの処理を記述する

アプリケーションサーバで$payloadに入れてjsonで送ったデータはpushイベントのevent.data.json()で取得できる。
また、showNotificationの第二引数に渡したオブジェクトは、notificationclickイベントのevent.notificationで取得する。

self.addEventListener('push', function(event) {
    const data = event.data.json() // payloadで送ったデータの取得

    return event.waitUntil(
        self.registration.showNotification(
            data.title,
            {
                // ここのデータをevent.notificationで取得する (event.notification.bodyとか)
                icon: '/img/icon.png',
                body: data.message,
                data: {
                    url: data.url
                }
            }
        )
    );
}, false);

self.addEventListener('notificationclick', function(event) {
    event.notification.close();

    const data = event.notification.data;
    event.waitUntil(clients.matchAll({
        type: 'window'
    }).then(function(clientList) {
        for (var i = 0; i < clientList.length; i++) {
            var client = clientList[i];
            if (client.url === data.url && 'focus' in client) {
                return client.focus();
            }
        }
        if (clients.openWindow) {
            return clients.openWindow(data.url); // $payloadで送ったurlを開く
        }
    }));
});
27
34
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
34