• 虹色ミツバチ
  • freoカスタマイズメモ、テンプレート・プラグイン配布/officeTIPS
検索プラグイン
虹色ミツバチ

> Entry >Other>CSS / jQuery / javascript> PWAでプッシュ通知を実装してみる(3)Firebaseでプッシュ通知に必要なトークンを処理する

【Other】【CSS / jQuery / javascript】PWAでプッシュ通知を実装してみる(3)Firebaseでプッシュ通知に必要なトークンを処理する

PWAでプッシュ通知を実装してみる(3)Firebaseでプッシュ通知に必要なトークンを処理する

前回までの記事で、PWA化にはmagnifest.jsonとserviceworker.jsが必要と解説し、そのサンプルを掲載しました。
今回からは目標だったプッシュ通知について解説します。

プッシュ通知の仕組みとFirebaseを利用することにした経緯

PWAを使ったプッシュ通知では、ブラウザで「プッシュ通知を受け取る」設定をした時に、「トークン」が発行されます。
これはブラウザ単位で発行されるので、PCのGoogle ChromeとスマホのAndroidでは別々のトークンが発行されます。

この「トークン」に対して、PWAを利用したサイト(ウェブアプリ)の管理者がサーバーからCurlコマンドを叩くことでプッシュ通知を送信します。
すなわち、「トークン」をどこかに保存しておく必要がありそうです。

トークンを取得したとしても、プッシュ通知を1件1件送っていくのは時間がかかりそうな気がします。
このサイトのような個人ブログであれば問題はないかもしれませんが、アクセスが多くプッシュ通知の購読者数も多いようなサイトの場合、受信者のタイムラグを減らす意味でも、複数人に一度にプッシュ通知を送信できる仕組みがほしいと感じます。

よって、今回はGoogle Firebase Cloud Messaging(FCM)を使用することにしました。
FCMでは、トークンをトピックというグループに関連付け、そのグループに対してプッシュ通知を送ることで、複数人に対し一度にプッシュ通知を送ることができます。
トークンの取得・トピックへの紐付け・プッシュ通知の受信・送信を、FCMと同じくFirebaseの簡易型データベースであるCloud Firestoreで実装します。

ワンポイント

トークンだけでなく、端末のUAや名前・メールアドレスなど、トークン以外のユーザー情報を取得しているサイトの場合は、データベースのユーザーテーブルにトークンのフィールドを増やすなり、プッシュ通知用のテーブルにユーザー情報を紐付けるなりしてください。
今回は通知を購読すると設定した全てのユーザー向けに通知を送ることを想定しています。
端末ごとに通知を送りたい、年齢ごとに送りたいなど、細かな設定が必要な場合はトークン以外のユーザー情報を取得して保存する別の仕組みが必要になるでしょう。

今回実装したプッシュ通知の概要

一番最初の記事で書いていますが、今回実装したプッシュ通知は、

  • アプリが起動されている状態で
  • ブラウザでプッシュ通知が許可されていて
  • プッシュ通知を受け取る設定になっている時

に受け取ることができるものです。

PWA化したサイトはPC・スマホにインストールすることが可能ですが、アプリを起動していない時にプッシュ通知を受信することはできません
起動していない時にまでプッシュ通知を受け取るのはそれはそれでうざったいという判断もできると思うので、現状は上記の状況で満足しています。

なお、アプリは起動してあるけれど別のアプリを使用している(アプリがバッググラウンドにある)場合は受信可能です。

プッシュ通知実装のためにFirebaseを利用する

プッシュ通知の受信・発信のために、今回は Google Firebase を利用しました。
Firebaseはウェブアプリ・ネイティブアプリのバックグラウンドで行われる様々な機能を担うサービスです。
プッシュ通知の送受信・SNSアカウントとの連携・データベースなど、多種多様な機能を利用することができます。

Firebase にプロジェクトを作成する

Firebaseの各種機能を利用するには、まずはFirebaseにログインし、プロジェクトを作成します。
Gmailアドレスが必要です。

  1. Gmailにログインした状態でFirebaseのコンソールにアクセスする。
  2. 「使ってみる」をクリックする
    20190730-01-01.jpg
  3. 「プロジェクトを作成」をクリックする
    20190730-01-02.jpg
  4. プロジェクト名などを入力して「プロジェクトを作成」をクリックする
    20190730-01-03.jpg
  5. プロジェクトが作成できたら「次へ」をクリックする
    20190730-01-04.jpg
  6. プロジェクト作成完了です。お疲れさまでした。
    20190730-01-05.jpg
Firebase のプロジェクトにアプリを設定する

Firebaseでプロジェクトを作成したら、WEBアプリ(サイト)を設定します。

  1. Firebaseコンソールで「</>」マークをクリックしてWEBアプリを登録します。
    20190730-02-01.jpg
  2. サイト名を入力し、「アプリを登録」をクリックする
    20190730-02-02.jpg
  3. サイトに埋め込むためのFirebase SDKが発行されるのでメモしておく。
    なお、このソースは後から編集するので今すぐ<body>タグ下部に埋め込まなくてOK。
    また、もしメモに失敗しても必要な情報はコンソールから確認することもできる。
    20190730-02-03.jpg
    メモが済んだら「コンソールに進む」をクリックする。
  4. コンソールの左側メニュー「Project Overview」の右にある歯車アイコンをクリックし、「プロジェクトの設定」を選択する。
    20190730-02-04.jpg
  5. 「プロジェクトの設定」の「全般」タブでアプリ情報を確認できる。
    また、同画面下部に(3)でメモしたFirebase SDKも随時確認できる。
    20190730-02-05.jpg

VAPID鍵を設定する

FCMでは「Voluntary Application Server Identification 鍵(VAPID 鍵)」と呼ばれるウェブ認証情報を利用して、ウェブプッシュ サービスへの送信要求が承認されます。
プッシュ通知を利用するには、VAPID鍵ペアをFirebaseプロジェクトに関連付ける必要があります。

  1. Firebaseコンソールからプロジェクトの設定→[クラウドメッセージング]をクリックし、ウェブ設定で「鍵ペアを生成」をクリックする。
    20190730-03-01.jpg
  2. 鍵が生成されるので、今後説明するfirebase.js内でCloud Messaging 利用時に鍵を利用する。
    20190730-03-02.jpg

Firestoreでusersテーブルを作成する

Cloud Firestore は Firebase で使用できる簡易的なデータベースです。
ここではトークンと購読状況を保存するだけの極シンプルなテーブルの作成方法を記載します。

  1. Firebaseコンソールから「開発」→「Database」をクリックする
    20190730-05-01.jpg
  2. 「データベースの作成」をクリックする
    20190730-05-02.jpg
  3. データベース作成用ダイアログが表示されるので、「テストモードで開始」を選択して「次へ」
    20190730-05-03.jpg
  4. ロケーションを設定する。多分どこでもいいとは思うけどなんとなく[asia-east2]を選んでみた。
    20190730-05-04.jpg
  5. 作成されたデータベースが表示される(少々時間がかかるのでおとなしく待つ)
    「コレクションを開始」をクリックする
    20190730-05-06.jpg
  6. コレクション作成ダイアログが表示される
    今回は[users]という名前のコレクションにデータを入れていくので、[users]と入力して「次へ」をクリックする
    20190730-05-07.jpg
  7. [users]コレクションに最初に入れるデータを挿入できる
    ドキュメントID部分で「自動ID」をクリックする
    20190730-05-08.jpg
  8. 自動に採番されたIDがドキュメントID部分に挿入される
    [subscribe]フィールドを[boolean]タイプで「true」に、[token]フィールドを[string]タイプで[適当になんか入れて]作成する
    20190730-05-09.jpg
  9. 挿入したデータが登録されていることを確認する
    20190730-05-10.jpg

Firestoreは、簡易型データベースです。
今回は[users]というテーブル(コレクション)に自動付与されたIDをキー(ドキュメント)にして[subscribe:true, token:任意の文字列]という配列を入れてみました。

このようにコンソールから直接登録値を挿入・編集・削除できますし、サイト(Webアプリ)側から内容を参照・編集することもできます。

参考サイト

Firebaseドキュメント:Cloud Firestore
Firebaseドキュメント:Cloud Firestore データモデル

 

では、実際にサイトでFirebaseを利用する準備をしてみます。

headタグにFirebase SDKを挿入する

サイトでFirebaseを利用するために、headタグにFirebase SDKを挿入します。
Firebaseコンソールでは基本的なスニペットしか取得できませんが、Firebaseの各種機能を使うために、それぞれのライブラリを追加で読み込む必要があります。

例えば、今回はCloud Messaging と Firebase Firestore を利用するので、Cloud Messaging と Firebase Firestore用の ライブラリを追加します。

そのため、headタグには

<script defer src="https://www.gstatic.com/firebasejs/6.2.0/firebase-app.js"></script>
<script defer src="https://www.gstatic.com/firebasejs/6.2.0/firebase-messaging.js"></script>
<script defer src="https://www.gstatic.com/firebasejs/6.2.0/firebase-firestore.js"></script>
<script defer src="/firebase.js"></script>

のように記載します。

firebase-app.jsがfirebaseを利用するための基本ライブラリです。
firebase-messaging.jsとfirebase-firestore.jsはCloud MessagingとFirebase Firestore用のライブラリです。
個人的にheadタグに色々書き加えていくのが嫌なタイプなので、firebaseを利用する部分の記述はfirebase.jsにまとめ、それを読み込む形にします。

firebase.jsの中身はこんな感じです。

// Firebase設定
var firebaseConfig = {
    apiKey: "YOUR-API-KEY",
    authDomain: "project-XXXXXXXXXX.firebaseapp.com",
    databaseURL: "https://project-XXXXXXXXXX.firebaseio.com",
    projectId: "project-XXXXXXXXXX",
    storageBucket: "project-XXXXXXXXXX.appspot.com",
    messagingSenderId: "YOUR-MESSAGING-SENDER-ID",
    appId: "YOUR-API-ID"
};
firebase.initializeApp(firebaseConfig);

// FCM使用準備
const messaging = firebase.messaging();
messaging.usePublicVapidKey('XXXXXXXXXXXXXXXXXXXXXXXXXX');// VAPIDを設定

// Firestore使用準備
var db = firebase.firestore();
var usersRef = db.collection("users");

メモしたFirebase SDKまたはFirebaseコンソールのプロジェクトの設定→全般タブにFirebase SDKスニペットが記載されているので、そこからconfig設定部分を記載しています。
前回の記事で解説したPWA用のmagnifest.jsonではgcm_sender_idをCloud Messagingを利用するための固定値を挿入しましたが、firebase.jsの[messagingSenderId]はプロジェクト固有のIDを挿入してください。

FCM及びFirestoreを使用するための記載を加えています。
VAPID部分は自分で取得したVAPIDに変更してください。

参考URL

Firebaseドキュメント:FirebaseをJavaScriptプロジェクトに追加する

プッシュ通知購読処理のための領域をサイト内に用意する

当サイトのプッシュ通知は、「プッシュ通知を受信する」とブラウザで設定すれば即購読開始となるものではなく、プッシュ通知購読処理及び購読状況の表示領域をサイト内に用意しており、そのブロックで購読開始・購読終了の手続きを行うことができるようにしました。
プッシュ通知の購読は、「購読する」ボタンの押下をもって行い、プッシュ通知購読の終了は「購読終了する」ボタンの押下をもって行うものとします。

bodyタグ内に例えば下記のように記載してください。

<div id="notification">
    <h3>NOTIFICATION</h3>
    <p>当サイトの更新をプッシュ通知で受け取ることができます。</p>
    <p class="notice"></p>
    <button id="EntryButton" onclick="getSubscription()">プッシュ通知を受け取る</button>
    <button id="RemoveButton" onclick="removeSubscription()">プッシュ通知を解除する</button>
</div>

p.noticeは空でOKです。
プッシュ通知を受け取るボタン・解除するボタンが押下された時、処理結果を表示するための段落です。

getSubscription()は購読処理、removeSubscription()は購読解除処理ですが、こちらの内容は今後firebase.jsに記載していきます。

当サイトでは下画像のようにサブメニューにプッシュ通知用のブロックを設置しています。

20190730-04-01.jpg

プッシュ通知の購読開始・購読解除処理

ここまでの準備が整ったら、プッシュ通知の購読開始・購読解除を行えるようにjsに処理を書き込みます。
firebase.jsに下記のように加筆してください。

// 購読確認を行う
checkSubscription();

// 購読確認処理
function checkSubscription() {
    //通知の承認を確認
    messaging.requestPermission().then(function() {
        //トークンを確認
        messaging.getToken().then(function(token) {
            //トークン発行
            if (token) {
                //トークンがDBに入っているか確認
                usersRef.where('token', '==', token).get().then(function(oldLog){
                    if(oldLog.empty){
                    //入っていなければ購読ボタン表示
                        console.log('トークンは登録されていません。');
                        $('#notification p.caution').text('通知を購読していません。');
                        ShowEntryButton();
                    } else {
                    //入っていれば購読状況確認
                        console.log('トークンはすでに登録されています。');
                        oldLog.forEach(function(doc){
                            var data = doc.data();
                            if(data.subscribe == true){
                            //購読している(=停止ボタン表示)
                                $('#notification p.caution').text('通知を購読しています。');
                                ShowRemoveButton();
                            } else {
                            //購読していない(=開始ボタン表示)
                                $('#notification p.caution').text('購読を解除しました。');
                                ShowEntryButton();
                            }
                        });
                    }
                });
            } else {
                    console.log('通知の承認が得られませんでした。');
                    $('#notification p.caution').text('購読を開始できませんでした。');
                    ShowEntryButton();
            }
        }).catch(function(err) {
                console.log('トークンを取得できませんでした。', err);
                $('#notification p.caution').text('購読を開始できませんでした。');
                ShowEntryButton();
        });
    }).catch(function (err) {
        //プッシュ通知未対応
            console.log('通知の承認が得られませんでした。', err);
            $('#notification p.caution').text('プッシュ通知が許可されていません。ブラウザの設定を確認してください。');
            ShowEntryButton();
    });
}

// 購読処理
function getSubscription() {
    //通知の承認を確認
  messaging.requestPermission().then(function() {
        //トークンを確認
        messaging.getToken().then(function(token) {
            //トークン発行
            if (token) {
                //トークンがDBに入っているか確認
                usersRef.where('token', '==', token).get().then(function(oldLog){
                    if(oldLog.empty){
                    //トークン登録がなければトークン登録・購読設定
                        usersRef.add({
                            token: token,
                            subscribe: true
                        });
                        console.log('トークン新規登録しました。');
                    } else {
                    //トークン登録があれば購読に設定変更
                        oldLog.forEach(function(doc){
                            console.log('トークンはすでに登録されています。');
                            usersRef.doc(doc.id).update({
                                subscribe: true
                            })
                        });
                    }
                    //購読解除ボタン表示
                    ShowRemoveButton();
                });
                //購読状況表示更新
                $('#notification p.caution').text('通知を購読しています。');
            } else {
                console.log('通知の承認が得られませんでした。');
                $('#notification p.caution').text('購読を開始できませんでした。');
                ShowEntryButton();
            }
        }).catch(function(err) {
            console.log('トークンを取得できませんでした。', err);
            $('#notification p.caution').text('購読を開始できませんでした。');
            ShowEntryButton();
        });
  }).catch(function (err) {
        console.log('通知の承認が得られませんでした。', err);
        $('#notification p.caution').text('プッシュ通知が許可されていません。ブラウザの設定を確認してください。');
        ShowEntryButton();
    });
}

// 購読解除処理
function removeSubscription() {
    //通知の承認を確認
    messaging.requestPermission().then(function() {
        //トークンを確認
        messaging.getToken().then(function(token) {
            //トークン発行
            if (token) {
                //トークンがDBに入っているか確認
                usersRef.where('token', '==', token).get().then(function(oldLog){
                    if(oldLog.empty){
                    //トークン登録がなければ購読ボタン表示
                        console.log('トークンは登録されていません。');
                        ShowEntryButton();
                    } else {
                    //トークン登録があれば購読解除を行う
                        oldLog.forEach(function(doc){
                            usersRef.doc(doc.id).update({
                                subscribe: false
                            })
                            .then(function() {
                                console.log("購読を解除しました。");
                                ShowEntryButton();
                            }).catch(function(error) {
                                console.error("Error removing document: ", error);
                            });
                        });
                    }
                });
                //購読状況表示更新
                $('#notification p.caution').text('購読を解除しました。');
            } else {
                console.log('トークンを取得できませんでした。');
                $('#notification p.caution').text('購読を開始できませんでした。');
            }
        }).catch(function(err) {
            console.log('トークンを取得できませんでした。', err);
            $('#notification p.caution').text('購読を開始できませんでした。');
        });
    }).catch(function (err) {
        console.log('通知の承認が得られませんでした。', err);
        $('#notification p.caution').text('プッシュ通知が許可されていません。ブラウザの設定を確認してください。');
        ShowEntryButton();
    });
}

// トークン表示
function displayToken() {
  messaging.getToken().then(token => {
        if (token) {
            console.log(token);
        } else {
            console.log('トークンを取得できませんでした。');
        }
  }).catch(function (err) {
    console.log('トークンの取得時にエラーが発生しました。', err);
  });
}


//購読ボタン表示
function ShowEntryButton() {
    $('#EntryButton').show();
    $('#RemoveButton').hide();
}

//購読取消ボタン表示
function ShowRemoveButton() {
    $('#EntryButton').hide();
    $('#RemoveButton').show();
}

画面表示時にcheckSubscription()を行って、新規訪問者がプッシュ通知の購読を行えるようにしています。
既存の訪問者(トークン発行済)の場合は、プッシュ通知購読状況表示領域の表示内容を更新します。

プッシュ通知購読状況表示領域のボタンが押下された時、getSubscription()で購読処理またはremoveSubscription()で購読解除処理を行います。

処理結果はconsole.logで適宜チェックできるようにしていますが、DBに登録されるトークンの内容は記載していません。
生成されたトークンとFirestoreに登録されるトークンの値が合致しているかを確認する時などのために、トークンを表示するための関数(displayToken())も入れているので、適宜使用してみてください。

参考URL:

Qiita:Progressive Web Apps (PWA) 学習者のメモ その2 (プッシュ通知とFCM)
Firebaseドキュメント:JavaScript Firebase Cloud Messaging クライアント アプリを設定する

今回のまとめ

PWAを使えばプッシュ通知を受信することができるようになりますが、実際に受信するためには様々な設定・処理が必要です。
Firebaseの登録は簡単ですが、やりたいことを実現するためにはドキュメントの読み込みが重要で、頭の悪い私は何度「日本語でおk」と呟いたかわかりません。

FCMだけでは不十分だったところを、Firestoreとの連携で割と理想的なフローに落ち着いて良かったと思います。
次回はプッシュ通知の受信・送信について解説していきたいと思います。

ページ移動

関連記事

ユーティリティ

Twitter

ページ上部へ