Niro*

#18 JSDeferred in Jetpack (Add-on SDK). Easy to dialogues between chrome and content.

I wrote a binding of JSDeferred*1 to Jetpack (Add-on SDK). This makes very easy to message dialogues between chrome and content in some cases.

Load this library and you can use Deferred.postie(constructor, options) function for creating new widget or panel.

A instance of a widget or panel created by Deferred.postie has post() method and bind() method.

  • post(args..., function) //=> Deferred
    • Call function with args in the content context of receiver and get the result as Deferred.
  • bind(selector, event, function) //=> Deferred
    • Bind event to all matched elements by selector in content context and call function in chrome context as a event is fired.

So you can write basic message dialogue with following:

const Deferred = require("jsdeferred").Deferred;
Deferred.define(this);

widget = Deferred.postie(widgets.Widget, {
	label      : "Foo",
	contentURL : 'http://example.com/',
	width      : 32,
	onClick    : function () {
		var self = this;

		next(function () {
			return self.
				post(1, 2, function (a, b) { // content context 
					return a + b;
				}).
				next(function (res) { // chrome context
					console.log(res);
				});
		}).
		next(function () {
			return wait(1);
		}).
		next(function () {
			return self.
				post(function (a, b) {
					throw "foo";
				}).
				next(function (res) {
					console.log(res);
				}).
				error(function (e) {
					console.log(e);
				});
		});
	},
	onMessage : function (message) {
		console.log(message);
	},
	contentScript : 'setTimeout(function () { postMessage("custom message") }, 1000)',
});

widget.post(function () {
	var d = new Deferred();
	setTimeout(function () {
		d.call(1);
	}, 1000);
	return d;
}).
next(function (a) {
	console.log("Hello! " + a);
}).
error(function (e) {
	console.log(e);
});

widget.bind("body", "click", function (e) {
	console.log("body clicked" + e);
}).
error(function (e) {
	console.log(e);
});

[[javascript]] [[jsdeferred]] [[jetpack]]

#17 pub/sub message queue with HTTP, simple implementation on MongoDB

  • Using MongoDB as a storage of callback list and buffer
  • Using HTTP as push message
  1. First, a subscriber requests to /sub with key which is wanted.
  2. When a publisher fired a event about a key, it requests to /pub with the key with a message
  3. This message queue server receives a pub request and turns over the message to subscribers by HTTP.

This is only for internal usage of web application. There are no authentications.

package Buspub;

use strict;
use warnings;
sub route ($$);
use Router::Simple;
use Tie::IxHash;
use AnyEvent::HTTP;
use HTTP::Request::Common;
use JSON::XS;
use POSIX ();

use MongoDB;
my $connection = MongoDB::Connection->new(host => 'localhost', port => 27017);
my $subscriptions = $connection->get_database('pubsub')->subscriptions;
$subscriptions->ensure_index(Tie::IxHash->new( key => 1, created => -1 ), { background => 1 });

route '/sub' => {
	action => sub {
		my ($r) = @_;
		my $key = $r->req->param('key') or return $r->json({ error => "key required"});;
		my $callback = $r->req->param('callback') or return $r->json({ error => "callback required"});;
		my $id = $subscriptions->insert({
			key      => $key,
			messages => [],
			callback => $callback,
			max      => 25,
			created  => scalar time(),
		});
		LOG("SUB:: %s with callback: %s", $key, $callback);
		$r->json({ id => "$id" });
	}
};

route '/pub' => {
	action => sub {
		my ($r) = @_;
		my $key = $r->req->param('key') or return $r->json({ error => "key required"});;
		my $message = decode_json $r->req->param('message');

		my $cursor = $subscriptions->query({ key => $key });
		my %count;
		while (my $obj = $cursor->next) {
			$count{all}++;
			my $id  = $obj->{_id} . "";
			my $uri = $obj->{callback};
			my $messages = [ @{$obj->{messages}}, $message ];
			my $req = POST $uri, [ id => $id, messages => encode_json($messages) ];

			LOG("PUB:: => %s => %s with %d messages", $key, $req->uri, scalar @$messages);
			http_request $req->method => $req->uri,
				body => $req->content,
				headers => {
					map { $_ => $req->header($_), } $req->headers->header_field_names
				},
				timeout => 20,
				sub {
					my ($body, $headers) = @_;
					LOG("PUB:: <= %s <= %s with status:%d", $key, $req->uri, $headers->{Status});
					if ($headers->{Status} =~ /^2/) {
						$subscriptions->update({ _id => $obj->{_id} }, { '$pullAll' => { messages => $obj->{messages} } });
					} elsif ($headers->{Status} =~ /^4/) {
						$subscriptions->remove({ _id => $obj->{_id} });
					} elsif ($headers->{Status} =~ /^5/) {
						if (@$messages > $obj->{max}) {
							$subscriptions->remove({ _id => $obj->{_id} });
						} else {
							$subscriptions->update({ _id => $obj->{_id} }, { '$push' => { messages => $message } });
						}
					}
				}
			;
		}
		$r->json({ key => $key, delivered => \%count });
	}
};

route '/test/callback' =>  {
	action => sub {
		my ($r) = @_;
		use Data::Dumper;
		warn Dumper $r->req->param('id') ;
		warn Dumper decode_json $r->req->param('messages') ;
		$r->res->status($r->req->param('code') || 404);
		$r->res->content_type('application/json; charset=utf8');
		$r->res->content('{}');
		$r->res->finalize;
	}
};


BEGIN {
	my $router = Router::Simple->new;
	sub route ($$) { $router->connect(@_) };

	sub run {
		my ($env) = @_;
		if ( my $handler = $router->match($env) ) {
			my $c = Buspub::Context->new($env);
			$handler->{action}->($c);
		} else {
			[ 404, [ 'Content-Type' => 'text/html' ], ['Not Found'] ];
		}
	}

	sub LOG {
		my ($message, @args) = @_;
		print sprintf("[%s] $message", POSIX::strftime("%Y-%m-%d %H:%M:%S", localtime), @args), "\n";
	}
};

package Buspub::Request;
use parent qw(Plack::Request);

package Buspub::Response;
use parent qw(Plack::Response);

package Buspub::Context;
use JSON::XS;

sub new {
	my ($class, $env) = @_;
	bless {
		req => Buspub::Request->new($env),
		res => Buspub::Response->new(200),
	}, $class;
}

sub req { $_[0]->{req} }
sub res { $_[0]->{res} }

sub json {
	my ($self, $vars) = @_;
	my $body = JSON::XS->new->ascii(1)->encode($vars);
	$self->res->content_type('application/json; charset=utf8');
	$self->res->content($body);
	$self->res->finalize;
}

\&Buspub::run;

[[perl]] [[http]] [[webhook]] [[messagequeue]]

#16 The JavaScript Philosophy

  1. Small is beautiful.
  2. Make each snippet do one thing well.
  3. Build a prototype as soon as possible.
  4. Choose portability over efficiency.
  5. Store data in plain object.
  6. Use software leverage to your advantage.
  7. Use callback to increase leverage and portability.
  8. Avoid captive user interfaces.
  9. Make every snippet independent.

Mike Gancarz: The UNIX Philosophy

[[javascript]]

#15 DSi browser hacks

You may often write javascript for DSi browser so I do. I have some knowledge for it.

How to change the cursor.

You may try 'cursor: default' CSS but DSi browser ignores it.

But you can change to default cursor by 'position: absolute'.

How to show caret on input area.

You can show caret on input area by using JavaScirpt focus() function, and press 'A' button to input \n.

When focus event is fired.

It is fired *after* inputting text.

Keyboard events that you are able to handle.

  • 'A' key events are always fired.
  • Pressing 'L' key or 'R' key, ↑↓←→ key events are fired.
    • But page is fixed (that is cannot be scrolled), ↑↓ key events are fired without 'L' or 'R'.

That's all.

And some information:

  • keyup event is not fired, only keypress and keydown event are fired.
  • Pressing continuously, keydown events are fired many times.

HTML canvas on DSi browser

You cannot use drawImage() function. It is callable but unusable.

Color's low 4bits are cutoffed. For Example, if you specified #FFFFFF as fillStyle and call fillRect, drawing color is #F0F0F0 actually. And that this rule is also applied to alpha channel, more properly, #FFFFFF is treat as rgba(240, 240, 240, 0.94). So you can exactly not draw true white color.

#14 Irssi web interface Irssw

I wrote new web interface of irssi which is an IRC client. I wanted a good IRC web interface on smartphones (Android or iPhone) but it isn't.

Install and Run

  1. Copy rpc.pl to ~/.irssi/scripts/auto
    • And once run "/script load auto/rpc" in irssi
  2. Copy app.conf.sample to app.conf and edit it
    • You must change the password
  3. Run HTTPD: perl script/server.pl -p 9876
  4. Access http://server:9876/

Design

This is mainly designed for Android (especialy "poor" Android, HT-03A). So Irssw has following features:

  • Save memory
  • Full Ajax (for UI performance)
  • Unread management integrated to irssi
  • Post current location (This is just the icing on the cake)

Enjoy.

login