تمكين إمكانيات وضع عدم الاتصال في JavaScript

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

إدارة التواجد في المنزل

في التطبيقات التي تعمل في الوقت الفعلي، من المفيد غالبًا رصد حالات اتصال العملاء وانقطاع اتصالهم. على سبيل المثال، قد تريد وضع علامة "غير متصل" على مستخدم عندما ينقطع اتصال العميل.

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

في ما يلي مثال بسيط على كتابة البيانات عند قطع الاتصال باستخدام العنصر الأساسي 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);

Clock Skew

مع أنّ firebase.database.ServerValue.TIMESTAMP أكثر دقة بكثير، وهو الخيار المفضّل لمعظم عمليات القراءة والكتابة، قد يكون من المفيد أحيانًا تقدير انحراف ساعة العميل بالنسبة إلى خوادم Firebase Realtime Database. يمكنك إرفاق دالة ردّ بالموقع الجغرافي /.info/serverTimeOffset للحصول على القيمة بالمللي ثانية التي تضيفها برامج Firebase Realtime Database إلى الوقت المحلي المسجّل (الوقت منذ بداية الحقبة بالمللي ثانية) لتقدير وقت الخادم. يُرجى العِلم أنّ دقة هذا الإزاحة يمكن أن تتأثر ببطء الشبكة، وبالتالي فهي مفيدة بشكل أساسي في رصد التناقضات الكبيرة (أكثر من ثانية واحدة) في الوقت.

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

تطبيق Sample Presence

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

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

في ما يلي نظام بسيط لتحديد حالة المستخدم:

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