במקרים מסוימים, יכול להיות שאפליקציית אינטרנט תצטרך ליצור ערוץ תקשורת דו-כיווני בין הדף לבין ה-service worker.
לדוגמה: בפודקאסט ב-PWA אפשר ליצור תכונה שמאפשרת למשתמש להוריד פרקים לצפייה אופליין ולאפשר ל-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);
שירות ה-worker מטמיע מאזין להודעות בקצה השני, ומגיב לשירות ה-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 ברמה נמוכה יותר שזמינים להטמעת תקשורת דו-כיוונית בין דפים לבין שירותי העבודה. יש להם כמה דמיון והבדלים:
תכונות דומות:
- בכל המקרים, התקשורת מתחילה בקצה אחד דרך הממשק
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 שולט בהן.
מכיוון שהדף נשלט על ידי קובץ שירות יחיד, הוא מקשיב להודעות של קובץ השירות הפעיל ושולח לו הודעות ישירות דרך ממשק 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 יכול לתקשר חזרה עם הדף כדי לעדכן את ממשק המשתמש, באמצעות כל אחד מ-APIs לתקשורת שצוינו למעלה.
חיפוש Google משתמש בסנכרון ברקע כדי לשמור שאילתות שנכשלו בגלל חיבור לקוי, ולנסות אותן שוב מאוחר יותר כשהמשתמש מחובר לאינטרנט. בסיום הפעולה, הם מעבירים את התוצאה למשתמש באמצעות התראה באינטרנט:
אחזור ברקע
לפעולות קצרות יחסית, כמו שליחת הודעה או שמירת רשימה של כתובות URL במטמון, האפשרויות שבדקנו עד עכשיו הן בחירה טובה. אם המשימה נמשכת יותר מדי זמן, הדפדפן יסגור את ה-service worker, אחרת הוא עלול לסכן את הפרטיות ואת הסוללה של המשתמש.
Background Fetch API מאפשר להעביר ל-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 כדי להודיע על עדכונים חשובים (למשל, גרסה חדשה של אפליקציית האינטרנט זמינה).