PWAの売りとしてWeb Push Notificationが出来るのはつとに聞いていましたが、 具体的にどうやるんだろう? と思ってました。 仕組みから考えて、WebSocketが必須なのかな? とかって勝手に思ってたんですが、違うんですね! 「やってみた」系の記事見て、Firebaseを使うものばっかりだったので、 えーっ! Firebase hosting必須なの?! と思いきや、 VAPID(Voluntary Application Server Identification)も あるということで一安心。 あーでも、別にFCM(Firebase Cloud Messaging)をhostingじゃなく使うことも出来るので、 その方が楽なのかな。 browser毎じゃなくてuser毎に送れるっていうし。 でもvendor lock onも嫌なので、 VAPIDからやってみました。
Web Pushの仕組みは、PWAのプッシュ通知の仕組みがよくまとまってました。 PWAとWeb Push Notificationは別なんですね。
browser上からは、Push Demoなどですぐ試せます。
自分の手に馴染ませないとよくわからないので、作ってみました。
- push server側で、VAPIDのkey(public, private)を決めておく。key generationは、web-pushの
client.js
をcommand lineから使って$ node_modules/web-push/src/cli.js generate-vapid-keys
とせよ、とか書いてあるものも他に多くありましたが、GoogleのAdding Push Notifications to a Web AppにあるようにPush Companionで簡単に作れますね。試しに作ってみたものではscriptで毎回自動生成してclient(=web browser, html and javascript側)に読ませてみるようにしてみましたが、実運用上はベタに固定値書いておいた方が楽だしわかりやすいし再起動の度に値変わらないからその方がいいのかな。 - あとserver側は各clientのnotificationEndPoint、publicKey、authを知ってないといけない。これらはclientが
serviceWorkerRegistration.pushManager.subscribe(subscribeParams)
した時に得られる。subscribeParameter
とは、server側のpublicKeyをurlB64ToUint8Array()
したものとuserVisibleOnly: true
は固定値。そうして得られた3つの値をserver側に引き渡さねばならないのだが、そのまま渡すのではなく、btoa(String.fromCharCode.apply(null, new Uint8Array(key)))
などとしてString化する必要がある。 - 上記でもらう
notificationEndPoint
って、mozillaならhttps://updates.push.services.mozilla.com/wpush/v2/...
、chrome(chromium)ならhttps://fcm.googleapis.com/fcm/...
というように、browser毎にmaker別のpush server持ってるんですね。ってことは、browserは裏ではこうしたsiteと常にconnection張ってるということ? - もっと深い原理(具体的にどういうheader/encryptionでpush serviceに送っているか)はWeb Pushの実装まとめ(Chrome/Firefox/Android共通)参照。
- client側では、
main.js
で、まずNotification.requestPermission()
で許可を得る。許可を求めるtimingは、page load直後かと思いきや、最初にいきなり許可求めると拒否られることが多いということで、button式に。button押したら許可求める、と。 - 許可が得られたら
navigator.serviceWorker.register('/sw.js')
でService Worker scriptを登録。navigator.serviceWorker.ready
で利用可能になるのを待つ。これのreturn value(の型)はServiceWorkerRegistration
でありnavigator.serviceWorker
ではないので注意。これからpushManager
を得ることになる。 - その後、push serverからserverのpublicKeyをGET。
subscribeParams
を作ってserviceWorkerRegistration.pushManager.getSubscription(subscribeParams)
してみる。既に登録があったらunsubscribeしないとならない。上述のように、parameterではserver側のpublicKeyをurlB64ToUint8Array()
したものとuserVisibleOnly: true
は固定値。 - それから
serviceWorkerRegistration.pushManager.subscribe(subscribeParams)
でsubscribeする。 - そうすると、endpoint、key、authが得られるので、それらをpush server側に通知。
sw.js
では、push通知が喜た場合push
eventがfireされるので、event.waitUntil(registration.showNotification(...)
する。このregistration
がどこから来るのかよくわからなかったが、ともあれ明示的にwaitUntil
してshowNotification
しないと表示されない(pushが来て勝手に表示されるわけではない)。 - 表示されたNotificationをclickすると
notificationClick
eventがfireされるので、clickしたらどこかへ遷移したい場合にはこのevent listenerをsw.js
に書いておく必要がある。 - PWA用に、
sw.js
のinstall
eventとfetch
event listenerでfile chacheの作成とその利用をcodeする(install
でcacheに加え、fetch
ではcacheにあったらそれを、なければfetchするようにする)。 - notificationをpushするには、push server側でまず
new PushService(publicKey, privateKey, "http://localhost")
してから、clientから貰った情報でpush.send(new Notification(...))
する。中では色々と暗号化しているが、nl.martijndwarsのwebpush-javaを使えばVAPIDのkey生成やnotificationのsendもone methodでよろしくやってくれる。他の言語もweb-push-libsに各種あり。
実際に自分で真似して書いてみて、 値やcodeを色々変えて試してみることで、 大分わかってきました。