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.

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

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

Trong phần cuối, chúng ta đã xem 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 thông báo (tức là thay vì nhấp vào thông báo, người dùng sẽ nhấp vào dấu chữ thập hoặc vuốt không có thông báo).

Sự kiện này thường được dùng để 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);
});

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

Khi nhận được một thông báo đẩy, thông thường 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 mở ra khi có người 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 một sự kiện đẩy 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ở một cửa sổ

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

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ở hơn là mở thẻ mới .

Lấy tiêu điểm một cửa sổ hiện có

Khi 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 lần người dùng nhấp vào một thông báo.

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

Trong ví dụ trước, chúng ta sẽ thay đổi mã để xem /demos/notification-examples/example-page.html đã mở cử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 tìm hiểu về 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 thủ thuật tinh tế 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 đó với URL của cửa sổ sau này.

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

Sau đó, chúng ta nhận được danh sách đối tượng WindowClient. Đây là danh sách đối tượng thẻ và cửa sổ hiện đ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 chuyển vào matchAll cho trình duyệt biết rằng chúng ta chỉ muốn để tìm kiếm "window" nhập ứng dụng khách (ví dụ: chỉ cần tìm thẻ và cửa sổ và loại trừ nhân viên web). includeUncontrolled cho phép chúng ta tìm kiếm tất cả các thẻ trên máy chủ gốc của bạn mà dịch vụ hiện tại không kiểm soát worker này, tức là service worker chạy mã này. Thông thường, bạn sẽ luôn muốn includeUncontrolled là true khi gọi matchAll().

Chúng ta ghi lại lời hứa được trả về dưới dạng promiseChain để có thể chuyển lời hứa đó vào event.waitUntil() sau đó, giúp trình chạy dịch vụ của chúng ta 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 qua các ứng dụng cửa sổ được trả về và so sánh URL của họ với URL mà chúng tôi 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 để làm cho cửa sổ đó thu hút sự chú ý của người dùng. Việc tập trung được thực hiện bằng Cuộc gọi matchingClient.focus().

Nếu không tìm thấy ứng dụng phù hợp, chúng ta sẽ mở một cửa sổ mới 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 thấy rằng việc thêm thẻ vào thông báo sẽ đồng ý với hành vi mà thông báo hiện tại có cùng thẻ sẽ được thay thế.

Tuy nhiên, bạn có thể phức tạp hơn với việc thu gọn thông báo bằng cách sử dụng API thông báo. Hãy cân nhắc sử dụng một ứng dụng trò chuyện mà có thể nhà phát triển muốn nhận thông báo mới hiện 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 báo cáo mới nhất .

Bạn có thể làm điều này hoặc thao tác với thông báo hiện tại theo những cách khác, sử dụng registration.getNotifications() API 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 bạn.

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

Trong ứng dụng nhắn tin, giả sử mỗi thông báo đều có một số dữ liệu bao gồm cả tên người dùng.

Việc đầ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. Chúng tôi sẽ nhận registration.getNotifications() và lặp lại qua các kết quả đó và kiểm tra notification.data cho 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ả mạo 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 và tăng dần dữ liệu đó 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ó thông báo đang được hiển thị, chúng ta sẽ tăng số lượng thông báo và thiết lập giá trị tiêu đề thông báo và nội dung thông báo cho phù hợp. Nếu có 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 thông báo thành như sau:

Thông báo thứ hai có hợp nhất.

Điều tuyệt vời với 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, sẽ trông gần gũi 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 đã cho biết rằng bạn phải hiển thị thông báo khi nhận được thông báo đẩy và trường hợp này luôn đúng hầu hết. Một trường hợp mà bạn không phải hiển thị thông báo là khi khiến trang web của bạn đang mở và tập trung vào.

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

Mã để tải tất cả cửa sổ và tìm cửa sổ được lấy tiêu điểm 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 tôi sử dụng clients.matchAll() để nhận tất cả ứng dụng cửa sổ, sau đó chúng ta 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 tin nhắn 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 rằng một sự kiện đã xảy ra, nhưng một thông báo nặng tay?

Một phương pháp là gửi thông báo từ trình chạy dịch vụ đến trang, theo 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 để cho họ biết về sự kiện đó. Thông tin này hữu ích cho trong các trường hợp khi một thông báo tinh tế trên trang sẽ tốt hơn 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 xem ứng dụng web của chúng ta hiện có được đặt tiêu điểm hay không, thì chúng tôi có thể "đăng thông báo" vào mỗi trang đang mở, tương tự 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 theo dõi thông báo bằng cách thêm một sự kiện thông báo trình nghe:

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 một giao diện người dùng tuỳ chỉnh trên trang của bạn hoặc hoàn toàn bỏ qua thông báo.

Một điểm khác đáng lưu ý là nếu bạn không xác định trình nghe thông báo trong trang web, thì các thông báo từ trình chạy dịch vụ sẽ không thực hiện bất kỳ hành động nào.

Lưu một trang vào bộ nhớ đệm và mở một 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 các trang web mà bạn muốn người dùng truy cập sau đó vào bộ nhớ đệm nhấp vào thông báo của bạn.

Bạn cầ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 bạn triển khai trình nghe sự kiện fetch, hãy nhớ lấy tận dụng dữ liệu này trong sự kiện push bằng cách lưu trang và các thành phần vào bộ nhớ đệm bạn cần làm trước khi hiện 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.
  • Cạnh: 17.
  • Firefox: 44.
  • Safari: 16.

Nguồn

Clients.openWindow()

Hỗ trợ trình duyệt

  • Chrome: 40.
  • Cạnh: 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.
  • Cạnh: 17.
  • Firefox: 54.
  • Safari: 11.1.

Nguồn

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

Điểm đến tiếp theo

Phòng thí nghiệm lập trình