Firebase cloud messaging sendToDevice works properly but sendMulticast fails for the same list of tokens
Asked Answered
B

1

3

For certain types of messages, I want to target users by FIRTokens vs topic, which are stored in my real-time database. I load these tokens with async/await and then decide if I want to send notifications to a topic vs a smaller list of users. The data loading code works as expected. But what's odd is that if I use .sendMulticast(payload), the notifications fail for all tokens in the list. On the other hand if I use .sendToDevice(adminFIRTokens, payload) the notification goes successfully to all my users. Right now my list has 2 tokens and with sendMulticast I have 2 failures and with sendToDevice I have 2 successes. Am I missing the point of what sendMulticast is supposed to do? According to the docs: Send messages to multiple devices:

The REST API and the Admin FCM APIs allow you to multicast a message to a list of device registration tokens. You can specify up to 500 device registration tokens per invocation.

So both should logically work. Then why does one fail and the other work? In fact with sendToDevice I get a multicastId in the response!

Here are some console outputs:

  1. sendToDevice:
Sent filtered message notification successfully: 
{ 
  results: 
    [ 
      { messageId: '0:1...45' }, 
      { messageId: '16...55' } 
    ], 
    canonicalRegistrationTokenCount: 0, 
    failureCount: 0, 
    successCount: 2, 
    multicastId: 3008...7000 
} 
  1. sendMulticast:
List of tokens that caused failures: dJP03n-RC_Y:...MvPkTbuV,fDo1S8jPbCM:...2YETyXef 

Cloud function to send the notification:

functions.database
  .ref("/discussionMessages/{autoId}/")
  .onCreate(async (snapshot, context) => {
    // console.log("Snapshot: ", snapshot);

    try {
      const groupsRef = admin.database().ref("people/groups");
      const adminUsersRef = groupsRef.child("admin");
      const filteredUsersRef = groupsRef.child("filtered");
      const filteredUsersSnapshot = await filteredUsersRef.once("value");
      const adminUsersSnapshot = await adminUsersRef.once("value");
      var adminUsersFIRTokens = {};
      var filteredUsersFIRTokens = {};

      if (filteredUsersSnapshot.exists()) {
        filteredUsersFIRTokens = filteredUsersSnapshot.val();
      }
      if (adminUsersSnapshot.exists()) {
        adminUsersFIRTokens = adminUsersSnapshot.val();
      }

      const topicName = "SpeechDrillDiscussions";
      const message = snapshot.val();
      const senderName = message.userName;
      const senderCountry = message.userCountryEmoji;
      const title = senderName + " " + senderCountry;
      const messageText = message.message;
      const messageTimestamp = message.messageTimestamp.toString();
      const messageID = message.hasOwnProperty("messageID")
        ? message.messageID
        : undefined;
      const senderEmailId = message.userEmailAddress;
      const senderUserName = getUserNameFromEmail(senderEmailId);

      const isSenderFiltered = filteredUsersFIRTokens.hasOwnProperty(
        senderUserName
      );

      var payload = {
        notification: {
          title: title,
          body: messageText,
          sound: "default",
        },
        data: {
          messageID: messageID,
          messageTimestamp: messageTimestamp,
        },
      };

      if (isSenderFiltered) {
        adminFIRTokens = Object.values(adminUsersFIRTokens);
        // payload.tokens = adminFIRTokens; //Needed for sendMulticast
        return (
          admin
            .messaging()
            .sendToDevice(adminFIRTokens, payload)
            // .sendMulticast(payload)
            .then(function (response) {
              if (response.failureCount === 0) {
                console.log(
                  "Sent filtered message notification successfully:",
                  response
                );
              } else {
                console.log(
                  "Sending filtered message notification failed for some tokens:",
                  response
                );
              }
              // if (response.failureCount > 0) {
              //   const failedTokens = [];
              //   response.responses.forEach((resp, idx) => {
              //     if (!resp.success) {
              //       failedTokens.push(adminFIRTokens[idx]);
              //     }
              //   });
              //   console.log(
              //     "List of tokens that caused failures: " + failedTokens
              //   );
              // }

              return true;
            })
        );
      } else {
        payload.topic = topicName;
        return admin
          .messaging()
          .send(payload)
          .then(function (response) {
            console.log("Notification sent successfully:", response);
            return true;
          });
      }
    } catch (error) {
      console.log("Notification sent failed:", error);
      return false;
    }
  });
Baro answered 6/2, 2021 at 8:56 Comment(0)
B
2

I think it's an issue of using a different payload structure.

This is the old one (without iOS specific info):

var payload = {
    notification: {
      title: title,
      body: messageText,
      sound: "default",
    },
    data: {
      messageID: messageID,
      messageTimestamp: messageTimestamp,
    },
  };

Whereas this is the new version (apns has iOS specific info)

var payload = {
    notification: {
      title: title,
      body: messageText,
    },
    data: {
      messageID: messageID,
      messageTimestamp: messageTimestamp,
    },
    apns: {
      payload: {
        aps: {
          sound: "default",
        },
      },
    },
  };

With the new structure, both send and sendMulticast are working properly. Which would fail to send or give errors like apns key is not supported in payload.

The new function:

functions.database
  .ref("/discussionMessages/{autoId}/")
  .onCreate(async (snapshot, context) => {
    // console.log("Snapshot: ", snapshot);

    try {
      const groupsRef = admin.database().ref("people/groups");
      const adminUsersRef = groupsRef.child("admin");
      const filteredUsersRef = groupsRef.child("filtered");
      const filteredUsersSnapshot = await filteredUsersRef.once("value");
      const adminUsersSnapshot = await adminUsersRef.once("value");
      var adminUsersFIRTokens = {};
      var filteredUsersFIRTokens = {};

      if (filteredUsersSnapshot.exists()) {
        filteredUsersFIRTokens = filteredUsersSnapshot.val();
      }
      if (adminUsersSnapshot.exists()) {
        adminUsersFIRTokens = adminUsersSnapshot.val();
      }

      // console.log(
      //   "Admin and Filtered Users: ",
      //   adminUsersFIRTokens,
      //   " ",
      //   filteredUsersFIRTokens
      // );

      const topicName = "SpeechDrillDiscussions";
      const message = snapshot.val();

      // console.log("Received new message: ", message);

      const senderName = message.userName;
      const senderCountry = message.userCountryEmoji;
      const title = senderName + " " + senderCountry;
      const messageText = message.message;
      const messageTimestamp = message.messageTimestamp.toString();
      const messageID = message.hasOwnProperty("messageID")
        ? message.messageID
        : undefined;
      const senderEmailId = message.userEmailAddress;
      const senderUserName = getUserNameFromEmail(senderEmailId);

      const isSenderFiltered = filteredUsersFIRTokens.hasOwnProperty(
        senderUserName
      );

      console.log(
        "Will attempt to send notification for message with message id: ",
        messageID
      );

      var payload = {
        notification: {
          title: title,
          body: messageText,
        },
        data: {
          messageID: messageID,
          messageTimestamp: messageTimestamp,
        },
        apns: {
          payload: {
            aps: {
              sound: "default",
            },
          },
        },
      };
      console.log("Is sender filtered? ", isSenderFiltered);

      if (isSenderFiltered) {
        adminFIRTokens = Object.values(adminUsersFIRTokens);
        console.log("Sending filtered notification with sendMulticast()");
        payload.tokens = adminFIRTokens; //Needed for sendMulticast
        return admin
          .messaging()
          .sendMulticast(payload)
          .then((response) => {
            console.log(
              "Sent filtered message (using sendMulticast) notification: ",
              JSON.stringify(response)
            );
            if (response.failureCount > 0) {
              const failedTokens = [];
              response.responses.forEach((resp, idx) => {
                if (!resp.success) {
                  failedTokens.push(adminFIRTokens[idx]);
                }
              });
              console.log(
                "List of tokens that caused failures: " + failedTokens
              );
            }
            return true;
          });
      } else {
        console.log("Sending topic message with send()");
        payload.topic = topicName;
        return admin
          .messaging()
          .send(payload)
          .then((response) => {
            console.log(
              "Sent topic message (using send) notification: ",
              JSON.stringify(response)
            );
            return true;
          });
      }
    } catch (error) {
      console.log("Notification sent failed:", error);
      return false;
    }
  });
Baro answered 6/2, 2021 at 12:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.