Habilita funciones sin conexión en JavaScript

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);
  }
});