Các mẫu thông báo phổ biến

Chúng ta sẽ xem xét một số mẫu triển khai phổ biến cho đẩy dữ liệu web.

Điều này sẽ liên quan đến việc sử dụng một vài API khác nhau có trong trình chạy dịch vụ.

Sự kiện đóng thông báo

Trong phần trước, chúng ta đã tìm hiểu cách theo dõi các sự kiện notificationclick.

Ngoài ra, một sự kiện notificationclose sẽ được gọi nếu người dùng đóng một trong các thông báo của bạn (tức là thay vì nhấp vào thông báo, người dùng nhấp vào dấu chéo hoặc vuốt thông báo sang một bên).

Sự kiện này thường được dùng cho mục đích phân tích nhằm theo dõi mức độ tương tác của người dùng thông qua thông báo.

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

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

Đang thêm dữ liệu vào một thông báo

Khi nhận được một thông báo đẩy, thường thì sẽ có dữ liệu chỉ hữu ích nếu người dùng đã nhấp vào thông báo. Ví dụ: URL sẽ mở khi người dùng nhấp vào một thông báo.

Cách dễ nhất để lấy dữ liệu từ một sự kiện đẩy và đính kèm dữ liệu đó vào thông báo là thêm thông số data vào đối tượng tuỳ chọn được chuyển vào showNotification(), như sau:

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

Bên trong một trình xử lý lượt nhấp, bạn có thể truy cập dữ liệu này bằng 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('');

Mở cửa sổ

Một trong những phản hồi phổ biến nhất cho một thông báo là mở một cửa sổ / thẻ dẫn đến một URL cụ thể. Chúng ta có thể thực hiện việc này bằng API clients.openWindow().

Trong sự kiện notificationclick, chúng ta sẽ chạy một số mã như sau:

const examplePage = '/demos/notification-examples/example-page.html';
const promiseChain = clients.openWindow(examplePage);
event.waitUntil(promiseChain);

Trong phần tiếp theo, chúng ta sẽ xem cách kiểm tra xem trang mà chúng ta muốn chuyển hướng người dùng đến đã mở hay chưa. Bằng cách này, chúng ta có thể tập trung vào thẻ đang mở thay vì mở các thẻ mới.

Lấy tiêu điểm vào cửa sổ hiện có

Khi có thể, chúng ta nên lấy tiêu điểm là một cửa sổ thay vì mở cửa sổ mới mỗi khi người dùng nhấp vào một thông báo.

Trước khi chúng ta xem xét cách đạt được điều này, bạn cần lưu ý rằng điều này chỉ có thể thực hiện được đối với các trang trên nguồn gốc của bạn. Lý do là chúng tôi chỉ có thể xem những trang nào đang mở thuộc về trang web của chúng tôi. Điều này khiến nhà phát triển không thể thấy tất cả các trang web mà người dùng đang xem.

Trong ví dụ trước, chúng ta sẽ thay đổi mã để xem /demos/notification-examples/example-page.html đã mở hay chưa.

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

Hãy xem qua mã.

Trước tiên, chúng tôi phân tích cú pháp trang mẫu của mình bằng URL API. Đây là một thủ thuật thú vị mà tôi học được từ Jeff Posnick. Việc gọi new URL() bằng đối tượng location sẽ trả về một URL tuyệt đối nếu chuỗi được truyền vào là tương đối (tức là / sẽ trở thành https://example.com/).

Chúng tôi tạo URL tuyệt đối để có thể so khớp URL này với URL cửa sổ sau này.

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

Sau đó, chúng ta sẽ nhận được danh sách các đối tượng WindowClient, chính là danh sách các thẻ và cửa sổ hiện đang mở. (Hãy nhớ rằng đây là các thẻ chỉ dành cho nguồn gốc của bạn.)

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

Các tuỳ chọn được chuyển vào matchAll thông báo cho trình duyệt rằng chúng ta chỉ muốn tìm kiếm các ứng dụng khách loại "cửa sổ" (tức là chỉ tìm thẻ và cửa sổ đồng thời loại trừ trình thực thi web). includeUncontrolled cho phép chúng ta tìm kiếm tất cả các thẻ từ nguồn gốc của bạn mà không do trình chạy dịch vụ hiện tại kiểm soát, tức là trình chạy dịch vụ đang chạy mã này. Nhìn chung, bạn sẽ luôn muốn includeUncontrolled mang giá trị true khi gọi matchAll().

Chúng ta thu thập lời hứa được trả về dưới dạng promiseChain để sau này có thể truyền vào event.waitUntil(), giúp duy trì một worker tiếp tục hoạt động.

Khi lời hứa matchAll() được giải quyết, chúng tôi lặp lại thông qua các ứng dụng cửa sổ được trả về và so sánh URL của các ứng dụng đó với URL mà chúng ta muốn mở. Nếu thấy phù hợp, chúng tôi sẽ tập trung vào ứng dụng đó và người dùng sẽ chú ý đến cửa sổ đó. Bạn có thể lấy tiêu điểm bằng lệnh gọi matchingClient.focus().

Nếu chúng ta không thể tìm thấy ứng dụng khách phù hợp, chúng ta sẽ mở một cửa sổ mới, giống như trong phần trước.

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

Hợp nhất thông báo

Chúng ta thấy rằng việc thêm thẻ vào thông báo sẽ chọn tham gia hành vi mà trong đó mọi thông báo hiện có có cùng thẻ sẽ được thay thế.

Tuy nhiên, bạn có thể thu gọn thông báo bằng cách sử dụng API Thông báo để tinh vi hơn. Hãy xem xét một ứng dụng nhắn tin, trong đó nhà phát triển có thể muốn một thông báo mới hiển thị một thông báo tương tự như "Bạn có hai tin nhắn của Matt" thay vì chỉ hiện tin nhắn mới nhất.

Bạn có thể thực hiện việc này hoặc thao tác với các thông báo hiện tại theo cách khác bằng cách sử dụng API registration.getNotifications(). API này sẽ cấp cho bạn quyền truy cập vào tất cả thông báo đang hiển thị cho ứng dụng web của mình.

Hãy xem cách chúng ta có thể sử dụng API này để triển khai ví dụ về cuộc trò chuyện.

Trong ứng dụng trò chuyện của chúng ta, hãy giả định mỗi thông báo đều có một số dữ liệu, bao gồm cả tên người dùng.

Điều đầu tiên chúng ta muốn làm là tìm mọi thông báo đang mở cho người dùng có tên người dùng cụ thể. Chúng ta sẽ nhận được registration.getNotifications() và lặp lại chúng, đồng thời kiểm tra notification.data để lấy tên người dùng cụ thể:

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

Bước tiếp theo là thay thông báo này bằng một thông báo mới.

Trong ứng dụng tin nhắn giả này, chúng ta sẽ theo dõi số lượng tin nhắn mới bằng cách thêm số lượng vào dữ liệu của thông báo mới và tăng số lượng tin nhắn đó với mỗi thông báo mới.

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

Nếu có một thông báo đang hiển thị, chúng tôi sẽ tăng số lượng thông báo và đặt tiêu đề thông báo cũng như nội dung thông báo cho phù hợp. Nếu không có thông báo, chúng ta sẽ tạo thông báo mới có newMessageCount là 1.

Kết quả là thông báo đầu tiên sẽ có dạng như sau:

Thông báo đầu tiên mà không hợp nhất.

Thông báo thứ hai sẽ thu gọn các thông báo thành như sau:

Thông báo thứ hai về việc hợp nhất.

Một điều thú vị của phương pháp này là nếu người dùng chứng kiến các thông báo xuất hiện chồng chéo nhau, thì thông báo sẽ có vẻ gắn kết hơn thay vì chỉ thay thế thông báo bằng thông báo mới nhất.

Ngoại lệ đối với quy tắc

Tôi đã khẳng định rằng bạn phải hiển thị thông báo khi nhận được một lượt đẩy và đây là điều trong hầu hết trường hợp. Một trường hợp mà bạn không phải hiển thị thông báo là khi người dùng mở trang web của bạn và lấy tiêu điểm.

Bên trong sự kiện đẩy, bạn có thể kiểm tra xem mình có cần hiển thị thông báo hay không bằng cách kiểm tra các ứng dụng cửa sổ và tìm một cửa sổ được lấy tiêu điểm.

Mã để lấy tất cả các cửa sổ và tìm một cửa sổ được lấy tiêu điểm sẽ có dạng như sau:

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

Chúng ta sử dụng clients.matchAll() để tải tất cả các ứng dụng cửa sổ, sau đó lặp lại các ứng dụng đó để kiểm tra tham số focused.

Bên trong sự kiện đẩy, chúng ta sẽ sử dụng hàm này để quyết định xem có cần hiển thị thông báo hay không:

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

Gửi thông báo cho một trang từ một sự kiện đẩy

Chúng tôi thấy rằng bạn có thể bỏ qua việc hiển thị thông báo nếu người dùng hiện đang ở trên trang web của bạn. Nhưng điều gì sẽ xảy ra nếu bạn vẫn muốn cho người dùng biết một sự kiện đã xảy ra nhưng lại thấy thông báo quá tốn kém?

Có một phương pháp là gửi thông báo từ trình chạy dịch vụ tới trang. Bằng cách này, trang web có thể hiển thị thông báo hoặc nội dung cập nhật cho người dùng để thông báo cho họ về sự kiện. Điều này rất hữu ích trong các trường hợp khi một thông báo tinh tế trên trang sẽ phù hợp và thân thiện hơn với người dùng.

Giả sử chúng ta nhận được một yêu cầu đẩy, kiểm tra để đảm bảo rằng ứng dụng web hiện đang được lấy làm tiêu điểm, thì chúng ta có thể "đăng thông báo" lên từng trang đang mở, như sau:

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

Trong mỗi trang, chúng tôi theo dõi thông báo bằng cách thêm trình nghe sự kiện tin nhắn:

navigator.serviceWorker.addEventListener('message', function (event) {
  console.log('Received a message from service worker: ', event.data);
});

Trong trình nghe thông báo này, bạn có thể làm bất cứ điều gì mình muốn, hiển thị giao diện người dùng tuỳ chỉnh trên trang hoặc hoàn toàn bỏ qua thông báo.

Ngoài ra, bạn cũng cần lưu ý rằng nếu không xác định trình nghe thông báo trong trang web của mình, thì thông báo từ trình chạy dịch vụ sẽ không thực hiện bất kỳ tác vụ nào.

Lưu một trang vào bộ nhớ đệm và mở cửa sổ

Một trường hợp nằm ngoài phạm vi của hướng dẫn này nhưng đáng để thảo luận là bạn có thể cải thiện trải nghiệm người dùng tổng thể của ứng dụng web bằng cách lưu các trang web mà bạn muốn người dùng truy cập sau khi nhấp vào thông báo vào bộ nhớ đệm.

Bạn phải thiết lập trình chạy dịch vụ để xử lý các sự kiện fetch, nhưng nếu triển khai trình nghe sự kiện fetch, hãy nhớ tận dụng trình nghe sự kiện này trong sự kiện push bằng cách lưu trang và các tài sản bạn cần vào bộ nhớ đệm trước khi hiển thị thông báo.

Khả năng tương thích với trình duyệt

Sự kiện notificationclose

Hỗ trợ trình duyệt

  • 50
  • 17
  • 44
  • 16

Nguồn

Clients.openWindow()

Hỗ trợ trình duyệt

  • 40
  • 17
  • 44
  • 11,1

Nguồn

ServiceWorkerRegistration.getNotifications()

Hỗ trợ trình duyệt

  • 40
  • 17
  • 44
  • 16

Nguồn

clients.matchAll()

Hỗ trợ trình duyệt

  • 42
  • 17
  • 54
  • 11,1

Nguồn

Để biết thêm thông tin, hãy xem bài đăng giới thiệu về nhân viên dịch vụ.

Điểm đến tiếp theo

Lớp học lập trình