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

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

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

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

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

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

نموذج تطبيق "التواجد"

من خلال الجمع بين عمليات قطع الاتصال ومراقبة حالة الاتصال و طوابع الوقت للخادم، يمكنك إنشاء نظام لرصد المستخدمين. في هذا النظام، يخزن كل مستخدم البيانات في موقع قاعدة بيانات للإشارة إلى ما إذا كان العميل 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);
  }
});