knowledge base

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

【PWA】シリーズPWA (6) プッシュ通知を送ってみる

はじめに

WebPushと呼びます。 ServiceWorkerを使わなくてもJavascriptでプッシュ通知自体は実装できます。

しかしそれはサイトをブラウザ表示したときのみ通知されるもので、そうでなくても送られるプッシュ通知とは異なるものです。

(厳密にいうと参照先のオブジェクトが異なります。通常の通知機能はNotificationオブジェクトを利用しますが、プッシュ通知機能はServiceWorkerRegistration.showNotificationメソッドを利用します。ただ引数はNotificationオブジェクトのコンストラクタと同じだとか。プッシュ通知を実装場合は、後者を使います)

ただし、ネイティブアプリケーションのそれとは異なり、WebPush によるプッシュ通知はブラウザが起動しているときでないと受信できません。

ブラウザを起動していない間にアプリケーションサーバーからプッシュ通知が送信された場合は、その次にブラウザを起動したタイミングで受信されます。

今回はキャッシュ制御よりも若干複雑になりますが、一つ一つをゆっくり紐解きつつ丁寧に追ってゆきます。

前提知識としてのWebPush APIの仕様やPushManagerの仕様についてはあえて解説はしませんので、適宜下記のリファレンスや参考記事等も合わせてご覧ください。

developer.mozilla.org

developer.mozilla.org

developers.google.com

qiita.com

基本的な仕組み

このような流れになります。

  1. メッセージングサービスでプロジェクトの登録
  2. manifest.jsonにプロジェクトとの紐付けを記述
  3. クライアント側ではServiceWorker登録時に通知の購読をしておく
  4. メッセージングサービスに対してリクエストを送る。この時にメッセージングサービスを利用するための認証情報やエンドポイントなどをリクエストヘッダーに混ぜて送る。この時に正しく暗号化されていれば通知に表示するメッセージなどをpayloadで送ることもできる。
  5. メッセージングサービスが、送信されてきたエンドポイントへ通知を送る。
  6. ServiceWorkerが通知を受け取ったら、showNotificationメソッドを利用してクライアントアプリ(ブラウザ)に通知

フロントエンドでの対応が必要となるのはこのうち 1 / 2 / 6 です。 特に 4 はバックエンドの開発やDB・インフラ設計が必要になります。

なにを用意すればWebPushが利用できるの?

先述の通りフロントエンドのみでは完結しません。WebPush実装に必要なものは次の3要素です。

  • プッシュサーバ:メッセージングサービスを提供するサーバ。インフラやサービスを構築する代わりにFirebaseが提供するFirebase Cloud Messagingを利用するのが近道。
  • アプリケーションサーバ:プッシュサーバに通知をリクエストするサーバ。今回でいうとHTMLをアップしているWebサーバーです。
  • クライアントアプリ:HTMLとServiceWorker。

アプリとメッセージングサービスを紐付けましょう

まずはアプリとメッセージングサービスとを紐づけるために、 というのもこれを最初に行なっていないと(後述するvapidを使用していない実装方法の場合) 通知を許可するダイアログを表示することも、通知を購読したり表示するロジックの検証をすることもできません。

現状メッセージングサービスはFirebase Cloud Messaging一択と言っても差し支えないので、Firebaseでプロジェクトを登録します。

プロジェクトの登録や必要な情報を取得するためのUIや手順は、各種サードパーティのコンソールと同様に頻繁に変更されるのでここでは割愛します。

無事に取得できたら、manifest.jsonにてgcm_sender_idというキーに対応する値としてメッセージングサービスに登録したプロジェクトの送信者ID番号を記述します(ここでは01234567が送信者ID番号であると仮定しています)。

"gcm_sender_id" : "01234567"

これでアプリとメッセージングサービスが紐づいたので、通知を許可するダイアログの表示と、通知の購読を実装します。

通知を許可するダイアログの表示と、通知の購読

こちらは至極シンプルで、ServiceWorkerRegistration.pushManager.subscribeメソッドを利用します。

基本的にプッシュ通知まわりはこのPushManagerが提供するインターフェイスを利用すことになります。

ServiceWorkerが登録されるとPushManagerも利用できるため、そのタイミングでsubscribeメソッドを呼び出します。

navigator.serviceWorker.ready.then(function(registration) {
	return registration.pushManager.subscribe({
		userVisibleOnly: true
	}).then(function (subscription) {
		console.log('通知の購読を開始します');
	});
});

subscribeメソッドを実行した時にメッセージングサービスとアプリが紐づいていれば、通知の許可をたずねるダイアログが表示され、許可したら購読が始まります。

ですがこのままだと、次回以降の来訪時にも、通知の購読を行なってしまうため、すでに購読済みの通知があるかを判定します。

こちらはmain.jsに記述をします。

navigator.serviceWorker.ready.then(function(registration) {
	return registration.pushManager.getSubscription().then(function(subscription) {
		if (subscription) return subscription;
		registration.pushManager.subscribe({
			userVisibleOnly: true
		}).then(function (subscription) {
			console.log('購読を登録しました');
		});
	})
});

getSubscriptionメソッドにて、すでに購読済みの通知があるかどうか判定します。

その際に購読済みの通知がある場合はそこで終了し、なければ先述した通り購読の開始を行います。

こちらで、通知を許可するダイアログの表示と、通知の購読が実装できました。

通知を表示する

実際に通知が来た時に、通知を表示するのはServiceWorkerの役割です。

通知がくるとpushイベントが発火するため、serviceworker.jsに以下のように記述します。

self.addEventListener('push', function(event) {});

ここに通知が来たときの処理を追記してゆきます。

eventオブジェクトにて(然るべき暗号化を行なっていれば)通知の内容を参照できるのですが、今回は暗号化については割愛するので、eventオブジェクトは参照せず、固定のタイトルやテキストを表示させることにします。

self.addEventListener('push', function(event) {
	self.registration.showNotification('PWA Test', {
		icon: '/icon/launcher-icon-144x144.png',
		body: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit'
	})
})

通知の表示はshowNotificationメソッドで行います。

第一引数に通知のタイトルを、第二引数にアイコンとテキストなどを指定することができます。

これで、メッセージングサービスから通知が来た時に、ブラウザにその内容を表示することができます。

ちなみに、もし然るべき暗号化がされており、eventオブジェクトに通知の内容が格納されている場合は、以下のようにすることができます。

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

アプリケーションサーバーからメッセージングサービスへリクエストを送る(からの、送って通知を表示する)

今回はまだ実装も初期段階なので、

(1)直接プログラムを叩いてリクエストを送ります

(2)また複数端末に同時に通知を送ることはせず自身の端末にのみ通知を送ります。

理由は(1)(2)ともバックエンドの開発が必要になるためです。 比較的実装が楽なNode.jsにて(1)のプログラムを「push.js」というファイル名で作成してみます。

//requestモジュールをrequire
var request = require('request');

//オプションを定義
var url = 'https://fcm.googleapis.com/fcm/send';
var serverkey = '*****'; // Firebaseコンソールから取得できるサーバーキー
var token = '*****'; // 購読登録時にエンドポイントから取得できるトークン(端末ひとつひとつを識別する)

// HTTP header
var headers = {
	'Content-Type': 'application/json',
	'Authorization': 'key=' + serverkey
};

// request options
var options = {
	url: url,
	method: 'POST',
	headers: headers,
	json: {
		'to': token
	}
};

//リクエスト送信
request(options, function(error, response, body) {
	if (body) {
		console.log(body);
	}
	if (error) {
		console.log(error);
	}
});

こちらが実装できたらコンソールから node push.js で実行することで通知が行くはずです。

ちなみにrequest optionsとしてわたすjsonには通知内容となるpayloadも送れると書かれている記事がありますが、現在は(暗号化しないとpayloadをおくれないという)仕様変更により割愛しています。

さてここでサーバーキーはFirebaseコンソールから取得できるとして、トークンはどうやって取得するのでしょうか。

main.js側に一手間加えます。

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

コンソールに、subscription.endpoint プロパティを出力しています。

Push API の PushSubscriptionにはその端末を識別するエンドポイントや、このあと暗号化に用いる認証キーなどが格納されています。

ちなみにこのエンドポイントは端末によって異なりますので、複数の端末に通知を送りたい時は、全てのエンドポイントをDBなどで管理する必要があります。

今回の実装でいうと、コンソールに出力したエンドポイントからトークンとなる文字列をコピーし、push.jsに貼り付けることになります。

ちなみに

予備知識として補足

Basic認証が設定されていても通知は届く?
届きます。ただしミニ情報バーの実装について触れている通り、manifest.jsonを正しく読めているようにしましょう(マニフェストのみ認証を解除するか、usecredential属性を設定)
また、OSの通知UIがアイコンを取得しにいけない場合があるので、アイコンが置かれているディレクトリは認証を解除してあげたほうがベターです
通知のサブスクリプションが変わるタイミングはいつ?
以下のとおりです。
(1)通知のパーミッションに変更があった時
(2)ServiceWorkerがUnregisterされた時
意外にもServiceWorkerにアップデートがあった時は起こらないよう。

次回予告

次回は通知内容をカスタマイズするため、暗号化について触れたいと思います。