في بعض الحالات، قد يحتاج تطبيق الويب إلى إنشاء قناة تواصل ثنائية الاتجاه بين الصفحة وعامل الخدمة.
على سبيل المثال: في تطبيق بودكاست متوافق مع الويب، يمكن إنشاء ميزة تتيح للمستخدم تنزيل الحلقات للاستماع إليها بلا إنترنت والسماح لملف الخدمي بإعلام الصفحة بانتظام بالتقدّم الذي أحرزه التطبيق، حتى تتمكّن سلسلة الرسائل الرئيسية من تعديل واجهة المستخدم.
في هذا الدليل، سنستكشف الطرق المختلفة لتنفيذ اتصالات ثنائية الاتجاه بين Window وسياق service worker، وذلك من خلال استكشاف مكتبة 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);
ينفِّذ worker الخدمة مستمعًا للرسائل في الطرف الآخر، ويستجيب لworker الخدمة المسجَّل:
const SW_VERSION = '1.0.0';
self.addEventListener('message', (event) => {
if (event.data.type === 'GET_VERSION') {
event.ports[0].postMessage(SW_VERSION);
}
});
في الواقع، تستخدم المكتبة واجهة برمجة تطبيقات للمتصفّح سنراجعها في القسم التالي: Message Channel، ولكنّها تلخّص العديد من تفاصيل التنفيذ، ما يسهّل استخدامها، مع الاستفادة من التوافق الواسع النطاق مع المتصفّحات الذي توفّره واجهة برمجة التطبيقات هذه.
استخدام واجهات برمجة تطبيقات المتصفّح
إذا لم تكن مكتبة Workbox كافية لتلبية احتياجاتك، تتوفّر عدة واجهات برمجة تطبيقات من المستوى الأدنى لتنفيذ التواصل"الثنائي الاتجاه" بين الصفحات وعمال الخدمة. هناك بعض أوجه التشابه والاختلاف بينهما:
أوجه التشابه:
- في جميع الحالات، يبدأ الاتصال من جهة واحدة عبر واجهة
postMessage()
ويتم استلامه على الطرف الآخر من خلال تنفيذ معالِجmessage
. - من الناحية العملية، تسمح لنا جميع واجهات برمجة التطبيقات المتاحة بتنفيذ حالات الاستخدام نفسها، ولكن قد يؤدي استخدام بعضها إلى تبسيط عملية التطوير في بعض السيناريوهات.
الاختلافات:
- ولكلّ منهما طرق مختلفة لتحديد الجانب الآخر من عملية التواصل: يستخدم بعضها إشارة صريحة إلى السياق الآخر، في حين يمكن للآخرين التواصل بشكل ضمني من خلال وكيل objيتم إنشاؤه على كل جانب.
- تختلف إتاحة المتصفحات حسب كل ميزة.
واجهة برمجة التطبيقات Broadcast Channel API
تتيح 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
تتيح لك 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
من خلال تنفيذ methods مثل
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
حاليًا في جميع
المتصفحات الرئيسية.
واجهات برمجة التطبيقات المتقدّمة: المزامنة في الخلفية وميزة "جلب البيانات في الخلفية"
في هذا الدليل، استكشَفنا طُرق تنفيذ تقنيات التواصل المتبادل، للحالات ال relativamente بسيطة، مثل تمرير رسالة سلسلة تصف العملية التي سيتم تنفيذها، أو قائمة بعناوين 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}%`);
});
الخطوات التالية
في هذا الدليل، اطّلعنا على الحالة الأكثر عمومية للتواصل بين عمال الخدمة والصفحات (التواصل الثنائي الاتجاه).
في كثير من الأحيان، قد يحتاج أحدهما إلى سياق واحد فقط للتواصل مع الآخر، بدون تلقّي ردّ. اطّلِع على الأدلة التالية للحصول على إرشادات حول كيفية تنفيذ تقنيات أحادية الاتجاه في صفحاتك من وإلى الخدمة العاملة، بالإضافة إلى حالات الاستخدام وأمثلة الإنتاج:
- دليل التخزين المؤقت الإلزامي: استدعاء مشغّل خدمة من الصفحة لتخزين الموارد مؤقتًا مسبقًا (على سبيل المثال، في سيناريوهات التحميل المُسبَق).
- نشر التحديثات: استدعاء الصفحة من الخدمة العاملة لإعلام العميل بالتحديثات المهمة (مثل توفّر إصدار جديد من تطبيق الويب).