במקרים מסוימים, יכול להיות שאפליקציית אינטרנט תצטרך ליצור ערוץ תקשורת דו-כיווני בין הדף לבין ה-service worker.
לדוגמה: באפליקציית PWA של פודקאסט, אפשר ליצור תכונה שמאפשרת למשתמש להוריד פרקים להאזנה אופליין, ולאפשר ל-Service Worker לעדכן את הדף באופן קבוע לגבי ההתקדמות, כדי שה-thread הראשי יוכל לעדכן את ממשק המשתמש.
במדריך הזה נסביר על הדרכים השונות להטמעת תקשורת דו-כיוונית בין ההקשר של Window לבין ההקשר של service worker, באמצעות ממשקי API שונים, ספריית Workbox ומקרים מתקדמים.
שימוש ב-Workbox
workbox-window הוא קבוצה של מודולים של ספריית Workbox שמיועדים לפעול בהקשר של החלון. הכיתה Workbox
מספקת שיטה messageSW() לשליחת הודעה ל-Service Worker הרשום של המכונה ולהמתנה לתגובה.
קוד הדף הבא יוצר מופע חדש של Workbox ושולח הודעה ל-service worker כדי לקבל את הגרסה שלו:
const wb = new Workbox('/sw.js');
wb.register();
const swVersion = await wb.messageSW({type: 'GET_VERSION'});
console.log('Service Worker version:', swVersion);
ה-service worker מטמיע מאזין להודעות בקצה השני, ומגיב ל-service worker הרשום:
const SW_VERSION = '1.0.0';
self.addEventListener('message', (event) => {
if (event.data.type === 'GET_VERSION') {
event.ports[0].postMessage(SW_VERSION);
}
});
מאחורי הקלעים, הספרייה משתמשת ב-API של הדפדפן שנסקור בקטע הבא: Message Channel, אבל היא מסתירה הרבה פרטי הטמעה, מה שמקל על השימוש, תוך ניצול התמיכה הרחבה בדפדפן שיש ל-API הזה.
שימוש בממשקי API של דפדפנים
אם ספריית Workbox לא מספיקה לצרכים שלכם, יש כמה ממשקי API ברמה נמוכה יותר שזמינים להטמעת תקשורת "דו-כיוונית" בין דפים לבין קובצי שירות (service workers). יש ביניהם כמה קווי דמיון והבדלים:
מה דומה:
- בכל המקרים, התקשורת מתחילה בקצה אחד דרך הממשק
postMessage()ומתקבלת בקצה השני על ידי הטמעה של handler מסוגmessage. - בפועל, כל ממשקי ה-API הזמינים מאפשרים לנו להטמיע את אותם תרחישי שימוש, אבל חלק מהם עשויים לפשט את הפיתוח בתרחישים מסוימים.
הבדלים:
- יש להם דרכים שונות לזהות את הצד השני של התקשורת: חלקם משתמשים בהפניה מפורשת להקשר השני, ואחרים יכולים לתקשר באופן משתמע באמצעות אובייקט proxy שנוצר בכל צד.
- התמיכה בדפדפנים משתנה בין השירותים.
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...
}
};
כפי שניתן לראות, אין הפניה מפורשת להקשר מסוים, ולכן אין צורך לקבל קודם הפניה ל-Service Worker או ללקוח מסוים.
החיסרון הוא שנכון למועד כתיבת המאמר הזה, ה-API נתמך ב-Chrome, ב-Firefox וב-Edge, אבל דפדפנים אחרים, כמו Safari, עדיין לא תומכים בו.
Client API
Client API מאפשר לכם לקבל הפניה לכל האובייקטים של WindowClient שמייצגים את הכרטיסיות הפעילות ש-Service Worker שולט בהן.
מכיוון שהדף נשלט על ידי קובץ שירות (service worker) אחד, הוא מאזין ושולח הודעות לקובץ השירות הפעיל ישירות דרך הממשק 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
}
};
באופן דומה, ה-service worker מאזין להודעות על ידי הטמעת מאזין onmessage:
//listen to messages
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'MSG_ID') {
//Process message
}
});
כדי לתקשר בחזרה עם כל אחד מהלקוחות שלו, ה-service worker מקבל מערך של אובייקטים מסוג 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 היא אפשרות טובה לתקשר בקלות עם כל הכרטיסיות הפעילות מ-Service Worker בצורה פשוטה יחסית. ה-API נתמך על ידי כל הדפדפנים המובילים, אבל יכול להיות שלא כל השיטות שלו זמינות, אז חשוב לבדוק את התמיכה בדפדפן לפני שמטמיעים אותו באתר.
ערוץ הודעות
כדי להשתמש ב-Message Channel צריך להגדיר ולהעביר יציאה מהקשר אחד לאחר כדי ליצור ערוץ תקשורת דו-כיווני.
כדי לאתחל את הערוץ, הדף יוצר אובייקט MessageChannel ומשתמש בו כדי לשלוח יציאה ל-Service Worker הרשום. בדף מוגדר גם מאזין 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
};
ה-service worker מקבל את היציאה, שומר הפניה אליה ומשתמש בה כדי לשלוח הודעה לצד השני:
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 בכל הדפדפנים המובילים.
ממשקי API מתקדמים: סנכרון ברקע ואחזור ברקע
במדריך הזה הסברנו איך להטמיע טכניקות תקשורת דו-כיווניות במקרים פשוטים יחסית, כמו העברת הודעת מחרוזת שמתארת את הפעולה שצריך לבצע, או רשימה של כתובות URL לשמירה במטמון מהקשר אחד לאחר. בקטע הזה נסקור שני ממשקי API לטיפול בתרחישים ספציפיים: חוסר קישוריות והורדות ארוכות.
סנכרון ברקע
באפליקציית צ'אט, למשל, חשוב לוודא שהודעות לא יאבדו בגלל חיבור לא יציב. Background Sync API מאפשר לדחות פעולות כדי לנסות אותן שוב כשלמשתמש יש קישוריות יציבה. זה שימושי כדי לוודא שכל מה שהמשתמש רוצה לשלוח, באמת נשלח.
במקום הממשק postMessage(), הדף רושם sync:
navigator.serviceWorker.ready.then(function (swRegistration) {
return swRegistration.sync.register('myFirstSync');
});
לאחר מכן, ה-service worker מאזין לאירוע sync כדי לעבד את ההודעה:
self.addEventListener('sync', function (event) {
if (event.tag == 'myFirstSync') {
event.waitUntil(doSomeStuff());
}
});
הפונקציה doSomeStuff() אמורה להחזיר הבטחה (promise) שמציינת אם הפעולה שהיא מנסה לבצע הצליחה או נכשלה. אם היא מצליחה, הסנכרון הושלם. אם הסנכרון נכשל, המערכת תתזמן סנכרון נוסף כדי לנסות שוב. גם ניסיונות חוזרים לסנכרון ממתינים לחיבור, ומשתמשים בהשהיה מעריכית לפני ניסיון חוזר.
אחרי שהפעולה בוצעה, ה-service worker יכול לתקשר בחזרה עם הדף כדי לעדכן את ממשק המשתמש, באמצעות כל אחד מממשקי ה-API לתקשורת שנסקרו קודם.
חיפוש Google משתמש בסנכרון ברקע כדי לשמור שאילתות שנכשלו בגלל קישוריות לא טובה, ולנסות אותן שוב מאוחר יותר כשהמשתמש מחובר לאינטרנט. אחרי שהפעולה מתבצעת, הם מעבירים את התוצאה למשתמש באמצעות התראה מאפליקציית אינטרנט:
אחזור ברקע
למשימות קצרות יחסית, כמו שליחת הודעה או רשימה של כתובות URL לשמירה במטמון, האפשרויות שבדקנו עד עכשיו הן בחירה טובה. אם המשימה נמשכת יותר מדי זמן, הדפדפן יפסיק את פעולת ה-service worker, אחרת יש סיכון לפרטיות ולסוללה של המשתמש.
Background Fetch API מאפשר להעביר משימה שנמשכת הרבה זמן לקובץ שירות (Service Worker), כמו הורדת סרטים, פודקאסטים או שלבים במשחק.
כדי לתקשר עם ה-service worker מהדף, משתמשים ב-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}%`);
});
השלבים הבאים
במדריך הזה בדקנו את המקרה הכללי ביותר של תקשורת בין דף ל-Service Worker (תקשורת דו-כיוונית).
הרבה פעמים, יכול להיות שצריך רק הקשר אחד כדי לתקשר עם הצד השני, בלי לקבל תשובה. במדריכים הבאים מוסבר איך להטמיע טכניקות חד-כיווניות בדפים שלכם, מ-Service Worker ואליו, וגם תרחישים לדוגמה ודוגמאות לסביבת ייצור:
- מדריך לשמירה הכרחית במטמון: קריאה לקובץ שירות (service worker) מהדף כדי לשמור משאבים במטמון מראש (למשל, בתרחישים של אחזור מראש).
- עדכונים בהפצה רחבה: קריאה לדף מ-Service Worker כדי להודיע על עדכונים חשובים (למשל, גרסה חדשה של אפליקציית האינטרנט זמינה).