|
'use strict'; |
|
|
|
var Faye = { |
|
VERSION: '0.8.9', |
|
|
|
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: (typeof window !== '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 = ''; |
|
while (parts--) string += this.random(32); |
|
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); |
|
|
|
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; |
|
while (i--) clone[i] = Faye.copyObject(object[i]); |
|
return clone; |
|
} else if (typeof object === 'object') { |
|
clone = (object === null) ? null : {}; |
|
for (key in object) clone[key] = Faye.copyObject(object[key]); |
|
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 { |
|
for (var key in object) { |
|
if (!object.hasOwnProperty(key)) continue; |
|
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; |
|
|
|
var iterate = function() { |
|
calls -= 1; |
|
i += 1; |
|
if (i === n) return callback && callback.call(context); |
|
iterator(list[i], resume); |
|
}; |
|
|
|
var loop = function() { |
|
if (looping) return; |
|
looping = true; |
|
while (calls > 0) iterate(); |
|
looping = false; |
|
}; |
|
|
|
var resume = function() { |
|
calls += 1; |
|
loop(); |
|
}; |
|
resume(); |
|
}, |
|
|
|
// http://assanka.net/content/tech/2009/09/02/json2-js-vs-prototype/ |
|
toJSON: function(object) { |
|
return JSON.stringify(object, function(key, value) { |
|
return (this[key] instanceof Array) ? this[key] : value; |
|
}); |
|
}, |
|
|
|
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 module !== 'undefined') |
|
module.exports = Faye; |
|
else 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]; |
|
} |
|
}); |
|
|
|
Faye.Error = Faye.Class({ |
|
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; |
|
} |
|
}); |
|
|
|
Faye.Error.parse = function(message) { |
|
message = message || ''; |
|
if (!Faye.Grammar.ERROR.test(message)) return new this(null, [], message); |
|
|
|
var parts = message.split(':'), |
|
code = parseInt(parts[0]), |
|
params = parts[1].split(','), |
|
message = parts[2]; |
|
|
|
return new this(code, params, message); |
|
}; |
|
|
|
|
|
|
|
|
|
Faye.Error.versionMismatch = function() { |
|
return new this(300, arguments, 'Version mismatch').toString(); |
|
}; |
|
|
|
Faye.Error.conntypeMismatch = function() { |
|
return new this(301, arguments, 'Connection types not supported').toString(); |
|
}; |
|
|
|
Faye.Error.extMismatch = function() { |
|
return new this(302, arguments, 'Extension mismatch').toString(); |
|
}; |
|
|
|
Faye.Error.badRequest = function() { |
|
return new this(400, arguments, 'Bad request').toString(); |
|
}; |
|
|
|
Faye.Error.clientUnknown = function() { |
|
return new this(401, arguments, 'Unknown client').toString(); |
|
}; |
|
|
|
Faye.Error.parameterMissing = function() { |
|
return new this(402, arguments, 'Missing required parameter').toString(); |
|
}; |
|
|
|
Faye.Error.channelForbidden = function() { |
|
return new this(403, arguments, 'Forbidden channel').toString(); |
|
}; |
|
|
|
Faye.Error.channelUnknown = function() { |
|
return new this(404, arguments, 'Unknown channel').toString(); |
|
}; |
|
|
|
Faye.Error.channelInvalid = function() { |
|
return new this(405, arguments, 'Invalid channel').toString(); |
|
}; |
|
|
|
Faye.Error.extUnknown = function() { |
|
return new this(406, arguments, 'Unknown extension').toString(); |
|
}; |
|
|
|
Faye.Error.publishFailed = function() { |
|
return new this(407, arguments, 'Failed to publish').toString(); |
|
}; |
|
|
|
Faye.Error.serverError = function() { |
|
return new this(500, arguments, 'Internal server error').toString(); |
|
}; |
|
|
|
|
|
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); |
|
|
|
this._errbacks = this._errbacks || []; |
|
this._errbacks.push([callback, context]); |
|
}, |
|
|
|
setDeferredStatus: function() { |
|
if (this._timer) |
|
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); |
|
} |
|
}; |
|
|
|
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); |
|
} |
|
} |
|
}; |
|
|
|
Faye.Timeouts = { |
|
addTimeout: function(name, delay, callback, context) { |
|
this._timeouts = this._timeouts || {}; |
|
if (this._timeouts.hasOwnProperty(name)) return; |
|
var self = this; |
|
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); |
|
delete this._timeouts[name]; |
|
} |
|
}; |
|
|
|
Faye.Logging = { |
|
LOG_LEVELS: { |
|
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() { |
|
for (var key in Faye.Logging.LOG_LEVELS) |
|
(function(level, value) { |
|
Faye.Logging[level] = function() { |
|
this.log(arguments, level); |
|
}; |
|
})(key, Faye.Logging.LOG_LEVELS[key]); |
|
})(); |
|
|
|
Faye.Grammar = { |
|
CHANNEL_NAME: /^\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*$/, |
|
CHANNEL_PATTERN: /^(\/(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)))+)*\/\*{1,2}$/, |
|
ERROR: /^([0-9][0-9][0-9]:(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*(,(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)*:(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*|[0-9][0-9][0-9]::(((([a-z]|[A-Z])|[0-9])|(\-|\_|\!|\~|\(|\)|\$|\@)| |\/|\*|\.))*)$/, |
|
VERSION: /^([0-9])+(\.(([a-z]|[A-Z])|[0-9])(((([a-z]|[A-Z])|[0-9])|\-|\_))*)*$/ |
|
}; |
|
|
|
Faye.Extensible = { |
|
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); |
|
} |
|
}; |
|
|
|
Faye.extend(Faye.Extensible, Faye.Logging); |
|
|
|
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; |
|
} |
|
}); |
|
|
|
Faye.extend(Faye.Channel.prototype, Faye.Publisher); |
|
|
|
Faye.extend(Faye.Channel, { |
|
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); |
|
} |
|
} |
|
}) |
|
}); |
|
|
|
Faye.Publication = Faye.Class(Faye.Deferrable); |
|
|
|
Faye.Subscription = Faye.Class({ |
|
initialize: function(client, channels, callback, context) { |
|
this._client = client; |
|
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(); |
|
} |
|
}); |
|
|
|
Faye.extend(Faye.Subscription.prototype, Faye.Deferrable); |
|
|
|
Faye.Client = Faye.Class({ |
|
UNCONNECTED: 1, |
|
CONNECTING: 2, |
|
CONNECTED: 3, |
|
DISCONNECTED: 4, |
|
|
|
HANDSHAKE: 'handshake', |
|
RETRY: 'retry', |
|
NONE: 'none', |
|
|
|
CONNECTION_TIMEOUT: 60, |
|
DEFAULT_RETRY: 5, |
|
|
|
DEFAULT_ENDPOINT: '/bayeux', |
|
INTERVAL: 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._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() { |
|
switch (this._state) { |
|
case this.UNCONNECTED: return 'UNCONNECTED'; |
|
case this.CONNECTING: return 'CONNECTING'; |
|
case this.CONNECTED: return 'CONNECTED'; |
|
case this.DISCONNECTED: return 'DISCONNECTED'; |
|
} |
|
}, |
|
|
|
// Request |
|
// MUST include: * channel |
|
// * 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 |
|
// * successful * advice |
|
// MAY include: * minimumVersion * version |
|
// * advice * minimumVersion |
|
// * ext * ext |
|
// * 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; |
|
|
|
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 |
|
// * id * advice |
|
// * ext |
|
// * 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 |
|
// * id MAY include: * error |
|
// * advice |
|
// * ext |
|
// * id |
|
// * timestamp |
|
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 |
|
// * id MAY include: * error |
|
// * advice |
|
// * ext |
|
// * id |
|
// * timestamp |
|
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) { |
|
if (response.successful) |
|
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, 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); |
|
} |
|
}); |
|
|
|
Faye.extend(Faye.Client.prototype, Faye.Deferrable); |
|
Faye.extend(Faye.Client.prototype, Faye.Publisher); |
|
Faye.extend(Faye.Client.prototype, Faye.Logging); |
|
Faye.extend(Faye.Client.prototype, Faye.Extensible); |
|
|
|
Faye.Transport = Faye.extend(Faye.Class({ |
|
MAX_DELAY: 0, |
|
batching: true, |
|
|
|
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); |
|
|
|
this._outbox.push(message); |
|
this._timeout = timeout; |
|
|
|
if (message.channel === Faye.Channel.HANDSHAKE) |
|
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'); |
|
|
|
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, allowed, disabled, callback, context) { |
|
var endpoint = client.endpoint; |
|
|
|
Faye.asyncEach(this._transports, function(pair, resume) { |
|
var connType = pair[0], klass = pair[1], |
|
connEndpoint = client.endpoints[connType] || endpoint; |
|
|
|
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: [] |
|
}); |
|
|
|
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; |
|
} |
|
} |
|
}; |
|
|
|
Faye.Event.on(Faye.ENV, 'unload', Faye.Event.detach, Faye.Event); |
|
|
|
Faye.URI = Faye.extend(Faye.Class({ |
|
queryString: function() { |
|
var pairs = []; |
|
for (var key in this.params) { |
|
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, 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; |
|
} |
|
|
|
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; |
|
} |
|
|
|
return uri; |
|
} |
|
}); |
|
|
|
/* |
|
json2.js |
|
2012-10-08 |
|
|
|
Public Domain. |
|
|
|
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. |
|
|
|
See http://www.JSON.org/js.html |
|
|
|
|
|
This code should be minified before deployment. |
|
See http://javascript.crockford.com/jsmin.html |
|
|
|
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO |
|
NOT CONTROL. |
|
|
|
|
|
This file creates a global JSON object containing two methods: stringify |
|
and parse. |
|
|
|
JSON.stringify(value, replacer, space) |
|
value any JavaScript value, usually an object or array. |
|
|
|
replacer an optional parameter that determines how object |
|
values are stringified for objects. It can be a |
|
function or an array of strings. |
|
|
|
space an optional parameter that specifies the indentation |
|
of nested structures. If it is omitted, the text will |
|
be packed without extra whitespace. If it is a number, |
|
it will specify the number of spaces to indent at each |
|
level. If it is a string (such as '\t' or ' '), |
|
it contains the characters used to indent at each level. |
|
|
|
This method produces a JSON text from a JavaScript value. |
|
|
|
When an object value is found, if the object contains a toJSON |
|
method, its toJSON method will be called and the result will be |
|
stringified. A toJSON method does not serialize: it returns the |
|
value represented by the name/value pair that should be serialized, |
|
or undefined if nothing should be serialized. The toJSON method |
|
will be passed the key associated with the value, and this will be |
|
bound to the value |
|
|
|
For example, this would serialize Dates as ISO strings. |
|
|
|
Date.prototype.toJSON = function (key) { |
|
function f(n) { |
|
// Format integers to have at least two digits. |
|
return n < 10 ? '0' + n : n; |
|
} |
|
|
|
return this.getUTCFullYear() + '-' + |
|
f(this.getUTCMonth() + 1) + '-' + |
|
f(this.getUTCDate()) + 'T' + |
|
f(this.getUTCHours()) + ':' + |
|
f(this.getUTCMinutes()) + ':' + |
|
f(this.getUTCSeconds()) + 'Z'; |
|
}; |
|
|
|
You can provide an optional replacer method. It will be passed the |
|
key and value of each member, with this bound to the containing |
|
object. The value that is returned from your method will be |
|
serialized. If your method returns undefined, then the member will |
|
be excluded from the serialization. |
|
|
|
If the replacer parameter is an array of strings, then it will be |
|
used to select the members to be serialized. It filters the results |
|
such that only members with keys listed in the replacer array are |
|
stringified. |
|
|
|
Values that do not have JSON representations, such as undefined or |
|
functions, will not be serialized. Such values in objects will be |
|
dropped; in arrays they will be replaced with null. You can use |
|
a replacer function to replace those with JSON values. |
|
JSON.stringify(undefined) returns undefined. |
|
|
|
The optional space parameter produces a stringification of the |
|
value that is filled with line breaks and indentation to make it |
|
easier to read. |
|
|
|
If the space parameter is a non-empty string, then that string will |
|
be used for indentation. If the space parameter is a number, then |
|
the indentation will be that many spaces. |
|
|
|
Example: |
|
|
|
text = JSON.stringify(['e', {pluribus: 'unum'}]); |
|
// text is '["e",{"pluribus":"unum"}]' |
|
|
|
|
|
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); |
|
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' |
|
|
|
text = JSON.stringify([new Date()], function (key, value) { |
|
return this[key] instanceof Date ? |
|
'Date(' + this[key] + ')' : value; |
|
}); |
|
// text is '["Date(---current time---)"]' |
|
|
|
|
|
JSON.parse(text, reviver) |
|
This method parses a JSON text to produce an object or array. |
|
It can throw a SyntaxError exception. |
|
|
|
The optional reviver parameter is a function that can filter and |
|
transform the results. It receives each of the keys and values, |
|
and its return value is used instead of the original value. |
|
If it returns what it received, then the structure is not modified. |
|
If it returns undefined then the member is deleted. |
|
|
|
Example: |
|
|
|
// Parse the text. Values that look like ISO date strings will |
|
// be converted to Date objects. |
|
|
|
myData = JSON.parse(text, function (key, value) { |
|
var a; |
|
if (typeof value === 'string') { |
|
a = |
|
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); |
|
if (a) { |
|
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], |
|
+a[5], +a[6])); |
|
} |
|
} |
|
return value; |
|
}); |
|
|
|
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { |
|
var d; |
|
if (typeof value === 'string' && |
|
value.slice(0, 5) === 'Date(' && |
|
value.slice(-1) === ')') { |
|
d = new Date(value.slice(5, -1)); |
|
if (d) { |
|
return d; |
|
} |
|
} |
|
return value; |
|
}); |
|
|
|
|
|
This is a reference implementation. You are free to copy, modify, or |
|
redistribute. |
|
*/ |
|
|
|
/*jslint evil: true, regexp: true */ |
|
|
|
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, |
|
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, |
|
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, |
|
lastIndex, length, parse, prototype, push, replace, slice, stringify, |
|
test, toJSON, toString, valueOf |
|
*/ |
|
|
|
|
|
// Create a JSON object only if one does not already exist. We create the |
|
// methods in a closure to avoid creating global variables. |
|
|
|
if (typeof JSON !== 'object') { |
|
JSON = {}; |
|
} |
|
|
|
(function () { |
|
'use strict'; |
|
|
|
function f(n) { |
|
// Format integers to have at least two digits. |
|
return n < 10 ? '0' + n : n; |
|
} |
|
|
|
if (typeof Date.prototype.toJSON !== 'function') { |
|
|
|
Date.prototype.toJSON = function (key) { |
|
|
|
return isFinite(this.valueOf()) |
|
? this.getUTCFullYear() + '-' + |
|
f(this.getUTCMonth() + 1) + '-' + |
|
f(this.getUTCDate()) + 'T' + |
|
f(this.getUTCHours()) + ':' + |
|
f(this.getUTCMinutes()) + ':' + |
|
f(this.getUTCSeconds()) + 'Z' |
|
: null; |
|
}; |
|
|
|
String.prototype.toJSON = |
|
Number.prototype.toJSON = |
|
Boolean.prototype.toJSON = function (key) { |
|
return this.valueOf(); |
|
}; |
|
} |
|
|
|
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, |
|
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, |
|
gap, |
|
indent, |
|
meta = { // table of character substitutions |
|
'\b': '\\b', |
|
'\t': '\\t', |
|
'\n': '\\n', |
|
'\f': '\\f', |
|
'\r': '\\r', |
|
'"' : '\\"', |
|
'\\': '\\\\' |
|
}, |
|
rep; |
|
|
|
|
|
function quote(string) { |
|
|
|
// If the string contains no control characters, no quote characters, and no |
|
// backslash characters, then we can safely slap some quotes around it. |
|
// Otherwise we must also replace the offending characters with safe escape |
|
// sequences. |
|
|
|
escapable.lastIndex = 0; |
|
return escapable.test(string) ? '"' + string.replace(escapable, function (a) { |
|
var c = meta[a]; |
|
return typeof c === 'string' |
|
? c |
|
: '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); |
|
}) + '"' : '"' + string + '"'; |
|
} |
|
|
|
|
|
function str(key, holder) { |
|
|
|
// Produce a string from holder[key]. |
|
|
|
var i, // The loop counter. |
|
k, // The member key. |
|
v, // The member value. |
|
length, |
|
mind = gap, |
|
partial, |
|
value = holder[key]; |
|
|
|
// If the value has a toJSON method, call it to obtain a replacement value. |
|
|
|
if (value && typeof value === 'object' && |
|
typeof value.toJSON === 'function') { |
|
value = value.toJSON(key); |
|
} |
|
|
|
// If we were called with a replacer function, then call the replacer to |
|
// obtain a replacement value. |
|
|
|
if (typeof rep === 'function') { |
|
value = rep.call(holder, key, value); |
|
} |
|
|
|
// What happens next depends on the value's type. |
|
|
|
switch (typeof value) { |
|
case 'string': |
|
return quote(value); |
|
|
|
case 'number': |
|
|
|
// JSON numbers must be finite. Encode non-finite numbers as null. |
|
|
|
return isFinite(value) ? String(value) : 'null'; |
|
|
|
case 'boolean': |
|
case 'null': |
|
|
|
// If the value is a boolean or null, convert it to a string. Note: |
|
// typeof null does not produce 'null'. The case is included here in |
|
// the remote chance that this gets fixed someday. |
|
|
|
return String(value); |
|
|
|
// If the type is 'object', we might be dealing with an object or an array or |
|
// null. |
|
|
|
case 'object': |
|
|
|
// Due to a specification blunder in ECMAScript, typeof null is 'object', |
|
// so watch out for that case. |
|
|
|
if (!value) { |
|
return 'null'; |
|
} |
|
|
|
// Make an array to hold the partial results of stringifying this object value. |
|
|
|
gap += indent; |
|
partial = []; |
|
|
|
// Is the value an array? |
|
|
|
if (Object.prototype.toString.apply(value) === '[object Array]') { |
|
|
|
// The value is an array. Stringify every element. Use null as a placeholder |
|
// for non-JSON values. |
|
|
|
length = value.length; |
|
for (i = 0; i < length; i += 1) { |
|
partial[i] = str(i, value) || 'null'; |
|
} |
|
|
|
// Join all of the elements together, separated with commas, and wrap them in |
|
// brackets. |
|
|
|
v = partial.length === 0 |
|
? '[]' |
|
: gap |
|
? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' |
|
: '[' + partial.join(',') + ']'; |
|
gap = mind; |
|
return v; |
|
} |
|
|
|
// If the replacer is an array, use it to select the members to be stringified. |
|
|
|
if (rep && typeof rep === 'object') { |
|
length = rep.length; |
|
for (i = 0; i < length; i += 1) { |
|
if (typeof rep[i] === 'string') { |
|
k = rep[i]; |
|
v = str(k, value); |
|
if (v) { |
|
partial.push(quote(k) + (gap ? ': ' : ':') + v); |
|
} |
|
} |
|
} |
|
} else { |
|
|
|
// Otherwise, iterate through all of the keys in the object. |
|
|
|
for (k in value) { |
|
if (Object.prototype.hasOwnProperty.call(value, k)) { |
|
v = str(k, value); |
|
if (v) { |
|
partial.push(quote(k) + (gap ? ': ' : ':') + v); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Join all of the member texts together, separated with commas, |
|
// and wrap them in braces. |
|
|
|
v = partial.length === 0 |
|
? '{}' |
|
: gap |
|
? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' |
|
: '{' + partial.join(',') + '}'; |
|
gap = mind; |
|
return v; |
|
} |
|
} |
|
|
|
// If the JSON object does not yet have a stringify method, give it one. |
|
|
|
if (typeof JSON.stringify !== 'function') { |
|
JSON.stringify = function (value, replacer, space) { |
|
|
|
// The stringify method takes a value and an optional replacer, and an optional |
|
// space parameter, and returns a JSON text. The replacer can be a function |
|
// that can replace values, or an array of strings that will select the keys. |
|
// A default replacer method can be provided. Use of the space parameter can |
|
// produce text that is more easily readable. |
|
|
|
var i; |
|
gap = ''; |
|
indent = ''; |
|
|
|
// If the space parameter is a number, make an indent string containing that |
|
// many spaces. |
|
|
|
if (typeof space === 'number') { |
|
for (i = 0; i < space; i += 1) { |
|
indent += ' '; |
|
} |
|
|
|
// If the space parameter is a string, it will be used as the indent string. |
|
|
|
} else if (typeof space === 'string') { |
|
indent = space; |
|
} |
|
|
|
// If there is a replacer, it must be a function or an array. |
|
// Otherwise, throw an error. |
|
|
|
rep = replacer; |
|
if (replacer && typeof replacer !== 'function' && |
|
(typeof replacer !== 'object' || |
|
typeof replacer.length !== 'number')) { |
|
throw new Error('JSON.stringify'); |
|
} |
|
|
|
// Make a fake root object containing our value under the key of ''. |
|
// Return the result of stringifying the value. |
|
|
|
return str('', {'': value}); |
|
}; |
|
} |
|
|
|
|
|
// If the JSON object does not yet have a parse method, give it one. |
|
|
|
if (typeof JSON.parse !== 'function') { |
|
JSON.parse = function (text, reviver) { |
|
|
|
// The parse method takes a text and an optional reviver function, and returns |
|
// a JavaScript value if the text is a valid JSON text. |
|
|
|
var j; |
|
|
|
function walk(holder, key) { |
|
|
|
// The walk method is used to recursively walk the resulting structure so |
|
// that modifications can be made. |
|
|
|
var k, v, value = holder[key]; |
|
if (value && typeof value === 'object') { |
|
for (k in value) { |
|
if (Object.prototype.hasOwnProperty.call(value, k)) { |
|
v = walk(value, k); |
|
if (v !== undefined) { |
|
value[k] = v; |
|
} else { |
|
delete value[k]; |
|
} |
|
} |
|
} |
|
} |
|
return reviver.call(holder, key, value); |
|
} |
|
|
|
|
|
// Parsing happens in four stages. In the first stage, we replace certain |
|
// Unicode characters with escape sequences. JavaScript handles many characters |
|
// incorrectly, either silently deleting them, or treating them as line endings. |
|
|
|
text = String(text); |
|
cx.lastIndex = 0; |
|
if (cx.test(text)) { |
|
text = text.replace(cx, function (a) { |
|
return '\\u' + |
|
('0000' + a.charCodeAt(0).toString(16)).slice(-4); |
|
}); |
|
} |
|
|
|
// In the second stage, we run the text against regular expressions that look |
|
// for non-JSON patterns. We are especially concerned with '()' and 'new' |
|
// because they can cause invocation, and '=' because it can cause mutation. |
|
// But just to be safe, we want to reject all unexpected forms. |
|
|
|
// We split the second stage into 4 regexp operations in order to work around |
|
// crippling inefficiencies in IE's and Safari's regexp engines. First we |
|
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we |
|
// replace all simple value tokens with ']' characters. Third, we delete all |
|
// open brackets that follow a colon or comma or that begin the text. Finally, |
|
// we look to see that the remaining characters are only whitespace or ']' or |
|
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. |
|
|
|
if (/^[\],:{}\s]*$/ |
|
.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') |
|
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') |
|
.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { |
|
|
|
// In the third stage we use the eval function to compile the text into a |
|
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity |
|
// in JavaScript: it can begin a block or an object literal. We wrap the text |
|
// in parens to eliminate the ambiguity. |
|
|
|
j = eval('(' + text + ')'); |
|
|
|
// In the optional fourth stage, we recursively walk the new structure, passing |
|
// each name/value pair to a reviver function for possible transformation. |
|
|
|
return typeof reviver === 'function' |
|
? walk({'': j}, '') |
|
: j; |
|
} |
|
|
|
// If the text is not JSON parseable, then a SyntaxError is thrown. |
|
|
|
throw new SyntaxError('JSON.parse'); |
|
}; |
|
} |
|
}()); |
|
|
|
Faye.Transport.WebSocket = Faye.extend(Faye.Class(Faye.Transport, { |
|
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._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; |
|
|
|
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 = 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 = this._socket.onerror = function() { |
|
var wasConnected = (self._state === self.CONNECTED); |
|
self.setDeferredStatus('deferred'); |
|
self._state = self.UNCONNECTED; |
|
|
|
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]; |
|
} |
|
}); |
|
|
|
Faye.extend(Faye.Transport.WebSocket.prototype, Faye.Deferrable); |
|
Faye.Transport.register('websocket', Faye.Transport.WebSocket); |
|
|
|
if (Faye.Event) |
|
Faye.Event.on(Faye.ENV, 'beforeunload', function() { |
|
Faye.Transport.WebSocket._unloaded = true; |
|
}); |
|
|
|
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 || {}, |
|
id = client.getClientId(), |
|
endpoint = endpoint + '/' + (id || ''); |
|
|
|
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, |
|
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(); |
|
self.trigger('down'); |
|
} |
|
}; |
|
|
|
xhr.send(Faye.toJSON(message)); |
|
} |
|
}), { |
|
isUsable: function(client, endpoint, callback, context) { |
|
callback.call(context, Faye.URI.parse(endpoint).isSameOrigin()); |
|
} |
|
}); |
|
|
|
Faye.Transport.register('long-polling', Faye.Transport.XHR); |
|
|
|
Faye.Transport.CORS = Faye.extend(Faye.Class(Faye.Transport, { |
|
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); |
|
} |
|
}); |
|
|
|
Faye.Transport.register('cross-origin-long-polling', Faye.Transport.CORS); |
|
|
|
Faye.Transport.JSONP = Faye.extend(Faye.Class(Faye.Transport, { |
|
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(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); |
|
} |
|
}); |
|
|
|
Faye.Transport.register('callback-polling', Faye.Transport.JSONP); |