Javascript-Flash socket bridge with haxe

Sat 29 November 2008

There are a bunch of solutions for making a bridge to use the flash sockets (flash.net.Socket) in javascript, notably socketBridge and jssockets. But I didn't like the way they made you write your client code: because you can't pass javascript function references through the ExternalInterface you usually pass your callbacks as strings representing the name of an global object. IMHO, that just sucks so I've made my own mix named HaxeSocketBridge.

You can write the client code like this:

var s = new FlashSocket({
    on_data: function(data) {
        document.getElementById('datapanel').value = data;
    },
    on_io_error: function(msg) {
        alert("IO ERROR: "+msg);
    },
    on_security_error: function(msg) {
        alert("SECURITY ERROR: "+msg);
    },
    on_close: function(msg) {
        alert("Connection closed.");
    },
    on_connect: function() {
        s.write("GET / HTTP/1.0\r\n\r\n");
    }
});
s.connect('example.com', 80);

Here's the whole example.

How does it work? You can execute arbitrary javascript code though the external interface, so why not execute some boilerplate code and setup a global FlashSocket class that handles all the nasty actionscript-javascript communication. I've seen this arguably good idea in the SWFHttpRequest bridge. If you do something like this be careful with those damn commas/semicolons or else you're in i-dont-know-why-it-breaks hell :)

Here's how the guts look like (note that the language is haxe):

ExternalInterface.addCallback("connect", connect);
ExternalInterface.addCallback("close", close);
ExternalInterface.addCallback("write", write);
ExternalInterface.addCallback("CAN_I_HAS_SOCKET", CAN_I_HAS_SOCKET);
ExternalInterface.call([
"(function(){",
    "if (window.FlashSocket) return;",
    "var Class = function(properties){",
        "var klass = function(event_handlers){ ",
            "for (var p in event_handlers) {",
                "if (event_handlers.hasOwnProperty(p)) {",
                    "this[p] = event_handlers[p];",
                "}",
            "}",
            "return this.init.apply(this);",
        "};",
        "klass.prototype = properties;",
        "klass.constructor = arguments.callee;",
        "return klass;",
    "};",
    "window.FlashSocket = new Class({",
        "init: function(){",
            "this._instance = ''+window.FlashSocket._instances.length;",
            "window.FlashSocket._instances.push(this);",
        "},",
        "close: function(){ ",
            "window.FlashSocket._instances[this._instance] = null;",
            "window.FlashSocket._bridge.close(this._instance );",
        "},",
        "write: function(data){ ",
            "window.FlashSocket._bridge.write(this._instance, data);",
        "},",
        "connect: function(host, port) {",
            "window.FlashSocket._bridge.connect(this._instance, host, port);",
        "}",
    "});",
    "window.FlashSocket._instances = [];",
    "var f = function(tag){",
        "var elems = document.getElementsByTagName(tag);",
        "for (var i=0; i<elems.length; i++) if (elems[i].CAN_I_HAS_SOCKET) return elems[i];",
    "};",
    "window.FlashSocket._bridge = f('embed') || f('object');",
"})" ].join('') );
if (flash.Lib.current.loaderInfo.parameters.onloadcallback != null)
    ExternalInterface.call(flash.Lib.current.loaderInfo.parameters.onloadcallback);

The trick is that we can setup a global javascript class-like object from the flash clip. You can grab the code here. Also, if you test from a local disc you might want to adjust your flash security settings. To compile the .hx file run "haxe compile.hxml". Now, the only thing I miss is some buffering/readline logic.

This entry was tagged as flash flash socket bridge haxe sockets