תקשורת דו-כיוונית עם עובדי השירות

Andrew Guan
Andrew Guan
Demián Renzulli
Demián Renzulli

במקרים מסוימים, יכול להיות שאפליקציית אינטרנט תצטרך ליצור ערוץ תקשורת דו-כיווני בין הדף לבין ה-service worker.

לדוגמה: בפודקאסט ב-PWA אפשר ליצור תכונה שמאפשרת למשתמש להוריד פרקים לצפייה אופליין ולאפשר ל-service worker לעדכן את הדף באופן קבוע לגבי ההתקדמות, כדי שהשרשור הראשי יוכל לעדכן את ממשק המשתמש.

במדריך הזה נסביר על הדרכים השונות להטמיע תקשורת דו-כיוונית בין ההקשר של החלון לבין ההקשר של העבד בשירות. לשם כך, נבחן ממשקי API שונים, את ספריית Workbox וגם כמה מקרים מתקדמים.

תרשים שבו מוצגים קובץ שירות (service worker) והדף שמחליפים הודעות.

שימוש ב-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 הזה.

תרשים שבו מוצגת תקשורת דו-כיוונית בין הדף לבין ה-service worker, באמצעות חלון Workbox.

שימוש בממשקי API לדפדפנים

אם ספריית Workbox לא מספיקה לצרכים שלכם, יש כמה ממשקי API ברמה נמוכה יותר שזמינים להטמעת תקשורת דו-כיוונית בין דפים לבין שירותי העבודה. יש להם כמה דמיון והבדלים:

תכונות דומות:

  • בכל המקרים, התקשורת מתחילה בקצה אחד דרך הממשק postMessage() ומתקבלת בקצה השני על ידי הטמעת טיפול (handler) של message.
  • בפועל, כל ממשקי ה-API הזמינים מאפשרים לנו להטמיע את אותם תרחישים לדוגמה, אבל חלק מהם עשויים לפשט את הפיתוח בתרחישים מסוימים.

ההבדלים:

  • יש להם דרכים שונות לזיהוי הצד השני של התקשורת: חלק מהן משתמשות בהפניה מפורשת להקשר השני, ואחרות יכולות לתקשר באופן משתמע באמצעות אובייקט proxy שנוצר בכל צד.
  • התמיכה בדפדפנים משתנה בהתאם לשירות.
תרשים שבו מוצגת תקשורת דו-כיוונית בין הדף לבין ה-service worker, וממשקי ה-API הזמינים בדפדפן.

Broadcast Channel API

תמיכה בדפדפנים

  • Chrome: 54.
  • Edge: ‏ 79.
  • Firefox: 38.
  • Safari: 15.4.

מקור

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 או ללקוח מסוים.

תרשים שמציג תקשורת דו-כיוונית בין הדף לבין ה-service worker, באמצעות אובייקט של ערוץ שידור.

החיסרון הוא שבזמן כתיבת שורות אלה, יש תמיכה ב-API ב-Chrome, ב-Firefox וב-Edge, אבל בדפדפנים אחרים, כמו Safari, עדיין אין תמיכה בו.

Client API

תמיכה בדפדפנים

  • Chrome: ‏ 40.
  • Edge: ‏ 17.
  • Firefox: 44.
  • Safari: 11.1.

מקור

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, אבל יכול להיות שלא כל השיטות שלו יהיו זמינות. לכן, חשוב לבדוק את תמיכת הדפדפן לפני שמטמיעים אותו באתר.

ערוץ הודעות

תמיכה בדפדפנים

  • Chrome: ‏ 2.
  • Edge: ‏ 12.
  • Firefox: 41.
  • Safari: 5.

מקור

כדי ליצור ערוץ תקשורת דו-כיווני, צריך להגדיר ולעביר יציאה מהקשר אחד לאחר ב-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 כדי ליצור תקשורת דו-כיוונית.

ה-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 לטיפול בתרחישים ספציפיים: חוסר קישוריות והורדות ארוכות.

סנכרון ברקע

תמיכה בדפדפנים

  • Chrome: ‏ 49.
  • Edge: ‏ 79.
  • Firefox: לא נתמך.
  • Safari: לא נתמך.

מקור

יכול להיות שאפליקציית צ'אט תרצה לוודא שהודעות אף פעם לא יאבד בגלל חיבור גרוע. 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 משתמש בסנכרון ברקע כדי לשמור שאילתות שנכשלו בגלל חיבור לקוי, ולנסות אותן שוב מאוחר יותר כשהמשתמש מחובר לאינטרנט. בסיום הפעולה, הם מעבירים את התוצאה למשתמש באמצעות התראה באינטרנט:

תרשים שבו מוצג דף שמעביר יציאה ל-service worker כדי ליצור תקשורת דו-כיוונית.

אחזור ברקע

תמיכה בדפדפנים

  • Chrome: 74.
  • Edge: ‏ 79.
  • Firefox: לא נתמך.
  • Safari: לא נתמך.

מקור

לפעולות קצרות יחסית, כמו שליחת הודעה או רשימה של כתובות 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) מהדף כדי לשמור משאבים במטמון מראש (למשל בתרחישים של אחסון מקדים).
  • שידור עדכונים: קריאה לדף מה-service worker כדי להודיע על עדכונים חשובים (למשל, גרסה חדשה של אפליקציית האינטרנט זמינה).