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

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

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

אירוע של סגירת התראה

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

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

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

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 worker.

לאן ממשיכים

Codelabs