webブラウザにPush通知送るサーバとjsのサンプル
このドキュメントは
以下の2つのドキュメントをよりプリミティブに理解するためのDRYな備忘録です。
- Adding Push Notifications to a Web App | Web | Google Developers
- The Web Push Protocol | Web | Google Developers
背景
かつて サーバからブラウザにプッシュ通知を送りたい(非WebSocket、非ロングポーリング) - DRYな備忘録 これ書いたけど、改めてProgressive Web Appのドキュメント行ったらアホみたいに分かりづらくなってて不条理を感じたので書きます。
サーバからPush通知がブラウザに対して送れて、ブラウザのjsが閉じててもServiceWorkerが生きてるからNotificationが出る、っていうやつです。最近だとウェブのGmailとかFacebook Messanger若干うざいやつとかでよく見る気がします。
この公式ドキュメント、パッとサンプル落としてきてハイ動くでしょ!っていうタイプなのか最小限の要素を理解させつつ動かすタイプなのか、完全にどっち付かずのドキュメントになっていて、若干わかりにくいです。
今回は、WebPushProtocolにおける暗号化されたbodyを送る、というところを除き、クライアントのコードとサーバのコードを書いたので、そのログを備忘録として晒します。
概念の確認
サービスとしては、3つです
- ブラウザ
- ネイティブアプリ開発における「デバイス」だと理解して問題ないです
- ブラウザではふたつのことをします
- ServiceWorkerを登録し、登録したServiceWorkerにプッシュをsubscribeさせ、PushSubscriptionモデルを取得します
- これが、ApplePushNotificationにおける
Device Token
的なものです
- これが、ApplePushNotificationにおける
- ServiceWorker内部では、タブのJavaScriptが死んでもbackgroundで動き続けるイベントリスナーを登録します
push
が来たとき、というイベントリスナーもここで定義します
- ServiceWorkerを登録し、登録したServiceWorkerにプッシュをsubscribeさせ、PushSubscriptionモデルを取得します
- 自前サーバ
- ブラウザで登録・取得されたPushSubscriptionモデルのデータを、自作アプリケーションにおけるユーザなどと紐付けて保存しておく必要があります
- 何らかのトリガーで(たとえばユーザによる他ユーザへの呼びかけなど)下記の「Pushサービス」に「このsubscription endpointへこの内容でプッシュ通知送ってちょんまげ」というリクエストをする役割があります
- Pushサービス
- 各デバイスに対してPushを実際に送るサービスです
- Web-Push-Protocolのインターフェースを満たすサービスである必要があります
- 僕らは自前サーバからこのエンドポイント叩くだけなのでその違いを意識する必要は無い ← ここだいじ!
うるせえ動くコード見せろ
ウッス
こんな感じに動きます
実際の処理
- ブラウザに登録されるServiceWorker
- ソースコード: service-worker.js
- やること
- 自分自身に何かイベントがあったらそれをハンドリングする
install
,activate
,push
- 自分自身に何かイベントがあったらそれをハンドリングする
- 必要なもの
- 無い。ぶっちゃけ登録されるServiceWorkerがやるべき責務は小さい。
- ウェブページのmain
- ソースコード: main.js
- やること
- ServiceWorkerをブラウザにregisterする
- そしたら
registration.pushManager.subscribe()
というのを呼ぶ - 上記で得られた
subscription
モデルを自作サーバへ送りつける- この
subscription
というのが、Pushを提供するGCMサーバのエンドポイントと、このデバイス(ブラウザ)のDeviceToken的なものを持っている
- この
- 必要なもの
- ApplicationServerPublicKeyが必要
- サーバのみが知りうるApplicationServerPrivateKeyに対をなすもの
- ApplicationServerPublicKeyが必要
- Pushをトリガーする自前サーバ
- ソースコード: server.js(もちろんjsで書く必要は無い)
- やること
- ウェブページのjsから送られてきた
subscription
の保存 - 任意のトリガーでその
subscription
へPushを送る- 厳密にはPushを"送る"のはgcmなりMozilaPushServiceなので、「Pushサービスが提供したエンドポイントを叩く」のほうが正確
- また、Pushサービスを叩くときのリクエストはWeb-Push-Protocolで定められた方法で暗号化されている必要があるが、今回はこれはweb-pushライブラリにすべて任せている
- ウェブページのjsから送られてきた
- 必要なもの
- ApplicationServerPrivateKeyが必要
注意点・ハマったところ
ServiceWorkerの更新ができない
- 問題: ページをリロードしても編集したServiceWorkerが適用されていない
- 原因: ServiceWorkerのライフサイクルは「ブラウザ自体」と「ウェブページのドメイン」によって定められるのであって、「ウェブページのタブ(いわゆるいつものブラウザのjsランタイム)」ではないので、ページをリロードしても更新されない
- 解決:
getRegistraion
からのunregister
を呼ぶ。あるいはchrome://serviceworker-internals/
に行き該当ウェブページに登録されているServiceWorkerを[Unregister]ボタンで削除するなりする必要がある
Subscriptionの更新ができない
InvalidStateError: Registration failed - A subscription with a different applicationServerKey (or gcm_sender_id) already exists; to change the applicationServerKey, unsubscribe then resubscribe.
- 問題:
registration.pushManager.subscribe()
を呼ぶと上記のようなエラーが出ることがある。 - 原因: エラーメッセージでは「別のapplicationServerKeyを使ったsubscriptionがすでにあるので、applicationServerKeyを変えてunsubscribeしてからもっかいsubscribeしてね」と言っている。基本的にはApplicationServerPublicKeyと ApplicationServerPrivateKeyの組み合わせは、1つのプロジェクトについてただ1つだけなので、このようなエラーは起きないと思うが、開発中とかだと起きるかもしれませんねハイ。上記のサンプルでは、npmのpost-installで1度だけVAPIDを作るようにしてます
- 解決: エラーメッセージにおとなしく従う
ApplicationServerKeyとは
InvalidAccessError: Failed to execute 'subscribe' on 'PushManager': The provided applicationServerKey is not valid.
Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.
- 問題: subscribeするときに、上記のようなエラーが出ることがある。どうやらapplicationServerKeyがよくないようだが、そもそもApplicationServerKeyって何なのよ。
- 原因: 今回いちばんハマったところなんですが、ApplicationServerKeyっていうのは、WebPushProtocolの文脈でのただの言い方で、その他いろいろな文脈とは関係ない、ただの、定められた方法で生成されたPublicKeyとPrivateKeyのペアというだけです。ここにGCM_API_KEYとかFCM_SERVER_KEYとか突っ込んで困る、というケースが多そう。まあ僕もなんですけど。RSAとかも無関係、名前似てるけど。
- 解決: ほんで、この「Keyのペア」は、今回はweb-pushライブラリのgenerateVAPIDKeysに全部まかせています。PushサーバとしてGCMやMozilaPushServiceを叩くことこそあれど、今回のサンプルはGCM, FCM, MPSを関知しない、Web-Push-Protocolを用いたアプリケーションの構成のサンプルという位置づけです。
雑感
- 過渡期とはいえドキュメントわかりづらすぎでしょ。1年前は絶賛したわかりやすさだったのに…
- Pushのトリガーする自前サーバ、今んとこGoで書いてるんだけど、
- もっと日本語で丁寧なやつ見つけました
- 炭水化物ダイエットはじめた時は過激すぎて体調崩したけど、ちょっと食っていいルールにしたら安定してみるみる痩せてます
DRYな備忘録として
Building Progressive Web Apps: Bringing the Power of Native to the Browser
- 作者: Tal Ater
- 出版社/メーカー: Oreilly & Associates Inc
- 発売日: 2017/07/25
- メディア: ペーパーバック
- この商品を含むブログを見る
はじめてのNode.js -サーバーサイドJavaScriptでWebアプリを開発する-
- 作者: 松島浩道
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2013/03/13
- メディア: 単行本
- クリック: 15回
- この商品を含むブログ (5件) を見る