こんにちは、入社2年目の茶谷です。
今回は、「Web Pushを試してみた(フロントエンド編)」の続きとして、バックエンドを実装していきます!
バックエンドからブラウザにプッシュ通知を送信するための準備
ブラウザにプッシュ通知を送信するために、バックエンドで下記の処理をおこないます。
- フロントエンドで発行したエンドポイントURLやキーをバックエンドで受け取る
- VAPID認証
- プッシュ通知のリクエストボディ(メッセージ)の暗号化(AES128GCM)
重要なのは、2と3の処理ですが、今回はPHPの「minishlink/web-push(^6.0)」というライブラリを使って実現します。
バックエンドの実装
1. push通知を実装するために、minishlink/web-push(^6.0)をインストール
まずは、必要なライブラリをインストールします。
1 |
composer require minishlink/web-push |
このライブラリが、VAPID認証とプッシュ通知のリクエストボディ(メッセージ)の暗号化をよしなにおこなってくれます。
2. ブラウザに通知を送信するために認証を行う
ブラウザにプッシュ通知を送信するには、認証を行う必要があります。
今回は下記のようにして実装しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// 認証 public function login() { // ブラウザに認証させる $auth = [ 'VAPID' => [ 'subject' => SYSTEM_URL, 'publicKey' => PUSHER_PUBLIC_KEY, 'privateKey' => PUSHER_PRIVATE_KEY, ] ]; $res = new MinishlinkWebPush($auth); return $res; } |
認証に必要な情報は、次のようになります。
subject:
通知の送信者(通知を送信するサーバー)を識別するためのURLまたはメールアドレス。
今回はGrpMailのテスト環境のURLを指定しています。
publicKey:
フロントエンド編のhttps://web-push-codelab.glitch.me/で発行した公開鍵を指定します。
privateKey:
フロントエンド編のhttps://web-push-codelab.glitch.me/で発行した秘密鍵を指定します。
3. 通知を送信する
いよいよブラウザにプッシュ通知を送信します。
送信処理は下記のように実装しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
/** * [機能説明] ブラウザに通知を送信 * * @param int $userId ユーザID * @param string $message 通知するデータ * @param MinishlinkWebPush $webPush * @return boolean 成否 **/ public function send($userId, $message, $webPush) { // 購読情報はユーザIDと紐づけてDBに格納されている $subscriptions = T_Push_Subscriptions::getByUserId($userId); // ユーザ1人につき、複数の購読情報を持っている場合がある foreach ($subscriptions as $subscription) { $subscriptionData = Subscription::create([ 'endpoint' => $subscription['endpoint'], 'publicKey' => $subscription['p256dh'], 'authToken' => $subscription['auth'], ]); $report = $webPush->sendOneNotification( $subscriptionData, $message ); if ($report->isSuccess()) { echo('送信成功!'); } else { echo('送信失敗....'); return false; } } return true; } |
これでプッシュ通知を送信する準備ができました!
今回実装したsend()は、新着メールを受信したときに実行されるようにしています。
プッシュ通知の送信に成功すると、ブラウザから通知が届きます!
おまけ:WebPushの再購読
実は、WebPushの購読情報には有効期限(expirationTime)が設定されている場合があります。
VAPIDかつChromeの場合は有効期限は無いらしく、常にexpirationTimeはnullを返します。
しかし、他のケースでは有効期限が切れる場合があるため、再度購読できるようにする必要があります。
購読情報変更の検知
pushsubscriptionchangeイベントで検知できるらしいですが、2024年2月現在、FireFoxとデスクトップ版のSafari以外はまだ実装されていないそうです。
(今回、FireFoxでも試してみましたがうまくいきませんでした...)
ServiceWorkerGlobalScope: pushsubscriptionchange イベント
もし、push通知の送信をフロントエンドからAPIを実行してバックエンドから送信させるなら、エラーが返ってきたときに再度購読処理を行えばいいですが、メールの受信時にpush通知させる場合はフロントエンドを介さないバックグラウンドでの実行になるため、フロントエンドにエラーを返すことができません。
そのため、購読情報の更新があれば発火するpushsubscriptionchangeイベントを使ってみたかったのですが、断念して次のようにして再購読処理を試みました。
ブラウザが購読情報を持っているかをpushManager.getSubscriptionで取得可能であり、購読情報が存在しなかったとき、pushManager.getSubscriptionはnullを返します。
つまり、現在のブラウザではまだ購読されていないので、ユーザに購読を促します。
実際の実装は今回記載しませんが、一応、再購読処理を行うことができるようになりました。
購読情報が有効期限切れであるレコードを削除する
次回から無駄なプッシュ通知を行わないように、有効期限切れになった購読情報を削除します。
具体的には、push serverからのレスポンスが410 (Gone)だった場合、該当の購読情報をDBから削除するといった処理を実装しました。
410は、指定された「subscription(購読情報)」が無効という意味になります。
引用:
If the user agent fails to acknowledge the receipt of the push
message and the push service ceases to retry delivery of the message
prior to its advertised expiration, then the push service MUST push a
failure response with a status code of 410 (Gone).
6.3. Receiving Push Message Receipts
終わりに
今回はWeb Pushを試してみましたが、新しい技術に触れることはやはり楽しいですね!
チャレンジラボではFlutterを用いたスマホアプリ開発にも取り組んでいます!
弊社では、新しいことに挑戦する社員ための教材やガジェット等の購入支援制度があります!
新しいもの好きの方、キー・ポイントで働いてみませんか?