Meilleures pratiques pour la gestion des jetons d'enregistrement FCM

Si vous utilisez des API FCM pour créer des demandes d'envoi par programmation, vous constaterez peut-être qu'au fil du temps, vous gaspillez des ressources en envoyant des messages à des appareils inactifs avec des jetons d'enregistrement obsolètes. Cette situation peut affecter les données de livraison des messages signalées dans la console Firebase ou les données exportées vers BigQuery, se traduisant par une baisse spectaculaire (mais pas réellement valide) des taux de livraison. Ce guide décrit certaines mesures que vous pouvez prendre pour garantir un ciblage efficace des messages et des rapports de livraison valides.

Bonnes pratiques de base

Il existe certaines pratiques fondamentales que vous devez suivre dans toute application qui utilise les API FCM pour créer des demandes d'envoi par programme. Les principales bonnes pratiques sont :

  • Stockez les jetons d'enregistrement sur votre serveur. Un rôle important pour le serveur est de suivre le jeton de chaque client et de conserver une liste à jour des jetons actifs. Nous vous recommandons vivement d'implémenter un horodatage de jeton dans votre code et vos serveurs, et de mettre à jour cet horodatage à intervalles réguliers.
  • Supprimez les jetons stockés qui deviennent obsolètes . En plus de supprimer les jetons dans les cas évidents de réponses de jeton non valides, vous devrez probablement surveiller d'autres signes indiquant que le jeton est obsolète. Ce guide présente certaines de vos options pour y parvenir.

Récupérer et stocker les jetons d'enregistrement

Lors du démarrage initial de votre application, le SDK FCM génère un jeton d'enregistrement pour l'instance de l'application cliente. Il s'agit du jeton que vous devez inclure dans les demandes d'envoi ciblées de l'API ou ajouter aux abonnements aux rubriques pour cibler les rubriques.

Comme indiqué dans nos guides de configuration client, votre application doit récupérer ce jeton au démarrage initial et l'enregistrer sur votre serveur d'application avec un horodatage . Cet horodatage doit être implémenté par votre code et vos serveurs, car il ne vous est pas fourni par les SDK FCM.

De plus, il est important d'enregistrer le jeton sur le serveur et de mettre à jour l'horodatage chaque fois qu'il change, par exemple lorsque :

  • L'application est restaurée sur un nouvel appareil
  • L'utilisateur désinstalle/réinstalle l'application
  • L'utilisateur efface les données de l'application.

Exemple : stocker des jetons et des horodatages dans Cloud Firestore

Par exemple, vous pouvez utiliser Cloud Firestore pour stocker des jetons dans une collection appelée fcmTokens . Chaque ID de document dans la collection correspond à un ID utilisateur, et le document stocke le jeton d'enregistrement actuel et son horodatage de dernière mise à jour. Utilisez la fonction set comme indiqué dans cet exemple Kotlin :

    /**
     * Persist token to third-party servers.
     *
     * Modify this method to associate the user's FCM registration token with any server-side account
     * maintained by your application.
     *
     * @param token The new token.
     */
    private fun sendTokenToServer(token: String?) {
        // If you're running your own server, call API to send token and today's date for the user

        // Example shown below with Firestore
        // Add token and timestamp to Firestore for this user
        val deviceToken = hashMapOf(
            "token" to token,
            "timestamp" to FieldValue.serverTimestamp(),
        )
        // Get user ID from Firebase Auth or your own server
        Firebase.firestore.collection("fcmTokens").document("myuserid")
            .set(deviceToken)
    }

Chaque fois qu'un jeton est récupéré, il est stocké dans Cloud Firestore en appelant sendTokenToServer :

    /**
     * Called if the FCM registration token is updated. This may occur if the security of
     * the previous token had been compromised. Note that this is called when the
     * FCM registration token is initially generated so this is where you would retrieve the token.
     */
    override fun onNewToken(token: String) {
        Log.d(TAG, "Refreshed token: $token")

        // If you want to send messages to this application instance or
        // manage this apps subscriptions on the server side, send the
        // FCM registration token to your app server.
        sendTokenToServer(token)
    }
        var token = Firebase.messaging.token.await()

        // Check whether the retrieved token matches the one on your server for this user's device
        val preferences = this.getPreferences(Context.MODE_PRIVATE)
        val tokenStored = preferences.getString("deviceToken", "")
        lifecycleScope.launch {
            if (tokenStored == "" || tokenStored != token)
            {
                // If you have your own server, call API to send the above token and Date() for this user's device

                // Example shown below with Firestore
                // Add token and timestamp to Firestore for this user
                val deviceToken = hashMapOf(
                    "token" to token,
                    "timestamp" to FieldValue.serverTimestamp(),
                )

                // Get user ID from Firebase Auth or your own server
                Firebase.firestore.collection("fcmTokens").document("myuserid")
                    .set(deviceToken).await()
            }
        }

Détecter les réponses de jeton non valides du backend FCM

Assurez-vous de détecter les réponses de jeton non valides de FCM et répondez en supprimant de votre système tous les jetons d'enregistrement connus pour être invalides. Avec l'API HTTP v1, ces messages d'erreur peuvent indiquer que votre demande d'envoi a ciblé des jetons obsolètes ou non valides :

  • UNREGISTERED (HTTP 404)
  • INVALID_ARGUMENT (HTTP 400)

Notez que, comme INVALID_ARGUMENT est également émis en cas de problèmes dans la charge utile du message, il signale un jeton invalide uniquement si la charge utile est entièrement valide. Voir Codes d'erreur pour plus d'informations.

Si vous êtes certain que la charge utile du message est valide et que vous recevez l'une de ces réponses pour un jeton ciblé, vous pouvez supprimer en toute sécurité votre enregistrement de ce jeton, car il ne sera plus jamais valide. Par exemple, pour supprimer des jetons non valides de Cloud Firestore, vous pouvez déployer et exécuter une fonction comme celle-ci :

    // Registration token comes from the client FCM SDKs
    const registrationToken = 'YOUR_REGISTRATION_TOKEN';

    const message = {
    data: {
        // Information you want to send inside of notification
    },
    token: registrationToken
    };

    // Send message to device with provided registration token
    getMessaging().send(message)
    .then((response) => {
        // Response is a message ID string.
    })
    .catch((error) => {
        // Delete token for user if error code is UNREGISTERED or INVALID_ARGUMENT.
        if (errorCode == "messaging/registration-token-not-registered") {
            // If you're running your own server, call API to delete the token for the user

            // Example shown below with Firestore
            // Get user ID from Firebase Auth or your own server
            Firebase.firestore.collection("fcmTokens").document(user.uid).delete()
        }
    });

Assurer la fraîcheur du jeton d'enregistrement

Déterminer si un jeton est frais ou obsolète n'est pas toujours simple. Pour couvrir tous les cas, vous devez adopter un seuil pour le moment où vous considérez les jetons obsolètes ; notre recommandation est de deux mois. Tout jeton datant de plus de deux mois est susceptible d'être un appareil inactif ; un appareil actif aurait sinon actualisé son jeton.

Mettre à jour les jetons régulièrement

Nous vous recommandons de récupérer et de mettre à jour périodiquement tous les jetons d'enregistrement sur votre serveur. Cela vous oblige à :

  • Ajoutez une logique d'application dans votre application cliente pour récupérer le jeton actuel à l'aide de l'appel d'API approprié (tel que token(completion): pour les plates-formes Apple ou getToken() pour Android), puis envoyez le jeton actuel à votre serveur d'application pour stockage (avec horodatage ). Il peut s'agir d'une tâche mensuelle configurée pour couvrir tous les clients/tokens.
  • Ajoutez une logique de serveur pour mettre à jour l'horodatage du jeton à intervalles réguliers, que le jeton ait changé ou non.

Pour un exemple de logique Android pour la mise à jour des jetons à l'aide de WorkManager , consultez Gestion des jetons Cloud Messaging sur le blog Firebase.

Quel que soit le modèle de synchronisation que vous suivez, assurez-vous de mettre à jour les jetons périodiquement. Une fréquence de mise à jour d'une fois tous les deux mois constitue probablement un bon équilibre entre l'impact de la batterie et la détection des jetons d'enregistrement inactifs. En effectuant cette actualisation, vous vous assurez également que tout appareil devenu inactif actualisera son enregistrement lorsqu'il redeviendra actif. Il n'y a aucun avantage à effectuer l'actualisation plus fréquemment qu'une fois par semaine.

Supprimer les jetons d'enregistrement obsolètes

Avant d'envoyer des messages à un appareil, assurez-vous que l'horodatage du jeton d'enregistrement de l'appareil se situe dans votre période de fenêtre d'obsolescence. Par exemple, vous pouvez mettre en œuvre Cloud Functions for Firebase pour exécuter une vérification quotidienne afin de vous assurer que l'horodatage se situe dans une période de fenêtre d'obsolescence définie, telle que const EXPIRATION_TIME = 1000 * 60 * 60 * 24 * 60; puis supprimez les jetons obsolètes :

exports.pruneTokens = functions.pubsub.schedule('every 24 hours').onRun(async (context) => {
  // Get all documents where the timestamp exceeds is not within the past month
  const staleTokensResult = await admin.firestore().collection('fcmTokens')
      .where("timestamp", "<", Date.now() - EXPIRATION_TIME)
      .get();
  // Delete devices with stale tokens
  staleTokensResult.forEach(function(doc) { doc.ref.delete(); });
});

Désabonner les jetons obsolètes des sujets

La gestion des abonnements aux rubriques pour supprimer les jetons d'enregistrement obsolètes est une autre considération. Cela implique deux étapes :

  1. Votre application doit se réabonner aux sujets une fois par mois et/ou chaque fois que le jeton d'inscription change. Cela forme une solution d'auto-guérison, où les abonnements réapparaissent automatiquement lorsqu'une application redevient active.
  2. Si une instance d'application est inactive pendant 2 mois (ou votre propre fenêtre d'obsolescence), vous devez vous désabonner des sujets à l'aide du SDK d'administration Firebase pour supprimer le mappage jeton/sujet du backend FCM.

L'avantage de ces deux étapes est que vos déploiements se produiront plus rapidement car il y a moins de jetons obsolètes à diffuser, et vos instances d'application obsolètes se réabonneront automatiquement une fois qu'elles seront à nouveau actives.

Mesurer le succès de la livraison

En règle générale, nous vous conseillons de cibler les messages en fonction des actions observées ou capturées à partir d'instances d'application activement utilisées. Ceci est particulièrement important si vous envoyez régulièrement des messages à des sujets avec un grand nombre d'abonnés ; si une partie de ces abonnés est réellement inactive, l'impact sur vos statistiques de livraison peut être significatif au fil du temps.

Avant de cibler des messages sur un jeton, considérez :

  • Google Analytics, les données capturées dans BigQuery ou d'autres signaux de suivi indiquent-ils que le jeton est actif ?
  • Les tentatives de livraison précédentes ont-elles échoué de manière constante sur une période donnée ?
  • Le jeton d'enregistrement a-t-il été mis à jour sur vos serveurs au cours des deux derniers mois ?
  • Pour les appareils Android, l' API de données FCM signale-t-elle un pourcentage élevé d'échecs de livraison de messages dus à droppedDeviceInactive ?

Pour plus d'informations sur la remise, voir Présentation de la remise des messages .