Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

この文章について

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

TakeshiNickOsanai
Developer Relation Manager, Sales Engineer/PreSales, User Community Supporter, CMS Engineer, AWS Certified Solutions Architect
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした