Check out what’s new from Firebase at Google I/O 2022. Learn more

Aktivieren von Offline-Funktionen in JavaScript

Firebase-Anwendungen funktionieren auch dann, wenn Ihre App vorübergehend ihre Netzwerkverbindung verliert. Wir stellen mehrere Tools zum Überwachen der Präsenz und zum Synchronisieren des lokalen Zustands mit dem Serverzustand bereit, die in diesem Dokument vorgestellt werden.

Präsenz verwalten

In Echtzeitanwendungen ist es oft nützlich zu erkennen, wann Clients sich verbinden und trennen. Sie möchten beispielsweise einen Benutzer als „offline“ markieren, wenn sein Client die Verbindung trennt.

Firebase-Datenbankclients stellen einfache Primitive bereit, mit denen Sie in die Datenbank schreiben können, wenn ein Client die Verbindung zu den Firebase-Datenbankservern trennt. Diese Aktualisierungen erfolgen unabhängig davon, ob der Client die Verbindung sauber trennt oder nicht, sodass Sie sich darauf verlassen können, dass sie Daten bereinigen, selbst wenn eine Verbindung unterbrochen wird oder ein Client abstürzt. Alle Schreiboperationen, einschließlich Setzen, Aktualisieren und Entfernen, können bei einer Trennung durchgeführt werden.

Hier ist ein einfaches Beispiel für das Schreiben von Daten beim Trennen der Verbindung mithilfe der onDisconnect Primitive:

Web version 9

import { getDatabase, ref, onDisconnect } from "firebase/database";

const db = getDatabase();
const presenceRef = ref(db, "disconnectmessage");
// Write a string when this client loses connection
onDisconnect(presenceRef).set("I disconnected!");

Web version 8

var presenceRef = firebase.database().ref("disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnect().set("I disconnected!");

So funktioniert onDisconnect

Wenn Sie eine onDisconnect() -Operation einrichten, befindet sich die Operation auf dem Firebase Realtime Database-Server. Der Server überprüft die Sicherheit, um sicherzustellen, dass der Benutzer das angeforderte Schreibereignis ausführen kann, und informiert Ihre App, wenn es ungültig ist. Der Server überwacht dann die Verbindung. Wenn die Verbindung zu irgendeinem Zeitpunkt abläuft oder vom Realtime Database-Client aktiv geschlossen wird, überprüft der Server die Sicherheit ein zweites Mal (um sicherzustellen, dass die Operation noch gültig ist) und ruft dann das Ereignis auf.

Ihre App kann den Rückruf für den Schreibvorgang verwenden, um sicherzustellen, dass onDisconnect korrekt angehängt wurde:

Web version 9

onDisconnect(presenceRef).remove().catch((err) => {
  if (err) {
    console.error("could not establish onDisconnect event", err);
  }
});

Web version 8

presenceRef.onDisconnect().remove((err) => {
  if (err) {
    console.error("could not establish onDisconnect event", err);
  }
});

Ein onDisconnect Ereignis kann auch durch Aufrufen von .cancel() abgebrochen werden:

Web version 9

const onDisconnectRef = onDisconnect(presenceRef);
onDisconnectRef.set("I disconnected");
// some time later when we change our minds
onDisconnectRef.cancel();

Web version 8

var onDisconnectRef = presenceRef.onDisconnect();
onDisconnectRef.set("I disconnected");
// some time later when we change our minds
onDisconnectRef.cancel();

Verbindungsstatus erkennen

Für viele anwesenheitsbezogene Funktionen ist es hilfreich, wenn Ihre App weiß, wann sie online oder offline ist. Die Firebase-Echtzeitdatenbank bietet einen speziellen Speicherort unter /.info/connected , der jedes Mal aktualisiert wird, wenn sich der Verbindungsstatus des Firebase-Echtzeitdatenbank-Clients ändert. Hier ist ein Beispiel:

Web version 9

import { getDatabase, ref, onValue } from "firebase/database";

const db = getDatabase();
const connectedRef = ref(db, ".info/connected");
onValue(connectedRef, (snap) => {
  if (snap.val() === true) {
    console.log("connected");
  } else {
    console.log("not connected");
  }
});

Web version 8

var connectedRef = firebase.database().ref(".info/connected");
connectedRef.on("value", (snap) => {
  if (snap.val() === true) {
    console.log("connected");
  } else {
    console.log("not connected");
  }
});

/.info/connected ist ein boolescher Wert, der nicht zwischen Realtime Database-Clients synchronisiert wird, da der Wert vom Status des Clients abhängt. Mit anderen Worten, wenn ein Client /.info/connected als falsch liest, ist dies keine Garantie dafür, dass ein anderer Client auch falsch liest.

Umgang mit Latenz

Server-Zeitstempel

Die Firebase Realtime Database-Server bieten einen Mechanismus zum Einfügen von auf dem Server generierten Zeitstempeln als Daten. Diese Funktion bietet in Kombination mit onDisconnect eine einfache Möglichkeit, zuverlässig den Zeitpunkt zu notieren, zu dem ein Realtime Database-Client die Verbindung getrennt hat:

Web version 9

import { getDatabase, ref, onDisconnect, serverTimestamp } from "firebase/database";

const db = getDatabase();
const userLastOnlineRef = ref(db, "users/joe/lastOnline");
onDisconnect(userLastOnlineRef).set(serverTimestamp());

Web version 8

var userLastOnlineRef = firebase.database().ref("users/joe/lastOnline");
userLastOnlineRef.onDisconnect().set(firebase.database.ServerValue.TIMESTAMP);

Zeitversatz

Während firebase.database.ServerValue.TIMESTAMP viel genauer und für die meisten Lese-/Schreibvorgänge vorzuziehen ist, kann es gelegentlich nützlich sein, die Zeitabweichung des Clients in Bezug auf die Server der Firebase Realtime Database zu schätzen. Sie können einen Rückruf an den Speicherort /.info/serverTimeOffset , um den Wert in Millisekunden abzurufen, den Firebase Realtime Database-Clients zur lokal gemeldeten Zeit (Epochenzeit in Millisekunden) hinzufügen, um die Serverzeit zu schätzen. Beachten Sie, dass die Genauigkeit dieses Offsets durch die Netzwerklatenz beeinträchtigt werden kann und daher in erster Linie nützlich ist, um große Abweichungen (> 1 Sekunde) in der Uhrzeit zu erkennen.

Web version 9

import { getDatabase, ref, onValue } from "firebase/database";

const db = getDatabase();
const offsetRef = ref(db, ".info/serverTimeOffset");
onValue(offsetRef, (snap) => {
  const offset = snap.val();
  const estimatedServerTimeMs = new Date().getTime() + offset;
});

Web version 8

var offsetRef = firebase.database().ref(".info/serverTimeOffset");
offsetRef.on("value", (snap) => {
  var offset = snap.val();
  var estimatedServerTimeMs = new Date().getTime() + offset;
});

Beispiel einer Präsenz-App

Durch die Kombination von Trennungsvorgängen mit der Überwachung des Verbindungsstatus und Serverzeitstempeln können Sie ein Benutzerpräsenzsystem aufbauen. In diesem System speichert jeder Benutzer Daten an einer Datenbankstelle, um anzuzeigen, ob ein Client der Echtzeitdatenbank online ist oder nicht. Clients setzen diesen Ort auf „true“, wenn sie online gehen, und einen Zeitstempel, wenn sie die Verbindung trennen. Dieser Zeitstempel gibt an, wann der angegebene Benutzer das letzte Mal online war.

Beachten Sie, dass Ihre App die Trennungsvorgänge in eine Warteschlange stellen sollte, bevor ein Benutzer als online markiert wird, um Racebedingungen zu vermeiden, falls die Netzwerkverbindung des Clients unterbrochen wird, bevor beide Befehle an den Server gesendet werden können.

Hier ist ein einfaches Benutzerpräsenzsystem:

Web version 9

import { getDatabase, ref, onValue, push, onDisconnect, set, serverTimestamp } from "firebase/database";

// Since I can connect from multiple devices or browser tabs, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
const db = getDatabase();
const myConnectionsRef = ref(db, 'users/joe/connections');

// stores the timestamp of my last disconnect (the last time I was seen online)
const lastOnlineRef = ref(db, 'users/joe/lastOnline');

const connectedRef = ref(db, '.info/connected');
onValue(connectedRef, (snap) => {
  if (snap.val() === true) {
    // We're connected (or reconnected)! Do anything here that should happen only if online (or on reconnect)
    const con = push(myConnectionsRef);

    // When I disconnect, remove this device
    onDisconnect(con).remove();

    // Add this device to my connections list
    // this value could contain info about the device or a timestamp too
    set(con, true);

    // When I disconnect, update the last time I was seen online
    onDisconnect(lastOnlineRef).set(serverTimestamp());
  }
});

Web version 8

// Since I can connect from multiple devices or browser tabs, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
var myConnectionsRef = firebase.database().ref('users/joe/connections');

// stores the timestamp of my last disconnect (the last time I was seen online)
var lastOnlineRef = firebase.database().ref('users/joe/lastOnline');

var connectedRef = firebase.database().ref('.info/connected');
connectedRef.on('value', (snap) => {
  if (snap.val() === true) {
    // We're connected (or reconnected)! Do anything here that should happen only if online (or on reconnect)
    var con = myConnectionsRef.push();

    // When I disconnect, remove this device
    con.onDisconnect().remove();

    // Add this device to my connections list
    // this value could contain info about the device or a timestamp too
    con.set(true);

    // When I disconnect, update the last time I was seen online
    lastOnlineRef.onDisconnect().set(firebase.database.ServerValue.TIMESTAMP);
  }
});