Edited at

Progressive Web Apps (PWA) 学習者のメモ その2 (プッシュ通知とFCM)


この文章について

PWAを構成する主要技術のひとつ、プッシュ通知(Push API)について調べたメモです。

合わせて、Firebase を利用したプッシュ通知の実装を行ってみました。

誤記などありましたらご指摘ください。

(2019年2月時点のメモとなります)

PWAの Service Worker についてはこちらの記事をご覧ください。


プッシュ通知について

Web技術を利用した通知用APIは

の2つがある。PWAの世界で「プッシュ通知」について説明する場合、後者の「Push API」を利用したプッシュ通知を指す場合が多い


Push APIを利用したプッシュ通知の仕組み

Google のドキュメントを元にまとめると

初期設定:


  1. サーバー側でプッシュ通知を行うためのアプリケーション・サーバーキー(VAPID)を準備する

プッシュ通知のサブスクリプション:


  1. エンドユーザー(Web訪問者、PWAユーザー)が通知の受信許可を行う

  2. PushSubscription がエンドユーザーから送信


    • PushSubscription には、ユーザーを個別に認識する情報が含まれる



  3. PushSubscriptionをサーバー側で保存する

  4. プッシュ通知を受け取るためのスクリプトをService Worker に登録し、通知の受信体制を作る

プッシュ通知の送信


  1. サーバーからプッシュ通知を送信

  2. PushSubscription に基づき、通知登録をしたユーザーへ個別に通知が送られる

  3. ユーザーがオンラインになったとき、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で開発、通知を行いときに実行を行う


サンプル

実際に動くサンプルはこちら

https://fbpush-feb60.firebaseapp.com/


Firebase の設定

Firebase で新しいプロジェクトを作成し、「プロジェクトの設定」をクリック

fb_0.png

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 がメッセージを取得し、通知表示のを行う

fb_3.png


参考情報


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 ファイルを分割すると、管理が大変そうなので、気をつける必要がありそう。