Расширьте присутствие в Cloud Firestore

В зависимости от типа создаваемого вами приложения вам может быть полезно определить, какие из ваших пользователей или устройств активно находятся в сети — это также известно как определение «присутствия».

Например, если вы создаете приложение, такое как социальная сеть, или развертываете парк устройств Интернета вещей, вы можете использовать эту информацию для отображения списка друзей, которые находятся в сети и могут свободно общаться, или сортировать устройства Интернета вещей по времени последнего посещения.

Cloud Firestore изначально не поддерживает присутствие, но вы можете использовать другие продукты Firebase для создания системы присутствия.

Решение: облачные функции с базой данных в реальном времени

Для подключения Cloud Firestore к собственной функции присутствия Firebase Realtime Database используйте Cloud Functions.

Используйте Realtime Database для отчета о состоянии соединения, а затем используйте Cloud Functions для зеркалирования этих данных в Cloud Firestore .

Использование присутствия в базе данных реального времени

Сначала рассмотрим, как традиционная система присутствия работает в базе данных реального времени.

Интернет

// Fetch the current user's ID from Firebase Authentication.
var uid = firebase.auth().currentUser.uid;

// Create a reference to this user's specific status node.
// This is where we will store data about being online/offline.
var userStatusDatabaseRef = firebase.database().ref('/status/' + uid);

// We'll create two constants which we will write to 
// the Realtime database when this device is offline
// or online.
var isOfflineForDatabase = {
    state: 'offline',
    last_changed: firebase.database.ServerValue.TIMESTAMP,
};

var isOnlineForDatabase = {
    state: 'online',
    last_changed: firebase.database.ServerValue.TIMESTAMP,
};

// Create a reference to the special '.info/connected' path in 
// Realtime Database. This path returns `true` when connected
// and `false` when disconnected.
firebase.database().ref('.info/connected').on('value', function(snapshot) {
    // If we're not currently connected, don't do anything.
    if (snapshot.val() == false) {
        return;
    };

    // If we are currently connected, then use the 'onDisconnect()' 
    // method to add a set which will only trigger once this 
    // client has disconnected by closing the app, 
    // losing internet, or any other means.
    userStatusDatabaseRef.onDisconnect().set(isOfflineForDatabase).then(function() {
        // The promise returned from .onDisconnect().set() will
        // resolve as soon as the server acknowledges the onDisconnect() 
        // request, NOT once we've actually disconnected:
        // https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect

        // We can now safely set ourselves as 'online' knowing that the
        // server will mark us as offline once we lose connection.
        userStatusDatabaseRef.set(isOnlineForDatabase);
    });
});

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

Подключение к Cloud Firestore

Чтобы реализовать аналогичное решение в Cloud Firestore используйте тот же код Realtime Database, а затем используйте Cloud Functions для синхронизации Realtime Database и Cloud Firestore .

Если вы еще этого не сделали, добавьте Realtime Database в свой проект и включите указанное выше решение по присутствию.

Далее вам необходимо синхронизировать состояние присутствия с Cloud Firestore с помощью следующих методов:

  1. Локально, в кэше Cloud Firestore автономного устройства, чтобы приложение знало, что оно находится в автономном режиме.
  2. Глобально, используя функцию облака, чтобы все другие устройства, получающие доступ к Cloud Firestore знали, что данное конкретное устройство находится в автономном режиме.

Обновление локального кэша Cloud Firestore

Давайте рассмотрим изменения, необходимые для решения первой проблемы — обновления локального кэша Cloud Firestore .

Интернет

// ...
var userStatusFirestoreRef = firebase.firestore().doc('/status/' + uid);

// Firestore uses a different server timestamp value, so we'll 
// create two more constants for Firestore state.
var isOfflineForFirestore = {
    state: 'offline',
    last_changed: firebase.firestore.FieldValue.serverTimestamp(),
};

var isOnlineForFirestore = {
    state: 'online',
    last_changed: firebase.firestore.FieldValue.serverTimestamp(),
};

firebase.database().ref('.info/connected').on('value', function(snapshot) {
    if (snapshot.val() == false) {
        // Instead of simply returning, we'll also set Firestore's state
        // to 'offline'. This ensures that our Firestore cache is aware
        // of the switch to 'offline.'
        userStatusFirestoreRef.set(isOfflineForFirestore);
        return;
    };

    userStatusDatabaseRef.onDisconnect().set(isOfflineForDatabase).then(function() {
        userStatusDatabaseRef.set(isOnlineForDatabase);

        // We'll also add Firestore set here for when we come online.
        userStatusFirestoreRef.set(isOnlineForFirestore);
    });
});

Благодаря этим изменениям мы гарантируем, что локальное состояние Cloud Firestore всегда будет отражать онлайн/офлайн-статус устройства. Это означает, что вы можете прослушивать документ /status/{uid} и использовать эти данные для изменения пользовательского интерфейса с учётом статуса подключения.

Интернет

userStatusFirestoreRef.onSnapshot(function(doc) {
    var isOnline = doc.data().state == 'online';
    // ... use isOnline
});

Глобальное обновление Cloud Firestore

Хотя наше приложение корректно сообщает себе о присутствии онлайн, этот статус пока не будет точным в других приложениях Cloud Firestore , поскольку запись статуса «офлайн» выполняется только локально и не синхронизируется при восстановлении соединения. Чтобы избежать этого, мы используем облачную функцию, которая отслеживает путь status/{uid} в базе данных Realtime Database. При изменении значения в базе данных Realtime Database оно будет синхронизироваться с Cloud Firestore , чтобы статусы всех пользователей были корректными.

Node.js

firebase.firestore().collection('status')
    .where('state', '==', 'online')
    .onSnapshot(function(snapshot) {
        snapshot.docChanges().forEach(function(change) {
            if (change.type === 'added') {
                var msg = 'User ' + change.doc.id + ' is online.';
                console.log(msg);
                // ...
            }
            if (change.type === 'removed') {
                var msg = 'User ' + change.doc.id + ' is offline.';
                console.log(msg);
                // ...
            }
        });
    });

После развертывания этой функции у вас будет полноценная система контроля присутствия, работающая с Cloud Firestore . Ниже приведён пример мониторинга всех пользователей, подключающихся и отключающихся от сети, с помощью запроса where() .

Интернет

firebase.firestore().collection('status')
    .where('state', '==', 'online')
    .onSnapshot(function(snapshot) {
        snapshot.docChanges().forEach(function(change) {
            if (change.type === 'added') {
                var msg = 'User ' + change.doc.id + ' is online.';
                console.log(msg);
                // ...
            }
            if (change.type === 'removed') {
                var msg = 'User ' + change.doc.id + ' is offline.';
                console.log(msg);
                // ...
            }
        });
    });

Ограничения

Использование Realtime Database для добавления присутствия в приложение Cloud Firestore масштабируемо и эффективно, но имеет некоторые ограничения:

  • Устранение дребезга — при отслеживании изменений в Cloud Firestore в реальном времени это решение, вероятно, вызовет несколько изменений. Если эти изменения вызывают больше событий, чем вам нужно, вручную устраните дребезг событий Cloud Firestore .
  • Подключение — эта реализация измеряет подключение к базе данных Realtime Database, а не к Cloud Firestore . Если состояние подключения к каждой базе данных различается, это решение может сообщать о неверном состоянии присутствия.
  • Android — на Android база данных Realtime отключается от бэкенда через 60 секунд бездействия. Бездействие означает отсутствие открытых прослушивателей или ожидающих операций. Чтобы соединение оставалось открытым, рекомендуется добавить прослушиватель событий value к пути помимо .info/connected . Например, можно выполнить FirebaseDatabase.getInstance().getReference((new Date()).toString()).keepSynced() в начале каждого сеанса. Подробнее см. в разделе Определение состояния соединения .