You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by pa...@apache.org on 2013/10/28 09:16:54 UTC

[38/47] Upgrade Atmosphere to 2.0.0

http://git-wip-us.apache.org/repos/asf/wicket/blob/a13dec06/wicket-experimental/wicket-atmosphere/src/main/java/org/apache/wicket/atmosphere/jquery.atmosphere.js
----------------------------------------------------------------------
diff --git a/wicket-experimental/wicket-atmosphere/src/main/java/org/apache/wicket/atmosphere/jquery.atmosphere.js b/wicket-experimental/wicket-atmosphere/src/main/java/org/apache/wicket/atmosphere/jquery.atmosphere.js
index 2b17666..b909609 100644
--- a/wicket-experimental/wicket-atmosphere/src/main/java/org/apache/wicket/atmosphere/jquery.atmosphere.js
+++ b/wicket-experimental/wicket-atmosphere/src/main/java/org/apache/wicket/atmosphere/jquery.atmosphere.js
@@ -28,19 +28,24 @@
 /**
  * Official documentation of this library: https://github.com/Atmosphere/atmosphere/wiki/jQuery.atmosphere.js-API
  */
-jQuery.atmosphere = function() {
-    jQuery(window).bind("unload.atmosphere", function() {
+jQuery.atmosphere = function () {
+
+    jQuery(window).bind("unload.atmosphere", function () {
+        jQuery.atmosphere.unsubscribe();
+    });
+
+    jQuery(window).bind("offline", function () {
         jQuery.atmosphere.unsubscribe();
     });
 
     // Prevent ESC to kill the connection from Firefox.
-    jQuery(window).keypress(function(e){
-        if(e.keyCode == 27){
+    jQuery(window).keypress(function (e) {
+        if (e.keyCode === 27) {
             e.preventDefault();
         }
     });
 
-    var parseHeaders = function(headerString) {
+    var parseHeaders = function (headerString) {
         var match, rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, headers = {};
         while (match = rheaders.exec(headerString)) {
             headers[match[1]] = match[2];
@@ -49,106 +54,119 @@ jQuery.atmosphere = function() {
     };
 
     return {
-        version : "1.0.13",
-        requests : [],
-        callbacks : [],
+        version: "2.0.3-jquery",
+        requests: [],
+        callbacks: [],
 
-        onError : function(response) {
+        onError: function (response) {
+        },
+        onClose: function (response) {
         },
-        onClose : function(response) {
+        onOpen: function (response) {
         },
-        onOpen : function(response) {
+        onMessage: function (response) {
         },
-        onMessage : function(response) {
+        onReconnect: function (request, response) {
         },
-        onReconnect : function(request, response) {
+        onMessagePublished: function (response) {
         },
-        onMessagePublished : function(response) {
+        onTransportFailure: function (errorMessage, _request) {
         },
-        onTransportFailure : function (reason, request) {
+        onLocalMessage: function (response) {
         },
-        onLocalMessage : function (response) {
+        onClientTimeout: function(request){
+        },
+        onFailureToReconnect: function (request, response) {
         },
 
-        AtmosphereRequest : function(options) {
+        AtmosphereRequest: function (options) {
 
             /**
              * {Object} Request parameters.
+             *
              * @private
              */
             var _request = {
                 timeout: 300000,
                 method: 'GET',
                 headers: {},
-                contentType : '',
+                contentType: '',
                 callback: null,
-                url : '',
-                data : '',
-                suspend : true,
-                maxRequest : -1,
-                reconnect : true,
-                maxStreamingLength : 10000000,
-                lastIndex : 0,
-                logLevel : 'info',
-                requestCount : 0,
+                url: '',
+                data: '',
+                suspend: true,
+                maxRequest: -1,
+                reconnect: true,
+                maxStreamingLength: 10000000,
+                lastIndex: 0,
+                logLevel: 'info',
+                requestCount: 0,
                 fallbackMethod: 'GET',
-                fallbackTransport : 'streaming',
-                transport : 'long-polling',
+                fallbackTransport: 'streaming',
+                transport: 'long-polling',
                 webSocketImpl: null,
-                webSocketUrl: null,
+                webSocketBinaryType: null,
+                dispatchUrl: null,
                 webSocketPathDelimiter: "@@",
-                enableXDR : false,
-                rewriteURL : false,
-                attachHeadersAsQueryString : true,
-                executeCallbackBeforeReconnect : false,
-                readyState : 0,
-                lastTimestamp : 0,
-                withCredentials : false,
-                trackMessageLength : false ,
-                messageDelimiter : '|',
-                connectTimeout : -1,
-                reconnectInterval : 0,
-                dropAtmosphereHeaders : true,
-                uuid : 0,
-                shared : false,
-                readResponsesHeaders : true,
+                enableXDR: false,
+                rewriteURL: false,
+                attachHeadersAsQueryString: true,
+                executeCallbackBeforeReconnect: false,
+                readyState: 0,
+                lastTimestamp: 0,
+                withCredentials: false,
+                trackMessageLength: false,
+                messageDelimiter: '|',
+                connectTimeout: -1,
+                reconnectInterval: 0,
+                dropAtmosphereHeaders: true,
+                uuid: 0,
+                shared: false,
+                readResponsesHeaders: false,
                 maxReconnectOnClose: 5,
-                enableProtocol: false,
-                onError : function(response) {
+                enableProtocol: true,
+                onError: function (response) {
+                },
+                onClose: function (response) {
+                },
+                onOpen: function (response) {
                 },
-                onClose : function(response) {
+                onMessage: function (response) {
                 },
-                onOpen : function(response) {
+                onReopen: function (request, response) {
                 },
-                onMessage : function(response) {
+                onReconnect: function (request, response) {
                 },
-                onReconnect : function(request, response) {
+                onMessagePublished: function (response) {
                 },
-                onMessagePublished : function(response) {
+                onTransportFailure: function (reason, request) {
                 },
-                onTransportFailure : function (reason, request) {
+                onLocalMessage: function (request) {
                 },
-                onLocalMessage : function (request) {
+                onFailureToReconnect: function (request, response) {
+                },
+                onClientTimeout: function(request){
                 }
             };
 
             /**
              * {Object} Request's last response.
+             *
              * @private
              */
             var _response = {
                 status: 200,
-                reasonPhrase : "OK",
-                responseBody : '',
-                messages : [],
-                headers : [],
-                state : "messageReceived",
-                transport : "polling",
+                reasonPhrase: "OK",
+                responseBody: '',
+                messages: [],
+                headers: [],
+                state: "messageReceived",
+                transport: "polling",
                 error: null,
-                request : null,
-                partialMessage : "",
+                request: null,
+                partialMessage: "",
                 errorHandled: false,
-                id : 0
+                id: 0
             };
 
             /**
@@ -166,8 +184,7 @@ jQuery.atmosphere = function() {
             var _sse = null;
 
             /**
-             * {XMLHttpRequest, ActiveXObject} Opened ajax request (in case of
-             * http-streaming or long-polling)
+             * {XMLHttpRequest, ActiveXObject} Opened ajax request (in case of http-streaming or long-polling)
              *
              * @private
              */
@@ -210,24 +227,28 @@ jQuery.atmosphere = function() {
 
             /**
              * A local "channel' of communication.
+             *
              * @private
              */
             var _localSocketF = null;
 
             /**
              * The storage used.
+             *
              * @private
              */
             var _storageService;
 
             /**
              * Local communication
+             *
              * @private
              */
             var _localStorageService = null;
 
             /**
              * A Unique ID
+             *
              * @private
              */
             var guid = jQuery.now();
@@ -256,6 +277,7 @@ jQuery.atmosphere = function() {
 
             /**
              * Re-initialize atmosphere object.
+             *
              * @private
              */
             function _reinit() {
@@ -267,8 +289,7 @@ jQuery.atmosphere = function() {
              * Subscribe request using request transport. <br>
              * If request is currently opened, this one will be closed.
              *
-             * @param {Object}
-                *            Request parameters.
+             * @param {Object} Request parameters.
              * @private
              */
             function _subscribe(options) {
@@ -283,11 +304,9 @@ jQuery.atmosphere = function() {
             }
 
             /**
-             * Check if web socket is supported (check for custom implementation
-             * provided by request object or browser implementation).
+             * Check if web socket is supported (check for custom implementation provided by request object or browser implementation).
              *
-             * @returns {boolean} True if web socket is supported, false
-             *          otherwise.
+             * @returns {boolean} True if web socket is supported, false otherwise.
              * @private
              */
             function _supportWebsocket() {
@@ -295,11 +314,9 @@ jQuery.atmosphere = function() {
             }
 
             /**
-             * Check if server side events (SSE) is supported (check for custom implementation
-             * provided by request object or browser implementation).
+             * Check if server side events (SSE) is supported (check for custom implementation provided by request object or browser implementation).
              *
-             * @returns {boolean} True if web socket is supported, false
-             *          otherwise.
+             * @returns {boolean} True if web socket is supported, false otherwise.
              * @private
              */
             function _supportSSE() {
@@ -308,9 +325,7 @@ jQuery.atmosphere = function() {
 
             /**
              * Open request using request transport. <br>
-             * If request transport is 'websocket' but websocket can't be
-             * opened, request will automatically reconnect using fallback
-             * transport.
+             * If request transport is 'websocket' but websocket can't be opened, request will automatically reconnect using fallback transport.
              *
              * @private
              */
@@ -319,7 +334,7 @@ jQuery.atmosphere = function() {
                 if (_request.shared) {
                     _localStorageService = _local(_request);
                     if (_localStorageService != null) {
-                        if (_request.logLevel == 'debug') {
+                        if (_request.logLevel === 'debug') {
                             jQuery.atmosphere.debug("Storage service available. All communication will be local");
                         }
 
@@ -329,7 +344,7 @@ jQuery.atmosphere = function() {
                         }
                     }
 
-                    if (_request.logLevel == 'debug') {
+                    if (_request.logLevel === 'debug') {
                         jQuery.atmosphere.debug("No Storage service available.");
                     }
                     // Wasn't local or an error occurred
@@ -337,25 +352,24 @@ jQuery.atmosphere = function() {
                 }
 
                 // Protocol
-                _request.firstMessage= true;
+                _request.firstMessage = true;
+                _request.isOpen = false;
                 _request.ctime = jQuery.now();
 
-                if (_request.transport != 'websocket' && _request.transport != 'sse') {
-                    // Gives a chance to the connection to be established before calling the callback
-                    setTimeout(function() {
-                        _open('opening', _request.transport, _request);
-                    }, 500);
-                    _executeRequest();
+                if (_request.transport !== 'websocket' && _request.transport !== 'sse') {
+                    _executeRequest(_request);
 
-                } else if (_request.transport == 'websocket') {
+                } else if (_request.transport === 'websocket') {
                     if (!_supportWebsocket()) {
-                        _reconnectWithFallbackTransport("Websocket is not supported, using request.fallbackTransport (" + _request.fallbackTransport + ")");
+                        _reconnectWithFallbackTransport("Websocket is not supported, using request.fallbackTransport (" + _request.fallbackTransport
+                            + ")");
                     } else {
                         _executeWebSocket(false);
                     }
-                } else if (_request.transport == 'sse') {
+                } else if (_request.transport === 'sse') {
                     if (!_supportSSE()) {
-                        _reconnectWithFallbackTransport("Server Side Events(SSE) is not supported, using request.fallbackTransport (" + _request.fallbackTransport + ")");
+                        _reconnectWithFallbackTransport("Server Side Events(SSE) is not supported, using request.fallbackTransport ("
+                            + _request.fallbackTransport + ")");
                     } else {
                         _executeSSE(false);
                     }
@@ -364,23 +378,21 @@ jQuery.atmosphere = function() {
 
             function _local(request) {
                 var trace, connector, orphan, name = "atmosphere-" + request.url, connectors = {
-                    storage: function() {
+                    storage: function () {
                         if (!jQuery.atmosphere.supportStorage()) {
                             return;
                         }
 
-                        var storage = window.localStorage,
-                            get = function(key) {
-                                return jQuery.parseJSON(storage.getItem(name + "-" + key));
-                            },
-                            set = function(key, value) {
-                                storage.setItem(name + "-" + key, jQuery.stringifyJSON(value));
-                            };
+                        var storage = window.localStorage, get = function (key) {
+                            return jQuery.parseJSON(storage.getItem(name + "-" + key));
+                        }, set = function (key, value) {
+                            storage.setItem(name + "-" + key, jQuery.stringifyJSON(value));
+                        };
 
                         return {
-                            init: function() {
+                            init: function () {
                                 set("children", get("children").concat([guid]));
-                                jQuery(window).on("storage.socket", function(event) {
+                                jQuery(window).on("storage.socket", function (event) {
                                     event = event.originalEvent;
                                     if (event.key === name && event.newValue) {
                                         listener(event.newValue);
@@ -388,10 +400,14 @@ jQuery.atmosphere = function() {
                                 });
                                 return get("opened");
                             },
-                            signal: function(type, data) {
-                                storage.setItem(name, jQuery.stringifyJSON({target: "p", type: type, data: data}));
+                            signal: function (type, data) {
+                                storage.setItem(name, jQuery.stringifyJSON({
+                                    target: "p",
+                                    type: type,
+                                    data: data
+                                }));
                             },
-                            close: function() {
+                            close: function () {
                                 var index, children = get("children");
 
                                 jQuery(window).off("storage.socket");
@@ -405,7 +421,7 @@ jQuery.atmosphere = function() {
                             }
                         };
                     },
-                    windowref: function() {
+                    windowref: function () {
                         var win = window.open("", name.replace(/\W/g, ""));
 
                         if (!win || win.closed || !win.callbacks) {
@@ -413,17 +429,21 @@ jQuery.atmosphere = function() {
                         }
 
                         return {
-                            init: function() {
+                            init: function () {
                                 win.callbacks.push(listener);
                                 win.children.push(guid);
                                 return win.opened;
                             },
-                            signal: function(type, data) {
+                            signal: function (type, data) {
                                 if (!win.closed && win.fire) {
-                                    win.fire(jQuery.stringifyJSON({target: "p", type: type, data: data}));
+                                    win.fire(jQuery.stringifyJSON({
+                                        target: "p",
+                                        type: type,
+                                        data: data
+                                    }));
                                 }
                             },
-                            close : function() {
+                            close: function () {
                                 function remove(array, e) {
                                     var index = jQuery.inArray(e, array);
                                     if (index > -1) {
@@ -449,7 +469,7 @@ jQuery.atmosphere = function() {
                     if (command.target === "c") {
                         switch (command.type) {
                             case "open":
-                                _open("opening", 'local', _request)
+                                _open("opening", 'local', _request);
                                 break;
                             case "close":
                                 if (!orphan) {
@@ -461,7 +481,7 @@ jQuery.atmosphere = function() {
                                         if (data.heir === guid) {
                                             _execute();
                                         } else {
-                                            setTimeout(function() {
+                                            setTimeout(function () {
                                                 _execute();
                                             }, 100);
                                         }
@@ -498,35 +518,45 @@ jQuery.atmosphere = function() {
                 }
 
                 return {
-                    open: function() {
+                    open: function () {
                         var parentOpened;
 
                         // Checks the shared one is alive
-                        _traceTimer = setInterval(function() {
+                        _traceTimer = setInterval(function () {
                             var oldTrace = trace;
                             trace = findTrace();
                             if (!trace || oldTrace.ts === trace.ts) {
                                 // Simulates a close signal
-                                listener(jQuery.stringifyJSON({target: "c", type: "close", data: {reason: "error", heir: oldTrace.heir}}));
+                                listener(jQuery.stringifyJSON({
+                                    target: "c",
+                                    type: "close",
+                                    data: {
+                                        reason: "error",
+                                        heir: oldTrace.heir
+                                    }
+                                }));
                             }
                         }, 1000);
 
                         parentOpened = connector.init();
                         if (parentOpened) {
                             // Firing the open event without delay robs the user of the opportunity to bind connecting event handlers
-                            setTimeout(function() {
-                                _open("opening", 'local', request)
+                            setTimeout(function () {
+                                _open("opening", 'local', request);
                             }, 50);
                         }
                         return parentOpened;
                     },
-                    send: function(event) {
+                    send: function (event) {
                         connector.signal("send", event);
                     },
-                    localSend: function(event) {
-                        connector.signal("localSend", jQuery.stringifyJSON({id: guid , event: event}));
+                    localSend: function (event) {
+                        connector.signal("localSend", jQuery.stringifyJSON({
+                            id: guid,
+                            event: event
+                        }));
                     },
-                    close: function() {
+                    close: function () {
                         // Do not signal the parent if this method is executed by the unload event handler
                         if (!_abordingConnection) {
                             clearInterval(_traceTimer);
@@ -535,13 +565,13 @@ jQuery.atmosphere = function() {
                         }
                     }
                 };
-            };
+            }
 
             function share() {
                 var storageService, name = "atmosphere-" + _request.url, servers = {
                     // Powered by the storage event and the localStorage
                     // http://www.w3.org/TR/webstorage/#event-storage
-                    storage: function() {
+                    storage: function () {
                         if (!jQuery.atmosphere.supportStorage()) {
                             return;
                         }
@@ -549,9 +579,9 @@ jQuery.atmosphere = function() {
                         var storage = window.localStorage;
 
                         return {
-                            init: function() {
+                            init: function () {
                                 // Handles the storage event
-                                jQuery(window).on("storage.socket", function(event) {
+                                jQuery(window).on("storage.socket", function (event) {
                                     event = event.originalEvent;
                                     // When a deletion, newValue initialized to null
                                     if (event.key === name && event.newValue) {
@@ -559,16 +589,20 @@ jQuery.atmosphere = function() {
                                     }
                                 });
                             },
-                            signal: function(type, data) {
-                                storage.setItem(name, jQuery.stringifyJSON({target: "c", type: type, data: data}));
+                            signal: function (type, data) {
+                                storage.setItem(name, jQuery.stringifyJSON({
+                                    target: "c",
+                                    type: type,
+                                    data: data
+                                }));
                             },
-                            get: function(key) {
+                            get: function (key) {
                                 return jQuery.parseJSON(storage.getItem(name + "-" + key));
                             },
-                            set: function(key, value) {
+                            set: function (key, value) {
                                 storage.setItem(name + "-" + key, jQuery.stringifyJSON(value));
                             },
-                            close : function() {
+                            close: function () {
                                 jQuery(window).off("storage.socket");
                                 storage.removeItem(name);
                                 storage.removeItem(name + "-opened");
@@ -579,18 +613,18 @@ jQuery.atmosphere = function() {
                     },
                     // Powered by the window.open method
                     // https://developer.mozilla.org/en/DOM/window.open
-                    windowref: function() {
+                    windowref: function () {
                         // Internet Explorer raises an invalid argument error
                         // when calling the window.open method with the name containing non-word characters
-                        var neim = name.replace(/\W/g, ""), win = (jQuery('iframe[name="' + neim + '"]')[0]
-                            || jQuery('<iframe name="' + neim + '" />').hide().appendTo("body")[0]).contentWindow;
+                        var neim = name.replace(/\W/g, ""), win = (jQuery('iframe[name="' + neim + '"]')[0] || jQuery(
+                            '<iframe name="' + neim + '" />').hide().appendTo("body")[0]).contentWindow;
 
                         return {
-                            init: function() {
+                            init: function () {
                                 // Callbacks from different windows
                                 win.callbacks = [listener];
                                 // In IE 8 and less, only string argument can be safely passed to the function in other window
-                                win.fire = function(string) {
+                                win.fire = function (string) {
                                     var i;
 
                                     for (i = 0; i < win.callbacks.length; i++) {
@@ -598,25 +632,29 @@ jQuery.atmosphere = function() {
                                     }
                                 };
                             },
-                            signal: function(type, data) {
+                            signal: function (type, data) {
                                 if (!win.closed && win.fire) {
-                                    win.fire(jQuery.stringifyJSON({target: "c", type: type, data: data}));
+                                    win.fire(jQuery.stringifyJSON({
+                                        target: "c",
+                                        type: type,
+                                        data: data
+                                    }));
                                 }
                             },
-                            get: function(key) {
+                            get: function (key) {
                                 return !win.closed ? win[key] : null;
                             },
-                            set: function(key, value) {
+                            set: function (key, value) {
                                 if (!win.closed) {
                                     win[key] = value;
                                 }
                             },
-                            close : function() {}
+                            close: function () {
+                            }
                         };
                     }
                 };
 
-
                 // Receives send and close command from the children
                 function listener(string) {
                     var command = jQuery.parseJSON(string), data = command.data;
@@ -638,20 +676,23 @@ jQuery.atmosphere = function() {
 
                 _localSocketF = function propagateMessageEvent(context) {
                     storageService.signal("message", context);
-                }
+                };
 
                 function leaveTrace() {
                     document.cookie = encodeURIComponent(name) + "=" +
                         // Opera's JSON implementation ignores a number whose a last digit of 0 strangely
                         // but has no problem with a number whose a last digit of 9 + 1
-                        encodeURIComponent(jQuery.stringifyJSON({ts: jQuery.now() + 1, heir: (storageService.get("children") || [])[0]}));
+                        encodeURIComponent(jQuery.stringifyJSON({
+                            ts: jQuery.now() + 1,
+                            heir: (storageService.get("children") || [])[0]
+                        }));
                 }
 
                 // Chooses a storageService
                 storageService = servers.storage() || servers.windowref();
                 storageService.init();
 
-                if (_request.logLevel == 'debug') {
+                if (_request.logLevel === 'debug') {
                     jQuery.atmosphere.debug("Installed StorageService " + storageService);
                 }
 
@@ -673,7 +714,7 @@ jQuery.atmosphere = function() {
              * @private
              */
             function _open(state, transport, request) {
-                if (_request.shared && transport != 'local') {
+                if (_request.shared && transport !== 'local') {
                     share();
                 }
 
@@ -681,15 +722,17 @@ jQuery.atmosphere = function() {
                     _storageService.set("opened", true);
                 }
 
-                request.close = function() {
+                request.close = function () {
                     _close();
                 };
 
-                if (_response.error == null) {
+                if (_requestCount > 0 && state === 're-connecting') {
+                    request.isReopen = true;
+                    _tryingToReconnect(_response);
+                } else if (_response.error == null) {
                     _response.request = request;
                     var prevState = _response.state;
                     _response.state = state;
-                    _response.status = 200;
                     var prevTransport = _response.transport;
                     _response.transport = transport;
 
@@ -705,54 +748,56 @@ jQuery.atmosphere = function() {
             /**
              * Execute request using jsonp transport.
              *
-             * @param request
-             *            {Object} request Request parameters, if
-             *            undefined _request object will be used.
+             * @param request {Object} request Request parameters, if undefined _request object will be used.
              * @private
              */
             function _jsonp(request) {
                 // When CORS is enabled, make sure we force the proper transport.
-                request.transport="jsonp";
+                request.transport = "jsonp";
 
                 var rq = _request;
-                if ((request != null) && (typeof(request) != 'undefined')) {
+                if ((request != null) && (typeof (request) !== 'undefined')) {
                     rq = request;
                 }
 
                 var url = rq.url;
+                if (rq.dispatchUrl != null) {
+                    url += rq.dispatchUrl;
+                }
+
                 var data = rq.data;
                 if (rq.attachHeadersAsQueryString) {
                     url = _attachHeaders(rq);
-                    if (data != '') {
+                    if (data !== '') {
                         url += "&X-Atmosphere-Post-Body=" + encodeURIComponent(data);
                     }
                     data = '';
                 }
 
                 _jqxhr = jQuery.ajax({
-                    url : url,
-                    type : rq.method,
+                    url: url,
+                    type: rq.method,
                     dataType: "jsonp",
-                    error : function(jqXHR, textStatus, errorThrown) {
+                    error: function (jqXHR, textStatus, errorThrown) {
                         _response.error = true;
-                        if (jqXHR.status < 300 && rq.reconnect && _requestCount++ < rq.maxReconnectOnClose) {
-                            _reconnect(_jqxhr, rq);
+                        if (jqXHR.status < 300) {
+                            _reconnect(_jqxhr, rq, 0);
                         } else {
                             _onError(jqXHR.status, errorThrown);
                         }
                     },
-                    jsonp : "jsonpTransport",
-                    success: function(json) {
+                    jsonp: "jsonpTransport",
+                    success: function (json) {
                         if (rq.reconnect) {
-                            if (rq.maxRequest == -1 || rq.requestCount++ < rq.maxRequest) {
+                            if (rq.maxRequest === -1 || rq.requestCount++ < rq.maxRequest) {
                                 _readHeaders(_jqxhr, rq);
 
                                 if (!rq.executeCallbackBeforeReconnect) {
-                                    _reconnect(_jqxhr, rq);
+                                    _reconnect(_jqxhr, rq, 0);
                                 }
 
                                 var msg = json.message;
-                                if (msg != null && typeof msg != 'string') {
+                                if (msg != null && typeof msg !== 'string') {
                                     try {
                                         msg = jQuery.stringifyJSON(msg);
                                     } catch (err) {
@@ -760,12 +805,13 @@ jQuery.atmosphere = function() {
                                     }
                                 }
 
-                                if (_handleProtocol(rq, msg)) {
-                                    _prepareCallback(msg, "messageReceived", 200, rq.transport);
+                                var skipCallbackInvocation = _trackMessageSize(msg, rq, _response);
+                                if (!skipCallbackInvocation) {
+                                    _prepareCallback(_response.responseBody, "messageReceived", 200, rq.transport);
                                 }
 
                                 if (rq.executeCallbackBeforeReconnect) {
-                                    _reconnect(_jqxhr, rq);
+                                    _reconnect(_jqxhr, rq, 0);
                                 }
                             } else {
                                 jQuery.atmosphere.log(_request.logLevel, ["JSONP reconnect maximum try reached " + _request.requestCount]);
@@ -773,8 +819,8 @@ jQuery.atmosphere = function() {
                             }
                         }
                     },
-                    data : rq.data,
-                    beforeSend : function(jqXHR) {
+                    data: rq.data,
+                    beforeSend: function (jqXHR) {
                         _doRequest(jqXHR, rq, false);
                     }
                 });
@@ -783,32 +829,34 @@ jQuery.atmosphere = function() {
             /**
              * Execute request using ajax transport.
              *
-             * @param request
-             *            {Object} request Request parameters, if
-             *            undefined _request object will be used.
+             * @param request {Object} request Request parameters, if undefined _request object will be used.
              * @private
              */
             function _ajax(request) {
                 var rq = _request;
-                if ((request != null) && (typeof(request) != 'undefined')) {
+                if ((request != null) && (typeof (request) !== 'undefined')) {
                     rq = request;
                 }
 
                 var url = rq.url;
+                if (rq.dispatchUrl != null) {
+                    url += rq.dispatchUrl;
+                }
+
                 var data = rq.data;
                 if (rq.attachHeadersAsQueryString) {
                     url = _attachHeaders(rq);
-                    if (data != '') {
+                    if (data !== '') {
                         url += "&X-Atmosphere-Post-Body=" + encodeURIComponent(data);
                     }
                     data = '';
                 }
 
-                var async = typeof(rq.async) != 'undefined' ? rq.async : true;
+                var async = typeof (rq.async) !== 'undefined' ? rq.async : true;
                 _jqxhr = jQuery.ajax({
-                    url : url,
-                    type : rq.method,
-                    error : function(jqXHR, textStatus, errorThrown) {
+                    url: url,
+                    type: rq.method,
+                    error: function (jqXHR, textStatus, errorThrown) {
                         _response.error = true;
                         if (jqXHR.status < 300) {
                             _reconnect(_jqxhr, rq);
@@ -816,20 +864,20 @@ jQuery.atmosphere = function() {
                             _onError(jqXHR.status, errorThrown);
                         }
                     },
-                    success: function(data, textStatus, jqXHR) {
+                    success: function (data, textStatus, jqXHR) {
 
                         if (rq.reconnect) {
-                            if (rq.maxRequest == -1 || rq.requestCount++ < rq.maxRequest) {
+                            if (rq.maxRequest === -1 || rq.requestCount++ < rq.maxRequest) {
                                 if (!rq.executeCallbackBeforeReconnect) {
-                                    _reconnect(_jqxhr, rq);
+                                    _reconnect(_jqxhr, rq, 0);
                                 }
-
-                                if (_handleProtocol(rq, data)) {
-                                    _prepareCallback(data, "messageReceived", 200, rq.transport);
+                                var skipCallbackInvocation = _trackMessageSize(data, rq, _response);
+                                if (!skipCallbackInvocation) {
+                                    _prepareCallback(_response.responseBody, "messageReceived", 200, rq.transport);
                                 }
 
                                 if (rq.executeCallbackBeforeReconnect) {
-                                    _reconnect(_jqxhr, rq);
+                                    _reconnect(_jqxhr, rq, 0);
                                 }
                             } else {
                                 jQuery.atmosphere.log(_request.logLevel, ["AJAX reconnect maximum try reached " + _request.requestCount]);
@@ -837,10 +885,10 @@ jQuery.atmosphere = function() {
                             }
                         }
                     },
-                    beforeSend : function(jqXHR) {
+                    beforeSend: function (jqXHR) {
                         _doRequest(jqXHR, rq, false);
                     },
-                    crossDomain : rq.enableXDR,
+                    crossDomain: rq.enableXDR,
                     async: async
                 });
             }
@@ -848,8 +896,7 @@ jQuery.atmosphere = function() {
             /**
              * Build websocket object.
              *
-             * @param location
-             *            {string} Web socket url.
+             * @param location {string} Web socket url.
              * @returns {websocket} Web socket object.
              * @private
              */
@@ -868,8 +915,7 @@ jQuery.atmosphere = function() {
             /**
              * Build web socket url from request url.
              *
-             * @return {string} Web socket url (start with "ws" or "wss" for
-             *         secure web socket).
+             * @return {string} Web socket url (start with "ws" or "wss" for secure web socket).
              * @private
              */
             function _buildWebSocketUrl() {
@@ -891,8 +937,7 @@ jQuery.atmosphere = function() {
 
             /**
              * Open SSE. <br>
-             * Automatically use fallback transport if SSE can't be
-             * opened.
+             * Automatically use fallback transport if SSE can't be opened.
              *
              * @private
              */
@@ -902,15 +947,11 @@ jQuery.atmosphere = function() {
 
                 var location = _buildSSEUrl(_request.url);
 
-                if (_request.logLevel == 'debug') {
+                if (_request.logLevel === 'debug') {
                     jQuery.atmosphere.debug("Invoking executeSSE");
                     jQuery.atmosphere.debug("Using URL: " + location);
                 }
 
-                if (sseOpened) {
-                    _open('re-opening', "sse", _request);
-                }
-
                 if (_request.enableProtocol && sseOpened) {
                     var time = jQuery.now() - _request.ctime;
                     _request.lastTimestamp = Number(_request.stime) + Number(time);
@@ -924,7 +965,9 @@ jQuery.atmosphere = function() {
                 }
 
                 try {
-                     _sse = new EventSource(location, {withCredentials: _request.withCredentials});
+                    _sse = new EventSource(location, {
+                        withCredentials: _request.withCredentials
+                    });
                 } catch (e) {
                     _onError(0, e);
                     _reconnectWithFallbackTransport("SSE failed. Downgrading to fallback transport and resending");
@@ -932,43 +975,46 @@ jQuery.atmosphere = function() {
                 }
 
                 if (_request.connectTimeout > 0) {
-                    _request.id = setTimeout(function() {
+                    _request.id = setTimeout(function () {
                         if (!sseOpened) {
                             _clearState();
                         }
                     }, _request.connectTimeout);
                 }
 
-                _sse.onopen = function(event) {
-                    if (_request.logLevel == 'debug') {
+                _sse.onopen = function (event) {
+                    _timeout(_request);
+                    if (_request.logLevel === 'debug') {
                         jQuery.atmosphere.debug("SSE successfully opened");
                     }
 
-                    if (!sseOpened) {
-                        _open('opening', "sse", _request);
+                    if (!_request.enableProtocol) {
+                        if (!sseOpened) {
+                            _open('opening', "sse", _request);
+                        } else {
+                            _open('re-opening', "sse", _request);
+                        }
                     }
                     sseOpened = true;
 
-                    if (_request.method == 'POST') {
+                    if (_request.method === 'POST') {
                         _response.state = "messageReceived";
                         _sse.send(_request.data);
                     }
                 };
 
-                _sse.onmessage = function(message) {
-                    if (message.origin != window.location.protocol + "//" + window.location.host) {
+                _sse.onmessage = function (message) {
+                    _timeout(_request);
+                    if (!_request.enableXDR && message.origin !== window.location.protocol + "//" + window.location.host) {
                         jQuery.atmosphere.log(_request.logLevel, ["Origin was not " + window.location.protocol + "//" + window.location.host]);
                         return;
                     }
 
-                    var data = message.data;
-
-                    if (!_handleProtocol(_request, data)) return;
-
                     _response.state = 'messageReceived';
                     _response.status = 200;
 
-                    var skipCallbackInvocation = _trackMessageSize(data, _request, _response);
+                    message = message.data;
+                    var skipCallbackInvocation = _trackMessageSize(message, _request, _response);
                     if (!skipCallbackInvocation) {
                         _invokeCallback();
                         _response.responseBody = '';
@@ -976,9 +1022,11 @@ jQuery.atmosphere = function() {
                     }
                 };
 
-                _sse.onerror = function(message) {
-
+                _sse.onerror = function (message) {
                     clearTimeout(_request.id);
+
+                    if (_response.state === 'closedByClient') return;
+
                     _invokeClose(sseOpened);
                     _clearState();
 
@@ -986,11 +1034,16 @@ jQuery.atmosphere = function() {
                         jQuery.atmosphere.log(_request.logLevel, ["SSE closed normally"]);
                     } else if (!sseOpened) {
                         _reconnectWithFallbackTransport("SSE failed. Downgrading to fallback transport and resending");
-                    } else if (_request.reconnect && (_response.transport == 'sse')) {
+                    } else if (_request.reconnect && (_response.transport === 'sse')) {
                         if (_requestCount++ < _request.maxReconnectOnClose) {
-                            _request.id = setTimeout(function() {
+                            _open('re-connecting', _request.transport, _request);
+                            if (_request.reconnectInterval > 0) {
+                                _request.id = setTimeout(function () {
+                                    _executeSSE(true);
+                                }, _request.reconnectInterval);
+                            } else {
                                 _executeSSE(true);
-                            }, _request.reconnectInterval);
+                            }
                             _response.responseBody = "";
                             _response.messages = [];
                         } else {
@@ -1003,8 +1056,7 @@ jQuery.atmosphere = function() {
 
             /**
              * Open web socket. <br>
-             * Automatically use fallback transport if web socket can't be
-             * opened.
+             * Automatically use fallback transport if web socket can't be opened.
              *
              * @private
              */
@@ -1018,17 +1070,11 @@ jQuery.atmosphere = function() {
                 }
 
                 var location = _buildWebSocketUrl(_request.url);
-                var closed = false;
-
-                if (_request.logLevel == 'debug') {
+                if (_request.logLevel === 'debug') {
                     jQuery.atmosphere.debug("Invoking executeWebSocket");
                     jQuery.atmosphere.debug("Using URL: " + location);
                 }
 
-                if (webSocketOpened) {
-                    _open('re-opening', "websocket", _request);
-                }
-
                 if (webSocketOpened && !_request.reconnect) {
                     if (_websocket != null) {
                         _clearState();
@@ -1037,13 +1083,17 @@ jQuery.atmosphere = function() {
                 }
 
                 _websocket = _getWebSocket(location);
+                if (_request.webSocketBinaryType != null) {
+                    _websocket.binaryType = _request.webSocketBinaryType;
+                }
+
                 if (_request.connectTimeout > 0) {
-                    _request.id = setTimeout(function() {
+                    _request.id = setTimeout(function () {
                         if (!webSocketOpened) {
                             var _message = {
-                                code : 1002,
-                                reason : "",
-                                wasClean : false
+                                code: 1002,
+                                reason: "",
+                                wasClean: false
                             };
                             _websocket.onclose(_message);
                             // Close it anyway
@@ -1057,83 +1107,84 @@ jQuery.atmosphere = function() {
                     }, _request.connectTimeout);
                 }
 
-                _request.id = setTimeout(function() {
-                    setTimeout(function () {
-                        _clearState();
-                    }, _request.reconnectInterval)
-                }, _request.timeout);
-
-                _websocket.onopen = function(message) {
-                    if (_request.logLevel == 'debug') {
+                _websocket.onopen = function (message) {
+                    _timeout(_request);
+                    if (_request.logLevel === 'debug') {
                         jQuery.atmosphere.debug("Websocket successfully opened");
                     }
 
-                    if (!webSocketOpened) {
-                        _open('opening', "websocket", _request);
+                    if (!_request.enableProtocol) {
+                        if (!webSocketOpened) {
+                            _open('opening', "websocket", _request);
+                        } else {
+                            _open('re-opening', "websocket", _request);
+                        }
                     }
 
                     webSocketOpened = true;
-                    _websocket.webSocketOpened = webSocketOpened;
+                    if (_websocket != null) {
+                        _websocket.webSocketOpened = webSocketOpened;
 
-                    if (_request.method == 'POST') {
-                        _response.state = "messageReceived";
-                        _websocket.send(_request.data);
+                        if (_request.method === 'POST') {
+                            _response.state = "messageReceived";
+                            _websocket.send(_request.data);
+                        }
                     }
                 };
 
-                _websocket.onmessage = function(message) {
-
-                    clearTimeout(_request.id);
-                    _request.id = setTimeout(function() {
-                        setTimeout(function () {
-                            _clearState();
-                        }, _request.reconnectInterval)
-                    }, _request.timeout);
-
-                    var data = message.data;
-
-                    if (!_handleProtocol(_request, data)) return;
+                _websocket.onmessage = function (message) {
+                    _timeout(_request);
 
                     _response.state = 'messageReceived';
                     _response.status = 200;
 
-                    var skipCallbackInvocation = _trackMessageSize(data, _request, _response);
-                    if (!skipCallbackInvocation) {
+                    message = message.data;
+                    var isString = typeof (message) === 'string';
+                    if (isString) {
+                        var skipCallbackInvocation = _trackMessageSize(message, _request, _response);
+                        if (!skipCallbackInvocation) {
+                            _invokeCallback();
+                            _response.responseBody = '';
+                            _response.messages = [];
+                        }
+                    } else {
+                        if (!_handleProtocol(_request, message))
+                            return;
+
+                        _response.responseBody = message;
                         _invokeCallback();
-                        _response.responseBody = '';
-                        _response.messages = [];
+                        _response.responseBody = null;
                     }
                 };
 
-                _websocket.onerror = function(message) {
-                    clearTimeout(_request.id)
+                _websocket.onerror = function (message) {
+                    clearTimeout(_request.id);
                 };
 
-                _websocket.onclose = function(message) {
-                    if (closed) return
+                _websocket.onclose = function (message) {
+                    if (_response.state === 'closed')
+                        return;
                     clearTimeout(_request.id);
 
                     var reason = message.reason;
                     if (reason === "") {
                         switch (message.code) {
                             case 1000:
-                                reason = "Normal closure; the connection successfully completed whatever purpose for which " +
-                                    "it was created.";
+                                reason = "Normal closure; the connection successfully completed whatever purpose for which " + "it was created.";
                                 break;
                             case 1001:
-                                reason = "The endpoint is going away, either because of a server failure or because the " +
-                                    "browser is navigating away from the page that opened the connection.";
+                                reason = "The endpoint is going away, either because of a server failure or because the "
+                                    + "browser is navigating away from the page that opened the connection.";
                                 break;
                             case 1002:
                                 reason = "The endpoint is terminating the connection due to a protocol error.";
                                 break;
                             case 1003:
-                                reason = "The connection is being terminated because the endpoint received data of a type it " +
-                                    "cannot accept (for example, a text-only endpoint received binary data).";
+                                reason = "The connection is being terminated because the endpoint received data of a type it "
+                                    + "cannot accept (for example, a text-only endpoint received binary data).";
                                 break;
                             case 1004:
-                                reason = "The endpoint is terminating the connection because a data frame was received that " +
-                                    "is too large.";
+                                reason = "The endpoint is terminating the connection because a data frame was received that " + "is too large.";
                                 break;
                             case 1005:
                                 reason = "Unknown: no status code was provided even though one was expected.";
@@ -1144,94 +1195,151 @@ jQuery.atmosphere = function() {
                         }
                     }
 
-                    jQuery.atmosphere.warn("Websocket closed, reason: " + reason);
-                    jQuery.atmosphere.warn("Websocket closed, wasClean: " + message.wasClean);
+                    if (_request.logLevel === 'warn') {
+                        jQuery.atmosphere.warn("Websocket closed, reason: " + reason);
+                        jQuery.atmosphere.warn("Websocket closed, wasClean: " + message.wasClean);
+                    }
+
+                    if (_response.state === 'closedByClient') {
+                        return;
+                    }
 
                     _invokeClose(webSocketOpened);
-                    closed = true;
+
+                    _response.state = 'closed';
 
                     if (_abordingConnection) {
                         jQuery.atmosphere.log(_request.logLevel, ["Websocket closed normally"]);
                     } else if (!webSocketOpened) {
                         _reconnectWithFallbackTransport("Websocket failed. Downgrading to Comet and resending");
 
-                    } else if (_request.reconnect && _response.transport == 'websocket') {
+                    } else if (_request.reconnect && _response.transport === 'websocket') {
                         _clearState();
-                        if (_request.reconnect && _requestCount++ < _request.maxReconnectOnClose) {
-                            _request.id = setTimeout(function() {
+                        if (_requestCount++ < _request.maxReconnectOnClose) {
+                            _open('re-connecting', _request.transport, _request);
+                            if (_request.reconnectInterval > 0) {
+                                _request.id = setTimeout(function () {
+                                    _response.responseBody = "";
+                                    _response.messages = [];
+                                    _executeWebSocket(true);
+                                }, _request.reconnectInterval);
+                            } else {
                                 _response.responseBody = "";
                                 _response.messages = [];
                                 _executeWebSocket(true);
-                            }, _request.reconnectInterval);
+                            }
                         } else {
-                            jQuery.atmosphere.log(_request.logLevel, ["Websocket reconnect maximum try reached " + _requestCount]);
-                            jQuery.atmosphere.warn("Websocket error, reason: " + message.reason);
+                            jQuery.atmosphere.log(_request.logLevel, ["Websocket reconnect maximum try reached " + _request.requestCount]);
+                            if (_request.logLevel === 'warn') {
+                                jQuery.atmosphere.warn("Websocket error, reason: " + message.reason);
+                            }
                             _onError(0, "maxReconnectOnClose reached");
                         }
                     }
                 };
+
+                if (_websocket.url === undefined) {
+                    // Android 4.1 does not really support websockets and fails silently
+                    _websocket.onclose({
+                        reason: "Android 4.1 does not support websockets.",
+                        wasClean: false
+                    });
+                }
             }
 
             function _handleProtocol(request, message) {
                 // The first messages is always the uuid.
-                if (jQuery.trim(message) != 0 && request.enableProtocol && request.firstMessage) {
-                    request.firstMessage  = false;
-                    var messages =  message.split(request.messageDelimiter);
-                    var pos = messages.length == 2 ? 0 : 1;
+                var b = true;
+                if (jQuery.trim(message).length !== 0 && request.enableProtocol && request.firstMessage) {
+                    request.firstMessage = false;
+                    var messages = message.split(request.messageDelimiter);
+                    var pos = messages.length === 2 ? 0 : 1;
                     request.uuid = jQuery.trim(messages[pos]);
                     request.stime = jQuery.trim(messages[pos + 1]);
-                    return false;
+                    b = false;
+                    if (request.transport !== 'long-polling') {
+                        _triggerOpen(request);
+                    }
+                } else if (request.enableProtocol && request.firstMessage) {
+                    // In case we are getting some junk from IE
+                    b = false;
+                } else {
+                    _triggerOpen(request);
                 }
-                return true;
+                return b;
+            }
+
+            function _timeout(_request) {
+                clearTimeout(_request.id);
+                if (_request.timeout > 0 && _request.transport !== 'polling') {
+                    _request.id = setTimeout(function () {
+                        _onClientTimeout(_request);
+                        _disconnect();
+                        _clearState();
+                    }, _request.timeout);
+                }
+            }
+
+            function _onClientTimeout(_request) {
+                _response.state = 'closedByClient';
+                _response.responseBody = "";
+                _response.status = 408;
+                _response.messages = [];
+                _invokeCallback();
             }
 
             function _onError(code, reason) {
                 _clearState();
-
+                clearTimeout(_request.id);
                 _response.state = 'error';
                 _response.reasonPhrase = reason;
                 _response.responseBody = "";
-                _response.messages = [];
                 _response.status = code;
+                _response.messages = [];
                 _invokeCallback();
             }
 
             /**
-             * Track received message and make sure callbacks/functions are only invoked when the complete message
-             * has been received.
+             * Track received message and make sure callbacks/functions are only invoked when the complete message has been received.
              *
              * @param message
              * @param request
              * @param response
              */
             function _trackMessageSize(message, request, response) {
-                if (request.trackMessageLength) {
+                if (!_handleProtocol(_request, message))
+                    return true;
+                if (message.length === 0)
+                    return true;
 
-                    // If we have found partial message, prepend them.
-                    if (response.partialMessage.length != 0) {
-                        message = response.partialMessage + message;
-                    }
+                if (request.trackMessageLength) {
+                    // prepend partialMessage if any
+                    message = response.partialMessage + message;
 
                     var messages = [];
-                    var messageLength = 0;
                     var messageStart = message.indexOf(request.messageDelimiter);
-                    while (messageStart != -1) {
-                        messageLength = jQuery.trim(message.substring(messageLength, messageStart));
-                        message = message.substring(messageStart + request.messageDelimiter.length, message.length);
-
-                        if (message.length == 0 || message.length < messageLength) break;
-
-                        messageStart = message.indexOf(request.messageDelimiter);
-                        messages.push(message.substring(0, messageLength));
+                    while (messageStart !== -1) {
+                        var str = jQuery.trim(message.substring(0, messageStart));
+                        var messageLength = parseInt(str, 10);
+                        if (isNaN(messageLength))
+                            throw 'message length "' + str + '" is not a number';
+                        messageStart += request.messageDelimiter.length;
+                        if (messageStart + messageLength > message.length) {
+                            // message not complete, so there is no trailing messageDelimiter
+                            messageStart = -1;
+                        } else {
+                            // message complete, so add it
+                            messages.push(message.substring(messageStart, messageStart + messageLength));
+                            // remove consumed characters
+                            message = message.substring(messageStart + messageLength, message.length);
+                            messageStart = message.indexOf(request.messageDelimiter);
+                        }
                     }
 
-                    if (messages.length == 0 || (messageStart != -1 && message.length != 0 && messageLength != message.length)){
-                        response.partialMessage = messageLength + request.messageDelimiter + message ;
-                    } else {
-                        response.partialMessage = "";
-                    }
+                    /* keep any remaining data */
+                    response.partialMessage = message;
 
-                    if (messages.length != 0) {
+                    if (messages.length !== 0) {
                         response.responseBody = messages.join(request.messageDelimiter);
                         response.messages = messages;
                         return false;
@@ -1255,21 +1363,25 @@ jQuery.atmosphere = function() {
             function _reconnectWithFallbackTransport(errorMessage) {
                 jQuery.atmosphere.log(_request.logLevel, [errorMessage]);
 
-                if (typeof(_request.onTransportFailure) != 'undefined') {
+                if (typeof (_request.onTransportFailure) !== 'undefined') {
                     _request.onTransportFailure(errorMessage, _request);
-                } else if (typeof(jQuery.atmosphere.onTransportFailure) != 'undefined') {
+                } else if (typeof (jQuery.atmosphere.onTransportFailure) !== 'undefined') {
                     jQuery.atmosphere.onTransportFailure(errorMessage, _request);
                 }
 
                 _request.transport = _request.fallbackTransport;
-                var reconnectInterval = _request.connectTimeout == -1 ? 0 : _request.connectTimeout;
-                if (_request.reconnect && _request.transport != 'none' || _request.transport == null) {
+                var reconnectInterval = _request.connectTimeout === -1 ? 0 : _request.connectTimeout;
+                if (_request.reconnect && _request.transport !== 'none' || _request.transport == null) {
                     _request.method = _request.fallbackMethod;
                     _response.transport = _request.fallbackTransport;
                     _request.fallbackTransport = 'none';
-                    _request.id = setTimeout(function() {
+                    if (reconnectInterval > 0) {
+                        _request.id = setTimeout(function () {
+                            _execute();
+                        }, reconnectInterval);
+                    } else {
                         _execute();
-                    }, reconnectInterval);
+                    }
                 } else {
                     _onError(500, "Unable to reconnect with fallback transport");
                 }
@@ -1278,31 +1390,31 @@ jQuery.atmosphere = function() {
             /**
              * Get url from request and attach headers to it.
              *
-             * @param request
-             *            {Object} request Request parameters, if
-             *            undefined _request object will be used.
+             * @param request {Object} request Request parameters, if undefined _request object will be used.
              *
-             * @returns {Object} Request object, if undefined,
-             *          _request object will be used.
+             * @returns {Object} Request object, if undefined, _request object will be used.
              * @private
              */
-            function _attachHeaders(request) {
+            function _attachHeaders(request, url) {
                 var rq = _request;
-                if ((request != null) && (typeof(request) != 'undefined')) {
+                if ((request != null) && (typeof (request) !== 'undefined')) {
                     rq = request;
                 }
 
-                var url = rq.url;
+                if (url == null) {
+                    url = rq.url;
+                }
 
                 // If not enabled
-                if (!rq.attachHeadersAsQueryString) return url;
+                if (!rq.attachHeadersAsQueryString)
+                    return url;
 
                 // If already added
-                if (url.indexOf("X-Atmosphere-Framework") != -1) {
+                if (url.indexOf("X-Atmosphere-Framework") !== -1) {
                     return url;
                 }
 
-                url += (url.indexOf('?') != -1) ? '&' : '?';
+                url += (url.indexOf('?') !== -1) ? '&' : '?';
                 url += "X-Atmosphere-tracking-id=" + rq.uuid;
                 url += "&X-Atmosphere-Framework=" + jQuery.atmosphere.version;
                 url += "&X-Atmosphere-Transport=" + rq.transport;
@@ -1311,21 +1423,21 @@ jQuery.atmosphere = function() {
                     url += "&X-Atmosphere-TrackMessageSize=" + "true";
                 }
 
-                if (rq.lastTimestamp != undefined) {
+                if (rq.lastTimestamp != null) {
                     url += "&X-Cache-Date=" + rq.lastTimestamp;
                 } else {
                     url += "&X-Cache-Date=" + 0;
                 }
 
-                if (rq.contentType != '') {
+                if (rq.contentType !== '') {
                     url += "&Content-Type=" + rq.contentType;
                 }
 
                 if (rq.enableProtocol) {
-                   url += "&X-atmo-protocol=true";
+                    url += "&X-atmo-protocol=true";
                 }
 
-                jQuery.each(rq.headers, function(name, value) {
+                jQuery.each(rq.headers, function (name, value) {
                     var h = jQuery.isFunction(value) ? value.call(this, rq, request, _response) : value;
                     if (h != null) {
                         url += "&" + encodeURIComponent(name) + "=" + encodeURIComponent(h);
@@ -1335,42 +1447,25 @@ jQuery.atmosphere = function() {
                 return url;
             }
 
-            /**
-             * Build ajax request. <br>
-             * Ajax Request is an XMLHttpRequest object, except for IE6 where
-             * ajax request is an ActiveXObject.
-             *
-             * @return {XMLHttpRequest, ActiveXObject} Ajax request.
-             * @private
-             */
-            function _buildAjaxRequest() {
-                if (jQuery.browser.msie) {
-                    if (typeof XMLHttpRequest == "undefined")
-                      XMLHttpRequest = function () {
-                        try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); }
-                          catch (e) {}
-                        try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); }
-                          catch (e) {}
-                        try { return new ActiveXObject("Microsoft.XMLHTTP"); }
-                          catch (e) {}
-                        //Microsoft.XMLHTTP points to Msxml2.XMLHTTP and is redundant
-                        throw new Error("This browser does not support XMLHttpRequest.");
-                      };
-                }
-                return new XMLHttpRequest();
+            function _triggerOpen(rq) {
+                if (!rq.isOpen) {
+                    rq.isOpen = true;
+                    _open('opening', rq.transport, rq);
+                } else if (rq.isReopen) {
+                    rq.isReopen = false;
+                    _open('re-opening', rq.transport, rq);
+                }
             }
 
             /**
              * Execute ajax request. <br>
              *
-             * @param request
-             *            {Object} request Request parameters, if
-             *            undefined _request object will be used.
+             * @param request {Object} request Request parameters, if undefined _request object will be used.
              * @private
              */
             function _executeRequest(request) {
                 var rq = _request;
-                if ((request != null) || (typeof(request) != 'undefined')) {
+                if ((request != null) || (typeof (request) !== 'undefined')) {
                     rq = request;
                 }
 
@@ -1378,19 +1473,23 @@ jQuery.atmosphere = function() {
                 rq.readyState = 0;
 
                 // CORS fake using JSONP
-                if ((rq.transport == 'jsonp') || ((rq.enableXDR) && (jQuery.atmosphere.checkCORSSupport()))) {
+                if ((rq.transport === 'jsonp') || ((rq.enableXDR) && (jQuery.atmosphere.checkCORSSupport()))) {
                     _jsonp(rq);
                     return;
                 }
 
-                if (rq.transport == 'ajax') {
+                if (rq.transport === 'ajax') {
                     _ajax(request);
                     return;
                 }
 
                 if (jQuery.browser.msie && jQuery.browser.version < 10) {
-                    if ((rq.transport == 'streaming')) {
-                        rq.enableXDR && window.XDomainRequest ? _ieXDR(rq) : _ieStreaming(rq);
+                    if ((rq.transport === 'streaming')) {
+                        if (rq.enableXDR && window.XDomainRequest) {
+                            _ieXDR(rq);
+                        } else {
+                            _ieStreaming(rq);
+                        }
                         return;
                     }
 
@@ -1400,99 +1499,102 @@ jQuery.atmosphere = function() {
                     }
                 }
 
-                var reconnectF =  function() {
+                var reconnectF = function () {
+                    rq.lastIndex = 0;
                     if (rq.reconnect && _requestCount++ < rq.maxReconnectOnClose) {
-                        _reconnect(ajaxRequest, rq, true);
+                        _open('re-connecting', request.transport, request);
+                        _reconnect(ajaxRequest, rq, request.reconnectInterval);
                     } else {
                         _onError(0, "maxReconnectOnClose reached");
                     }
                 };
 
-                if (rq.reconnect && ( rq.maxRequest == -1 || rq.requestCount++ < rq.maxRequest)) {
-                    var ajaxRequest = _buildAjaxRequest();
+                if (rq.reconnect && (rq.maxRequest === -1 || rq.requestCount++ < rq.maxRequest)) {
+                    var ajaxRequest = jQuery.ajaxSettings.xhr();
+                    ajaxRequest.hasData = false;
+
                     _doRequest(ajaxRequest, rq, true);
 
                     if (rq.suspend) {
                         _activeRequest = ajaxRequest;
                     }
 
-                    if (rq.transport != 'polling') {
+                    if (rq.transport !== 'polling') {
                         _response.transport = rq.transport;
-                    }
 
-                    ajaxRequest.onabort = function () {
-                        _invokeClose(true);
-                    };
+                        ajaxRequest.onabort = function () {
+                            _invokeClose(true);
+                        };
 
-                    ajaxRequest.onerror = function() {
-                        _response.error = true;
-                        try {
-                            _response.status = XMLHttpRequest.status;
-                        } catch(e) {
-                            _response.status = 500;
-                        }
+                        ajaxRequest.onerror = function () {
+                            _response.error = true;
+                            try {
+                                _response.status = XMLHttpRequest.status;
+                            } catch (e) {
+                                _response.status = 500;
+                            }
 
-                        if (!_response.status) {
-                            _response.status = 500;
-                        }
-                        _clearState();
-                        if (!_response.errorHandled) {
-                            reconnectF();
-                        }
-                    };
+                            if (!_response.status) {
+                                _response.status = 500;
+                            }
+                            _clearState();
+                            if (!_response.errorHandled) {
+                                reconnectF();
+                            }
+                        };
+                    }
 
-                    ajaxRequest.onreadystatechange = function() {
+                    ajaxRequest.onreadystatechange = function () {
                         if (_abordingConnection) {
                             return;
                         }
+
                         _response.error = null;
                         var skipCallbackInvocation = false;
                         var update = false;
 
-
-                        // Opera doesn't call onerror if the server disconnect.
-                        if (jQuery.browser.opera
-                            && rq.transport == 'streaming'
-                            && rq.readyState > 2
-                            && ajaxRequest.readyState == 4) {
-
-                            rq.readyState = 0;
-                            rq.lastIndex = 0;
-
+                        if (rq.transport === 'streaming' && rq.readyState > 2 && ajaxRequest.readyState === 4) {
+                            _clearState();
                             reconnectF();
                             return;
                         }
 
                         rq.readyState = ajaxRequest.readyState;
 
-                        if (rq.transport == 'streaming' && ajaxRequest.readyState >= 3) {
+                        if (rq.transport === 'streaming' && ajaxRequest.readyState >= 3) {
                             update = true;
-                        } else if (rq.transport == 'long-polling' && ajaxRequest.readyState === 4) {
+                        } else if (rq.transport === 'long-polling' && ajaxRequest.readyState === 4) {
                             update = true;
                         }
-                        clearTimeout(rq.id);
+                        _timeout(_request);
 
-                        if (update) {
+                        if (rq.transport !== 'polling') {
+                            if ((!rq.enableProtocol || !request.firstMessage) && ajaxRequest.readyState === 2) {
+                                _triggerOpen(rq);
+                            }
                             // MSIE 9 and lower status can be higher than 1000, Chrome can be 0
-                            var status = 0;
-                            if (ajaxRequest.readyState != 0) {
+                            var status = 200;
+                            if (ajaxRequest.readyState > 1) {
                                 status = ajaxRequest.status > 1000 ? 0 : ajaxRequest.status;
                             }
 
-                            if (status >= 300 || status == 0) {
+                            if (status >= 300 || status === 0) {
                                 // Prevent onerror callback to be called
                                 _response.errorHandled = true;
                                 _clearState();
                                 reconnectF();
                                 return;
                             }
+                        }
+
+                        if (update) {
                             var responseText = ajaxRequest.responseText;
 
-                            if (jQuery.trim(responseText.length) == 0 && rq.transport == 'long-polling') {
+                            if (jQuery.trim(responseText.length).length === 0 && rq.transport === 'long-polling') {
                                 // For browser that aren't support onabort
                                 if (!ajaxRequest.hasData) {
                                     reconnectF();
-                                }  else {
+                                } else {
                                     ajaxRequest.hasData = false;
                                 }
                                 return;
@@ -1501,49 +1603,48 @@ jQuery.atmosphere = function() {
 
                             _readHeaders(ajaxRequest, _request);
 
-                            if (rq.transport == 'streaming') {
+                            if (rq.transport === 'streaming') {
                                 if (!jQuery.browser.opera) {
                                     var message = responseText.substring(rq.lastIndex, responseText.length);
+                                    skipCallbackInvocation = _trackMessageSize(message, rq, _response);
+
                                     rq.lastIndex = responseText.length;
-                                    if (!_handleProtocol(_request, message)) {
+                                    if (skipCallbackInvocation) {
                                         return;
                                     }
-                                    skipCallbackInvocation = _trackMessageSize(message, rq, _response);
                                 } else {
                                     jQuery.atmosphere.iterate(function () {
-                                        if (_response.status != 500 && ajaxRequest.responseText.length > rq.lastIndex) {
+                                        if (_response.status !== 500 && ajaxRequest.responseText.length > rq.lastIndex) {
                                             try {
                                                 _response.status = ajaxRequest.status;
-                                            }
-                                            catch (e) {
+                                                _response.headers = parseHeaders(ajaxRequest.getAllResponseHeaders());
+
+                                                _readHeaders(ajaxRequest, _request);
+
+                                            } catch (e) {
                                                 _response.status = 404;
                                             }
-                                            _response.state = "messageReceived";
+                                            _timeout(_request);
 
+                                            _response.state = "messageReceived";
                                             var message = ajaxRequest.responseText.substring(rq.lastIndex);
                                             rq.lastIndex = ajaxRequest.responseText.length;
-                                            if (_handleProtocol(_request, message)) {
-                                                skipCallbackInvocation = _trackMessageSize(message, rq, _response);
-                                                if (!skipCallbackInvocation) {
-                                                    _invokeCallback();
-                                                }
 
-                                                _verifyStreamingLength(ajaxRequest, rq);
+                                            skipCallbackInvocation = _trackMessageSize(message, rq, _response);
+                                            if (!skipCallbackInvocation) {
+                                                _invokeCallback();
                                             }
-                                        } else if (_response.status > 400){
+
+                                            _verifyStreamingLength(ajaxRequest, rq);
+                                        } else if (_response.status > 400) {
+                                            // Prevent replaying the last message.
                                             rq.lastIndex = ajaxRequest.responseText.length;
                                             return false;
                                         }
                                     }, 0);
                                 }
                             } else {
-                                if (!_handleProtocol( _request, responseText)) {
-                                    _reconnect(ajaxRequest, rq, false);
-                                    return;
-                                }
-
                                 skipCallbackInvocation = _trackMessageSize(responseText, rq, _response);
-                                rq.lastIndex = responseText.length;
                             }
 
                             try {
@@ -1551,52 +1652,36 @@ jQuery.atmosphere = function() {
                                 _response.headers = parseHeaders(ajaxRequest.getAllResponseHeaders());
 
                                 _readHeaders(ajaxRequest, rq);
-                            } catch(e) {
+                            } catch (e) {
                                 _response.status = 404;
                             }
 
                             if (rq.suspend) {
-                                _response.state = _response.status == 0 ? "closed" : "messageReceived";
+                                _response.state = _response.status === 0 ? "closed" : "messageReceived";
                             } else {
                                 _response.state = "messagePublished";
                             }
 
-                            if (!rq.executeCallbackBeforeReconnect) {
-                                _reconnect(ajaxRequest, rq, false);
-                            }
-
-                            // For backward compatibility with Atmosphere < 0.8
-                            if (_response.responseBody.indexOf("parent.callback") != -1) {
-                                jQuery.atmosphere.log(rq.logLevel, ["parent.callback no longer supported with 0.8 version and up. Please upgrade"]);
+                            var isAllowedToReconnect = request.transport !== 'streaming';
+                            if (isAllowedToReconnect && !rq.executeCallbackBeforeReconnect) {
+                                _reconnect(ajaxRequest, rq, 0);
                             }
 
-                            if (!skipCallbackInvocation) {
+                            if (_response.responseBody.length !== 0 && !skipCallbackInvocation)
                                 _invokeCallback();
-                            }
 
-                            if (rq.executeCallbackBeforeReconnect) {
-                                _reconnect(ajaxRequest, rq, false);
+                            if (isAllowedToReconnect && rq.executeCallbackBeforeReconnect) {
+                                _reconnect(ajaxRequest, rq, 0);
                             }
 
                             _verifyStreamingLength(ajaxRequest, rq);
                         }
                     };
-                    ajaxRequest.send(rq.data);
 
-                    if (rq.suspend) {
-                        rq.id = setTimeout(function() {
-                            if (_subscribed) {
-                                setTimeout(function () {
-                                    _clearState();
-                                    _executeRequest(rq);
-                                }, rq.reconnectInterval)
-                            }
-                        }, rq.timeout);
-                    }
+                    ajaxRequest.send(rq.data);
                     _subscribed = true;
-
                 } else {
-                    if (rq.logLevel == 'debug') {
+                    if (rq.logLevel === 'debug') {
                         jQuery.atmosphere.log(rq.logLevel, ["Max re-connection reached."]);
                     }
                     _onError(0, "maxRequest reached");
@@ -1605,20 +1690,25 @@ jQuery.atmosphere = function() {
 
             /**
              * Do ajax request.
+             *
              * @param ajaxRequest Ajax request.
              * @param request Request parameters.
              * @param create If ajax request has to be open.
              */
             function _doRequest(ajaxRequest, request, create) {
                 // Prevent Android to cache request
-                var url = _attachHeaders(request);
+                var url = request.url;
+                if (request.dispatchUrl != null && request.method === 'POST') {
+                    url += request.dispatchUrl;
+                }
+                url = _attachHeaders(request, url);
                 url = jQuery.atmosphere.prepareURL(url);
 
                 if (create) {
                     ajaxRequest.open(request.method, url, true);
-                    if (request.connectTimeout > -1) {
-                        request.id = setTimeout(function() {
-                            if (request.requestCount == 0) {
+                    if (request.connectTimeout > 0) {
+                        request.id = setTimeout(function () {
+                            if (request.requestCount === 0) {
                                 _clearState();
                                 _prepareC

<TRUNCATED>