Edited at

Cloud Functionsでチャットのメッセージを監視&プッシュ通知

More than 1 year has passed since last update.


やりたいこと

アプリからメッセージを送信→DB(Firestore)にメッセージが保存される→それを監視していたCloudFunctionsがプッシュ通知を送る

Cloud Functions で可能な処理


関心事が発生したときにユーザーに通知する


notify.png

これと同じようなイメージ。


Firestore設計

chatrooms(コレクション)

 ∟chatroom1(ドキュメント)

  ∟messages(コレクション)

   ∟message1(ドキュメント)

   ∟message2(ドキュメント)

   ∟message3(ドキュメント)

 ∟chatroom2(ドキュメント)

  ∟messages(コレクション)

   ∟message1(ドキュメント)

   ∟message2(ドキュメント)




  • ルートレベルにchatroomsコレクションがある

  • chatroomsドキュメント配下にmessagesサブコレクションがある

このように設計しました。


アプリ設計

設計というほどではないのですが、以下のように事前にトピックを購読させます。

タイミングとしてはLINEでいうところの「グループチャットに参加する」のようなものを契機にすればよいのではないでしょうか。

その際トピック名としてチャットルームのドキュメントIDを指定します。

Messaging.messaging().subscribe(toTopic: "/topics/\(chatroomドキュメントのid)")

こうすることで/topics/{chatroomドキュメントのid}宛てにFCMメッセージが送られた際にプッシュ通知を受け取れるようになります。


CloudFunctionsの設計

CLIツールをインストールしていない場合はインストールしてください。

$ npm install -g firebase-tools

適当にディレクトリを作ってそこでプロジェクトを初期化します。

$ firebase init functions

image.png

プロジェクトを選択し、JavascriptかTypescriptを選択します。

今回はJavascriptを選択した前提で話を進めます。

そのあと色々聞かれますが全てEnterでOKです。

image.png

こんなファイル群が自動で生成されるはずです。

index.jsに以下を追記します。(公式の手順通りですが)

これで


functions/index.js

// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.

const functions = require('firebase-functions');

// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);


これでFirestoreにアクセスできるようになります。

続いて肝心のプッシュ通知を行う関数を定義します。

Firestoreへの追加をトリガーにプッシュ通知を送っています。

こちらを参考にしました。

Cloud Functions による Cloud Firestore の拡張


functions/index.js

exports.sendNotifications = functions.firestore

.document('/chatrooms/{chatroomId}/messages/{messageId}')
.onCreate((event, snapshot) => { // 公式ではeventとなっていたが、そうするとevent.params.chatroomIdのように値を取ってこれなかった。

var topic = snapshot.params.chatroomId; // こうすることで上で指定したドキュメントのパスの{chatroomId}部分が取得できる。

var payload = {
notification: {
title: "新着メッセージ", // この辺りを動的に取ってくるケースが多いかな?
body: "新着メッセージがあります"
}
};

var options = {
priority: "high"
};

// 指定したtopic宛てにメッセージを送る
return admin.messaging().sendToTopic(topic, payload, options)
.then(function(response) {
return console.log("Successfully sent message:", response);
})
.catch(function(error) {
return console.log("Error sending message:", error);
});
});


そしてデプロイします。

$ firebase deploy --only functions

上記のコードであればエラーは出ないと思いますが、当初自分はsendToTopic内のconsole.logにreturnをつけていなかったので以下のようにLintで怒られました。

Each then() should return a value or throw promise/always-return

うまくデプロイが済めばこうなるはずです。

✔  functions: Finished running predeploy script.

i functions: ensuring necessary APIs are enabled...
✔ functions: all necessary APIs are enabled
i functions: preparing functions directory for uploading...
i functions: packaged functions (49.67 KB) for uploading
✔ functions: functions folder uploaded successfully
i functions: creating function sendNotifications...
✔ functions[generateThumbnail]: Successful delete operation.
✔ functions[sendNotifications]: Successful create operation.

✔ Deploy complete!

そしてアプリからメッセージを送信します。(当記事ではメッセージ送信箇所について言及していませんが、Firestoreのmessageドキュメントを生成する処理を実行すればOKです。アプリでそれを実装するのが面倒な場合はFirestoreのコンソールから直接messagesドキュメントを作っても動作確認は可能です。)

すると、そのチャットルームを購読している端末に通知がいくと思います。

Cloud Functionsのログにも結果が出力されているはずです。

image.png

もっといい方法があれば教えていただきたいです。