u-ryo's blog

various information for coding...

Category: Javascript

Web Push Notification for PWA

| Comments

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などですぐ試せます。

自分の手に馴染ませないとよくわからないので、作ってみました

  1. push server側で、VAPIDのkey(public, private)を決めておく。key generationは、web-pushclient.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側)に読ませてみるようにしてみましたが、実運用上はベタに固定値書いておいた方が楽だしわかりやすいし再起動の度に値変わらないからその方がいいのかな。
  2. あと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化する必要がある。
  3. 上記でもらうnotificationEndPointって、mozillaならhttps://updates.push.services.mozilla.com/wpush/v2/...、chrome(chromium)ならhttps://fcm.googleapis.com/fcm/...というように、browser毎にmaker別のpush server持ってるんですね。ってことは、browserは裏ではこうしたsiteと常にconnection張ってるということ?
  4. もっと深い原理(具体的にどういうheader/encryptionでpush serviceに送っているか)はWeb Pushの実装まとめ(Chrome/Firefox/Android共通)参照。
  5. client側では、main.jsで、まずNotification.requestPermission()で許可を得る。許可を求めるtimingは、page load直後かと思いきや、最初にいきなり許可求めると拒否られることが多いということで、button式に。button押したら許可求める、と。
  6. 許可が得られたらnavigator.serviceWorker.register('/sw.js')でService Worker scriptを登録。navigator.serviceWorker.readyで利用可能になるのを待つ。これのreturn value(の型)はServiceWorkerRegistrationでありnavigator.serviceWorkerではないので注意。これからpushManagerを得ることになる。
  7. その後、push serverからserverのpublicKeyをGET。
  8. subscribeParamsを作ってserviceWorkerRegistration.pushManager.getSubscription(subscribeParams)してみる。既に登録があったらunsubscribeしないとならない。上述のように、parameterではserver側のpublicKeyをurlB64ToUint8Array()したものとuserVisibleOnly: trueは固定値。
  9. それからserviceWorkerRegistration.pushManager.subscribe(subscribeParams)でsubscribeする。
  10. そうすると、endpoint、key、authが得られるので、それらをpush server側に通知。
  11. sw.jsでは、push通知が喜た場合push eventがfireされるので、event.waitUntil(registration.showNotification(...)する。このregistrationがどこから来るのかよくわからなかったが、ともあれ明示的にwaitUntilしてshowNotificationしないと表示されない(pushが来て勝手に表示されるわけではない)。
  12. 表示されたNotificationをclickするとnotificationClick eventがfireされるので、clickしたらどこかへ遷移したい場合にはこのevent listenerをsw.jsに書いておく必要がある。
  13. PWA用に、sw.jsinstall eventとfetch event listenerでfile chacheの作成とその利用をcodeする(installでcacheに加え、fetchではcacheにあったらそれを、なければfetchするようにする)。
  14. 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を色々変えて試してみることで、 大分わかってきました。

BotUI - ChatBot Only by JavaScript

| Comments

paiza開発日誌で紹介されていたBotUI、なるほど予め型にはまった会話ならこれだけでお手軽にJavaScriptだけで(.then(function(){...})で繋ぐだけで)出来ちゃうんですね。注文を取るとか、特定のAPI叩く(Wizardを会話でやる)とか、サポートセンターで特定の電話番号につなぐとか。AIは使ってないので、user側の曖昧な自然言語を受け取って処理する、というものではないですけど、そういうのに繋げればいい? いや、IBMのWatsonとかみると、そういう会話のplatformも含めて提供しているので、そうなるとBotUIの出番は無い筈。 Git Repositoriesの総数を答えるsampleは、公式のsamplesより面白かった(興味深かった)です。