接下來我們將探討一些網路推送的常見實作模式。
這牽涉到使用 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
事件
Clients.openWindow()
ServiceWorkerRegistration.getNotifications()
clients.matchAll()
如需詳細資訊,請參閱這份服務工作處理程序簡介 文章。