訂閱使用者

Matt Gaunt

第一步是取得使用者授權 向他們傳送推送訊息 購買 PushSubscription

要透過 JavaScript API 執行這項程序時相當直接順位 從邏輯流程中學習

特徵偵測

首先,我們必須檢查目前的瀏覽器是否支援推送訊息。我們可以 表示支援推送作業,但只要進行兩項簡單的檢查即可

  1. 在「Navgator」上檢查 serviceWorker
  2. 檢查 window 中是否有 PushManager
if (!('serviceWorker' in navigator)) {
  // Service Worker isn't supported on this browser, disable or hide UI.
  return;
}

if (!('PushManager' in window)) {
  // Push isn't supported on this browser, disable or hide UI.
  return;
}

雖然瀏覽器支援人員和 Service Worker 和 建議改用功能偵測 漸進增強

註冊 Service Worker

這項功能偵測到系統支援 Service Worker 和 Push下一步 註冊為「註冊」服務工作處理程序

註冊 Service Worker 時,我們會告知瀏覽器 Service Worker 檔案的位置。 這個檔案也仍然是 JavaScript,但瀏覽器會「允許存取」Service Worker API,包括推送項目確切來說,瀏覽器會在 Service Worker 中執行檔案 環境。

如要註冊 Service Worker,請呼叫 navigator.serviceWorker.register(),並傳入 這個檔案。如下所示:

function registerServiceWorker() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      console.log('Service worker successfully registered.');
      return registration;
    })
    .catch(function (err) {
      console.error('Unable to register service worker.', err);
    });
}

這個函式會告知瀏覽器是否有 Service Worker 檔案,以及檔案的位置。於 在本例中,Service Worker 檔案位於 /service-worker.js。瀏覽器幕後花絮 呼叫 register() 後會執行下列步驟:

  1. 下載 Service Worker 檔案。

  2. 執行 JavaScript。

  3. 如果一切運作正常且沒有錯誤,register() 會傳回承諾。 解決問題。如果出現任何類型的錯誤,承諾會予以拒絕。

如果 register() 拒絕,請檢查 Chrome 開發人員工具中的 JavaScript 是否有拼寫 / 錯誤。

register() 解析後,系統會傳回 ServiceWorkerRegistration。使用 註冊來存取 PushManager API

PushManager API 瀏覽器相容性

瀏覽器支援

  • Chrome:42.
  • Edge:17。
  • Firefox:44。
  • Safari:16.

資料來源

正在要求權限

我們已註冊 Service Worker,並已準備好訂閱使用者,下一步是 允許使用者傳送推送訊息。

用來取得權限的 API 相對簡單,缺點是 API 最近從接收回呼改為傳回 Promise。 發生問題,是我們無法判斷目前使用哪個版本的 API 實作了哪個版本? 因此,您必須同時實作並處理兩者。

function askPermission() {
  return new Promise(function (resolve, reject) {
    const permissionResult = Notification.requestPermission(function (result) {
      resolve(result);
    });

    if (permissionResult) {
      permissionResult.then(resolve, reject);
    }
  }).then(function (permissionResult) {
    if (permissionResult !== 'granted') {
      throw new Error("We weren't granted permission.");
    }
  });
}

在上述程式碼中,重要的程式碼片段是 Notification.requestPermission()。這個方法會向使用者顯示提示:

電腦版和行動版 Chrome 的權限提示。

使用者透過按下「允許」、「封鎖」或「關閉」的方式與權限提示互動後, 我們會將結果以字串形式提供給:'granted''default''denied'

在上述程式碼範例中,如果權限許可,askPermission() 傳回的承諾會解析 否則我們會擲回錯誤,做出承諾拒絕。

當使用者按一下「封鎖」按鈕。如果這是 ,您的網頁應用程式將無法再次要求使用者授予權限。所需作業 手動「解除封鎖」變更應用程式的權限,導致應用程式遭到忽略 設定面板請仔細思考向使用者要求權限的方式和時機。 因為如果他們按下「封鎖」 就無法輕易反轉該決定

好消息是大部分使用者 他們知道要求權限的原因。

我們稍後會介紹某些熱門網站要求取得權限。

透過 PushManager 訂閱使用者

一旦註冊服務工作人員並取得授權,使用者就能夠訂閱 正在撥打 registration.pushManager.subscribe()

function subscribeUserToPush() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      const subscribeOptions = {
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(
          'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
        ),
      };

      return registration.pushManager.subscribe(subscribeOptions);
    })
    .then(function (pushSubscription) {
      console.log(
        'Received PushSubscription: ',
        JSON.stringify(pushSubscription),
      );
      return pushSubscription;
    });
}

呼叫 subscribe() 方法時,我們會傳入「options」物件,該物件包含 必要和自選參數

現在來看看可以傳入的所有選項。

userVisibleOnly 選項

瀏覽器首次採用推送功能時,我們無法確定開發人員是否該 無法傳送推送訊息,但不顯示通知。這通常稱為靜音 不斷推斷,原因是使用者不知道在背景發生某個情況。

問題在於開發人員可以進行惡劣的分析,例如透過某個裝置追蹤使用者的位置 並在使用者不知情的情況下持續更新

為了避免這種情況,並讓規格作者有時間思考如何以最佳方式提供支援 地圖項目,新增了 userVisibleOnly 選項並傳入 true 的值,會是一項符號 網頁應用程式一律會在 (即未靜音推送)。

目前,您必須傳遞 true 的值。如果您沒有在網址中加入 userVisibleOnly 金鑰或傳入 false 就會發生下列錯誤:

目前 Chrome 僅支援 Push API 的訂閱項目, 向使用者顯示的訊息您只要呼叫 請改為使用「pushManager.subscribe({userVisibleOnly: true})」。詳情請見 https://goo.gl/yqv4Q4

目前 Chrome 永遠不會導入無訊息通知。 規格作者正在研究預算 API 的概念,此 API 能夠讓網頁應用程式 靜音推送訊息的數量 (根據網頁應用程式的使用情形計算得出)。

applicationServerKey 選項

先前提過「應用程式伺服器金鑰」先前的章節「應用程式 伺服器金鑰」可讓推送服務識別訂閱使用者的應用程式 確保同一個應用程式能向使用者傳送訊息。

應用程式伺服器金鑰是應用程式專屬的公開和私密金鑰組。 私密金鑰應保密至應用程式,且可共用公開金鑰

傳遞至 subscribe() 呼叫的 applicationServerKey 選項是應用程式的公開 鍵。瀏覽器會在訂閱使用者時將其傳遞至推送服務, 服務可以將應用程式的公開金鑰連結至使用者的 PushSubscription

下圖說明這些步驟。

  1. 網路瀏覽器會載入,並呼叫 subscribe() 並傳入公開應用程式 應用程式伺服器金鑰
  2. 接著,瀏覽器會向推送服務發出網路要求,然後產生端點 將這個端點與應用程式公開金鑰建立關聯,並將端點傳回給 。
  3. 瀏覽器會將這個端點新增至 PushSubscription,此端點會透過 保證 subscribe()

訂閱中使用了公開應用程式伺服器金鑰的插圖
方法。

如果您之後想要傳送推送訊息,必須建立 Authorization 標頭 當中含有使用應用程式伺服器私密金鑰簽署的資訊。當 推送服務收到傳送推送訊息的要求,可驗證這個已簽署的 Authorization 標頭 查詢與接收要求的端點連結的公開金鑰。如果簽名為 有效的推送服務,就會知道服務必須來自具有 比對私密金鑰。基本上,這是一套安全措施 傳送給應用程式使用者的訊息。

傳送私人應用程式伺服器金鑰時,系統會如何使用私人應用程式伺服器金鑰
訊息

嚴格說來,applicationServerKey 是選用項目。不過, Chrome 瀏覽器和 Chrome 瀏覽器,在 Chrome 瀏覽器上 未來的發展方向在 Firefox 中,此欄位為選用。

定義應用程式伺服器金鑰的「內容」規格 VAPID 規格 當您讀取「應用程式伺服器金鑰」「VAPID 金鑰」,請記得這些金鑰相同。

如何建立應用程式伺服器金鑰

如要建立一組公開和私密的應用程式伺服器金鑰,請前往 web-push-codelab.glitch.me,或是使用 web-push 指令列 如何產生金鑰:

    $ npm install -g web-push
    $ web-push generate-vapid-keys

您只需要為應用程式建立這些金鑰一次,只要確保 私密金鑰的私密性。(沒錯,我剛說過。)

權限與 subscription()

呼叫 subscribe() 有連帶效果。如果您的網頁應用程式沒有 顯示呼叫 subscribe() 時,瀏覽器會要求 授予您相關權限如果您的 UI 可以用於此流程,這項功能就非常實用 控管 (而且我認為大多數開發人員都會) 使用 Notification.requestPermission() API

什麼是 PushSubscription?

我們呼叫 subscribe() 並傳入一些選項,得到的承諾會解析為 PushSubscription 後會產生類似如下的程式碼:

function subscribeUserToPush() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      const subscribeOptions = {
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(
          'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
        ),
      };

      return registration.pushManager.subscribe(subscribeOptions);
    })
    .then(function (pushSubscription) {
      console.log(
        'Received PushSubscription: ',
        JSON.stringify(pushSubscription),
      );
      return pushSubscription;
    });
}

PushSubscription 物件包含傳送推送作業所需的所有必要資訊 傳送給使用者的訊息如果使用 JSON.stringify() 列印內容,您會看到 包括:

    {
      "endpoint": "https://some.pushservice.com/something-unique",
      "keys": {
        "p256dh":
    "BIPUL12DLfytvTajnryr2PRdAgXS3HGKiLqndGcJGabyhHheJYlNGCeXl1dn18gSJ1WAkAPIxr4gK0_dQds4yiI=",
        "auth":"FPssNDTKnInHVndSTdbKFw=="
      }
    }

endpoint 是推送服務網址。如要觸發推送訊息,請提出 POST 要求 。

keys 物件包含的值,可用於加密與推送訊息一併傳送的訊息資料 (本節稍後將有說明)。

定期重新訂閱,避免過期

訂閱推播通知時,您通常會收到 nullPushSubscription.expirationTime。理論上來說,這代表訂閱項目絕不會過期 (相對於收到 DOMHighResTimeStamp 的時間,後者會顯示訂閱到期的確切時間點)。但實際上,瀏覽器仍會使訂閱到期,例如長時間沒有收到推播通知,或是瀏覽器偵測到使用者未使用具備推播通知權限的應用程式時。為了避免這種情況,其中一種模式是在每則收到通知時重新訂閱使用者,如以下程式碼片段所示。您必須頻繁地傳送通知,瀏覽器才不會自動在訂閱到期,因此,請務必仔細衡量正當通知需求的優點與缺點,以免使用者無意間發送垃圾內容,以免訂閱過期。畢竟,您不應該反而為了保護使用者,避免他們因忘記通知而訂閱太多內容。

/* In the Service Worker. */

self.addEventListener('push', function(event) {
  console.log('Received a push message', event);

  // Display notification or handle data
  // Example: show a notification
  const title = 'New Notification';
  const body = 'You have new updates!';
  const icon = '/images/icon.png';
  const tag = 'simple-push-demo-notification-tag';

  event.waitUntil(
    self.registration.showNotification(title, {
      body: body,
      icon: icon,
      tag: tag
    })
  );

  // Attempt to resubscribe after receiving a notification
  event.waitUntil(resubscribeToPush());
});

function resubscribeToPush() {
  return self.registration.pushManager.getSubscription()
    .then(function(subscription) {
      if (subscription) {
        return subscription.unsubscribe();
      }
    })
    .then(function() {
      return self.registration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array('YOUR_PUBLIC_VAPID_KEY_HERE')
      });
    })
    .then(function(subscription) {
      console.log('Resubscribed to push notifications:', subscription);
      // Optionally, send new subscription details to your server
    })
    .catch(function(error) {
      console.error('Failed to resubscribe:', error);
    });
}

將訂閱項目傳送至您的伺服器

推送訂閱後,請將它傳送至伺服器。要怎麼做都由您決定 不過有個小秘訣,就是使用 JSON.stringify() 從 Vertex AI 中擷取所有必要資料 訂閱項目物件你也可以將 手動產生結果,如下所示:

const subscriptionObject = {
  endpoint: pushSubscription.endpoint,
  keys: {
    p256dh: pushSubscription.getKeys('p256dh'),
    auth: pushSubscription.getKeys('auth'),
  },
};

// The above is the same output as:

const subscriptionObjectToo = JSON.stringify(pushSubscription);

在網頁上傳送訂閱項目的過程如下:

function sendSubscriptionToBackEnd(subscription) {
  return fetch('/api/save-subscription/', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(subscription),
  })
    .then(function (response) {
      if (!response.ok) {
        throw new Error('Bad status code from server.');
      }

      return response.json();
    })
    .then(function (responseData) {
      if (!(responseData.data && responseData.data.success)) {
        throw new Error('Bad response from server.');
      }
    });
}

節點伺服器會收到這項要求,並將資料儲存至資料庫,以供日後使用。

app.post('/api/save-subscription/', function (req, res) {
  if (!isValidSaveRequest(req, res)) {
    return;
  }

  return saveSubscriptionToDatabase(req.body)
    .then(function (subscriptionId) {
      res.setHeader('Content-Type', 'application/json');
      res.send(JSON.stringify({data: {success: true}}));
    })
    .catch(function (err) {
      res.status(500);
      res.setHeader('Content-Type', 'application/json');
      res.send(
        JSON.stringify({
          error: {
            id: 'unable-to-save-subscription',
            message:
              'The subscription was received but we were unable to save it to our database.',
          },
        }),
      );
    });
});

解決伺服器上的 PushSubscription 詳細資料後,我們建議將使用者導向 或傳送訊息

定期重新訂閱,避免過期

訂閱推播通知時,您通常會收到 nullPushSubscription.expirationTime。理論上來說,這代表訂閱項目絕不會過期 (相對於收到 DOMHighResTimeStamp 的時間,後者會顯示訂閱到期的確切時間點)。但實際上,瀏覽器仍會使訂閱到期,例如長時間沒有收到推播通知,或是瀏覽器偵測到使用者未使用具備推播通知權限的應用程式時。為了避免這種情況,其中一種模式是在每則收到通知時重新訂閱使用者,如以下程式碼片段所示。您必須頻繁地傳送通知,瀏覽器才不會自動在訂閱到期。此外,請務必審慎評估正當通知所需要的優缺點,避免使用者收到垃圾郵件,以免訂閱項目失效。畢竟,您不應該反而為了保護使用者,避免他們因忘記通知而訂閱太多內容。

/* In the Service Worker. */

self.addEventListener('push', function(event) {
  console.log('Received a push message', event);

  // Display notification or handle data
  // Example: show a notification
  const title = 'New Notification';
  const body = 'You have new updates!';
  const icon = '/images/icon.png';
  const tag = 'simple-push-demo-notification-tag';

  event.waitUntil(
    self.registration.showNotification(title, {
      body: body,
      icon: icon,
      tag: tag
    })
  );

  // Attempt to resubscribe after receiving a notification
  event.waitUntil(resubscribeToPush());
});

function resubscribeToPush() {
  return self.registration.pushManager.getSubscription()
    .then(function(subscription) {
      if (subscription) {
        return subscription.unsubscribe();
      }
    })
    .then(function() {
      return self.registration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array('YOUR_PUBLIC_VAPID_KEY_HERE')
      });
    })
    .then(function(subscription) {
      console.log('Resubscribed to push notifications:', subscription);
      // Optionally, send new subscription details to your server
    })
    .catch(function(error) {
      console.error('Failed to resubscribe:', error);
    });
}

常見問題

目前大家曾提出的幾個常見問題:

我可以變更瀏覽器使用的推送服務嗎?

否。推送服務是由瀏覽器選取,如同我們所見 subscribe() 呼叫,瀏覽器會向推送服務發出網路要求 擷取 PushSubscription 的組成詳細資料。

每種瀏覽器都使用不同的推送服務,沒有不同的 API 嗎?

所有推送服務都將採用相同的 API。

這個常見的 API 稱為 網路推送通訊協定 並描述網路要求 您的應用程式必須 進行 ,才能觸發推送訊息。

如果在電腦上訂閱使用者,他們是否也會在手機上訂閱?

很抱歉,不行。使用者在想要使用的每個瀏覽器上註冊接收推送 接收訊息。另外值得注意的是 使用者會在每部裝置上授予權限。

後續步驟

程式碼研究室