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