「JavaScript」 カテゴリーの記事です。

JavaScriptでiOSアプリ、Androidアプリを作る環境は今はだいぶ整っている


しばらくJavaScriptでの仕事が続いていたので、じゃあ鉄は熱いうちに打つかと、作りたかったスマホゲームアプリをプライベートで、しかもJavaScript開発で一気呵成に作ってしまおうと思い立ちました。

そのゲームは以下に。もしお気に召していただけたのなら、お布施代わりに課金していただけるとうれしいです(フルバージョンになるだけの一回課金です)。

tamasabo

過去にも何度かJavaScriptでアプリ開発をやろうとして失敗したんですよね。

JavaScript開発でネイティブアプリにするフレームワークは、PhoneGapを初めとして、当時いくつか選択肢はありましたが、そのときは、どれもイマイチでした(できることが少なく、難度が高いという意味で)。

しかし、今回もCordva(PhoneGap)を使いましたが、当時とは状況がガラリと大きく変わっていたようです。

JavaScriptだけですべてを表現できるわけではない

「JavaScriptさえ書ければ、何でも表現できる!」と鼻息荒く始めるのですが、実際ハードウェアや、ネイティブ部分の壁にぶち当たると、もはやほとんど無力です。

今回でいえば、

  • アプリ内課金
  • アプリ内広告
  • ソーシャルシェア

です。中でもアプリ内課金では、iOS内のStoreKit(ネイティブ)を操作してiTunesサーバまで問い合わせるなんて、想像しただけでも、JavaScriptだけでは100%不可能。

「ああ、、、やっぱりムリだよなあ」と思って調べてたら、現在ではCordovaのプラグインが山ほどあって、アプリ内課金だけでも二つ三つ見つかる状況です。以下、今回のゲームアプリで使ったプラグインです。

アプリ課金
https://github.com/AlexDisler/cordova-plugin-inapppurchase

アプリ内広告
https://apps.admob.com
プラグイン
https://github.com/appfeel/admob-google-cordova

ソーシャルシェア
https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin

いずれもMITライセンスという素晴らしさ(たしかにGPLだとApple審査で弾かれますもんね。当たり前と言えば当たり前なのですが)。

PhpStormの万能感

phpstorm

まさか、JavaScriptによるスマホアプリ開発で、PhpStormが使えるとは思ってもみませんでした。

「PHPですよね?・・・」と知人から指摘を受けたとき、
「な、何を言ってるのか、わからねーと思うが・・・」

と、脳内ポルナレフ状態でした。たしかにPHP含めたサーバサイド開発環境じゃないのかよ、と思われますが、JavaScriptも扱うので、つまりは、iOS, Androidの統合開発環境としても使えます。

設定はとっても簡単。Cordovaのプロジェクトを作ったら、上部メニューにある「Select Run/Debug Configuration」を開きます。

select-run-and-debug

あとは、左ペインにある「PhoneGap/Cordova」を選択するだけです。

select-cordova

iOSの場合

ios-emulate

PhpStorm上で「実行」をすると、iOSの場合は、エミュレータが起動します。

別のプラグインを使えば、実機転送まで行けるそうですが、これだけでも充分すぎるくらいのデバッグ環境です。

Androidの場合

android-run

Androidの場合は、adbサーバが起動した状態で、Android端末がUSB接続されていれば、自動的に表示され、選択状態となります。

これでPhpStorm上の「実行」で、アプリはAndroid端末に転送され、デバッグできる状態になります。

一人でやっている人はすごい

以前はチームとして開発していたことがあるのですが、こうしてすべての工程を一人でこなすことになるとは思いも寄りませんでした。

今回は音関係以外、グラフィックやプログラミングは一人でやりましたが、なんと言っても大変だったのは、課金部分のデバッグでしょうか。一回だけでも課金が成功してしまえば、そのAppleアカウントはもうテストで使えなくなるからです。果たしていくつのAppleIDを作ったのやら・・・

あとは、それだけじゃないんですね。

リリースまで行くには、いろいろやるべきことがある。

プライバシーポリシーの作成(英語版も出す場合は、英語版も!)と、それを外部に置くための場所、ランディングページ(https://tamasabo.jp/)の作成をしなくてはならない。

App Storeに置くスクリーンショット画像の作成。解像度がiPhone、iPad含めて大量にあるので、地味に用意するのがたいへんだったり。

Appleの審査でのやり取りは依然として英語ですし、時差の関係か夜中の1時頃に、質問が飛んできたりします。

たいしたアプリではないので、さほど多くもないでしょうが、今後は開発者アカウントが存続するかぎり、サポートも続けていかないと、いけないのだろうなあ、と思っているところです。

JavaScriptでCryptoJSを使って、AESの暗号化と復号を行う


ウェブサイトを見回してみても、正しく実装されていなかったり、良いサンプルが無かったので、記事にしてみました。

→ DEMO&ソースコード:https://jsfiddle.net/hibara/qzono8jb/

CryptJSについては、以下にあります。
https://code.google.com/archive/p/crypto-js/

暗号化するにあたって

CryptJSの本題に入る前に、少し暗号化についてのお作法を知っておく必要があります。詳細は僕が書いた、別記事の「Visual Studio C#でファイルを暗号化してみる」を参照していただきたいですが、一応ざっとおさらい。

暗号化モードではCBCモードを使うのがベター

ブロック暗号方式と呼ばれるものは、その名のとおり、何バイトかずつブロック単位で暗号化していきますが、ここでやりがちなのがECBモードでしょうか。

ECBモード

これの何が問題かといえば、毎回同じデータ、同じパスワードだと、毎回同じ内容の暗号化データがでてきしまうという点です。

あるいは、各ブロックが小さくなるので、暗号化データへの総当たり攻撃(ブルート・フォースアタック)がしやすくなります。

基本的には、CBCモードを使いましょう。

CBCモード/暗号化

冒頭に、乱数による初期化ベクトル(Initialization Vector)を与えて、各暗号化ブロックに捻り合わせて行くイメージでしょうか。

CBCモード/復号する

ちなみに、暗号の大家であるブルース・シュナイアー氏がその著書『暗号技術大全』(日本語訳版は絶版・・・)の中でも、

ファイルを暗号化するのであれば、CBCモードがベストだろう。このモードを使えば、セキュリティは大きく向上するし、保存したデータに多少エラーが発生しても、同期エラーが発生することはまずない。アプリケーションが(ハードウェアではなく)ソフトウェアベースであれば、CBCがほぼ確実にいちばんいい。

と書いています。

パディングモード

ブロック暗号方式では、何バイトかのブロック単位で暗号化することにより、場合によっては、「端数」が出てしまいます。

これは暗号化されると、どこまでが暗号化データだったのか、復号時に正しく判別ができなくなくなるということです。

パディングモードでよく使われるのは、PKCS7のパディングモードでしょうか。

たとえば、以下の例ですと、データ長が8バイトで、実際のデータ列が9バイトあれば、残りの7バイトは、以下のように埋められます。

PKCS7パディングモード

つまり「余り」に埋められた合計サイズが、数値として埋められるというわけです。
これにより、復号時に、データ境界線をプログラムで判別できるようになります。

CryptoJSにはすべて揃っている

ところが、いざ、CBCモードで、PKCS7のパディングモードで暗号化したいと、該当のソースファイルを当たったら、どこにも見当たらない。「おかしい」と思って、本家のページを当たってみたところ、いずれも「Default」であるということが判明。

ユーザーが万一、なにも設定せずに使ってしまっても、黙ってCBCモードでPKCS7パディングモードで暗号化されるという親切設計でした(笑)。

CryptoJS supports the following modes:

CBC (the default)
CFB
CTR
OFB
ECB
And CryptoJS supports the following padding schemes:

Pkcs7 (the default)
Iso97971
AnsiX923
Iso10126
ZeroPadding
NoPadding

暗号化キーは鍵空間を広く使う

これは暗号化ユーティリティを使うすべてのユーザーにも言えることですが、パスワードはなるべく長い文字列で使ってほしいところです。当然、総当たり攻撃がしやすいという問題があるからです。

とはいえ、ユーザビリティを強制するのも、ツールの自由度を下げます。ただ、開発者側もそういった問題に少なからずフォローすることはできます。

たとえば、一文字のみパスワードを入れられても、鍵空間を目一杯使って、毎回異なるパスワードキーを生成してあげれば、多少この問題を和らげることができます(ただし、ブルートフォースアタックのような攻撃には何の訳にも立ちません)。

鍵空間

本題の「CryptoJS」

さて、本題の「CryptoJS」ですが、Google Code Archiveに上がっている、ライブラリです。そのサイトには、こう書かれています。

CryptoJS is a growing collection of standard and secure cryptographic algorithms implemented in JavaScript using best practices and patterns. They are fast, and they have a consistent and simple interface.

CryptoJSは、ベストプラクティス、ベストな形で、JavaScriptにおいての安全な暗号アルゴリズム標準となるべく開発しているものの一つです。これらは高速であり、一貫性とシンプルなインターフェイスを提供しています。

実際、オープンソースで、CDNやGitHubにも上がっています。

使い方は、たしかに簡単で、実際に使いたいjsファイルをhtmlヘッダ内で定義するだけです。

CryptoJSのAESを使ってみる

<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js"></script>
  <script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/pbkdf2.js"></script>

通常なら、「aes.js」だけで行けますが、今回は鍵空間を広げるためのライブラリを使うので、「pbkdf2.js」を含めました。

CryptoJS を使う上での注意点は、やはり「バイナリ」の扱いでしょうか。

おそらくウェブ上(サーバ間)でのデータのやりとりも考慮されているのか、データをバイナリデータ(Hex = 16進文字列)や、Base64エンコーディングして渡す場面が何度かあります。

ただ、CryptoJS では、それらを適宜、必要なデータへコンバートするためのメソッドも用意されています。

CryptoJS.enc.Hex.parse()
CryptoJS.enc.Base64.parse()
CryptoJS.enc.Utf8.parse()

などを駆使して暗号化します。

まずは、暗号化から。この程度のソースコードにjQueryを使っているのはご容赦ください。

  $('#encrypt').on('click', function () {
    //パスワードはUTF-8エンコーディング
    var secret_passphrase = CryptoJS.enc.Utf8.parse($('#encrypt-password').val());
    //alert(secret_passphrase.toString(CryptoJS.enc.Utf8));
    var salt = CryptoJS.lib.WordArray.random(128 / 8);
    var key128Bits500Iterations =
      CryptoJS.PBKDF2(secret_passphrase, salt, {keySize: 128 / 8, iterations: 500 });
    //初期化ベクトル(ブロック長と同じ)
    var iv = CryptoJS.lib.WordArray.random(128 / 8);
    //暗号化オプション(IV:初期化ベクトル, CBCモード, パディングモード:PKCS7
    var options = {iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7};
    //暗号化内容のエンコーディングは「UTF-8」
    var message_text = CryptoJS.enc.Utf8.parse($('#encypt-text').val());</p>

    //----------------------------------------------------------------------
    //暗号化
    var encrypted = CryptoJS.AES.encrypt(message_text, key128Bits500Iterations, options);
    //----------------------------------------------------------------------

    //暗号結果データをカンマ(&quot;,&quot;)で結合してまとめる(復号時にわかるように)
    //(salt + iv + ciphertext)
    var binary_data = CryptoJS.enc.Hex.stringify(salt);
    binary_data += (',' + CryptoJS.enc.Hex.stringify(iv));
    binary_data += (',' + encrypted);
    $('#encypted-data').text(binary_data);
});

暗号化の際の注意点としては、暗号に必要なsaltやIVなどを、暗号化データに含めないといけない点です。

ここでは単に、カンマ区切りとしていますが、他に方法があるのなら、どのような手段でも良いでしょう。

そして、復号はこちら。

  $('#decrypt').on('click', function () {
    // あからじめ仕込んでおいた暗号化データのカンマ","を使って文字列をそれぞれに分割
    var array_rawData = $('#encypted-data').text().split(',');</p>

    var salt = CryptoJS.enc.Hex.parse(array_rawData[0]);  // パスワードSalt
    var iv = CryptoJS.enc.Hex.parse(array_rawData[1]);    // 初期化ベクトル(IV)
    var encrypted_data = CryptoJS.enc.Base64.parse(array_rawData[2]); //暗号化データ本体

    //パスワード(鍵空間の定義)
    var secret_passphrase = CryptoJS.enc.Utf8.parse($('#decrypt-password').val());
    var key128Bits500Iterations =
      CryptoJS.PBKDF2(secret_passphrase, salt, {keySize: 128 / 8, iterations: 500 });

    //復号オプション(暗号化と同様)
    var options = {iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7};

    //復号
    var decrypted = CryptoJS.AES.decrypt({&quot;ciphertext&quot;:encrypted_data}, key128Bits500Iterations, options);
    // 文字コードをUTF-8にする
    $('#decrypt-text').val(decrypted.toString(CryptoJS.enc.Utf8));
});

以上です。

実際のデモは、先にも書きましたが、
https://jsfiddle.net/hibara/qzono8jb/

で、見られます。

QtでMacOSXアプリケーションをつくる


最近また、Qt を始めています。

実は、NokiaがQtを手放してから、もう終わったか・・・と、しばらく触っていなかったのですが、
しばらく見ないうちに、高機能で、マルチプラットフォーム化が進んでいたので、びっくりです。
商用ですが、Android、iOSにも対応しているようです。

そんなわけで、改めてなぜいま Qt なのかというと、Mac OSX アプリケーションが作りたくて。
心地よく日本語テキストを打ち込める、軽量な執筆エディターがほしくて探し回りましたが、
ぴったりのものが見つからないんですよね。

海外製のは、変換候補があさっての方へ飛んでいってしまったり、
段落下げしたら、以降、全部段落が下がるという、完全に海外文書仕様になっていて、
日本語的な一字下げのみということができなかったり、そもそも日本語対応は微妙なのが多い。

で、とりあえず僕はいま、Qtに、面倒なMacOSX側の処理をやってもらって、
あとは、エディタ部分も含め、WebView(Weblit+JavaScript)の方で
表示・編集できるように開発してこうと考えています。

いわゆる The QtWebKit Bridge(http://qt-project.org/doc/qt-5/qtwebkit-bridge.html)を使います。

Qt Creatorから、「新しいプロジェクト」を開くと、
「HTML5アプリケーション」という項目があるので、基本はそれで作ります。

ただ、これだと、シングルウィンドウのWebブラウザアプリと変わりがなくなってしまいます。
ファイルを保存したり、ファイルを開く、アプリ自体の設定なんかも各々のマシンに記憶しておきたい。

そうなると、MacOSXのネイティブなところをいじる必要があるわけです。
つまり、Qt(MacOSX)ネイティブ+JavaScriptのハイブリッドアプリケーションの構成で、というわけです。

ちなみに、エディタ部分は、CodeMirrorを使います。

これがなかなかの高機能で、ライセンスも、MITライセンス。
これをWebKitに組み込んで使っている海外製のエディタアプリケーションも多い。

このサンプルソースは、なかなか見当たらないのですが、以下の記事が参考になりました。

hybrid web+native: desktop codemirror
http://ariya.ofilabs.com/2011/09/hybrid-webnative-desktop-codemirror.html

「CodeMirrorを使う」という目的だけの、エディタの機能としては少なめのサンプルです。
とはいえ、限定されている分、ソースはシンプルで分かりやすいかもしれません。

ソースコードは、同ページにリンクが貼ってありますが、わかりにくいので、ここに再掲。
https://github.com/ariya/X2/tree/master/webkit/codemirror

ゆくゆくは、App Storeに出したいなあ、とは思っていますが、年間登録料をケチって野良配布するかもしれません。

とりあえず自分が使いたいツールを作るのが目標です。オープンソースも視野に入れています。
 
 

QtでMacOSXアプリケーションメニューに「環境設定」項目を追加、表示する


一昨日の土曜日、はじめてQt 勉強会 #11 @Tokyoにも参加。まとめは、こちらです。

めっちゃ濃いことやっている人たちばかりで腰引けました(笑)。

僕はせいぜいライブラリの寄せ集めで、さくっとアプリつくっちゃうという軽さが、とても恥ずかくなります(笑)。続々成果が発表されるなか、勉強会でやりたかったこと、をやる前に、ちがう問題が発生し、さらにそれもその日に解決できずに、宿題になってしまったという・・・

ただ、主催者の @task_jpさんからのアドバイスもあり、その後、それら宿題を解消できたのでここにシェアします。

メインメニュー「環境設定」というは、いわゆるコレです。

環境設定メニューの画像

Qtの公式ページを見ると、
http://qt-project.org/doc/qt-5/qmenubar.html#qmenubar-on-mac-os-x

If you want all windows in a Mac application to share one menu bar, you must create a menu bar that does not have a parent. Create a parent-less menu bar this way:

  
QMenuBar *menuBar = new QMenuBar(0);
  

親のないメニューバーから生成する、とある。
ただ、すでにそのようなメニューは作っていて、どこに「それら」を挿入するのかが分からない。

  
QMenu *fileMenu = menuBar()->addMenu("&File");
fileMenu->addAction(tr("&New Window"), this, SLOT(fileNew()), QKeySequence::New);
fileMenu->addAction(tr("&Open..."), this, SLOT(fileOpen()), QKeySequence::Open);
fileMenu->addAction(tr("&Save"), this, SLOT(fileSave()), QKeySequence::Save);
fileMenu->addAction(tr("Save &As..."), this, SLOT(fileSaveAs()));
  

ファイルメニュー

結論を言うと、なんか、実に微妙な実装なのですが、AddMenu() にある特定の文字列があれば、
QMenu のどこに存在してようと、良いみたい。

マニュアルにもそう書いてある。
http://qt-project.org/doc/qt-5/qmenubar.html#qmenubar-on-mac-os-x

String matches Placement Notes
about.* Application Menu | About <application name> The application name is fetched from the Info.plist file (see note below). If this entry is not found no About item will appear in the Application Menu.
config, options, setup, settings or preferences Application Menu | Preferences If this entry is not found the Settings item will be disabled
quit or exit Application Menu | Quit <application name> If this entry is not found a default Quit item will be created to call QApplication::quit()

なので、ソースコードには、こんなふうにしました。

   
QMenu *fileMenu = menuBar()->addMenu("&File");
fileMenu->addAction(tr("&New Window"), this, SLOT(fileNew()), QKeySequence::New);
fileMenu->addAction(tr("&Open..."), this, SLOT(fileOpen()), QKeySequence::Open);
fileMenu->addAction(tr("&Save"), this, SLOT(fileSave()), QKeySequence::Save);
fileMenu->addAction(tr("Save &As..."), this, SLOT(fileSaveAs()));

//ファイルメニューにぶら下がるようにコードを挿入
fileMenu->addAction(tr("about.*"), this, SLOT(aboutPaneView()));
fileMenu->addAction(tr("preferences"), this, SLOT(optionPanelView()));

こう書くと、「ファイル」メニューのプルダウン項目に表示されそうですが、そこには表示されず、
実際は、アプリケーションメニュー内に表示されます。

アプリケーションメニュー

「about.*」は、ママです。そのままアスタリスクを書きます。.plistにある「アプリケーション名」をQt が自動的に引っ張ってきて、「About AppName」という感じに表示してくれます。たぶん、アプリケーションが日本語表示対応なら、「AppNameについて」というメニューになるはずです。

「preferences」も同様に、日本語環境に対応させると「環境設定」と表示されるようです。

File メニューの中に AddMenu() したのに、実際はアプリケーションメニューに表示されるという、なんだか気持ち悪い仕様ですが、MacOSXがそうなのであって、他のOSでは、ちがう実装になるということに注意が必要でしょう。

一昨日のQt 勉強会 #11 @Tokyoでも、「気持ち悪い」「ホントに大丈夫なの?」との感想がありましたが、「マニュアルがそうなっているので、そうなんでしょう」っていう結論でした(笑)。
 
 

Qt上でJavaScriptの実行をデバッグする


Qtで、WebKit Bridge で作成していると、Qt側での処理は、
QtCreator上でデバッグ可能ですが、値がWebKitへ渡った後の、
JavaScript実行までは追えません。

ただ、一昨日のQt 勉強会 #11 @Tokyoにて、
そのことについて口にしたところ、主催者の @task_jpさんから、

QWebInspectorクラスを使うといいよ」とのアドバイスをいただきました。

こんな便利なものまで、クラスであるのか!と喜び勇んで実装してみました。

  
//QWebInspector instance.
QWebInspector *inspector = new QWebInspector;
inspector->setPage(this->page());
inspector->show();
  

this->page() は、WebKitの、QWebView::page() のポインタを指定します。

ところが、とても残念な表示に。。。

空のWebインスペクタ

ちゃんと、show() しているんですけどね、、、なぜ出ないのだぁ〜、と悩むこと小一時間、もうすでに消えてしまったWebページで、検索キャッシュの中に答えを見つけました(笑)。

  
//↓これを追加。
view->page()->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true);

QWebInspector *inspector = new QWebInspector;
inspector = new QWebInspector;
inspector->setPage(this->page());
inspector->show();
  

表示する側、Webページの方の、設定も変更しないと出ないということがわかりました。

検索しても、あまり事例が出ないところを見ると、QWebInspectorクラスがQtにあることを知らないか、
かつ、Web Bridgeとかする前に、HTML状態のJavaScriptをデバッグしてから載せろってことでしょうかね。。。

とりあえず、これも勉強会の宿題だったので、一つ解決っと。
 
 

s