1

We have a cross-platform app that uses Firebase Cloud Messaging to drive an in-app chat feature. Some users might use the app actively on more than one device. So, whenever a user's device receives an onTokenRefresh trigger, we send that new registration token to the server to be saved against the user. Now say a user already has some registration tokens stored in the server database, how will we know if those tokens were for the same device and should now be deleted or if they are for a different device and we should keep sending to all of them?

I have read the docs on Device Group Messaging, but it looks like too much overhead for our application and it doesn't look like the Firebase server will automatically delete a superseded registration token from the group for you.

If we simply assume all the user's registration tokens on record are active and send to all, can we use the response to decide if we need to prune a token on the server?

{
    "multicast_id": 6538766984100364080,
    "success": 1,
    "failure": 0,
    "canonical_ids": 0,
    "results": [
        {
            "message_id": "0:1510294979553090%029da28f029da28f"
        }
    ]
}

According to this answer and some tests against the HTTP API with replaced tokens, it doesn't look like the "success":1 result is a reliable indicator that the token should not be removed, because replaced tokens tend to live on. Also, a "success": 0 result might not be a reliable indicator that we can remove the token, because it might just indicate an ad-hoc network error on a valid, active token.

The API documentation talks about how to interpret an optional registration_id in the result, but it is not clear how this differs from a NotRegistered error and what the best action is to take.

Any insight or best practice on how to handle and manage the arrival of a FCM device token on the server will be much appreciated.

Jannie Theunissen
  • 28,256
  • 21
  • 100
  • 127

2 Answers2

0

I also came across the exact challenge and had to resolve to a solution: Storing each token for the user against the device id. It's interesting enough to know that this function in fact exists in the firebase messaging method. But more surprising is the fact that there's no documentation to handle such scenario. https://firebase.google.com/docs/reference/android/com/google/firebase/iid/FirebaseInstanceId.html#getId()

So in summary, while sending the new token to the server, also send along the device id returned by the getId() method and use it to enforce uniqueness of token per device.

Problem solved :D

tormuto
  • 587
  • 5
  • 16
  • 1
    The docs says an "identifier that uniquely identifies the app instance", which would mean that it renews every time the app is newly launched on the same device with the same device id. This can only help identify device ids that get replaced in the same app session (instance) which typically is not when they get replaced. – Jannie Theunissen Nov 28 '17 at 14:30
0

We are going with the approach where we assume all onTokenRefresh ids are new, additional devices that we add to the device list on the server. Then, whenever we send a message we use the returned result to delete or replace deprecated device tokens. Implementation in PHP:

// $devices is a list of the device ids to send to

// 1. send a message to a list of devices
$response = Firebase::request('POST', 'send', ['json' => $this->payloadFor($devices)]);

// 2. check the response to see if we need to make changes to the device list

// if it is a network error, no changes needed
if ($response->getStatusCode() != 200) {
    Log::info("FCM http error " . $response->getStatusCode());
    return;
}

$body = json_decode($response->getBody(), $asArray = true);

// do we need to dig deeper?
if ($body['failure'] == 0 && $body['canonical_ids'] == 0) return;

if (count($body['results']) != count($devices)) {
    Log::info("FCM error : device count not matching result count");
    return;
}

// we have errors that need processing, so step through the results list
foreach ($body['results'] as $key => $result) {

    if (isset($result['error'])) {
        switch ($result['error']) {
            case 'NotRegistered':
            case 'InvalidRegistration':
                $deletedRows = Device::where('token', $devices[$key])->delete();
                Log::info("FCM trimmed: $devices[$key]");
                break;

            default:
                Log::info("FCM error " . $result['error']);
                break;
        }
    }

    // we need to update some device tokens
    if (isset($result['registration_id'])) {
        Device::deprecate($devices[$key], $result['registration_id']);
        Log::info("FCM replaced: " . $devices[$key]);
    }
}
Jannie Theunissen
  • 28,256
  • 21
  • 100
  • 127