アプリ更新を強要されるよりかはマシだな

     以前Google様からのお達しによりアプリのアップデートを強要されたことを書きましたが、今回は自分の都合で自作のアプリのアップデートをしようとした際にCordovaの仕様変更に対応する必要に迫られたので、誰かの役に立てばと思い記事を書きます。ハイブリッドアプリの脆弱性という意味では前回の件と似たようなものだと思うのですが、前回のように既にリリースされているアプリにまで対応を迫るということはなかったようです。CordovaにCSP(ContentSecurityPolicy)が導入されたのがいつかは知りませんが、とりあえず最近までこのアプリに関してはCSPを設定しないままでリリース出来ていました、ほとんどアクティブユーザーはいませんが:sweat_smile:

    外部サーバーからのデータ取得(JSONP)

     アプリの中でJSONPを使ってherokuで運用している外部サーバーからデータ(ユーザーのランキングデータ)を取得する処理があるのですが、適切にCSPを書かなければRefused to connect to 'http://XXX.herokuapp.com/XXX'というエラーでデータが取得出来なくなったようです。Whitelistプラグインを使ってconfig.xmlに記載するだけではダメみたいです、通常のHTTPリクエストよりAjaxリクエストはさらに規制を強くするということでしょう。

    <!-- config.xml -->
    <access origin="https://tictactoe-cf.herokuapp.com" />
    

     以下のようにindex.htmlにmetaタグを書くことでデータ取得に成功しました。

    <!-- index.html -->
    <meta http-equiv="Content-Security-Policy" content="default-src *; style-src 'self' 'unsafe-inline'; script-src 'self' http://localhost:3000 https://stictactoe-cf.herokuapp.com 'unsafe-inline' 'unsafe-eval'">
    
    

     script-srcの部分にテスト時のローカルのURLと本番で使う外部サーバーのURLをスペースで区切って記述します。default-srcに記述すれば代替出来ると書かれたサイトもありましたが、私が試した限りではscript-srcに書かないと動きませんでした。

    他にもあった開発時の制約(XMLHttpRequest)

     つい最近もう一つの別のアプリを弄っててわかったことですが、初期化データをローカルディスクから読み込む際に以下のような感じでXMLHttpRequestを使えていました。

    #CoffeeScriptファイル
            xhr = new XMLHttpRequest
            fileReader = new FileReader
            xhr.open 'GET', './XXXX.json', true
            xhr.responseType = 'json'
            xhr.addEventListener 'load', (=>
                if (xhr.status == 200)
                # if xhr.readyState == 4 and xhr.status == 0
                    buf = xhr.response
                    compressed = LZString.compressToUTF16(JSON.stringify(buf))
                    try
                        localStorage.setItem 'SerializedData', compressed
                    catch e
                        console.log 'Storage failed: ' + e
                return
            ), false
            xhr.send()
    

     今現在リリースしているアプリ(確かビルドに使用したCordovaのバージョンは6.5でした)はこの方法を使って動いているのですが、手元にある今使っているCordova(version7.0.1)では使えなくなっているようです。だから次にアプリをバージョンアップするときは他のやり方に変更する必要があります。
     なんか自分がアプリをリリースする度に次のリリース時には改変を強いられるということが続くので、「またこんなことする奴が出て来たよ〜」と言われている気がするんですが考えすぎでしょうか:sweat_smile:
     対策としてはデータを外部サーバーに置くかファイル操作を行うプラグインを使う必要が出て来そうですが、ファイル入出力操作はandroidアプリとiPhoneアプリの仕様の違い(ファイルの設置場所等)を考慮しないといけないので面倒です。となると外部サーバーに置くかということになりそうですが、もっと簡単な回避策があります。以下のように必要なデータをHTMLファイルに埋め込んで、それを読み込むようにすることで上のコードと同じことを実現出来ました1

    #CoffeeScriptファイル
            data = document.getElementById("initialdata");
            buf = JSON.parse(data.textContent);
            compressed = LZString.compressToUTF16(JSON.stringify(buf))
            try
                localStorage.setItem 'SerializedData', compressed
            catch e
                console.log 'Storage failed: ' + e
    
    <!-- htmlファイルの例 -->
        <script type="application/json" id="initialdata">
          {"value":0,"child":[],"score":[50.872293099057885, 63.13216317016328, 62.51541431117913, 60.18288478188489, 67.52947485847494, 57.466225441225525, 64.30766966366978, 48.17378088578095, 57.74998440774921]}
        </script>
    

     セキュリティの観点から言っても外部サーバーからデータを取得するより安全でしょう。
     ネイティブアプリと違ってWebViewというブラウザーコンポーネント上で動くハイブリッドアプリは、開発時にいろいろ制約が付き纏います。でもそれ以上にUI部分をHTMLとCSSで簡単に書けるというメリットは大きいと思います。


    1. 自分がテストで使っていた端末はAndroid4.4(Kitkat)で、XMLHttpRequest(ajax)で読み込めていたローカルファイルが開発環境を変えたことで読み込めなくなったのですが、古い端末だと搭載されているブラウザの制限で元々出来ないようで、やるべきじゃなかったようです。HTMLに埋め込むやり方なら古い端末でも動作しました。