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

استخدام واجهات برمجة التطبيقات في المتصفّح
إذا لم تكن مكتبة Workbox كافية لتلبية احتياجاتك، تتوفّر العديد من واجهات برمجة التطبيقات ذات المستوى الأدنى لتنفيذ عملية التواصل "في اتجاهين" بين الصفحات وخدمات العاملين. تتضمّن هذه الأجهزة بعض أوجه التشابه والاختلاف:
أوجه التشابه:
- في جميع الحالات، يبدأ التواصل من أحد الطرفين عبر واجهة
postMessage()
ويتم تلقّيه من الطرف الآخر من خلال تنفيذ معالجmessage
. - في الواقع، تتيح لنا جميع واجهات برمجة التطبيقات المتاحة تنفيذ حالات الاستخدام نفسها، ولكن قد يسهّل بعضها عملية التطوير في بعض السيناريوهات.
الاختلافات:
- تتوفّر طرق مختلفة لتحديد الجانب الآخر من عملية التواصل، فبعضها يستخدم مرجعًا صريحًا للسياق الآخر، بينما يمكن للبعض الآخر التواصل بشكل ضمني من خلال عنصر وكيل يتم إنشاء مثيل له على كل جانب.
- وتختلف المتصفحات المتوافقة مع كل منها.

Broadcast Channel API
تتيح واجهة برمجة التطبيقات لقنوات البث التواصل الأساسي بين سياقات التصفّح من خلال عناصر 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...
}
};
كما هو موضّح، ليس هناك إشارة صريحة إلى سياق معيّن، لذا ليس من الضروري الحصول على مرجع أولاً إلى عامل الخدمة أو أي عميل معيّن.

أما العيب فهو أنّ واجهة برمجة التطبيقات متوافقة حاليًا مع Chrome وFirefox وEdge، ولكن لا تتوافق مع متصفحات أخرى مثل Safari.
واجهة برمجة التطبيقات للعملاء
تتيح لك 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
هو خيار جيد للتواصل بسهولة مع جميع علامات التبويب النشطة من عامل الخدمة بطريقة بسيطة نسبيًا. تتوافق واجهة برمجة التطبيقات مع جميع المتصفّحات الرئيسية، ولكن قد لا تتوفّر جميع طرقها، لذا احرص على التحقّق من توافق المتصفّح قبل تنفيذها في موقعك الإلكتروني.
قناة الرسائل
يتطلّب قناة الرسائل تحديد منفذ وتمريره من سياق إلى آخر لإنشاء قناة اتصال ثنائية الاتجاه.
لتهيئة القناة، تنشئ الصفحة عنصر 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 المطلوب تخزينها مؤقتًا من سياق إلى آخر. في هذا القسم، سنتعرّف على واجهتَي برمجة تطبيقات للتعامل مع سيناريوهَين محدّدين، وهما: عدم توفّر اتصال بالإنترنت وعمليات التنزيل الطويلة.
المزامنة في الخلفية
قد يريد تطبيق محادثة التأكّد من عدم فقدان الرسائل أبدًا بسبب ضعف الاتصال. تتيح لك واجهة برمجة التطبيقات 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" ميزة "المزامنة في الخلفية" للاحتفاظ بالطلبات التي تعذّر إجراؤها بسبب ضعف الاتصال، وإعادة محاولة إجرائها لاحقًا عندما يكون المستخدم متصلاً بالإنترنت. بعد تنفيذ العملية، يتم إبلاغ المستخدم بالنتيجة من خلال إشعار فوري على الويب:

استرجاع البيانات في الخلفية
بالنسبة إلى المهام القصيرة نسبيًا، مثل إرسال رسالة أو قائمة بعناوين 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}%`);
});

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