Präsenz aufbauen

Abhängig von der Art der Anwendung, die Sie erstellen, ist es möglicherweise hilfreich zu erkennen, welche Ihrer Nutzer oder Geräte aktiv online sind. Dies wird auch als Erkennen der "Präsenz" bezeichnet.

Wenn Sie beispielsweise eine Anwendung (etwa ein soziales Netzwerk) erstellen oder einen Pool mit IoT-Geräten bereitstellen, können Sie mithilfe dieser Informationen eine Liste der Freunde anzeigen, die online und bereit zu chatten sind, oder Ihre IoT-Geräte nach "Zuletzt erfasst" sortieren.

Firestore unterstützt die Präsenzerkennung nicht nativ. Sie können aber andere Firebase-Produkte nutzen, um ein Präsenzsystem zu erstellen.

Lösung: Cloud Functions mit Realtime Database

Verwenden Sie Cloud Functions, um Firestore mit dem nativen Präsenzfeature von Firebase Realtime Database zu verbinden.

Verwenden Sie Realtime Database, um den Verbindungsstatus abzurufen, und verwenden Sie dann Cloud Functions, um diese Daten in Firestore zu spiegeln.

Präsenz in Realtime Database verwenden

Betrachten Sie zuerst, wie ein herkömmliches Präsenzsystem in Realtime Database funktioniert.

Web

// Fetch the current user's ID from Firebase Authentication.
var uid = firebase.auth().currentUser.uid;

// Create a reference to this user's specific status node.
// This is where we will store data about being online/offline.
var userStatusDatabaseRef = firebase.database().ref('/status/' + uid);

// We'll create two constants which we will write to
// the Realtime database when this device is offline
// or online.
var isOfflineForDatabase = {
    state: 'offline',
    last_changed: firebase.database.ServerValue.TIMESTAMP,
};

var isOnlineForDatabase = {
    state: 'online',
    last_changed: firebase.database.ServerValue.TIMESTAMP,
};

// Create a reference to the special '.info/connected' path in
// Realtime Database. This path returns `true` when connected
// and `false` when disconnected.
firebase.database().ref('.info/connected').on('value', function(snapshot) {
    // If we're not currently connected, don't do anything.
    if (snapshot.val() == false) {
        return;
    };

    // If we are currently connected, then use the 'onDisconnect()'
    // method to add a set which will only trigger once this
    // client has disconnected by closing the app,
    // losing internet, or any other means.
    userStatusDatabaseRef.onDisconnect().set(isOfflineForDatabase).then(function() {
        // The promise returned from .onDisconnect().set() will
        // resolve as soon as the server acknowledges the onDisconnect()
        // request, NOT once we've actually disconnected:
        // https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect

        // We can now safely set ourselves as 'online' knowing that the
        // server will mark us as offline once we lose connection.
        userStatusDatabaseRef.set(isOnlineForDatabase);
    });
});

Dieses Beispiel ist ein vollständiges Realtime Database-Präsenzsystem. Es verarbeitet mehrere Verbindungsabbrüche, Abstürze und so weiter.

Verbindung zu Firestore herstellen

Verwenden Sie denselben Realtime Database-Code, um eine ähnliche Lösung in Firestore zu implementieren, und verwenden Sie dann Cloud Functions, um Realtime Database und Firestore zu synchronisieren.

Fügen Sie Realtime Database zu Ihrem Projekt hinzu und schließen Sie die obige Präsenzlösung ein, falls Sie dies noch nicht getan haben.

Als Nächstes synchronisieren Sie den Präsenzstatus mithilfe der folgenden Methoden mit Firestore:

  1. Lokal im Firestore-Cache des Offlinegeräts, damit der Anwendung mitgeteilt wird, dass sie offline ist.
  2. Global mithilfe von Cloud Functions, damit allen anderen Geräten, die auf Firestore zugreifen, mitgeteilt wird, dass dieses Gerät offline ist.

Lokalen Cache von Firestore aktualisieren

Sehen wir uns die Änderungen an, die zur Lösung der ersten Anforderung erforderlich sind: das Aktualisieren des lokalen Caches von Firestore.

Web

// ...
var userStatusFirestoreRef = firebase.firestore().doc('/status/' + uid);

// Firestore uses a different server timestamp value, so we'll
// create two more constants for Firestore state.
var isOfflineForFirestore = {
    state: 'offline',
    last_changed: firebase.firestore.FieldValue.serverTimestamp(),
};

var isOnlineForFirestore = {
    state: 'online',
    last_changed: firebase.firestore.FieldValue.serverTimestamp(),
};

firebase.database().ref('.info/connected').on('value', function(snapshot) {
    if (snapshot.val() == false) {
        // Instead of simply returning, we'll also set Firestore's state
        // to 'offline'. This ensures that our Firestore cache is aware
        // of the switch to 'offline.'
        userStatusFirestoreRef.set(isOfflineForFirestore);
        return;
    };

    userStatusDatabaseRef.onDisconnect().set(isOfflineForDatabase).then(function() {
        userStatusDatabaseRef.set(isOnlineForDatabase);

        // We'll also add Firestore set here for when we come online.
        userStatusFirestoreRef.set(isOnlineForFirestore);
    });
});

Mit diesen Änderungen wird bewirkt, dass der lokale Firestore-Status immer den Online- bzw. Offlinestatus des Geräts widerspiegelt. Das bedeutet, dass Sie das Dokument /status/{uid} überwachen und die Daten verwenden können, um Ihre UI entsprechend dem Verbindungsstatus zu ändern.

Web

userStatusFirestoreRef.onSnapshot(function(doc) {
    var isOnline = doc.data().state == 'online';
    // ... use isOnline
});

Firestore global aktualisieren

Obwohl unsere Anwendung die Onlinepräsenz nun korrekt an sich selbst meldet, ist dieser Status in anderen Firestore-Anwendungen noch nicht korrekt, da unsere Schreibvorgänge im Status "Offline" nur lokal sind und nicht synchronisiert werden, wenn die Verbindung wiederhergestellt wird. Zum Zweck der Synchronisierung verwenden wir eine Cloud Functions-Funktion, die den Pfad status/{uid} in Realtime Database überwacht. Wenn sich der Realtime Database-Wert ändert, wird er mit Firestore synchronisiert, sodass die Status aller Nutzer korrekt sind.

Node.js

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();

// Since this code will be running in the Cloud Functions environment
// we call initialize Firestore without any arguments because it
// detects authentication from the environment.
const firestore = admin.firestore();

// Create a new function which is triggered on changes to /status/{uid}
// Note: This is a Realtime Database trigger, *not* Cloud Firestore.
exports.onUserStatusChanged = functions.database.ref('/status/{uid}').onUpdate(
    async (change, context) => {
      // Get the data written to Realtime Database
      const eventStatus = change.after.val();

      // Then use other event data to create a reference to the
      // corresponding Firestore document.
      const userStatusFirestoreRef = firestore.doc(`status/${context.params.uid}`);

      // It is likely that the Realtime Database change that triggered
      // this event has already been overwritten by a fast change in
      // online / offline status, so we'll re-read the current data
      // and compare the timestamps.
      const statusSnapshot = await change.after.ref.once('value');
      const status = statusSnapshot.val();
      console.log(status, eventStatus);
      // If the current timestamp for this data is newer than
      // the data that triggered this event, we exit this function.
      if (status.last_changed > eventStatus.last_changed) {
        return null;
      }

      // Otherwise, we convert the last_changed field to a Date
      eventStatus.last_changed = new Date(eventStatus.last_changed);

      // ... and write it to Firestore.
      return userStatusFirestoreRef.set(eventStatus);
    });

Nach der Bereitstellung dieser Funktion haben Sie ein vollständiges Präsenzsystem, das auf Firestore fußt. Im folgenden Beispiel werden Nutzer, die online oder offline gehen, mithilfe einer where()-Abfrage überwacht.

Web

firebase.firestore().collection('status')
    .where('state', '==', 'online')
    .onSnapshot(function(snapshot) {
        snapshot.docChanges().forEach(function(change) {
            if (change.type === 'added') {
                var msg = 'User ' + change.doc.id + ' is online.';
                console.log(msg);
                // ...
            }
            if (change.type === 'removed') {
                var msg = 'User ' + change.doc.id + ' is offline.';
                console.log(msg);
                // ...
            }
        });
    });

Beschränkungen

Die Verwendung von Realtime Database zum Hinzufügen von Präsenz zu Ihrer Firestore-Anwendung ist skalierbar und effektiv, hat jedoch einige Beschränkungen:

  • Entprellung: Wenn Sie Echtzeitänderungen in Firestore überwachen, werden mit dieser Lösung wahrscheinlich mehrere Änderungen ausgelöst. Wenn diese Änderungen mehr Ereignisse als gewünscht auslösen, müssen Sie die Firestore-Ereignisse manuell entprellen.
  • Konnektivität: Mit dieser Implementierung wird die Konnektivität zu Realtime Database und nicht zu Firestore gemessen. Wenn der Verbindungsstatus zu den einzelnen Datenbanken nicht derselbe ist, gibt diese Lösung möglicherweise einen falschen Präsenzstatus wieder.
  • Android: Unter Android trennt Realtime Database die Verbindung zum Back-End nach 60 Sekunden Inaktivität. Inaktivität bedeutet, dass es keine offenen Listener oder ausstehenden Vorgänge gibt. Wir empfehlen, zusätzlich zu .info/connected einen Wert-Ereignis-Listener zu einem Pfad hinzuzufügen, um die Verbindung offen zu halten. Beispielsweise können Sie zu Beginn jeder Sitzung FirebaseDatabase.getInstance().getReference((new Date()).toString()).keepSynced() verwenden. Weitere Informationen finden Sie unter Verbindungsstatus ermitteln.