How to manage FCM push tokens

Viewed 812

I would like to find a sustainable solution for storing FCM tokens used for push notifications. I am using React Native with Firebase.

The requirements are:

  1. User-based application
  2. Multiple devices per user supported
  3. No stale tokens stored at any time.

My current approach is:

Maintain a table user_devices with columns:

  • user_id,
  • device_uuid
  • fcm_token

Maintain a uniqueness constraint on fcm_token and device_uuid.

Upon key events, including but not limited to:

  • application startup
  • sign in
  • onTokenRefresh

send the following to the backend:

  • device unique id
  • FCM registration token
  • user id, if there is one (done through authentication token)

The backend then upserts a row with the above values. If the device uuid exists, that means, we overwrite the token (and, potentially, the user_id) for that device uuid.

The above approach is flawed because:

  • device unique id can change at any point (device reset, update, app reinstall, etc...)
  • registration token can change at any point

In other words, the table ends up storing many stale tokens and the application backend sends notifications to those stale tokens.

Additionally, the "Devices" frontend page (which shows the user their registered devices and reads from the above table) shows many duplicate devices that, really, represent the same device but have different device uuids.

One mitigation step could be to periodically send notifications to all tokens stored in the table with dry_run=true. Then, remove rows with tokens for which an error occurred. This is bad because it implies there is a period during which tokens are stale in the table.

I have scanned multiple resources online, including FCM documentation, but there is no mention on storing and maintaining those tokens in a real-world application. Are there any suggested approaches?

1 Answers

The idiomatic approach is to remove a token when the Firebase Cloud Messaging API to send messages tells you that it is not valid.

For an example of how to do this, you can have a look at the Cloud Functions sample of sending notifications, which does the following:

  const response = await admin.messaging().sendToDevice(tokens, payload);
  // For each message check if there was an error.
  const tokensToRemove = [];
  response.results.forEach((result, index) => {
    const error = result.error;
    if (error) {
      functions.logger.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());
      }
    }
  });
  return Promise.all(tokensToRemove);

So each time you send one of more messages through FCM, you cull the invalid/outdated tokens from the database.

Related