diff --git a/README.md b/README.md index 21023a9e..6de53d9a 100644 --- a/README.md +++ b/README.md @@ -155,8 +155,8 @@ bad practice. If you absolutely must do it, you can use mutliple subdomains, using different subdomain for every SockJS connection. -Supported transports, by browser --------------------------------- +Supported transports, by browser (html served from http:// or https://) +----------------------------------------------------------------------- _Browser_ | _Websockets_ | _Streaming_ | _Polling_ ----------------|------------------|-------------|----------- @@ -182,6 +182,21 @@ Konqueror | no | no | jsonp-polling Websockets "hixie-76". They can still be enabled by manually changing a browser setting. +Supported transports, by browser (html served from file://) +----------------------------------------------------------- + +Sometimes you may want to serve your html from "file://" address - for +development or if you're using PhoneGap or similar technologies. But +due to the Cross Origin Policy files served from "file://" have no +Origin, and that means some of SockJS transports won't work. For this +reason the SockJS protocol table is different than usually, major +differences are: + +_Browser_ | _Websockets_ | _Streaming_ | _Polling_ +----------------|---------------|--------------------|----------- +IE 8, 9 | same as above | iframe-htmlfile | iframe-xhr-polling +Other | same as above | iframe-eventsource | iframe-xhr-polling + Supported transports, by name ----------------------------- @@ -344,12 +359,6 @@ There are various browser quirks which we don't intend to address: that have a proper Unicode support. * Having a global function called `onmessage` or such is probably a bad idea, as it could be called by the built-in `postMessage` API. - * Serving an html page that uses SockJS from `file://` url will not - work. This is due to a badly thought through - [CORS specification](http://dvcs.w3.org/hg/cors/raw-file/tip/Overview.html) - It is impossible to receive response to an Ajax request with - cookies set (`withCredentials` set to `true`) sent from a `file://` - origin. * From SockJS point of view there is nothing special about SSL/HTTPS. Connecting between unencrypted and encrypted sites should work just fine. diff --git a/lib/dom2.js b/lib/dom2.js index c5a4afe5..b49fbd5b 100644 --- a/lib/dom2.js +++ b/lib/dom2.js @@ -1,11 +1,11 @@ -var XHRObject = utils.XHRObject = function(method, url, payload) { - var that = this; - utils.delay(function(){that._start(method, url, payload);}); +var XHRObject = utils.XHRObject = function() { + var that = this, args = arguments; + utils.delay(function(){that._start.apply(that, args);}); }; XHRObject.prototype = new EventEmitter(['chunk', 'finish']); -XHRObject.prototype._start = function(method, url, payload) { +XHRObject.prototype._start = function(method, url, payload, opts) { var that = this; try { that.xhr = new _window.ActiveXObject('Microsoft.XMLHTTP'); @@ -28,7 +28,7 @@ XHRObject.prototype._start = function(method, url, payload) { return; }; - if ('withCredentials' in that.xhr) { + if ('withCredentials' in that.xhr && (!opts || !opts.no_credentials)) { that.xhr.withCredentials = 'true'; } @@ -143,7 +143,8 @@ utils.isXHRCorsCapable = function() { if (_window.XMLHttpRequest && 'withCredentials' in new XMLHttpRequest()) { return 1; } - if (_window.XDomainRequest) { + // XDomainRequest doesn't work if page is served from file:// + if (_window.XDomainRequest && _document.domain) { return 2; } if (IframeTransport.enabled()) { diff --git a/lib/info.js b/lib/info.js index a3543ae9..aa857642 100644 --- a/lib/info.js +++ b/lib/info.js @@ -8,7 +8,8 @@ InfoReceiver.prototype = new EventEmitter(['finish']); InfoReceiver.prototype.doXhr = function(base_url, AjaxObject) { var that = this; var t0 = (new Date()).getTime(); - var xo = new AjaxObject('GET', base_url + '/info' , null); + var xo = new AjaxObject('GET', base_url + '/info', null, + {no_credentials: true}); var tref = utils.delay(8000, function(){xo.ontimeout();}); diff --git a/lib/sockjs.js b/lib/sockjs.js index 858b9ff8..887479f5 100644 --- a/lib/sockjs.js +++ b/lib/sockjs.js @@ -233,6 +233,7 @@ SockJS.prototype._applyInfo = function(info, rtt, protocols_whitelist) { that._options.info = info; that._options.rtt = rtt; that._options.rto = utils.countRTO(rtt); + that._options.info.null_origin = !_document.domain; var probed = utils.probeProtocols(); that._protocols = utils.detectProtocols(probed, protocols_whitelist, info); }; diff --git a/lib/utils.js b/lib/utils.js index ad1674b3..7dbc69c6 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -283,21 +283,27 @@ utils.detectProtocols = function(probed, protocols_whitelist, info) { } // 2. Streaming - if (pe['xdr-streaming'] && !info.cookie_needed) { + if (pe['xdr-streaming'] && !info.cookie_needed && !info.null_origin) { protocols.push('xdr-streaming'); } else { - maybe_push(['xhr-streaming', - 'iframe-eventsource', - 'iframe-htmlfile']); + if (pe['xhr-streaming'] && !info.null_origin) { + protocols.push('xhr-streaming'); + } else { + maybe_push(['iframe-eventsource', + 'iframe-htmlfile']); + } } // 3. Polling - if (pe['xdr-polling'] && !info.cookie_needed) { + if (pe['xdr-polling'] && !info.cookie_needed && !info.null_origin) { protocols.push('xdr-polling'); } else { - maybe_push(['xhr-polling', - 'iframe-xhr-polling', - 'jsonp-polling']); + if (pe['xhr-polling'] && !info.null_origin) { + protocols.push('xhr-polling'); + } else { + maybe_push(['iframe-xhr-polling', + 'jsonp-polling']); + } } return protocols; } diff --git a/tests/html/src/unittests.coffee b/tests/html/src/unittests.coffee index b33b9fac..b1b0feed 100644 --- a/tests/html/src/unittests.coffee +++ b/tests/html/src/unittests.coffee @@ -177,6 +177,21 @@ test 'detectProtocols', -> deepEqual(u.detectProtocols(ie8_probed, null, {cookie_needed:true}), ['iframe-htmlfile', 'iframe-xhr-polling']) + # Check if protocols are picked up correctly when served from file:// + deepEqual(u.detectProtocols(chrome_probed, null, {null_origin:true}), + ['websocket', 'iframe-eventsource', 'iframe-xhr-polling']) + deepEqual(u.detectProtocols(chrome_probed, null, + {websocket:false, null_origin:true}), + ['iframe-eventsource', 'iframe-xhr-polling']) + + deepEqual(u.detectProtocols(opera_probed, null, {null_origin:true}), + ['iframe-eventsource', 'iframe-xhr-polling']) + + deepEqual(u.detectProtocols(ie6_probed, null, {null_origin:true}), + ['jsonp-polling']) + deepEqual(u.detectProtocols(ie8_probed, null, {null_origin:true}), + ['iframe-htmlfile', 'iframe-xhr-polling']) + test "EventEmitter", -> expect(4) r = new SockJS('//wrongdomainthatdoesntresolveatall/abc', null,