تعزيز حضورك في Cloud Firestore

استنادًا إلى نوع التطبيق الذي تنشئه، قد يكون من المفيد رصد المستخدمين أو الأجهزة المتصلة بالإنترنت بشكل نشط، وهو ما يُعرف أيضًا باسم رصد "التواجد".

على سبيل المثال، إذا كنت تنشئ تطبيقًا مثل شبكة اجتماعية أو تنشر مجموعة من أجهزة إنترنت الأشياء، يمكنك استخدام هذه المعلومات لعرض قائمة بالأصدقاء المتصلين بالإنترنت والمتاحين للدردشة، أو فرز أجهزة إنترنت الأشياء حسب "آخر ظهور".

لا تتيح Cloud Firestore ميزة "التواجد" بشكل تلقائي، ولكن يمكنك الاستفادة من منتجات Firebase الأخرى لإنشاء نظام تواجد.

الحلّ: Cloud Functions مع قاعدة بيانات الوقت الفعلي

لربط Cloud Firestore بميزة الحضور الأصلية في "قاعدة بيانات Firebase في الوقت الفعلي"، استخدِم 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، استخدِم رمز قاعدة بيانات الوقت الفعلي نفسه، ثم استخدِم Cloud Functions لإبقاء قاعدة بيانات الوقت الفعلي وCloud Firestore متزامنتَين.

أضِف قاعدة بيانات الوقت الفعلي إلى مشروعك، في حال لم يسبق لك إجراء ذلك، وضمِّن حلّ التواجد أعلاه.

بعد ذلك، عليك مزامنة حالة التواجد مع "Cloud Firestore" من خلال الطرق التالية:

  1. محليًا، إلى ذاكرة التخزين المؤقت Cloud Firestore للجهاز غير المتصل بالإنترنت ليعرف التطبيق أنّه غير متصل بالإنترنت.
  2. على مستوى العالم، باستخدام دالة Cloud Function لكي تعرف جميع الأجهزة الأخرى التي تصل إلى Cloud Firestore أنّ هذا الجهاز تحديدًا غير متصل بالإنترنت.

لا يمكن تشغيل الدوال المقترَحة في هذا البرنامج التعليمي في تطبيق عميل، بل يجب نشرها في Cloud Functions for Firebase، وهي تتطلّب منطقًا من جهة الخادم من "مدير SDK في Firebase". للحصول على إرشادات مفصّلة، يُرجى الاطّلاع على مستندات 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} في قاعدة بيانات الوقت الفعلي. عندما تتغيّر قيمة قاعدة بيانات الوقت الفعلي، ستتم مزامنة القيمة مع 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 هو حلّ قابل للتوسّع وفعّال، ولكنّه يتضمّن بعض القيود:

  • إلغاء الارتداد: عند الاستماع إلى التغييرات في الوقت الفعلي في Cloud Firestore، من المرجّح أن يؤدي هذا الحل إلى إجراء تغييرات متعددة. إذا أدّت هذه التغييرات إلى تشغيل أحداث أكثر مما تريد، يمكنك إلغاء ارتداد أحداث Cloud Firestore يدويًا.
  • الاتصال: يقيس هذا التنفيذ الاتصال بقاعدة بيانات الوقت الفعلي، وليس بـ Cloud Firestore. إذا لم تكن حالة الاتصال بكل قاعدة بيانات متطابقة، قد يعرض هذا الحلّ حالة حضور غير صحيحة.
  • ‫Android: على أجهزة Android، يتم قطع اتصال قاعدة بيانات الوقت الفعلي بالخلفية بعد 60 ثانية من عدم النشاط. يعني عدم النشاط عدم وجود مستمعين مفتوحين أو عمليات معلّقة. لإبقاء الاتصال مفتوحًا، ننصحك بإضافة متتبِّع أحداث القيمة إلى مسار آخر غير .info/connected. على سبيل المثال، يمكنك إجراء FirebaseDatabase.getInstance().getReference((new Date()).toString()).keepSynced() في بداية كل جلسة. لمزيد من المعلومات، يُرجى الاطّلاع على رصد حالة الاتصال.