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

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

צריך להשתמש בכמה ממשקי API שונים שזמינים ב-Service Worker.

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

בקטע האחרון ראינו איך אנחנו יכולים להאזין לאירועי notificationclick.

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

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

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

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

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

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

הדרך הקלה ביותר לקחת נתונים מאירוע Push ולצרף אותם אל ההתראה היא להוסיף את הפרמטר 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);

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

באירוע 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. זה טריק קטן שאספתי מג'ף Posnick. קריאה אל new URL() עם האובייקט location תבצע מחזירה כתובת אתר מוחלטת אם המחרוזת שמועברת היא יחסית (כלומר, / תהפוך ל- https://example.com/).

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

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

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

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

האפשרויות שמועברות אל matchAll מיידעות את הדפדפן שאנחנו רוצים כדי לחפש את "window" סוגי לקוחות (כלומר, פשוט מחפשים כרטיסיות וחלונות) ולהחריג עובדי אינטרנט). includeUncontrolled מאפשר לנו לחפש כל הכרטיסיות מהמקור שלך שלא בשליטת השירות הנוכחי כלומר, ה-Service Worker שמריץ את הקוד. באופן כללי, אני רוצה שהערך של 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);
  }
});

מיזוג התראות

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

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

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

בואו נראה איך אפשר להשתמש ב-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
  );
});

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

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

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

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

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

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

היוצא מן הכלל

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

בתוך אירוע ה-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.

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

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);

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

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

אחת הגישות היא לשלוח הודעה מה-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);

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

תמיכה בדפדפן

  • 50
  • 17
  • 44
  • 16

מקור

Clients.openWindow()

תמיכה בדפדפן

  • 40
  • 17
  • 44
  • 11.1

מקור

ServiceWorkerRegistration.getNotifications()

תמיכה בדפדפן

  • 40
  • 17
  • 44
  • 16

מקור

clients.matchAll()

תמיכה בדפדפן

  • 42
  • 17
  • 54
  • 11.1

מקור

למידע נוסף, אפשר לקרוא את המבוא הזה לעובדי שירות (service worker) פוסט.

מה השלב הבא?

שיעורי Lab קוד