Overview
「Web Push 通知を実装してみた」で Web Push 通知の概要と, web-push モジュールを利用した Web Push 通知の実装を記載しました. しかしながら, web-push モジュールを利用した Web Push 通知は鍵のやりとりが少々煩雑で実装も複雑になってしまいます.
Firebase Cloud Messaging (FCM) を利用すると, 鍵のやりとりの代わりにトークンを利用することで Web Push 通知を簡単に実装することができます.
What is Firebase Cloud Messaging ?
クロスプラットフォームのプッシュ通知のためのソリューションです. 詳細は, こちらのドキュメントを参考にしてください.
How to use Firebase Cloud Messaging ?
Firebase console から, アプリに Firebase を追加します.
Install Firebase
$ npm init -y $ npm install --save firebase
Install Packages
今回は, クライアントサイドは, ES2015 (Babel) を利用し, サーバーサイドは express を利用して実装します. また, ビルドには webpack を利用します.
$ npm install --save body-parser express $ npm install --save-dev babel-core babel-loader babel-plugin-transform-class-properties babel-preset-es2015 webpack
webpack.config.js
module.exports = { entry: { js: './src/main.js' }, output: { path: `${__dirname}/public`, filename: 'app.js' }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: 'babel-loader', } ] }, devtool: 'source-map' };
.babelrc
{ "presets": ["es2015"], "plugins": ["transform-class-properties"] }
コマンドを簡単に実行できるように, npm scripts も定義しておきましょう.
package.json
// ... "scripts": { "build": "webpack", "start": "npm run build && node server.js" }, // ...
Implement Server Side Script
ローカルサーバーを起動できるように, サーバーサイドのスクリプトを実装します (Web Push に関連する処理はのちほど実装します).
server.js
'use strict'; const express = require('express'); const bodyParser = require('body-parser'); const app = express(); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); app.use(express.static('public')); const port = process.env.PORT || 5000; app.listen(port, () => { console.log(`Listening on port ${port} ...`); });
public/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Web Push Example by Firebase Cloud Messaging (FCM)</title> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" /> <link rel="stylesheet" href="./app.css" type="text/css" media="all" /> </head> <body> <div class="WebPush"> <form method="post" action="/api/webpush/subscribe"> <dl> <dt><label for="text-title">Title</label></dt> <dd><input type="text" id="text-title" name="text-title" /></dd> <dt><label for="text-body">Body</label></dt> <dd><input type="text" id="text-body" name="text-body" /></dd> <dt><label for="url-icon">Icon</label></dt> <dd><input type="url" id="url-icon" name="url-icon" /></dd> <dt><label for="url-link">Link</label></dt> <dd><input type="url" id="url-link" name="url-link" /></dd> </dl> <button type="submit">Web Push</button> </form> </div> <script type="text/javascript" src="./app.js"></script> </body> </html>
public/app.css
@charset "UTF-8"; * { margin: 0; padding: 0; } body { font-family:Helvetica, Arial, sans-serif; font-size: 16px; color: #999; line-height: 1.5; min-width: 320px; } .WebPush { margin: 24px auto 0; width: 90%; } dl > dt { margin-bottom: 0.5rem; font-size: 1.25rem; } dl > dd { margin-bottom: 0.5rem; } input { outline: none; border: 2px solid #CCC; padding: 0.5rem; width: 18rem; font-size: 1rem; color: #999; border-radius: 12px; transition: box-shadow 0.6s ease; } input:focus { box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.3) inset; } button { cursor: pointer; outline: none; margin-top: 1rem; border: none; padding: 1rem 1.5rem; font-size: 1rem; color: #FFF; background-color: #999; border-radius: 12px; transition: background-color 0.6s ease; } button:hover { background-color: #666; } button:active { box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.6) inset; }
以上の実装をして,
$ npm start
を実行すれば, http://localhost:5000 でページが表示されるようになります.
Implement Web Push
ここからが本題で, Firebase Cloud Messaging を利用した Web Push 通知を実装します.
まずは, クライアントサイドで読み込むスクリプトから実装します.
src/main.js
'use strict'; import * as firebase from 'firebase'; // Initialize Firebase const config = { // ... }; firebase.initializeApp(config); const messaging = firebase.messaging();
config は, Firebase console で Firebase に追加したアプリの初期化コードスニペットからコピペしてきます.
内容としては, 以下のようなキーとバリューのプレインオブジェクトです.
const config = { apiKey : '', authDomain : '.firebaseapp.com', databaseURL : 'https://.firebaseio.com', storageBucket : '.appspot.com', messagingSenderId : '', };
たったこれだけのコードで, Firebase Cloud Messaging を使う準備ができます. web-push モジュールを利用する場合と比較すると, 非常に簡潔になることがわかるかと思います.
続いて, Web Push 通知を有効にするために,
- 通知の許可をユーザーから得る
- Firebase トークンを取得する
処理を実装します.
src/main.js
// ...
if (navigator.serviceWorker) {
navigator.serviceWorker.register('./firebase-messaging-sw.js').then(() => {
return navigator.serviceWorker.ready;
}).catch((error) => {
console.error(error);
}).then((registration) => {
messaging.useServiceWorker(registration);
// 通知の許可 -> トークンの取得の順でないと, トークンの取得に失敗する
messaging.requestPermission().then(() => {
console.log('Notification permission granted.');
messaging.getToken().then((token) => {
console.log(token);
}).catch((error) => {
console.error(error);
});
}).catch((error) => {
console.log('Unable to get permission to notify.', error);
});
});
}
Firebase Messaging Object (messaging) が Notification オブジェクトをラップしているので, そのメソッド (messaging.requestPermission) を利用して, ユーザーから通知の許可を得るダイアログを表示し, 許可を得ることができれば, Firebase トークンの取得 (messaging.getToken) を実行します. コメントにあるように, この順を踏まないと, Firebase トークンの取得に失敗してしまいます.
また, Service Worker のファイルは, firebase-messagin-sw.js でないと警告が表示されるので, 命名は合わせておきます.
最後に, 取得した Firebase トークンをサーバーに送信して, 保存します.
src/main.js
// ...
if (navigator.serviceWorker) {
navigator.serviceWorker.register('./firebase-messaging-sw.js').then(() => {
return navigator.serviceWorker.ready;
}).catch((error) => {
console.error(error);
}).then((registration) => {
messaging.useServiceWorker(registration);
// 通知の許可 -> トークンの取得の順でないと, トークンの取得に失敗する
messaging.requestPermission().then(() => {
console.log('Notification permission granted.');
messaging.getToken().then((token) => {
console.log(token);
const options = {
method : 'POST',
headers : new Headers({ 'Content-Type' : 'application/json' }),
body : JSON.stringify({ token })
};
fetch('/api/webpush/register', options).then((res) => {
console.dir(res);
}).catch((error) => {
console.error(error);
});
}).catch((error) => {
console.error(error);
});
}).catch((error) => {
console.log('Unable to get permission to notify.', error);
});
});
}
ビルドして, public/app.js を生成しておきます.
$ npm run build
続いて, Firebase トークンを保存するための API を実装します.
server.js
// ... const bodies = []; app.post('/api/webpush/register', (req, res) => { const body = req.body; bodies.push(body); res.status(200).set('Content-Type', 'application/json').send(JSON.stringify(body)); });
特に難しい処理はなく, 受信した Firebase トークンを配列に保存して, Web Push 通知を送信するときに利用できるようにしておきます (実際には, DB に保存することになるでしょうが ).
そして, Web Push 通知を送信するための API を実装します.
server.js
// ... const https = require('https'); // ... const bodies = []; // ... app.post('/api/webpush/subscribe', (req, res) => { const notification = { title : req.body['text-title'], body : req.body['text-body'], icon : req.body['url-icon'] }; const data = { url : req.body['url-link'] }; Promise.all(bodies.map((body) => { return new Promise((resolve, reject) => { const options = { method : 'POST', host : 'fcm.googleapis.com', path : '/fcm/send', headers : { 'Content-Type' : 'application/json', 'Authorization' : 'key=`Your Server Key`' } }; const to = body.token; const content_available = true; https.request(options, (response) => { const data = []; response.on('data', (chunk) => data.push(chunk)); response.on('end', () => resolve(JSON.parse(Buffer.concat(data).toString()))); response.on('error', (error) => reject(error)); }).end(JSON.stringify({ notification, data, to, content_available })); }); })).then((result) => { res.status(200).set('Content-Type', 'application/json').send(JSON.stringify(result)); }).catch((error) => { res.status(500).set('Content-Type', 'application/json').send(JSON.stringify(error)); }); });
処理の概要としては, プッシュ 通知を送信する, https://fcm.googleapis.com/fcm/send に POST するための HTTP クライアントを実装して, そのレスポンスを Service Worker に渡すだけです.
'Authorization' : 'key=`Your Server Key`'
ここのキーの取得方法は,
- Google Developers Console にアクセス
- 「認証情報」の Server key の キーをコピペ
1 つ注意点としては, プッシュ通知に送る任意の情報として, data キーのプレインオブジェクトがありがますが, これを notification のなかに含めてしまうと, Service Worker 側でアクセスできないので, notification とは別に Service Worker に渡す必要があります.
あとは, Service Worker のスクリプトを実装をすれば完成です
public/firebase-messagin-sw.js
'use strict'; self.addEventListener('install', (event) => { event.waitUntil(skipWaiting()); }, false); self.addEventListener('activate', (event) => { event.waitUntil(self.clients.claim()); }, false); self.addEventListener('push', (event) => { if (!event.data) { return; } const parsedData = event.data.json(); const notification = parsedData.notification; const title = notification.title; const body = notification.body; const icon = notification.icon; const data = parsedData.data; event.waitUntil( self.registration.showNotification(title, { body, icon, data }) ); }, false); self.addEventListener('notificationclick', (event) => { event.waitUntil(self.clients.openWindow(event.notification.data.url)); }, false);
Service Worker の処理は, web-push モジュールを利用した場合の実装と大差ありません. push イベントで取得するデータの構造が少々変更されているぐらいです.