以前、PWAについて紹介しましたが、
今回はPWAでのPush通知(WebPush)にフォーカスしてお伝えしたいと思います。
PWAでPush通知を行う場合、PushAPIとNotificationAPIを使用します。
PushAPIはPushサーバからの通知を受け取り、NotificationAPIはユーザーへ通知を行います。
Push API は、ウェブアプリケーションがサーバーからメッセージ (プッシュ通知) を受信できるようにします。ウェブアプリケーションがフォアグランド状態かどうか、読み込まれているかどうかに関わらず利用できます。開発者は、オプトインしたユーザーへ非同期の通知と更新を届けることができ、タイムリーな新着コンテンツによってユーザーの関心を得られるでしょう。
出典元:MDN Web Docs – PushAPI
出典元:MDN Web Docs – Notification
Notification
は Notifications API のインターフェイスで、ユーザーへのデスクトップ通知の設定と表示に使われます。これらの通知の表示方法や機能はプラットフォームによって異なりますが、一般にユーザーに対して非同期に情報を提供する方法を提供します。
どちらもアプリにService Workerがインストールされている必要があります。
Push通知を行う為には配信サーバで認証を行う必要があります。
認証方式は主に
の2つがあります。
この記事ではFirebaseにプロジェクトを準備しなくて良い等、手間がかからない利点があるVAPIDを利用します。
laravel
┣ app
┃ ┣ Http
┃ ┃ ┗ Controllers
┃ ┃ ┗ NotificationController.php
┃ ┗ Models
┃ ┗ Notification.php
┣ public
┃ ┣ pwa_asset
┃ ┃ ┣ push.js //起点となるjsファイル
┃ ┃ ┗ manifest.json
┃ ┗ sw.js //Service Worker
┗ resources
┗ views
┗ header.blade.php
composer require minishlink/web-push
引用元:github – web-push-libs/web-push-php
今回は.envファイルに転記しておきます。
$ 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
引用元:github – web-push-libs/web-push-php
JavaScriptでServiceWorkerを登録します。
まずServiceWorkerのJavaScriptファイルを作成します。
//sw.js
// プッシュイベント
self.addEventListener("push", (event) => {
let data = event.data.text();
data = JSON.parse(data);
const options = {
body: data.body,
icon: data.icon,
actions: [
{ action: "yes", title: "yes" },
{ action: "no", title: "no" },
],
};
event.waitUntil(self.registration.showNotification(data.title, options));
});
// プッシュ通知をクリックしたときのイベント
self.addEventListener("notificationclick", (event) => {
event.notification.close();
if (event.action === "yes") {
console.log('clicked yes');
} else if (event.action === "no") {
console.log('clicked no');
} else {
console.log('something else');
}
});
self.addEventListener("fetch", (event)=> {
console.log('fetched');
});
self.addEventListener("install", (event) => {
console.log("service worker install ...");
});
self.addEventListener('activate', (event) => {
console.log("activated");
});
次にServiceWorkerを登録する為のJavaScriptを作成します。
この部分が処理の起点になります。
//push.js
self.addEventListener("load", async () => {
if ("serviceWorker" in navigator) {
window.sw = await navigator.serviceWorker
// 作成したServiceWorkerのJavaScriptファイルを指定
.register("/sw.js", { scope: "/" })
.then((reg) => {
reg.onupdatefound = function() {
reg.update();
}
}).catch(function (err) {
console.log("Failed ! Error: ", err);
});
}
});
ユーザー通知許可を得た後で、
Push通知に必要な情報(エンドポイント、公開鍵、トークン)の取得とDB保存を行います。
//push.js
function allowPushNotification(appServerKey) {
if ("Notification" in window) {
let permission = Notification.permission;
if (permission === "denied") {
alert("Push通知が拒否されています。ブラウザのPush通知を許可してください");
return false;
}
}
// 公開鍵
const applicationServerKey = urlB64ToUint8Array(appServerKey);
// 公開鍵とパラメータを渡し、戻り値で取得したエンドポイント、公開鍵、トークンをDB登録する。
navigator.serviceWorker.ready.then(
function(serviceWorkerRegistration) {
let options = {
userVisibleOnly: true,
applicationServerKey: applicationServerKey
};
// ユーザーからの通知許可が出ると以下が実行される
serviceWorkerRegistration.pushManager.subscribe(options).then(
function(pushSubscription) {
const key = pushSubscription.getKey("p256dh");
const token = pushSubscription.getKey("auth");
let data = new FormData()
data.append('endpoint', pushSubscription.endpoint)
data.append('userPublicKey',
key ? btoa( String.fromCharCode.apply(null, new Uint8Array(key)) ) : null),
data.append('userAuthToken',
token ? btoa(String.fromCharCode.apply(null, new Uint8Array(token))) : null)
let csrf_token = document.getElementsByName('csrf-token')[0].getAttribute('content');
// DB登録
fetch('/subscription', {
method: 'POST',
body: data,
headers: {
'X-CSRF-TOKEN': csrf_token
},
}).then(() => console.log('Subscription ended'))
}, function(error) {
console.log(error);
}
);
});
}
Pushサーバに通知を依頼します。
その際には先程DBに保存したエンドポイント、公開鍵、トークンを用います。
//Notification.php
// Push通知実行
public function execPushNotification()
{
// 環境変数から取得
$auth = [
'VAPID' => [
'subject' => env('APP_VAPID_SUBJECT');
'publicKey' => env('PWA_PUBLIC_KEY');
'privateKey' => env('PWA_PRIVATE_KEY');
]
];
// 認証する
$webPush = new WebPush($auth);
// 通知対象とぺーロードを設定
$notifications[] = [
'subscription' => Subscription::create([
'endpoint' => 'DBに保存したエンドポイント',
'publicKey' => 'DBに保存した公開鍵',
'authToken' => 'DBに保存したトークン'
]),
'payload' => '{
"body":"this is body",
"title":"this is title",
"url":"https://example.com",
"icon":"pwa_asset/logo.png"
}'
],
[
'subscription' => Subscription::create([
'endpoint' => 'DBに保存したエンドポイント',
'publicKey' => 'DBに保存した公開鍵',
'authToken' => 'DBに保存したトークン'
]),
'payload' => '{
"body":"this is body2",
"title":"this is title2",
"url":"https://example.com",
"icon":"pwa_asset/logo2.png"
}'
];
// キューに入れる
foreach ($notifications as $notification) {
$webPush->queueNotification(
$notification['subscription'],
$notification['payload']
);
}
// Push通知を依頼
foreach ($webPush->flush() as $report) {
$endpoint = $report->getRequest()->getUri()->__toString();
if ($report->isSuccess()) {
// sent successfully
}
}
}
以上です。