בניית נוכחות ב-Cloud Firestore

בהתאם לסוג האפליקציה שאתם מפתחים, יכול להיות שיהיה שימושי לזהות אילו מהמשתמשים או המכשירים שלכם נמצאים באופן פעיל באינטרנט – או במילים אחרות, לזהות את 'הנוכחות' שלהם.

לדוגמה, אם אתם מפתחים אפליקציה כמו רשת חברתית או פורסים אוסף של מכשירי IoT, תוכלו להשתמש במידע הזה כדי להציג רשימה של חברים שמחוברים לצ'אט באינטרנט, או למיין את מכשירי ה-IoT שלכם לפי 'נראה לאחרונה'.

Cloud Firestore לא תומך באופן מובנה בסטטוס 'נמצא בקרבת מקום', אבל אפשר להשתמש במוצרים אחרים של Firebase כדי ליצור מערכת לסטטוס 'נמצא בקרבת מקום'.

הפתרון: Cloud Functions עם מסד נתונים בזמן אמת

כדי לקשר את Cloud Firestore לתכונה המובנית של Firebase Realtime Database למעקב אחרי נוכחות, צריך להשתמש ב-Cloud Functions.

משתמשים ב-Realtime Database כדי לדווח על סטטוס החיבור, ואז משתמשים ב-Cloud Functions כדי לשקף את הנתונים האלה ב-Cloud Firestore.

שימוש בנוכחות במסד נתונים בזמן אמת

קודם נראה איך מערכת סטנדרטית למעקב אחרי נוכחות פועלת ב-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);
    });
});

הדוגמה הזו היא מערכת מלאה למעקב אחרי נוכחות ב-Realtime Database. הוא מטפל במספר ניתוקים, קריסות וכו'.

מתבצע חיבור אל Cloud Firestore

כדי להטמיע פתרון דומה ב-Cloud Firestore, צריך להשתמש באותו קוד של מסד נתונים בזמן אמת ואז להשתמש ב-Cloud Functions כדי לשמור על סנכרון של מסד הנתונים בזמן אמת ושל Cloud Firestore.

אם עדיין לא עשיתם זאת, מוסיפים את מסד הנתונים בזמן אמת לפרויקט וכוללים את הפתרון שלמעלה לזיהוי נוכחות.

בשלב הבא תסנכרנו את סטטוס הנוכחות עם Cloud Firestore באמצעות השיטות הבאות:

  1. באופן מקומי, במטמון Cloud Firestore של המכשיר במצב אופליין, כדי שהאפליקציה תדע שהיא במצב אופליין.
  2. באופן גלובלי, באמצעות Cloud Function, כך שכל המכשירים האחרים שמחוברים ל-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 כי הכתיבה של הסטטוס 'אופליין' היא מקומית בלבד, והיא לא תנוהל בסנכרון כשהחיבור ישוחזר. כדי למנוע את זה, נשתמש בפונקציה של Cloud Functions שעוקבת אחרי הנתיב status/{uid} במסד הנתונים בזמן אמת. כשהערך ב-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);
                // ...
            }
        });
    });

מגבלות

השימוש במסד נתונים בזמן אמת כדי להוסיף נוכחות לאפליקציית Cloud Firestore הוא יעיל וניתן להרחבה, אבל יש לו כמה מגבלות:

  • Debouncing – כשמקשיב לשינויים בזמן אמת ב-Cloud Firestore, הפתרון הזה עלול להפעיל כמה שינויים. אם השינויים האלה יגרמו להפעלה של יותר אירועים ממה שרצוי, תוכלו לבצע דחייה ידנית של האירועים מסוג Cloud Firestore.
  • קישוריות – ההטמעה הזו מודדת את הקישוריות למסד הנתונים בזמן אמת, ולא ל-Cloud Firestore. אם סטטוס החיבור לכל מסד נתונים שונה, יכול להיות שהפתרון הזה ידווח על מצב נוכחות שגוי.
  • Android – ב-Android, מסד הנתונים בזמן אמת מתנתק מקצה העורפי אחרי 60 שניות של חוסר פעילות. אי-פעילות פירושה שאין מאזינים פתוחים או פעולות בהמתנה. כדי שהחיבור יישאר פתוח, מומלץ להוסיף פונקציות event listener לנתיב מלבד .info/connected. לדוגמה, אפשר לעשות FirebaseDatabase.getInstance().getReference((new Date()).toString()).keepSynced() בתחילת כל סשן. מידע נוסף זמין במאמר זיהוי מצב החיבור.