この文章について
PWAを構成する主要技術のひとつ、プッシュ通知(Push API)について調べたメモです。
合わせて、Firebase を利用したプッシュ通知の実装を行ってみました。
誤記などありましたらご指摘ください。
(2019年2月時点のメモとなります)
PWAの Service Worker についてはこちらの記事をご覧ください。
プッシュ通知について
Web技術を利用した通知用APIは
の2つがある。PWAの世界で「プッシュ通知」について説明する場合、後者の「Push API」を利用したプッシュ通知を指す場合が多い
Push APIを利用したプッシュ通知の仕組み
Google のドキュメントを元にまとめると
初期設定:
- サーバー側でプッシュ通知を行うためのアプリケーション・サーバーキー(VAPID)を準備する
プッシュ通知のサブスクリプション:
- エンドユーザー(Web訪問者、PWAユーザー)が通知の受信許可を行う
-
PushSubscription がエンドユーザーから送信
- PushSubscription には、ユーザーを個別に認識する情報が含まれる
- PushSubscriptionをサーバー側で保存する
- プッシュ通知を受け取るためのスクリプトをService Worker に登録し、通知の受信体制を作る
プッシュ通知の送信
- サーバーからプッシュ通知を送信
- PushSubscription に基づき、通知登録をしたユーザーへ個別に通知が送られる
- ユーザーがオンラインになったとき、Service Worker を通じてデリバーされた通知をデバイス上に表示する
Firebase Cloud Messaging (FCM)
Firebase には Firebase Cloud Messaging (FCM)というプッシュ通知機能がある。FCMを利用することで、PWAに対してプッシュ通知を送ることができる。
プッシュ通知を送信するサーバーと、プッシュ通知を届けるアプリケーションは別個となる。
(Firebase のプッシュ通知サービス、Firebase Cloud Messaging は両者が一体となっているため把握しづらいが、プッシュ通知のデリバー用サーバーとアプリは別物となる)
ネット上には、PWAのプッシュ通知に Firebase を利用しているケースが多いが、Firebase を使わなくともプッシュ通知の実装は行える。
Googleのコードラボでは、プッシュ通知用のテストサーバーを利用したチュートリアルが公開されている。
Firebase を利用したプッシュ通知の実装
FCMと Firebase JavaScript SDK を利用したプッシュ通知を実装してみる。
大まかな仕様
- プッシュ通知を受け取るだけのアプリ
- ユーザーが「購読」ボタンを押すことで、プッシュ通知のサブスクリプションを行う。
- エンドポイントへPOSTを行うアプリを別途phpで開発、通知を行いときに実行を行う
Firebase の設定
Firebase で新しいプロジェクトを作成し、「プロジェクトの設定」をクリック
Firebase の「クラウドメッセージング」タブから、サーバーキーと ウェブプッシュ証明書の鍵ペア(VAPIDKey) を取得
コード
index.html
<!DOCTYPE HTML>
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>プッシュ通知の購読</title>
<link rel="manifest" href="./manifest.json">
<link rel="stylesheet" type="text/css" href="./bootstrap.min.css">
<script src="https://www.gstatic.com/firebasejs/5.5.2/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.5.2/firebase-messaging.js"></script>
</head>
<body>
<div class="col-sm-3"></div>
<div class="col-sm-6">
<h1 class="text-center">プッシュ通知の購読</h1>
<h2 id="newitem" class="text-center"></h2>
<button id="button" onclick="getSubscription()" class="btn center-block"></button>
</div>
<div class="col-sm-3"></div>
<script>
// Firebase のSDKを利用し、SenderIDを設定して初期化
const config = {
messagingSenderId: "SenderIDを記述"
};
firebase.initializeApp(config);
const messaging = firebase.messaging();
messaging.usePublicVapidKey("ウェブプッシュ証明書キーを記述");
// Service Worker ファイルを登録し、ボタン表示を行う
registSW();
initialButton();
function initialButton() {
messaging.getToken().then(token => {
if (token) {
document.getElementById("button").innerText = "プッシュ通知を購読中";
} else {
document.getElementById("button").innerText = "プッシュ通知を受け取る";
}
}).catch(function (err) {
console.log('An error occurred while retrieving token. ', err);
});
}
// トークンが未取得の場合 = プッシュ通知を未購読の場合、プッシュ通知の登録許可を行う
// すでに購読済みの場合、取得済みのFirebase用トークンを表示
function getSubscription() {
messaging.getToken().then(token => {
if (!token) {
getNotification();
} else {
displayToken();
}
}).catch(function (err) {
console.log('An error occurred while retrieving token. ', err);
});
}
// Firebase のSDKを使い、プッシュ通知の購読処理を行う
function getNotification() {
messaging.requestPermission().then(function () {
console.log('Notification permission granted.');
displayToken();
initialButton();
}).catch(function (err) {
console.log('Unable to get permission to notify.', err);
});
}
// トークン表示
function displayToken() {
messaging.getToken().then(token => {
if (token) {
console.log(token);
} else {
console.log('No Instance ID token available. Request permission to generate one.');
}
}).catch(function (err) {
console.log('An error occurred while retrieving token. ', err);
});
}
// Service Worker ファイルを登録
function registSW() {
// Service Worker ファイル「firebaes-messaging-sw.js」を登録する
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('/firebase-messaging-sw.js')
.then(function (registration) {
console.log('firebase-messaging-sw.js registration successful with scope: ', registration.scope);
}, function (err) {
console.log('firebase-messaging-sw.js registration failed: ', err);
});
});
}
}
</script>
</body>
</html>
manifest.json
Firebase のFCMを利用する場合、gcm_sender_idを記述する必要がある。
JSON内の「gcm_sender_id」は、103953800507で固定で、変更できない。
{
"short_name": "Firebase のプッシュ通知購読アプリ",
"name": "Firebaseのプッシュ通知購読アプリ",
"start_url": "index.html",
"gcm_sender_id": "103953800507"
}
FCMを利用して Service Worker を登録する場合、プッシュ通知を受信するためのサービスワーカーは、ファイル名を必ず「firebase-messaging-sw.js」としなければいけない。また、必ずドメイン直下のルートディレクトリに配置しなければいけない。
firebase-messaging-sw.js内でSDKを利用するため、SDKをインポートし、初期化処理を行う必要がある。
firebase-messaging-sw.js
importScripts('https://www.gstatic.com/firebasejs/5.5.2/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/5.5.2/firebase-messaging.js');
firebase.initializeApp({
'messagingSenderId': 'SenderIDを記述'
});
const messaging = firebase.messaging();
messaging.setBackgroundMessageHandler(function (payload) {
console.log('[firebase-messaging-sw.js] Received background message ', payload);
// Customize notification here
let notificationTitle = 'Background Message Title';
let notificationOptions = {
body: 'Background Message body.',
};
return self.registration.showNotification(notificationTitle,
notificationOptions);
});
通知用のアプリ
プッシュ通知を送るための、phpコード例。
send.php
<?php
$json = '{
"notification":
{
"title": "テスト送信",
"body": "This Is 送信テストです"
},
"to": "index.htmlで取得したトークンを記述"}';
$ch = curl_init();
$headers = array(
'Content-Type: application/json',
'Authorization: key=Firebaseの秘密キーを記述'
);
curl_setopt_array($ch, array(
CURLOPT_URL => 'https://fcm.googleapis.com/fcm/send',
CURLOPT_HTTPHEADER => $headers,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => $json
));
$response = curl_exec($ch);
curl_close($ch);
?>
phpを実行すると、Firebase の Service Worker がメッセージを取得し、通知表示のを行う
参考情報
MDN
MDNのプッシュ通知に関するリファレンス
https://developer.mozilla.org/ja/docs/Web/API/Push_API
Firebase Cloud messaging のリファレンス
https://firebase.google.com/docs/reference/js/firebase.messaging?hl=ja
https://firebase.google.com/docs/reference/js/firebase.messaging.Messaging?hl=ja
Qiitaの記事
https://qiita.com/rubytomato@github/items/1c07a109f580c138bce7
https://qiita.com/ryo_hisano/items/1171beca22d5a04ed802
https://qiita.com/tomoyukilabs/items/2ae4a0f708a1af75f13e
購読解除はまだ手探り状態?
購読したプッシュ通知を解除する方法はないかと調べたところ、「unsubscribe」というメソッドが見つかった。
https://developer.mozilla.org/ja/docs/Web/API/PushSubscription/unsubscribe
しかし、対応ブラウザがまだ少なく、Chromeでも挙動が安定しなかった。
まだ使える環境が整っていない感じ。
数年後には標準的なメソッドになっているかもしれない。
実装あれこれ
今回の実装では、トークンを取得後、一回ずつphpを実行しているが、実際には
・プッシュ通知を購読したユーザーのトークンをサーバー側へ送信
・サーバー側のDBにトークンを登録
・DBの登録に対して、プッシュ通知を行うループ処理を実行
という流れになりそうです。
FCM用の Service Worker ファイルと、アプリ側のService Worker ファイルを分けて、2つの Service Worker を別個に登録することもできる。
以下のような感じ
function registSW() {
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('./sw.js', { scope: './' })
.then(function (registration) {
console.log('SW.js registration successful with scope: ', registration.scope);
}, function (err) {
console.log('Sw.js registration failed: ', err);
});
});
}
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('/firebase-messaging-sw.js')
.then(function (registration) {
console.log('firebase-messaging-sw.js registration successful with scope: ', registration.scope);
}, function (err) {
console.log('firebase-messaging-sw.js registration failed: ', err);
});
});
}
}
必要以上に Service Worker ファイルを分割すると、管理が大変そうなので、気をつける必要がありそう。