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

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

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

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

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

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

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

workbox-window היא קבוצה של של ספריית תיבות העבודה לפעול בהקשר של החלון. 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 Worker. יש כמה דמיון והבדלים:

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

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

הבדלים:

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

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

תמיכה בדפדפן

  • Chrome: 54.
  • קצה: 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 object:

//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 של לקוח

תמיכה בדפדפן

  • Chrome: 40.
  • קצה: 17.
  • Firefox: 44.
  • Safari: 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 מאזין להודעות על ידי הטמעת אוזן 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 נתמך על ידי כל דפדפנים, אבל לא כל השיטות עשויות להיות זמינות, לכן חשוב לבדוק את התמיכה של הדפדפן לפני להטמיע אותו באתר.

ערוץ הודעות

תמיכה בדפדפן

  • Chrome: 2.
  • קצה: 12.
  • Firefox: 41.
  • Safari: 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) לצורך תקשורת דו-כיוונית.

קובץ השירות מקבל את היציאה, שומר הפניה אליה ומשתמש בה כדי לשלוח הודעה לאחר צד:

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

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

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

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

אחזור ברקע

תמיכה בדפדפן

  • Chrome: 74.
  • קצה: 79.
  • Firefox: לא נתמך.
  • Safari: לא נתמך.

מקור

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

ה-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 Workers (תקשורת דו-כיוונית).

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

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