常見的通知模式

Matt Gaunt

接下來我們將探討一些網路推送的常見實作模式。

這牽涉到使用 Service Worker 中幾種不同的 API。

通知關閉事件

在上一節中,我們說明瞭如何監聽 notificationclick 事件。

如果使用者關閉其中一項應用程式,系統也會呼叫 notificationclose 事件 通知 (也就是使用者點選交叉圖示或滑開 通知)。

這個事件通常用於 Analytics 追蹤使用者與通知的互動情形。

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

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

在通知中新增資料

收到推送訊息時,資料通常 當使用者點擊通知時相當實用例如: 當使用者點選通知時應該開啟的元件

讓您輕鬆從推送事件擷取資料,並附加至 通知是在傳遞的選項物件中加入 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('');

開啟視窗

開啟通知時,最常見的回應之一就是 視窗 / Tab 鍵瀏覽至特定網址。我們可以透過 clients.openWindow()敬上 也能使用 Google Cloud CLI 或 Compute Engine 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。使用 location 物件呼叫 new URL() 將會 如果傳入的字串是相對性質,則會傳回絕對網址 (即 / 會變為 https://example.com/)。

我們會採用絕對網址,以便之後能將網址與視窗網址進行比對。

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

然後我們會取得 WindowClient 物件清單,也就是 目前開啟的分頁和視窗(請注意,這裡只會列出來源所在的分頁)。

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

傳遞至 matchAll 的選項會通知瀏覽器 搜尋「視窗」類型用戶端 (也就是只搜尋分頁和視窗) 以及排除網路工作站)。includeUncontrolled可以搜尋 所有非由目前服務控制的來源分頁 工作站,也就是執行此程式碼的 Service Worker。一般來說 呼叫 matchAll() 時,一律希望 includeUncontrolled 為 true。

我們會擷取傳回的承諾使用 promiseChain,以便將其傳入 event.waitUntil()稍後會,為我們的服務工作人員保持運作。

matchAll() 承諾解析時,我們會反覆查看傳回的視窗用戶端,並 與我們要開啟的網址相互比對找到相符內容時,我們會鎖定 這可以把這個視窗帶到使用者眼前有了 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);
  }
});

合併通知

我們發現在通知中加入標記後,就表示觸發了 會取代含有相同標記的現有通知。

如果你想進行更加複雜的通知,可以使用 通知 API。以即時通訊應用程式為例,開發人員可能希望收到新通知 顯示類似「你有兩則 Matt 訊息」的訊息而不是單純顯示 撰寫新的電子郵件訊息

您可以使用 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
  );
});

如果畫面上有顯示通知,我們會累計訊息數量,並設定 通知標題和內文訊息如果有 沒有通知,我們要建立含有 1 的 newMessageCount 的新通知。

結果顯示第一則訊息應如下所示:

未合併的第一則通知。

第二則通知會將通知收合為以下內容:

第二則通知:合併。

這種方法的一大優點是,如果使用者發現 通知之間會互相顯示 這不只是以最新訊息取代通知即可

規則的例外狀況

我一向指出,你「必須」在收到推播通知時顯示通知 總是「大部分」。無需顯示通知的情境, 使用者開啟您的網站後

在推送事件中,您可以檢查是否需要顯示通知,步驟如下: 正在檢查視窗用戶端,並尋找聚焦的視窗。

取得所有視窗以及尋找聚焦視窗的程式碼如下所示:

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 將訊息從 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);

我們會在每個頁面上加入訊息事件來監聽訊息 事件監聽器:

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

在這個訊息事件監聽器中,您可以執行任何操作、在以下位置顯示自訂 UI: 或完全忽略這則訊息

另外值得注意的是,如果沒有在網頁中定義訊息接聽器, 來自 Service Worker 的訊息不會有任何動作。

快取網頁並開啟視窗

不過,這不在本指南的涵蓋範圍內,但值得探討的是,您可以 快取使用者預期會造訪的網頁,以改善網頁應用程式的整體使用者體驗 點選通知即可

您必須設定 Service Worker 才能處理 fetch 事件。 但如果您實作 fetch 事件監聽器,請務必 快取網頁和素材資源,在push事件中充分運用這些模組 並顯示通知

瀏覽器相容性

notificationclose 事件

瀏覽器支援

  • 50
  • 17
  • 44
  • 16

資料來源

Clients.openWindow()

瀏覽器支援

  • 40
  • 17
  • 44
  • 11.1

資料來源

ServiceWorkerRegistration.getNotifications()

瀏覽器支援

  • 40
  • 17
  • 44
  • 16

資料來源

clients.matchAll()

瀏覽器支援

  • 42
  • 17
  • 54
  • 11.1

資料來源

如需詳細資訊,請參閱這份服務工作處理程序簡介 文章

後續步驟

程式碼研究室