PWAの基礎知識(その9)「Push API/VAPID編」
Webブラウザにプッシュ通知を送れるPush APIは魅力的な機能ですが、各ブラウザによって実装が異なっていたり、Firebaseでプロジェクトを登録したりするのが手間でした。それを共通化し、さらにプロジェクト登録不要で使えるようにする仕組みがVAPIDになります。
今回はこのVAPIDを使ったPush APIの使い方です。
必要なもの
今回はVAPIDに対応したライブラリ、web-pushを使います。今回はNode.jsのライブラリです。zaru/webpushというRuby向けのライブラリもあります。
初期設定
まずサーバ側で秘密鍵と公開鍵を用意します。これは以下のコードで可能です。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const webpush = require("web-push"); | |
JSON.stringify(webpush.generateVAPIDKeys()); | |
// '{"publicKey":"BDC..tP4","privateKey":"Kne…Km8"}' |
この内容を application-server-keys.json
などとしてサーバに保存しておきます。
JavaScriptの準備
Service Worker
VAPIDを使った場合の魅力として、ペイロードが送れるという点が挙げられます。メッセージ付きで送れるので、クライアントサイドで処理判別ができます。そのための処理をService Worker用のJavaScriptに入れておきます。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
self.addEventListener('install', () => { | |
console.log('ServiceWorker がインストールされました'); | |
}); | |
self.addEventListener('push', ev => { | |
// payloadの取得 | |
const {title, msg, icon} = ev.data.json(); | |
self.registration.showNotification(title, { | |
icon: icon, | |
body: msg | |
}); | |
self.registration.pushManager.getSubscription().then(subscription => { | |
console.log(subscription); | |
}, err => console.log(err)); | |
}); |
クライアントサイド
次にクライアントサイドですが、まずService Workerを読み込みます。そして、その後プッシュ通知の登録状態を確認しています。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if (!('serviceWorker' in navigator)) { | |
// Service Worker非対応 | |
} | |
navigator.serviceWorker.register('./serviceworker.js') | |
.then(() => { | |
if (!('showNotification' in ServiceWorkerRegistration.prototype)) { | |
// プッシュ通知がサポートされていない場合 | |
return; | |
} | |
if (Notification.permission === 'denied') { | |
// プッシュ通知を拒否された場合 | |
return; | |
} | |
if (!('PushManager' in window)) { | |
// PushManagerが存在しない場合 | |
return; | |
} | |
return navigator.serviceWorker.ready; | |
}) | |
.then(serviceWorkerRegistration => { | |
return serviceWorkerRegistration.pushManager.getSubscription(); | |
}) | |
.then(subscription => { | |
if (!subscription) { | |
// すでに購読中 | |
} else { | |
// 未購読 | |
console.log(subscription.toJSON()); | |
} | |
}) |
購読を開始する際のイベントは次の通りです。大事なポイントとして、 applicationServerKey
を追加しています。ここには公開鍵をunit8にしたものを適用します(後述)。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
navigator.serviceWorker.ready | |
.then(serviceWorkerRegistration => { | |
return serviceWorkerRegistration.pushManager.subscribe({ | |
userVisibleOnly: true, | |
applicationServerKey: convertedVapidKey | |
}); | |
}) | |
.then(subscription => { | |
// 購読開始 | |
console.log(subscription.toJSON()); | |
}) |
公開鍵の設定です。 vapidPublicKey
には web-push で生成した公開鍵を指定します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const vapidPublicKey = 'BOO…YEk'; | |
const convertedVapidKey = urlBase64ToUint8Array(vapidPublicKey); | |
function urlBase64ToUint8Array(base64String) { | |
const padding = '='.repeat((4 – base64String.length % 4) % 4); | |
const base64 = (base64String + padding) | |
.replace(/\-/g, '+') | |
.replace(/_/g, '/'); | |
const rawData = window.atob(base64); | |
const outputArray = new Uint8Array(rawData.length); | |
for (let i = 0; i < rawData.length; ++i) { | |
outputArray[i] = rawData.charCodeAt(i); | |
} | |
return outputArray; | |
} |
Web Pushの購読データ(subscription)をJSON出力すると、次のような内容になっています。このデータをすべて保存しておきます(サーバなど)。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"endpoint":"https://fcm.googleapis.com/fcm/send/e7K…Hag", | |
"expirationTime":null, | |
"keys":{ | |
"p256dh":"BK7…e5U", | |
"auth":"DGt…D8g" | |
} | |
} |
Web Pushを送る
ではサーバからプッシュ通知を送ってみましょう。コードは以下のようになります。まず鍵の設定と受信者を指定します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const webpush = require("web-push"); | |
const keys = require("./application-server-keys.json"); | |
webpush.setVapidDetails( | |
"mailto:admin@moongift.jp", | |
keys.publicKey, | |
keys.privateKey | |
); | |
const subscribers = [ | |
{ | |
"endpoint":"https://fcm.googleapis.com/fcm/send/e7K…Hag", | |
"expirationTime":null, | |
"keys":{ | |
"p256dh":"BK7…e5U", | |
"auth":"DGt…D8g" | |
} | |
} | |
]; |
そしてプッシュ通知を送ります。 icon で表示するアイコンを指定しています。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const icon = `app.png`; | |
const params = { | |
title: "プッシュ通知です!", | |
msg: `これはサーバから送っています. 今は ${new Date().toLocaleString()} です。 メッセージとアイコンも送っています `, | |
icon: icon | |
}; | |
Promise.all(subscribers.map(subscription => { | |
return webpush.sendNotification(subscription, JSON.stringify(params), {}); | |
})) | |
.then(res => console.log(res)) | |
.catch(err => console.log('ERROR', err)); |
そしてGoogle ChromeやFirefoxに対して通知が送れます。
Firefoxの場合です。
VAPIDを使った場合の利点としては、Firebaseでプロジェクトを作る手間がなく、Webブラウザの共通仕様の基で開発できるということです。なお、Safariはこの仕様に則っていない(そもそも独自仕様)ので、対応できません。
コメントは受け付けていません。