FirebaseのFirestore更新をトリガーにFunctionsでプッシュ通知

[繋がらないマッチングプラットフォーム「FLAPTALK」](https://flaptalk.jp)を運営する[株式会社OneSmallStep](https://1smallstep.jp)の[@_takeshi_24](https://twitter.com/_takeshi_24)です。

この記事は「Nuxt.jsとFirebaseとCloudFunctionsでWebアプリ開発」シリーズとして、連載していきます。
Nuxt.jsとFirebaseなどを使ってWebアプリケーション開発にチャレンジしたい方、是非Qiitaアカウントか[twitterをフォロー](https://twitter.com/_takeshi_24)していただき、ツッコミやいいね！お願いします！

今回はアプリケーションにプッシュ通知を送るための、Firebase Cloud Messagingについて説明します。
Webアプリケーションにもプッシュ通知可能ですが、iOSのWebアプリケーションは対象外になります。

今回は、RealtimeDatabase、Firestoreの更新をトリガーに、CloudFunctionsの関数を実行し、特定のユーザーにCloudMessagingでプッシュ通知を送る方法について記載します。

## はじめに
こちらの記事は、「Nuxt.jsとFirebaseとCloudFunctionsでWebアプリ開発」シリーズとして連載していますので、「[diffeasyCTO西の24(にし)日連続投稿チャレンジ Advent Calendar 2019](https://qiita.com/advent-calendar/2019/diffeasy24)」の過去の記事もご覧ください。

CloudMessagingの準備はこちら「[Nuxt.jsアプリにFirebase Cloud Messagingでプッシュ通知](https://qiita.com/_takeshi_24/items/0545fabb4685c25c4d9c)」に記載していますので、CloudMessagingの準備がまだの方は、まずこちらの記事で準備をお願いします。

##FirestoreをトリガーにCloudFunctions実行
まずは、Firestoreのデータが更新されたら、特定のユーザーにプッシュ通知を送る処理について説明していきます。

###トリガーのもととなるFirestore更新の画面
まずは、トリガーを発生させるFirestore更新のサンプルを実装します。
メッセージ入力欄と、ユーザー一覧とユーザーごとに「プッシュ通知」ボタンを配置し、「プッシュ通知」ボタンをクリックしたら、対象のユーザー宛てにメッセージ入力欄の内容を通知するサンプルです。
<img width="1197" alt="スクリーンショット 2019-12-05 7.59.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3003/0289003e-9470-e0af-32a7-fb0e4d5c4265.png">

画面のソースは以下の通りです。

```vuejs:/pages/users/index.vue
<template>
  <section class="container">
    <v-text-field v-model="message"></v-text-field>
    <v-row v-for="(user, key, index) in users" :key="index">
      <v-col>
        <p>{{ user.data.familyName }} {{ user.data.firstName }}</p>
      </v-col>
      <v-col>
        <v-btn @click="sendMessage(user.uid)">プッシュ通知</v-btn>
      </v-col>
    </v-row>
  </section>
</template>
<script>
import firebase from "firebase";
const db = firebase.firestore();

export default {
  data() {
    return {
      message: "",
      users: []
    };
  },
  created() {
    this.getUsers();
  },
  methods: {
    async getUsers() {
      const snapShot = await db.collection("users").get();
      this.users = snapShot.docs.map(doc => {
        const obj = { uid: doc.id, data: doc.data() };
        return obj;
      });
    },
    sendMessage(uid) {
      db.collection("messages")
        .doc(uid)
        .set({
          message: this.message
        });
      this.message = "";
    }
  }
};
</script>
```

解説していきます。
1.created()のなかで、getUsers()を実行し、Firestoreからユーザーの一覧を取得しています。データの内容は以下のような形です。FirebaseAuthenticationのUIDをusersのIDに設定していて、fcmTokenにFirebase Cloud Messagingのトークンがセットされます。
（参照：[Nuxt.jsアプリにFirebase Cloud Messagingでプッシュ通知]()）
<img width="904" alt="スクリーンショット 2019-12-05 21.28.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3003/ccc31dde-27ee-0d43-2c98-72693061cd65.png">

2.sendMessage()で、対象のuidのmessagesコレクションに、テキストのメッセージをセットします。
<img width="902" alt="スクリーンショット 2019-12-05 21.34.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3003/8e6fa785-5964-9783-df6c-753148051582.png">

3.Firestoreのルールを追加します。

```config:firestore.rules
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      allow update, delete: if request.auth.uid == userId;
      allow read, create: if request.auth.uid != null;
    }
    match /messages/{userId} {
      allow create, read, update, delete: if request.auth.uid != null;
    }
  }
}
```

###更新をトリガーにCloudFunctionsを実行
messagesコレクションのデータが更新されると、それをトリガーにCloudFunctionsの関数が実行されます。

1.functionsのindex.jsに以下の関数を追加します。

```javascript:index.js
exports.sendPushMessage = firestore
  .document("messages/{userId}")
  .onWrite(async (change, context) => {
    const data = change.after.data();
    const previousData = change.before.data();

    // uidから通知先のユーザー情報を取得
    const userRef = await admin
      .firestore()
      .collection("users")
      .doc(context.params.userId);
    const userDoc = await userRef.get();

    if (userDoc.exists) {
      const user = userDoc.data();
      // 通知のタイトルと本文を設定
      const payload = {
        notification: {
          title: `${user.familyName} ${user.firstName}さんへ`,
          body: data.message
        }
      };

      /// プッシュ通知を送信
      if (user.fcmToken) {
        admin.messaging().sendToDevice(user.fcmToken, payload);
      } else {
        console.error("No Firebase Cloud Messaging Token.");
      }
    } else {
      console.error("No User.");
    }

    return true;
  });
```

ここでは、onWriteを使って、ドキュメントに書き込み、更新があったときに関数を呼び出しています。
その他、以下のようなトリガーが利用できます。

| イベントタイプ | トリガー |
|:--|:--|| onCreate | ドキュメントが最初に書き込まれたときにトリガーされます。 |
| onCreate | ドキュメントが最初に書き込まれたときにトリガーされます。 |
| onUpdate | すでに存在するドキュメントの値が変更されたときにトリガーされます。 |
| onDelete | データを含むドキュメントが削除されたときにトリガーされます。 |
| onWrite | onCreate、onUpdate または onDelete がトリガーされたときにトリガーされます。 |

[Cloud Firestore トリガーより](https://firebase.google.com/docs/functions/firestore-events?hl=ja)

2.ローカルのエミュレーターではプッシュ通知は送れませんので、Firebaseにデプロイします。

```terminal
$ firebase deploy
```

3.画面からメッセージを入力し、通知を送るユーザーの「プッシュ通知」ボタンをクリックします。
<img width="1203" alt="スクリーンショット 2019-12-05 22.03.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3003/6cd60f16-3036-6d1a-27b4-1691ca511a3a.png">

4.CloudFunctionsが起動されます。Firebaseのコンソールでログを監視できます。
<img width="1201" alt="スクリーンショット 2019-12-05 22.18.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3003/173fe6ad-849c-cb5a-b51f-f383849c96b5.png">

5.通知が表示されます。Android端末だと、待ち受けにも通知が表示されます。
<img width="280px" alt="スクリーンショット 2019-12-05 22.18.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3003/69c388e3-a5a4-2fc2-a62c-18203dea4d70.jpeg">

##RealtimeDatabaseをトリガーにCloudFunctionsを実行
RealtimeDatabaseでも以下の通り、Firestoreと同じようにデータの更新をトリガーにCloudFunctionsを実行できます。

```javascript
exports.sendPushMessage = functions.database
  .ref("/messages/{pushId}/original")
  .onCreate((snapshot, context) => {
      // ・・・　
  });
```

| イベントタイプ | トリガー |
|:--|:--|
|onWrite | Realtime Database 内でデータが作成、更新、削除されるとトリガーされます。|
|onCreate | Realtime Database 内で新しいデータが作成されるとトリガーされます。|
|onUpdate | Realtime Database 内でデータが更新されるとトリガーされます。|
|onDelete | Realtime Database 内でデータが削除されるとトリガーされます。|
[Database トリガーより](https://firebase.google.com/docs/functions/database-events?hl=ja)

##最後に
この記事は「Nuxt.jsとFirebaseとCloudFunctionsでWebアプリ開発」シリーズとして、連載していきます。
続きはアドベントカレンダー「[diffeasyCTO西の24(にし)日連続投稿チャレンジ Advent Calendar 2019](https://qiita.com/advent-calendar/2019/diffeasy24)」に掲載していきます。

Nuxt.jsとFirebaseなどを使ってWebアプリケーション開発にチャレンジしたい方、是非Qiitaアカウントか[twitterをフォロー](https://twitter.com/_takeshi_24)していただき、ツッコミやいいね！お願いします！

[#advent_24](https://twitter.com/hashtag/advent_24)のハッシュタグでフィードバックいただけると嬉しいです！