推播通知 Push Notification

使用 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 端的兩種訊息類別分別為

  1. 通知訊息(Notification Messages):FCM 會過濾掉非已定義好的使用者可見的欄位,並將它們移到 Data Messages。
  2. 資料訊息(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。

可參考-Error in IE9 and IE10. Cannot instantiate firebase-database.js - be sure to load firebase-app.js first

出現錯誤訊息: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": "吃什麼",
  "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 的狀況。

參考資料

附上之前的筆記。

推薦閱讀


Push Notification Service Worker PWA Firebase Worker