
// NOTE: to log/debug with Orbited, there are two methods:
//          Use firebug 
//              1) include Orbited.js (and not Log4js)
//              2) Orbited.loggers[LOGGERNAME].enabled = true
//              And it should do logging for that logger
//          Use log4js
//              1) include log4js.js BEFORE including Orbited.js
//              2) Orbited.loggers[LOGGERNAME].setLevel(Log4js.Level.ALL)
//              3) Orbited.loggers[LOGGERNAME].addAppender(new Log4js.ConsoleAppender())
//              Note: Other levels and appenders can be set as well (see Log4js docs)
//
//       When you are making a call to the logger, prefix the line (first three
//       Characters) with ;;; which we will strip out at build time. So if you
//       have an if statement thats logging specific, start that line with ;;;
//       as well. If you do a try/catch thats specific to logging, prefix all
//       lines involved with ;;;. You'll want to put the try closing } and the
//       catch statement on the same line, or this won't work.
//
//       the logging functions (info, warn, debug, error, etc.) take any number
//       of arguments, like in firebug. If you're using firebug for the logging,
//       you'll actually be able to inspect the objects that you log. Therefore
//       don't do logger.debug(obj1 + " -> " + obj2); as this will convert both
//       objects to strings and not allow you to inspect them in firebug. 
//       Instead call logger.debug(obj1, "->" obj2); Of course, for the Log4js
//       back-end, it will still toString the objects.

(function() {


var HANDSHAKE_TIMEOUT = 30000
var RETRY_INTERVAL = 250
var RETRY_TIMEOUT = 30000

Orbited = {}

Orbited.settings = {}
Orbited.settings.hostname = document.domain
Orbited.settings.port = (location.port.length > 0) ? location.port : 80
Orbited.settings.protocol = 'http'
Orbited.settings.log = false;
Orbited.settings.streaming = true;
Orbited.settings.HEARTBEAT_TIMEOUT = 6000
Orbited.settings.POLL_INTERVAL = 2000
Orbited.settings.pageLoggerHeight = '200px'
Orbited.settings.pageLoggerWidth = null;
Orbited.settings.enableFFPrivleges = false;
Orbited.singleton = {}


// Orbited CometSession Errors
Orbited.Errors = {}
Orbited.Errors.ConnectionTimeout = 101
Orbited.Errors.InvalidHandshake = 102
Orbited.Errors.UserConnectionReset = 103
Orbited.Errors.Unauthorized = 106

Orbited.Statuses = {}
Orbited.Statuses.ServerClosedConnection = 201


Orbited.util = {}

Orbited.util.browser = null;
if (typeof(ActiveXObject) != "undefined") {
    Orbited.util.browser = 'ie'
} else if (navigator.product == 'Gecko' && window.find && !navigator.savePreferences) {
    Orbited.util.browser = 'firefox'
} else if((typeof window.addEventStream) === 'function') {
    Orbited.util.browser = 'opera'
} 


////
// NB: Base64 code was borrowed from Dojo; we had to fix decode for not
//     striping NULs though.  Tom Trenka from Dojo wont fix this because
//     he claims it helped to detect and avoid broken encoded data.
//     See http://svn.dojotoolkit.org/src/dojox/trunk/encoding/base64.js
//     See http://bugs.dojotoolkit.org/ticket/7400
(function(){
    Orbited.base64 = {};

    var p = "=";
    var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

    if (window.btoa && window.btoa('1') == 'MQ==') {
        Orbited.base64.encode = function(data) { return btoa(data) };
        Orbited.base64.decode = function(data) { return atob(data) };
        return
    }

    Orbited.base64.encode=function(/* String */ba){
        //  summary
        //  Encode a string as a base64-encoded string
        var s=[], l=ba.length;
        var rm=l%3;
        var x=l-rm;
        for (var i=0; i<x;){
            var t=ba.charCodeAt(i++)<<16|ba.charCodeAt(i++)<<8|ba.charCodeAt(i++);
            s.push(tab.charAt((t>>>18)&0x3f)); 
            s.push(tab.charAt((t>>>12)&0x3f));
            s.push(tab.charAt((t>>>6)&0x3f));
            s.push(tab.charAt(t&0x3f));
        }
        //  deal with trailers, based on patch from Peter Wood.
        switch(rm){
            case 2:{
                var t=ba.charCodeAt(i++)<<16|ba.charCodeAt(i++)<<8;
                s.push(tab.charAt((t>>>18)&0x3f));
                s.push(tab.charAt((t>>>12)&0x3f));
                s.push(tab.charAt((t>>>6)&0x3f));
                s.push(p);
                break;
            }
            case 1:{
                var t=ba.charCodeAt(i++)<<16;
                s.push(tab.charAt((t>>>18)&0x3f));
                s.push(tab.charAt((t>>>12)&0x3f));
                s.push(p);
                s.push(p);
                break;
            }
        }
        return s.join("");  //  string
    };


    Orbited.base64.decode=function(/* string */str){
        //  summary
        //  Convert a base64-encoded string to an array of bytes
        var s=str.split(""), out=[];
        var l=s.length;
        var tl=0;
        while(s[--l]==p){ ++tl; }   //  strip off trailing padding
        for (var i=0; i<l;){
            var t=tab.indexOf(s[i++])<<18;
            if(i<=l){ t|=tab.indexOf(s[i++])<<12 };
            if(i<=l){ t|=tab.indexOf(s[i++])<<6 };
            if(i<=l){ t|=tab.indexOf(s[i++]) };
            out.push(String.fromCharCode((t>>>16)&0xff));
            out.push(String.fromCharCode((t>>>8)&0xff));
            out.push(String.fromCharCode(t&0xff));
        }
        // strip off trailing padding
        while(tl--){ out.pop(); }
        return out.join(""); //  string
    };
})();




Orbited.loggers = {}
Orbited.Loggers = {}
Orbited.util.loggingSystem = null;

if (window.Log4js) {
    Orbited.util.loggingSystem = 'log4js';
}
else if (window.console && console.firebug) {
    Orbited.util.loggingSystem = 'firebug';
}

Orbited.getLogger = function(name) {
    if (!Orbited.loggers[name]) {
        var logger = null;
        switch (Orbited.util.loggingSystem) {
            case 'firebug':
                logger = new Orbited.Loggers.FirebugLogger(name)
                break;
            case 'log4js':
                logger = new Orbited.Loggers.Log4jsLogger(name)
                break;

            default:
                logger = new Orbited.Loggers.PageLogger(name);
                break;
        }
        Orbited.loggers[name] = logger;
    }
    return Orbited.loggers[name]
}

// TODO: is it confusing to have Orbited.Loggers be the various logging classes
//       and Orbited.loggers be actual instances of logging classes?

Orbited.Loggers.FirebugLogger = function(name) {
    var self = this;
    self.name = name;
    self.enabled = false;
    var padArgs = function(args) {
        var newArgs = [ name + ":" ]
        for (var i = 0; i < args.length; ++i) {
            newArgs.push(args[i]);
        }
        return newArgs
    }
    self.log = function() {
        if (!self.enabled) { return }
        console.log.apply(this, padArgs(arguments))
    }
    self.debug = function() {
        if (!self.enabled) { return }
        console.debug.apply(this, padArgs(arguments))
    }
    self.info = function() {
        if (!self.enabled) { return }
        console.info.apply(this, padArgs(arguments))
    }
    self.warn = function() {
        if (!self.enabled) { return }
        console.warn.apply(this, padArgs(arguments))
    }    
    self.error = function() {
        if (!self.enabled) { return }
        console.error.apply(this, padArgs(arguments))
    }
    self.assert = function() {
        if (!self.enabled) { return }
        var newArgs = [arguments[0], name + ":" ]
        for (var i = 1; i < arguments.length; ++i) {
            newArgs.push(arguments[i]);
        }
        console.assert.apply(this, newArgs)
    }
    self.trace = function() {
        if (!self.enabled) { return }
        console.trace.apply(this, padArgs(arguments))
    }
}
Orbited.singleton.pageLoggerPane = null;

Orbited.Loggers.PageLogger = function(name) {
    var self = this;
    self.enabled = false;
    self.name = name;

    var checkPane = function() {
        if (!Orbited.singleton.pageLoggerPane) {
            var p = document.createElement("div");
            p.border = "1px solid black"
            if(Orbited.settings.pageLoggerHeight) {
                p.style.height = Orbited.settings.pageLoggerHeight;
            }
            if(Orbited.settings.pageLoggerWidth) {
                p.style.height = Orbited.settings.pageLoggerWidth;
            }

            p.style.overflow = "scroll"
            document.body.appendChild(p)
            Orbited.singleton.pageLoggerPane = p
        }
    }
    var show = function(data) {
        checkPane();
        var d = document.createElement('div')
        d.innerHTML = data
        Orbited.singleton.pageLoggerPane.appendChild(d)
        Orbited.singleton.pageLoggerPane.scrollTop = Orbited.singleton.pageLoggerPane.scrollHeight;
    }
    self.log = function() {
        if (!self.enabled) { return }
        var newArgs = [ "log", new Date(), "debug", "<b>" + name + "</b>" ]
        for (var i = 0; i < arguments.length; ++i) {
            newArgs.push(arguments[i]);
        }
        show(newArgs.join(", "));
    }
    self.debug = function() {
        if (!self.enabled) { return }
        var newArgs = [ new Date(), "debug", "<b>" + name + "</b>" ]
        for (var i = 0; i < arguments.length; ++i) {
            newArgs.push(arguments[i]);
        }
        show(newArgs.join(", "));
    }
    self.info = function() {
        if (!self.enabled) { return }
        var newArgs = [ new Date(), "info", "<b>" + name + "</b>" ]
        for (var i = 0; i < arguments.length; ++i) {
            newArgs.push(arguments[i]);
        }
        show(newArgs.join(", "));
    }
    self.warn = function() {
    }    
    self.error = function() {
    }
    self.assert = function() {
    }
    self.trace = function() {
    }
}


Orbited.Loggers.Log4jsLogger = function(name) {
    var self = this;
    self.name = name;
    // NOTE: Why oh WHY doesn't Log4js accept dots in the logger names, and 
    //       more importantly, why don't they have reasonble error messages?!
    var log4jsName = name
    while (log4jsName.indexOf('.') != -1) {
        log4jsName = log4jsName.replace('.', '_')
    }
    var logger = Log4js.getLogger(log4jsName)
    self.logger = logger
    logger.setLevel(Log4js.Level.OFF)

    var generateOutput = function(args) {
        var newArgs = [ name + ":" ]
        for (var i = 0; i < args.length; ++i) {
            newArgs.push(args[i]);
        }
        return newArgs.join(" ")
    }

    self.setLevel = function(level) {
        logger.setLevel(level)
    }
    self.addAppender = function(a) {
        logger.addAppender(a);
    }
    self.log= function() {
        // NOTE: log doesn't mean anything in Log4js. mapping it to info
        logger.info(generateOutput(arguments));
    }
    self.debug = function() {
        logger.debug(generateOutput(arguments));
    }
    self.info = function() {
        logger.info(generateOutput(arguments));
    }
    self.warn = function() {
        logger.warn(generateOutput(arguments));
    }    
    self.error = function() {
        logger.error(generateOutput(arguments));
    }
    self.assert = function() {
    }
    self.trace = function() {
    }

}
Orbited.system = Orbited.getLogger('system')



Orbited.CometTransports = {}

Orbited.util.chooseTransport = function() {
    if (Orbited.settings.streaming == false) {
        return Orbited.CometTransports.LongPoll;
    }
    var choices = []
    for (var name in Orbited.CometTransports) {
        var transport = Orbited.CometTransports[name];
        if (typeof(transport[Orbited.util.browser]) == "number") {
            Orbited.system.log('viable transport: ', name)
            choices.push(transport)
        }
    }
    // TODO: sort the choices by the values of transport[Orbited.util.browser]
    //       and return the transport with the highest value.
//    return XHRStream
    return choices[0]
}



var createXHR = function () {
    try { return new XMLHttpRequest(); } catch(e) {}
    try { return new ActiveXObject('MSXML3.XMLHTTP'); } catch(e) {}
    try { return new ActiveXObject('MSXML2.XMLHTTP.3.0'); } catch(e) {}
    try { return new ActiveXObject('Msxml2.XMLHTTP'); } catch(e) {}
    try { return new ActiveXObject('Microsoft.XMLHTTP'); } catch(e) {}
    throw new Error('Could not find XMLHttpRequest or an alternative.');
}


Orbited.legacy = {}
//Orbited.web.connect = function() {
//
//}

Orbited.CometSession = function() {
    var self = this;
    self.readyState = self.READY_STATE_INITIALIZED;
    self.onopen = function() {}
    self.onread = function() {}
    self.onclose = function() {}
    var sessionUrl = null;
    var sessionKey = null;
    var sendQueue = []
    var packetCount = 0;
    var xhr = null;
    var handshakeTimer = null;
    var cometTransport = null;
    var pingInterval = 30000;
    var pingTimeout = 30000;
    var timeoutTimer = null;
    var lastPacketId = 0
    var sending = false;

    /*
     * self.open can only be used when readyState is INITIALIZED. Immediately
     * following a call to self.open, the readyState will be OPENING until a
     * connection with the server has been negotiated. self.open takes a url
     * as a single argument which desiginates the remote url with which to
     * establish the connection.
     */
    self.open = function(_url) {
        self.readyState = self.READY_STATE_OPENING;
        var url = new Orbited.URL(_url)
        if (url.isSameDomain(location.href)) {
            xhr = createXHR();
        }
        else {
            xhr = new Orbited.XSDR();
        }
//        xhr = createXHR();
        if (Orbited.settings.enableFFPrivleges) {
            try { 
                netscape.security.PrivilegeManager.enablePrivilege('UniversalBrowserRead'); 
            } catch (ex) { } 
        }        

        xhr.open('GET', _url, true);
        xhr.onreadystatechange = function() {
            if (xhr.readyState == 4) {
                if (xhr.status == 200) {
                    sessionKey = xhr.responseText;
;;;                 self.logger.debug('session key is: ', sessionKey)
                    resetTimeout();
                    sessionUrl = new Orbited.URL(_url)
                    // START new URL way
//                    sessionUrl.extendPath(sessionKey)
                    // END: new URL way

                    // START: old URL way
                    if (sessionUrl.path[sessionUrl.path.length] != '/')
                        sessionUrl.path += '/'
                    sessionUrl.path += sessionKey
                    // END: old Url way
                    var transportClass = Orbited.util.chooseTransport()
                    cometTransport = new transportClass();
                    cometTransport.onReadFrame = transportOnReadFrame;
                    cometTransport.onclose = transportOnClose;
                    cometTransport.connect(sessionUrl.render())
                }
                
                else {
                    xhr = null;
                    self.readyState = self.READY_STATE_CLOSED;
                    self.onclose(Orbited.Errors.InvalidHandshake)
                }
            }
        }
        xhr.send(null);
    }
    
    /* 
     * self.send is only callable when readyState is OPEN. It will queue the data
     * up for delivery as soon as the upstream xhr is ready.
     */
    self.send = function(data) {
;;;     self.logger.debug('session.send', data)
        if (self.readyState != self.READY_STATE_OPEN) {
            throw new Error("Invalid readyState")
        }
        var data = Orbited.base64.encode(data)
        sendQueue.push([++packetCount, "data", data])
;;;     self.logger.debug('sending==', sending);
        if (!sending) {
;;;         self.logger.debug('starting send');
            doSend()
        }
    }

    /* 
     * self.close sends a close frame to the server, at the end of the queue.
     * It also sets the readyState to CLOSING so that no further data may be
     * sent. onclose is not called immediately -- it waits for the server to
     * send a close event.
     */
    self.close = function() {
        switch(self.readyState) {
            case self.READY_STATE_CLOSING:
            case self.READY_STATE_CLOSED:
                return
            case self.READY_STATE_INITIALIZED:
                // TODO: call onclose here?
                self.readyState = self.READY_STATE_CLOSED
                return
            default:
                break
        }
        self.readyState = self.READY_STATE_CLOSING;
        // TODO: don't have a third element (remove the null).
        sendQueue.push([++packetCount, "close", null])
        if (!sending) {
            doSend()
        }
    }

    /* self.reset is a way to close immediately. The send queue will be discarded
     * and a close frame will be sent to the server. onclose is called immediately
     * without waiting for a reply from the server.
     */
    self.reset = function() {
        var origState = self.readyState
        self.readyState = self.READY_STATE_CLOSED;
        switch(origState) {
            case self.READY_STATE_INITIALIZED:
                self.onclose(Orbited.Errors.UserConnectionReset);
                break;
            case self.READY_STATE_OPENING:
                xhr.onreadystatechange = function() {};
                xhr.abort();
                self.onclose();
                break;
            case self.READY_STATE_OPEN:
                self.sendQueue = []
                self.sending = false;
                if (xhr.readyState < 4) {
                    xhr.onreadystatechange = function() {}
                    xhr.abort();
                }
                doClose(Orbited.Errors.UserConnectionReset);
                // TODO: send close frame
                //       -mcarter 7-29-08
                break;
            case self.READY_STATE_CLOSING:
                // TODO: Do nothing here?
                //       we need to figure out if we've attempted to send the close
                //       frame yet or not If not, we do something similar to case
                //       OPEN. either way, we should kill the transport and
                //       trigger onclose
                //       -mcarter 7-29-08                
                break;

            case self.READY_STATE_CLOSED:
                break
        }
    }

    var transportOnReadFrame = function(frame) {
;;;     self.logger.debug('transportOnReadFrame')
;;;     self.logger.debug('READ FRAME: ', frame.id, frame.name, frame.data ? frame.data.length : '');
        if (!isNaN(frame.id)) {
            lastPacketId = Math.max(lastPacketId, frame.id);
        }
;;;     self.logger.debug(frame)
        switch(frame.name) {
            case 'close':
                if (self.readyState < self.READY_STATE_CLOSED) {
                    doClose(Orbited.Statuses.ServerClosedConnection)
                }
                break;
            case 'data':
;;;             self.logger.debug('base64 decoding ' + frame.data.length + ' bytes of data')
                var data = Orbited.base64.decode(frame.data)
;;;             self.logger.debug('decode complete');
                self.onread(data);
                break;
            case 'open':
                if (self.readyState == self.READY_STATE_OPENING) {
                    self.readyState = self.READY_STATE_OPEN;
;;;                 self.logger.debug('Call self.onopen()');
                    self.onopen();
                }
                else {
                    //TODO Throw and error?
                }
                break;
            case 'ping':
                // TODO: don't have a third element (remove the null).
                // NOTE: don't waste a request when we get a longpoll ping.
                switch(cometTransport.name) {
                    case 'longpoll':
                        break;
                    case 'poll':
                        break;
                    default:
                        sendQueue.push([++packetCount, "ping", null])
                        if (!sending) {
                            doSend()
                        }
                        break;                    
                }
                break;
            case 'opt':
                var args = frame.data.split(',')
                switch(args[0]) {
                    case 'pingTimeout':
                        pingTimeout = parseInt(args[1])*1000
                        break
                    case 'pingInterval':
                        pingInterval = parseInt(args[1])*1000
                        break;
                    default:
;;;                     self.logger.warn('unknown opt key', args[0])
                        break;
                }
        }
        resetTimeout();
    }
    var transportOnClose = function() {
;;;     self.logger.debug('transportOnClose');
        if (self.readyState < self.READY_STATE_CLOSED) {
            try {
                doClose(Orbited.Statuses.ServerClosedConnection)
            }
            catch(e) {
//              Fix for navigation-close
                return
            }
        }
    }        
    var encodePackets = function(queue) {
        //TODO: optimize this.
        var output = []        
        for (var i =0; i < queue.length; ++i) {
            var frame = queue[i];
            for (var j =0; j < frame.length; ++j) {
                var arg = frame[j]
                if (arg == null) {
                    arg = ""
                }
                if (j == frame.length-1) {
                    output.push('0')
                }
                else {
                    output.push('1')
                }
                output.push(arg.toString().length)
                output.push(',')
                output.push(arg.toString())
            }
        }
        return output.join("")
    }

    var doSend = function(retries) {
;;;     self.logger.debug('in doSend');
        if (typeof(retries) == "undefined") {
            retries = 0
        }
        // TODO: I don't think this timeout formula is quite right...
        //       -mcarter 8-3-08
        if (retries*RETRY_INTERVAL >= RETRY_TIMEOUT) {
            doClose(Orbited.Errors.ConnectionTimeout)
            sending = false;
            return
        }
        if (sendQueue.length == 0) {
;;;         self.logger.debug('sendQueue exhausted');
            sending = false;
            return
        }
        sending = true;
;;;     self.logger.debug('setting sending=true');
        var numSent = sendQueue.length
        sessionUrl.setQsParameter('ack', lastPacketId)
        var tdata = encodePackets(sendQueue)
;;;     self.logger.debug('post', retries, tdata);
        if (Orbited.settings.enableFFPrivleges) {
            try { 
                netscape.security.PrivilegeManager.enablePrivilege('UniversalBrowserRead'); 
            } catch (ex) { } 
        }        
        xhr.open('POST', sessionUrl.render(), true)
        // NB: its awkard, but for reusing the XHR object in IE (7 at least),
        //     we can only reset the onreadystatechange *after* we call open;
        //     if we don't do this, the XHR will stop sending data.
        // See "Reusing XMLHttpRequest Object in IE"
        //     at http://keelypavan.blogspot.com/2006/03/reusing-xmlhttprequest-object-in-ie.html
        xhr.onreadystatechange = function() {
            switch(xhr.readyState) {
                case 4:
                    if (xhr.status == 200) {
                        resetTimeout();
                        sendQueue.splice(0, numSent)
                        return doSend();
                    }
                    else {
                        //TODO: implement retry back-off;
                        window.setTimeout(
                            function() {
                                doSend(++retries);
                            },
                            RETRY_INTERVAL
                        );
                    }
            }
        }
        xhr.send(tdata)
    }
    
    var doClose = function(code) {
;;;     self.logger.debug('doClose', code)
        unsetTimeout();
        self.readyState = self.READY_STATE_CLOSED;
        cometTransport.onReadFrame = function() {}
        if (cometTransport != null) {
            // TODO: is this line necessary?
            cometTransport.onclose = function() { }
            cometTransport.close()
        }
        self.onclose(code);

    }

    var resetTimeout = function() {
;;;     self.logger.debug('reset Timeout', pingInterval+pingTimeout)
        unsetTimeout();
        timeoutTimer = window.setTimeout(timedOut, pingInterval + pingTimeout);
    }
    var unsetTimeout = function() {
        window.clearTimeout(timeoutTimer);

    }
    var timedOut = function() {
;;;     self.logger.debug('timed out!')
        doClose(Orbited.Errors.ConnectionTimeout)
    }
};
Orbited.CometSession.prototype.logger = Orbited.getLogger("Orbited.CometSession");
Orbited.CometSession.prototype.READY_STATE_INITIALIZED  = 1;
Orbited.CometSession.prototype.READY_STATE_OPENING      = 2;
Orbited.CometSession.prototype.READY_STATE_OPEN         = 3;
Orbited.CometSession.prototype.READY_STATE_CLOSING      = 4;
Orbited.CometSession.prototype.READY_STATE_CLOSED       = 5;


Orbited.TCPSocket = function() {
    var self = this;

    // So we don't completely ambush people used to the 0.5 api...
    if (arguments.length > 0) {
        throw new Error("TCPSocket() accepts no arguments")
    }
    self.readyState = self.READY_STATE_INITIALIZED;
    self.onopen = function() { }
    self.onread = function() { }
    self.onclose = function() { }
    var onCloseTriggered = false;
    var buffer = ""
    var session = null;
    var binary = false;
    var handshakeState = null;
    var hostname = null;
    var port = null;

    /* self.open attempts to establish a tcp connection to the specified remote
     * hostname on the specified port. When specified as true, the optional
     * argument, isBinary, will cause onread to return byte arrays, and send
     * will only accept a byte array.
     */
    self.open = function(_hostname, _port, isBinary) {
        if (self.readyState != self.READY_STATE_INITIALIZED) {
            // TODO: allow reuse from readyState == self.READY_STATE_CLOSED?
            //       Re-use makes sense for xhr due to memory concerns, but
            //       probably not for tcp sockets. How often do you reconnect
            //       in the same page?
            //       -mcarter 7-30-08
            throw new Error("Invalid readyState");
        }
        // handle isBinary undefined/null case
        binary = !!isBinary;
        self.readyState = self.READY_STATE_OPENING;
        hostname = _hostname;
        port = _port;
        session = new Orbited.CometSession()
        var sessionUrl = new Orbited.URL('/tcp')
        sessionUrl.domain = Orbited.settings.hostname
        sessionUrl.port = Orbited.settings.port
        sessionUrl.protocol = Orbited.settings.protocol
        sessionUrl.setQsParameter('nocache', Math.random())
        session.open(sessionUrl.render())
        session.onopen = sessionOnOpen;
        session.onread = sessionOnRead;
        session.onclose = sessionOnClose;
        handshakeState = "initial";
    }

    self.close = function() {
        if (self.readyState == self.READY_STATE_CLOSED) {
            return
        }
        self.readyState = self.READY_STATE_CLOSED
        doClose()
    }
    
    /* self.reset closes the connection from this end immediately. The server
     * may be notified, but there is no guarantee. The main purpose of the reset
     * function is for a quick teardown in the case of a user navigation.
     * if reset is not called when IE navigates, for instance, there will be 
     * potential issues with future TCPSocket communication.
     */
    self.reset = function() {
        if (session)
            session.reset();
    }

    self.send = function(data) {
        if (self.readyState != self.READY_STATE_OPEN) {
            throw new Error("Invalid readyState");
        }
        if (!binary) {
            data = Orbited.utf8.encode(data)
        }
;;;     self.logger.debug('SEND: ', data)
        session.send(data)
    }

    var process = function() {
        var result = Orbited.utf8.decode(buffer);
        var data = result[0];
        var i = result[1];
        buffer = buffer.slice(i);
        if (data.length > 0) {
            window.setTimeout(function() { self.onread(data) }, 0);
        }
    }

    var sessionOnRead = function(data) {
        switch(self.readyState) {
            case self.READY_STATE_OPEN:
;;;             self.logger.debug('READ: ', data)
                var data = data;
                if (binary) {
                    window.setTimeout(function() { self.onread(data) }, 0);
                }
                else {
;;;                 self.logger.debug('start buffer size:', buffer.length)
                    buffer += data;
                    process()
;;;                 self.logger.debug('end buffer size:', buffer.length)
                }
            break;
            case self.READY_STATE_OPENING:
                switch(handshakeState) {
                    case 'initial':
                        // NOTE: we should only get complete payloads during
                        //       the handshake. no need to buffer, then parse
                        data = Orbited.utf8.decode(data)[0];
;;;                     self.logger.debug('initial');
;;;                     self.logger.debug('data', data)
;;;                     self.logger.debug('len', data.length);
;;;                     self.logger.debug('typeof(data)', typeof(data))
;;;                     self.logger.debug('data[0] ', data.slice(0,1))
;;;                     self.logger.debug('type ', typeof(data.slice(0,1)))
                        var result = (data.slice(0,1) == '1')
;;;                     self.logger.debug('result', result)
                        if (!result) {
;;;                         self.logger.debug('!result');
                            var errorCode = data.slice(1,4)
                            doClose(parseInt(errorCode))
                        }
                        if (result) {
                            self.readyState = self.READY_STATE_OPEN;
;;;                         self.logger.debug('tcpsocket.onopen..')
                            self.onopen();
;;;                         self.logger.debug('did onopen');
                        }
                        break;
                }
                break;
        }
    }
    var doClose = function(code) {
;;;     self.logger.debug('doClose', code)
        if (session) {
            sessionOnClose = function() {}
            session.close()
            session = null;
        }
;;;     self.logger.debug('onCloseTriggered', onCloseTriggered)
        if (!onCloseTriggered) {
;;;         self.logger.debug('triggerClose timer', code)
            onCloseTriggered = true;
            window.setTimeout(function() {
;;;             self.logger.debug('onclose!', code);
                self.onclose(code) 
            }, 0)
        }
    }
    
    var sessionOnOpen = function(data) {
        // TODO: TCPSocket handshake
        var payload = hostname + ':' + port + '\n' 
;;;     self.logger.debug('sessionOpen; sending:', payload)
        payload = Orbited.utf8.encode(payload)
;;;     self.logger.debug('encoded payload:', payload)
        X = payload
        session.send(payload)
        handshakeState = 'initial'
    }
    
    var sessionOnClose = function(code) {
;;;     self.logger.debug('sessionOnClose');
        // If we are in the OPENING state, then the handshake code should
        // handle the close
        doClose(code);
    }
};
Orbited.TCPSocket.prototype.logger = Orbited.getLogger("Orbited.TCPSocket");
Orbited.TCPSocket.prototype.READY_STATE_INITIALIZED  = 1;
Orbited.TCPSocket.prototype.READY_STATE_OPENING      = 2;
Orbited.TCPSocket.prototype.READY_STATE_OPEN         = 3;
Orbited.TCPSocket.prototype.READY_STATE_CLOSING      = 4;
Orbited.TCPSocket.prototype.READY_STATE_CLOSED       = 5;





// XXX: the Orbited.XSDR stuff (presumably) doesn't work yet.
//      mcarter - 8-9-08 (~rev 476)

Orbited.singleton.XSDR = {
    receiveCbs: {},
    queues: {},
    id: 0,
    register: function(receive, queue) {
        var id = ++Orbited.singleton.XSDR.id;
        Orbited.singleton.XSDR.receiveCbs[id] = receive;
        Orbited.singleton.XSDR.queues[id] = queue;
;;;     Orbited.system.debug('id is', id)
        return id;
    }
}
Orbited.XSDR = function() {
    var self = this;
    var ifr = null;
    var url;
    var method;
    var data;
    var requestHeaders;
    var queue = []
    var id = Orbited.singleton.XSDR.register(
        function(data) { receive(data) }, 
        queue
    )
    var bridgeUrl = new Orbited.URL("")
    bridgeUrl.domain = Orbited.settings.hostname
    bridgeUrl.port = Orbited.settings.port
    bridgeUrl.path = '/static/xsdrBridge.html'
    bridgeUrl.hash = id.toString();
    bridgeUrl.protocol = Orbited.settings.protocol
;;; self.logger.debug('bridgeUrl.hash is', bridgeUrl.hash);
;;; self.logger.debug('bridgeUrl.path is', bridgeUrl.path);
;;; self.logger.debug('bridgeUrl is', bridgeUrl.render());
    var reset = function() {
        self.responseText = ""
        self.status = null;
        self.readyState = 0;
        url = null;
        method = null;
        data = null;
        requestHeaders = {};

    }
    reset();
    self.onreadystatechange = function() { }
    self.open = function(_method, _url, async) {
        if (self.readyState == 4) {
            reset();
        }
        if (self.readyState != 0) {
            throw new Error("Invalid readyState");
        }
        if (!async) {
            throw new Error("Only Async XSDR supported")
        }
;;;     self.logger.debug('open', _method, _url, async)
        self.readyState = 1;
        url = _url;
        method = _method;        
    }

    self.send = function(data) {
        if (self.readyState != 1) {
            throw new Error("Invalid readyState");
        }
;;;     self.logger.debug('send', data)
        if (!ifr) {
;;;         self.logger.debug('creating iframe')
            ifr = document.createElement("iframe")
            hideIframe(ifr);
            ifr.src = bridgeUrl.render()
;;;         self.logger.debug('set ifr.src to', ifr.src);
            document.body.appendChild(ifr);
        }
        else {
            queue.push([method, url, data, requestHeaders]);
        }
    }

    self.abort = function() {
        if (self.readyState > 0 && self.readyState < 4) {
            // TODO: push an ABORT command (so as not to reload the iframe)
//            queue.push(['ABORT']);
;;;         self.logger.debug('ABORT called');
            ifr.src = "about:blank"
            document.body.removeChild(ifr)
            ifr = null;
            self.readyState = 4;
            self.onreadystatechange();
        }
    }



//    self.abort = function() {
//        if (self.readyState > 0 && self.readyState < 4) {
//            queue.push(['ABORT']);
//        }
//    }

    self.setRequestHeader = function(key, val) {
        if (self.readyState != 0) {
            throw new Error("Invalid readyState");
        }
        requestHeaders[key] = val;
    }

    self.getResponseHeader = function() {
        if (self.readyState < 2) {
            throw new Error("Invalid readyState");
        }
        return responseHeaders[key]
    }

    var receive = function(payload) {
;;;     self.logger.debug('received', payload)
        switch(payload[0]) {
            case 'initialized':
                queue.push([method, url, data, requestHeaders]);
;;;             self.logger.debug('queue is', queue)
;;;             self.logger.debug('Orbited.singleton.XSDR.queues[id] is', Orbited.singleton.XSDR.queues[id])
                break;
            case 'readystatechange':
                var data = payload[1]
                self.readyState = data.readyState
;;;             self.logger.debug('readystatechange', self.readyState)
                if (data.status) {
                    self.status = data.status
;;;                 self.logger.debug('status', data.status)
                }
                if (data.responseText) {
                    self.responseText += data.responseText
;;;                 self.logger.debug('responseText', data.responseText)
                }
;;;             self.logger.debug('doing trigger');
                self.onreadystatechange();
;;;             self.logger.debug('trigger complete');
        }
    }

    var hideIframe =function (ifr) {
        ifr.style.display = 'block';
        ifr.style.width = '0';
        ifr.style.height = '0';
        ifr.style.border = '0';
        ifr.style.margin = '0';
        ifr.style.padding = '0';
        ifr.style.overflow = 'hidden';
        ifr.style.visibility = 'hidden';
    }
    
}

if (Orbited.util.browser == "opera")
{
    document.addEventListener('message', function(e) {
        var msg = e.data.split(" ");
        var cmd = msg.shift();
        if (cmd == "event") 
        {
            var id = msg.shift();
            var dataString = msg.join(" ");
            var data = JSON.parse(dataString);
    
            Orbited.singleton.XSDR.receiveCbs[id](data)
        }
        if (cmd == "queues")
        {
            var id = msg.shift();
            var queue = Orbited.singleton.XSDR.queues[id];
            if (queue.length > 0) {
                var data = queue.shift();
                e.source.postMessage(JSON.stringify(data), e.origin);
            }
        }
    }, false
    );
}

Orbited.XSDR.prototype.logger = Orbited.getLogger("Orbited.XSDR");
Orbited.singleton.XSDRBridgeLogger = Orbited.getLogger('XSDRBridge');

/* Comet Transports!
 */

Orbited.CometTransports.XHRStream = function() {
    var self = this;
    self.name = 'xhrstream'
    var url = null;
    var xhr = null;
    var ackId = null;
    var offset = 0;
    var heartbeatTimer = null;
    var retryTimer = null;
    var buffer = ""
    var retryInterval = 50
    self.readyState = 0
    self.onReadFrame = function(frame) {}
    self.onread = function(packet) { self.onReadFrame(packet); }
    self.onclose = function() { }

    self.close = function() {
        if (self.readyState == 2) {
            return
        }
        if (xhr != null && (xhr.readyState > 1 || xhr.readyState < 4)) {
            xhr.onreadystatechange = function() { }
            xhr.abort()
            xhr = null;
        }
        self.readyState = 2
        window.clearTimeout(heartbeatTimer);
        window.clearTimeout(retryTimer);
        self.onclose();
    }

    self.connect = function(_url) {
        if (self.readyState == 1) {
            throw new Error("Already Connected")
        }
        url = new Orbited.URL(_url)
        if (xhr == null) {
            if (url.isSameDomain(location.href)) {
                xhr = createXHR();
            }
            else {
                xhr = new Orbited.XSDR();
            }
        }
        url.path += '/xhrstream'
//        url.setQsParameter('transport', 'xhrstream')
        self.readyState = 1
        open()
    }
    var open = function() {
        try {
            if (typeof(ackId) == "number") {
                url.setQsParameter('ack', ackId)
            }
            if (typeof(xhr)== "undefined" || xhr == null) {
                throw new Error("how did this happen?");
            }
            if (Orbited.settings.enableFFPrivleges) {
                try { 
                    netscape.security.PrivilegeManager.enablePrivilege('UniversalBrowserRead'); 
                } catch (ex) { } 
            }        
            
            xhr.open('GET', url.render(), true)
            xhr.onreadystatechange = function() {
;;;             self.logger.debug(xhr.readyState);
                if (self.readyState == 2) { 
                    return
                }
                switch(xhr.readyState) {
                    case 2:
                        // If we can't get the status, then we didn't actually
                        // get a valid xhr response -- we got a network error
                        try {
                            var status = xhr.status
                        }
                        catch(e) {
                            return
                        }
                        // If we got a 200, then we're in business
                        if (status == 200) {
                            try {
                                heartbeatTimer = window.setTimeout(heartbeatTimeout, Orbited.settings.HEARTBEAT_TIMEOUT);
                            }
                            catch(e) {
//                               Happens after navigation
                                 self.close()
                                 return
                            }
                            var testtimer = heartbeatTimer;
                        }
                        // Otherwise, case 4 should handle the reconnect,
                        // so do nothing here.
                        break;
                    case 3:
                        // If we can't get the status, then we didn't actually
                        // get a valid xhr response -- we got a network error
                        try {
                            var status = xhr.status
                        }
                        catch(e) {
                            return
                        }
                        // We successfully established a connection, so put the
                        // retryInterval back to a short value
                        if (status == 200) {
                            retryInterval = 50;
                            process();
                        }
                        break;
                    case 4:
                        var doReconnect = true;
                        try {
                            if (xhr.status === null) {
                                doReconnect = true;
                            }
                            else {
                                doReconnect = false;
                            }
                        }
                        catch(e) {
                        }
                        if (doReconnect) {
                            // Expoential backoff: Every time we fail to 
                            // reconnect, double the interval.
                            // TODO cap the max value. 
                            retryInterval *= 2;
//                            self.logger.debug('retryInterval', retryInterval)
                            window.clearTimeout(heartbeatTimer);
                            retryTimer = window.setTimeout(reconnect, retryInterval)
                            return;
                        }
                        switch(xhr.status) {
                            case 200:
//                                alert('finished, call process');
//                               if (typeof(Orbited) == "undefined") {
//                                    alert('must have reloaded')
//                                    break
//                                }
//                                alert('a');
//                                alert('stream over ' +  typeof(console) + ' ' + typeof(Orbited) + ' ' + Orbited + ' ...');
                                process();
                                offset = 0;
                                setTimeout(open, 0)
                                window.clearTimeout(heartbeatTimer);
                                break;
                            case 404:
                                self.close();
                            default:
                                self.close();
                        }
                }
            }
            xhr.send(null);
        }
        catch(e) {
            self.close()
        }
    }

    var reconnect = function() {
//        self.logger.debug('reconnect...')
        if (xhr.readyState < 4 && xhr.readyState > 0) {
            xhr.onreadystatechange = function() {
                if (xhr.readyState == 4) {
                    reconnect();
                }
            }
//            self.logger.debug('do abort..')
            xhr.abort();
            window.clearTimeout(heartbeatTimer);
        }
        else {
;;;         self.logger.debug('reconnect do open')
            offset = 0;
            setTimeout(open, 0)
        }
    }
    // 12,ab011,hello world
    var commaPos = -1;
    var argEnd = null;
    var frame = []
    var process = function() {
        var stream = xhr.responseText;
        receivedHeartbeat()

        // ignore leading whitespace, such as at the start of an xhr stream
        while (stream[offset] == ' ') {
            offset += 1
        }
        // ignore leading whitespace, such as at the start of an xhr stream
        while (stream[offset] == 'x') {
            offset += 1
        }

        var k = 0
        while (true) {
            k += 1
            if (k > 2000) {
                throw new Error("Borked XHRStream transport");
                return
            }
            if (commaPos == -1) {
                commaPos = stream.indexOf(',', offset)
            }
            if (commaPos == -1) {
                return
            }
            if (argEnd == null) {
                argSize = parseInt(stream.slice(offset+1, commaPos))
                argEnd = commaPos +1 + argSize
            }
            
            if (stream.length < argEnd) {
                return
            }
            var data = stream.slice(commaPos+1, argEnd)
            frame.push(data)
            var isLast = (stream.charAt(offset) == '0')
            offset = argEnd;
            argEnd = null;
            commaPos = -1
            if (isLast) {
                var frameCopy = frame
                frame = []
                receivedPacket(frameCopy)                
            }
        } 

    }
    var receivedHeartbeat = function() {
        window.clearTimeout(heartbeatTimer);
;;;     self.logger.debug('clearing heartbeatTimer', heartbeatTimer)
        try {
            heartbeatTimer = window.setTimeout(function() { 
;;;         self.logger.debug('timer', testtimer, 'did it'); 
                heartbeatTimeout();
            }, Orbited.settings.HEARTBEAT_TIMEOUT);
        }
        catch(e) {
            
            return
        }
        var testtimer = heartbeatTimer;

;;;     self.logger.debug('heartbeatTimer is now', heartbeatTimer)
    }
    var heartbeatTimeout = function() {
;;;     self.logger.debug('heartbeat timeout... reconnect')
        reconnect();
    }
    var receivedPacket = function(args) {
        var testAckId = parseInt(args[0])
        if (!isNaN(testAckId)) {
            ackId = testAckId
        }
        var packet = {
            id: testAckId,
            name: args[1],
            data: args[2]
        }
        // TODO: shouldn't we put this in a window.setTimeout so that user
        //       code won't mess up our code?
        self.onread(packet)
    }
}
Orbited.CometTransports.XHRStream.prototype.logger = Orbited.getLogger("Orbited.CometTransports.XHRStream");
// XHRStream supported browsers
Orbited.CometTransports.XHRStream.firefox = 1.0
Orbited.CometTransports.XHRStream.firefox2 = 1.0
Orbited.CometTransports.XHRStream.firefox3 = 1.0
Orbited.CometTransports.XHRStream.safari2 = 1.0
Orbited.CometTransports.XHRStream.safari3 = 1.0





Orbited.CometTransports.LongPoll = function() {
    var self = this;
    self.name = 'longpoll'
    var url = null;
    var xhr = null;
    var ackId = null;
    var retryTimer = null;
    var buffer = ""
    var retryInterval = 50
    self.readyState = 0
    self.onReadFrame = function(frame) {}
    self.onclose = function() { }

    self.close = function() {
        if (self.readyState == 2) {
            return
        }
        if (xhr != null && (xhr.readyState > 1 || xhr.readyState < 4)) {
            xhr.onreadystatechange = function() { }
            xhr.abort()
            xhr = null;
        }
        self.readyState = 2
        window.clearTimeout(retryTimer);
        self.onclose();
    }

    self.connect = function(_url) {
        if (self.readyState == 1) {
            throw new Error("Already Connected")
        }
        url = new Orbited.URL(_url)
        if (xhr == null) {
            if (url.isSameDomain(location.href)) {
                xhr = createXHR();
            }
            else {
                xhr = new Orbited.XSDR();
            }
        }
        url.path += '/longpoll'
//        url.setQsParameter('transport', 'xhrstream')
        self.readyState = 1
        open()
    }
    var open = function() {
        try {
            if (typeof(ackId) == "number") {
                url.setQsParameter('ack', ackId)
            }
            if (typeof(xhr)== "undefined" || xhr == null) {
                throw new Error("how did this happen?");
            }
            
            if (Orbited.settings.enableFFPrivleges) {
                try { 
                    netscape.security.PrivilegeManager.enablePrivilege('UniversalBrowserRead'); 
                } catch (ex) { } 
            }        
            xhr.open('GET', url.render(), true)
            xhr.onreadystatechange = function() {
;;;             self.logger.debug('readystate', xhr.readyState)
                switch(xhr.readyState) {
                   case 4:
                        try {
                            xhr.status
                        }
                        catch(e) {
                            // Expoential backoff: Every time we fail to 
                            // reconnect, double the interval.
                            // TODO cap the max value. 
;;;                         self.logger.debug("start reconnect Timer (couldn't access xhr.status)")
                            retryInterval *= 2;
                            window.setTimeout(reconnect, retryInterval)
                            return;
                        }
                        switch(xhr.status) {
                            case 200:
                                process();
;;;                         self.logger.debug("completed request, reconnect immediately")
                                setTimeout(open, 0)
                                break;
                            case 404:
                                self.close();
                                break
                            case null:
                                // NOTE: for the XSDR case: 
                                // (we can always get status, but maybe its null)
                                retryInterval *= 2;
;;;                         self.logger.debug("start reconnect Timer (null xhr.status)")
                                window.setTimeout(reconnect, retryInterval)
                                break;                                
                            default:
                                // TODO: do we want to retry here?
;;;                         self.logger.debug("something broke, xhr.status=", xhr.status)
                                self.close();
                                break
                        }
                }
            }
            xhr.send(null);
        }
        catch(e) {
            self.close()
        }
    }

    var reconnect = function() {
;;;     self.logger.debug('reconnect...')
        if (xhr.readyState < 4 && xhr.readyState > 0) {
            xhr.onreadystatechange = function() {
                if (xhr.readyState == 4) {
                    reconnect();
                }
            }
;;;         self.logger.debug('do abort..')
            xhr.abort();
            window.clearTimeout(heartbeatTimer);            
        }
        else {
;;;         self.logger.debug('reconnect do open')
            offset = 0;
            setTimeout(open, 0)
        }
    }
    // 12,ab011,hello world
    var process = function() {
        var commaPos = -1;
        var argEnd = null;
        var argSize;
        var frame = []
        var stream = xhr.responseText;
        var offset = 0


        var k = 0
        while (true) {
            k += 1
            if (k > 2000) {
                throw new Error("Borked XHRStream transport");
                return
            }
            if (commaPos == -1) {
                commaPos = stream.indexOf(',', offset)
            }
            if (commaPos == -1) {
;;;             self.logger.debug('no more commas. offset:', offset, 'stream.length:', stream.length);
                return
            }
            if (argEnd == null) {
                argSize = parseInt(stream.slice(offset+1, commaPos))
                argEnd = commaPos +1 + argSize
            }
;;;         self.logger.assert(true);
/*            if (stream.length < argEnd) {
;;;             self.logger.debug('how did we get here? stream.length:', stream.length, 'argEnd:', argEnd, 'offset:', offset)
                return
            }*/
            var data = stream.slice(commaPos+1, argEnd)
;;;         self.logger.assert(data.length == argSize, 'argSize:', argSize, 'data.length', data.length)
            if (data.length != argSize) {
                DEBUGDATA = stream
            }
            frame.push(data)
            var isLast = (stream.charAt(offset) == '0')
            offset = argEnd;
            argEnd = null;
            commaPos = -1
            if (isLast) {
                var frameCopy = frame
                frame = []
                receivedPacket(frameCopy)                
            }
        } 

    }
    var receivedPacket = function(args) {
        var testAckId = parseInt(args[0])
;;;     self.logger.debug('args', args)
        if (!isNaN(testAckId)) {
            ackId = testAckId
        }
;;;     self.logger.debug('testAckId', testAckId, 'ackId', ackId)
        var packet = {
            id: testAckId,
            name: args[1],
            data: args[2]
        }
        // TODO: shouldn't we put this in a window.setTimeout so that user
        //       code won't mess up our code?
        self.onReadFrame(packet)
    }
}
Orbited.CometTransports.LongPoll.prototype.logger = Orbited.getLogger("Orbited.CometTransports.LongPoll");
// LongPoll supported browsers
/*
Orbited.CometTransports.LongPoll.firefox = 0.9
Orbited.CometTransports.LongPoll.firefox2 = 0.9
Orbited.CometTransports.LongPoll.firefox3 = 0.9
Orbited.CometTransports.LongPoll.safari2 = 0.9
Orbited.CometTransports.LongPoll.safari3 = 0.9
Orbited.CometTransports.LongPoll.opera = 0.9
Orbited.CometTransports.LongPoll.ie = 0.9
*/



Orbited.CometTransports.Poll = function() {
    var self = this;
    self.name = 'poll'
    var url = null;
    var xhr = null;
    var ackId = null;
    var retryTimer = null;
    var buffer = ""
    var baseRetryInterval = Orbited.settings.POLL_INTERVAL
    var retryInterval = baseRetryInterval;
    self.readyState = 0
    self.onReadFrame = function(frame) {}
    self.onclose = function() { }

    self.close = function() {
        if (self.readyState == 2) {
            return
        }
        if (xhr != null && (xhr.readyState > 1 || xhr.readyState < 4)) {
            xhr.onreadystatechange = function() { }
            xhr.abort()
            xhr = null;
        }
        self.readyState = 2
        window.clearTimeout(retryTimer);
        self.onclose();
    }

    self.connect = function(_url) {
        if (self.readyState == 1) {
            throw new Error("Already Connected")
        }
        url = new Orbited.URL(_url)
        if (xhr == null) {
            if (url.isSameDomain(location.href)) {
                xhr = createXHR();
            }
            else {
                xhr = new Orbited.XSDR();
            }
        }
        url.path += '/poll'
//        url.setQsParameter('transport', 'xhrstream')
        self.readyState = 1
        open()
    }
    var open = function() {
        try {
            if (typeof(ackId) == "number") {
                url.setQsParameter('ack', ackId)
            }
            if (typeof(xhr)== "undefined" || xhr == null) {
                throw new Error("how did this happen?");
            }
            
            if (Orbited.settings.enableFFPrivleges) {
                try { 
                    netscape.security.PrivilegeManager.enablePrivilege('UniversalBrowserRead'); 
                } catch (ex) { } 
            }        
            xhr.open('GET', url.render(), true)
            xhr.onreadystatechange = function() {
                switch(xhr.readyState) {
                   case 4:
                        try {
                            xhr.status
                        }
                        catch(e) {
                            // Expoential backoff: Every time we fail to 
                            // reconnect, double the interval.
                            // TODO cap the max value. 
                            retryInterval *= 2;
                            window.setTimeout(reconnect, retryInterval)
                            return;
                        }
                        switch(xhr.status) {
                            case 200:
                                retryInterval = baseRetryInterval;
                                process();
                                setTimeout(open, retryInterval)
                                break;
                            case 404:
                                self.close();
                                break
                            case null:
                                // NOTE: for the XSDR case: Long
                                // (we can always get status, but maybe its null)
                                retryInterval *= 2;
                                window.setTimeout(reconnect, retryInterval)
                                break;                                
                            default:
                                // TODO: do we want to retry here?
                                self.close();
                                break
                        }
                }
            }
            xhr.send(null);
        }
        catch(e) {
            self.close()
        }
    }

    var reconnect = function() {
;;;     self.logger.debug('reconnect...')
        if (xhr.readyState < 4 && xhr.readyState > 0) {
            xhr.onreadystatechange = function() {
                if (xhr.readyState == 4) {
                    reconnect();
                }
            }
;;;         self.logger.debug('do abort..')
            xhr.abort();
            window.clearTimeout(heartbeatTimer);            
        }
        else {
;;;         self.logger.debug('reconnect do open')
            offset = 0;
            setTimeout(open, 0)
        }
    }
    // 12,ab011,hello world
    var process = function() {
        var commaPos = -1;
        var argEnd = null;
        var argSize;
        var frame = []
        var stream = xhr.responseText;
        var offset = 0


        var k = 0
        while (true) {
            k += 1
            if (k > 2000) {
                throw new Error("Borked XHRStream transport");
                return
            }
            if (commaPos == -1) {
                commaPos = stream.indexOf(',', offset)
            }
            if (commaPos == -1) {
;;;             self.logger.debug('no more commas. offset:', offset, 'stream.length:', stream.length);
                return
            }
            if (argEnd == null) {
                argSize = parseInt(stream.slice(offset+1, commaPos))
                argEnd = commaPos +1 + argSize
            }
;;;         self.logger.assert(true);
/*            if (stream.length < argEnd) {
;;;             self.logger.debug('how did we get here? stream.length:', stream.length, 'argEnd:', argEnd, 'offset:', offset)
                return
            }*/
            var data = stream.slice(commaPos+1, argEnd)
;;;         self.logger.assert(data.length == argSize, 'argSize:', argSize, 'data.length', data.length)
            if (data.length != argSize) {
                DEBUGDATA = stream
            }
            frame.push(data)
            var isLast = (stream.charAt(offset) == '0')
            offset = argEnd;
            argEnd = null;
            commaPos = -1
            if (isLast) {
                var frameCopy = frame
                frame = []
                receivedPacket(frameCopy)                
            }
        } 

    }
    var receivedPacket = function(args) {
        var testAckId = parseInt(args[0])
;;;     self.logger.debug('args', args)
        if (!isNaN(testAckId)) {
            ackId = testAckId
        }
;;;     self.logger.debug('testAckId', testAckId, 'ackId', ackId)
        var packet = {
            id: testAckId,
            name: args[1],
            data: args[2]
        }
        // TODO: shouldn't we put this in a window.setTimeout so that user
        //       code won't mess up our code?
        self.onReadFrame(packet)
    }
}
Orbited.CometTransports.Poll.prototype.logger = Orbited.getLogger("Orbited.CometTransports.Poll");

// Poll supported browsers
/*Orbited.CometTransports.Poll.firefox = 0.5
Orbited.CometTransports.Poll.opera = 0.5
Orbited.CometTransports.Poll.ie = 0.5
*/





Orbited.CometTransports.HTMLFile = function() {
    var self = this;
    self.name = 'htmlfile'
    var id = ++Orbited.singleton.HTMLFile.i;
    Orbited.singleton.HTMLFile.instances[id] = self;
    var htmlfile = null
    var ifr = null;
    var url = null;
    var restartUrl = null;
    var restartTimer = null;
    // TODO: move constant to Orbited.settings
    var baseRestartTimeout = 2000;
    var restartTimeout = baseRestartTimeout;
    self.onReadFrame = function(frame) {}
    self.onread = function(packet) { self.onReadFrame(packet); }
    self.onclose = function() { }
    self.connect = function(_url) {
        if (self.readyState == 1) {
            throw new Error("Already Connected")
        }
        self.logger.debug('self.connect', _url)
        url = new Orbited.URL(_url)
        url.path += '/htmlfile'
        url.setQsParameter('frameID', id.toString())
        self.readyState = 1
        doOpen(url.render())
    }

    var doOpenIfr = function() {
        
        var ifr = document.createElement('iframe')
        ifr.src = url.render()
        document.body.appendChild(ifr)
    }

    var doOpen = function(_url) {
;;;     self.logger.debug('doOpen', _url)
        htmlfile = new ActiveXObject('htmlfile'); // magical microsoft object
        htmlfile.open();
//        htmlfile.write('<html><script>' + 'document.domain="' + document.domain + '";' + '</script></html>');
        htmlfile.write('<html></html>');
        htmlfile.parentWindow.Orbited = Orbited;
        htmlfile.close();
        var iframe_div = htmlfile.createElement('div');
        htmlfile.body.appendChild(iframe_div);
        ifr = htmlfile.createElement('iframe');
        iframe_div.appendChild(ifr);
        ifr.src = _url;
        restartUrl = _url;
        restartTimer = window.setTimeout(reconnect, restartTimeout)
    }
    
    // TODO: expose this in another way besides the public api
    self.restartingStream = function(_url) {
        restartUrl = _url;
        restartTimer = window.setTimeout(reconnect, restartTimeout)
    }
    
    var reconnect = function() {
;;;     self.logger.debug('doing reconnect... ' + restartTimeout);
        restartTimeout*=2;
        ifr.src = restartUrl;
        restartTimer = window.setTimeout(reconnect, restartTimeout)        
    }

    self.streamStarted = function() {
;;;     self.logger.debug('stream started..');
        window.clearTimeout(restartTimer);
        restartTimer = null;
        restartTimeout = baseRestartTimeout;
    }
    
    self.streamClosed = function() {
;;;     self.logger.debug('stream closed!');
        window.clearTimeout(restartTimer);
        self.close()
    }

    self.receive = function(id, name, data) {
        packet = {
            id: id,
            name: name,
            data: data
        }
        self.onread(packet)
    }
    
    self.close = function() {
        if (self.readyState == 2) {
            return
        }
;;;     self.logger.debug('close called, clearing timer');
        window.clearTimeout(restartTimer);
        self.readyState = 2
        ifr.src = 'about:blank'
        htmlfile = null;
        CollectGarbage();
        self.onclose();
    }

}
Orbited.CometTransports.HTMLFile.prototype.logger = Orbited.getLogger("Orbited.CometTransports.HTMLFile");
// HTMLFile supported browsers
Orbited.CometTransports.HTMLFile.ie = 1.0;
Orbited.singleton.HTMLFile = {
    i: 0,
    instances: {}
}




Orbited.CometTransports.SSE = function() {
    var self = this;
    self.name = 'sse'
    self.onReadFrame = function(frame) {}
    self.onclose = function() { }
    self.readyState = 0;
    var heartbeatTimer = null;
    var source = null
    var url = null;
    var lastEventId = -1;

    self.close = function() {
        if (self.readyState == 2) {
            return;
        }
        // TODO: can someone test this and get back to me? (No opera at the moment)
        //     : -mcarter 7-26-08
        self.readyState = 2
        doClose();
        self.onclose();
    }

    self.connect = function(_url) {
        if (self.readyState == 1) {
            throw new Error("Already Connected")
        }
        url = new Orbited.URL(_url)
        url.path += '/sse'
        self.readyState = 1
        doOpen();
    }
    doClose = function() {
        source.removeEventSource(source.getAttribute('src'))
        source.setAttribute('src',"")
        if (opera.version() < 9.5) {
            document.body.removeChild(source)
        }
        source = null;
    }
    doOpen = function() {
/*
        if (typeof(lastEventId) == "number") {
            url.setQsParameter('ack', lastEventId)
        }
*/
        source = document.createElement("event-source");
        source.setAttribute('src', url.render());
        // NOTE: without this check opera 9.5 would make two connections.
        if (opera.version() < 9.5) {
            document.body.appendChild(source);
        }
        source.addEventListener('payload', receivePayload, false);

//        source.addEventListener('heartbeat', receiveHeartbeat, false);
        // start up the heartbeat timer...
//        receiveHeartbeat();
    }

    var receivePayload = function(event) {
        var data = eval(event.data);
        if (typeof(data) != 'undefined') {
            for (var i = 0; i < data.length; ++i) {
                var packet = data[i]
                receive(packet[0], packet[1], packet[2]);
            }
        }
    
    }
/*    var receiveHeartbeat = function() {
        window.clearTimeout(heartbeatTimer);
        heartbeatTimer = window.setTimeout(reconnect, Orbited.settings.HEARTBEAT_TIMEOUT)
    }
*/
    var receive = function(id, name, data) {
        var tempId = parseInt(id);
        if (!isNaN(tempId)) {
            // NOTE: The old application/x-dom-event-stream transport doesn't
            //       allow us to put in the lastEventId on reconnect, so we are
            //       bound to get double copies of some of the events. Therefore
            //       we are going to throw out the duplicates. Its not clear to
            //       me that this is a perfect solution.
            //       -mcarter 8-9-08
            if (tempId <= lastEventId) {
                return
            }
            lastEventId = tempId;
        }
        // NOTE: we are dispatching null-id packets. Is this correct?
        //       -mcarter 9-8-08
        packet = {
            id: id,
            name: name,
            data: data
        }
        self.onReadFrame(packet)
    }
}
Orbited.CometTransports.SSE.prototype.logger = Orbited.getLogger("Orbited.CometTransports.SSE");

Orbited.CometTransports.SSE.opera = 1.0;
Orbited.CometTransports.SSE.opera8 = 1.0;
Orbited.CometTransports.SSE.opera9 = 1.0;
Orbited.CometTransports.SSE.opera9_5 = 0.8;



/* This is an old implementation of the URL class. Jacob is cleaning it up
 * -mcarter, 7-30-08
 * 
 * Jacob is actually throwing this away and rewriting from scratch
 * -mcarter 11-14-08
 */
Orbited.URL = function(_url) {
    var self = this;
    var protocolIndex = _url.indexOf("://")
    if (protocolIndex != -1)
        self.protocol = _url.slice(0,protocolIndex)
    else
        protocolIndex = -3
    var domainIndex = _url.indexOf('/', protocolIndex+3)
    if (domainIndex == -1)
        domainIndex=_url.length
    var hashIndex = _url.indexOf("#", domainIndex)
    if (hashIndex != -1)
        self.hash = _url.slice(hashIndex+1)
    else
        hashIndex = _url.length
    var uri = _url.slice(domainIndex, hashIndex)
    var qsIndex = uri.indexOf('?')
    if (qsIndex == -1)
        qsIndex=uri.length
    self.path = uri.slice(0, qsIndex)
    self.qs = uri.slice(qsIndex+1)
    if (self.path == "")
        self.path = "/"
    var domain = _url.slice(protocolIndex+3, domainIndex)
    var portIndex = domain.indexOf(":")
    if (portIndex == -1) {
        self.port = 80
        portIndex = domain.length
    }
    else {
        self.port = parseInt(domain.slice(portIndex+1))
    }
    if (isNaN(this.port))
        throw new Error("Invalid _url")
    self.domain = domain.slice(0, portIndex)

    self.render = function() {
        var output = ""
        if (typeof(self.protocol) != "undefined")
            output += self.protocol + "://"
        output += self.domain
        if (self.port != 80 && typeof(self.port) != "undefined" && self.port != null)
            if (typeof(self.port) != "string" || self.port.length > 0)
                output += ":" + self.port
        if (typeof(self.path) == "undefined" || self.path == null)
            output += '/'
        else
            output += self.path
        if (self.qs.length > 0)
            output += '?' + self.qs
        if (typeof(self.hash) != "undefined" && self.hash.length > 0)
            output += "#" + self.hash
        return output
    }
    self.isSameDomain = function(_url) {
        _url = new Orbited.URL(_url)

        if (!_url.domain || !self.domain)
            return true
        return (_url.port == self.port && _url.domain == self.domain)
    }
    self.isSameParentDomain = function(_url) {
        _url = new Orbited.URL(_url)
        if (_url.domain == self.domain) {
            return true;
        }
        var orig_domain = _url.domain;
        var parts = document.domain.split('.')
//        var orig_domain = document.domain
        for (var i = 0; i < parts.length-1; ++i) {
            var new_domain = parts.slice(i).join(".")
            if (orig_domain == new_domain)
                return true;
        }
        return false
    }

    var decodeQs = function(qs) {
    //    alert('a')
        if (qs.indexOf('=') == -1) return {}
        var result = {}
        var chunks = qs.split('&')
        for (var i = 0; i < chunks.length; ++i) {
            var cur = chunks[i]
            var pieces = cur.split('=')
            result[pieces[0]] = pieces[1]
        }
        return result
    }
    var encodeQs = function(o) {
            var output = ""
            for (var key in o)
                output += "&" + key + "=" + o[key]
            return output.slice(1)
        }
    self.setQsParameter = function(key, val) {
        var curQsObj = decodeQs(self.qs)
        curQsObj[key] = val
        self.qs = encodeQs(curQsObj)
    }

    self.mergeQs = function(qs) {
        var newQsObj = decodeQs(qs)
        for (key in newQsObj) {
            curQsObj[key] = newQsObj[key]
        }
    }
    self.removeQsParameter = function(key) {
        var curQsObj = decodeQs(self.qs)
        delete curQsObj[key]
        self.qs = encodeQs(curQsObj)
    }

    self.merge = function(targetUrl) {
        if (typeof(self.protocol) != "undefined" && self.protocol.length > 0) {
            self.protocol = targetUrl.protocol
        }
        if (targetUrl.domain.length > 0) {
            self.domain = targetUrl.domain
            self.port = targetUrl.port
        }
        self.path = targetUrl.path
        self.qs = targetUrl.qs
        self.hash = targetUrl.hash
    }

}

Orbited.utf8 = {}
Orbited.utf8.decode = function(s) {    
    var ret = [];
    var j = 0
    function pad6(str) {
        while(str.length < 6) { str = "0" + str; } return str;
    }
    for (var i=0; i < s.length; i++) {
        if ((s.charCodeAt(i) & 0xf8) == 0xf0) {
            if (s.length -j < 4) { break }
            j+=4;
            ret.push(String.fromCharCode(parseInt(
                         (s.charCodeAt(i) & 0x07).toString(2) +
                  pad6((s.charCodeAt(i+1) & 0x3f).toString(2)) +
                  pad6((s.charCodeAt(i+2) & 0x3f).toString(2)) +
                  pad6((s.charCodeAt(i+3) & 0x3f).toString(2))
                , 2)));
            i += 3;
        } else if ((s.charCodeAt(i) & 0xf0) == 0xe0) {
            if (s.length -j < 3) { break }
            j+=3;
            ret.push(String.fromCharCode(parseInt(
                  (s.charCodeAt(i) & 0x0f).toString(2) +
                  pad6((s.charCodeAt(i+1) & 0x3f).toString(2)) +
                  pad6((s.charCodeAt(i+2) & 0x3f).toString(2))
                , 2)));
            i += 2;
        } else if ((s.charCodeAt(i) & 0xe0) == 0xc0) {
            j+=2
            if (s.length -j < 2) { break }
                ret.push(String.fromCharCode(parseInt(
                       (s.charCodeAt(i) & 0x1f).toString(2) +
                pad6((s.charCodeAt(i+1) & 0x3f).toString(2), 6)
                , 2)));
            i += 1;
        } else {
            j+=1
            ret.push(String.fromCharCode(s.charCodeAt(i)));
        }
    }
    return [ret.join(""), j];
}

// TODO rename to encode
Orbited.utf8.encode = function(text) {
    var ret = [];
    
    function pad(str, len) {
        while(str.length < len) { str = "0" + str; } return str;
    }
    var e = String.fromCharCode
    for (var i=0; i < text.length; i++) {
        var chr = text.charCodeAt(i);
        if (chr <= 0x7F) {
            ret.push(e(chr));
        } else if(chr <= 0x7FF) {
            var binary = pad(chr.toString(2), 11);
            ret.push(e(parseInt("110"   + binary.substr(0,5), 2)));
            ret.push(e(parseInt("10"    + binary.substr(5,6), 2)));
        } else if(chr <= 0xFFFF) {
            var binary = pad(chr.toString(2), 16);
            ret.push(e(parseInt("1110"  + binary.substr(0,4), 2)));
            ret.push(e(parseInt("10"    + binary.substr(4,6), 2)));
            ret.push(e(parseInt("10"    + binary.substr(10,6), 2)));
        } else if(chr <= 0x10FFFF) {
            var binary = pad(chr.toString(2), 21);
            ret.push(e(parseInt("11110" + binary.substr(0,3), 2)));
            ret.push(e(parseInt("10"    + binary.substr(3,6), 2)));
            ret.push(e(parseInt("10"    + binary.substr(9,6), 2)));
            ret.push(e(parseInt("10"    + binary.substr(15,6), 2)));
        }
    }
    return ret.join("");
}

if (!this.JSON) {

// Create a JSON object only if one does not already exist. We create the
// object in a closure to avoid creating global variables.

    JSON = function () {

        function f(n) {
            // Format integers to have at least two digits.
            return n < 10 ? '0' + n : n;
        }

        Date.prototype.toJSON = function (key) {

            return this.getUTCFullYear()   + '-' +
                 f(this.getUTCMonth() + 1) + '-' +
                 f(this.getUTCDate())      + 'T' +
                 f(this.getUTCHours())     + ':' +
                 f(this.getUTCMinutes())   + ':' +
                 f(this.getUTCSeconds())   + 'Z';
        };

        String.prototype.toJSON =
        Number.prototype.toJSON =
        Boolean.prototype.toJSON = function (key) {
            return this.valueOf();
        };

        var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
            escapeable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
            gap,
            indent,
            meta = {    // table of character substitutions
                '\b': '\\b',
                '\t': '\\t',
                '\n': '\\n',
                '\f': '\\f',
                '\r': '\\r',
                '"' : '\\"',
                '\\': '\\\\'
            },
            rep;


        function quote(string) {

// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.

            escapeable.lastIndex = 0;
            return escapeable.test(string) ?
                '"' + string.replace(escapeable, function (a) {
                    var c = meta[a];
                    if (typeof c === 'string') {
                        return c;
                    }
                    return '\\u' + ('0000' +
                            (+(a.charCodeAt(0))).toString(16)).slice(-4);
                }) + '"' :
                '"' + string + '"';
        }


        function str(key, holder) {

// Produce a string from holder[key].

            var i,          // The loop counter.
                k,          // The member key.
                v,          // The member value.
                length,
                mind = gap,
                partial,
                value = holder[key];

// If the value has a toJSON method, call it to obtain a replacement value.

            if (value && typeof value === 'object' &&
                    typeof value.toJSON === 'function') {
                value = value.toJSON(key);
            }

// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.

            if (typeof rep === 'function') {
                value = rep.call(holder, key, value);
            }

// What happens next depends on the value's type.

            switch (typeof value) {
            case 'string':
                return quote(value);

            case 'number':

// JSON numbers must be finite. Encode non-finite numbers as null.

                return isFinite(value) ? String(value) : 'null';

            case 'boolean':
            case 'null':

// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.

                return String(value);

// If the type is 'object', we might be dealing with an object or an array or
// null.

            case 'object':

// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.

                if (!value) {
                    return 'null';
                }

// Make an array to hold the partial results of stringifying this object value.

                gap += indent;
                partial = [];

// If the object has a dontEnum length property, we'll treat it as an array.

                if (typeof value.length === 'number' &&
                        !(value.propertyIsEnumerable('length'))) {

// The object is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.

                    length = value.length;
                    for (i = 0; i < length; i += 1) {
                        partial[i] = str(i, value) || 'null';
                    }

// Join all of the elements together, separated with commas, and wrap them in
// brackets.

                    v = partial.length === 0 ? '[]' :
                        gap ? '[\n' + gap +
                                partial.join(',\n' + gap) + '\n' +
                                    mind + ']' :
                              '[' + partial.join(',') + ']';
                    gap = mind;
                    return v;
                }

// If the replacer is an array, use it to select the members to be stringified.

                if (rep && typeof rep === 'object') {
                    length = rep.length;
                    for (i = 0; i < length; i += 1) {
                        k = rep[i];
                        if (typeof k === 'string') {
                            v = str(k, value);
                            if (v) {
                                partial.push(quote(k) + (gap ? ': ' : ':') + v);
                            }
                        }
                    }
                } else {

// Otherwise, iterate through all of the keys in the object.

                    for (k in value) {
                        if (Object.hasOwnProperty.call(value, k)) {
                            v = str(k, value);
                            if (v) {
                                partial.push(quote(k) + (gap ? ': ' : ':') + v);
                            }
                        }
                    }
                }

// Join all of the member texts together, separated with commas,
// and wrap them in braces.

                v = partial.length === 0 ? '{}' :
                    gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
                            mind + '}' : '{' + partial.join(',') + '}';
                gap = mind;
                return v;
            }
        }

// Return the JSON object containing the stringify and parse methods.

        return {
            stringify: function (value, replacer, space) {

// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.

                var i;
                gap = '';
                indent = '';

// If the space parameter is a number, make an indent string containing that
// many spaces.

                if (typeof space === 'number') {
                    for (i = 0; i < space; i += 1) {
                        indent += ' ';
                    }

// If the space parameter is a string, it will be used as the indent string.

                } else if (typeof space === 'string') {
                    indent = space;
                }

// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.

                rep = replacer;
                if (replacer && typeof replacer !== 'function' &&
                        (typeof replacer !== 'object' ||
                         typeof replacer.length !== 'number')) {
                    throw new Error('JSON.stringify');
                }

// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.

                return str('', {'': value});
            },


            parse: function (text, reviver) {

// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.

                var j;

                function walk(holder, key) {

// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.

                    var k, v, value = holder[key];
                    if (value && typeof value === 'object') {
                        for (k in value) {
                            if (Object.hasOwnProperty.call(value, k)) {
                                v = walk(value, k);
                                if (v !== undefined) {
                                    value[k] = v;
                                } else {
                                    delete value[k];
                                }
                            }
                        }
                    }
                    return reviver.call(holder, key, value);
                }


// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.

                cx.lastIndex = 0;
                if (cx.test(text)) {
                    text = text.replace(cx, function (a) {
                        return '\\u' + ('0000' +
                                (+(a.charCodeAt(0))).toString(16)).slice(-4);
                    });
                }

// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.

// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

                if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.

                    j = eval('(' + text + ')');

// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.

                    return typeof reviver === 'function' ?
                        walk({'': j}, '') : j;
                }

// If the text is not JSON parseable, then a SyntaxError is thrown.

                throw new SyntaxError('JSON.parse');
            }
        };
    }();
}
Orbited.JSON = JSON;


})();


// Try to auto detect the Orbited port and hostname
(function() {
try {
    var scripts = document.getElementsByTagName('script')
    for (var i = 0; i < scripts.length; ++i) {
        var script = scripts[0]
        if (script.src.match('/static/Orbited\.js$')) {
            var url = new Orbited.URL(script.src)
            if (url.render().indexOf('http') != 0) {
                var url = new Orbited.URL(window.location.toString());
            }
            Orbited.settings.hostname = url.domain
            Orbited.settings.port = url.port
            break
        }
    }
} catch(e) { 
//    alert("Error! " + e.name + ": " + e.message);
}
})()
