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

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

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

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

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

תרשים שמציג Service Worker והדף מחליף הודעות.

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

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

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

שימוש בממשקי API לדפדפן

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

נקודות דמיון:

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

הבדלים:

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

ממשק API של ערוצי שידור

תמיכה בדפדפן

  • 54
  • 79
  • 38
  • 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, עדיין לא תומכים בו.

ממשק API של לקוח

תמיכה בדפדפן

  • 40
  • 17
  • 44
  • 11.1

מקור

באמצעות Client API אפשר לקבל הפניה לכל האובייקטים WindowClient שמייצגים את הכרטיסיות הפעילות שנמצאות בשליטת ה-Service Worker.

מכיוון שהדף נשלט על ידי קובץ שירות (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 מאזין להודעות על ידי הטמעת listener של 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'});
  }
});
תרשים שמראה קובץ שירות (service worker) שמתקשר עם מערך של לקוחות.

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

ערוץ הודעות

תמיכה בדפדפן

  • 2
  • 12
  • 41
  • 5

מקור

כדי ליצור ערוץ תקשורת דו-כיווני, צריך להגדיר בערוץ הודעות יציאה ולהעביר אותה מהקשר אחד לאחר.

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

סנכרון ברקע

תמיכה בדפדפן

  • 49
  • 79
  • x
  • x

מקור

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

במקום הממשק postMessage(), הדף רושם sync:

navigator.serviceWorker.ready.then(function (swRegistration) {
  return swRegistration.sync.register('myFirstSync');
});

לאחר מכן, קובץ השירות מאזין לאירוע sync כדי לעבד את ההודעה:

self.addEventListener('sync', function (event) {
  if (event.tag == 'myFirstSync') {
    event.waitUntil(doSomeStuff());
  }
});

הפונקציה doSomeStuff() צריכה להחזיר הבטחה שמציינת את ההצלחה או הכישלון של המטרה שמנסים לעשות. אם הפעולה מתקיימת, הסנכרון הושלם. אם הניסיון נכשל, יתבצע תזמון ניסיון חוזר של סנכרון נוסף. סנכרונים חוזרים ממתינים גם הם לקישוריות ויש להם השהיה מעריכית לפני ניסיון חוזר (exponential backoff).

לאחר ביצוע הפעולה, ה-Service Worker יכול ליצור קשר עם הדף כדי לעדכן את ממשק המשתמש, באמצעות כל אחד מממשקי ה-API של התקשורת שסקרתם קודם.

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

תרשים שמציג דף המעביר יציאה ל-Service Worker, למטרת תקשורת דו-כיוונית.

אחזור רקע

תמיכה בדפדפן

  • 74
  • 79
  • x
  • x

מקור

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

השלבים הבאים

במדריך הזה בדקנו את המקרה הכללי ביותר של תקשורת בין דף ל-Service Workers (תקשורת דו-כיוונית).

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

  • מדריך ציווי לשמירה במטמון: קריאה ל-Service Worker מהדף כדי לשמור מראש משאבים (למשל בתרחישים של שליפה מראש).
  • עדכוני שידור: קריאה לדף מ-Service Worker כדי לקבל עדכונים חשובים (לדוגמה, אם יש גרסה חדשה של אפליקציית האינטרנט).