u-ryo's blog

various information for coding...

Category: Angular

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()だけで済むのでラクです。

Downloading a File on Angular

| Comments

JHipsterのAngularでfileをdownloadするUIを作っていました。 AタグlinkからserverのAPI叩いて、 Content-Disposition: attachment;filename=... と返せば済むだろう、 と思ってたんですが、そういえば認証通さねばなりません。 となると一旦browserで全部受けてblobにしてから返さないとならなさそうです。 認証header自体は、JHipsterならHTTPのinterceptorがあって、 フツーにthis.http.get(...)とかすれば勝手に付けてくれます。 HTTP Headerの付け方は公式documentにある通りです。 Angular 2 download .CSV file click event with authenticationを参考に、 service化しました。 FileSaver.jsを使うと楽そうですけど、 そんなに互換性が重視されるわけではないことと大したcode量ではないことから、 自分で書きました。 最後、window.open(url);だとwindowが上がってきてしまうので、 AngularでCSVをAPIからDLするときに色々したお話にあるように、 裏で自分で<a href="...">作って自分で叩いて自分で消す、 というように書いたら、うまく行きました。

Push Notification on Angular5

| Comments

もう先月ですが、Web Push Notificationを勉強して自分で試すことで、 理解を深めました。作ってもみました

やりたいアプリはJhipsterAngular使ってるんですが、 ではそういえば、AngularではWeb Pushってどうやるんだろう?! ということからまた勉強が始まりました。 調べてみると、簡単に使えるようになっているんですね(Angular CLI 1.6以降は)。

多くの導きのおかげで、angular5にてどのようにservice workerを導入するか、 はわかってきました。

要するに、アプリをgenerateする時に、Angular CLI(1.6+、現時点では1.7.3)で、

1
ng new application-name --service-worker

とするか、既にgenerateしてしまったアプリに対しては、 (Angular公式) Service Workerを始めるにあるように、

  1. yarn add @angular/service-worker
  2. ng set apps.0.serviceWorker=true または .angular-cli.jsonを編集してappsの下レベルに"serviceWorker": trueを挿入
  3. src/app/app.module.tsにService Workerをimportして登録(但しその際、base hrefを/の他にしたいならServiceWorkerModule.register('./ngsw-worker.js', {enabled: environment.production})./ngsw-worker.jsを相対指定しないとダメ)
  4. src/ngsw-config.jsonを作成(雛形をコピペ)
  5. ng build --prodとしてbuild(ng serveではダメ)(base hrefを/の他にしたいなら--base-href /another/directory/が必要。←最後が/でないとダメ)

PWAとしては、あとmanifest.jsonが必要?(Progressive Web Apps using the Angular Service Worker, AngularアプリをPWAにする方法)

これで、当該projectをweb browserで表示させると、 service workerが読み込まれ、 offline cacheが効くようになる、筈... なのですが、どう試しても、 offline modeにしてからreloadすると、504 Gateway Timeout (from ServiceWorker)になってしまいます。 PWCatでは上手くcacheされていることから、 多分これはAngularのbugと思います。

なのでoffline cacheは今は諦めて、push notificationの方策を探りました。 AngularのService Workerの話は、公式も含めて、 cacheやupdateばかりでpush notificationについては触れられてないんですね。 そういう中、A new Angular Service Worker — creating automatic progressive web apps. Part 1: theory, A new Angular Service Worker — creating automatic progressive web apps. Part 2: practiceという記事があり、 PWAtterとそのserverがありましたので、 非常に参考にさせてもらえました。 というかほぼそのままコピペして使わせてもらってます。 PWAtterはtwitterを拾うので物凄い勢いでpush notificationが来て、 明示的に消さないと消えないので、かなりうざいのですが、 これでAngularでPush Notificationが実装できるようになりました。 非Angularで作った時は、notificationの形についてはあまり気にしてなくて、 clickTargetとかNotification object specを無視していましたが、 Angularではngsw-worker.jsNOTIFICATION_OPTION_NAMESがあって、 そこで有効なproperty namesが規定されており、 NOTIFICATION_OPTION_NAMES.filter(name => desc.hasOwnProperty(name))として filterしているので、変なproperty書いても効かないんですね。 ちゃんとNotification object specに則った形のJSONをsendするようにすると、 push notificationが表示されるようになりました。

あとは、Notificationをclickしたら消えたり指定のpageに飛ぶように、 と思ったんですが、どうやったらいいのか。 notification.actionとか色々指定してみたものの、一向に何も起きず。 何でかなーって思ったら、どうやらnot yet implementedなんですね。 現に、node_modules/@angular/service-worker/ngsw-worker.jsに、 addEventListener('push', (event)... 等はあっても、 addEventListener('notificationclick', (event)... は無いんですね。(- -; workaroundとしては、以前の知見を活かして、 似たようなことを書けばそれだけでpush notificationを実現できることを確認しました。 push notificationの動作確認済のアプリを作成しました。

既に公式Angularでもissuesに似たような話はありましたので、 comment付けておきました。

ホントはPull Request作りたかった、です。 上記前者の方は、どこでどうやって書き込んでいるのかわからず、codeに出来ませんでした。 後者は、angular/packages/service-worker/worker/src/driver.tsangular/packages/service-worker/worker/src/service-worker.d.tsだというのはわかって、 書いてみたんですが、 いや難しいですね。 まずeventってnotification以下のJSONを含んでいるわけですが、 同時にevent.notification.close()っていうmethodもあるわけで、 こういうのを厳密にobjectとして表現しなきゃいけないようになってるんですね。 凄いなぁ。そういうのが曖昧模糊渾然一体としているのがjavascriptなのに。 また、clients.openWindow(...)clientsもいきなり出て来るobjectで、 出自がよくわからないのですけど、そういう曖昧さを許さないように出来ています。 他を見てどうやらthis.scope.clientsとすればよさそうなのはわかったんですが、 それでも./build.shをかけると driver.ts(246,28): error TS2339: Property 'openWindow' does not exist on type 'Clients'.と言われて、お手上げでした。 notification.close()の方も、 error TS2339: Property 'notification' does not exist on type 'Object | Client'と言われて、 どうしていいかわからず。 生成されるべきcodeはわかってるのに、悔しい、です。 折角Code of ConductCONTRIBUTING.md読んでcoding ruleやcommit message formatを学んだりCLA(Contributor License Agreement)登録したりDEVELOPER.md読んでbuildやtestの仕方学んだりしたのにー。 でもまぁ確かに、飛び先のURLはどこに書くべきか、とか、 clickしたら飛ばないでただ閉じるように/閉じないようにしたい、とかするには、 eventListener('notificationclick',(event)=>{...});を 直接いじらないとならないし、 仕様を含めたもっと別の方策が必要だと思います本当は。

favicon等iconを作るのに、Real Favicon Generator、いいですね。

JHipster

| Comments

ここんとこPWA(Progressive Web Application)作ろうとしていて、 ずっとJEEかなぁって思ってたんですけどSpringの勢いが凄くて、 それと今ならSPAでぼくはAngular派なので、 それに今時認証は自分でやりたくないよねとOAuth2で、 連動させるの大変だなぁとかって思ってたら、 そういうの全部ひっくるめたJHipsterというのがあるんだ! というのが衝撃でした。 かなり前からあったんですね。

ただこれ難しいですね。 scaffoldingまでは簡単ですけど、 そこからどうしていいのか。 自動生成filesはなるべく弄りたくないですが、 そうもいかないですよねこれ。 samplesも、「そこから先」についてはなかなか。 JHipster Mini-Book 4.0も見てみましたものの、それをなぞって行けるわけでは無かったので。 scaffoldingと、JDLでentities作って、 jhipster spring-controller Foong g component Barとかのcommandで取っ掛かりを作って、 そこから作り込んでいく、感じでしょうか。

Angular CLIがうまく動かなかった等あったのでメモ。 jhipsterのversionが変われば、こんな知識も不要になると思われますが。

  • JDLでfield名はsnakeやhyphenationではなくcamelで。 DBだからhyphenかなと思ったんですが、 生成されたclassのfield名がcamelにならなかったので。
  • JDLでUserは特別なentity。 Manualに書いてありました。 scaffoldingの次は取り敢えず JDL Studioでentity作成、import、 になるのに、解説すっ飛ばしてJDL見たので、なんだろう?って思うんですよね。
  • jhipster 4.13.3で入るAngular CLI(./node_modules/@angular/cli/)のversionは 1.6.0(package.json)でbug持ち(@angular-devkit/coreが入らない)なので、 ng g ...が失敗します。package.json中の@angular/cliの version numberを最新のもの(本執筆時点では1.6.6)に書き換え、 yarn installするとうまく行きました (Angular CLIをglobalでinstallしてもlocalのversionのモノを使うのでダメ、 @angular-devkit/coreを個別にnpm install ...しても、 次から次へと足りないものが出て来て、 全部入れた挙句の果てにyarn startがコケるようになるのでダメ)。
  • ng g component Fooとしてそれを表示させるにはどうしたらいいんだろう? と悩みました。route作ってそこで表示させるべく、他のentityとか見よう見真似でfoo.route.ts作ってpath直打ちして試したものの、表示されず。errorも何も出ないので、何が悪いのか分かりません。多分登録が届いてないんだろうとは思ってたんですけど、具体的にどこをどう直せばいいのかわけわかめです。StackOverflowに回答があったので出来ました。結局、0.ng g component Fooでcomponentを作る 1.foo.route.tsを作る(その際Routesで配列にするのではなくRouteで単数に?) 2.index.tsを真似して作る(foo.moduleのimportは記載不要) 3.foo.module.tsを作る 4.app.module.tsに登録(その際Angular CLIが自動登録したcomponentは削除しないとtop pageが表示されず) →そもそもcomponentではなくmoduleをgenerateするべきだったのでしょうか。よくわかりませんが、scaffoldingの後のことはなかなか書いてないので、「こんなのわかんないよー」という感じです。

JHipsterは「Java/Angular版Rails」か! とも思ったんですが、 もう出てだいぶ経つみたいですし、 でもまだそこまで洗練されてはいないのかなと。 Angularの難しさ、面倒臭さ、 とnode modules周りの変化が激しいのもあるためか、 使いこなすのはなかなか大変だと思います。