アシアルブログ

アシアルの中の人が技術と想いのたけをつづるブログです

MonacaにChrome Appsビルド機能が追加されました

こんにちは。生形です。

先日、Monacaがリニューアルしました。
ダッシュボードのデザインが大きく変更されたので、お気づきの方も多いのではないでしょうか。

今日は、リニューアルによって追加された機能の中でも目玉の、「Chrome Appsビルド機能」についてご紹介します。

そもそも、Chrome Appsとは何かと言いますと、
Chromeのエンジンによって動作する「デスクトップアプリ」です。
Chromeブラウザの中で動くのではなく、一般的なデスクトップアプリのように単独のウィンドウで動作します。
インストールしたアプリはオフラインでも利用することができます。

Chrome AppsはChromeウェブストアで公開されています。
動作確認用アプリ「Monacaデバッガー」も、Chrome Apps版がリリースされています。
Chromeウェブストアで「monaca」で検索してみて下さい。
「無料」ボタンを押すとインストールが行われます。




それでは、Chrome Appsの開発手順について、順を追って解説していきます。

(1) 開発
アプリの作り方はAndroidアプリやiOSアプリを作るときと同様に、HTML、CSSJavaScriptで開発します。
PhoneGapの機能は使えないので気をつけてください。
また、ダイアログの表示やローカルストレージを利用したい場合は、
Web標準APIでは動かないため、Chrome Apps用のAPIを使います。
その他、インラインスクリプトは使えないなどの制約もあります。
詳しくは公式のデベロッパーサイトに記載されていますが、まだ英語ページしかありません。


(2) テスト
Chrome Apps版 Monacaデバッガーを使います。
Chromeブラウザの右上の三本線のアイコンから、ツール>拡張機能 の順に選択して「拡張機能」画面を表示し、
Monacaデバッガーの「起動」リンクを押して下さい。



また、Chromeランチャーをインストールしておけば、
デスクトップからアプリを起動することもできます。



アカウント情報を入力してログインすると、プロジェクトの一覧が表示されます。



対象のプロジェクトを選択すると、初回のみ「ワーキングディレクトリを選択」という画面が表示されます。
ワーキングディレクトリとは、クラウド上のソースコードをダウンロードする場所のことです。
任意のフォルダを指定して下さい。



ワーキングディレクトリを設定すると、プロジェクトのダウンロードが始まります。
ダウンロードが完了すると、「(アプリ名)の起動を行ってください」という画面が表示されます。



アプリの起動の仕方は、まず、ワーキングディレクトリとして設定したフォルダを開きます。
そして対象のプロジェクトフォルダを、Chromeの「拡張機能」画面にドラッグ&ドロップします。
少々面倒ですが、アプリの追加はセキュリティ上の問題で手作業で行わなければなりません。
この作業は最初の1回だけやってしまえば、次回以降は行う必要がありません。



追加したアプリを「起動」リンクかChromeランチャーから選択すると、アプリが立ち上がります。




(3) ビルド
アプリが完成したら、Chromeウェブストアにリリースするためのファイルを生成します。
アプリ名などの変更はMonaca IDEの 設定>Chrome Apps設定 から行うことができます。
ビルドはMonaca IDEの ビルド>Chrome Appsのビルド を選択するだけです。
生成されたzipファイルをChromeウェブストアの「デベロッパーダッシュボード」から登録します。
なお、Chromeウェブストアのデベロッパー登録には5ドルかかります。



Chrome Appsに関してはまだ開発のノウハウがそれほど蓄積されていないため、
特に日本語の情報は手に入りにくい状況にあります。
しかし、モバイルと併せてPC向けにもアプリを展開したいという要件に対しては、
HTMLでデスクトップアプリを開発できるChrome Appsはまさにうってつけではないでしょうか。
これまでAndroidiPhoneアプリ開発を行ってきたMonacaユーザーの皆さま、
この機会に是非Chrome Appsアプリにも挑戦してみてください!

Chrome Appsで簡易Webサーバ構築

はじめに



今回は、Chrome Appで簡易サーバを作ってみます。Chrome AppsはHTML5を使用して作成され、Chromeをプラットフォームとして動作します。Chromeの画面内部で動くのではなく、アプリケーションごとに個別の画面を持ち、デスクトップ型アプリケーションと同様に振る舞います。様々なAPIが提供され、その種類もどんどん増えてきており、ネイティブアプリに近いことを実装できるようになってきました。一度Chrome Appsを作ってみると、HTML5の世界が広がります。

Chrome AppsのAPIでは、Chromeバージョン24からchrome.socket APIが提供され、UDPTCPでの通信を実行できるようになりました。さらに、バージョン33からは、chrome.socketがdeprecatedとなり、代わりに以下のAPIが提供されています。


この新しいAPIを使用して簡易Webサーバを構築してみたいと思います。

※ ここではChrome Appsの作り方は知っていることを前提にしています。

各種ファイル



今回のアプリケーションはブラウザでhttp://localhost:3000にアクセスすると、Hello worldと表示する簡易サーバです。このアプリケーションは次の4つのファイルから構成されます。

  • manifest.json: Chrome Appsの基本情報やパーミッションを記述
  • background.js: 画面の構築等を記述
  • index.html: 画面の内容
  • main.js: 画面内部の処理を記述(後述)

manifest.json



まず、Manifestファイルで最低限のパーミッションのみを記述しておきます。今回は、sockets.tcpとsockets.tcpServerを使用します。なお、これらのパーミッションは、"permissions"の外に記述します。



{
    "name": "Asial Blog 2014-04-15",
    "description": "Asial blog sample",
    "version": "1.0",
    "app": {
        "background": {
            "scripts": ["background.js"]
        }
    },
    "sockets": {
        "tcp": {
            "connect": "*"
        },
        "tcpServer": {
            "listen": "*"
        }
    },
    "permissions": []
}


background.js



background.jsでは、単に画面を開きます。200x300の画面をディスプレイ左上に表示します。



chrome.app.runtime.onLaunched.addListener(function(launchData) {
    chrome.app.window.create('index.html', {
        'bounds': {
            'width' : 200,
            'height': 300,
            'top' : 0,
            'left': 0
        }
    });
});


index.html



開かれた画面では、main.jsを読み込むだけとします。アプリを作成する際には、画面の内容や処理結果を表示しますが、今回は全てconsole.logで確認しましょう。



<!DOCTYPE html>
<html>
    <head>
        <script src="main.js"></script>
    </head>
    <body>
    </body>
</html>



サーバの構築



サーバでのデータ送受信の手順は次のようになります。

  1. サーバ用ソケットを作成する(create)
  2. サーバ用ソケットで特定のポートをlistenし、待機状態にする(listen)
  3. サーバ用ソケットへのリクエストが来たら、リクエスト用ソケットを作成する(accept)
  4. リクエスト用ソケットからリクエストの内容を取得し解釈する(receive)
  5. リクエスト用ソケットを使ってレスポンスを返す(send)
  6. リクエスト用ソケットを破棄する(disconnect, close)
  7. 3〜6を繰り返す
  8. サーバを停止し、サーバ用ソケットを破棄する(disconnect, close)

実際にHTTPサーバをChrome Appsで記述すると以下のようになります。

main.js


var serverSocketId;

/**
 * サーバ起動
 */
chrome.sockets.tcpServer.create({}, function(createInfo) {
    // サーバ用のソケット
    serverSocketId = createInfo.socketId;

    // 3000番ポートをlisten
    chrome.sockets.tcpServer.listen(serverSocketId, '0.0.0.0', 3000, function(resultCode) {
        if (resultCode < 0) {
            console.log("Error listening:" + chrome.runtime.lastError.message);
        }
    });
});

/**
 * リクエスト用ソケット作成
 */
chrome.sockets.tcpServer.onAccept.addListener(function(info) {
    if (info.socketId === serverSocketId) {
        chrome.sockets.tcp.setPaused(info.clientSocketId, false);
    }
});

/**
 * リクエスト受信
 */
chrome.sockets.tcp.onReceive.addListener(function(info) {
    console.log("Receive: ", info);

    // リクエスト確認: ArrayBufferを文字列に変換
    // 本来はヘッダの先頭と、Content-Length等からリクエストの範囲を検出し、
    // 受信データからHTTPリクエストを取り出す必要がある
    var requestText = ab2str(info.data);
    console.log(requestText);

    // レスポンス送信
    var socketId = info.socketId;
    var message = 'Hello world';
    var responseText = [
        ' HTTP/1.1 200 OK',
        'Content-Type: text/plain',
        'Content-Length: ' + message.length,
        '',
        message
    ].join("\n");
    chrome.sockets.tcp.send(socketId, str2ab(responseText), function(info) {
        if (info.resultCode < 0) {
            console.log("Error sending:" + chrome.runtime.lastError.message);
        }

        // ソケット破棄
        chrome.sockets.tcp.disconnect(socketId);
        chrome.sockets.tcp.close(socketId);
    });
});

/**
 * データ受信エラー
 */
chrome.sockets.tcp.onReceiveError.addListener(function(info) {
    console.log("Error: ", info);
});

/**
 * 文字列をArrayBufferに変換する(ASCIIコード専用)
 *
 * @param text
 * @returns {ArrayBuffer}
 */
function str2ab(text) {
    var typedArray = new Uint8Array(text.length);

    for (var i = 0; i < typedArray.length; i++) {
        typedArray[i] = text.charCodeAt(i);
    }

    return typedArray.buffer;
}

/**
 * ArrayBufferを文字列に変換する(ASCIIコード専用)
 *
 * @param arrayBuffer
 * @returns {string}
 */
function ab2str(arrayBuffer) {
    var typedArray = new Uint8Array(arrayBuffer);
    var text = '';

    for (var i = 0; i < typedArray.length; i++) {
        text += String.fromCharCode(typedArray[i]);
    }

    return text;
}


chrome.sockets.tcpServer.createでサーバ用のソケットを生成します。chrome.sockets.tcpServer.listenで3000番ポートでリクエストを待ちます。

そして、chrome.sockets.tcpServer.onAccept.addListenerを使用して、HTTPリクエストを受け取る準備をします。このコールバックは、3000番ポートへのアクセスが発生するたびに呼び出されます。コールバック内で、クライアント用のソケットIDを受け取り、chrome.sockets.tcp.setPausedでソケットの停止状態を解除します。

ここまで来ると、データを受信できます。データの受信は、chrome.sockets.tcp.onReceive.addListenerへ渡したコールバック内部にて行います。受信データはArrayBuffer型から文字列に変換し、その内容を確認しましょう。ヘッダが長い場合や、POSTやKeep-Alive等を考慮する場合、データの取得はもっと複雑になります。今回は短いGETリクエストのみを対象とし、簡略化しています。Webサーバでは、HTTPリクエスト内容を解釈し、レスポンスを決定します。ここでは、どのようなリクエストでも、"Hello world"を返すこととします。データの送信には、chrome.sockets.tcp.sendを使用します。送信データはArrayBuffer型で渡す必要があります。

データの送信が終わったら、リクエスト用ソケットをchrome.sockets.tcp.disconnectで接続を解除し、chrome.sockets.tcp.closeでリクエスト用ソケットを破棄します。HTTPリクエストが届くたびに、データの受信・送信処理とソケット破棄が実行されます。

また、今回は記述していませんが、サーバを停止する場合には、chrome.sockets.tcpServer.diconnectを使用します。さらに、サーバ用ソケットを破棄するには、chrome.sockets.tcpServer.closeを使用します。

Chrome Appsを起動して、ブラウザからhttp://localhost:3000へアクセスして下さい。ブラウザ上にHello worldと表示されていれば成功です。もし上手く動かなければ、アプリを再起動(もしくは再読み込み)してください。アプリ画面上で右クリックし、要素の検証からDeveloper Toolsを表示し、ログを確認すれば、HTTPリクエストが表示されています。

おわりに



Hello worldを返す単純なサーバを構築しました。たったこれだけの内容でも、HTML5の可能性を味わえたと思います。本格的にサーバを構築する場合、HTTPリクエストの解析と解釈、HTTPレスポンスでのデータ送信等を実装していく必要があります。Webサーバを実装できれば、その先のWebSocketサーバなども実装できます。興味があれば、挑戦してみて下さい。

GoogleChromeのExtension作成 タブの削除時に直前のタブに戻る拡張を作ってみる

こんばんは。松田です。
今日はGoogleChromeのExtensionを作ってみたいと思います。
実装するExtensionの内容はとっても簡単。「タブを閉じたときに直前に開いていたタブに戻る」Extensionです。

みなさんGoogleChrome使ってますか?
Extension入れまくってますか?
自分はChromeにExtensionを入れられるようになってから完全にChromeの虜となっています。

そんな中、最近気に入っている拡張がこれです。

Right Click Opens Link in New Foreground Tab
https://chrome.google.com/extensions/detail/afalkcagoidkdjdlfoaicbanbfgoamoo?hl=ja

リンクを右クリックしたら新しいタブでリンク先を開いてくれる、ただそんだけのExtensionです。
とてもシンプルなものですが、自タブに表示されている内容を消したくないときや、画像を別枠で見たいときなどに重宝してます。
要するに画像がいっぱいあるようなサイトで画像をポンポン別タブで開くときにすごい便利なんです。

しかし、このExtensionを使っていて気になってくるのが、新しく開いたタブを消した時の遷移先です。
別タブで開いて見たいリンク先は、大抵そんなに重要でない内容のものが多いので、開いて消して開いて消してを繰り返すことになります。
自分としてはリンクを別タブで開いて消して開いて消して・・・を繰り返したいのですが、Chromeは消したタブの右のタブにフォーカスを当ててしまうため、思ったような快適な操作ができません。

というわけで、前置きが長くなりましたが、そんな理由から「タブを閉じたときに直前に開いていたタブに戻る」動作をするExtensionを作ってみることにしました。




Extensionのつくりかた基礎

まず最初に見たサイトはここ。
http://code.google.com/chrome/extensions/getstarted.html
英語ですがExtensionの作り方の基礎がシンプルで分かりやすく解説されています。
このページの内容を追っていくだけで5分もかからずにExtensionが作れてしまいます。
まずはこのページの内容をさらーっと追って作ってみるとよいかと思います。

次に見たのがここ。
http://dev.screw-axis.com/doc/chrome_extensions/
そうは言ってもやっぱ日本語で見たいよねー。ということで読みあさりました。
上の本家リファレンスの日本語訳が載っています。




実際につくってみる

1. まずExtensionを開発するディレクトリを用意します。


2. その中にmanifest.jsonファイルを作成します。


このmanifest.jsonファイルがExtensionの設定ファイルとなります。
作成するExtensionの名称や基本動作をここで設定します。

manifest.json


{
  "name": "BackToTheLastTab",
  "version": "1.0",
  "description": "Go back to the last tab when you close a tab.",
  "background_page": "background.html",
  "permissions": [
  	"tabs"
  ]
}


Extensionの実際の処理を行うのが、ここで"background_page"に設定した "background.html" になります。
このHTMLに記述したJavaScriptの処理がExtensionの実行結果となります。

"background_page"はバックグラウンドで処理を行わせるための項目ですが、この他にもExtensionの処理を行わせる方法はいくつか存在します。
その中でもよく使われそうなものをいくつかピックアップします。



■ browser_action
上部メニューにアイコンが追加される、最も基礎的なExtension。
アイコンをクリックしたときにExtensionが実行される。
すべてのページで共通して使えるような機能を実装する。

■ page_action
アドレスバーの右にアイコンが表示される形式のExtension
特定のページにだけ有効な機能を実装するのに使われる。
RSS取得ボタン等々。。

■ options_page
Extensionの設定を変更するオプションページのHTMLを指定する。
HTMLとJavaScriptで記述。

■ chrome_url_overrides
Chromeの特定のページを上書きする。
新規タブを開いたときや、履歴ページを開いたときに、独自のページを表示させることができる。
Feedlyとかが使ってるのがこれ。

■ background_page
Extensionが起動した瞬間からずーっと実行されているページ。
HTMLで記述されてるけど実際は表示されない。
ここにJavaScriptでコードを仕込んでおいて、必要なときに実行させたりする。
とりあえず何かしたかったらここにJavaScript仕込めばいいと見た。


今回作成するExtensionでは表示する画面は必要なく、裏側で起動されていればいいだけなので、"browser_action" も "page_action" も記述していません。
また、タブの操作を行わせるため、permissionsにtabsを渡しています。


3. 同じディレクトリ内に、background.htmlをおきます。


background.html


<html>
<head><title>background</title></head>
<script type="text/javascript">

var selectedId1 = -1; // 直前に開いたタブのID
var selectedId2 = -1; // ↑のひとつ前に開いていたタブのID

/**
 * タブが切り替えられたときの処理
 */
chrome.tabs.onSelectionChanged.addListener(function(tabId, selectInfo) {
	//	alert(tabId + ':' + selectInfo);
	selectedId2 = selectedId1;
	selectedId1 = tabId;
});

/**
 * タブが閉じられたときの処理
 */
chrome.tabs.onRemoved.addListener(function(id, removedInfo) {
	//	alert(id + ':' + removedInfo);
	//	alert('selected2 = ' + selectedId2);
	
	if (selectedId2 > 0) {
		chrome.tabs.update(selectedId2, {selected:true});
	}
});

</script>
<body>
</body>
</html>

このExtensionの本体になるのがこのファイルのScript部分です。
やりたいことが簡単なのでスクリプトもえらくシンプルです。

タブが切り替えられたときに、今まで開いていたタブのIDを記憶しておく処理と、タブが閉じられたときに、記憶していたタブにフォーカスを当てる処理を書いています。
デバッグ方法がよくわかんなかったので、いたるところにalert()を仕込んで実行しまくりました。
script開始部分にalert('hoge');を仕込み、Extension実行時に 'hoge' が表示されなければJSがどこか間違ってます。
多分もっとかっこいい開発方法があるはずです。

タブの操作なので、下のURLを読んで片っ端から実行してみました。
http://dev.screw-axis.com/doc/chrome_extensions/ref/api/tabs/


これでExtensionは完成です。


GoogleChromeにExtensionをインストール
次に、作ったExtensionをChromeにインストールしてみます。

1. まずはChrome右上の設定ボタンから、「拡張機能」画面を開きます。


2. 次に「デベロッパーモード」をクリック。
ボタンが3つ現れるので、「パッケージ化されていない拡張機能を読み込みます」ボタンをクリックします。


3. フォルダ選択のウィンドウが出るので、作成したExtensionのフォルダを選択。
すると他のExtensionと同様に作成したExtensionが読み込まれます。


これでインストール完了です。
作成したExtensionが一覧に表示され、実行されます。
もし納得した動きになっていなかったら、スクリプトを修正して「再読み込み」を繰り返します。



作ったExtensionを公開する

作ったついでに以下のページを参考に公開してみました。
http://blog.smartnetwork.co.jp/staff/node/44

BackToTheLastTab

https://chrome.google.com/extensions/detail/gmnobciacgaahhngegnpblmomljmbcmd
ちゃちゃーっとファイルを圧縮してアップしただけでほんとに公開されちゃってます。
簡単すぎてびびります。


最初のサンプルの実行に5分。このExtensionの実装に1時間ほど。
Extension開発というネーミングから取っ付きづらいものかとイメージしてたのですが、驚くほどあっさりと作れました。
快適なChrome生活を送るために、ぜひ皆さんもステキなExtensionを作ってみてください。