u-ryo's blog

various information for coding...

Category: Pwa

Dont Use the Same Image File in Css and Manifest.webapp on Webpack

| Comments

notificationに使っていたlogicを変えたいと思って、 アプリとしてinstallしていたPWAを一旦uninstallしてから browserでそのpageを読んでも、「ホーム画面に追加」が現れなかったんですね。 何でんだろー、一旦uninstallしちゃうとダメなのかな? いやそんなことないだろうな、とは思いつつも 明確に「uninstallしても大丈夫」だと書いてあるところもなく、 心配だったんです。 「ホーム画面に追加」が出ないのは何が悪いのか、わからないんですよね。

「ホーム画面に追加」が出る条件、というのがあって、 公式ページ(ウェブアプリのインストール バナー)によると、

  1. short_name(ホーム画面で使用)
  2. name(バナーで使用)
  3. 192x192のpngアイコン(アイコンの宣言にはMIMEタイプimage/pngの指定が必要)
  4. 読み込み先のstart_url

というんですが、 「ホーム画面に追加」からはじめる『PWA(Service Worker)』には192のpngの話はなく、 また【3ステップではじめる】PWAによる「ホーム画面に追加」バナーの実装ではfetch eventの定義が必要だとあります。 こちとら、一度は出来たわけだし、実際には PWAで「ホーム画面に追加」が表示されない時に確認する事にある「manifest.json内のアイコン画像が404である」といったところが一番多いんじゃないでしょうか。 この人もそうだったといいますし、ぼくもそうでした。

ただ、その原因は困ったものでした。 webpackでhash化された名前の付いたpng fileが壊れていたからです。 とても悩みました。調べてもそんな事例、出てきません。 色々試行錯誤した挙句わかったのは、 css内とmanifest.webapp(manifest.json)内で 指定しているpngが同じだと、 webpackがpng fileをhash名化する時にバッティングするようで、 生成後のpng fileはviewerで見えないし、 fileコマンドで見ると「data」とか出るし。 cssでのpng fileを../../../../../../webpack/logo-jhipster.png といったようにしてmanifest.webpackと別物にすると、うまく行きました。 ./src/...以下でなくてもいいんですね!

Service Worker on JHipster

| Comments

JHipsterでService WorkerでWeb Pushを、 と思っていて、Angularだから、 前やったように app.modules.tsServiceWorkerModule.register('./service-worker.js',...);してから@angular/service-workerSwPushをinjectして、 って思ってたんですが、違うんですね。 JHipsterではworkboxでService Worker使うようになってるんですね予め。 確かにindex.htmlservice-worker.jsを有効にして挙動を見てると、 cachingは綺麗に行っている様子です。 えーでも調べてみると、workboxってWeb Pushはないじゃーないですかー。

特に私の知っている範囲ではworkboxはプッシュ通知のロジックを作ってくれないので、そこは自分で書いてやる必要があります。 ServiceWorkerを簡単に書けるworkbox-swの使い方

しかも、自分のcodeとmergeする、 即ちworkboxPlugin(...)swSrc:を追加すると、 generateSWが使えず自分で書かないといけないの!? ですか? いやー、jhipsterで生成されるservice-worker.jsとか見ると、 色んなfilesにhash値?が付与されているから、 これを自分で作るというのはあり得ないでしょー。 どーしたらいーのー?!

と、途方に暮れました。

workboxを捨ててAngularのSwPushにする? いやー、でもjhipsterでのbuildではng実行されないので、 いくら.angular-cli.json"serviceWorker": trueと書いても効かないんですよー。 Angularのservice workerは生成されないわけですね。

jhipsterで生成されたbuild/www/service-worker.jsを見ていると、 上部のコメントに、

1
2
3
4
 * The rest of the code is auto-generated. Please don't update this file
 * directly; instead, make changes to your Workbox build configuration
 * and re-run your build process.
 * See https://goo.gl/2aRDsh

とあります。言われるままにそのURLを見てみると、 importScriptsというoptionがあることがわかりました。 これを指定するとどうなるのかなー、試してみます。 指定する場所は、webpackでworkboxのservice worker生成をやっているので、 webpack/webpack.prod.jsになります。 それの最下段、new WorkboxPlugin.GenerateSW({...})の中に importScripts: ['push-notifications.js']と書いてみると、 自動生成されたbuild/www/service-worker.js中に、 importScripts("push-notifications.js",...)と出ます。 なるほど。 ではこのpush-notifications.jsというのを作ってwww root folderに置き、 そこにpush notificationのlogicを書いておけばいいんじゃーん。 でも、基本的には全てのfile名はhash化?されてしまいます。 どうやって名前をhash化されないようにするの?! favicon.ico等の例から手探りで探し当てました。 webpack/webpack.common.jsnew CopyWebpackPlugin([..])に 書いておけばいーんですね。なるほど。

結局要するにworkboxのimportScriptsを使うということで、具体的には、

  1. src/main/webapp/push-notification.jsにWeb Push通知時の処理addEventListener('push', function(event) {...});と通知をclickした時の処理addEventListener('notificationclick', function(event) {...});を書いておく
  2. webpack/webpack.prod.jsnew WorkboxPlugin.GenerateSW({...})importScripts: ['push-notifications.js']を書く
  3. webpack/webpack.common.jsnew CopyWebpackPlugin([..]){ from: './src/main/webapp/push-notifications.js', to: 'push-notifications.js' },を書く

で目出度くJHipster application上でのWeb Push通知が出来ました。

尚、通知の許可を求める処理やendpoint、auth、publicKeyを求める処理では、 AngularのSwPushを使うことが出来ます。 JavaScriptでゴリゴリ書かなくてもthis.swPush.requestSubscription()だけで済むのでラクです。

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を色々変えて試してみることで、 大分わかってきました。