knowledge base

マークアップ/フロントエンドエンジニアのWEB制作における備忘録です。平日はWEB屋、休日は社会人劇団の主宰・劇作家をしています。

【PWA】シリーズPWA (7) 暗号化してみよう

VAPID

やっぱり通知内容をカスタマイズしたい。 今回はそのために、VAPIDと呼ばれる仕組みを利用します。

必要なものは、Firebase Consoleから取得できる公開鍵と秘密鍵のペア。

SSHのようにこのキーペアを利用します。

利用する箇所は(1)通知登録時のPushManager (2)メッセージングサービスに通知を送るプログラム の2箇所です。

 

まず、Firebase Consoleからキーペアを取得します。

取得方法はUIの更新などあるため、今回も割愛しますが、以下のようなbase64形式のキーが取得できるはずです(セキュリティ上、...で省略しています)

公開鍵: BGz_P8oc2IJUlpLst ……
秘密鍵: 85FdjzL1HH52YtvFluR ……

ここまで取得できたら、まずmain.jsから変えてゆきましょう。

pushManager.subscribeにわたすオプションに項目を追加します。

navigator.serviceWorker.ready.then(function(registration) {
    return registration.pushManager.getSubscription().then(function(subscription) {
       if (subscription) return subscription;
        return registration.pushManager.subscribe({
            userVisibleOnly: true,
            applicationServerKey: convertedVapidKey
        }).then(function (subscription) {
            console.log(subscription.endpoint);  //この値の「https://fcm.googleapis.com/fcm/send/」以下の文字列がトークンになります。
        });
    })
});

applicationServerKeyという項目が追加されたのはわかりましたが、convertedVapidKeyという新登場の変数がありますね。

こちらの変数に格納する値を設定します。 先ほどFirebase Consoleから取得した公開鍵を埋め込んでいる箇所がありますので、ご確認ください。

// Base64形式をUnit8形式に変換する
var urlBase64ToUint8Array = function(base64String) {
  var padding = '='.repeat((4 - base64String.length % 4) % 4);
  var base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/');
  var rawData = window.atob(base64);
  var outputArray = new Uint8Array(rawData.length);
  for (var i = 0; i < rawData.length; ++i) {
    outputArray[i] = rawData.charCodeAt(i);
  }
  return outputArray;
}
var vapidPublicKey = '******';  // Firebase Consoleから取得した公開鍵を指定
var convertedVapidKey = urlBase64ToUint8Array(vapidPublicKey);

urlBase64ToUint8Arrayという関数がありますがここでは説明を割愛します。

スニペット的なものと捉えてください。

まとめると以下のようになります。

// Base64形式をUnit8形式に変換する
var urlBase64ToUint8Array = function(base64String) {
  var padding = '='.repeat((4 - base64String.length % 4) % 4);
  var base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/');
  var rawData = window.atob(base64);
  var outputArray = new Uint8Array(rawData.length);
  for (var i = 0; i < rawData.length; ++i) {
    outputArray[i] = rawData.charCodeAt(i);
  }
  return outputArray;
}
var vapidPublicKey = '******';  // Firebase Consoleから取得した公開鍵を指定
var convertedVapidKey = urlBase64ToUint8Array(vapidPublicKey);

navigator.serviceWorker.ready.then(function(registration) {
    return registration.pushManager.getSubscription().then(function(subscription) {
        if (subscription) {
            //viewSubscription(subscription);
            return subscription
        }
        return registration.pushManager.subscribe({
            userVisibleOnly: true,
            applicationServerKey: convertedVapidKey
        }).then(function (subscription) {
            console.log(subscription.endpoint);  //この値の「https://fcm.googleapis.com/fcm/send/」以下の文字列がトークンになります。
        });
    })
});

更に一手間加えます。

暗号化前は購読時に取得できるendpointの値をpush.jsに埋め込んでいましたが、今回は追加で値を取得して埋め込みます。

// Base64形式をUnit8形式に変換する
var urlBase64ToUint8Array = function(base64String) {
  var padding = '='.repeat((4 - base64String.length % 4) % 4);
  var base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/');
  var rawData = window.atob(base64);
  var outputArray = new Uint8Array(rawData.length);
  for (var i = 0; i < rawData.length; ++i) {
    outputArray[i] = rawData.charCodeAt(i);
  }
  return outputArray;
}
var vapidPublicKey = '******';  // Firebase Consoleから取得した公開鍵を指定
var convertedVapidKey = urlBase64ToUint8Array(vapidPublicKey);

navigator.serviceWorker.ready.then(function(registration) {
    return registration.pushManager.getSubscription().then(function(subscription) {
        if (subscription) return subscription
        return registration.pushManager.subscribe({
            userVisibleOnly: true,
            applicationServerKey: convertedVapidKey
        }).then(function (subscription) {
            var rawKey = subscription.getKey ? subscription.getKey('p256dh') : '';
            var key = rawKey ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawKey))) : '';
            var rawAuthSecret = subscription.getKey ? subscription.getKey('auth') : '';
            var auth = rawAuthSecret ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawAuthSecret))) : '';
            var endpoint = subscription.endpoint;
            console.log(key);
            console.log(auth);
            console.log(endpoint);
        });
    })
});

keyとauthという値を取得しています。

サーバーのキーペア以外にも、この端末へ通知を届けるためのサブスクリプションのキーペアが存在し、そちらを取得しています。

こちらもスニペット的に機械的に利用してしまったほうが今は幸せかもしれません。

メッセージングサービスに暗号化した情報を送る

push.jsはガラリと変わります。

web-pushモジュールを使用しますので、あらかじめnpmでインストールください。

www.npmjs.com

const webpush = require('web-push');

webpush.setVapidDetails(
  'admin@xxxxx.jp',
  '*******', // Firebase Consoleから取得した公開鍵を指定
  '*******'  // Firebase Consoleから取得した秘密鍵を指定
);

// 複数指定可能 サブスクリプションが有効であればここに登録した全てのエンドポイントに通知が送られます
const subscribers = [{
  endpoint: '*******', // main.jsのconsoleに表示させたエンドポイントを指定
  expirationTime: null,
  keys:{
    p256dh: '*******', // main.jsのconsoleに表示させたkeyを指定
    auth: '*******' // main.jsのconsoleに表示させたauthを指定
  }
}];

// 通知の内容
const params = {
  title: 'プッシュ通知です!',
  msg: 'これはサーバから送っています.メッセージとアイコンも送っています ',
  icon:  '/icon/launcher-icon-144x144.png'
};

// 通知を送信
Promise.all(subscribers.map(subscription => {
  return webpush.sendNotification(subscription, JSON.stringify(params), {});
}))
.then(function(res) { console.log(res); })
.catch(function(err) { console.log(err); });

また、serviceworker.jsでは前回通知内容に固定の文字列を渡していたので、以下に変更します。

self.addEventListener('push', function(event) {
  var data = event.data.json();
  var title = data.title;
  var icon = data.icon;
  var msg = data.msg;
  event.waitUntil(
    self.registration.showNotification(title, {
      icon: icon,
      body: msg
    })
  )
});

暗号化前と同様にコンソールからこのプログラムを実行することで通知が送られるはずです。

通知のメッセージやタイトルがこちらで指定した内容になっていることも確認いただけると思います。

次回予告

次回は、この仕組みの上で複数端末に同時に通知を送る方法について解説します。