التواصل الثنائي مع عاملي الخدمة

Andrew Guan
Andrew Guan
Demián Renzulli
Demián Renzulli

في بعض الحالات، قد يحتاج تطبيق الويب إلى إنشاء قناة اتصال ثنائية الاتجاه بين الصفحة وخدمة العامل.

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

في هذا الدليل، سنتعرّف على الطرق المختلفة لتنفيذ عملية تواصل ثنائية الاتجاه بين سياقَي النافذة وعامل الخدمة، وذلك من خلال استكشاف واجهات برمجة التطبيقات المختلفة ومكتبة Workbox وبعض الحالات المتقدّمة.

مخطّط بياني يعرض مشغّل خدمة والصفحة يتبادلان الرسائل.

استخدام Workbox

workbox-window هي مجموعة من الوحدات النمطية الخاصة بمكتبة Workbox والمصمَّمة للتشغيل في سياق النافذة. يوفّر الصف Workbox الطريقة messageSW() لإرسال رسالة إلى عامل الخدمة المسجّل في المثيل وانتظار الرد.

ينشئ رمز الصفحة التالي مثيلاً جديدًا من Workbox ويرسل رسالة إلى عامل الخدمة للحصول على إصداره:

const wb = new Workbox('/sw.js');
wb.register();

const swVersion = await wb.messageSW({type: 'GET_VERSION'});
console.log('Service Worker version:', swVersion);

ينفّذ عامل الخدمة أداة معالجة الرسائل على الطرف الآخر، ويستجيب لعامل الخدمة المسجّل:

const SW_VERSION = '1.0.0';

self.addEventListener('message', (event) => {
  if (event.data.type === 'GET_VERSION') {
    event.ports[0].postMessage(SW_VERSION);
  }
});

تستخدم المكتبة في الخلفية واجهة برمجة تطبيقات للمتصفّح سنراجعها في القسم التالي: MessageChannel، ولكنها تجرّد العديد من تفاصيل التنفيذ، ما يسهّل استخدامها، مع الاستفادة من التوافق الواسع مع المتصفّحات الذي توفّره واجهة برمجة التطبيقات هذه.

مخطّط يوضّح التواصل المتبادل بين الصفحة وService Worker باستخدام Workbox Window

استخدام واجهات برمجة التطبيقات في المتصفّح

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

أوجه التشابه:

  • في جميع الحالات، يبدأ التواصل من أحد الطرفين عبر واجهة postMessage() ويتم تلقّيه من الطرف الآخر من خلال تنفيذ معالج message.
  • في الواقع، تتيح لنا جميع واجهات برمجة التطبيقات المتاحة تنفيذ حالات الاستخدام نفسها، ولكن قد يسهّل بعضها عملية التطوير في بعض السيناريوهات.

الاختلافات:

  • تتوفّر طرق مختلفة لتحديد الجانب الآخر من عملية التواصل، فبعضها يستخدم مرجعًا صريحًا للسياق الآخر، بينما يمكن للبعض الآخر التواصل بشكل ضمني من خلال عنصر وكيل يتم إنشاء مثيل له على كل جانب.
  • وتختلف المتصفحات المتوافقة مع كل منها.
مخطّط يوضّح التواصل في اتجاهين بين الصفحة وService Worker، وواجهات برمجة التطبيقات المتاحة في المتصفّح

‫Broadcast Channel API

Browser Support

  • Chrome: 54.
  • Edge: 79.
  • Firefox: 38.
  • Safari: 15.4.

Source

تتيح واجهة برمجة التطبيقات لقنوات البث التواصل الأساسي بين سياقات التصفّح من خلال عناصر BroadcastChannel.

لتنفيذ ذلك، يجب أولاً أن ينشئ كل سياق عنصر BroadcastChannel بالمعرّف نفسه ويرسل الرسائل ويستقبلها منه:

const broadcast = new BroadcastChannel('channel-123');

يعرض عنصر BroadcastChannel واجهة postMessage() لإرسال رسالة إلى أي سياق يستمع إلى الرسالة:

//send message
broadcast.postMessage({ type: 'MSG_ID', });

يمكن لأي سياق متصفّح الاستماع إلى الرسائل من خلال طريقة onmessage الخاصة بالكائن BroadcastChannel:

//listen to messages
broadcast.onmessage = (event) => {
  if (event.data && event.data.type === 'MSG_ID') {
    //process message...
  }
};

كما هو موضّح، ليس هناك إشارة صريحة إلى سياق معيّن، لذا ليس من الضروري الحصول على مرجع أولاً إلى عامل الخدمة أو أي عميل معيّن.

مخطط يوضّح التواصل في اتجاهين بين الصفحة وService Worker، باستخدام عنصر Broadcast Channel.

أما العيب فهو أنّ واجهة برمجة التطبيقات متوافقة حاليًا مع Chrome وFirefox وEdge، ولكن لا تتوافق مع متصفحات أخرى مثل Safari.

واجهة برمجة التطبيقات للعملاء

Browser Support

  • Chrome: 40.
  • Edge: 17.
  • Firefox: 44.
  • Safari: 11.1.

Source

تتيح لك Client API الحصول على مرجع لجميع عناصر WindowClient التي تمثّل علامات التبويب النشطة التي يتحكّم فيها عامل الخدمة.

بما أنّ مشغّل الخدمات يتحكّم في الصفحة، يستمع إلى الرسائل ويرسلها إلى مشغّل الخدمات النشط مباشرةً من خلال واجهة serviceWorker:

//send message
navigator.serviceWorker.controller.postMessage({
  type: 'MSG_ID',
});

//listen to messages
navigator.serviceWorker.onmessage = (event) => {
  if (event.data && event.data.type === 'MSG_ID') {
    //process response
  }
};

وبالمثل، يستمع عامل الخدمة إلى الرسائل من خلال تنفيذ أداة معالجة onmessage:

//listen to messages
self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'MSG_ID') {
    //Process message
  }
});

للتواصل مع أي من عملائها، يحصل عامل الخدمة على مصفوفة من عناصر WindowClient من خلال تنفيذ طرق مثل Clients.matchAll() و Clients.get(). بعد ذلك، يمكنه postMessage() أيًّا مما يلي:

//Obtain an array of Window client objects
self.clients.matchAll(options).then(function (clients) {
  if (clients && clients.length) {
    //Respond to last focused tab
    clients[0].postMessage({type: 'MSG_ID'});
  }
});
مخطّط يوضّح عامل الخدمة وهو يتواصل مع مجموعة من العملاء

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

قناة الرسائل

Browser Support

  • Chrome: 2.
  • Edge: 12.
  • Firefox: 41.
  • Safari: 5.

Source

يتطلّب قناة الرسائل تحديد منفذ وتمريره من سياق إلى آخر لإنشاء قناة اتصال ثنائية الاتجاه.

لتهيئة القناة، تنشئ الصفحة عنصر MessageChannel وتستخدمه لإرسال منفذ إلى عامل الخدمة المسجَّل. تنفّذ الصفحة أيضًا أداة معالجة onmessage عليها لتلقّي الرسائل من السياق الآخر:

const messageChannel = new MessageChannel();

//Init port
navigator.serviceWorker.controller.postMessage({type: 'PORT_INITIALIZATION'}, [
  messageChannel.port2,
]);

//Listen to messages
messageChannel.port1.onmessage = (event) => {
  // Process message
};
مخطّط بياني يعرض صفحة تمرّر منفذًا إلى عامل خدمة، وذلك لإنشاء اتصال ثنائي الاتجاه.

يتلقّى عامل الخدمة المنفذ ويحفظ مرجعًا إليه ويستخدمه لإرسال رسالة إلى الجانب الآخر:

let communicationPort;

//Save reference to port
self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'PORT_INITIALIZATION') {
    communicationPort = event.ports[0];
  }
});

//Send messages
communicationPort.postMessage({type: 'MSG_ID'});

تتوافق MessageChannel حاليًا مع جميع المتصفّحات الرئيسية.

واجهات برمجة التطبيقات المتقدّمة: المزامنة في الخلفية وجلب البيانات في الخلفية

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

المزامنة في الخلفية

Browser Support

  • Chrome: 49.
  • Edge: 79.
  • Firefox: not supported.
  • Safari: not supported.

Source

قد يريد تطبيق محادثة التأكّد من عدم فقدان الرسائل أبدًا بسبب ضعف الاتصال. تتيح لك واجهة برمجة التطبيقات Background Sync API تأجيل الإجراءات لإعادة محاولة تنفيذها عندما يكون اتصال المستخدم ثابتًا. ويفيد ذلك في ضمان إرسال ما يريد المستخدم إرساله.

بدلاً من واجهة postMessage()، تسجّل الصفحة sync:

navigator.serviceWorker.ready.then(function (swRegistration) {
  return swRegistration.sync.register('myFirstSync');
});

بعد ذلك، يستمع عامل الخدمة إلى الحدث sync لمعالجة الرسالة:

self.addEventListener('sync', function (event) {
  if (event.tag == 'myFirstSync') {
    event.waitUntil(doSomeStuff());
  }
});

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

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

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

مخطّط بياني يعرض صفحة تمرّر منفذًا إلى عامل خدمة، وذلك لإنشاء اتصال ثنائي الاتجاه.

استرجاع البيانات في الخلفية

Browser Support

  • Chrome: 74.
  • Edge: 79.
  • Firefox: not supported.
  • Safari: not supported.

Source

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

تتيح لك واجهة برمجة التطبيقات Background Fetch API تفويض مهمة طويلة إلى عامل خدمة، مثل تنزيل الأفلام أو برامج البودكاست أو مستويات إحدى الألعاب.

للتواصل مع مشغّل الخدمات من الصفحة، استخدِم backgroundFetch.fetch بدلاً من postMessage():

navigator.serviceWorker.ready.then(async (swReg) => {
  const bgFetch = await swReg.backgroundFetch.fetch(
    'my-fetch',
    ['/ep-5.mp3', 'ep-5-artwork.jpg'],
    {
      title: 'Episode 5: Interesting things.',
      icons: [
        {
          sizes: '300x300',
          src: '/ep-5-icon.png',
          type: 'image/png',
        },
      ],
      downloadTotal: 60 * 1024 * 1024,
    },
  );
});

يسمح العنصر BackgroundFetchRegistration للصفحة بالاستماع إلى الحدث progress لتتبُّع تقدُّم عملية التنزيل:

bgFetch.addEventListener('progress', () => {
  // If we didn't provide a total, we can't provide a %.
  if (!bgFetch.downloadTotal) return;

  const percent = Math.round(
    (bgFetch.downloaded / bgFetch.downloadTotal) * 100,
  );
  console.log(`Download progress: ${percent}%`);
});
مخطّط بياني يعرض صفحة تمرّر منفذًا إلى عامل خدمة، وذلك لإنشاء اتصال ثنائي الاتجاه.
يتم تعديل واجهة المستخدم للإشارة إلى مستوى تقدّم عملية التنزيل (على اليمين). بفضل عاملي الخدمة، يمكن أن تستمر العملية في العمل عند إغلاق جميع علامات التبويب (على اليسار).

الخطوات التالية

في هذا الدليل، استعرضنا الحالة الأكثر عمومية للتواصل بين الصفحة وخدمات العاملين (التواصل الثنائي الاتجاه).

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

  • دليل التخزين المؤقت الإلزامي: استدعاء مشغّل خدمة من الصفحة لتخزين الموارد مؤقتًا مسبقًا (على سبيل المثال، في سيناريوهات الجلب المسبق)
  • إرسال إشعارات: يتم استدعاء الصفحة من عامل الخدمة لإبلاغ المستخدمين بالمستجدّات المهمة (مثل توفّر إصدار جديد من تطبيق الويب).