lib/faye-browser.js in faye-0.8.8 vs lib/faye-browser.js in faye-0.8.9
- old
+ new
@@ -1,31 +1,30 @@
-var Faye = (typeof Faye === 'object') ? Faye : {};
-if (typeof window !== 'undefined') window.Faye = Faye;
+'use strict';
-Faye.extend = function(dest, source, overwrite) {
- if (!source) return dest;
- for (var key in source) {
- if (!source.hasOwnProperty(key)) continue;
- if (dest.hasOwnProperty(key) && overwrite === false) continue;
- if (dest[key] !== source[key])
- dest[key] = source[key];
- }
- return dest;
-};
+var Faye = {
+ VERSION: '0.8.9',
-Faye.extend(Faye, {
- VERSION: '0.8.8',
-
BAYEUX_VERSION: '1.0',
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'],
-
- ENV: (function() { return this })(),
-
+
+ ENV: (typeof global === 'undefined') ? window : global,
+
+ extend: function(dest, source, overwrite) {
+ if (!source) return dest;
+ for (var key in source) {
+ if (!source.hasOwnProperty(key)) continue;
+ if (dest.hasOwnProperty(key) && overwrite === false) continue;
+ if (dest[key] !== source[key])
+ dest[key] = source[key];
+ }
+ return dest;
+ },
+
random: function(bitlength) {
bitlength = bitlength || this.ID_LENGTH;
if (bitlength > 32) {
var parts = Math.ceil(bitlength / 32),
string = '';
@@ -35,20 +34,20 @@
return result;
}
var limit = Math.pow(2, bitlength) - 1,
maxSize = limit.toString(36).length,
string = Math.floor(Math.random() * limit).toString(36);
-
+
while (string.length < maxSize) string = '0' + string;
return string;
},
-
+
clientIdFromMessages: function(messages) {
var first = [].concat(messages)[0];
return first && first.clientId;
},
-
+
copyObject: function(object) {
var clone, i, key;
if (object instanceof Array) {
clone = [];
i = object.length;
@@ -60,32 +59,32 @@
return clone;
} else {
return object;
}
},
-
+
commonElement: function(lista, listb) {
for (var i = 0, n = lista.length; i < n; i++) {
if (this.indexOf(listb, lista[i]) !== -1)
return lista[i];
}
return null;
},
-
+
indexOf: function(list, needle) {
if (list.indexOf) return list.indexOf(needle);
-
+
for (var i = 0, n = list.length; i < n; i++) {
if (list[i] === needle) return i;
}
return -1;
},
-
+
map: function(object, callback, context) {
if (object.map) return object.map(callback, context);
var result = [];
-
+
if (object instanceof Array) {
for (var i = 0, n = object.length; i < n; i++) {
result.push(callback.call(context || null, object[i], i));
}
} else {
@@ -94,20 +93,20 @@
result.push(callback.call(context || null, key, object[key]));
}
}
return result;
},
-
+
filter: function(array, callback, context) {
var result = [];
for (var i = 0, n = array.length; i < n; i++) {
if (callback.call(context || null, array[i], i))
result.push(array[i]);
}
return result;
},
-
+
asyncEach: function(list, iterator, callback, context) {
var n = list.length,
i = -1,
calls = 0,
looping = false;
@@ -130,83 +129,86 @@
calls += 1;
loop();
};
resume();
},
-
+
// http://assanka.net/content/tech/2009/09/02/json2-js-vs-prototype/
toJSON: function(object) {
if (this.stringify)
return this.stringify(object, function(key, value) {
return (this[key] instanceof Array)
? this[key]
: value;
});
-
+
return JSON.stringify(object);
},
-
+
logger: function(message) {
if (typeof console !== 'undefined') console.log(message);
},
-
+
timestamp: function() {
var date = new Date(),
year = date.getFullYear(),
month = date.getMonth() + 1,
day = date.getDate(),
hour = date.getHours(),
minute = date.getMinutes(),
second = date.getSeconds();
-
+
var pad = function(n) {
return n < 10 ? '0' + n : String(n);
};
-
+
return pad(year) + '-' + pad(month) + '-' + pad(day) + ' ' +
pad(hour) + ':' + pad(minute) + ':' + pad(second);
}
-});
+};
+if (typeof window !== 'undefined')
+ window.Faye = Faye;
+
Faye.Class = function(parent, methods) {
if (typeof parent !== 'function') {
methods = parent;
parent = Object;
}
-
+
var klass = function() {
if (!this.initialize) return this;
return this.initialize.apply(this, arguments) || this;
};
-
+
var bridge = function() {};
bridge.prototype = parent.prototype;
-
+
klass.prototype = new bridge();
Faye.extend(klass.prototype, methods);
-
+
return klass;
};
Faye.Namespace = Faye.Class({
initialize: function() {
this._used = {};
},
-
+
exists: function(id) {
return this._used.hasOwnProperty(id);
},
-
+
generate: function() {
var name = Faye.random();
while (this._used.hasOwnProperty(name))
name = Faye.random();
return this._used[name] = name;
},
-
+
release: function(id) {
delete this._used[id];
}
});
@@ -215,11 +217,11 @@
initialize: function(code, params, message) {
this.code = code;
this.params = Array.prototype.slice.call(params);
this.message = message;
},
-
+
toString: function() {
return this.code + ':' +
this.params.join(',') + ':' +
this.message;
}
@@ -276,26 +278,26 @@
Faye.Deferrable = {
callback: function(callback, context) {
if (!callback) return;
-
+
if (this._deferredStatus === 'succeeded')
return callback.apply(context, this._deferredArgs);
-
+
this._callbacks = this._callbacks || [];
this._callbacks.push([callback, context]);
},
-
+
timeout: function(seconds, message) {
var _this = this;
var timer = Faye.ENV.setTimeout(function() {
_this.setDeferredStatus('failed', message);
}, seconds * 1000);
this._timer = timer;
},
-
+
errback: function(callback, context) {
if (!callback) return;
if (this._deferredStatus === 'failed')
return callback.apply(context, this._deferredArgs);
@@ -309,21 +311,21 @@
Faye.ENV.clearTimeout(this._timer);
var args = Array.prototype.slice.call(arguments),
status = args.shift(),
callbacks;
-
+
this._deferredStatus = status;
this._deferredArgs = args;
-
+
if (status === 'succeeded')
callbacks = this._callbacks;
else if (status === 'failed')
callbacks = this._errbacks;
-
+
if (!callbacks) return;
-
+
var callback;
while (callback = callbacks.shift())
callback[0].apply(callback[1], this._deferredArgs);
}
};
@@ -332,43 +334,43 @@
Faye.Publisher = {
countListeners: function(eventType) {
if (!this._subscribers || !this._subscribers[eventType]) return 0;
return this._subscribers[eventType].length;
},
-
+
bind: function(eventType, listener, context) {
this._subscribers = this._subscribers || {};
var list = this._subscribers[eventType] = this._subscribers[eventType] || [];
list.push([listener, context]);
},
-
+
unbind: function(eventType, listener, context) {
if (!this._subscribers || !this._subscribers[eventType]) return;
-
+
if (!listener) {
delete this._subscribers[eventType];
return;
}
var list = this._subscribers[eventType],
i = list.length;
-
+
while (i--) {
if (listener !== list[i][0]) continue;
if (context && list[i][1] !== context) continue;
list.splice(i,1);
}
},
-
+
trigger: function() {
var args = Array.prototype.slice.call(arguments),
eventType = args.shift();
-
+
if (!this._subscribers || !this._subscribers[eventType]) return;
-
+
var listeners = this._subscribers[eventType].slice(),
listener;
-
+
for (var i = 0, n = listeners.length; i < n; i++) {
listener = listeners[i];
listener[0].apply(listener[1], args);
}
}
@@ -383,11 +385,11 @@
this._timeouts[name] = Faye.ENV.setTimeout(function() {
delete self._timeouts[name];
callback.call(context);
}, 1000 * delay);
},
-
+
removeTimeout: function(name) {
this._timeouts = this._timeouts || {};
var timeout = this._timeouts[name];
if (!timeout) return;
clearTimeout(timeout);
@@ -401,39 +403,39 @@
error: 3,
warn: 2,
info: 1,
debug: 0
},
-
+
logLevel: 'error',
-
+
log: function(messageArgs, level) {
if (!Faye.logger) return;
-
+
var levels = Faye.Logging.LOG_LEVELS;
if (levels[Faye.Logging.logLevel] > levels[level]) return;
-
+
var messageArgs = Array.prototype.slice.apply(messageArgs),
banner = ' [' + level.toUpperCase() + '] [Faye',
klass = this.className,
-
+
message = messageArgs.shift().replace(/\?/g, function() {
try {
return Faye.toJSON(messageArgs.shift());
} catch (e) {
return '[Object]';
}
});
-
+
for (var key in Faye) {
if (klass) continue;
if (typeof Faye[key] !== 'function') continue;
if (this instanceof Faye[key]) klass = key;
}
if (klass) banner += '.' + klass;
banner += '] ';
-
+
Faye.logger(Faye.timestamp() + banner + message);
}
};
(function() {
@@ -476,33 +478,33 @@
addExtension: function(extension) {
this._extensions = this._extensions || [];
this._extensions.push(extension);
if (extension.added) extension.added(this);
},
-
+
removeExtension: function(extension) {
if (!this._extensions) return;
var i = this._extensions.length;
while (i--) {
if (this._extensions[i] !== extension) continue;
this._extensions.splice(i,1);
if (extension.removed) extension.removed(this);
}
},
-
+
pipeThroughExtensions: function(stage, message, callback, context) {
this.debug('Passing through ? extensions: ?', stage, message);
if (!this._extensions) return callback.call(context, message);
var extensions = this._extensions.slice();
-
+
var pipe = function(message) {
if (!message) return callback.call(context, message);
-
+
var extension = extensions.shift();
if (!extension) return callback.call(context, message);
-
+
if (extension[stage]) extension[stage](message, pipe);
else pipe(message);
};
pipe(message);
}
@@ -512,15 +514,15 @@
Faye.Channel = Faye.Class({
initialize: function(name) {
this.id = this.name = name;
},
-
+
push: function(message) {
this.trigger('message', message);
},
-
+
isUnused: function() {
return this.countListeners('message') === 0;
}
});
@@ -530,105 +532,105 @@
HANDSHAKE: '/meta/handshake',
CONNECT: '/meta/connect',
SUBSCRIBE: '/meta/subscribe',
UNSUBSCRIBE: '/meta/unsubscribe',
DISCONNECT: '/meta/disconnect',
-
+
META: 'meta',
SERVICE: 'service',
-
+
expand: function(name) {
var segments = this.parse(name),
channels = ['/**', name];
-
+
var copy = segments.slice();
copy[copy.length - 1] = '*';
channels.push(this.unparse(copy));
-
+
for (var i = 1, n = segments.length; i < n; i++) {
copy = segments.slice(0, i);
copy.push('**');
channels.push(this.unparse(copy));
}
-
+
return channels;
},
-
+
isValid: function(name) {
return Faye.Grammar.CHANNEL_NAME.test(name) ||
Faye.Grammar.CHANNEL_PATTERN.test(name);
},
-
+
parse: function(name) {
if (!this.isValid(name)) return null;
return name.split('/').slice(1);
},
-
+
unparse: function(segments) {
return '/' + segments.join('/');
},
-
+
isMeta: function(name) {
var segments = this.parse(name);
return segments ? (segments[0] === this.META) : null;
},
-
+
isService: function(name) {
var segments = this.parse(name);
return segments ? (segments[0] === this.SERVICE) : null;
},
-
+
isSubscribable: function(name) {
if (!this.isValid(name)) return null;
return !this.isMeta(name) && !this.isService(name);
},
-
+
Set: Faye.Class({
initialize: function() {
this._channels = {};
},
-
+
getKeys: function() {
var keys = [];
for (var key in this._channels) keys.push(key);
return keys;
},
-
+
remove: function(name) {
delete this._channels[name];
},
-
+
hasSubscription: function(name) {
return this._channels.hasOwnProperty(name);
},
-
+
subscribe: function(names, callback, context) {
if (!callback) return;
var name;
for (var i = 0, n = names.length; i < n; i++) {
name = names[i];
var channel = this._channels[name] = this._channels[name] || new Faye.Channel(name);
channel.bind('message', callback, context);
}
},
-
+
unsubscribe: function(name, callback, context) {
var channel = this._channels[name];
if (!channel) return false;
channel.unbind('message', callback, context);
-
+
if (channel.isUnused()) {
this.remove(name);
return true;
} else {
return false;
}
},
-
+
distributeMessage: function(message) {
var channels = Faye.Channel.expand(message.channel);
-
+
for (var i = 0, n = channels.length; i < n; i++) {
var channel = this._channels[channels[i]];
if (channel) channel.trigger('message', message.data);
}
}
@@ -645,17 +647,17 @@
this._channels = channels;
this._callback = callback;
this._context = context;
this._cancelled = false;
},
-
+
cancel: function() {
if (this._cancelled) return;
this._client.unsubscribe(this._channels, this._callback, this._context);
this._cancelled = true;
},
-
+
unsubscribe: function() {
this.cancel();
}
});
@@ -665,62 +667,60 @@
Faye.Client = Faye.Class({
UNCONNECTED: 1,
CONNECTING: 2,
CONNECTED: 3,
DISCONNECTED: 4,
-
+
HANDSHAKE: 'handshake',
RETRY: 'retry',
NONE: 'none',
-
+
CONNECTION_TIMEOUT: 60.0,
DEFAULT_RETRY: 5.0,
-
+
DEFAULT_ENDPOINT: '/bayeux',
INTERVAL: 0.0,
-
+
initialize: function(endpoint, options) {
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;
-
- this._selectTransport(Faye.MANDATORY_CONNECTION_TYPES);
-
+
this._state = this.UNCONNECTED;
this._channels = new Faye.Channel.Set();
this._messageId = 0;
-
+
this._responseCallbacks = {};
-
+
this._advice = {
reconnect: this.RETRY,
interval: 1000 * (this._options.interval || this.INTERVAL),
timeout: 1000 * (this._options.timeout || this.CONNECTION_TIMEOUT)
};
-
+
if (Faye.Event)
Faye.Event.on(Faye.ENV, 'beforeunload', function() {
if (Faye.indexOf(this._disabled, 'autodisconnect') < 0)
this.disconnect();
}, this);
},
-
+
disable: function(feature) {
this._disabled.push(feature);
},
-
+
setHeader: function(name, value) {
this._headers[name] = value;
},
-
+
getClientId: function() {
return this._clientId;
},
getState: function() {
@@ -737,11 +737,11 @@
// * version
// * supportedConnectionTypes
// MAY include: * minimumVersion
// * ext
// * id
- //
+ //
// Success Response Failed Response
// MUST include: * channel MUST include: * channel
// * version * successful
// * supportedConnectionTypes * error
// * clientId MAY include: * supportedConnectionTypes
@@ -752,45 +752,43 @@
// * id * id
// * authSuccessful
handshake: function(callback, context) {
if (this._advice.reconnect === this.NONE) return;
if (this._state !== this.UNCONNECTED) return;
-
+
this._state = this.CONNECTING;
var self = this;
-
+
this.info('Initiating handshake with ?', this.endpoint);
-
+ this._selectTransport(Faye.MANDATORY_CONNECTION_TYPES);
+
this._send({
channel: Faye.Channel.HANDSHAKE,
version: Faye.BAYEUX_VERSION,
supportedConnectionTypes: [this._transport.connectionType]
-
+
}, function(response) {
-
+
if (response.successful) {
this._state = this.CONNECTED;
this._clientId = response.clientId;
-
- var connectionTypes = Faye.filter(response.supportedConnectionTypes, function(connType) {
- return Faye.indexOf(this._disabled, connType) < 0;
- }, this);
- this._selectTransport(connectionTypes);
-
+
+ this._selectTransport(response.supportedConnectionTypes);
+
this.info('Handshake successful: ?', this._clientId);
-
+
this.subscribe(this._channels.getKeys(), true);
if (callback) callback.call(context);
-
+
} else {
this.info('Handshake unsuccessful');
Faye.ENV.setTimeout(function() { self.handshake(callback, context) }, this._advice.interval);
this._state = this.UNCONNECTED;
}
}, this);
},
-
+
// Request Response
// MUST include: * channel MUST include: * channel
// * clientId * successful
// * connectionType * clientId
// MAY include: * ext MAY include: * error
@@ -799,59 +797,59 @@
// * id
// * timestamp
connect: function(callback, context) {
if (this._advice.reconnect === this.NONE) return;
if (this._state === this.DISCONNECTED) return;
-
+
if (this._state === this.UNCONNECTED)
return this.handshake(function() { this.connect(callback, context) }, this);
-
+
this.callback(callback, context);
if (this._state !== this.CONNECTED) return;
-
+
this.info('Calling deferred actions for ?', this._clientId);
this.setDeferredStatus('succeeded');
this.setDeferredStatus('deferred');
-
+
if (this._connectRequest) return;
this._connectRequest = true;
-
+
this.info('Initiating connection for ?', this._clientId);
-
+
this._send({
channel: Faye.Channel.CONNECT,
clientId: this._clientId,
connectionType: this._transport.connectionType
-
+
}, this._cycleConnection, this);
},
-
+
// Request Response
// MUST include: * channel MUST include: * channel
// * clientId * successful
// MAY include: * ext * clientId
// * id MAY include: * error
// * ext
// * id
disconnect: function() {
if (this._state !== this.CONNECTED) return;
this._state = this.DISCONNECTED;
-
+
this.info('Disconnecting ?', this._clientId);
-
+
this._send({
channel: Faye.Channel.DISCONNECT,
clientId: this._clientId
-
+
}, function(response) {
if (response.successful) this._transport.close();
}, this);
-
+
this.info('Clearing channel listeners for ?', this._clientId);
this._channels = new Faye.Channel.Set();
},
-
+
// Request Response
// MUST include: * channel MUST include: * channel
// * clientId * successful
// * subscription * clientId
// MAY include: * ext * subscription
@@ -863,45 +861,45 @@
subscribe: function(channel, callback, context) {
if (channel instanceof Array)
return Faye.map(channel, function(c) {
return this.subscribe(c, callback, context);
}, this);
-
+
var subscription = new Faye.Subscription(this, channel, callback, context),
force = (callback === true),
hasSubscribe = this._channels.hasSubscription(channel);
-
+
if (hasSubscribe && !force) {
this._channels.subscribe([channel], callback, context);
subscription.setDeferredStatus('succeeded');
return subscription;
}
-
+
this.connect(function() {
this.info('Client ? attempting to subscribe to ?', this._clientId, channel);
if (!force) this._channels.subscribe([channel], callback, context);
-
+
this._send({
channel: Faye.Channel.SUBSCRIBE,
clientId: this._clientId,
subscription: channel
-
+
}, function(response) {
if (!response.successful) {
subscription.setDeferredStatus('failed', Faye.Error.parse(response.error));
return this._channels.unsubscribe(channel, callback, context);
}
-
+
var channels = [].concat(response.subscription);
this.info('Subscription acknowledged for ? to ?', this._clientId, channels);
subscription.setDeferredStatus('succeeded');
}, this);
}, this);
-
+
return subscription;
},
-
+
// Request Response
// MUST include: * channel MUST include: * channel
// * clientId * successful
// * subscription * clientId
// MAY include: * ext * subscription
@@ -913,43 +911,43 @@
unsubscribe: function(channel, callback, context) {
if (channel instanceof Array)
return Faye.map(channel, function(c) {
return this.unsubscribe(c, callback, context);
}, this);
-
+
var dead = this._channels.unsubscribe(channel, callback, context);
if (!dead) return;
-
+
this.connect(function() {
this.info('Client ? attempting to unsubscribe from ?', this._clientId, channel);
-
+
this._send({
channel: Faye.Channel.UNSUBSCRIBE,
clientId: this._clientId,
subscription: channel
-
+
}, function(response) {
if (!response.successful) return;
-
+
var channels = [].concat(response.subscription);
this.info('Unsubscription acknowledged for ? from ?', this._clientId, channels);
}, this);
}, this);
},
-
+
// Request Response
// MUST include: * channel MUST include: * channel
// * data * successful
// MAY include: * clientId MAY include: * id
// * id * error
// * ext * ext
publish: function(channel, data) {
var publication = new Faye.Publication();
-
+
this.connect(function() {
this.info('Client ? queueing published message to ?: ?', this._clientId, channel, data);
-
+
this._send({
channel: channel,
data: data,
clientId: this._clientId
}, function(response) {
@@ -957,91 +955,94 @@
publication.setDeferredStatus('succeeded');
else
publication.setDeferredStatus('failed', Faye.Error.parse(response.error));
}, this);
}, this);
-
+
return publication;
},
-
+
receiveMessage: function(message) {
this.pipeThroughExtensions('incoming', message, function(message) {
if (!message) return;
-
+
if (message.advice) this._handleAdvice(message.advice);
this._deliverMessage(message);
-
+
if (message.successful === undefined) return;
-
+
var callback = this._responseCallbacks[message.id];
if (!callback) return;
-
+
delete this._responseCallbacks[message.id];
callback[0].call(callback[1], message);
}, this);
},
-
+
_selectTransport: function(transportTypes) {
- Faye.Transport.get(this, transportTypes, function(transport) {
+ Faye.Transport.get(this, transportTypes, this._disabled, function(transport) {
this.debug('Selected ? transport for ?', transport.connectionType, transport.endpoint);
-
+
+ if (transport === this._transport) return;
+ if (this._transport) this._transport.close();
+
this._transport = transport;
this._transport.cookies = this._cookies;
this._transport.headers = this._headers;
-
+
transport.bind('down', function() {
if (this._transportUp !== undefined && !this._transportUp) return;
this._transportUp = false;
this.trigger('transport:down');
}, this);
-
+
transport.bind('up', function() {
if (this._transportUp !== undefined && this._transportUp) return;
this._transportUp = true;
this.trigger('transport:up');
}, this);
}, this);
},
-
+
_send: function(message, callback, context) {
message.id = this._generateMessageId();
if (callback) this._responseCallbacks[message.id] = [callback, context];
this.pipeThroughExtensions('outgoing', message, function(message) {
if (!message) return;
this._transport.send(message, this._advice.timeout / 1000);
}, this);
},
-
+
_generateMessageId: function() {
this._messageId += 1;
if (this._messageId >= Math.pow(2,32)) this._messageId = 0;
return this._messageId.toString(36);
},
_handleAdvice: function(advice) {
Faye.extend(this._advice, advice);
-
+
if (this._advice.reconnect === this.HANDSHAKE && this._state !== this.DISCONNECTED) {
this._state = this.UNCONNECTED;
this._clientId = null;
this._cycleConnection();
}
},
-
+
_deliverMessage: function(message) {
if (!message.channel || message.data === undefined) return;
this.info('Client ? calling listeners for ? with ?', this._clientId, message.channel, message.data);
this._channels.distributeMessage(message);
},
-
+
_teardownConnection: function() {
if (!this._connectRequest) return;
this._connectRequest = null;
this.info('Closed connection for ?', this._clientId);
},
-
+
_cycleConnection: function() {
this._teardownConnection();
var self = this;
Faye.ENV.setTimeout(function() { self.connect() }, this._advice.interval);
}
@@ -1060,13 +1061,13 @@
initialize: function(client, endpoint) {
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);
if (!this.batching) return this.request([message], timeout);
@@ -1091,115 +1092,113 @@
if (this._outbox.length > 1 && this._connectMessage)
this._connectMessage.advice = {timeout: 0};
this.request(this._outbox, this._timeout);
-
+
this._connectMessage = null;
this._outbox = [];
},
-
+
receive: function(responses) {
this.debug('Client ? received from ?: ?',
this._client._clientId, this.endpoint, responses);
-
+
for (var i = 0, n = responses.length; i < n; i++) {
this._client.receiveMessage(responses[i]);
}
},
-
+
retry: function(message, timeout) {
var called = false,
retry = this._client.retry * 1000,
self = this;
-
+
return function() {
if (called) return;
called = true;
Faye.ENV.setTimeout(function() { self.request(message, timeout) }, retry);
};
}
-
+
}), {
MAX_URL_LENGTH: 2048,
-
- get: function(client, connectionTypes, callback, context) {
+
+ get: function(client, allowed, disabled, 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) {
+
+ if (Faye.indexOf(disabled, connType) >= 0)
+ return resume();
+
+ if (Faye.indexOf(allowed, connType) < 0) {
klass.isUsable(client, connEndpoint, function() {});
return 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);
});
},
-
+
register: function(type, klass) {
this._transports.push([type, klass]);
klass.prototype.connectionType = type;
},
-
- _transports: [],
-
- supportedConnectionTypes: function() {
- return Faye.map(this._transports, function(pair) { return pair[0] });
- }
+
+ _transports: []
});
Faye.extend(Faye.Transport.prototype, Faye.Logging);
Faye.extend(Faye.Transport.prototype, Faye.Publisher);
Faye.extend(Faye.Transport.prototype, Faye.Timeouts);
Faye.Event = {
_registry: [],
-
+
on: function(element, eventName, callback, context) {
var wrapped = function() { callback.call(context) };
-
+
if (element.addEventListener)
element.addEventListener(eventName, wrapped, false);
else
element.attachEvent('on' + eventName, wrapped);
-
+
this._registry.push({
_element: element,
_type: eventName,
_callback: callback,
_context: context,
_handler: wrapped
});
},
-
+
detach: function(element, eventName, callback, context) {
var i = this._registry.length, register;
while (i--) {
register = this._registry[i];
-
+
if ((element && element !== register._element) ||
(eventName && eventName !== register._type) ||
(callback && callback !== register._callback) ||
(context && context !== register._context))
continue;
-
+
if (register._element.removeEventListener)
register._element.removeEventListener(register._type, register._handler, false);
else
register._element.detachEvent('on' + register._type, register._handler);
-
+
this._registry.splice(i,1);
register = null;
}
}
};
@@ -1214,71 +1213,75 @@
if (!this.params.hasOwnProperty(key)) continue;
pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(this.params[key]));
}
return pairs.join('&');
},
-
+
isSameOrigin: function() {
- var host = Faye.URI.parse(Faye.ENV.location.href);
-
+ var host = Faye.URI.parse(Faye.ENV.location.href, false);
+
var external = (host.hostname !== this.hostname) ||
(host.port !== this.port) ||
(host.protocol !== this.protocol);
-
+
return !external;
},
-
+
toURL: function() {
var query = this.queryString();
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 consume = function(name, pattern, infer) {
url = url.replace(pattern, function(match) {
uri[name] = match;
return '';
});
if (uri[name] === undefined)
uri[name] = infer ? Faye.ENV.location[name] : '';
};
-
+
consume('protocol', /^https?\:/, true);
consume('host', /^\/\/[^\/]+/, true);
-
+
if (!/^\//.test(url)) url = Faye.ENV.location.pathname.replace(/[^\/]*$/, '') + url;
consume('pathname', /^\/[^\?#]*/);
consume('search', /^\?[^#]*/);
consume('hash', /^#.*/);
-
+
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 = uri.search.replace(/^\?/, ''),
- pairs = query ? query.split('&') : [],
- n = pairs.length,
- data = {};
-
- while (n--) {
- parts = pairs[n].split('=');
- data[decodeURIComponent(parts[0] || '')] = decodeURIComponent(parts[1] || '');
+
+ if (params === false) {
+ uri.params = {};
+ } else {
+ var query = uri.search.replace(/^\?/, ''),
+ pairs = query ? query.split('&') : [],
+ n = pairs.length,
+ data = {};
+
+ while (n--) {
+ parts = pairs[n].split('=');
+ data[decodeURIComponent(parts[0] || '')] = decodeURIComponent(parts[1] || '');
+ }
+ if (typeof params === 'object') Faye.extend(data, params);
+
+ uri.params = data;
}
- if (typeof params === 'object') Faye.extend(data, params);
-
- uri.params = data;
-
+
return uri;
}
});
@@ -1771,100 +1774,106 @@
UNCONNECTED: 1,
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.callback(function(socket) { socket.send(Faye.toJSON(messages)) });
this.connect();
},
-
+
close: function() {
- if (this._closed) return;
- this._closed = true;
- if (this._socket) this._socket.close();
+ if (!this._socket) return;
+ this._socket.onclose = this._socket.onerror = null;
+ this._socket.close();
+ delete this._socket;
+ this.setDeferredStatus('deferred');
+ this._state = this.UNCONNECTED;
},
-
+
connect: function() {
if (Faye.Transport.WebSocket._unloaded) return;
- if (this._closed) return;
-
+
this._state = this._state || this.UNCONNECTED;
if (this._state !== this.UNCONNECTED) return;
-
+
this._state = this.CONNECTING;
-
+
var ws = Faye.Transport.WebSocket.getClass();
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) {
- var messages = [].concat(JSON.parse(event.data));
+ var messages = JSON.parse(event.data);
+ if (!messages) return;
+ messages = [].concat(messages);
+
for (var i = 0, n = messages.length; i < n; i++) {
delete self._messages[messages[i].id];
}
self.receive(messages);
};
-
- this._socket.onclose = function() {
+
+ this._socket.onclose = this._socket.onerror = function() {
var wasConnected = (self._state === self.CONNECTED);
self.setDeferredStatus('deferred');
self._state = self.UNCONNECTED;
- delete self._socket;
-
+
+ self.close();
+
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);
}
}), {
getSocketUrl: function(endpoint) {
if (Faye.URI) endpoint = Faye.URI.parse(endpoint).toURL();
return endpoint.replace(/^http(s?):/ig, 'ws$1:');
},
-
+
getClass: function() {
return (Faye.WebSocket && Faye.WebSocket.Client) ||
Faye.ENV.WebSocket ||
Faye.ENV.MozWebSocket;
},
-
+
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];
}
@@ -1881,64 +1890,70 @@
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() {
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() {
+ if (!this._socket) return;
+ this._socket.onerror = null;
this._socket.close();
+ delete this._socket;
}
}), {
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 || {};
+ var sockets = client.transports.eventsource = client.transports.eventsource || {},
+ id = client.getClientId(),
+ endpoint = endpoint + '/' + (id || '');
+
sockets[endpoint] = sockets[endpoint] || new this(client, endpoint);
return sockets[endpoint];
}
});
@@ -1952,52 +1967,52 @@
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;
xhr.setRequestHeader(key, headers[key]);
}
-
+
var abort = function() { xhr.abort() };
Faye.Event.on(Faye.ENV, 'beforeunload', abort);
-
+
var cleanUp = function() {
Faye.Event.detach(Faye.ENV, 'beforeunload', abort);
xhr.onreadystatechange = function() {};
xhr = null;
};
-
+
xhr.onreadystatechange = function() {
if (xhr.readyState !== 4) return;
-
+
var parsedMessage = null,
status = xhr.status,
successful = ((status >= 200 && status < 300) ||
status === 304 ||
status === 1223);
-
+
if (!successful) {
cleanUp();
retry();
return self.trigger('down');
}
-
+
try {
parsedMessage = JSON.parse(xhr.responseText);
} catch (e) {}
-
+
cleanUp();
-
+
if (parsedMessage) {
self.receive(parsedMessage);
self.trigger('up');
} else {
retry();
@@ -2019,60 +2034,60 @@
request: function(message, timeout) {
var xhrClass = Faye.ENV.XDomainRequest ? XDomainRequest : XMLHttpRequest,
xhr = new xhrClass(),
retry = this.retry(message, timeout),
self = this;
-
+
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;
Faye.ENV.clearTimeout(timer);
return true;
};
-
+
xhr.onload = function() {
var parsedMessage = null;
try {
parsedMessage = JSON.parse(xhr.responseText);
} catch (e) {}
-
+
cleanUp();
-
+
if (parsedMessage) {
self.receive(parsedMessage);
self.trigger('up');
} else {
retry();
self.trigger('down');
}
};
-
+
var onerror = function() {
cleanUp();
retry();
self.trigger('down');
};
var timer = Faye.ENV.setTimeout(onerror, 1.5 * 1000 * timeout);
xhr.onerror = onerror;
xhr.ontimeout = onerror;
-
+
xhr.onprogress = function() {};
xhr.send('message=' + encodeURIComponent(Faye.toJSON(message)));
}
}), {
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);
-
+
if (Faye.ENV.XMLHttpRequest) {
var xhr = new Faye.ENV.XMLHttpRequest();
return callback.call(context, xhr.withCredentials !== undefined);
}
return callback.call(context, false);
@@ -2089,53 +2104,53 @@
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(messages, timeout),
self = this;
-
+
Faye.ENV[callbackName] = function(data) {
cleanUp();
self.receive(data);
self.trigger('up');
};
-
+
var timer = Faye.ENV.setTimeout(function() {
cleanUp();
retry();
self.trigger('down');
}, 1.5 * 1000 * timeout);
-
+
var cleanUp = function() {
if (!Faye.ENV[callbackName]) return false;
Faye.ENV[callbackName] = undefined;
try { delete Faye.ENV[callbackName] } catch (e) {}
Faye.ENV.clearTimeout(timer);
script.parentNode.removeChild(script);
return true;
};
-
+
location.params.jsonp = callbackName;
script.type = 'text/javascript';
script.src = location.toURL();
head.appendChild(script);
}
}), {
_cbCount: 0,
-
+
getCallbackName: function() {
this._cbCount += 1;
return '__jsonp' + this._cbCount + '__';
},
-
+
isUsable: function(client, endpoint, callback, context) {
callback.call(context, true);
}
});