Включение автономных возможностей в JavaScript

Приложения Firebase работают, даже если ваше приложение временно теряет подключение к сети. Мы предоставляем несколько инструментов для мониторинга присутствия и синхронизации локального состояния с состоянием сервера, которые представлены в этом документе.

Управление присутствием

В приложениях реального времени часто бывает полезно определить, когда клиенты подключаются и отключаются. Например, вы можете пометить пользователя как «не в сети», когда его клиент отключается.

Клиенты базы данных Firebase предоставляют простые примитивы, которые вы можете использовать для записи в базу данных, когда клиент отключается от серверов базы данных Firebase. Эти обновления происходят независимо от того, отключается клиент или нет, поэтому вы можете положиться на них в очистке данных даже в случае разрыва соединения или сбоя клиента. Все операции записи, включая установку, обновление и удаление, могут быть выполнены после отключения.

Вот простой пример записи данных при отключении с помощью примитива 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!");

Как работает onDisconnect

Когда вы устанавливаете операцию onDisconnect() , она находится на сервере Firebase Realtime Database . Сервер проверяет безопасность, чтобы убедиться, что пользователь может выполнить запрошенное событие записи, и сообщает вашему приложению, если оно недействительно. Затем сервер контролирует соединение. Если в какой-то момент время соединения истекает или оно активно закрывается клиентом Realtime Database , сервер проверяет безопасность второй раз (чтобы убедиться, что операция все еще действительна), а затем вызывает событие.

Ваше приложение может использовать обратный вызов при операции записи, чтобы убедиться, что 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);
  }
});

Событие onDisconnect также можно отменить, вызвав .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();

Определение состояния соединения

Для многих функций, связанных с присутствием, вашему приложению полезно знать, когда оно онлайн или офлайн. Firebase Realtime Database предоставляет специальное местоположение в /.info/connected , которое обновляется каждый раз, когда изменяется состояние подключения клиента Firebase Realtime Database . Вот пример:

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 — это логическое значение, которое не синхронизируется между клиентами Realtime Database , поскольку это значение зависит от состояния клиента. Другими словами, если один клиент читает /.info/connected как ложь, это не гарантирует, что другой клиент также прочтет ложь.

Обработка задержки

Временные метки сервера

Серверы Firebase Realtime Database предоставляют механизм для вставки временных меток, сгенерированных на сервере, в качестве данных. Эта функция в сочетании с onDisconnect обеспечивает простой способ надежного отслеживания времени отключения клиента 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);

Перекос часов

Хотя firebase.database.ServerValue.TIMESTAMP гораздо точнее и предпочтительнее для большинства операций чтения/записи, иногда может быть полезно оценить сдвиг часов клиента по отношению к серверам Firebase Realtime Database . Вы можете прикрепить обратный вызов к местоположению /.info/serverTimeOffset , чтобы получить значение в миллисекундах, которое клиенты Firebase Realtime Database добавляют к локальному отчетному времени (время эпохи в миллисекундах) для оценки времени сервера. Обратите внимание, что на точность этого смещения может влиять задержка в сети, поэтому оно полезно в первую очередь для обнаружения больших (> 1 секунды) расхождений во времени часов.

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

Пример приложения присутствия

Комбинируя операции отключения с мониторингом состояния соединения и временными метками сервера, вы можете построить систему присутствия пользователей. В этой системе каждый пользователь хранит данные в определенном месте базы данных, чтобы указать, находится ли клиент Realtime Database в сети. Клиенты устанавливают для этого местоположения значение true, когда они подключаются к сети, и метку времени, когда они отключаются. Эта временная метка указывает, когда данный пользователь в последний раз был онлайн.

Обратите внимание, что ваше приложение должно поставить в очередь операции отключения до того, как пользователь будет помечен как онлайн, чтобы избежать каких-либо условий гонки в случае, если сетевое соединение клиента будет потеряно до того, как обе команды смогут быть отправлены на сервер.

Вот простая система присутствия пользователя:

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