דפוסי התראות נפוצים

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

לשם כך, תצטרכו להשתמש בכמה ממשקי API שונים שזמינים ב-service worker.

בקטע הקודם ראינו איך אפשר להאזין לאירועים מסוג notificationclick.

יש גם אירוע notificationclose שנקרא אם המשתמש סוגר אחת מההתראות שלכם (כלומר, במקום ללחוץ על ההתראה, המשתמש לוחץ על הסמל X או מחליק את ההתראה).

האירוע הזה משמש בדרך כלל לניתוח נתונים כדי לעקוב אחרי התעניינות המשתמשים בהתראות.

self.addEventListener('notificationclose', function (event) {
 
const dismissedNotification = event.notification;

 
const promiseChain = notificationCloseAnalytics();
  event
.waitUntil(promiseChain);
});

הוספת נתונים להתראה

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

הדרך הקלה ביותר להעביר נתונים מאירוע דחיפה ולצרף אותם להתרעה היא להוסיף פרמטר data לאובייקט האפשרויות שמוענק ל-showNotification(), באופן הבא:

const options = {
  body
:
   
'This notification has data attached to it that is printed ' +
   
"to the console when it's clicked.",
  tag
: 'data-notification',
  data
: {
    time
: new Date(Date.now()).toString(),
    message
: 'Hello, World!',
 
},
};
registration
.showNotification('Notification with Data', options);

בתוך פונקציית טיפול בקליק, אפשר לגשת לנתונים באמצעות event.notification.data.

const notificationData = event.notification.data;
console
.log('');
console
.log('The notification data has the following parameters:');
Object.keys(notificationData).forEach((key) => {
  console
.log(`  ${key}: ${notificationData[key]}`);
});
console
.log('');

פתיחת חלון

אחת התגובות הנפוצות ביותר להודעה היא פתיחת חלון או כרטיסייה לכתובת URL ספציפית. אנחנו יכולים לעשות זאת באמצעות ה-API של clients.openWindow().

באירוע notificationclick, נריץ קוד כמו זה:

const examplePage = '/demos/notification-examples/example-page.html';
const promiseChain = clients.openWindow(examplePage);
event
.waitUntil(promiseChain);

בקטע הבא נסביר איך בודקים אם הדף שאליו רוצים להפנות את המשתמש כבר פתוח. כך נוכל להתמקד בכרטיסייה הפתוחה במקום לפתוח כרטיסיות חדשות.

מיקוד בחלון קיים

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

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

בדוגמה הקודמת, נשנה את הקוד כדי לבדוק אם /demos/notification-examples/example-page.html כבר פתוח.

const urlToOpen = new URL(examplePage, self.location.origin).href;

const promiseChain = clients
 
.matchAll({
    type
: 'window',
    includeUncontrolled
: true,
 
})
 
.then((windowClients) => {
    let matchingClient
= null;

   
for (let i = 0; i < windowClients.length; i++) {
     
const windowClient = windowClients[i];
     
if (windowClient.url === urlToOpen) {
        matchingClient
= windowClient;
       
break;
     
}
   
}

   
if (matchingClient) {
     
return matchingClient.focus();
   
} else {
     
return clients.openWindow(urlToOpen);
   
}
 
});

event
.waitUntil(promiseChain);

נעבור על הקוד.

קודם כל, אנחנו מנתחים את דף הדוגמה באמצעות URL API. זהו טריק נחמד שלמדתי מJeff Posnick. קריאה ל-new URL() עם האובייקט location תחזיר כתובת URL מוחלטת אם המחרוזת שהועברה היא יחסית (כלומר, / תהפוך ל-https://example.com/).

אנחנו הופכים את כתובת ה-URL לאבסולוטית כדי שנוכל להתאים אותה לכתובות ה-URL של החלונות בשלב מאוחר יותר.

const urlToOpen = new URL(examplePage, self.location.origin).href;

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

const promiseChain = clients.matchAll({
  type
: 'window',
  includeUncontrolled
: true,
});

האפשרויות המועברות אל matchAll מעדכנות את הדפדפן שאנחנו רוצים לחפש רק לקוחות מסוג 'חלון' (כלומר, לחפש רק כרטיסיות וחלונות ולהחריג משימות אינטרנט). includeUncontrolled מאפשר לנו לחפש את כל הכרטיסיות מהמקור שלכם שלא נמצאות בשליטת קובץ השירות הנוכחי, כלומר קובץ השירות שמריץ את הקוד הזה. באופן כללי, תמיד כדאי שהערך של includeUncontrolled יהיה true כשקוראים ל-matchAll().

אנחנו מתעדים את ההבטחה שחוזרת בתור promiseChain כדי שנוכל להעביר אותה ל-event.waitUntil() מאוחר יותר, וכך לשמור על הפעילות של ה-service worker.

כשההתחייבות של matchAll() מתקבלת, אנחנו עוברים על לקוחות החלון שהוחזרו ומשווים את כתובות ה-URL שלהם לכתובת ה-URL שאנחנו רוצים לפתוח. אם נמצא התאמה, נתמקד בלקוח הזה, וכך החלון הזה ימשוך את תשומת הלב של המשתמשים. התמקדות מתבצעת באמצעות השיחה matchingClient.focus().

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

.then((windowClients) => {
  let matchingClient
= null;

 
for (let i = 0; i < windowClients.length; i++) {
   
const windowClient = windowClients[i];
   
if (windowClient.url === urlToOpen) {
      matchingClient
= windowClient;
     
break;
   
}
 
}

 
if (matchingClient) {
   
return matchingClient.focus();
 
} else {
   
return clients.openWindow(urlToOpen);
 
}
});

מיזוג התראות

גילינו שהוספת תג להתראה מובילה לכך שכל התראה קיימת עם אותו תג תוחלף.

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

אפשר לעשות זאת, או לבצע פעולות אחרות על ההתראות הקיימות, באמצעות ה-API registration.getNotifications(), שמעניק גישה לכל ההתראות שגלויות כרגע באפליקציית האינטרנט.

נראה איך אפשר להשתמש ב-API הזה כדי להטמיע את דוגמת הצ'אט.

באפליקציית הצ'אט שלנו, נניח שלכל התראה יש נתונים מסוימים שכוללים שם משתמש.

הדבר הראשון שנעשה הוא למצוא התראות פתוחות של משתמש עם שם משתמש ספציפי. נקבל את registration.getNotifications() ונעביר אותם בלולאה ונבדוק את notification.data כדי למצוא שם משתמש ספציפי:

const promiseChain = registration.getNotifications().then((notifications) => {
  let currentNotification
;

 
for (let i = 0; i < notifications.length; i++) {
   
if (notifications[i].data && notifications[i].data.userName === userName) {
      currentNotification
= notifications[i];
   
}
 
}

 
return currentNotification;
});

בשלב הבא מחליפים את ההתראה הזו בהתראה חדשה.

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

.then((currentNotification) => {
  let notificationTitle
;
 
const options = {
    icon
: userIcon,
 
}

 
if (currentNotification) {
   
// We have an open notification, let's do something with it.
   
const messageCount = currentNotification.data.newMessageCount + 1;

    options
.body = `You have ${messageCount} new messages from ${userName}.`;
    options
.data = {
      userName
: userName,
      newMessageCount
: messageCount
   
};
    notificationTitle
= `New Messages from ${userName}`;

   
// Remember to close the old notification.
    currentNotification
.close();
 
} else {
    options
.body = `"${userMessage}"`;
    options
.data = {
      userName
: userName,
      newMessageCount
: 1
   
};
    notificationTitle
= `New Message from ${userName}`;
 
}

 
return registration.showNotification(
    notificationTitle
,
    options
 
);
});

אם יש כרגע התראה מוצגת, אנחנו מוסיפים לספירת ההודעות ומגדירים את שם ההתראה ואת גוף ההודעה בהתאם. אם אין התראות, יוצרים התראה חדשה עם הערך 1 ב-newMessageCount.

התוצאה היא שההודעה הראשונה תיראה כך:

ההתראה הראשונה ללא מיזוג.

התראה שנייה תכווצ את ההתראות כך:

ההודעה השנייה עם המיזוג.

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

החריג לכלל

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

באירוע ה-push, אפשר לבדוק אם צריך להציג התראה או לא על ידי בדיקת לקוחות החלון וחיפוש חלון שממוקד.

הקוד לקבלת כל החלונות ולחיפוש חלון ממוקד נראה כך:

function isClientFocused() {
 
return clients
   
.matchAll({
      type
: 'window',
      includeUncontrolled
: true,
   
})
   
.then((windowClients) => {
      let clientIsFocused
= false;

     
for (let i = 0; i < windowClients.length; i++) {
       
const windowClient = windowClients[i];
       
if (windowClient.focused) {
          clientIsFocused
= true;
         
break;
       
}
     
}

     
return clientIsFocused;
   
});
}

אנחנו משתמשים ב-clients.matchAll() כדי לקבל את כל לקוחות החלון שלנו, ואז אנחנו מבצעים לולאה עליהם ובודקים את הפרמטר focused.

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

const promiseChain = isClientFocused().then((clientIsFocused) => {
 
if (clientIsFocused) {
    console
.log("Don't need to show a notification.");
   
return;
 
}

 
// Client isn't focused, we need to show a notification.
 
return self.registration.showNotification('Had to show a notification.');
});

event
.waitUntil(promiseChain);

שליחת הודעה לדף מאירוע דחיפה

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

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

נניח שקיבלנו התראה, בדקנו שאפליקציית האינטרנט שלנו מוגדרת כמרכזית כרגע, ואז אנחנו יכולים "לפרסם הודעה" בכל דף פתוח, כך:

const promiseChain = isClientFocused().then((clientIsFocused) => {
 
if (clientIsFocused) {
    windowClients
.forEach((windowClient) => {
      windowClient
.postMessage({
        message
: 'Received a push message.',
        time
: new Date().toString(),
     
});
   
});
 
} else {
   
return self.registration.showNotification('No focused windows', {
      body
: 'Had to show a notification instead of messaging each page.',
   
});
 
}
});

event
.waitUntil(promiseChain);

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

navigator.serviceWorker.addEventListener('message', function (event) {
  console
.log('Received a message from service worker: ', event.data);
});

אפשר לעשות כל מה שרוצים בבורר ההודעות הזה, להציג ממשק משתמש בהתאמה אישית בדף או להתעלם מההודעה לגמרי.

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

שמירת דף במטמון ופתיחת חלון

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

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

תאימות דפדפן

האירוע notificationclose

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

  • Chrome: 50.
  • Edge: ‏ 17.
  • Firefox: 44.
  • Safari: 16.

מקור

Clients.openWindow()

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

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

מקור

ServiceWorkerRegistration.getNotifications()

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

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

מקור

clients.matchAll()

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

  • Chrome: 42.
  • Edge: ‏ 17.
  • Firefox: 54.
  • Safari: 11.1.

מקור

מידע נוסף זמין בפוסט הזה על מבוא ל-Service Workers.

לאן ממשיכים

Code labs