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

FCM + ServiceWorker でPUSH通知のメモ

More than 1 year has passed since last update.

背景

PUSH通知という言葉に惹かれ、独自に勉強を始めた。しかし、これがとても苦労したのでメモを残す。

前提

  • 正しい組み込み方ではな無いかもしれない。
  • とりあえず動いた!という喜びの勢いでメモ残している。
  • ServiceWorkerを利用するにはSSLでページにアクセスする必要があるので、その環境があること。

ディレクトリ構造

DIRECTORY構成
/(ROOT)
 ├ index.html
 ├ firebase-messaging-sw.js
 ├ app.js
 ├ manifest.json
 ├ script/
 │   └ firebase-init.js
 ├ images/
     ├ 144.png
     └ 192.png

手順

index.htmlを作成する

index.htmlはページを表示するとともに、必要なJavascriptを読み込むことが目的となる。今回の場合はfirebaseを利用する為、firebaseを利用する為に必要なJavascriptserviceworkerを利用する為の処理が書かれたJavascriptを読み込むことになる。

index.html
<!DOCTYPE html>
<html>

  <head>
    <meta charset="UTF-8">

    <!-- manifest.json を指定 -->
    <link rel="manifest" href="/manifest.json">

    <!-- firebaseを利用する為のJavascript -->
    <script defer src="https://www.gstatic.com/firebasejs/7.3.0/firebase-app.js"></script>
    <script defer src="https://www.gstatic.com/firebasejs/7.3.0/firebase-messaging.js"></script>
    <script defer src="/script/firebase-init.js"></script>

    <!-- ServiceWorkerを利用する為のJavascript -->
    <script defer src="app.js"></script>
  </head>

  <body>
    <h1>SAMPLE ServiceWorker - 00</h1>
  </body>

</html>

manifest.json

manifest.jsonは、本アプリの名称などを定義する役割がある。ファイル名自体に意味は無いようだが、json形式で定義する必要がある。定義する項目については割愛するが、FCMを利用したPUSH通知に必要な項目としてはgcm_sender_idがある。この値は103953800507でとりあえず固定らしい。何故かは知らないが、そのうち調べる。

manifest.json
{
    "name": "Service Worker Sample",
    "short_name": "sw sample",
    "icons": [
        {
            "src": "/images/144.png",
            "sizes": "144x144",
            "type": "image/png"
        },
        {
            "src": "/images/192.png",
            "sizes": "192x192",
            "type": "image/png"
        }
    ],
    "start_url": "/",
    "display": "standalone",
    "theme_color": "#e91e63",
    "gcm_sender_id": "103953800507",
}

今回は通知しか試さないため、iconsの項目は不要かも。

firebase-init.js

firebase-init.jsの名称に指定は無い。とりあえずFirebaseの初期化の意味を込めている。

script/firebase-init.js
  // firebaseConfigの内容は、firebaseのコンソールから取得出来る。
  var firebaseConfig = {
    apiKey: "[APIKEYAPIKEYAPIKEYAPIKEYAPIKEY]",
    authDomain: "[AUTHDOMAINAUTHDOMAINAUTHDOMAIN]",
    databaseURL: "[DATABASEURLDATABASEURLDATABASEURL]",
    projectId: "[PROJECTIDPROJECTIDPROJECTID]",
    storageBucket: "[STORAGEBUCKETSTORAGEBUCKETSTORAGEBUCKET]",
    messagingSenderId: "[MESSAGINGSENDERID]",
    appId: "[APPIDAPPIDAPPID]"
  };

  // Initialize Firebase
  firebase.initializeApp(firebaseConfig);

  const messaging = firebase.messaging();
  messaging.usePublicVapidKey('[PUBLICVAPIDKEYPUBLICVAPIDKEYPUBLICVAPIDKEYPUBLICVAPIDKEY]');


  messaging.onMessage( payload => {
    // WEBアプリがフォアグラウンドの状態で通知を受信するとonMessageが呼び出される。
    console.log("onMessage")


  })

messaging.onTokenRefresh(() => {
    //トークンが更新されるとonTokenRefreshが呼び出される。
    messaging.getToken().then((refreshedToken) => {
    console.log(refreshedToken)
    }).catch((err) => {
    console.log('Unable to retrieve refreshed token ', err);
    showToken('Unable to retrieve refreshed token ', err);
    });
});

理解出来ていない部分も多いがとりあえずこんな感じで動いた。firebasConfigの内容はfirebaseのコンソールから取得出来るのでコピペでよい。
ただし、コピペで得られる情報にはmessagingSenderIdが含まれていないので追加する必要がある。firebaseのコンソールより、クラウドメッセージングへ進むと送信者IDという項目があるので、その値を用いる。
更にmessaging.usePublicVapidKeyに与えるキーもfirebaseのコンソールからウェブプッシュ証明書を生成して取得する。

app.js

ServiceWorkerを登録する処理を行う。具体的には以下の点をおさえておけばよい。

  • ブラウザのServiceWorker対応状況
  • 通知許可をユーザに仰ぐ
  • 許可されたら、通知の購読(サブスクリプション)を登録する
app.js
if ('serviceWorker' in navigator) {
  window.addEventListener('load', function () {

    navigator.serviceWorker.register('/firebase-messaging-sw.js')
      .then(registration => {

        //通知の許可をユーザに確認
        Notification.requestPermission()
          .then(permission => {

            messaging.getToken().then(
              token => {
                console.log(token)
              })


            if (permission === 'granted') {
              //通知が許可されている場合
              console.log('granted!!!!!')
              navigator.serviceWorker.ready.then(p => {

                p.pushManager.getSubscription().then(subscription => {

                  if (subscription === null) {

                    //通知の購読が存在しない場合は登録する。
                    let re = p.pushManager.subscribe({
                      userVisibleOnly: true
                    })

                  }
                })

              })

            } else {
              //通知が許可されなかった場合  
              console.log(permission)
            }
          })
      })
  })
}

firebase-messaging-sw.js

firebase-messaging-sw.jsはバックグラウンドで動作するスクリプト。ブラウザに登録されてバックグラウンドで独立して動作するので、firebaseの利用に必要なJavascriptの読込処理などは全て記載する必要がある(という事なのかな)。

大切なのは以下の点。

  • PUSH通知を受信した際のイベントを登録する
  • PUSH通知をバックグラウンドで受信した際のイベントを登録する
firebase-messaging-sw.js
importScripts("https://www.gstatic.com/firebasejs/7.3.0/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/7.3.0/firebase-messaging.js");

// firebaseConfigの内容は、firebaseのコンソールから取得出来る。
var firebaseConfig = {
  apiKey: "[APIKEYAPIKEYAPIKEYAPIKEYAPIKEY]",
  authDomain: "[AUTHDOMAINAUTHDOMAINAUTHDOMAIN]",
  databaseURL: "[DATABASEURLDATABASEURLDATABASEURL]",
  projectId: "[PROJECTIDPROJECTIDPROJECTID]",
  storageBucket: "[STORAGEBUCKETSTORAGEBUCKETSTORAGEBUCKET]",
  messagingSenderId: "[MESSAGINGSENDERID]",
  appId: "[APPIDAPPIDAPPID]"
};

// Initialize Firebase
firebase.initializeApp(firebaseConfig);

const messaging = firebase.messaging();

// 通知を受けとると push イベントが呼び出される。
self.addEventListener('push', function (event) {

  console.log("event:push")
  let messageTitle = "MESSAGETITLE"
  let messageBody = "MESSAGEBODY"
  let messageTag = "MESSAGETAG"

  const notificationPromise = self.registration.showNotification(
    messageTitle,
    {
      body: messageBody,
      tag: messageTag
    });

  event.waitUntil(notificationPromise);

}, false)

// WEBアプリがバックグラウンドの場合にはsetBackGroundMessageHandlerが呼び出される。
messaging.setBackgroundMessageHandler(function (payload) {

  console.log("backgroundMessage")

  let messageTitle = "MESSAGETITLE"
  let messageBody = "MESSAGEBODY"

  return self.registration.showNotification(
    messageTitle,
    {
      body: messageBody,
      tag: messageTag
    });
});

self.registration.showNotificationを呼び出すことによって通知が作成される。引数にはタイトルとオプション(本文など)を渡す。

messageTitlemessageBodyは固定値にしているが、本来はeventpayloadから取得し、適切な情報にする。後述する「Firebaseのコンソールから通知の送信」「curlコマンドによる通知の送信」を試してみるとわかるが、タイトルや本文を入力するフィールドがある。

検証

Firebaseのコンソールから通知の送信

Firebaseのコンソールより、Cloud Messagingを開くと通知を作成することが出来るので、タイトルと本文を入力し、ターゲットのアプリを設定すると送信が実行できる。

curlコマンドによる通知の送信

curlを利用してFirebase経由で通知を送信する
curl -X POST \
--header "Authorization: key=[SERVERKEY] \
--header Content-Type:"application/json" \
https://fcm.googleapis.com/fcm/send \
-d @- << EOF
{
  "notification": {
    "title":"test",
    "body":"テストメッセージ"
    },
  "to": "[DEVICETOKEN]"
}
EOF
  • SERVERKEYはFirebaseのコンソールから取得する。
  • DEVICETOKENはapp.jsかfirebase-init.jsでトークンを取得した際にブラウザのコンソールに出力されるので、コピペする。

ブラウザ(Chrome)

デベロッパーツールを開き、ApplicationタブでServiceWorkerの状態を確認することが出来る。上手く動作しないときはUnregisterリンクを押し再度ページを読み込む。通知を受信するとConsoleタブに"event:push","onMessage","backgroundMessage"のいずれかが出力される。

正しく動作していれば、ブラウザを最小化したり、ページタブを閉じていても通知ダイアログが表示される。

その他

正しく理解していないがとりあえず通知を表示することが出来たのでメモとして残した。その他、気になったことや苦戦した点をココに残しておく。

messaging.setBackgroundMessageHandlerは、ほとんど呼び出されることがなかった。

これはFirebaseのドキュメントにも記載がある。送信されてきた情報に通知フィールド(notification)が存在する場合はsetBackgroundMessageHandlerが呼び出されないらしい。前述した「curlコマンドによる通知の送信」に手を加え、notificationをdataに書き換えてみると、setBackgroundMessageHandlerが呼び出された。

curlを利用してFirebase経由で通知を送信する(notification項目無し)
curl -X POST \
--header "Authorization: key=[SERVERKEY] \
--header Content-Type:"application/json" \
https://fcm.googleapis.com/fcm/send \
-d @- << EOF
{
  "data": {
    "title":"test",
    "body":"テストメッセージ"
    },
  "to": "[DEVICETOKEN]"
}
EOF

通知ダイアログが表示されるのはWEBアプリがフォアグラウンド状態ではない時?

動作確認には苦労した。そもそも、何が正しい動作なのかを理解していなかった。通知ダイアログが表示されるのはWEBアプリがフォアグラウンド状態ではない時だけのようだった(…もしかしたら、この理解も間違っているかもしれない)。そして、どの状態がフォアグラウンドでどの状態がそうでないのかよくわからなかったので、「ウィンドウの最小化」や「タブを閉じる」とか、明らかにフォアグラウンドでない状態にするようにした。

niibori
フリーランスプログラマです。プログラマとして活動しながら、研修の講師なども行います。新しいモノはとりあえず触ってみたい。動かしてみたい。
https://www.n5-creation.com/
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