2010年 10月 17日

DSi Browser にも console.log が欲しい

DSi Browser 上で動く JavaScript を書く必要があることがしばしばあると思いますが、今のご時世でもデバッグが面倒で、alert デバッグに頼ったりすることがあります。これは大変今時としてはありえないので、console.log が欲しいところです。とはいえ、DSi Browser の表示領域というのは非常に限らており、console.log を作るにしても表示する場所がありません。

なので以下のようなサーバを立ててやることがあります。

#!/usr/bin/env plackup -s AnyEvent 
# vim:set ft=perl:
use strict;
use warnings;
use lib 'lib/perl/lib/perl5', 'lib/perl/lib/perl5/x86_64-linux-thread-multi';

use Plack::Request;
use AnyEvent;
use JSON::XS;

my $template = <<'EOS';
<!DOCTYPE html>
<title>tail</title>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.4.2.min.js"></script>
<script type="text/javascript">$(function () {
var log   = document.getElementById('log');
var $style = $(document.createElement('style')).appendTo(document.body);

$('.host').live('click', function () {
	var host = $(this).text();
	$style.text( '.line { display: none } ' + '.line.' + classOfHost(host) + ' { display: block }');
});

$(document.body).dblclick(function () {
	$style.text('.line { display: block }');
});

function classOfHost (host) {
	return 'h-' + host.replace(/\./g, '-');
}


function read () {
	$.ajax({
		url: '/api/read',
		dataType : 'json',
		success : function (data) {
			var messages = data.messages;
			for (var i = 0, len = messages.length; i < len; i++) {
				var message = messages[i];
				$('<div class="line"></div>').
					append($('<span class="host"></span>').text(message.host)).
					append($('<span class="body"></span>').text(message.body)).
					addClass(classOfHost(message.host)).
					prependTo(log);
			}
			while (log.childNodes.length > 100) log.removeChild(log.lastChild);
		},

		complete : function () {
			setTimeout(function () {
				read();
			}, 1000);
		}
	});
}

read();

})</script>
<style type="text/css">
pre {
	font-size: 14px;
	line-height: 1.33;
}

.host {
	color: #666;
	margin: 0 1em 0 0;
	cursor: pointer;
}

.body {
}
</style>
<pre id="log"></pre>
EOS

my $i   = 0;
my $sessions = {};

my $app = sub {
	my $env = shift;
	$env->{'psgi.streaming'} or die;
	sub {
		my $callback = shift;

		my $req = Plack::Request->new($env);
		my $res = $req->new_response(200);
		eval {
			+{
				'/' => sub {
					$res->content_type('text/html');
					$res->content($template);
					$callback->($res->finalize);
				},
				'/api/read' => sub {
					my $sid = $req->cookies->{sid} || $i++;
					my $session = $sessions->{$sid} || +{
						sid      => $sid,
						messages => [],
						callback => undef,
						expire   => 0,
						ua       => $req->header('User-Agent'),
					};
					$session->{expire} = time() + 60 * 60;
					$sessions->{$sid} = $session;
					$res->cookies->{sid} = $sid;

					if (@{ $session->{messages} }) {
						$res->content(encode_json +{
							sid => $sid,
							messages => $session->{messages},
						});
						$session->{messages} = [];
						$callback->($res->finalize);
					} else {
						$session->{callback} = sub {
							my $message = shift;
							delete $session->{callback};
							$res->content(encode_json +{
								sid => $sid,
								messages => [ $message ],
							});
							$res->content_type('application/json');
							$callback->($res->finalize);
						};
					}
				},
				'/p' => sub {
					my $message = {
						host => $req->address,
						body => $req->param('m'),
					};
					for my $sid (keys %$sessions) {
						my $session = $sessions->{$sid};
						if ($session->{expire} < time()) {
							delete $sessions->{$sid};
							next;
						}

						if ($session->{callback}) {
							$session->{callback}->($message);
						} else {
							push @{ $session->{messages} }, $message;
						}
					}

					$res->content_type('application/json');
					$res->content(encode_json +{ status => 'ok' });
					$callback->($res->finalize);
				},
			}->{ $req->path }->();
		};

		if ($@) {
			$res->code(200);
			$res->content($@);
			$callback->($res->finalize);
		}
	};
};

use Plack::Builder;

builder {
	enable "Plack::Middleware::ReverseProxy";
	$app;
};

このサーバを立てたうえで、JS で以下のようなコードをつくっておきます。

function toTail (msg) {
	var img = new Image();
	img.src = 'http://tail.psgi/p?m=' + encodeURIComponent(msg);
}

if (typeof console == 'undefined') {
	console = {
		log : toTail
	};
}

どや