Habilitación de capacidades sin conexión en JavaScript

Las aplicaciones de Firebase funcionan incluso si su aplicación pierde temporalmente su conexión de red. Proporcionamos varias herramientas para monitorear la presencia y sincronizar el estado local con el estado del servidor, que se presentan en este documento.

Gestionar la presencia

En aplicaciones en tiempo real, suele resultar útil detectar cuándo los clientes se conectan y desconectan. Por ejemplo, es posible que desees marcar a un usuario como "fuera de línea" cuando su 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 independientemente de que el cliente se desconecte limpiamente o no, por lo que puede confiar en ellas para limpiar los datos incluso si se interrumpe una conexión o un cliente falla. Todas las operaciones de escritura, incluida la configuración, actualización y eliminación, se pueden realizar tras una desconexión.

A continuación se muestra un ejemplo sencillo de escritura de datos tras una desconexión utilizando la primitiva onDisconnect :

Web modular API

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 namespaced API

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() , la operación reside en el servidor de Firebase Realtime Database. El servidor verifica la seguridad para asegurarse de que el usuario pueda realizar el evento de escritura solicitado e informa a su aplicación si no es válido. Luego, el servidor monitorea la conexión. Si en algún momento la conexión se agota o el cliente Realtime Database la cierra activamente, el servidor verifica la seguridad por segunda vez (para asegurarse de que la operación aún sea válida) y luego invoca el evento.

Su aplicación puede usar la devolución de llamada en la operación de escritura para garantizar que onDisconnect se adjuntó correctamente:

Web modular API

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

Web namespaced API

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

Un evento onDisconnect también se puede cancelar llamando .cancel() :

Web modular API

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

Web namespaced API

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

Detectando el estado de la conexión

Para muchas funciones relacionadas con la presencia, es útil que su aplicación sepa cuándo está en línea o fuera de línea. 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. Aquí hay un ejemplo:

Web modular API

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 namespaced API

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 está sincronizado entre clientes de Realtime Database porque el valor depende del estado del cliente. En otras palabras, si un cliente lee /.info/connected como falso, esto no garantiza que otro cliente también lea falso.

Manejo de 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 característica, combinada con onDisconnect , proporciona una manera fácil de tomar nota de manera confiable del momento en que un cliente de Realtime Database se desconectó:

Web modular API

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

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

Web namespaced API

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

Desviación del reloj

Si bien firebase.database.ServerValue.TIMESTAMP es mucho más preciso y preferible para la mayoría de las operaciones de lectura/escritura, en ocasiones puede resultar útil para estimar la desviación del reloj del cliente con respecto a 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 al tiempo reportado local (tiempo de época en milisegundos) para estimar el tiempo del servidor. Tenga en cuenta que la precisión de esta compensación puede verse afectada por la latencia de la red y, por lo tanto, es útil principalmente para descubrir grandes discrepancias (> 1 segundo) en el tiempo del reloj.

Web modular API

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 namespaced API

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

Aplicación de presencia de muestra

Al combinar operaciones de desconexión con monitoreo del estado de la conexión y marcas de tiempo del servidor, puede crear un sistema de presencia de usuarios. En este sistema, cada usuario almacena datos en una ubicación de base de datos para indicar si un cliente de Realtime Database está en línea o no. Los clientes configuran esta ubicación como verdadera cuando se conectan y una marca de tiempo cuando se desconectan. Esta marca de tiempo indica la última vez que el usuario determinado estuvo en línea.

Tenga en cuenta que su aplicación debe poner en cola las operaciones de desconexión antes de que un usuario sea marcado en línea, para evitar condiciones de carrera en caso de que la conexión de red del cliente se pierda antes de que ambos comandos puedan enviarse al servidor.

A continuación se muestra un sistema sencillo de presencia de usuarios:

Web modular API

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 namespaced API

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