u-ryo's blog

various information for coding...

Author: U-ryo

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/...以下でなくてもいいんですね!

Illegal Forward Reference

| Comments

Javaの珍しいcompile error messageに、 illegal forward referenceというのがあります。 これを題材に、後輩氏に課題を出しました。

最も単純な形にして挙例すると、

1
2
3
4
5
6
7
8
9
10
import java.util.function.Function;
class Test {
    Function f = new Function() {
            @Override
            public Object apply(Object t) {
                return o;
            }
        };
    Object o;
}

↑これをlambda式に直しなさい、といったようなものです。

実はこれ、単純にlambda式にすると、compile errorになります。

1
2
3
4
5
6
7
8
9
class Test {
    java.util.function.Function f = t -> o;
    Object o;
}
$ javac /tmp/Test.java
/tmp/Test.java:2: error: illegal forward reference
    java.util.function.Function f = t -> o;
                                         ^
1 error

error messageからしてどう直せばいいかは自明かと思ったんですが、 英語が不自由な後輩氏の1日弱かけた答えはこうでした。

1
2
3
4
class Test {
    java.util.function.Function f = t -> o;
    static Object o;
}

流石に「何でもかんでもstaticにすればいいってもんじゃない」 ってことはわかってて、「これがFA」とまでは言ってませんでしたけど、 ここをみて、 「staticにすればいいんじゃね、って書いてある」と思ったんだそう。 そうは書いてないんだけどなー。

でも、こういう回答は想像してなくて、新鮮でした。

そもそもdeclarationが後ろに書いてあったこと自体がおかしい、とは思いますけどね。 そういう「色々おかしい」ソフトを直しています。

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

Service Worker in Private Browsing Mode

| Comments

Service WorkerでWeb Pushを色々試していて、 違うaccountでloginしてみようとprivate browsing mode用いると、 何か出来ないんですねこれ。 httpslocalhostじゃないとダメ、というのは知ってましたが。 気を付けましょう。 以上。

Firefox ではプライベートブラウジングモードでサービスワーカー API を利用することはできません。 サービスワーカーの概念と使い方

chromeはsecret mode(private window)でのService Workerも大丈夫、のようですか。

Spring Auth and JWT Behind the Reverse Proxy

| Comments

JHipsterのSpring Authなapplicationを httpsのreverse proxy(nginx)の後ろに置いて、 GoogleのOAuth2でJWTな認証をしようとしました。 当然、backend serverからはGoogle APIに自分のhost名でaccessするような URLを返してしまい、Google APIから戻ってきたところでJWT認証は弾かれます。 backend serverはfrontend serverの名前を知らないんですから、 そりゃあ当然です。 こういうreverse proxyの後ろにbackend server置いてOAuth2 + JWTなんて そもそもダメなの? 何とかならないの? と調べてみると、 Spring Boot and OAuth2: redirect url over reverse proxyに、 reverse proxy側でX-Forwarded-Portとかのproxy用HTTP Response Headerを設定し、 Spring application側でserver.use-forward-headers=trueにすればいいよ、 とあったので、 じゃぁnginxではどうやるのだろうと調べると、 Nginx のリバースプロキシ設定のメモにありました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server {
  listen 80;
  server_name hoge.com;

  location / {
    proxy_set_header X-Real-IP $remote_addr;
    index index.html index.htm;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://127.0.0.1:3000;
  }
}

この通りやってみると、 何故かGoogle APIにはhttp://proxy-server/...で渡っており、 じゃぁっていうんでproxy_set_header X-Forwarded-Proto https; とベタ書きしてみてもダメで、 うーんとか思っていると、 nginx でリバースプロキシするときの Tipsoffじゃなくてproxy_redirect http:// https://;という記述があったので、 試してみると、上手く行きました。 あーちなみに、proxy_set_header X-Forwarded-Proto https;も ベタ書きじゃないとダメでした。 結局うちの場合は、以下の通りになりました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
server {
        listen 80 default_server;
        listen [::]:80 default_server;

        # Everything is a 404
        location / {
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-Proto https;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header Host $http_host;
                proxy_redirect http:// https://;
                proxy_pass http://walt.mydns.bz:10022/;
        }

        # You may need this to prevent return 404 recursion.
        location = /404.html {
                internal;
        }
}

で、sudo /usr/sbin/nginx -s reloadです。

でも、これでGoogle APIから無事戻ってくるようにはなったものの、 その後「No-providerで登録」になってしまい、 まだ完成しません。 ただ、その問題は別のもののようで、一歩は進んだと思うので、記事にしました。

↑その「No-providerで登録」になってしまうのは、 backendで以下のようなerrorが出ていて。

1
2
3
4
5
6
7
8
9
10
11
javax.validation.ConstraintViolationException: Validation failed for classes [bz.mydns.walt.canmatch.domain.User] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
        ConstraintViolationImpl{interpolatedMessage='must match "^[_'.@A-Za-z0-9-]*$"', propertyPath=login, rootBeanClass=class bz.mydns.walt.canmatch.domain.User, messageTemplate='{javax.validation.constraints.Pattern.message}'}
]
        at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.validate(BeanValidationEventListener.java:140)
        at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.onPreInsert(BeanValidationEventListener.java:80)
        at org.hibernate.action.internal.EntityIdentityInsertAction.preInsert(EntityIdentityInsertAction.java:197)
        at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:75)
        at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:626)
   :
   :

何なんでしょうね。 これは、account mail addressが「w.disney@somecompany.co.jp」みたいな 「.」が入るものなんですが、それがいけないとかなのでしょうか。 というのも、フツーの「gepetto@gmail.com」みたいなmail accountなら 全く同じcodeで何の問題もなく入れるのです。 「must match」の対象が何なのか、よく分かりません。