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

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

ซึ่งจะรวมถึงการใช้ API ต่างๆ ที่มีใน Service Worker

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

ในส่วนสุดท้าย เราได้เห็นวิธีฟังกิจกรรม notificationclick แล้ว

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

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

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

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

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

เมื่อได้รับข้อความ Push เป็นเรื่องปกติที่จะมีเฉพาะข้อมูล มีประโยชน์เมื่อผู้ใช้คลิกการแจ้งเตือน เช่น 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 ที่เฉพาะเจาะจง เราดำเนินการดังกล่าวได้ด้วย 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 ของหน้าต่างได้ในภายหลัง

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

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

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

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

เราบันทึกคำสัญญาที่ได้รับเป็น promiseChain เพื่อให้เราส่งต่อไปให้ หลังจากนั้น event.waitUntil() เพื่อให้พนักงานบริการทำงานต่อ

เมื่อสัญญา 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 พิจารณาใช้แอปแชทที่นักพัฒนาแอปอาจต้องการการแจ้งเตือนใหม่ แสดงข้อความที่คล้ายคลึงกับ "คุณมี 2 ข้อความจากโมทย์" แทนที่จะแสดงเฉพาะข้อมูลล่าสุด

คุณสามารถดำเนินการนี้หรือจัดการการแจ้งเตือนปัจจุบันด้วยวิธีการอื่นๆ โดยใช้ 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

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

การแจ้งเตือนครั้งแรกโดยไม่ต้องรวม

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

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

ข้อดีของวิธีนี้ก็คือ หากผู้ใช้ของคุณเห็น การแจ้งเตือนที่จะปรากฏขึ้นมากกว่า 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);

ข้อความในหน้าเว็บจากเหตุการณ์พุช

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

วิธีการหนึ่งคือการส่งข้อความจาก Service Worker ไปยังหน้าเว็บ ด้วยวิธีนี้ สามารถแสดงการแจ้งเตือนหรือข้อมูลอัปเดตแก่ผู้ใช้เพื่อแจ้งเกี่ยวกับกิจกรรม วิธีนี้เป็นประโยชน์สำหรับ เมื่อมีการแจ้งเตือนเล็กๆ น้อยๆ ในหน้าเว็บ จะเป็นประโยชน์ต่อผู้ใช้มากขึ้น

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

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

ใน Listener ข้อความนี้ คุณจะทำทุกอย่างได้ตามที่ต้องการ และแสดง UI ที่กำหนดเองใน หน้าเว็บของคุณหรือไม่ต้องสนใจข้อความนั้นเลย

และโปรดทราบว่าหากคุณไม่กำหนด Listener ข้อความในหน้าเว็บ ข้อความจากโปรแกรมทำงานของบริการจะไม่ส่งผลใดๆ

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

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

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

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

เหตุการณ์ notificationclose

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

  • Chrome: 50
  • ขอบ: 17
  • Firefox: 44
  • Safari: 16.

แหล่งที่มา

Clients.openWindow()

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

  • Chrome: 40
  • ขอบ: 17
  • Firefox: 44
  • Safari: 11.1

แหล่งที่มา

ServiceWorkerRegistration.getNotifications()

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

  • Chrome: 40
  • ขอบ: 17
  • Firefox: 44
  • Safari: 16.

แหล่งที่มา

clients.matchAll()

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

  • Chrome: 42
  • ขอบ: 17
  • Firefox: 54
  • Safari: 11.1

แหล่งที่มา

สำหรับข้อมูลเพิ่มเติม โปรดดูข้อมูลเบื้องต้นเกี่ยวกับ Service Worker โพสต์

สถานที่ที่จะไปต่อ

Code Lab