รูปแบบการแจ้งเตือนทั่วไป

เราจะดูรูปแบบการใช้งานที่พบบ่อยสำหรับ Push บนเว็บ

ซึ่งจะเกี่ยวข้องกับการใช้ API ต่างๆ 2-3 รายการที่มีอยู่ใน Service Worker

เหตุการณ์การปิดการแจ้งเตือน

ในส่วนที่แล้ว เราได้ดูวิธีฟังเหตุการณ์ notificationclick

นอกจากนี้ยังมีเหตุการณ์ notificationclose ที่เรียกใช้หากผู้ใช้ปิดการแจ้งเตือนของคุณ (นั่นคือ ผู้ใช้คลิกเครื่องหมายกากบาทหรือปัดการแจ้งเตือนออกแทนที่จะคลิกการแจ้งเตือน)

โดยทั่วไปเหตุการณ์นี้ใช้สําหรับข้อมูลวิเคราะห์เพื่อติดตามการมีส่วนร่วมของผู้ใช้กับการแจ้งเตือน

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

การเพิ่มข้อมูลในการแจ้งเตือน

เมื่อได้รับข้อความพุช ข้อมูลที่ได้รับมักมีประโยชน์ก็ต่อเมื่อผู้ใช้คลิกการแจ้งเตือนเท่านั้น เช่น URL ที่ควรเปิดขึ้นเมื่อมีการคลิกการแจ้งเตือน

วิธีง่ายที่สุดในการนําข้อมูลจากเหตุการณ์ Push และแนบไปกับการแจ้งเตือนคือการเพิ่มพารามิเตอร์ data ไปยังออบเจ็กต์ options ที่ส่งไปยัง 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 ที่เฉพาะเจาะจง เราทําสิ่งนี้ได้โดยใช้ 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 นี่เป็นเคล็ดลับเจ๋งๆ ที่เราได้มาจาก Jeff Posnick การเรียกใช้ new URL() ด้วยออบเจ็กต์ location จะแสดงผล URL แบบสัมบูรณ์หากสตริงที่ส่งเป็นสัมพัทธ์ (เช่น / จะกลายเป็น https://example.com/)

เราทำให้ URL เป็น URL แบบสัมบูรณ์เพื่อให้จับคู่กับ URL ของกรอบเวลาในภายหลังได้

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

จากนั้นเราจะได้รายการออบเจ็กต์ WindowClient ซึ่งเป็นรายการแท็บและหน้าต่างที่เปิดอยู่ในปัจจุบัน (โปรดทราบว่าแท็บเหล่านี้เป็นแท็บสำหรับต้นทางของคุณเท่านั้น)

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

ตัวเลือกที่ส่งไปยัง matchAll จะแจ้งให้เบราว์เซอร์ทราบว่าเราต้องการค้นหาเฉพาะไคลเอ็นต์ประเภท "หน้าต่าง" เท่านั้น (กล่าวคือ ค้นหาเฉพาะแท็บและหน้าต่าง และยกเว้น Web Worker) includeUncontrolled ช่วยให้เราค้นหาแท็บทั้งหมดจากต้นทางของคุณที่ไม่ได้ควบคุมโดย Service Worker ปัจจุบัน เช่น Service Worker ที่เรียกใช้โค้ดนี้ โดยทั่วไปแล้ว คุณจะต้องให้ includeUncontrolled เป็นจริงเสมอเมื่อเรียกใช้ matchAll()

เราบันทึก Promise ที่แสดงผลเป็น 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);   } }); 

การรวมการแจ้งเตือน

เราพบว่าการเพิ่มแท็กในการแจ้งเตือนจะเลือกใช้ลักษณะการทำงานที่จะแทนที่การแจ้งเตือนที่มีอยู่ด้วยแท็กเดียวกัน

อย่างไรก็ตาม คุณสามารถใช้การยุบการแจ้งเตือนได้โดยใช้ Notification API ลองนึกถึงแอปรับแชทที่นักพัฒนาแอปอาจต้องการให้การแจ้งเตือนใหม่แสดงข้อความที่คล้ายกับ "คุณมีข้อความ 2 ข้อความจาก 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   ); }); 

หากมีการแสดงการแจ้งเตือนอยู่ เราจะเพิ่มจำนวนข้อความและตั้งค่าชื่อและข้อความเนื้อหาของการแจ้งเตือนตามความเหมาะสม หากไม่มีการแจ้งเตือน เราจะสร้างการแจ้งเตือนใหม่ที่มี newMessageCount เป็น 1

ผลลัพธ์ที่ได้คือข้อความแรกจะมีลักษณะดังนี้

การแจ้งเตือนครั้งแรกแบบไม่ผสาน

การแจ้งเตือนครั้งที่ 2 จะยุบการแจ้งเตือนเป็นดังนี้

การแจ้งเตือนครั้งที่ 2 ที่มีการผสาน

ข้อดีของแนวทางนี้คือ หากผู้ใช้เห็นการแจ้งเตือนปรากฏขึ้นทีละรายการ ข้อความจะดูสอดคล้องกันมากกว่าการแทนที่การแจ้งเตือนด้วยข้อความล่าสุด

ข้อยกเว้นของกฎ

เราเคยแจ้งว่าคุณต้องต้องแสดงการแจ้งเตือนเมื่อได้รับการ 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() เพื่อรับ Window Client ทั้งหมด จากนั้นวนตรวจสอบพารามิเตอร์ 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); 

ส่งข้อความถึงหน้าเว็บจากเหตุการณ์ 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); }); 

ในโปรแกรมรับฟังข้อความนี้ คุณทําสิ่งใดก็ได้ที่ต้องการ เช่น แสดง UI ที่กําหนดเองในหน้าเว็บ หรือละเว้นข้อความไปเลย

นอกจากนี้ โปรดทราบว่าหากคุณไม่ได้กำหนดโปรแกรมรับฟังข้อความในหน้าเว็บ ข้อความจาก Service Worker จะไม่ทำงาน

แคชหน้าเว็บและเปิดหน้าต่าง

สถานการณ์หนึ่งที่อยู่นอกขอบเขตของคู่มือนี้แต่ควรกล่าวถึงคือคุณสามารถปรับปรุง UX โดยรวมของเว็บแอปได้โดยแคชหน้าเว็บที่คาดว่าผู้ใช้จะเข้าชมหลังจากคลิกการแจ้งเตือน

ซึ่งต้องมีการตั้งค่า Service Worker เพื่อจัดการเหตุการณ์ fetch แต่หากคุณใช้ fetch event listener โปรดใช้ประโยชน์จากเหตุการณ์ push โดยการแคชหน้าเว็บและชิ้นงานที่จําเป็นก่อนแสดงการแจ้งเตือน

ความเข้ากันได้กับเบราว์เซอร์

กิจกรรม notificationclose

Browser Support

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

Source

Clients.openWindow()

Browser Support

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

Source

ServiceWorkerRegistration.getNotifications()

Browser Support

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

Source

clients.matchAll()

Browser Support

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

Source

ดูข้อมูลเพิ่มเติมได้ที่โพสต์แนะนําเกี่ยวกับ Service Worker

ขั้นตอนถัดไป

Code labs