diff --git a/README.md b/README.md index e3c8ce66..60dd5d1f 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,11 @@ Where `options` is a hash which can contain: the number of expected round trips. This setting will establish a minimum, but if the calculated timeout is higher, that will be used. + * **transportOptions (object)** + + Additional options to pass per transport, keyed on transport name. For example: + ```{ 'xhr-streaming': { headers: { Authorization: 'xxx' } } }``` + Although the 'SockJS' object tries to emulate the 'WebSocket' behaviour, it's impossible to support all of its features. An important SockJS limitation is the fact that you're not allowed to diff --git a/lib/event/info.js b/lib/event/info.js new file mode 100644 index 00000000..3b7aee82 --- /dev/null +++ b/lib/event/info.js @@ -0,0 +1,17 @@ +'use strict'; + +var inherits = require('inherits') + , Event = require('./event') + ; + +function InfoEvent(info, rtt, headers) { + Event.call(this); + this.initEvent('info', false, false); + this.info = info; + this.rtt = rtt; + this.headers = headers; +} + +inherits(InfoEvent, Event); + +module.exports = InfoEvent; diff --git a/lib/info-ajax.js b/lib/info-ajax.js index 057cd4ac..c198a35c 100644 --- a/lib/info-ajax.js +++ b/lib/info-ajax.js @@ -10,14 +10,14 @@ if (process.env.NODE_ENV !== 'production') { debug = require('debug')('sockjs-client:info-ajax'); } -function InfoAjax(url, AjaxObject) { +function InfoAjax(url, AjaxObject, opts) { EventEmitter.call(this); var self = this; var t0 = +new Date(); - this.xo = new AjaxObject('GET', url); + this.xo = new AjaxObject('GET', url, null, opts); - this.xo.once('finish', function(status, text) { + this.xo.once('finish', function(status, text, headers) { var info, rtt; if (status === 200) { rtt = (+new Date()) - t0; @@ -33,7 +33,7 @@ function InfoAjax(url, AjaxObject) { info = {}; } } - self.emit('finish', info, rtt, status); + self.emit('finish', info, rtt, status, headers); self.removeAllListeners(); }); } diff --git a/lib/info-iframe-receiver.js b/lib/info-iframe-receiver.js index 70cbc9b4..38c9cc8b 100644 --- a/lib/info-iframe-receiver.js +++ b/lib/info-iframe-receiver.js @@ -6,11 +6,11 @@ var inherits = require('inherits') , InfoAjax = require('./info-ajax') ; -function InfoReceiverIframe(transUrl) { +function InfoReceiverIframe(transUrl, ignore, opts) { var self = this; EventEmitter.call(this); - this.ir = new InfoAjax(transUrl, XHRLocalObject); + this.ir = new InfoAjax(transUrl, XHRLocalObject, opts); this.ir.once('finish', function(info, rtt) { self.ir = null; self.emit('message', JSON.stringify([info, rtt])); diff --git a/lib/info-receiver.js b/lib/info-receiver.js index 8eb6824c..7d4faa01 100644 --- a/lib/info-receiver.js +++ b/lib/info-receiver.js @@ -16,13 +16,13 @@ if (process.env.NODE_ENV !== 'production') { debug = require('debug')('sockjs-client:info-receiver'); } -function InfoReceiver(baseUrl, urlInfo) { +function InfoReceiver(baseUrl, urlInfo, transportOptions) { debug(baseUrl); var self = this; EventEmitter.call(this); setTimeout(function() { - self.doXhr(baseUrl, urlInfo); + self.doXhr(baseUrl, urlInfo, transportOptions); }, 0); } @@ -30,30 +30,30 @@ inherits(InfoReceiver, EventEmitter); // TODO this is currently ignoring the list of available transports and the whitelist -InfoReceiver._getReceiver = function(baseUrl, url, urlInfo) { +InfoReceiver._getReceiver = function(baseUrl, url, urlInfo, transportOptions) { // determine method of CORS support (if needed) if (urlInfo.sameOrigin) { - return new InfoAjax(url, XHRLocal); + return new InfoAjax(url, XHRLocal, transportOptions['xhr-streaming'] || transportOptions['xhr-polling']); } if (XHRCors.enabled) { - return new InfoAjax(url, XHRCors); + return new InfoAjax(url, XHRCors, transportOptions['xhr-streaming'] || transportOptions['xhr-polling']); } if (XDR.enabled && urlInfo.sameScheme) { - return new InfoAjax(url, XDR); + return new InfoAjax(url, XDR, transportOptions['xdr-streaming'] || transportOptions['xdr-polling']); } if (InfoIframe.enabled()) { - return new InfoIframe(baseUrl, url); + return new InfoIframe(baseUrl, url, transportOptions['iframe']); } return new InfoAjax(url, XHRFake); }; -InfoReceiver.prototype.doXhr = function(baseUrl, urlInfo) { +InfoReceiver.prototype.doXhr = function(baseUrl, urlInfo, transportOptions) { var self = this , url = urlUtils.addPath(baseUrl, '/info') ; debug('doXhr', url); - this.xo = InfoReceiver._getReceiver(baseUrl, url, urlInfo); + this.xo = InfoReceiver._getReceiver(baseUrl, url, urlInfo, transportOptions); this.timeoutRef = setTimeout(function() { debug('timeout'); @@ -61,10 +61,10 @@ InfoReceiver.prototype.doXhr = function(baseUrl, urlInfo) { self.emit('finish'); }, InfoReceiver.timeout); - this.xo.once('finish', function(info, rtt, status) { - debug('finish', info, rtt); + this.xo.once('finish', function(info, rtt, status, headers) { + debug('finish', info, rtt, status, headers); self._cleanup(true); - self.emit('finish', info, rtt, status); + self.emit('finish', info, rtt, status, headers || {}); }); }; diff --git a/lib/main.js b/lib/main.js index 8d3b280a..e28f5043 100644 --- a/lib/main.js +++ b/lib/main.js @@ -17,6 +17,7 @@ var URL = require('url-parse') , loc = require('./location') , CloseEvent = require('./event/close') , TransportMessageEvent = require('./event/trans-message') + , InfoEvent = require('./event/info') , InfoReceiver = require('./info-receiver') ; @@ -121,7 +122,7 @@ function SockJS(url, protocols, options) { , sameScheme: urlUtils.isSchemeEqual(this.url, loc.href) }; - this._ir = new InfoReceiver(this.url, this._urlInfo); + this._ir = new InfoReceiver(this.url, this._urlInfo, this._transportOptions); this._ir.once('finish', this._receiveInfo.bind(this)); } @@ -163,7 +164,7 @@ SockJS.prototype.send = function(data) { if (this.readyState !== SockJS.OPEN) { return; } - this._transport.send(escape.quote(data)); + return this._transport.send(escape.quote(data)); }; SockJS.version = require('./version'); @@ -173,8 +174,9 @@ SockJS.OPEN = 1; SockJS.CLOSING = 2; SockJS.CLOSED = 3; -SockJS.prototype._receiveInfo = function(info, rtt, status) { +SockJS.prototype._receiveInfo = function(info, rtt, status, headers) { debug('_receiveInfo', rtt); + this.dispatchEvent(new InfoEvent(info, rtt, headers)); this._ir = null; if (!info) { this._close(status || 1002, 'Cannot connect to server'); @@ -197,6 +199,7 @@ SockJS.prototype._receiveInfo = function(info, rtt, status) { }; SockJS.prototype._connect = function() { + var self = this; for (var Transport = this._transports.shift(); Transport; Transport = this._transports.shift()) { debug('attempt', Transport.transportName); if (Transport.needBody) { @@ -221,6 +224,10 @@ SockJS.prototype._connect = function() { debug('transport url', transportUrl); var transportObj = new Transport(transportUrl, this._transUrl, options); transportObj.on('message', this._transportMessage.bind(this)); + // for transports that support backpressure handle drain event + transportObj.on('drain', function() { + self.dispatchEvent(new Event('drain')); + }); transportObj.once('close', this._transportClose.bind(this)); transportObj.transportName = Transport.transportName; this._transport = transportObj; diff --git a/lib/transport/driver/xhr.js b/lib/transport/driver/xhr.js index 9e1217b2..1fab97e9 100644 --- a/lib/transport/driver/xhr.js +++ b/lib/transport/driver/xhr.js @@ -38,7 +38,7 @@ function XhrDriver(method, url, payload, opts) { }); res.once('end', function() { debug('end'); - self.emit('finish', res.statusCode, responseText); + self.emit('finish', res.statusCode, responseText, res.headers); self.req = null; }); }); diff --git a/lib/transport/eventsource.js b/lib/transport/eventsource.js index 62685cfd..3a355c88 100644 --- a/lib/transport/eventsource.js +++ b/lib/transport/eventsource.js @@ -7,12 +7,12 @@ var inherits = require('inherits') , EventSourceDriver = require('eventsource') ; -function EventSourceTransport(transUrl) { +function EventSourceTransport(transUrl, ignore, opts) { if (!EventSourceTransport.enabled()) { throw new Error('Transport created when disabled'); } - AjaxBasedTransport.call(this, transUrl, '/eventsource', EventSourceReceiver, XHRCorsObject); + AjaxBasedTransport.call(this, transUrl, '/eventsource', EventSourceReceiver, XHRCorsObject, opts); } inherits(EventSourceTransport, AjaxBasedTransport); diff --git a/lib/transport/htmlfile.js b/lib/transport/htmlfile.js index 633b0774..10efada3 100644 --- a/lib/transport/htmlfile.js +++ b/lib/transport/htmlfile.js @@ -6,11 +6,11 @@ var inherits = require('inherits') , AjaxBasedTransport = require('./lib/ajax-based') ; -function HtmlFileTransport(transUrl) { +function HtmlFileTransport(transUrl, ignore, opts) { if (!HtmlfileReceiver.enabled) { throw new Error('Transport created when disabled'); } - AjaxBasedTransport.call(this, transUrl, '/htmlfile', HtmlfileReceiver, XHRLocalObject); + AjaxBasedTransport.call(this, transUrl, '/htmlfile', HtmlfileReceiver, XHRLocalObject, opts); } inherits(HtmlFileTransport, AjaxBasedTransport); diff --git a/lib/transport/iframe.js b/lib/transport/iframe.js index d567339f..736a2948 100644 --- a/lib/transport/iframe.js +++ b/lib/transport/iframe.js @@ -128,6 +128,7 @@ IframeTransport.prototype.postMessage = function(type, data) { IframeTransport.prototype.send = function(message) { debug('send', message); this.postMessage('m', message); + return true; }; IframeTransport.enabled = function() { diff --git a/lib/transport/lib/ajax-based.js b/lib/transport/lib/ajax-based.js index 29e104e7..0141dee3 100644 --- a/lib/transport/lib/ajax-based.js +++ b/lib/transport/lib/ajax-based.js @@ -10,15 +10,16 @@ if (process.env.NODE_ENV !== 'production') { debug = require('debug')('sockjs-client:ajax-based'); } -function createAjaxSender(AjaxObject) { +function createAjaxSender(AjaxObject, opts) { return function(url, payload, callback) { debug('create ajax sender', url, payload); - var opt = {}; + opts = opts || {}; if (typeof payload === 'string') { - opt.headers = {'Content-type': 'text/plain'}; + opts.headers = opts.headers || {}; + opts.headers['Content-type'] = 'text/plain'; } var ajaxUrl = urlUtils.addPath(url, '/xhr_send'); - var xo = new AjaxObject('POST', ajaxUrl, payload, opt); + var xo = new AjaxObject('POST', ajaxUrl, payload, opts); xo.once('finish', function(status) { debug('finish', status); xo = null; @@ -40,8 +41,8 @@ function createAjaxSender(AjaxObject) { }; } -function AjaxBasedTransport(transUrl, urlSuffix, Receiver, AjaxObject) { - SenderReceiver.call(this, transUrl, urlSuffix, createAjaxSender(AjaxObject), Receiver, AjaxObject); +function AjaxBasedTransport(transUrl, urlSuffix, Receiver, AjaxObject, opts) { + SenderReceiver.call(this, transUrl, urlSuffix, createAjaxSender(AjaxObject, opts), Receiver, AjaxObject, opts); } inherits(AjaxBasedTransport, SenderReceiver); diff --git a/lib/transport/lib/buffered-sender.js b/lib/transport/lib/buffered-sender.js index dda9163f..e0b33602 100644 --- a/lib/transport/lib/buffered-sender.js +++ b/lib/transport/lib/buffered-sender.js @@ -25,6 +25,7 @@ BufferedSender.prototype.send = function(message) { if (!this.sendStop) { this.sendSchedule(); } + return true; }; // For polling transports in a situation when in the message callback, diff --git a/lib/transport/lib/polling.js b/lib/transport/lib/polling.js index 8108432b..e966207a 100644 --- a/lib/transport/lib/polling.js +++ b/lib/transport/lib/polling.js @@ -9,12 +9,13 @@ if (process.env.NODE_ENV !== 'production') { debug = require('debug')('sockjs-client:polling'); } -function Polling(Receiver, receiveUrl, AjaxObject) { +function Polling(Receiver, receiveUrl, AjaxObject, opts) { debug(receiveUrl); EventEmitter.call(this); this.Receiver = Receiver; this.receiveUrl = receiveUrl; this.AjaxObject = AjaxObject; + this.opts = opts; this._scheduleReceiver(); } @@ -23,7 +24,7 @@ inherits(Polling, EventEmitter); Polling.prototype._scheduleReceiver = function() { debug('_scheduleReceiver'); var self = this; - var poll = this.poll = new this.Receiver(this.receiveUrl, this.AjaxObject); + var poll = this.poll = new this.Receiver(this.receiveUrl, this.AjaxObject, this.opts); poll.on('message', function(msg) { debug('message', msg); diff --git a/lib/transport/lib/sender-receiver.js b/lib/transport/lib/sender-receiver.js index 8154994c..075157ed 100644 --- a/lib/transport/lib/sender-receiver.js +++ b/lib/transport/lib/sender-receiver.js @@ -11,13 +11,13 @@ if (process.env.NODE_ENV !== 'production') { debug = require('debug')('sockjs-client:sender-receiver'); } -function SenderReceiver(transUrl, urlSuffix, senderFunc, Receiver, AjaxObject) { +function SenderReceiver(transUrl, urlSuffix, senderFunc, Receiver, AjaxObject, opts) { var pollUrl = urlUtils.addPath(transUrl, urlSuffix); debug(pollUrl); var self = this; BufferedSender.call(this, transUrl, senderFunc); - this.poll = new Polling(Receiver, pollUrl, AjaxObject); + this.poll = new Polling(Receiver, pollUrl, AjaxObject, opts); this.poll.on('message', function(msg) { debug('poll message', msg); self.emit('message', msg); diff --git a/lib/transport/receiver/xhr.js b/lib/transport/receiver/xhr.js index 8ec7bc84..4bd00bd8 100644 --- a/lib/transport/receiver/xhr.js +++ b/lib/transport/receiver/xhr.js @@ -9,14 +9,15 @@ if (process.env.NODE_ENV !== 'production') { debug = require('debug')('sockjs-client:receiver:xhr'); } -function XhrReceiver(url, AjaxObject) { +function XhrReceiver(url, AjaxObject, opts) { debug(url); EventEmitter.call(this); var self = this; this.bufferPosition = 0; - this.xo = new AjaxObject('POST', url, null); + this.opts = opts; + this.xo = new AjaxObject('POST', url, null, opts); this.xo.on('chunk', this._chunkHandler.bind(this)); this.xo.once('finish', function(status, text) { debug('finish', status, text); diff --git a/lib/transport/sender/xhr-local.js b/lib/transport/sender/xhr-local.js index defe4d25..1f71e675 100644 --- a/lib/transport/sender/xhr-local.js +++ b/lib/transport/sender/xhr-local.js @@ -4,10 +4,10 @@ var inherits = require('inherits') , XhrDriver = require('../driver/xhr') ; -function XHRLocalObject(method, url, payload /*, opts */) { - XhrDriver.call(this, method, url, payload, { - noCredentials: true - }); +function XHRLocalObject(method, url, payload, opts) { + opts = opts || {}; + opts.noCredentials = true; + XhrDriver.call(this, method, url, payload, opts); } inherits(XHRLocalObject, XhrDriver); diff --git a/lib/transport/websocket.js b/lib/transport/websocket.js index b092e286..37fc4b45 100644 --- a/lib/transport/websocket.js +++ b/lib/transport/websocket.js @@ -54,6 +54,9 @@ function WebSocketTransport(transUrl, ignore, options) { self.emit('close', 1006, 'WebSocket connection broken'); self._cleanup(); }; + this.ws.on('drain', function() { + self.emit('drain'); + }) } inherits(WebSocketTransport, EventEmitter); @@ -61,7 +64,7 @@ inherits(WebSocketTransport, EventEmitter); WebSocketTransport.prototype.send = function(data) { var msg = '[' + data + ']'; debug('send', msg); - this.ws.send(msg); + return this.ws.send(msg); }; WebSocketTransport.prototype.close = function() { diff --git a/lib/transport/xdr-polling.js b/lib/transport/xdr-polling.js index da84404d..3d79c9f4 100644 --- a/lib/transport/xdr-polling.js +++ b/lib/transport/xdr-polling.js @@ -7,11 +7,11 @@ var inherits = require('inherits') , XDRObject = require('./sender/xdr') ; -function XdrPollingTransport(transUrl) { +function XdrPollingTransport(transUrl, ignore, opts) { if (!XDRObject.enabled) { throw new Error('Transport created when disabled'); } - AjaxBasedTransport.call(this, transUrl, '/xhr', XhrReceiver, XDRObject); + AjaxBasedTransport.call(this, transUrl, '/xhr', XhrReceiver, XDRObject, opts); } inherits(XdrPollingTransport, AjaxBasedTransport); diff --git a/lib/transport/xdr-streaming.js b/lib/transport/xdr-streaming.js index 0fede496..3ff46c38 100644 --- a/lib/transport/xdr-streaming.js +++ b/lib/transport/xdr-streaming.js @@ -10,11 +10,11 @@ var inherits = require('inherits') // http://stackoverflow.com/questions/1641507/detect-browser-support-for-cross-domain-xmlhttprequests // http://hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/ -function XdrStreamingTransport(transUrl) { +function XdrStreamingTransport(transUrl, ignore, opts) { if (!XDRObject.enabled) { throw new Error('Transport created when disabled'); } - AjaxBasedTransport.call(this, transUrl, '/xhr_streaming', XhrReceiver, XDRObject); + AjaxBasedTransport.call(this, transUrl, '/xhr_streaming', XhrReceiver, XDRObject, opts); } inherits(XdrStreamingTransport, AjaxBasedTransport); diff --git a/lib/transport/xhr-polling.js b/lib/transport/xhr-polling.js index a07ad9f1..cc9e00ec 100644 --- a/lib/transport/xhr-polling.js +++ b/lib/transport/xhr-polling.js @@ -7,11 +7,11 @@ var inherits = require('inherits') , XHRLocalObject = require('./sender/xhr-local') ; -function XhrPollingTransport(transUrl) { +function XhrPollingTransport(transUrl, ignore, opts) { if (!XHRLocalObject.enabled && !XHRCorsObject.enabled) { throw new Error('Transport created when disabled'); } - AjaxBasedTransport.call(this, transUrl, '/xhr', XhrReceiver, XHRCorsObject); + AjaxBasedTransport.call(this, transUrl, '/xhr', XhrReceiver, XHRCorsObject, opts); } inherits(XhrPollingTransport, AjaxBasedTransport); diff --git a/lib/transport/xhr-streaming.js b/lib/transport/xhr-streaming.js index 4c3b54b8..05576f14 100644 --- a/lib/transport/xhr-streaming.js +++ b/lib/transport/xhr-streaming.js @@ -8,11 +8,11 @@ var inherits = require('inherits') , browser = require('../utils/browser') ; -function XhrStreamingTransport(transUrl) { +function XhrStreamingTransport(transUrl, ignore, opts) { if (!XHRLocalObject.enabled && !XHRCorsObject.enabled) { throw new Error('Transport created when disabled'); } - AjaxBasedTransport.call(this, transUrl, '/xhr_streaming', XhrReceiver, XHRCorsObject); + AjaxBasedTransport.call(this, transUrl, '/xhr_streaming', XhrReceiver, XHRCorsObject, opts); } inherits(XhrStreamingTransport, AjaxBasedTransport); diff --git a/lib/version.js b/lib/version.js index 8bd1caa6..128b83a9 100644 --- a/lib/version.js +++ b/lib/version.js @@ -1 +1 @@ -module.exports = '1.6.1'; +module.exports = '1.6.2';