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
:
API modular 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!");
API con espacio de nombres 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 de 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
:
API modular web
onDisconnect(presenceRef).remove().catch((err) => { if (err) { console.error("could not establish onDisconnect event", err); } });
API con espacio de nombres 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()
:
API modular web
const onDisconnectRef = onDisconnect(presenceRef); onDisconnectRef.set("I disconnected"); // some time later when we change our minds onDisconnectRef.cancel();
API con espacio de nombres 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:
API modular 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"); } });
API con espacio de nombres 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 de inserción de marcas de tiempo que se generan 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:
API modular web
import { getDatabase, ref, onDisconnect, serverTimestamp } from "firebase/database"; const db = getDatabase(); const userLastOnlineRef = ref(db, "users/joe/lastOnline"); onDisconnect(userLastOnlineRef).set(serverTimestamp());
API con espacio de nombres 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. Se puede 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).
API modular 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; });
API con espacio de nombres 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:
API modular 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()); } });
API con espacio de nombres 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); } });