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

เราจะดูรูปแบบการใช้งานที่พบบ่อยสําหรับ Web 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

การรองรับเบราว์เซอร์

  • 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

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

Code labs