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

ข้อดีของแนวทางนี้คือ หากผู้ใช้เห็นการแจ้งเตือนปรากฏขึ้นทีละรายการ ข้อความจะดูสอดคล้องกันมากกว่าการแทนที่การแจ้งเตือนด้วยข้อความล่าสุด
ข้อยกเว้นของกฎ
เราเคยแจ้งว่าคุณต้องต้องแสดงการแจ้งเตือนเมื่อได้รับการ 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
Clients.openWindow()
ServiceWorkerRegistration.getNotifications()
clients.matchAll()
ดูข้อมูลเพิ่มเติมได้ที่โพสต์แนะนําเกี่ยวกับ Service Worker
ขั้นตอนถัดไป
- ภาพรวมข้อความ Push บนเว็บ
- วิธีการทำงานของ Push
- การติดตามผู้ใช้
- UX ของสิทธิ์
- การส่งข้อความด้วยไลบรารี Web Push
- Web Push Protocol
- การจัดการเหตุการณ์ Push
- การแสดงการแจ้งเตือน
- ลักษณะการทํางานของการแจ้งเตือน
- รูปแบบการแจ้งเตือนที่พบบ่อย
- คำถามที่พบบ่อยเกี่ยวกับข้อความ Push
- ปัญหาที่พบได้ทั่วไปและการรายงานข้อบกพร่อง