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

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

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

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

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

תרשים שמראה קובץ שירות (service worker) והדף שעובר בין הודעות.

שימוש בתיבת העבודה

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 הזה.

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

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

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

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

  • בכל המקרים, התקשורת מתחילה בקצה אחד דרך הממשק 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', });

בכל הקשר בדפדפן אפשר להאזין להודעות באמצעות ה-method onmessage של האובייקט BroadcastChannel:

//listen to messages
broadcast.onmessage = (event) => {
  if (event.data && event.data.type === 'MSG_ID') {
    //process message...
  }
};

כפי שרואים, אין התייחסות מפורשת להקשר מסוים, ולכן לא צריך קודם הפניה ל-Service Worker או ללקוח מסוים.

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

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

Client API

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

  • Chrome: 40.
  • קצה: 17.
  • Firefox: 44.
  • Safari: 11.1.

מקור

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

ערוץ הודעות

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

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

בחיפוש Google נעשה שימוש בסנכרון ברקע כדי לשמור שאילתות שנכשלו בגלל חיבור לקוי, ולנסות אותן שוב מאוחר יותר כשהמשתמש מחובר לאינטרנט. בסיום הפעולה, הם מעבירים את התוצאה למשתמש באמצעות התראה באינטרנט:

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

אחזור ברקע

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

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

מקור

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