FCMでWeb Push。Firebase Javascript SDKを使ったプッシュ通知とトピック送信を試した。

FirebaseのFirebase Cloud Messaging(FCM)を試している。今回のエントリではFCMのJavaScriptライブラリを使ってブラウザにプッシュ通知やトピックにメッセージを送信する方法をまとめていく。

FCMではトピック端末グループへのメッセージングなどの機能が利用できる。これらの機能をPush APIをサポートしているブラウザにも同様に利用することができる。

developers-jp.googleblog.com

ここからはFCMのJavaScriptライブラリの使い方とサーバからメッセージを送信する方法などクライアントとサーバに分けてまとめていく。

クライアント

クライアントではJasvaScript SDKを通してトークンを取得する。そのトークンをサーバに送りストレージに保持させる。
サーバではトークンを用いてFCMと連携を行い通知リクエストを送信することになる。

必要なSDK

必要なJavaScript SDKは次の2つである。

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

またfirebaseアプリを定義するために次のコードで初期設定を完了させる。

var config = {
    messagingSenderId: fcmSenderId // Firebaseのコンソールで確認できる送信者IDを設定する
};
firebase.initializeApp(config);

manifest.json

manifest.jsongcm_sender_idを定義する。このIDは固定で103953800507とする。

{
  "gcm_sender_id": "103953800507"
}

serviceworker.js

ブラウザに通知を表示させるためにserviceworker.jsで表示処理を行うがSDKを読み込んだ場合はserviceworker.jsのファイル名が固定となる。
ファイル名はfirebase-messaging-sw.jsとする。firebase-messaging-sw.jsに通知表示処理のコードを記述する。

トークンの取得

SDKを使いプッシュ通知に必要なトークンを取得する。
取得したトークンを用いて特定の端末へのメッセージ送信やトピック送信を行う。

let messaging = firebase.messaging();

messaging.getToken()
    .then(function(currentToken) {
        // 取得したトークンをサーバへ送る。サーバ側でユーザIDとトークンを連携させDBなどのストレージに保持する。
    })
    .catch(function(err) {
        console.log('An error occurred while retrieving token. ', err);
    });

トークンの取得の前にブラウザで通知を購読させたい場合は次のように処理をする。

messaging.requestPermission()
    .then(function() {
        // 通知購読が成功した場合、`messaging.getToken()` でトークンを取得しサーバAPIと連携させる。
    })
    .catch(function(err) {
        console.log('Unable to get permission to notify.', err);
    });

サーバ

サーバではクライアントから送信されたトークンをストレージに保持したりFCMと連携して通知リクエストを送信する。

特定の端末へのメッセージ送信

fun send() {
    val to = "token"
    val payload = Payload(title, body, tag, icon, clickaction)
    val content = objectMapper().writeValueAsString(FcmRequest(to, payload, CustomPayload("custom")))
    val body = okhttp3.RequestBody.create(MediaType.parse("application/json"), content)

    val request = okhttp3.Request.Builder()
            .url("https://fcm.googleapis.com/fcm/send")
            .header("Authorization", "key=%s".format(appProperties.serverKey))
            .header("Content-Type", "application/json")
            .post(body)
            .build()
    val client = OkHttpClient.Builder().build()
    val response = client.newCall(request).execute()

    response.body().use { body ->
        val responseBody = body.string()
        log.info("body:%s".format(content))
        log.info("response:%s".format(responseBody))
    }
}
 
 data class FcmRequest(
            val to: String,
            val notification: Payload,
            val data: CustomPayload)

data class CustomPayload(
            val topic: String?)

curlで表すと次のようになる

curl -X POST \
-H "Authorization: key={Firebaseのコンソールで確認できるサーバーキー}" \
-H "Content-Type: application/json" \
-d '{
  "notification": {
    "title": "Portugal vs. Denmark",
    "body": "5 to 1",
    "icon": "/image/ic_alarm_black_48dp_2x.png",
    "click_action": "http://localhost"
  },
  "data": {
    "score": "3x1"
  },
  "to": "{クライアントで取得したトークン}"
}'  \
"https://fcm.googleapis.com/fcm/send"

HTTP メッセージ(JSON)の詳細

FCMへ通知をリクエストするHTTP メッセージ(JSON)の詳細は次のページから参照できる。

トピックにメッセージを送信する

トピックにメッセージを送信するには取得したトークンをトピックに登録する必要がある。

トークンをトピックに登録する

fun postTopic() {
    val body = okhttp3.RequestBody.create(MediaType.parse("application/json"), "{}")
    val request = okhttp3.Request.Builder()
            .url("%s/%s/rel%s".format("https://iid.googleapis.com/iid/v1", "token", "/topics/movies"))
            .header("Authorization", "key=%s".format(appProperties.serverKey))
            .header("Content-Type", "application/json")
            .header("Content-Length", "0")
            .post(body)
            .build()
        val client = OkHttpClient.Builder().build()
        client.newCall(request).execute()
}

curlで表すと次のようになる

curl -X POST \
-H "Authorization: key={Firebaseのコンソールで確認できるサーバーキー}" \
-H "Content-Type: application/json" \
-H "Content-Length: 0" \
"https://iid.googleapis.com/iid/v1/{クライアントで取得したトークン}/rel/topics/movies"

トピックにメッセージを送信

fun send() {
    val to = "/topics/movies"
    val payload = Payload(title, body, tag, icon, clickaction)
    val content = objectMapper().writeValueAsString(FcmRequest(to, payload, CustomPayload("custom")))
    val body = okhttp3.RequestBody.create(MediaType.parse("application/json"), content)

    val request = okhttp3.Request.Builder()
            .url("https://fcm.googleapis.com/fcm/send")
            .header("Authorization", "key=%s".format(appProperties.serverKey))
            .header("Content-Type", "application/json")
            .post(body)
            .build()
    val client = OkHttpClient.Builder().build()
    val response = client.newCall(request).execute()

    response.body().use { body ->
        val responseBody = body.string()
        log.info("body:%s".format(content))
        log.info("response:%s".format(responseBody))
    }
}

特定の端末へのメッセージ送信のコードのうちtoの変数が/topics/moviesに変更されている。それ以外は変更なしである。

ペイロードの暗号化は?

ペイロードの暗号化はFCM APIを使えばサポートしてくれる。クライアントでトークン取得時にブラウザの公開鍵(p256dh)乱数(auth)SDKを通してfcm.googleapis.comにPostされている。トークンはkeyとauthを用いている生成されているためクライアント側もサーバ側も暗号化を意識することなくWeb Pushを利用することができる。

まとめ

  • FCMのJavascript SDKを使ったプッシュ通知とトピック送信を試してみた。トピック送信は非常に強力な機能である。この機能をWeb Pushでも使うことで通知運用の幅も広がる。
  • FirebaseのFCMを使っているのでネイティブの通知も合わせて運用できる。通知運用がネイティブとウェブを一元管理できることは良いことである。

コード

紹介したコードは断片のためgithubを参照してほしい。動作確認ができるようにまとめてある。 github.com