4

I am working with an iOS app which calls an Firebase cloud function, to store FCM tokens, for later use when sending notifications. The problem is that it does not work.

I am using a Cloud Firestore database.

When the function is called, here is what I want to happen. The function checks the parameters against a database. If the data in the provided parameter is already found in the DB then nothing should happen. If it is not found, then it should be added to the DB.

My cloud function code is below. I would be glad if someone could help me find the precise issue.

exports.addNewElement = functions.https.onCall((data, context) => {
  console.log('--addNewElement-- has been called with:\n' + data.fcmToken + '\n!!');
  var collectionRef = db.collection("THE_COLLECTION");

  // Create a query against the collection.
  var query = collectionRef.where("fcmToken", "==", data.fcmToken);
  query.get().then(function(doc) {
    if (doc.exists) { // We should do nothing.
        console.log("Document data:", doc.data());
    } else { // We should add the new element.
        // doc.data() will be undefined in this case
        console.log("No such document!");
        collectionRef.add({"fcmToken": fcmToken})
    }
  }).catch(function(error) {
    console.log("Error getting document:", error);
  });
});

I could imagine other directions to handle FCM tokens. Is there any recommend way to use as best practice?

droidBomb
  • 850
  • 5
  • 8
Michel
  • 10,303
  • 17
  • 82
  • 179
  • What's the problem? – Frank van Puffelen Dec 03 '18 at 07:01
  • The problem is that I am not storing anything now. Because what I do is not working. Beside, I am not sure the way I want to do it is the best practice. I could store the token by making DB write request directly from the app, I have already tried successfully, but I would like to avoid storing twice the same token, that is why I thought using a cloud function might be a better approach. – Michel Dec 03 '18 at 07:14
  • why you used `collectionRef.add({"fcmToken": fcmToken})` insted of `collectionRef.add({"fcmToken": data.fcmToken})` – Jatin Kathrotiya Dec 03 '18 at 07:20
  • Right! This was a mistyping. Sorry! – Michel Dec 03 '18 at 08:28

1 Answers1

5

I take a bit of a different approach. I have an ionic app and after the app has registered with FCM (we have FCM token), I add the token to a 'devices' collection straight from the app. This way, a user can log in to more then one device and we will have the token for each device enabling us to send the message to each device. If you want to send a message to a user, you query the devices collection for that uid to get all the tokens for that user

Saving the token:

    private saveTokenToFirestore(person: Person, token) {
        if (!token) return;
        const devicesRef = this.afs.collection('devices')

        const docData = { 
          token,
          userId: person.id,
        }

        return devicesRef.doc(token).set(docData)
      }

where person.id is the firebase uid.

I then use firebase functions to monitor some nodes to figure out when to send FCM messages.

e.g. we have teams with persons as members and they can chat to each other. When a person sends a message to a team, each team member (except the sender himself) needs to get a notification.

Sending a notification to all members except the sender himself:

exports.chatMessageOnCreateSendFcm = functions.firestore
    .document('chatGroups/{teamId}/messages/{messageId}')
    .onCreate(async (snap, context) => {
        const data = snap.data();
        const teamId = context.params.teamId;
        const name = data.pName;
        const message = data.msg;
        const userId = data.pId;

        // Notification content
        const payload = {
            notification: {
                title: name,
                body: message,
            }
        }

        const db = admin.firestore();

        // get the team (chatGroup)
        const teamRef = db.collection('teams').doc(teamId);
        const teamSnapshot = await teamRef.get();
        const team = teamSnapshot.data();
        const devicesRef = db.collection('devices');
        const queries: Promise<FirebaseFirestore.QuerySnapshot>[] = team.members
            .filter(f => f.id !== userId)
            .map(member => {
                return devicesRef.where('userId', '==', member.id).get();
            });

        return Promise.all(queries)
            .then((querySnapshots) => {
                const tokens = [];
                querySnapshots.forEach(snapShot => {
                    if (snapShot) {
                        snapShot.docs.forEach(doc => {
                            if (doc) {
                                const token = doc.data().token;
                                if (token) {
                                    tokens.push(token);
                                }
                            }
                        })
                    }
                });

                if (tokens.length === 0) {
                    return Promise.resolve(null);
                } else {
                    return admin.messaging().sendToDevice(tokens, payload);
                }
            })
            .catch(err => {
                console.error(err);
                return Promise.resolve(null);
            });
    });

You can modify the above to suit your needs. Hope it helps

Jan Feyen
  • 535
  • 3
  • 13
  • OK. Why not, but in this approach you may end up with several times the same fcm token in you DB? Tell me if I am wrong. Or maybe you just don't care? – Michel Dec 03 '18 at 08:31
  • 2
    you won't end up with the same token multiple times as the token is the key of the collection. It will insert if not exist and update / replace if exists (doc.set is an upsert action) You might end up with collections with the same uid which is normal if a user logs in with iPhone and iPad for instance. He will get notification on each device – Jan Feyen Dec 03 '18 at 08:35
  • When you are sending your notifications with "admin.messaging().sendToDevice(tokens, payload)"; can you have a big number of items inside tokens? You are not using what is called a "topic"? Right? – Michel Dec 04 '18 at 03:08
  • correct ... I'm not using a topic. In my scenario, it's sport teams which have by it's nature a small and limited set of members. It depends of your interpretation of big. I wouldn't recommend using this to send to a couple of thousands tokens or more. – Jan Feyen Dec 05 '18 at 06:32
  • OK. So for me using a topic is better. – Michel Dec 05 '18 at 06:55