Las aplicaciones de Firebase funcionan incluso si tu app pierde temporalmente la conexión de red. Proporcionamos varias herramientas para controlar la presencia y sincronizar el estado local con el estado del servidor, conceptos que se presentan en este documento.
Administra la presencia
En aplicaciones en tiempo real, detectar en qué momento se conectan y desconectan los clientes suele ser útil. Por ejemplo, puede ser conveniente marcar a un usuario como "sin conexión" cuando el cliente se desconecta.
Los clientes de Firebase Database proporcionan primitivas simples que puedes usar para escribir en la base de datos cuando un cliente se desconecta de los servidores de Firebase Database. Estas actualizaciones ocurren sin importar si el cliente se desconecta de manera limpia o no, de modo que puedes confiar en ellas y limpiar los datos, incluso si se pierde la conexión o un cliente falla. Cuando ocurre una desconexión, se pueden ejecutar todas las operaciones de escritura (que incluyen configurar, actualizar y quitar).
A continuación, se muestra un ejemplo sencillo de escritura de datos cuando ocurre una desconexión, mediante la primitiva onDisconnect
:
Web
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
var presenceRef = firebase.database().ref("disconnectmessage"); // Write a string when this client loses connection presenceRef.onDisconnect().set("I disconnected!");
Cómo funciona onDisconnect
Cuando estableces una operación onDisconnect()
, esta se aloja
en el servidor Firebase Realtime Database. El servidor verifica la seguridad para comprobar que el usuario pueda ejecutar el evento de escritura solicitado y le informa a la app si no es válido. Después, el servidor supervisa la conexión. Si en algún momento se agota el tiempo de espera de
la conexión o el cliente de Realtime Database la cierra, el servidor verifica la seguridad por
segunda vez (para asegurarse de que la operación todavía sea válida) y después invoca
el evento.
La app puede usar la devolución de llamada en la operación de escritura para asegurarse de que se haya adjuntado correctamente la operación onDisconnect
:
Web
onDisconnect(presenceRef).remove().catch((err) => { if (err) { console.error("could not establish onDisconnect event", err); } });
Web
presenceRef.onDisconnect().remove((err) => { if (err) { console.error("could not establish onDisconnect event", err); } });
Los eventos onDisconnect
también se pueden cancelar llamando a .cancel()
:
Web
const onDisconnectRef = onDisconnect(presenceRef); onDisconnectRef.set("I disconnected"); // some time later when we change our minds onDisconnectRef.cancel();
Web
var onDisconnectRef = presenceRef.onDisconnect(); onDisconnectRef.set("I disconnected"); // some time later when we change our minds onDisconnectRef.cancel();
Detecta el estado de conexión
Para muchas funciones relacionadas con la presencia, es útil que la app sepa si está en línea o no. Firebase Realtime Database
proporciona una ubicación especial en /.info/connected
, que
se actualiza cada vez que cambia el estado de conexión del cliente
de Firebase Realtime Database. A continuación, se muestra un ejemplo:
Web
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
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
es un valor booleano que no se
sincroniza entre clientes de Realtime Database, ya que el valor depende
del estado del cliente. En otras palabras, si un cliente
lee que /.info/connected
es falso, no se
garantiza que otro cliente también lo lea como falso.
Administra la latencia
Marcas de tiempo del servidor
Los servidores de Firebase Realtime Database proporcionan un mecanismo para insertar
marcas de tiempo generadas en el servidor como datos. Esta función, combinada con
onDisconnect
, proporciona una manera simple y confiable de tomar nota de
la hora a la que se desconectó el cliente de Realtime Database:
Web
import { getDatabase, ref, onDisconnect, serverTimestamp } from "firebase/database"; const db = getDatabase(); const userLastOnlineRef = ref(db, "users/joe/lastOnline"); onDisconnect(userLastOnlineRef).set(serverTimestamp());
Web
var userLastOnlineRef = firebase.database().ref("users/joe/lastOnline"); userLastOnlineRef.onDisconnect().set(firebase.database.ServerValue.TIMESTAMP);
Sesgo de reloj
Aunque firebase.database.ServerValue.TIMESTAMP
es mucho más
preciso y se prefiere para la mayoría de las operaciones de lectura o escritura,
en ocasiones, puede ser útil hacer un cálculo aproximado del sesgo de reloj del cliente en
relación con los servidores de Firebase Realtime Database. Puedes
adjuntar una devolución de llamada a la ubicación /.info/serverTimeOffset
para obtener el valor en milisegundos que los clientes de Firebase Realtime Database
agregan a la hora local informada (época en milisegundos) para calcular de manera aproximada
la hora del servidor. Ten en cuenta que la precisión de este ajuste horario puede verse afectada por la latencia de la red, por lo que es útil principalmente para descubrir discrepancias grandes en la hora del reloj (de más de 1 segundo).
Web
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
var offsetRef = firebase.database().ref(".info/serverTimeOffset"); offsetRef.on("value", (snap) => { var offset = snap.val(); var estimatedServerTimeMs = new Date().getTime() + offset; });
Ejemplo de app con presencia
Puedes combinar operaciones de desconexión con la supervisión del estado de conexión y las marcas de tiempo del servidor para crear un sistema de presencia del usuario. En este sistema, cada usuario almacena datos en una ubicación de la base de datos para indicar si el cliente de Realtime Database está en línea. Los clientes configuran esta ubicación con el valor "true" cuando están en línea y dejan una marca de tiempo cuando se desconectan. La marca de tiempo indica la hora en que el usuario estuvo en línea por última vez.
Ten en cuenta que la app debe poner en cola las operaciones de desconexión antes de que se marque a un usuario como "en línea", a fin de evitar cualquier condición de carrera en caso de que la conexión de red del cliente se pierda antes de que se envíen al servidor los dos comandos.
Aquí se muestra un sistema simple de presencia del usuario:
Web
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
// 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); } });