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

Chúng ta sẽ xem xét một số mô hình triển khai phổ biến cho việc đẩy web.

Việc này sẽ liên quan đến việc sử dụng một vài API có sẵn trong worker dịch vụ.

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

Trong phần cuối cùng, chúng ta đã xem cách nghe các sự kiện notificationclick.

Ngoài ra, còn có một sự kiện notificationclose đượ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 thập hoặc vuốt thông báo đi).

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

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

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

Thêm dữ liệu vào thông báo

Khi nhận được thông báo đẩy, thường thì 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 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 tham số data vào đối tượng tuỳ chọn được truyề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ẻ tới 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 xét 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.

Đặt tiêu điểm vào một cửa sổ hiện có

Nếu có thể, chúng ta nên lấy tiêu điểm vào một cửa sổ thay vì mở một 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 tìm hiểu cách thực hiện việc này, bạn cần nhấn mạnh rằng việc này chỉ áp dụng cho các trang trên nguồn gốc của bạn. Lý do là chúng tôi chỉ có thể xem trang nào đang mở thuộc trang web của chúng tôi. Do đó, nhà phát triển sẽ không xem được tất cả các trang web mà người dùng đang xem.

Lấy ví dụ trước, chúng ta sẽ thay đổi mã để xem liệu /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 cùng xem qua mã này.

Trước tiên, chúng ta phân tích cú pháp trang mẫu bằng cách sử dụng API URL. Đây là một mẹo hay 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 ta đặt URL ở dạng tuyệt đối để có thể so khớp URL này với URL của 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, đây là danh sách các cửa sổ và thẻ đang mở. (Hãy nhớ rằng đây chỉ là các thẻ cho máy chủ gốc của bạn.)

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

Các tuỳ chọn được truyền vào matchAll sẽ 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 loại "cửa sổ" (tức là chỉ tìm kiếm các thẻ và cửa sổ và loại trừ các worker 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 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 phải luôn muốn includeUncontrolled có giá trị true khi gọi matchAll().

Chúng ta sẽ ghi lại lời hứa được trả về dưới dạng promiseChain để có thể truyền lời hứa đó vào event.waitUntil() sau này, giúp worker của dịch vụ luôn hoạt động.

Khi lời hứa matchAll() được giải quyết, chúng ta lặp lại 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 chúng tôi tìm thấy kết quả phù hợp, chúng tôi sẽ tập trung vào khách hàng đó để cửa sổ đó thu hút sự chú ý của người dùng. Việc lấy nét được thực hiện bằng lệnh gọi matchingClient.focus().

Nếu không 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 tôi nhận thấy rằng việc thêm thẻ vào thông báo sẽ chọn một hành vi trong đó mọi thông báo hiện có có cùng thẻ sẽ bị thay thế.

Tuy nhiên, bạn có thể sử dụng API Thông báo để thu gọn thông báo một cách phức tạp hơn. Hãy xem xét một ứng dụng trò chuyện, trong đó nhà phát triển có thể muốn 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 từ Matt" thay vì chỉ hiển thị 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 những cách khác bằng cách sử dụng API registration.getNotifications(). API này cho phép bạn truy cập vào tất cả thông báo hiện đ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, giả sử mỗi thông báo có một số dữ liệu bao gồm tên người dùng.

Điều đầu tiên chúng ta cầ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ẽ lấy registration.getNotifications() và lặp lại các tên người dùng đó, đồng thời kiểm tra notification.data để tìm một 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ế 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 một số đếm vào dữ liệu của thông báo mới và tăng số lượng này theo 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ó thông báo đang hiển thị, chúng ta sẽ tăng số lượng thông báo và đặt tiêu đề thông báo và nội dung thông báo cho phù hợp. Nếu không có thông báo nào, chúng ta sẽ tạo một thông báo mới với 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 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 có hoạt động hợp nhất.

Điều hay của phương pháp này là nếu người dùng của bạn chứng kiến các thông báo xuất hiện lần lượt, thì thông báo sẽ trông và cảm thấy gắn kết hơn so với việc chỉ thay thế thông báo bằng thông báo mới nhất.

Trường hợp ngoại lệ của quy tắc

Tôi đã nói rằng bạn phải hiển thị thông báo khi nhận được thông báo đẩy và điều này là đúng 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ở và lấy tiêu điểm trang web của bạn.

Bên trong sự kiện đẩy, bạn có thể kiểm tra xem có cần hiển thị thông báo hay không bằng cách kiểm tra ứ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 dùng clients.matchAll() để tải tất cả ứng dụng cửa sổ, sau đó lặp lại qua 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 tin nhắn cho một trang từ sự kiện đẩy

Chúng tôi nhận 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 truy cập vào trang web của bạn. Tuy nhiên, điều gì sẽ xảy ra nếu bạn vẫn muốn cho người dùng biết rằng một sự kiện đã xảy ra, nhưng một thông báo lại quá nặng ký?

Một phương pháp là gửi thông báo từ worker dịch vụ đến 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 tình huống khi một thông báo tinh tế trên trang sẽ hữu ích và thân thiện hơn cho người dùng.

Giả sử chúng ta đã nhận được một thông báo đẩy, kiểm tra để đảm bảo ứng dụng web của chúng ta đang được lấy làm tâm điểm, sau đó 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 ta sẽ nghe thông báo bằng cách thêm trình nghe sự kiện thông báo:

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ứ việc gì bạn 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.

Cũng cần lưu ý rằng nếu bạn không xác định trình nghe thông báo trong trang web, thì thông báo từ worker dịch vụ sẽ không làm gì cả.

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

Một tình huống 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 vào bộ nhớ đệm 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.

Để làm được điều này, bạn cần 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 đó trong sự kiện push bằng cách lưu trang và các thành phầ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

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

Nguồn

Clients.openWindow()

Hỗ trợ trình duyệt

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

Nguồn

ServiceWorkerRegistration.getNotifications()

Hỗ trợ trình duyệt

  • Chrome: 40.
  • Cạnh: 17.
  • Firefox: 44.
  • Safari: 16.

Nguồn

clients.matchAll()

Hỗ trợ trình duyệt

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

Nguồn

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

Bước tiếp theo

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