Cordova + vue.js + AWS SDKでS3バケットの内容を一覧してみた

まえがき

mediba メディアシステム開発部 ポイント開発Gの佐藤禎章と申します。スマートフォン向けのネイティブアプリを構築/運用しています。

今回のエントリでは Apache Cordova の上で Vue.js を利用して Amazon AWS の機能を呼び出してみます。

利用するプロダクトの紹介

Apache Cordova

http://cordova.apache.org/

もともと Adobe のプロダクトで、 PhoneGap と言われていました。 Apache に寄贈されて名前が変わりました。

アプリの構築を HTML + CSS + JavaScript で実現してしまおう、というぶっ飛んだ発想ですが、[アプリの中にブラウザを含めてしまい、その中で HTML / JavaScript / CSS を動かす]という手法で実現しています。なので、ブラウザで出来ることは大概、出来ます。

また、マルチプラットフォーム対応も特徴で、 iOS / Android / WindowsPhone / Firefox OS / Blackberry などなど、様々なプラットフォーム向けにネイティブアプリとしてビルドできます。

ネイティブアプリ特有の機能(カメラやGPSなど)については、プラットフォームごとにプラグインという形で実装することで、その差異を吸収しています。

ガチガチのゲームであったりスマートフォンの細かい機能を操作したりするような用途には向きませんが、カバーできる範囲は想像以上に広いです。

Vue.js

http://jp.vuejs.org/

JavaScript の MVVM フレームワークです。つい先日、 1.0 がリリースされました。

MVVM とは Model - View - ViewModel という考え方で、 Model はデータそのもの、 View は見た目の定義。 ViewModel のところが、 Model と View の双方を結びつけて管理してくれる、みたいな仕組みです。

Model ←→ ViewModel ←→ View

View の側で状態が変わってもちゃんと Model に反映してくれるし、 Model の側で状態が変わっても View の側にちゃんと反映してくれるので、動きを見てて面白いです。

今回、見た目の定義と非同期でのデータ更新に利用します。

AWS SDK

https://aws.amazon.com/jp/sdk-for-browser/

みんな大好き Amazon AWS のサービスをプログラム言語から弄るためのキットです。

今回は AWS SDK for JavaScript を使用し、 Cognito と S3 を利用します。

本来ブラウザから動かすものですが、 Cordova でもちゃんと動くのかどうか。

準備

npm が実行できる環境を用意して下さい。

Mac であれば brew install node で npm の環境もまとめて導入できます。

Cordovaのインストール

下記のようにして、 npm から cordova をインストールして下さい。

$ npm install -g cordova

これで cordova コマンドツールが利用可能になります。

このコマンドツールを利用して、アプリケーションの骨格作成からビルドまで行います。

アプリのスケルトン作成

まず、ベースを作ります。パッケージ名などもこの時に指定します。

$ cordova create cordovatest jp.mediba.ysato.cordovatest CordovaTest

cordova create [ディレクトリ名] [パッケージ名] [アプリケーション名] という指定の仕方です。

指定したディレクトリの下に、こんな感じでファイルが生成されます。

image

続いて、プラットフォームを追加します。

ここで追加したプラットフォーム分、アプリケーションがビルドされることになります。

$ cordova platform add ios
$ cordova platform add android

追加できるプラットフォームについては、下記を参照下さい。 Firefox OS とか Blackberry とかも対象に出来ます。

https://cordova.apache.org/docs/en/4.0.0/guide/support/index.html

アプリを実行

実行する時は、コマンドラインから下記のようにします。

$ cordova run

cordova run だけで引数を指定しないと、有効になっている全てのプラットフォームにて動かそうとしてくれます。先の例であれば、 Android と iOS ですね。

$ cordova run android 

などと指定すれば、そのプラットフォームの分だけ、ビルド/実行してくれます。

cordova create しただけの状態でターゲットを iOS として run すると、こんなサンプルが動きます。

ようこそ、 Cordova の世界へ。

image

JS外部ライブラリをCordovaで使う

では早速、アプリを作ってみましょう。

但し AWS 関連についてはちょっと置いておいて、ここでは[誕生日を指定したら、今日から30日後までのバイオリズムをグラフで出力する]というアプリを作ってみます。

グラフ出力には ccchart を使用します。データをJSONで羅列するだけで美麗なグラフを描いてくれる、便利なライブラリです。

コード

<script src="./js/vue.min.js"></script><ul id="biorythm"><li>誕生日: <input type="date"></li>
  <li>身体: {{physical}}</li>
  <li>感情: {{sensitive}}</li>
  <li>知性: {{intel}}</li>
</ul><canvas id="biograph"></canvas><script src="./js/biorythm.js"></script>
// 誕生日設定フォーム部分と更新処理
  var biorythm = new Vue({
    el: '#biorythm',
    lazy: true,
    data: {
      birthday: '1976-03-30',
      physical: 0,
      sensitive: 0,
      intel: 0,
    },
    methods: {
      // 今日のバイオリズムデータで更新
      // 引数 : 誕生日
      update: function(){
        birthday = biorythm.birthday;
        var bio_today = new Bio(birthday,new Date());
        biorythm.physical = bio_today.physical;
        biorythm.sensitive = bio_today.sensitive;
        biorythm.intel = bio_today.intel;
        update_chart(birthday);
      }
    }
  })

// DOM変更を検知
  biorythm.$watch('birthday', function(value, mutation){
    biorythm.update();
  });


// ccchart のグラフ定義
// data: に描画する要素を配列で指定する
// ccchart.init(キャンバスID, 定義データ) で実際にグラフを描画する
var chartdata6 = {
  "config": {
    "width": 320,
    "height": 240,
    "xScaleFont": "100 6px 'Arial'",
    "yScaleFont": "100 6px 'Arial'",
    "hanreiFont": "100 6px 'Arial'",
    "colorSet":
          ["#F00","#0F0","#00F"],
  },
  "data": [[],[],[],[]]
};

// ccchart グラフデータ(今日から30日後まで)の更新
// 引数 : 誕生日
  function update_chart(birthday){
    chartdata6.data[0].length=0;
    chartdata6.data[0].push("日");
    chartdata6.data[1].length=0;
    chartdata6.data[1].push("身体");
    chartdata6.data[2].length=0;
    chartdata6.data[2].push("感情");
    chartdata6.data[3].length=0;
    chartdata6.data[3].push("知性");

    for(var i=0;i<30;i++){
      var today = new Date();
      var dateChart = new Date(today.getTime() + i * 1000*60*60*24) ;
      var dateChartBio = new Bio(birthday,dateChart.getTime());
      chartdata6.data[0].push(dateChart.getDate());
      chartdata6.data[1].push(dateChartBio.physical);
      chartdata6.data[2].push(dateChartBio.sensitive);
      chartdata6.data[3].push(dateChartBio.intel);
    }
    ccchart.init('biograph', chartdata6);
  }

// バイオリズム計算
// 引数 : 誕生日 , 基準日
  function Bio(birthday,basedate){
    dateDiff = (new Date(basedate) - new Date(birthday)) / 1000/60/60/24;
    dateDiff = Math.floor(dateDiff);

    var bioNumer = 2 * Math.PI * dateDiff;
    this.physical = Math.round( Math.sin( bioNumer / 23 ) * 10000) / 10000;
    this.sensitive = Math.round( Math.sin( bioNumer / 28 ) * 10000) / 10000;
    this.intel = Math.round( Math.sin( bioNumer / 33 ) * 10000) / 10000;
  }


// 更新処理を一回走らせてグラフを書かせる
  biorythm.update();

結果

image

DOM 操作やイベントハンドリングまでやる Vue.js や、 Canvas 描画ゴリゴリやる ccchart も、 Cordova の中で問題なく動きました。本当に何の問題も起きなかったので、逆に驚きました。 全く同じファイルを Google Chrome などで表示しても、やっぱりちゃんと実行できます。凄いものですねえ….

Vue.js について

今回 Vue.js には、下記の役割を担ってもらいました。

誕生日を指定するフォームの値を監視し、変更されるたびにイベントを発行する

第一引数に監視する要素、第二引数がコールバックになるので無名関数で処理を書く、みたいな使いかたになります。

vm.$watch('datakey', function(value, mutation){
  // codes
});

vm という Vue オブジェクトにおいて vm.$watch で datakey を監視させます。 変更が発生すると、 value に値が入り、 mutation には変更に関する情報(method, args, result, inserted, removed)が入ります。

今回は Vue.js の中に書いたメソッドを呼び、更新処理を走らせています。

誕生日の変更によって再計算される[身体][精神][知性]のバイオリズム値を画面に反映する

<div id="vm">{{parts}}
<script type="text/javascript">
  var vm = new Vue({
    data: {
      parts: 0,
    }
  }
</script>

こんな風に定義してあげると、Vue.js が HTML 側の {{parts}} と JavaScript 側の vm.parts を結びつけてくれます。 こうすると、 JavaScript側で vm.parts = 1 などとして値を変更した時に、 HTML 側を自動で書き換えてくれるわけです。

フォームに関してはちょっと特殊で、 <input> などと v-model というディレクティブを使用する必要があります。

ccchart について

解説するような所はあんまりないのですが、 ccchart でグラフを書くためにすることは、ざっくりと下記になります。簡単。

HTML 内にグラフを描画する canvas を用意する グラフの内容とデータを定義した JSON を用意する 描画処理を呼び出す http://ccchart.com/ にはグラフの種類や実際の書き方などについて、サンプルコードと実行例が豊富に並んでいます。

株価情報みたいなチャートを出したりリアルタイムに情報ソースをポーリングしたりと様々なことが出来ますので、まずはサイトの方を覗いてみてください。

AWS SDK for JavaScriptを使う

事前の注意

iOSに限った話なのですが、 ATS(Application Transport Security) という機能がiOS に加わり、その副作用として

http アクセスが全部 https に差し替えられるため、 AWS の API アクセスも全て https に差し替わる *.amazonaws.com の証明書がSHA-1なので、セキュリティレベル低いとアクセスをブロックされる という事象が発生します。結果、 iOS 上の Cordova から AWS SDK for JavaScript を使用出来ない状態になります。
https://mobile.awsblog.com/post/Tx2QM69ZE6BGTYX/Preparing-Your-Apps-for-iOS-9http://dev.classmethod.jp/smartphone/iphone/ios-9-intro-ats/

回避方法は、 Cordova が生成した iOS 向けビルド用のソース一式の中にある、 info.plist を編集し、当該ドメインのATSを無効にすること。

但し、 Apple が用意したセキュリティの仕組みを迂回する、セキュリティレベルを下げることになるため、よく理解した上で使用して下さい。

NSAppTransportSecurity
NSExceptionDomains
  amazonaws.com

      NSThirdPartyExceptionMinimumTLSVersion
      TLSv1.0
      NSThirdPartyExceptionRequiresForwardSecrecy

      NSIncludesSubdomains

前準備

Cordova から外部のリソースにアクセスする際は、 Content-Security-Policy にてアクセス先の指定を行う必要があります。ホワイトリスト形式ですね。

Cordova が生成してくれた HTML には基本的なポリシが既に記載されていますが、外部サーバのリソースなどを利用したい場合は、随時許可を書いてあげる必要があります。

Amazon AWS へのアクセスを許可する場合は、下記のようになります。

<meta content="connect-src https://*.amazonaws.com/">

JQuery などをCDNから読み込むようにする、外部のデータをリソースとして使用する、等の場合にも、 Content-Security-Policy は通るようにしておいてあげないといけません。

Content-Security-Policy の詳細については、下記サイトを参照下さい。

http://content-security-policy.com/

コード

<div id="s3list_vue">
  <ul><li>
  </ul></div>

<script src="./js/awssample.js"></script>
var loginCognito = function(){
  AWS.config.region = 'us-east-1';
  AWS.config.credentials = new AWS.CognitoIdentityCredentials({
      IdentityPoolId: 'us-east-1:xxxx'
  });
}

var getS3Buckets = function(){
  var filelist = [];
  var s3bucketname = 'BUCKETNAME';
  var s3 = new AWS.S3({region: 'ap-northeast-1', maxRetries: 15});
  s3.listObjects({Bucket: s3bucketname}, function(error, data) {
    if (error) {
      console.log("S3 Error : " + error);
    } else {
      var targetNode = document.getElementById('s3list');
      for(var i = 0 ; i < data.Contents.length ; i++){
        var params = {Bucket: s3bucketname, Key: data.Contents[i].Key};

        s3list_vue.s3list_vue_parts.push({
          url: s3.getSignedUrl('getObject', params) ,
          filename: data.Contents[i].Key ,
        });
      }

    }
  });
}

var s3list_vue = new Vue({
  el: '#s3list_vue',
  template: '{{filename}}',
  replace: true,
  data: {
    s3list_vue_parts: [],
  },
  methods: {
    onClick: function (getter) {
      var ref = cordova.InAppBrowser.open(getter, '_blank', 'location=yes');
    }
  }
})

loginCognito();
getS3Buckets();

結果

S3の方に、こんなふうにファイルを設置しておきます。

image

S3 バケットリスト

image

画像を InAppBrowser で表示

image

解説

AWS周り

見ての通りですが、 loginCognito() で AWS の Cognito にログインしています。何のためかというと、S3へアクセスするための Credential を取得するためです。 Cognito 自体の解説については本家の https://aws.amazon.com/jp/cognito/ などから手繰れますが、 Cognito 認証の結果に応じて様々なロールを割り付けられるため、 AWS の各種コンポーネントへのアクセス許可を出すときに便利です。

コレが無かった頃は AccessToken / SecretToken の対で制御してましたね。

で、取得した Credential を使用して、 getS3Buckets() で S3 バケットの中身を取得、表示しています。

listObjects() のコールバックで結果を取得して、 Vue で定義しているモデルに一つ一つ push するだけ。

表示の更新は Vue に任せています。双方向バインディングばんざい。

InAppBrowser

また、ファイル名をタップした時に開いているのは、 InAppBrowser という、 Cordova が用意したアプリ内ブラウザです。 Cordova の中でも開けるのですが、先に説明した Content-Security-Policy の制限を受けますので、そういう制限を無くしたい場合は InAppBrowser を使うことが多いようです。

Vue の v-on ディレクティブでタップ時に onClick() メソッドを呼ぶよう定義し、その中で cordova.InAppBrowser.open() にて InAppBrowser を呼び出しています。 open() の第二引数によって、下記のように挙動が変わります。

_self - URL がホワイトリストにある場合は Cordova の WebView で、そうでなければ InAppBrowser で開く (デフォルト) _blank - 常に InAppBrowser で開く _system - 常にシステムの Web ブラウザで開く

まとめ

スマートフォンでのクロスプラットフォーム開発を検討する際に必ず目にする Cordova ですが、「えー、 JavaScript で制御するとか凄い面倒そう、 JQuery とか DOM とか分かってないとダメなんでしょー」と、食わず嫌いをしておりました。

でも実際にやってみると、 Vue.js というフレームワークの強力さも相まって、すごく簡単にやりたい事を書けるようになってきていることを知りました。食わず嫌いよくない。

もちろんネイティブに比べたら出来ることは限られますが、それでも[一回書いたらどっちでも動く]事を考えると、案件次第では採用するメリットは相当ありそうだぞ、と思っています。

みなさんも、選定の選択肢に1つ、 Cordova 、如何でしょう?