推播通知 Push Notification
17 Feb 2018使用 Firebase 實作瀏覽器推播通知,這裡分為幾個部份說明:初始化 Firebase、訂閱 / 取消訂閱、前景處理接收到的訊息、背景處理接收到的訊息、遇到的問題 / 解法。
初始化 Firebase
這部份的程式碼與資料在「Add Firebase to your web app」都可直接取得。
firebase.initializeApp({
// initialize firebase
apiKey: 'AIz...',
authDomain: 'sample.firebaseapp.com',
databaseURL: 'https://sample.firebaseio.com',
projectId: 'sample-test-12345',
storageBucket: 'sample-test-12345.appspot.com',
messagingSenderId: '1234567890xy',
});
const messaging = firebase.messaging();
訂閱 / 取消訂閱
使用者提供的權限分為三種:「granted」(同意)、「denied」(拒絕)和「default」(未授權),其中老舊瀏覽器可能會有 undefined 的狀況。若使用者從未授權過(default)或 undefined(老舊瀏覽器的未知狀態),可使用 requestPermission
請求使用者授權來接受通知。
messaging.requestPermission().then(() => {
// notification permissin granted
messaging.getToken().then((currentToken) => {
// 授權成功後取得 token
});
});
若想更改權限設定,須至瀏覽器設定來設定允許、封鎖或詢問。
重設權限後,若為同意,則會重新取得 token。
messaging.onTokenRefresh(() => {
messaging.getToken().then((refreshedToken) => {
// token refreshed
// 重新取得 token
});
});
前景處理接收到的訊息
使用 onMessage
在前景(Foreground)監聽訊息。收到訊息後,就看是要更新網站的訊息中心或是建立瀏覽器通知(這是不好的做法,畢竟是在站內,可參考 Best Practices)。
messaging.onMessage((payload) => {
// 前景處理接收到的訊息
});
背景處理接收到的訊息
使用 setBackgroundMessageHandler
在背景(Background)即 Service Worker 中監聽訊息,例如:
- 關閉瀏覽器 或
- 開啟瀏覽器但未打開該網站的狀況
messaging.setBackgroundMessageHandler((payload) => {
const notificationTitle = '露天拍賣通知!';
const notificationOptions = {
body: '快回來露天拍賣!',
icon: 'https://www.ruten.com.tw/images/logo_s.gif',
click_action: 'https://mybid.ruten.com.tw/master/my.php',
};
return self.registration.showNotification(notificationTitle, notificationOptions);
});
遇到的問題 / 解法
FCM Messages 針對自訂欄位的處理方式
FCM 送到 client 端的兩種訊息類別分別為
- 通知訊息(Notification Messages):FCM 會過濾掉非已定義好的使用者可見的欄位,並將它們移到 Data Messages。
- 資料訊息(Data Messages):非使用者可見或自定義欄位。
所以,當想要取得使用者可見的欄位(例如:title、body、icon、click_action)時,要使用 payload.notification
;而要取得非使用者可見(例如:id、tag)或自訂欄位時(例如:custom_field),就要使用 payload.data
。
messaging.onMessage(payload => {
let notifyMsg = payload.notification,
dataMsg = payload.data;
new Notification(notifyMsg.title , {
body: notifyMsg.body,
icon: notifyMsg.icon,
click_action: notifyMsg.click_action,
id: dataMsg['gcm.notification.id'],
tag: dataMsg['gcm.notification.tag'],
custom_field: dataMsg['gcm.notification.custom_field'].
});
});
Service Worker 無法存取 DOM Element、Local Storage 等瀏覽器才有的資訊,該如何處理?
關於存取 DOM Element 的解法,以下參考 Access dom by web worker。
Service Worker 並沒有直接存取 DOM Element 的方法,但可透過 Web Worker 來 Post 訊息到 JavaScript Main UI Thread,再由這個 Main Thread 更新瀏覽器的 DOM Element。
Step 1:(1) 在載入 Service Worker 的位置加入監聽 Service Worker 的 message
事件;(2) 對 Service Worker 傳送訊息 「hi」
(function() {
'use strict';
if (!navigator.serviceWorker || !navigator.serviceWorker.register) {
console.log("This browser doesn't support service workers");
return;
}
// Listen to messages from service workers.
navigator.serviceWorker.addEventListener('message', function(event) {
console.log('Got reply from service worker: ' + event.data);
});
// Are we being controlled?
if (navigator.serviceWorker.controller) {
// Yes, send our controller a message.
console.log("Sending 'hi' to controller");
navigator.serviceWorker.controller.postMessage('hi');
} else {
// No, register a service worker to control pages like us.
// Note that it won't control this instance of this page, it only takes effect
// for pages in its scope loaded *after* it's installed.
navigator.serviceWorker
.register('service-worker.js')
.then(function(registration) {
console.log('Service worker registered, scope: ' + registration.scope);
console.log('Refresh the page to talk to it.');
// If we want to, we might do `location.reload();` so that we'd be controlled by it
})
.catch(function(error) {
console.log('Service worker registration failed: ' + error.message);
});
}
})();
Step 2:在 Service Worker 加入監聽的 message
事件來回應訊息
self.addEventListener('message', function(event) {
event.source.postMessage('Responding to ' + event.data);
});
什麼狀況會使用前景接收訊息?什麼狀況會使用背景接收訊息?
在這裡討論的是網站擁有多個 Domain 的狀況。接受授權的 Domain 會使用前景接收訊息,其餘 Domain(當成未打開該網站的狀況)或不打開瀏覽器的狀況會使用背景接受訊息。
若需支援 IE 10/11,要注意使用的版本
改用 Firebase v4.1.2。
出現錯誤訊息:Manifest: Line: 1, column: 1, Syntax error
(2021/06/14 更新)
背景
從 Codesandbox 下載專案在本機後,在瀏覽器的 console 出現錯誤訊息「Manifest: Line: 1, column: 1, Syntax error.」。
原因
此 app 沒有加入 manifest.json 檔案。
解法
在此 app 的 root 加入設定 manifest.json 檔案。
在 entry page 加入 manifest.json 。
<link rel="manifest" href="/manifest.json" />
必要包含的資訊
- name 或 short name:app 的名稱,short name 可用於限制字數的地方
- icons:根據瀏覽器或設備的狀況選擇適合的尺寸。
- start_URL:app 的起始網址
範例
{
"name": "吃什麼,どっち",
"short_name": "吃什麼",
"icons": [
{
"src": "/static/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/static/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "browser",
"start_url": "/"
}
測試
在 Chrome DevTools 的「Application」tab 查看 Manifest 項目,即可檢視 manifest.json 的狀況。
參考資料
- Add Firebase to your JavaScript Project
- Firebase Cloud Messaging Quickstart
- Firebase API Documentation
- Adding Push Notifications to a Web App
附上之前的筆記。