Device Group Management With Firebase Cloud Messaging

April 15, 2018

Firebase Cloud Messaging (FCM) is a free cross-platform messaging solution that lets you deliver push messages to your client apps. Using FCM you can send push notifications to individual devices, to groups of devices, or to devices subscribed to specific “topics”.

This article will outline your options for managing multiple devices per user.

To target individual devices, or groups of devices, you need to store their registration tokens – unique IDs that are generated by the FCM SDK for each app instance.

You also need to delete the device’s token when the user logs out.

Lastly, you need to delete the tokens that are no longer valid. The user may uninstall your app, and FCM may regenerate a device’s token. In both cases the token should be deemed invalid and should no longer be targeted for messaging. If you route messages to invalid tokens, FCM will respond with an error.

The simplest strategy is to only store one token per user in your database. Whenever you obtain a device’s registration token, send it to your server and store it. When the user logs out, delete the token. When a notification fails because it was targeted to an invalid token, FCM will respond with an error message and you can safely delete it from your database.

However, this won’t work very well if you have users with multiple devices; only one device will receive each push message.

To send a notification to all devices associated with a user, you need to store the token of each device. You have two options:

  1. Manage tokens yourself. This entails maintaining a list of tokens for each user, storing and deleting them as appropriate, and removing tokens that eventually become invalid.
  2. Use FCM’s device group messaging. This feature lets you add multiple tokens to the same “group”, and route messages to the group instead of to each individual device.

Manage tokens yourself

Managing tokens yourself means storing them in your own database and deleting them when necessary.

Whenever your client app obtains a registration token, you need to send it to your server and store it in your database along with the rest of the user’s data. Using a relational database, this could be achieved with a one-to-many relationship between a user and his/her tokens. In a document store, you could save the tokens as an array in your user’s document.

You would need to make sure that no duplicate tokens are stored so that messages don’t get sent to the same device more than once. And when the user logs out, you should delete the specific device’s token. This can be done, for example, by including the current token in the body of the logout request, or in a separate request.

After a user logs into your app, and whenever the token is refreshed (i.e. regenerated by Firebase), you’d do something like this (the example code is in Javascript, but every FCM SDK contains equivalent functionality):

1
2
3
4
5
6
7
8
messaging.getToken().then(function(currentToken) {
if (currentToken) {
sendTokenToServer(currentToken);
// ...
} else {
// ...
}
});

When a user logs out, you’d do something like this:

1
deleteTokenFromServer(currentToken);

This takes care of storing tokens when they’re retrieved and deleting them on logout. But tokens will eventually become outdated and invalid, which you won’t notice until you actually send a message. This usually happens when a user has uninstalled your app, switched devices or revoked or renewed push notification permissions. In this case, the response from FCM will indicate that the token is invalid, thus prompting you to delete it from your database.

How you delete invalid tokens depends on whether you’re using the “legacy” HTTP protocol or the new “v1” HTTP protocol.

The Legacy FCM HTTP protocol

In late 2017 Firebase announced the new “HTTP v1” protocol for sending messages. The “legacy” protocol is still being used for various reasons, one of which is that it supports “device groups” and multicast messages.

Multicast messages are messages being sent to multiple device tokens using one request to the FCM server:

1
2
3
4
5
6
7
8
9
10
POST https://fcm.googleapis.com/fcm/send
Content-Type:application/json
Authorization:key=AIzaSyZ-1u...0GBYzPu7Udno5aA

{
"notification": {
"body": "Hello!"
},
"registration_ids" : ["token1", "token2"]
}

A successful response looks like this:

1
2
3
4
5
6
7
8
9
{
"multicast_id": 108,
"success": 2,
"failure": 0,
"results": [
{ "message_id": "1:08" },
{ "message_id": "1:09" }
]
}

In this case the message was delivered to both devices. However, if one of the tokens were invalid, the response would look like this:

1
2
3
4
5
6
7
8
9
{
"multicast_id": 108,
"success": 1,
"failure": 1,
"results": [
{ "message_id": "1:08" },
{ "error": "InvalidRegistration" }
]
}

This response doesn’t tell you which token failed, but that it was the second one you provided. In other words, your server needs to know which tokens it specified, and in what exact order.

For example, in the official Cloud Functions example repository there’s a sample function in which a user’s tokens are stored as an array in a document store. If one or more messages fail, the results array is looped through and any failing item’s index is used to delete the corresponding token from the database:

1
2
3
4
5
6
7
8
9
10
11
12
13
// For each message check if there was an error.
const tokensToRemove = [];
response.results.forEach((result, index) => {
const error = result.error;
if (error) {
console.error('Failure sending notification to', tokens[index], error);
// Cleanup the tokens who are not registered anymore.
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered') {
tokensToRemove.push(tokensSnapshot.ref.child(tokens[index]).remove());
}
}
});

You can find the source here.

The v1 HTTP protocol

The new “v1” HTTP protocol doesn’t support multicast messages and any message is sent to one device only. Sending a message to 5 devices requires 5 requests to the FCM server.

If a device token fails (error code UNREGISTERED), the token should be deleted. Again, your server needs to know which token it used to send the message. But in contrast to multicast messages it doesn’t need to know in what order the tokens were provided, because there’s only one token to consider.

Using Device Groups (legacy protocol only)

Device group messaging can make handling multiple devices much easier. You should note that this feature is only supported by the legacy protocol and will likely never be supported by the new v1 protocol.

Device group messaging works like this: First, you create a device group for a user. Second, whenever you retrieve a token from one of your users’ device, send it to your server and add it to the user’s group. On logout, send the current token to your server and delete it from the group.

How is this easier than doing it yourself? You don’t need to worry about duplicates because FCM will never add the same token twice. And if a token fails after a message attempt, FCM will automatically remove it from the group. This saves you from a lot of tedious work.

(Please refer to the official documentation on how to actually add and remove registration tokens to a device group.)

Unfortunately, there’s a potential downside. What if your user wants to delete his or her account and all associated data? In order to delete a device group, you need to delete each and every registration token first. Only after you’ve deleted them all, the group itself is removed from Firebase.

In other words, to completely remove a user’s information from your system, including every device token, you need to store all registration tokens in case you ever need to delete them.

There are other disadvantages to using FCM’s device group messaging, but those depend entirely on your use case. For example, you can’t store metadata together with each token, like platform information, localization information etc.

Closing remarks

Firebase has a lot of offer, but it could definitely make things easier if it provided a better way of managing registration tokens using the new v1 protocol, which seems to be the future of FCM. If you don’t want to manage token groups yourself you may need to resort to third party services.

(If you use the FCM Admin SDKs, the actual HTTP requests used to send messages is hidden from you. The SDKs use both the legacy protocol and the v1 protocol, depending on the operation.)