推播通知 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

參考資料

附上之前的筆記。

推薦閱讀


comments powered by Disqus