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