Progressive Web App 推播通知行為 用戶端行為設定與事件流程處理

me
林彥成
2021-09-29 | 3 min.
文章目錄
  1. 1. 推播通知行為
  2. 2. 通知行為設定
    1. 2.1. 通知分組 (Tag)
    2. 2.2. 重複通知 (Renotify)
    3. 2.3. 靜音通知設定 (Silent)
    4. 2.4. 通知互動設定 (Require Interaction)
    5. 2.5. 通知動作 (Actions)
  3. 3. 通知事件流程處理
    1. 3.1. 點擊通知
      1. 3.1.1. 開啟視窗
      2. 3.1.2. Focus Tab
    2. 3.2. 關閉通知
    3. 3.3. 合併通知
  4. 4. 實作

推播通知行為

推播通知行為主要分成視覺、後續事件兩大部分,前幾天的文章開箱了:

這篇文章主要會更進一步解說視覺上可以進行設定的參數,以及收到通知後的行為,關於行為相關的參數如下。

1
2
3
4
5
6
7
8
9
10
11
{
"//": "行為相關參數",
"tag": "<String>",
"renotify": "<Boolean>",
"data": "<Anything>",
"requireInteraction": "<boolean>",
"silent": "<Boolean>",

"//": "視覺與行為參數",
"actions": "<Array of Strings>"
}

如果擔心覺得太抽象,歡迎先用以下連結的通知產生器先試玩:
https://tests.peter.sh/notification-generator/

這次的 Demo 連結如下也歡迎各位大大試玩看看:
https://linyencheng-push-notification.herokuapp.com/

通知行為設定

除了單純 UI 顯示相關的參數以外,接下來介紹一些會影響流程的相關參數:

  • 通知分組 (Tag)
  • 重複通知 (Renotify)
  • 通知動作 (Actions)
  • 靜音通知設定 (Silent)
  • 通知互動設定 (Require Interaction)

通知分組 (Tag)

Tag 這個設定是方便讓訊息不要一直疊加,只要有同個 Tag 的新訊息就會關掉舊的並用新的取代,要注意的是後面取代的通知都不會觸發裝置的聲音或是震動。

1
2
3
4
5
6
const title = "Notification 1 of 3";
const options = {
body: "With 'tag' of 'message-group-1'",
tag: "message-group-1",
};
registration.showNotification(title, options);

重複通知 (Renotify)

因為 Tag 的實作不會觸發聲音或是震動,在聊天軟體中的某些情況下需要再次被叮咚,所以在 tag 的基礎上增加了 renotify 這個配置。

1
2
3
4
5
6
const title = "Notification 2 of 2";
const options = {
tag: "renotify",
renotify: true,
};
registration.showNotification(title, options);

靜音通知設定 (Silent)

停用震動、鈴聲硬體通知。

1
2
3
4
5
const title = "Silent Notification";
const options = {
silent: true,
};
registration.showNotification(title, options);

通知互動設定 (Require Interaction)

需要等使用者針對通知互動後才會關閉通知,Android 沒有這個問題但 Windows 7 桌面版本的通知會在一定時間後消失,直到 Windows 10 才會在通知列,所以有了這個配置。

1
2
3
4
5
6
const title = "Require Interaction Notification";
const options = {
body: "With \"requireInteraction: 'true'\".",
requireInteraction: true,
};
registration.showNotification(title, options);

通知動作 (Actions)

透過定義 actions 就能夠讓通知帶有按鈕,將通知加上按鈕後,就可以針對按鈕補上後續的事件,以這次小編實作的功能來說,就是以下的配置。

1
2
3
4
5
6
7
8
9
10
self.registration.showNotification(data.title, {
image: "https://linyencheng.github.io/img/404-bg.jpg",
icon: "https://linyencheng.github.io/img/icon_wechat.png",
vibrate: [200, 100, 200, 100, 400],
body: "嗨,我是彥成,喜歡爬山的前端工程師,有個部落格叫前端三分鐘 :)",
actions: [
{ action: "know-more", title: "了解更多" },
{ action: "fans", title: "按讚粉專" },
],
});

實作後實際的畫面

通知事件流程處理

經過相關進階設定後,就是針對相關的事件進行後續的處理和優化:

  • 點擊通知
    • 開啟視窗
    • Focus Tab
  • 關閉通知
  • 合併通知

點擊通知

透過 service worker 去監聽點擊的事件做後續處理。

1
2
3
self.addEventListener("notificationclick", function (event) {
const clickedNotification = event.notification;
});

開啟視窗

點擊通知後開啟視窗並開啟網頁。

1
2
3
const examplePage = "/hello.html";
const promiseChain = clients.openWindow(examplePage);
event.waitUntil(promiseChain);

Focus Tab

如果 URL 已經在瀏覽器中被開啟就直接到那個頁面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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);

關閉通知

關閉通知也有相關事件。

1
2
3
self.addEventListener("notificationclose", function (event) {
const dismissedNotification = event.notification;
});

合併通知

假設今天加入了 requireInteraction 通知就會常駐,若是通知一直疊加上去也是非常惱人,這時候就可以透過合併通知的設計來優化用戶體驗。

  1. 先抓出現有的
  2. 把現有的關閉再發一則新的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
});

if (currentNotification) {
currentNotification.close();
}

實作

這次小編實作的程式碼如下:

  1. 跳過等待直接生效
  2. 處理 push 事件,收到就顯示通知,並且在通知加上 aciton
  3. 處理 notificationclick 事件,並且針對不同 action 加上各自的事件
  4. 開啟不同的連結
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
self.addEventListener("install", function (event) {
self.skipWaiting();
});

self.addEventListener("push", (event) => {
const data = event.data.json();
switch (data.title) {
case "前端三分鐘":
self.registration.showNotification(data.title, {
image: "https://linyencheng.github.io/img/404-bg.jpg",
icon: "https://linyencheng.github.io/img/icon_wechat.png",
vibrate: [200, 100, 200, 100, 400],
body: "嗨,我是彥成,喜歡爬山的前端工程師,有個部落格叫前端三分鐘 :)",
actions: [
{ action: "know-more", title: "了解更多" },
{ action: "fans", title: "按讚粉專" },
],
});
break;
default:
self.registration.showNotification(data.title, {
icon: "https://linyencheng.github.io/img/icon_wechat.png",
body: "恭喜! 成功註冊推播通知",
});
break;
}
});

self.addEventListener("notificationclick", function (event) {
let url;
if (!event.action) {
console.log("Notification Click.");
return;
}

switch (event.action) {
case "know-more":
url = "https://linyencheng.github.io/";
break;
case "fans":
url = "https://www.facebook.com/linyencheng.tw";
break;
default:
console.log(`Unknown action clicked: '${event.action}'`);
break;
}

event.notification.close(); // Android 需要觸發關閉
event.waitUntil(
clients.matchAll({ type: "window" }).then((windowClients) => {
// 看有沒有開過了
for (var i = 0; i < windowClients.length; i++) {
var client = windowClients[i];
// 有的話就 Focus 就好了
if (client.url === url && "focus" in client) {
return client.focus();
}
}
// 開新的頁面
if (clients.openWindow) {
return clients.openWindow(url);
}
})
);
});

喜歡這篇文章,請幫忙拍拍手喔 🤣

share