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);