lib/faye-browser.js in faye-0.8.3 vs lib/faye-browser.js in faye-0.8.4

- old
+ new

@@ -11,14 +11,14 @@ } return dest; }; Faye.extend(Faye, { - VERSION: '0.8.3', + VERSION: '0.8.4', BAYEUX_VERSION: '1.0', - ID_LENGTH: 128, + ID_LENGTH: 160, JSONP_CALLBACK: 'jsonpcallback', CONNECTION_TYPES: ['long-polling', 'cross-origin-long-polling', 'callback-polling', 'websocket', 'eventsource', 'in-process'], MANDATORY_CONNECTION_TYPES: ['long-polling', 'callback-polling', 'in-process'], @@ -28,11 +28,13 @@ bitlength = bitlength || this.ID_LENGTH; if (bitlength > 32) { var parts = Math.ceil(bitlength / 32), string = ''; while (parts--) string += this.random(32); - return string; + var chars = string.split(''), result = ''; + while (chars.length > 0) result += chars.pop(); + return result; } var limit = Math.pow(2, bitlength) - 1, maxSize = limit.toString(36).length, string = Math.floor(Math.random() * limit).toString(36); @@ -716,10 +718,11 @@ this.info('New client created for ?', endpoint); this._options = options || {}; this.endpoint = endpoint || this.DEFAULT_ENDPOINT; this.endpoints = this._options.endpoints || {}; + this.transports = {}; this._cookies = Faye.CookieJar && new Faye.CookieJar(); this._headers = {}; this._disabled = []; this.retry = this._options.retry || this.DEFAULT_RETRY; @@ -1013,10 +1016,12 @@ }, this); }, _selectTransport: function(transportTypes) { Faye.Transport.get(this, transportTypes, function(transport) { + this.debug('Selected ? transport for ?', transport.connectionType, transport.endpoint); + this._transport = transport; this._transport.cookies = this._cookies; this._transport.headers = this._headers; transport.bind('down', function() { @@ -1087,33 +1092,35 @@ Faye.Transport = Faye.extend(Faye.Class({ MAX_DELAY: 0.0, batching: true, initialize: function(client, endpoint) { - this.debug('Created new ? transport for ?', this.connectionType, endpoint); - this._client = client; - this._endpoint = endpoint; - this._outbox = []; + this._client = client; + this.endpoint = endpoint; + this._outbox = []; }, close: function() {}, send: function(message, timeout) { this.debug('Client ? sending message to ?: ?', - this._client._clientId, this._endpoint, message); + this._client._clientId, this.endpoint, message); if (!this.batching) return this.request([message], timeout); this._outbox.push(message); this._timeout = timeout; if (message.channel === Faye.Channel.HANDSHAKE) - return this.flush(); + return this.addTimeout('publish', 0.01, this.flush, this); if (message.channel === Faye.Channel.CONNECT) this._connectMessage = message; + if (this.shouldFlush && this.shouldFlush(this._outbox)) + return this.flush(); + this.addTimeout('publish', this.MAX_DELAY, this.flush, this); }, flush: function() { this.removeTimeout('publish'); @@ -1127,11 +1134,11 @@ this._outbox = []; }, receive: function(responses) { this.debug('Client ? received from ?: ?', - this._client._clientId, this._endpoint, responses); + this._client._clientId, this.endpoint, responses); for (var i = 0, n = responses.length; i < n; i++) { this._client.receiveMessage(responses[i]); } }, @@ -1147,23 +1154,29 @@ Faye.ENV.setTimeout(function() { self.request(message, timeout) }, retry); }; } }), { + MAX_URL_LENGTH: 2048, + get: function(client, connectionTypes, callback, context) { var endpoint = client.endpoint; if (connectionTypes === undefined) connectionTypes = this.supportedConnectionTypes(); Faye.asyncEach(this._transports, function(pair, resume) { var connType = pair[0], klass = pair[1], connEndpoint = client.endpoints[connType] || endpoint; - if (Faye.indexOf(connectionTypes, connType) < 0) return resume(); + if (Faye.indexOf(connectionTypes, connType) < 0) { + klass.isUsable(client, connEndpoint, function() {}); + return resume(); + } - klass.isUsable(connEndpoint, function(isUsable) { - if (isUsable) callback.call(context, new klass(client, connEndpoint)); - else resume(); + klass.isUsable(client, connEndpoint, function(isUsable) { + if (!isUsable) return resume(); + var transport = klass.hasOwnProperty('create') ? klass.create(client, connEndpoint) : new klass(client, connEndpoint); + callback.call(context, transport); }); }, function() { throw new Error('Could not find a usable connection type for ' + endpoint); }); }, @@ -1238,11 +1251,11 @@ pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(this.params[key])); } return pairs.join('&'); }, - isLocal: function() { + isSameOrigin: function() { var host = Faye.URI.parse(Faye.ENV.location.href); var external = (host.hostname !== this.hostname) || (host.port !== this.port) || (host.protocol !== this.protocol); @@ -1250,36 +1263,48 @@ return !external; }, toURL: function() { var query = this.queryString(); - return this.protocol + this.hostname + (this.port ? ':' + this.port : '') + - this.pathname + (query ? '?' + query : ''); + return this.protocol + '//' + this.hostname + (this.port ? ':' + this.port : '') + + this.pathname + (query ? '?' + query : '') + this.hash; } }), { parse: function(url, params) { if (typeof url !== 'string') return url; + var uri = new this(), parts; - var a = document.createElement('a'), - uri = new this(); + var consume = function(name, pattern) { + url = url.replace(pattern, function(match) { + uri[name] = match; + return ''; + }); + if (uri[name] === undefined) uri[name] = Faye.ENV.location[name]; + }; - a.href = url; + consume('protocol', /^https?\:/); + consume('host', /^\/\/[^\/]+/); - uri.protocol = a.protocol + '//'; - uri.hostname = a.hostname; - uri.pathname = a.pathname.replace(/^\/?/, '/'); + if (!/^\//.test(url)) url = Faye.ENV.location.pathname.replace(/[^\/]*$/, '') + url; + consume('pathname', /^\/[^\?#]*/); + consume('search', /^\?[^#]*/); + consume('hash', /^#.*/); - if (a.port === '0' || a.port === '') - uri.port = (a.protocol === 'https:') ? '443' : '80'; - else - uri.port = a.port; + if (/^\/\//.test(uri.host)) { + uri.host = uri.host.substr(2); + parts = uri.host.split(':'); + uri.hostname = parts[0]; + uri.port = parts[1] || ''; + } else { + uri.hostname = Faye.ENV.location.hostname; + uri.port = Faye.ENV.location.port; + } - var query = a.search.replace(/^\?/, ''), + var query = uri.search.replace(/^\?/, ''), pairs = query ? query.split('&') : [], n = pairs.length, - data = {}, - parts; + data = {}; while (n--) { parts = pairs[n].split('='); data[decodeURIComponent(parts[0] || '')] = decodeURIComponent(parts[1] || ''); } @@ -1782,22 +1807,24 @@ CONNECTING: 2, CONNECTED: 3, batching: false, + isUsable: function(callback, context) { + this.callback(function() { callback.call(context, true) }); + this.errback(function() { callback.call(context, false) }); + this.connect(); + }, + request: function(messages, timeout) { if (messages.length === 0) return; this._messages = this._messages || {}; for (var i = 0, n = messages.length; i < n; i++) { this._messages[messages[i].id] = messages[i]; } - this.withSocket(function(socket) { socket.send(Faye.toJSON(messages)) }); - }, - - withSocket: function(callback, context) { - this.callback(callback, context); + this.callback(function(socket) { socket.send(Faye.toJSON(messages)) }); this.connect(); }, close: function() { if (this._closed) return; @@ -1813,15 +1840,18 @@ if (this._state !== this.UNCONNECTED) return; this._state = this.CONNECTING; var ws = Faye.Transport.WebSocket.getClass(); - this._socket = new ws(Faye.Transport.WebSocket.getSocketUrl(this._endpoint)); + if (!ws) return this.setDeferredStatus('failed'); + + this._socket = new ws(Faye.Transport.WebSocket.getSocketUrl(this.endpoint)); var self = this; this._socket.onopen = function() { self._state = self.CONNECTED; + self._everConnected = true; self.setDeferredStatus('succeeded', self._socket); self.trigger('up'); }; this._socket.onmessage = function(event) { @@ -1837,24 +1867,24 @@ self.setDeferredStatus('deferred'); self._state = self.UNCONNECTED; delete self._socket; if (wasConnected) return self.resend(); + if (!self._everConnected) return self.setDeferredStatus('failed'); var retry = self._client.retry * 1000; Faye.ENV.setTimeout(function() { self.connect() }, retry); self.trigger('down'); }; }, resend: function() { + if (!this._messages) return; var messages = Faye.map(this._messages, function(id, msg) { return msg }); this.request(messages); } }), { - WEBSOCKET_TIMEOUT: 1000, - getSocketUrl: function(endpoint) { if (Faye.URI) endpoint = Faye.URI.parse(endpoint).toURL(); return endpoint.replace(/^http(s?):/ig, 'ws$1:'); }, @@ -1862,34 +1892,18 @@ return (Faye.WebSocket && Faye.WebSocket.Client) || Faye.ENV.WebSocket || Faye.ENV.MozWebSocket; }, - isUsable: function(endpoint, callback, context) { - var ws = this.getClass(); - if (!ws) return callback.call(context, false); - - var connected = false, - called = false, - socketUrl = this.getSocketUrl(endpoint), - socket = new ws(socketUrl); - - socket.onopen = function() { - connected = true; - socket.close(); - callback.call(context, true); - called = true; - socket = null; - }; - - var notconnected = function() { - if (!called && !connected) callback.call(context, false); - called = true; - }; - - socket.onclose = socket.onerror = notconnected; - Faye.ENV.setTimeout(notconnected, this.WEBSOCKET_TIMEOUT); + isUsable: function(client, endpoint, callback, context) { + this.create(client, endpoint).isUsable(callback, context); + }, + + create: function(client, endpoint) { + var sockets = client.transports.websocket = client.transports.websocket || {}; + sockets[endpoint] = sockets[endpoint] || new this(client, endpoint); + return sockets[endpoint]; } }); Faye.extend(Faye.Transport.WebSocket.prototype, Faye.Deferrable); Faye.Transport.register('websocket', Faye.Transport.WebSocket); @@ -1901,59 +1915,86 @@ Faye.Transport.EventSource = Faye.extend(Faye.Class(Faye.Transport, { initialize: function(client, endpoint) { Faye.Transport.prototype.initialize.call(this, client, endpoint); + if (!Faye.ENV.EventSource) return this.setDeferredStatus('failed'); + this._xhr = new Faye.Transport.XHR(client, endpoint); var socket = new EventSource(endpoint + '/' + client.getClientId()), self = this; socket.onopen = function() { + self._everConnected = true; + self.setDeferredStatus('succeeded'); self.trigger('up'); }; socket.onerror = function() { - self.trigger('down'); + if (self._everConnected) { + self.trigger('down'); + } else { + self.setDeferredStatus('failed'); + socket.close(); + } }; socket.onmessage = function(event) { self.receive(JSON.parse(event.data)); + self.trigger('up'); }; this._socket = socket; }, + isUsable: function(callback, context) { + this.callback(function() { callback.call(context, true) }); + this.errback(function() { callback.call(context, false) }); + }, + request: function(message, timeout) { this._xhr.request(message, timeout); }, close: function() { this._socket.close(); } }), { - isUsable: function(endpoint, callback, context) { - Faye.Transport.XHR.isUsable(endpoint, function(usable) { - callback.call(context, usable && Faye.ENV.EventSource); - }); + isUsable: function(client, endpoint, callback, context) { + var id = client.getClientId(); + if (!id) return callback.call(context, false); + + Faye.Transport.XHR.isUsable(client, endpoint, function(usable) { + if (!usable) return callback.call(context, false); + this.create(client, endpoint).isUsable(callback, context); + }, this); + }, + + create: function(client, endpoint) { + var sockets = client.transports.eventsource = client.transports.eventsource || {}; + sockets[endpoint] = sockets[endpoint] || new this(client, endpoint); + return sockets[endpoint]; } }); +Faye.extend(Faye.Transport.EventSource.prototype, Faye.Deferrable); Faye.Transport.register('eventsource', Faye.Transport.EventSource); Faye.Transport.XHR = Faye.extend(Faye.Class(Faye.Transport, { request: function(message, timeout) { var retry = this.retry(message, timeout), - path = Faye.URI.parse(this._endpoint).pathname, + path = Faye.URI.parse(this.endpoint).pathname, self = this, xhr = Faye.ENV.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest(); xhr.open('POST', path, true); xhr.setRequestHeader('Content-Type', 'application/json'); + xhr.setRequestHeader('Pragma', 'no-cache'); xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); var headers = this.headers; for (var key in headers) { if (!headers.hasOwnProperty(key)) continue; @@ -2000,12 +2041,12 @@ }; xhr.send(Faye.toJSON(message)); } }), { - isUsable: function(endpoint, callback, context) { - callback.call(context, Faye.URI.parse(endpoint).isLocal()); + isUsable: function(client, endpoint, callback, context) { + callback.call(context, Faye.URI.parse(endpoint).isSameOrigin()); } }); Faye.Transport.register('long-polling', Faye.Transport.XHR); @@ -2014,11 +2055,12 @@ var xhrClass = Faye.ENV.XDomainRequest ? XDomainRequest : XMLHttpRequest, xhr = new xhrClass(), retry = this.retry(message, timeout), self = this; - xhr.open('POST', this._endpoint, true); + xhr.open('POST', this.endpoint, true); + if (xhr.setRequestHeader) xhr.setRequestHeader('Pragma', 'no-cache'); var cleanUp = function() { if (!xhr) return false; xhr.onload = xhr.onerror = xhr.ontimeout = xhr.onprogress = null; xhr = null; @@ -2054,12 +2096,12 @@ xhr.onprogress = function() {}; xhr.send('message=' + encodeURIComponent(Faye.toJSON(message))); } }), { - isUsable: function(endpoint, callback, context) { - if (Faye.URI.parse(endpoint).isLocal()) + isUsable: function(client, endpoint, callback, context) { + if (Faye.URI.parse(endpoint).isSameOrigin()) return callback.call(context, false); if (Faye.ENV.XDomainRequest) return callback.call(context, Faye.URI.parse(endpoint).protocol === Faye.URI.parse(Faye.ENV.location).protocol); @@ -2074,17 +2116,26 @@ Faye.Transport.register('cross-origin-long-polling', Faye.Transport.CORS); Faye.Transport.JSONP = Faye.extend(Faye.Class(Faye.Transport, { - request: function(message, timeout) { - var params = {message: Faye.toJSON(message)}, + shouldFlush: function(messages) { + var params = { + message: Faye.toJSON(messages), + jsonp: '__jsonp' + Faye.Transport.JSONP._cbCount + '__' + }; + var location = Faye.URI.parse(this.endpoint, params).toURL(); + return location.length >= Faye.Transport.MAX_URL_LENGTH; + }, + + request: function(messages, timeout) { + var params = {message: Faye.toJSON(messages)}, head = document.getElementsByTagName('head')[0], script = document.createElement('script'), callbackName = Faye.Transport.JSONP.getCallbackName(), - location = Faye.URI.parse(this._endpoint, params), - retry = this.retry(message, timeout), + location = Faye.URI.parse(this.endpoint, params), + retry = this.retry(messages, timeout), self = this; Faye.ENV[callbackName] = function(data) { cleanUp(); self.receive(data); @@ -2117,10 +2168,10 @@ getCallbackName: function() { this._cbCount += 1; return '__jsonp' + this._cbCount + '__'; }, - isUsable: function(endpoint, callback, context) { + isUsable: function(client, endpoint, callback, context) { callback.call(context, true); } }); Faye.Transport.register('callback-polling', Faye.Transport.JSONP);