ساختن حضور در Cloud Firestore

بسته به نوع برنامه‌ای که می‌سازید، ممکن است تشخیص اینکه کدام‌یک از کاربران یا دستگاه‌های شما به‌طور فعال آنلاین هستند برایتان مفید باشد — که به آن تشخیص «حضور» نیز می‌گویند.

برای مثال، اگر درحال ساختن برنامه‌ای مثل شبکه اجتماعی هستید یا ناوگانی از دستگاه‌های اینترنت اشیا را مستقر می‌کنید، می‌توانید از این اطلاعات برای نمایش دادن فهرست دوستانی که آنلاین هستند و می‌توانند گپ بزنند استفاده کنید، یا دستگاه‌های اینترنت اشیا را براساس «آخرین بازدید» مرتب کنید.

Cloud Firestore به‌طور پیش‌فرض از حضور پشتیبانی نمی‌کند، اما می‌توانید از دیگر محصولات Firebase برای ساختن سیستم حضور استفاده کنید.

راه‌حل: Cloud Functions با Realtime Database

برای متصل کردن Cloud Firestore به ویژگی حضور بومی «پایگاه داده بی‌درنگ Firebase»، از Cloud Functions استفاده کنید.

از Realtime Database برای گزارش وضعیت اتصال استفاده کنید، سپس از Cloud Functions برای انعکاس آن داده‌ها در Cloud Firestore استفاده کنید.

استفاده از حضور در Realtime Database

ابتدا، نحوه عملکرد سیستم حضور سنتی را در Realtime Database درنظر بگیرید.

وب

// 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 Function به‌طوری‌که همه دستگاه‌های دیگری که به Cloud Firestore دسترسی دارند بدانند این دستگاه خاص آفلاین است.

توابع توصیه‌شده در این آموزش نمی‌توانند در برنامه کارخواه اجرا شوند. این توابع باید در Cloud Functions for Firebase مستقر شوند و به منطق سمت سرور از Firebase Admin SDK نیاز دارند. برای راهنمایی دقیق، اسناد Cloud Functions را ببینید.

درحال به‌روزرسانی حافظه نهان محلی 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 دقیق نخواهد بود زیرا نوشتن وضعیت «آفلاین» ما فقط محلی است و وقتی اتصال بازیابی شود همگام‌سازی نخواهد شد. برای مقابله با این مشکل، از یک Cloud Function استفاده می‌کنیم که مسیر status/{uid} را در Realtime Database نظارت می‌کند. وقتی مقدار Realtime Database تغییر می‌کند، مقدار با Cloud Firestore همگام‌سازی می‌شود تا وضعیت همه کاربران درست باشد.

Node.js

const functions = require('firebase-functions/v1');
const { initializeApp } = require('firebase-admin/app');
const { getFirestore } = require('firebase-admin/firestore');
initializeApp();

// Since this code will be running in the Cloud Functions environment
// we call initialize Firestore without any arguments because it
// detects authentication from the environment.
const firestore = getFirestore();

// Create a new function which is triggered on changes to /status/{uid}
// Note: This is a Realtime Database trigger, *not* Firestore.
exports.onUserStatusChanged = functions.database.ref('/status/{uid}').onUpdate(
    async (change, context) => {
      // Get the data written to Realtime Database
      const eventStatus = change.after.val();

      // Then use other event data to create a reference to the
      // corresponding Firestore document.
      const userStatusFirestoreRef = firestore.doc(`status/${context.params.uid}`);

      // It is likely that the Realtime Database change that triggered
      // this event has already been overwritten by a fast change in
      // online / offline status, so we'll re-read the current data
      // and compare the timestamps.
      const statusSnapshot = await change.after.ref.once('value');
      const status = statusSnapshot.val();
      functions.logger.log(status, eventStatus);
      // If the current timestamp for this data is newer than
      // the data that triggered this event, we exit this function.
      if (status.last_changed > eventStatus.last_changed) {
        return null;
      }

      // Otherwise, we convert the last_changed field to a Date
      eventStatus.last_changed = new Date(eventStatus.last_changed);

      // ... and write it to Firestore.
      return userStatusFirestoreRef.set(eventStatus);
    });

وقتی این تابع را مستقر کنید، سیستم حضور کاملی خواهید داشت که با 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);
                // ...
            }
        });
    });

محدودیت‌ها

استفاده از «پایگاه داده بی‌درنگ» برای افزودن حضور به برنامه Cloud Firestore شما مقیاس‌پذیر و مؤثر است اما محدودیت‌هایی دارد:

  • Debouncing - when listening to realtime changes in Cloud Firestore, this solution is likely to trigger multiple changes. اگر این تغییرات رویدادهای بیشتری از آنچه می‌خواهید راه‌اندازی می‌کند، رویدادهای Cloud Firestore را به‌صورت دستی لرزش‌گیری کنید.
  • اتصال‌پذیری - این پیاده‌سازی اتصال‌پذیری به Realtime Database را اندازه‌گیری می‌کند، نه به Cloud Firestore. اگر وضعیت اتصال به هر پایگاه داده یکسان نباشد، این راه‌حل ممکن است وضعیت حضور نادرستی را گزارش کند.
  • Android - در Android، «پایگاه داده بی‌درنگ» پس‌از ۶۰ ثانیه عدم فعالیت، از پشتیبان قطع می‌شود. غیرفعال بودن یعنی هیچ شنونده باز یا عملیات معلقی وجود ندارد. برای باز نگه داشتن اتصال، توصیه می‌کنیم شنونده رویداد مقدار را به مسیری غیراز .info/connected اضافه کنید. برای مثال، می‌توانید در شروع هر جلسه FirebaseDatabase.getInstance().getReference((new Date()).toString()).keepSynced() را انجام دهید. برای اطلاعات بیشتر، به تشخیص وضعیت اتصال مراجعه کنید.