diff --git a/public/js/simplewebrtc.bundle.js b/public/js/simplewebrtc.bundle.js index 9ec74fc..b834353 100644 --- a/public/js/simplewebrtc.bundle.js +++ b/public/js/simplewebrtc.bundle.js @@ -1,19 +1,19 @@ (function(e){if("function"==typeof bootstrap)bootstrap("simplewebrtc",e);else if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else if("undefined"!=typeof ses){if(!ses.ok())return;ses.makeSimpleWebRTC=e}else"undefined"!=typeof window?window.SimpleWebRTC=e():global.SimpleWebRTC=e()})(function(){var define,ses,bootstrap,module,exports; return (function(e,t,n){function i(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(r)return r(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0].call(u.exports,function(t){var r=e[n][1][t];return i(r?r:t)},u,u.exports)}return t[n].exports}var r=typeof require=="function"&&require;for(var s=0;s= 26) || - (window.navigator.userAgent.match('Firefox') && parseInt(window.navigator.userAgent.match(/Firefox\/(.*)/)[1], 10) >= 33)); + ((prefix === 'webkit' && version >= 26) || + (prefix === 'moz' && version >= 33)) var AudioContext = window.AudioContext || window.webkitAudioContext; -var supportVp8 = document.createElement('video').canPlayType('video/webm; codecs="vp8", vorbis') === "probably"; +var videoEl = document.createElement('video'); +var supportVp8 = videoEl && videoEl.canPlayType && videoEl.canPlayType('video/webm; codecs="vp8", vorbis') === "probably"; var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia || navigator.mozGetUserMedia; // export support flags and constructors.prototype && PC module.exports = { prefix: prefix, + browserVersion: version, support: !!PC && supportVp8 && !!getUserMedia, // new support style supportRTCPeerConnection: !!PC, @@ -700,2507 +660,2663 @@ module.exports = { getUserMedia: getUserMedia }; -},{}],7:[function(require,module,exports){ -/*! Socket.IO.js build:0.9.16, development. Copyright(c) 2011 LearnBoost MIT Licensed */ - -var io = ('undefined' === typeof module ? {} : module.exports); -(function() { - -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, global) { - - /** - * IO namespace. - * - * @namespace - */ - - var io = exports; - - /** - * Socket.IO version - * - * @api public - */ - - io.version = '0.9.16'; - - /** - * Protocol implemented. - * - * @api public - */ - - io.protocol = 1; +},{}],6:[function(require,module,exports){ +var methods = "assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,markTimeline,profile,profileEnd,time,timeEnd,trace,warn".split(","); +var l = methods.length; +var fn = function () {}; +var mockconsole = {}; - /** - * Available transports, these will be populated with the available transports - * - * @api public - */ +while (l--) { + mockconsole[methods[l]] = fn; +} - io.transports = []; +module.exports = mockconsole; - /** - * Keep track of jsonp callbacks. - * - * @api private - */ +},{}],7:[function(require,module,exports){ +module.exports = function (stream, el, options) { + var URL = window.URL; + var opts = { + autoplay: true, + mirror: false, + muted: false + }; + var element = el || document.createElement('video'); + var item; - io.j = []; + if (options) { + for (item in options) { + opts[item] = options[item]; + } + } - /** - * Keep track of our io.Sockets - * - * @api private - */ - io.sockets = {}; + if (opts.autoplay) element.autoplay = 'autoplay'; + if (opts.muted) element.muted = true; + if (opts.mirror) { + ['', 'moz', 'webkit', 'o', 'ms'].forEach(function (prefix) { + var styleName = prefix ? prefix + 'Transform' : 'transform'; + element.style[styleName] = 'scaleX(-1)'; + }); + } + // this first one should work most everywhere now + // but we have a few fallbacks just in case. + if (URL && URL.createObjectURL) { + element.src = URL.createObjectURL(stream); + } else if (element.srcObject) { + element.srcObject = stream; + } else if (element.mozSrcObject) { + element.mozSrcObject = stream; + } else { + return false; + } - /** - * Manages connections to hosts. - * - * @param {String} uri - * @Param {Boolean} force creation of new socket (defaults to false) - * @api public - */ + return element; +}; - io.connect = function (host, details) { - var uri = io.util.parseUri(host) - , uuri - , socket; +},{}],2:[function(require,module,exports){ +var util = require('util'); +var webrtc = require('webrtcsupport'); +var WildEmitter = require('wildemitter'); +var mockconsole = require('mockconsole'); +var localMedia = require('localmedia'); +var Peer = require('./peer'); - if (global && global.location) { - uri.protocol = uri.protocol || global.location.protocol.slice(0, -1); - uri.host = uri.host || (global.document - ? global.document.domain : global.location.hostname); - uri.port = uri.port || global.location.port; - } - uuri = io.util.uniqueUri(uri); +function WebRTC(opts) { + var self = this; + var options = opts || {}; + var config = this.config = { + debug: false, + // makes the entire PC config overridable + peerConnectionConfig: { + iceServers: [{"url": "stun:stun.l.google.com:19302"}] + }, + peerConnectionConstraints: { + optional: [ + {DtlsSrtpKeyAgreement: true} + ] + }, + receiveMedia: { + mandatory: { + OfferToReceiveAudio: true, + OfferToReceiveVideo: true + } + }, + enableDataChannels: true + }; + var item; - var options = { - host: uri.host - , secure: 'https' == uri.protocol - , port: uri.port || ('https' == uri.protocol ? 443 : 80) - , query: uri.query || '' - }; + // expose screensharing check + this.screenSharingSupport = webrtc.screenSharing; - io.util.merge(options, details); + // We also allow a 'logger' option. It can be any object that implements + // log, warn, and error methods. + // We log nothing by default, following "the rule of silence": + // http://www.linfo.org/rule_of_silence.html + this.logger = function () { + // we assume that if you're in debug mode and you didn't + // pass in a logger, you actually want to log as much as + // possible. + if (opts.debug) { + return opts.logger || console; + } else { + // or we'll use your logger which should have its own logic + // for output. Or we'll return the no-op. + return opts.logger || mockconsole; + } + }(); - if (options['force new connection'] || !io.sockets[uuri]) { - socket = new io.Socket(options); + // set options + for (item in options) { + this.config[item] = options[item]; } - if (!options['force new connection'] && socket) { - io.sockets[uuri] = socket; + // check for support + if (!webrtc.support) { + this.logger.error('Your browser doesn\'t seem to support WebRTC'); } - socket = socket || io.sockets[uuri]; - - // if path is different from '' or / - return socket.of(uri.path.length > 1 ? uri.path : ''); - }; + // where we'll store our peer connections + this.peers = []; -})('object' === typeof module ? module.exports : (this.io = {}), this); -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ + // call localMedia constructor + localMedia.call(this, this.config); -(function (exports, global) { - - /** - * Utilities namespace. - * - * @namespace - */ + this.on('speaking', function () { + if (!self.hardMuted) { + // FIXME: should use sendDirectlyToAll, but currently has different semantics wrt payload + self.peers.forEach(function (peer) { + if (peer.enableDataChannels) { + var dc = peer.getDataChannel('hark'); + if (dc.readyState != 'open') return; + dc.send(JSON.stringify({type: 'speaking'})); + } + }); + } + }); + this.on('stoppedSpeaking', function () { + if (!self.hardMuted) { + // FIXME: should use sendDirectlyToAll, but currently has different semantics wrt payload + self.peers.forEach(function (peer) { + if (peer.enableDataChannels) { + var dc = peer.getDataChannel('hark'); + if (dc.readyState != 'open') return; + dc.send(JSON.stringify({type: 'stoppedSpeaking'})); + } + }); + } + }); + this.on('volumeChange', function (volume, treshold) { + if (!self.hardMuted) { + // FIXME: should use sendDirectlyToAll, but currently has different semantics wrt payload + self.peers.forEach(function (peer) { + if (peer.enableDataChannels) { + var dc = peer.getDataChannel('hark'); + if (dc.readyState != 'open') return; + dc.send(JSON.stringify({type: 'volume', volume: volume })); + } + }); + } + }); - var util = exports.util = {}; + // log events in debug mode + if (this.config.debug) { + this.on('*', function (event, val1, val2) { + var logger; + // if you didn't pass in a logger and you explicitly turning on debug + // we're just going to assume you're wanting log output with console + if (self.config.logger === mockconsole) { + logger = console; + } else { + logger = self.logger; + } + logger.log('event:', event, val1, val2); + }); + } +} - /** - * Parses an URI - * - * @author Steven Levithan (MIT license) - * @api public - */ +util.inherits(WebRTC, localMedia); - var re = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; +WebRTC.prototype.createPeer = function (opts) { + var peer; + opts.parent = this; + peer = new Peer(opts); + this.peers.push(peer); + return peer; +}; - var parts = ['source', 'protocol', 'authority', 'userInfo', 'user', 'password', - 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', - 'anchor']; +// removes peers +WebRTC.prototype.removePeers = function (id, type) { + this.getPeers(id, type).forEach(function (peer) { + peer.end(); + }); +}; - util.parseUri = function (str) { - var m = re.exec(str || '') - , uri = {} - , i = 14; +// fetches all Peer objects by session id and/or type +WebRTC.prototype.getPeers = function (sessionId, type) { + return this.peers.filter(function (peer) { + return (!sessionId || peer.id === sessionId) && (!type || peer.type === type); + }); +}; - while (i--) { - uri[parts[i]] = m[i] || ''; - } +// sends message to all +WebRTC.prototype.sendToAll = function (message, payload) { + this.peers.forEach(function (peer) { + peer.send(message, payload); + }); +}; - return uri; - }; +// sends message to all using a datachannel +// only sends to anyone who has an open datachannel +WebRTC.prototype.sendDirectlyToAll = function (channel, message, payload) { + this.peers.forEach(function (peer) { + if (peer.enableDataChannels) { + peer.sendDirectly(channel, message, payload); + } + }); +}; - /** - * Produces a unique url that identifies a Socket.IO connection. - * - * @param {Object} uri - * @api public - */ +module.exports = WebRTC; - util.uniqueUri = function (uri) { - var protocol = uri.protocol - , host = uri.host - , port = uri.port; +},{"./peer":9,"localmedia":10,"mockconsole":6,"util":8,"webrtcsupport":5,"wildemitter":4}],3:[function(require,module,exports){ +var io = require('socket.io-client'); - if ('document' in global) { - host = host || document.domain; - port = port || (protocol == 'https' - && document.location.protocol !== 'https:' ? 443 : document.location.port); - } else { - host = host || 'localhost'; +function SocketIoConnection(config) { + this.connection = io.connect(config.url, config.socketio); +} - if (!port && protocol == 'https') { - port = 443; - } - } +SocketIoConnection.prototype.on = function (ev, fn) { + this.connection.on(ev, fn); +}; - return (protocol || 'http') + '://' + host + ':' + (port || 80); - }; +SocketIoConnection.prototype.emit = function () { + this.connection.emit.apply(this.connection, arguments); +}; - /** - * Mergest 2 query strings in to once unique query string - * - * @param {String} base - * @param {String} addition - * @api public - */ +SocketIoConnection.prototype.getSessionid = function () { + return this.connection.socket.sessionid; +}; - util.query = function (base, addition) { - var query = util.chunkQuery(base || '') - , components = []; +SocketIoConnection.prototype.disconnect = function () { + return this.connection.disconnect(); +}; - util.merge(query, util.chunkQuery(addition || '')); - for (var part in query) { - if (query.hasOwnProperty(part)) { - components.push(part + '=' + query[part]); - } - } +module.exports = SocketIoConnection; - return components.length ? '?' + components.join('&') : ''; - }; +},{"socket.io-client":11}],8:[function(require,module,exports){ +var events = require('events'); - /** - * Transforms a querystring in to an object - * - * @param {String} qs - * @api public - */ +exports.isArray = isArray; +exports.isDate = function(obj){return Object.prototype.toString.call(obj) === '[object Date]'}; +exports.isRegExp = function(obj){return Object.prototype.toString.call(obj) === '[object RegExp]'}; - util.chunkQuery = function (qs) { - var query = {} - , params = qs.split('&') - , i = 0 - , l = params.length - , kv; - for (; i < l; ++i) { - kv = params[i].split('='); - if (kv[0]) { - query[kv[0]] = kv[1]; - } - } +exports.print = function () {}; +exports.puts = function () {}; +exports.debug = function() {}; - return query; - }; +exports.inspect = function(obj, showHidden, depth, colors) { + var seen = []; - /** - * Executes the given function when the page is loaded. - * - * io.util.load(function () { console.log('page loaded'); }); - * - * @param {Function} fn - * @api public - */ + var stylize = function(str, styleType) { + // http://en.wikipedia.org/wiki/ANSI_escape_code#graphics + var styles = + { 'bold' : [1, 22], + 'italic' : [3, 23], + 'underline' : [4, 24], + 'inverse' : [7, 27], + 'white' : [37, 39], + 'grey' : [90, 39], + 'black' : [30, 39], + 'blue' : [34, 39], + 'cyan' : [36, 39], + 'green' : [32, 39], + 'magenta' : [35, 39], + 'red' : [31, 39], + 'yellow' : [33, 39] }; - var pageLoaded = false; + var style = + { 'special': 'cyan', + 'number': 'blue', + 'boolean': 'yellow', + 'undefined': 'grey', + 'null': 'bold', + 'string': 'green', + 'date': 'magenta', + // "name": intentionally not styling + 'regexp': 'red' }[styleType]; - util.load = function (fn) { - if ('document' in global && document.readyState === 'complete' || pageLoaded) { - return fn(); + if (style) { + return '\u001b[' + styles[style][0] + 'm' + str + + '\u001b[' + styles[style][1] + 'm'; + } else { + return str; } - - util.on(global, 'load', fn, false); }; + if (! colors) { + stylize = function(str, styleType) { return str; }; + } - /** - * Adds an event. - * - * @api private - */ - - util.on = function (element, event, fn, capture) { - if (element.attachEvent) { - element.attachEvent('on' + event, fn); - } else if (element.addEventListener) { - element.addEventListener(event, fn, capture); + function format(value, recurseTimes) { + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it + if (value && typeof value.inspect === 'function' && + // Filter out the util module, it's inspect function is special + value !== exports && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value)) { + return value.inspect(recurseTimes); } - }; - /** - * Generates the correct `XMLHttpRequest` for regular and cross domain requests. - * - * @param {Boolean} [xdomain] Create a request that can be used cross domain. - * @returns {XMLHttpRequest|false} If we can create a XMLHttpRequest. - * @api private - */ + // Primitive types cannot have properties + switch (typeof value) { + case 'undefined': + return stylize('undefined', 'undefined'); - util.request = function (xdomain) { + case 'string': + var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + '\''; + return stylize(simple, 'string'); - if (xdomain && 'undefined' != typeof XDomainRequest && !util.ua.hasCORS) { - return new XDomainRequest(); - } + case 'number': + return stylize('' + value, 'number'); - if ('undefined' != typeof XMLHttpRequest && (!xdomain || util.ua.hasCORS)) { - return new XMLHttpRequest(); + case 'boolean': + return stylize('' + value, 'boolean'); } - - if (!xdomain) { - try { - return new window[(['Active'].concat('Object').join('X'))]('Microsoft.XMLHTTP'); - } catch(e) { } + // For some reason typeof null is "object", so special case here. + if (value === null) { + return stylize('null', 'null'); } - return null; - }; + // Look up the keys of the object. + var visible_keys = Object_keys(value); + var keys = showHidden ? Object_getOwnPropertyNames(value) : visible_keys; - /** - * XHR based transport constructor. - * - * @constructor - * @api public - */ + // Functions without properties can be shortcutted. + if (typeof value === 'function' && keys.length === 0) { + if (isRegExp(value)) { + return stylize('' + value, 'regexp'); + } else { + var name = value.name ? ': ' + value.name : ''; + return stylize('[Function' + name + ']', 'special'); + } + } - /** - * Change the internal pageLoaded value. - */ + // Dates without properties can be shortcutted + if (isDate(value) && keys.length === 0) { + return stylize(value.toUTCString(), 'date'); + } - if ('undefined' != typeof window) { - util.load(function () { - pageLoaded = true; - }); - } + var base, type, braces; + // Determine the object type + if (isArray(value)) { + type = 'Array'; + braces = ['[', ']']; + } else { + type = 'Object'; + braces = ['{', '}']; + } - /** - * Defers a function to ensure a spinner is not displayed by the browser - * - * @param {Function} fn - * @api public - */ + // Make functions say that they are functions + if (typeof value === 'function') { + var n = value.name ? ': ' + value.name : ''; + base = (isRegExp(value)) ? ' ' + value : ' [Function' + n + ']'; + } else { + base = ''; + } - util.defer = function (fn) { - if (!util.ua.webkit || 'undefined' != typeof importScripts) { - return fn(); + // Make dates with properties first say the date + if (isDate(value)) { + base = ' ' + value.toUTCString(); } - util.load(function () { - setTimeout(fn, 100); - }); - }; + if (keys.length === 0) { + return braces[0] + base + braces[1]; + } - /** - * Merges two objects. - * - * @api public - */ + if (recurseTimes < 0) { + if (isRegExp(value)) { + return stylize('' + value, 'regexp'); + } else { + return stylize('[Object]', 'special'); + } + } - util.merge = function merge (target, additional, deep, lastseen) { - var seen = lastseen || [] - , depth = typeof deep == 'undefined' ? 2 : deep - , prop; + seen.push(value); - for (prop in additional) { - if (additional.hasOwnProperty(prop) && util.indexOf(seen, prop) < 0) { - if (typeof target[prop] !== 'object' || !depth) { - target[prop] = additional[prop]; - seen.push(additional[prop]); + var output = keys.map(function(key) { + var name, str; + if (value.__lookupGetter__) { + if (value.__lookupGetter__(key)) { + if (value.__lookupSetter__(key)) { + str = stylize('[Getter/Setter]', 'special'); + } else { + str = stylize('[Getter]', 'special'); + } } else { - util.merge(target[prop], additional[prop], depth - 1, seen); + if (value.__lookupSetter__(key)) { + str = stylize('[Setter]', 'special'); + } + } + } + if (visible_keys.indexOf(key) < 0) { + name = '[' + key + ']'; + } + if (!str) { + if (seen.indexOf(value[key]) < 0) { + if (recurseTimes === null) { + str = format(value[key]); + } else { + str = format(value[key], recurseTimes - 1); + } + if (str.indexOf('\n') > -1) { + if (isArray(value)) { + str = str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n').substr(2); + } else { + str = '\n' + str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n'); + } + } + } else { + str = stylize('[Circular]', 'special'); + } + } + if (typeof name === 'undefined') { + if (type === 'Array' && key.match(/^\d+$/)) { + return str; + } + name = JSON.stringify('' + key); + if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { + name = name.substr(1, name.length - 2); + name = stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'") + .replace(/\\"/g, '"') + .replace(/(^"|"$)/g, "'"); + name = stylize(name, 'string'); } } - } - return target; - }; + return name + ': ' + str; + }); - /** - * Merges prototypes from objects - * - * @api public - */ + seen.pop(); - util.mixin = function (ctor, ctor2) { - util.merge(ctor.prototype, ctor2.prototype); - }; + var numLinesEst = 0; + var length = output.reduce(function(prev, cur) { + numLinesEst++; + if (cur.indexOf('\n') >= 0) numLinesEst++; + return prev + cur.length + 1; + }, 0); - /** - * Shortcut for prototypical and static inheritance. - * - * @api private - */ + if (length > 50) { + output = braces[0] + + (base === '' ? '' : base + '\n ') + + ' ' + + output.join(',\n ') + + ' ' + + braces[1]; - util.inherit = function (ctor, ctor2) { - function f() {}; - f.prototype = ctor2.prototype; - ctor.prototype = new f; - }; + } else { + output = braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; + } - /** - * Checks if the given object is an Array. - * - * io.util.isArray([]); // true - * io.util.isArray({}); // false - * - * @param Object obj - * @api public - */ + return output; + } + return format(obj, (typeof depth === 'undefined' ? 2 : depth)); +}; - util.isArray = Array.isArray || function (obj) { - return Object.prototype.toString.call(obj) === '[object Array]'; - }; - /** - * Intersects values of two arrays into a third - * - * @api public - */ +function isArray(ar) { + return Array.isArray(ar) || + (typeof ar === 'object' && Object.prototype.toString.call(ar) === '[object Array]'); +} - util.intersect = function (arr, arr2) { - var ret = [] - , longest = arr.length > arr2.length ? arr : arr2 - , shortest = arr.length > arr2.length ? arr2 : arr; - for (var i = 0, l = shortest.length; i < l; i++) { - if (~util.indexOf(longest, shortest[i])) - ret.push(shortest[i]); - } +function isRegExp(re) { + typeof re === 'object' && Object.prototype.toString.call(re) === '[object RegExp]'; +} - return ret; - }; - /** - * Array indexOf compatibility. - * - * @see bit.ly/a5Dxa2 - * @api public - */ +function isDate(d) { + return typeof d === 'object' && Object.prototype.toString.call(d) === '[object Date]'; +} - util.indexOf = function (arr, o, i) { +function pad(n) { + return n < 10 ? '0' + n.toString(10) : n.toString(10); +} - for (var j = arr.length, i = i < 0 ? i + j < 0 ? 0 : i + j : i || 0; - i < j && arr[i] !== o; i++) {} +var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', + 'Oct', 'Nov', 'Dec']; - return j <= i ? -1 : i; - }; +// 26 Feb 16:19:34 +function timestamp() { + var d = new Date(); + var time = [pad(d.getHours()), + pad(d.getMinutes()), + pad(d.getSeconds())].join(':'); + return [d.getDate(), months[d.getMonth()], time].join(' '); +} - /** - * Converts enumerables to array. - * - * @api public - */ +exports.log = function (msg) {}; - util.toArray = function (enu) { - var arr = []; +exports.pump = null; - for (var i = 0, l = enu.length; i < l; i++) - arr.push(enu[i]); +var Object_keys = Object.keys || function (obj) { + var res = []; + for (var key in obj) res.push(key); + return res; +}; - return arr; - }; +var Object_getOwnPropertyNames = Object.getOwnPropertyNames || function (obj) { + var res = []; + for (var key in obj) { + if (Object.hasOwnProperty.call(obj, key)) res.push(key); + } + return res; +}; + +var Object_create = Object.create || function (prototype, properties) { + // from es5-shim + var object; + if (prototype === null) { + object = { '__proto__' : null }; + } + else { + if (typeof prototype !== 'object') { + throw new TypeError( + 'typeof prototype[' + (typeof prototype) + '] != \'object\'' + ); + } + var Type = function () {}; + Type.prototype = prototype; + object = new Type(); + object.__proto__ = prototype; + } + if (typeof properties !== 'undefined' && Object.defineProperties) { + Object.defineProperties(object, properties); + } + return object; +}; + +exports.inherits = function(ctor, superCtor) { + ctor.super_ = superCtor; + ctor.prototype = Object_create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); +}; + +var formatRegExp = /%[sdj%]/g; +exports.format = function(f) { + if (typeof f !== 'string') { + var objects = []; + for (var i = 0; i < arguments.length; i++) { + objects.push(exports.inspect(arguments[i])); + } + return objects.join(' '); + } + + var i = 1; + var args = arguments; + var len = args.length; + var str = String(f).replace(formatRegExp, function(x) { + if (x === '%%') return '%'; + if (i >= len) return x; + switch (x) { + case '%s': return String(args[i++]); + case '%d': return Number(args[i++]); + case '%j': return JSON.stringify(args[i++]); + default: + return x; + } + }); + for(var x = args[i]; i < len; x = args[++i]){ + if (x === null || typeof x !== 'object') { + str += ' ' + x; + } else { + str += ' ' + exports.inspect(x); + } + } + return str; +}; + +},{"events":12}],11:[function(require,module,exports){ +/*! Socket.IO.js build:0.9.16, development. Copyright(c) 2011 LearnBoost MIT Licensed */ + +var io = ('undefined' === typeof module ? {} : module.exports); +(function() { + +/** + * socket.io + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +(function (exports, global) { /** - * UA / engines detection namespace. + * IO namespace. * * @namespace */ - util.ua = {}; + var io = exports; /** - * Whether the UA supports CORS for XHR. + * Socket.IO version * * @api public */ - util.ua.hasCORS = 'undefined' != typeof XMLHttpRequest && (function () { - try { - var a = new XMLHttpRequest(); - } catch (e) { - return false; - } - - return a.withCredentials != undefined; - })(); + io.version = '0.9.16'; /** - * Detect webkit. + * Protocol implemented. * * @api public */ - util.ua.webkit = 'undefined' != typeof navigator - && /webkit/i.test(navigator.userAgent); + io.protocol = 1; - /** - * Detect iPad/iPhone/iPod. + /** + * Available transports, these will be populated with the available transports * * @api public */ - util.ua.iDevice = 'undefined' != typeof navigator - && /iPad|iPhone|iPod/i.test(navigator.userAgent); - -})('undefined' != typeof io ? io : module.exports, this); -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, io) { + io.transports = []; /** - * Expose constructor. + * Keep track of jsonp callbacks. + * + * @api private */ - exports.EventEmitter = EventEmitter; + io.j = []; /** - * Event emitter constructor. + * Keep track of our io.Sockets * - * @api public. + * @api private */ + io.sockets = {}; - function EventEmitter () {}; /** - * Adds a listener + * Manages connections to hosts. * + * @param {String} uri + * @Param {Boolean} force creation of new socket (defaults to false) * @api public */ - EventEmitter.prototype.on = function (name, fn) { - if (!this.$events) { - this.$events = {}; - } + io.connect = function (host, details) { + var uri = io.util.parseUri(host) + , uuri + , socket; - if (!this.$events[name]) { - this.$events[name] = fn; - } else if (io.util.isArray(this.$events[name])) { - this.$events[name].push(fn); - } else { - this.$events[name] = [this.$events[name], fn]; + if (global && global.location) { + uri.protocol = uri.protocol || global.location.protocol.slice(0, -1); + uri.host = uri.host || (global.document + ? global.document.domain : global.location.hostname); + uri.port = uri.port || global.location.port; } - return this; - }; + uuri = io.util.uniqueUri(uri); - EventEmitter.prototype.addListener = EventEmitter.prototype.on; + var options = { + host: uri.host + , secure: 'https' == uri.protocol + , port: uri.port || ('https' == uri.protocol ? 443 : 80) + , query: uri.query || '' + }; - /** - * Adds a volatile listener. - * - * @api public - */ + io.util.merge(options, details); - EventEmitter.prototype.once = function (name, fn) { - var self = this; + if (options['force new connection'] || !io.sockets[uuri]) { + socket = new io.Socket(options); + } - function on () { - self.removeListener(name, on); - fn.apply(this, arguments); - }; + if (!options['force new connection'] && socket) { + io.sockets[uuri] = socket; + } - on.listener = fn; - this.on(name, on); + socket = socket || io.sockets[uuri]; - return this; + // if path is different from '' or / + return socket.of(uri.path.length > 1 ? uri.path : ''); }; +})('object' === typeof module ? module.exports : (this.io = {}), this); +/** + * socket.io + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +(function (exports, global) { + /** - * Removes a listener. + * Utilities namespace. * - * @api public + * @namespace */ - EventEmitter.prototype.removeListener = function (name, fn) { - if (this.$events && this.$events[name]) { - var list = this.$events[name]; + var util = exports.util = {}; - if (io.util.isArray(list)) { - var pos = -1; + /** + * Parses an URI + * + * @author Steven Levithan (MIT license) + * @api public + */ - for (var i = 0, l = list.length; i < l; i++) { - if (list[i] === fn || (list[i].listener && list[i].listener === fn)) { - pos = i; - break; - } - } + var re = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; - if (pos < 0) { - return this; - } + var parts = ['source', 'protocol', 'authority', 'userInfo', 'user', 'password', + 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', + 'anchor']; - list.splice(pos, 1); + util.parseUri = function (str) { + var m = re.exec(str || '') + , uri = {} + , i = 14; - if (!list.length) { - delete this.$events[name]; - } - } else if (list === fn || (list.listener && list.listener === fn)) { - delete this.$events[name]; - } + while (i--) { + uri[parts[i]] = m[i] || ''; } - return this; + return uri; }; /** - * Removes all listeners for an event. + * Produces a unique url that identifies a Socket.IO connection. * + * @param {Object} uri * @api public */ - EventEmitter.prototype.removeAllListeners = function (name) { - if (name === undefined) { - this.$events = {}; - return this; - } + util.uniqueUri = function (uri) { + var protocol = uri.protocol + , host = uri.host + , port = uri.port; - if (this.$events && this.$events[name]) { - this.$events[name] = null; + if ('document' in global) { + host = host || document.domain; + port = port || (protocol == 'https' + && document.location.protocol !== 'https:' ? 443 : document.location.port); + } else { + host = host || 'localhost'; + + if (!port && protocol == 'https') { + port = 443; + } } - return this; + return (protocol || 'http') + '://' + host + ':' + (port || 80); }; /** - * Gets all listeners for a certain event. + * Mergest 2 query strings in to once unique query string * - * @api publci + * @param {String} base + * @param {String} addition + * @api public */ - EventEmitter.prototype.listeners = function (name) { - if (!this.$events) { - this.$events = {}; - } - - if (!this.$events[name]) { - this.$events[name] = []; - } + util.query = function (base, addition) { + var query = util.chunkQuery(base || '') + , components = []; - if (!io.util.isArray(this.$events[name])) { - this.$events[name] = [this.$events[name]]; + util.merge(query, util.chunkQuery(addition || '')); + for (var part in query) { + if (query.hasOwnProperty(part)) { + components.push(part + '=' + query[part]); + } } - return this.$events[name]; + return components.length ? '?' + components.join('&') : ''; }; /** - * Emits an event. + * Transforms a querystring in to an object * + * @param {String} qs * @api public */ - EventEmitter.prototype.emit = function (name) { - if (!this.$events) { - return false; - } - - var handler = this.$events[name]; + util.chunkQuery = function (qs) { + var query = {} + , params = qs.split('&') + , i = 0 + , l = params.length + , kv; - if (!handler) { - return false; + for (; i < l; ++i) { + kv = params[i].split('='); + if (kv[0]) { + query[kv[0]] = kv[1]; + } } - var args = Array.prototype.slice.call(arguments, 1); + return query; + }; - if ('function' == typeof handler) { - handler.apply(this, args); - } else if (io.util.isArray(handler)) { - var listeners = handler.slice(); + /** + * Executes the given function when the page is loaded. + * + * io.util.load(function () { console.log('page loaded'); }); + * + * @param {Function} fn + * @api public + */ - for (var i = 0, l = listeners.length; i < l; i++) { - listeners[i].apply(this, args); - } - } else { - return false; + var pageLoaded = false; + + util.load = function (fn) { + if ('document' in global && document.readyState === 'complete' || pageLoaded) { + return fn(); } - return true; + util.on(global, 'load', fn, false); }; -})( - 'undefined' != typeof io ? io : module.exports - , 'undefined' != typeof io ? io : module.parent.exports -); + /** + * Adds an event. + * + * @api private + */ -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ + util.on = function (element, event, fn, capture) { + if (element.attachEvent) { + element.attachEvent('on' + event, fn); + } else if (element.addEventListener) { + element.addEventListener(event, fn, capture); + } + }; -/** - * Based on JSON2 (http://www.JSON.org/js.html). - */ + /** + * Generates the correct `XMLHttpRequest` for regular and cross domain requests. + * + * @param {Boolean} [xdomain] Create a request that can be used cross domain. + * @returns {XMLHttpRequest|false} If we can create a XMLHttpRequest. + * @api private + */ -(function (exports, nativeJSON) { - "use strict"; + util.request = function (xdomain) { - // use native JSON if it's available - if (nativeJSON && nativeJSON.parse){ - return exports.JSON = { - parse: nativeJSON.parse - , stringify: nativeJSON.stringify - }; - } + if (xdomain && 'undefined' != typeof XDomainRequest && !util.ua.hasCORS) { + return new XDomainRequest(); + } - var JSON = exports.JSON = {}; + if ('undefined' != typeof XMLHttpRequest && (!xdomain || util.ua.hasCORS)) { + return new XMLHttpRequest(); + } - function f(n) { - // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } + if (!xdomain) { + try { + return new window[(['Active'].concat('Object').join('X'))]('Microsoft.XMLHTTP'); + } catch(e) { } + } - function date(d, key) { - return isFinite(d.valueOf()) ? - d.getUTCFullYear() + '-' + - f(d.getUTCMonth() + 1) + '-' + - f(d.getUTCDate()) + 'T' + - f(d.getUTCHours()) + ':' + - f(d.getUTCMinutes()) + ':' + - f(d.getUTCSeconds()) + 'Z' : null; + return null; }; - 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) { + /** + * XHR based transport constructor. + * + * @constructor + * @api public + */ -// 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. + /** + * Change the internal pageLoaded value. + */ - 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 + '"'; + if ('undefined' != typeof window) { + util.load(function () { + pageLoaded = true; + }); } + /** + * Defers a function to ensure a spinner is not displayed by the browser + * + * @param {Function} fn + * @api public + */ - function str(key, holder) { - -// Produce a string from holder[key]. + util.defer = function (fn) { + if (!util.ua.webkit || 'undefined' != typeof importScripts) { + return fn(); + } - 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. + util.load(function () { + setTimeout(fn, 100); + }); + }; - if (value instanceof Date) { - value = date(key); - } + /** + * Merges two objects. + * + * @api public + */ -// If we were called with a replacer function, then call the replacer to -// obtain a replacement value. + util.merge = function merge (target, additional, deep, lastseen) { + var seen = lastseen || [] + , depth = typeof deep == 'undefined' ? 2 : deep + , prop; - if (typeof rep === 'function') { - value = rep.call(holder, key, value); + for (prop in additional) { + if (additional.hasOwnProperty(prop) && util.indexOf(seen, prop) < 0) { + if (typeof target[prop] !== 'object' || !depth) { + target[prop] = additional[prop]; + seen.push(additional[prop]); + } else { + util.merge(target[prop], additional[prop], depth - 1, seen); + } } + } -// What happens next depends on the value's type. + return target; + }; - switch (typeof value) { - case 'string': - return quote(value); + /** + * Merges prototypes from objects + * + * @api public + */ - case 'number': + util.mixin = function (ctor, ctor2) { + util.merge(ctor.prototype, ctor2.prototype); + }; -// JSON numbers must be finite. Encode non-finite numbers as null. + /** + * Shortcut for prototypical and static inheritance. + * + * @api private + */ - return isFinite(value) ? String(value) : 'null'; + util.inherit = function (ctor, ctor2) { + function f() {}; + f.prototype = ctor2.prototype; + ctor.prototype = new f; + }; - case 'boolean': - case 'null': + /** + * Checks if the given object is an Array. + * + * io.util.isArray([]); // true + * io.util.isArray({}); // false + * + * @param Object obj + * @api public + */ -// 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. + util.isArray = Array.isArray || function (obj) { + return Object.prototype.toString.call(obj) === '[object Array]'; + }; - return String(value); + /** + * Intersects values of two arrays into a third + * + * @api public + */ -// If the type is 'object', we might be dealing with an object or an array or -// null. + util.intersect = function (arr, arr2) { + var ret = [] + , longest = arr.length > arr2.length ? arr : arr2 + , shortest = arr.length > arr2.length ? arr2 : arr; - case 'object': + for (var i = 0, l = shortest.length; i < l; i++) { + if (~util.indexOf(longest, shortest[i])) + ret.push(shortest[i]); + } -// Due to a specification blunder in ECMAScript, typeof null is 'object', -// so watch out for that case. + return ret; + }; - if (!value) { - return 'null'; - } + /** + * Array indexOf compatibility. + * + * @see bit.ly/a5Dxa2 + * @api public + */ -// Make an array to hold the partial results of stringifying this object value. + util.indexOf = function (arr, o, i) { - gap += indent; - partial = []; + for (var j = arr.length, i = i < 0 ? i + j < 0 ? 0 : i + j : i || 0; + i < j && arr[i] !== o; i++) {} -// Is the value an array? + return j <= i ? -1 : i; + }; - if (Object.prototype.toString.apply(value) === '[object Array]') { + /** + * Converts enumerables to array. + * + * @api public + */ -// The value is an array. Stringify every element. Use null as a placeholder -// for non-JSON values. + util.toArray = function (enu) { + var arr = []; - length = value.length; - for (i = 0; i < length; i += 1) { - partial[i] = str(i, value) || 'null'; - } + for (var i = 0, l = enu.length; i < l; i++) + arr.push(enu[i]); -// Join all of the elements together, separated with commas, and wrap them in -// brackets. + return arr; + }; - v = partial.length === 0 ? '[]' : gap ? - '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : - '[' + partial.join(',') + ']'; - gap = mind; - return v; - } + /** + * UA / engines detection namespace. + * + * @namespace + */ -// If the replacer is an array, use it to select the members to be stringified. + util.ua = {}; - 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 { + /** + * Whether the UA supports CORS for XHR. + * + * @api public + */ -// Otherwise, iterate through all of the keys in the object. + util.ua.hasCORS = 'undefined' != typeof XMLHttpRequest && (function () { + try { + var a = new XMLHttpRequest(); + } catch (e) { + return false; + } - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } + return a.withCredentials != undefined; + })(); -// Join all of the member texts together, separated with commas, -// and wrap them in braces. + /** + * Detect webkit. + * + * @api public + */ - v = partial.length === 0 ? '{}' : gap ? - '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : - '{' + partial.join(',') + '}'; - gap = mind; - return v; - } - } + util.ua.webkit = 'undefined' != typeof navigator + && /webkit/i.test(navigator.userAgent); -// If the JSON object does not yet have a stringify method, give it one. + /** + * Detect iPad/iPhone/iPod. + * + * @api public + */ - JSON.stringify = function (value, replacer, space) { + util.ua.iDevice = 'undefined' != typeof navigator + && /iPad|iPhone|iPod/i.test(navigator.userAgent); -// 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. +})('undefined' != typeof io ? io : module.exports, this); +/** + * socket.io + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ - var i; - gap = ''; - indent = ''; +(function (exports, io) { -// If the space parameter is a number, make an indent string containing that -// many spaces. + /** + * Expose constructor. + */ - if (typeof space === 'number') { - for (i = 0; i < space; i += 1) { - indent += ' '; - } + exports.EventEmitter = EventEmitter; -// If the space parameter is a string, it will be used as the indent string. + /** + * Event emitter constructor. + * + * @api public. + */ - } else if (typeof space === 'string') { - indent = space; - } + function EventEmitter () {}; -// If there is a replacer, it must be a function or an array. -// Otherwise, throw an error. + /** + * Adds a listener + * + * @api public + */ - rep = replacer; - if (replacer && typeof replacer !== 'function' && - (typeof replacer !== 'object' || - typeof replacer.length !== 'number')) { - throw new Error('JSON.stringify'); - } + EventEmitter.prototype.on = function (name, fn) { + if (!this.$events) { + this.$events = {}; + } -// Make a fake root object containing our value under the key of ''. -// Return the result of stringifying the value. + if (!this.$events[name]) { + this.$events[name] = fn; + } else if (io.util.isArray(this.$events[name])) { + this.$events[name].push(fn); + } else { + this.$events[name] = [this.$events[name], fn]; + } - return str('', {'': value}); + return this; }; -// If the JSON object does not yet have a parse method, give it one. - - 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) { + EventEmitter.prototype.addListener = EventEmitter.prototype.on; - // The walk method is used to recursively walk the resulting structure so - // that modifications can be made. + /** + * Adds a volatile listener. + * + * @api public + */ - 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); - } + EventEmitter.prototype.once = function (name, fn) { + var self = this; + function on () { + self.removeListener(name, on); + fn.apply(this, arguments); + }; - // 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. + on.listener = fn; + this.on(name, on); - 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); - }); - } + return this; + }; - // 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. + /** + * Removes a listener. + * + * @api public + */ - // 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. + EventEmitter.prototype.removeListener = function (name, fn) { + if (this.$events && this.$events[name]) { + var list = this.$events[name]; - 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, ''))) { + if (io.util.isArray(list)) { + var pos = -1; - // 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. + for (var i = 0, l = list.length; i < l; i++) { + if (list[i] === fn || (list[i].listener && list[i].listener === fn)) { + pos = i; + break; + } + } - j = eval('(' + text + ')'); + if (pos < 0) { + return this; + } - // In the optional fourth stage, we recursively walk the new structure, passing - // each name/value pair to a reviver function for possible transformation. + list.splice(pos, 1); - return typeof reviver === 'function' ? - walk({'': j}, '') : j; + if (!list.length) { + delete this.$events[name]; + } + } else if (list === fn || (list.listener && list.listener === fn)) { + delete this.$events[name]; } + } - // If the text is not JSON parseable, then a SyntaxError is thrown. - - throw new SyntaxError('JSON.parse'); + return this; }; -})( - 'undefined' != typeof io ? io : module.exports - , typeof JSON !== 'undefined' ? JSON : undefined -); - -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, io) { - /** - * Parser namespace. + * Removes all listeners for an event. * - * @namespace + * @api public */ - var parser = exports.parser = {}; + EventEmitter.prototype.removeAllListeners = function (name) { + if (name === undefined) { + this.$events = {}; + return this; + } - /** - * Packet types. - */ + if (this.$events && this.$events[name]) { + this.$events[name] = null; + } - var packets = parser.packets = [ - 'disconnect' - , 'connect' - , 'heartbeat' - , 'message' - , 'json' - , 'event' - , 'ack' - , 'error' - , 'noop' - ]; + return this; + }; /** - * Errors reasons. + * Gets all listeners for a certain event. + * + * @api publci */ - var reasons = parser.reasons = [ - 'transport not supported' - , 'client not handshaken' - , 'unauthorized' - ]; - - /** - * Errors advice. - */ + EventEmitter.prototype.listeners = function (name) { + if (!this.$events) { + this.$events = {}; + } - var advice = parser.advice = [ - 'reconnect' - ]; + if (!this.$events[name]) { + this.$events[name] = []; + } - /** - * Shortcuts. - */ + if (!io.util.isArray(this.$events[name])) { + this.$events[name] = [this.$events[name]]; + } - var JSON = io.JSON - , indexOf = io.util.indexOf; + return this.$events[name]; + }; /** - * Encodes a packet. + * Emits an event. * - * @api private + * @api public */ - parser.encodePacket = function (packet) { - var type = indexOf(packets, packet.type) - , id = packet.id || '' - , endpoint = packet.endpoint || '' - , ack = packet.ack - , data = null; + EventEmitter.prototype.emit = function (name) { + if (!this.$events) { + return false; + } - switch (packet.type) { - case 'error': - var reason = packet.reason ? indexOf(reasons, packet.reason) : '' - , adv = packet.advice ? indexOf(advice, packet.advice) : ''; + var handler = this.$events[name]; - if (reason !== '' || adv !== '') - data = reason + (adv !== '' ? ('+' + adv) : ''); + if (!handler) { + return false; + } - break; + var args = Array.prototype.slice.call(arguments, 1); - case 'message': - if (packet.data !== '') - data = packet.data; - break; + if ('function' == typeof handler) { + handler.apply(this, args); + } else if (io.util.isArray(handler)) { + var listeners = handler.slice(); - case 'event': - var ev = { name: packet.name }; + for (var i = 0, l = listeners.length; i < l; i++) { + listeners[i].apply(this, args); + } + } else { + return false; + } - if (packet.args && packet.args.length) { - ev.args = packet.args; - } + return true; + }; - data = JSON.stringify(ev); - break; +})( + 'undefined' != typeof io ? io : module.exports + , 'undefined' != typeof io ? io : module.parent.exports +); - case 'json': - data = JSON.stringify(packet.data); - break; +/** + * socket.io + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ - case 'connect': - if (packet.qs) - data = packet.qs; - break; +/** + * Based on JSON2 (http://www.JSON.org/js.html). + */ - case 'ack': - data = packet.ackId - + (packet.args && packet.args.length - ? '+' + JSON.stringify(packet.args) : ''); - break; - } +(function (exports, nativeJSON) { + "use strict"; - // construct packet with required fragments - var encoded = [ - type - , id + (ack == 'data' ? '+' : '') - , endpoint - ]; + // use native JSON if it's available + if (nativeJSON && nativeJSON.parse){ + return exports.JSON = { + parse: nativeJSON.parse + , stringify: nativeJSON.stringify + }; + } - // data fragment is optional - if (data !== null && data !== undefined) - encoded.push(data); + var JSON = exports.JSON = {}; - return encoded.join(':'); + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + function date(d, key) { + return isFinite(d.valueOf()) ? + d.getUTCFullYear() + '-' + + f(d.getUTCMonth() + 1) + '-' + + f(d.getUTCDate()) + 'T' + + f(d.getUTCHours()) + ':' + + f(d.getUTCMinutes()) + ':' + + f(d.getUTCSeconds()) + 'Z' : null; }; - /** - * Encodes multiple messages (payload). - * - * @param {Array} messages - * @api private - */ + 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; - parser.encodePayload = function (packets) { - var decoded = ''; - if (packets.length == 1) - return packets[0]; + function quote(string) { - for (var i = 0, l = packets.length; i < l; i++) { - var packet = packets[i]; - decoded += '\ufffd' + packet.length + '\ufffd' + packets[i]; - } +// 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. - return decoded; - }; + 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 + '"'; + } - /** - * Decodes a packet - * - * @api private - */ - var regexp = /([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/; + function str(key, holder) { - parser.decodePacket = function (data) { - var pieces = data.match(regexp); +// Produce a string from holder[key]. - if (!pieces) return {}; + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; - var id = pieces[2] || '' - , data = pieces[5] || '' - , packet = { - type: packets[pieces[1]] - , endpoint: pieces[4] || '' - }; +// If the value has a toJSON method, call it to obtain a replacement value. - // whether we need to acknowledge the packet - if (id) { - packet.id = id; - if (pieces[3]) - packet.ack = 'data'; - else - packet.ack = true; - } + if (value instanceof Date) { + value = date(key); + } - // handle different packet types - switch (packet.type) { - case 'error': - var pieces = data.split('+'); - packet.reason = reasons[pieces[0]] || ''; - packet.advice = advice[pieces[1]] || ''; - break; +// If we were called with a replacer function, then call the replacer to +// obtain a replacement value. - case 'message': - packet.data = data || ''; - break; + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } - case 'event': - try { - var opts = JSON.parse(data); - packet.name = opts.name; - packet.args = opts.args; - } catch (e) { } +// What happens next depends on the value's type. - packet.args = packet.args || []; - break; + switch (typeof value) { + case 'string': + return quote(value); - case 'json': - try { - packet.data = JSON.parse(data); - } catch (e) { } - break; + case 'number': - case 'connect': - packet.qs = data || ''; - break; +// JSON numbers must be finite. Encode non-finite numbers as null. - case 'ack': - var pieces = data.match(/^([0-9]+)(\+)?(.*)/); - if (pieces) { - packet.ackId = pieces[1]; - packet.args = []; + return isFinite(value) ? String(value) : 'null'; - if (pieces[3]) { - try { - packet.args = pieces[3] ? JSON.parse(pieces[3]) : []; - } catch (e) { } - } - } - break; + case 'boolean': + case 'null': - case 'disconnect': - case 'heartbeat': - break; - }; +// 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 packet; - }; + return String(value); - /** - * Decodes data payload. Detects multiple messages - * - * @return {Array} messages - * @api public - */ +// If the type is 'object', we might be dealing with an object or an array or +// null. - parser.decodePayload = function (data) { - // IE doesn't like data[i] for unicode chars, charAt works fine - if (data.charAt(0) == '\ufffd') { - var ret = []; + case 'object': - for (var i = 1, length = ''; i < data.length; i++) { - if (data.charAt(i) == '\ufffd') { - ret.push(parser.decodePacket(data.substr(i + 1).substr(0, length))); - i += Number(length) + 1; - length = ''; - } else { - length += data.charAt(i); - } - } +// Due to a specification blunder in ECMAScript, typeof null is 'object', +// so watch out for that case. - return ret; - } else { - return [parser.decodePacket(data)]; - } - }; + if (!value) { + return 'null'; + } -})( - 'undefined' != typeof io ? io : module.exports - , 'undefined' != typeof io ? io : module.parent.exports -); -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ +// Make an array to hold the partial results of stringifying this object value. -(function (exports, io) { + gap += indent; + partial = []; - /** - * Expose constructor. - */ +// Is the value an array? - exports.Transport = Transport; + if (Object.prototype.toString.apply(value) === '[object Array]') { - /** - * This is the transport template for all supported transport methods. - * - * @constructor - * @api public - */ +// The value is an array. Stringify every element. Use null as a placeholder +// for non-JSON values. - function Transport (socket, sessid) { - this.socket = socket; - this.sessid = sessid; - }; + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } - /** - * Apply EventEmitter mixin. - */ +// Join all of the elements together, separated with commas, and wrap them in +// brackets. - io.util.mixin(Transport, io.EventEmitter); + 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. - /** - * Indicates whether heartbeats is enabled for this transport - * - * @api private - */ + 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 { - Transport.prototype.heartbeats = function () { - return true; - }; +// Otherwise, iterate through all of the keys in the object. - /** - * Handles the response from the server. When a new response is received - * it will automatically update the timeout, decode the message and - * forwards the response to the onMessage function for further processing. - * - * @param {String} data Response from the server. - * @api private - */ + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } - Transport.prototype.onData = function (data) { - this.clearCloseTimeout(); +// Join all of the member texts together, separated with commas, +// and wrap them in braces. - // If the connection in currently open (or in a reopening state) reset the close - // timeout since we have just received data. This check is necessary so - // that we don't reset the timeout on an explicitly disconnected connection. - if (this.socket.connected || this.socket.connecting || this.socket.reconnecting) { - this.setCloseTimeout(); - } + v = partial.length === 0 ? '{}' : gap ? + '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : + '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + } - if (data !== '') { - // todo: we should only do decodePayload for xhr transports - var msgs = io.parser.decodePayload(data); +// If the JSON object does not yet have a stringify method, give it one. - if (msgs && msgs.length) { - for (var i = 0, l = msgs.length; i < l; i++) { - this.onPacket(msgs[i]); - } + 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; } - } - return this; +// 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}); }; - /** - * Handles packets. - * - * @api private - */ +// If the JSON object does not yet have a parse method, give it one. - Transport.prototype.onPacket = function (packet) { - this.socket.setHeartbeatTimeout(); + 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. - if (packet.type == 'heartbeat') { - return this.onHeartbeat(); - } + var j; - if (packet.type == 'connect' && packet.endpoint == '') { - this.onConnect(); - } + function walk(holder, key) { - if (packet.type == 'error' && packet.advice == 'reconnect') { - this.isOpen = false; - } + // The walk method is used to recursively walk the resulting structure so + // that modifications can be made. - this.socket.onPacket(packet); + 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); + } - return this; - }; - /** - * Sets close timeout - * - * @api private - */ + // 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. - Transport.prototype.setCloseTimeout = function () { - if (!this.closeTimeout) { - var self = this; + 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); + }); + } - this.closeTimeout = setTimeout(function () { - self.onDisconnect(); - }, this.socket.closeTimeout); - } - }; + // 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. - /** - * Called when transport disconnects. - * - * @api private - */ + // 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. - Transport.prototype.onDisconnect = function () { - if (this.isOpen) this.close(); - this.clearTimeouts(); - this.socket.onDisconnect(); - return this; + 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'); }; +})( + 'undefined' != typeof io ? io : module.exports + , typeof JSON !== 'undefined' ? JSON : undefined +); + +/** + * socket.io + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +(function (exports, io) { + /** - * Called when transport connects + * Parser namespace. * - * @api private + * @namespace */ - Transport.prototype.onConnect = function () { - this.socket.onConnect(); - return this; - }; + var parser = exports.parser = {}; /** - * Clears close timeout - * - * @api private + * Packet types. */ - Transport.prototype.clearCloseTimeout = function () { - if (this.closeTimeout) { - clearTimeout(this.closeTimeout); - this.closeTimeout = null; - } - }; - - /** - * Clear timeouts - * - * @api private - */ - - Transport.prototype.clearTimeouts = function () { - this.clearCloseTimeout(); - - if (this.reopenTimeout) { - clearTimeout(this.reopenTimeout); - } - }; - - /** - * Sends a packet - * - * @param {Object} packet object. - * @api private - */ - - Transport.prototype.packet = function (packet) { - this.send(io.parser.encodePacket(packet)); - }; + var packets = parser.packets = [ + 'disconnect' + , 'connect' + , 'heartbeat' + , 'message' + , 'json' + , 'event' + , 'ack' + , 'error' + , 'noop' + ]; /** - * Send the received heartbeat message back to server. So the server - * knows we are still connected. - * - * @param {String} heartbeat Heartbeat response from the server. - * @api private + * Errors reasons. */ - Transport.prototype.onHeartbeat = function (heartbeat) { - this.packet({ type: 'heartbeat' }); - }; + var reasons = parser.reasons = [ + 'transport not supported' + , 'client not handshaken' + , 'unauthorized' + ]; /** - * Called when the transport opens. - * - * @api private + * Errors advice. */ - Transport.prototype.onOpen = function () { - this.isOpen = true; - this.clearCloseTimeout(); - this.socket.onOpen(); - }; + var advice = parser.advice = [ + 'reconnect' + ]; /** - * Notifies the base when the connection with the Socket.IO server - * has been disconnected. - * - * @api private + * Shortcuts. */ - Transport.prototype.onClose = function () { - var self = this; - - /* FIXME: reopen delay causing a infinit loop - this.reopenTimeout = setTimeout(function () { - self.open(); - }, this.socket.options['reopen delay']);*/ - - this.isOpen = false; - this.socket.onClose(); - this.onDisconnect(); - }; + var JSON = io.JSON + , indexOf = io.util.indexOf; /** - * Generates a connection url based on the Socket.IO URL Protocol. - * See for more details. + * Encodes a packet. * - * @returns {String} Connection url * @api private */ - Transport.prototype.prepareUrl = function () { - var options = this.socket.options; - - return this.scheme() + '://' - + options.host + ':' + options.port + '/' - + options.resource + '/' + io.protocol - + '/' + this.name + '/' + this.sessid; - }; + parser.encodePacket = function (packet) { + var type = indexOf(packets, packet.type) + , id = packet.id || '' + , endpoint = packet.endpoint || '' + , ack = packet.ack + , data = null; - /** - * Checks if the transport is ready to start a connection. - * - * @param {Socket} socket The socket instance that needs a transport - * @param {Function} fn The callback - * @api private - */ + switch (packet.type) { + case 'error': + var reason = packet.reason ? indexOf(reasons, packet.reason) : '' + , adv = packet.advice ? indexOf(advice, packet.advice) : ''; - Transport.prototype.ready = function (socket, fn) { - fn.call(this); - }; -})( - 'undefined' != typeof io ? io : module.exports - , 'undefined' != typeof io ? io : module.parent.exports -); -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ + if (reason !== '' || adv !== '') + data = reason + (adv !== '' ? ('+' + adv) : ''); -(function (exports, io, global) { + break; - /** - * Expose constructor. - */ + case 'message': + if (packet.data !== '') + data = packet.data; + break; - exports.Socket = Socket; + case 'event': + var ev = { name: packet.name }; - /** - * Create a new `Socket.IO client` which can establish a persistent - * connection with a Socket.IO enabled server. - * - * @api public - */ + if (packet.args && packet.args.length) { + ev.args = packet.args; + } - function Socket (options) { - this.options = { - port: 80 - , secure: false - , document: 'document' in global ? document : false - , resource: 'socket.io' - , transports: io.transports - , 'connect timeout': 10000 - , 'try multiple transports': true - , 'reconnect': true - , 'reconnection delay': 500 - , 'reconnection limit': Infinity - , 'reopen delay': 3000 - , 'max reconnection attempts': 10 - , 'sync disconnect on unload': false - , 'auto connect': true - , 'flash policy port': 10843 - , 'manualFlush': false - }; + data = JSON.stringify(ev); + break; - io.util.merge(this.options, options); + case 'json': + data = JSON.stringify(packet.data); + break; - this.connected = false; - this.open = false; - this.connecting = false; - this.reconnecting = false; - this.namespaces = {}; - this.buffer = []; - this.doBuffer = false; + case 'connect': + if (packet.qs) + data = packet.qs; + break; - if (this.options['sync disconnect on unload'] && - (!this.isXDomain() || io.util.ua.hasCORS)) { - var self = this; - io.util.on(global, 'beforeunload', function () { - self.disconnectSync(); - }, false); + case 'ack': + data = packet.ackId + + (packet.args && packet.args.length + ? '+' + JSON.stringify(packet.args) : ''); + break; } - if (this.options['auto connect']) { - this.connect(); - } -}; + // construct packet with required fragments + var encoded = [ + type + , id + (ack == 'data' ? '+' : '') + , endpoint + ]; - /** - * Apply EventEmitter mixin. - */ + // data fragment is optional + if (data !== null && data !== undefined) + encoded.push(data); - io.util.mixin(Socket, io.EventEmitter); + return encoded.join(':'); + }; /** - * Returns a namespace listener/emitter for this socket + * Encodes multiple messages (payload). * - * @api public + * @param {Array} messages + * @api private */ - Socket.prototype.of = function (name) { - if (!this.namespaces[name]) { - this.namespaces[name] = new io.SocketNamespace(this, name); + parser.encodePayload = function (packets) { + var decoded = ''; - if (name !== '') { - this.namespaces[name].packet({ type: 'connect' }); - } + if (packets.length == 1) + return packets[0]; + + for (var i = 0, l = packets.length; i < l; i++) { + var packet = packets[i]; + decoded += '\ufffd' + packet.length + '\ufffd' + packets[i]; } - return this.namespaces[name]; + return decoded; }; /** - * Emits the given event to the Socket and all namespaces + * Decodes a packet * * @api private */ - Socket.prototype.publish = function () { - this.emit.apply(this, arguments); + var regexp = /([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/; - var nsp; + parser.decodePacket = function (data) { + var pieces = data.match(regexp); - for (var i in this.namespaces) { - if (this.namespaces.hasOwnProperty(i)) { - nsp = this.of(i); - nsp.$emit.apply(nsp, arguments); - } - } - }; + if (!pieces) return {}; - /** - * Performs the handshake - * - * @api private - */ + var id = pieces[2] || '' + , data = pieces[5] || '' + , packet = { + type: packets[pieces[1]] + , endpoint: pieces[4] || '' + }; - function empty () { }; + // whether we need to acknowledge the packet + if (id) { + packet.id = id; + if (pieces[3]) + packet.ack = 'data'; + else + packet.ack = true; + } - Socket.prototype.handshake = function (fn) { - var self = this - , options = this.options; + // handle different packet types + switch (packet.type) { + case 'error': + var pieces = data.split('+'); + packet.reason = reasons[pieces[0]] || ''; + packet.advice = advice[pieces[1]] || ''; + break; - function complete (data) { - if (data instanceof Error) { - self.connecting = false; - self.onError(data.message); - } else { - fn.apply(null, data.split(':')); - } - }; + case 'message': + packet.data = data || ''; + break; - var url = [ - 'http' + (options.secure ? 's' : '') + ':/' - , options.host + ':' + options.port - , options.resource - , io.protocol - , io.util.query(this.options.query, 't=' + +new Date) - ].join('/'); + case 'event': + try { + var opts = JSON.parse(data); + packet.name = opts.name; + packet.args = opts.args; + } catch (e) { } - if (this.isXDomain() && !io.util.ua.hasCORS) { - var insertAt = document.getElementsByTagName('script')[0] - , script = document.createElement('script'); + packet.args = packet.args || []; + break; - script.src = url + '&jsonp=' + io.j.length; - insertAt.parentNode.insertBefore(script, insertAt); + case 'json': + try { + packet.data = JSON.parse(data); + } catch (e) { } + break; - io.j.push(function (data) { - complete(data); - script.parentNode.removeChild(script); - }); - } else { - var xhr = io.util.request(); + case 'connect': + packet.qs = data || ''; + break; - xhr.open('GET', url, true); - if (this.isXDomain()) { - xhr.withCredentials = true; - } - xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - xhr.onreadystatechange = empty; + case 'ack': + var pieces = data.match(/^([0-9]+)(\+)?(.*)/); + if (pieces) { + packet.ackId = pieces[1]; + packet.args = []; - if (xhr.status == 200) { - complete(xhr.responseText); - } else if (xhr.status == 403) { - self.onError(xhr.responseText); - } else { - self.connecting = false; - !self.reconnecting && self.onError(xhr.responseText); + if (pieces[3]) { + try { + packet.args = pieces[3] ? JSON.parse(pieces[3]) : []; + } catch (e) { } } } - }; - xhr.send(null); - } - }; - - /** - * Find an available transport based on the options supplied in the constructor. - * - * @api private - */ - - Socket.prototype.getTransport = function (override) { - var transports = override || this.transports, match; + break; - for (var i = 0, transport; transport = transports[i]; i++) { - if (io.Transport[transport] - && io.Transport[transport].check(this) - && (!this.isXDomain() || io.Transport[transport].xdomainCheck(this))) { - return new io.Transport[transport](this, this.sessionid); - } - } + case 'disconnect': + case 'heartbeat': + break; + }; - return null; + return packet; }; /** - * Connects to the server. + * Decodes data payload. Detects multiple messages * - * @param {Function} [fn] Callback. - * @returns {io.Socket} + * @return {Array} messages * @api public */ - Socket.prototype.connect = function (fn) { - if (this.connecting) { - return this; - } - - var self = this; - self.connecting = true; - - this.handshake(function (sid, heartbeat, close, transports) { - self.sessionid = sid; - self.closeTimeout = close * 1000; - self.heartbeatTimeout = heartbeat * 1000; - if(!self.transports) - self.transports = self.origTransports = (transports ? io.util.intersect( - transports.split(',') - , self.options.transports - ) : self.options.transports); - - self.setHeartbeatTimeout(); - - function connect (transports){ - if (self.transport) self.transport.clearTimeouts(); - - self.transport = self.getTransport(transports); - if (!self.transport) return self.publish('connect_failed'); - - // once the transport is ready - self.transport.ready(self, function () { - self.connecting = true; - self.publish('connecting', self.transport.name); - self.transport.open(); - - if (self.options['connect timeout']) { - self.connectTimeoutTimer = setTimeout(function () { - if (!self.connected) { - self.connecting = false; - - if (self.options['try multiple transports']) { - var remaining = self.transports; - - while (remaining.length > 0 && remaining.splice(0,1)[0] != - self.transport.name) {} + parser.decodePayload = function (data) { + // IE doesn't like data[i] for unicode chars, charAt works fine + if (data.charAt(0) == '\ufffd') { + var ret = []; - if (remaining.length){ - connect(remaining); - } else { - self.publish('connect_failed'); - } - } - } - }, self.options['connect timeout']); - } - }); + for (var i = 1, length = ''; i < data.length; i++) { + if (data.charAt(i) == '\ufffd') { + ret.push(parser.decodePacket(data.substr(i + 1).substr(0, length))); + i += Number(length) + 1; + length = ''; + } else { + length += data.charAt(i); + } } - connect(self.transports); + return ret; + } else { + return [parser.decodePacket(data)]; + } + }; - self.once('connect', function (){ - clearTimeout(self.connectTimeoutTimer); +})( + 'undefined' != typeof io ? io : module.exports + , 'undefined' != typeof io ? io : module.parent.exports +); +/** + * socket.io + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ - fn && typeof fn == 'function' && fn(); - }); - }); +(function (exports, io) { - return this; - }; + /** + * Expose constructor. + */ + + exports.Transport = Transport; /** - * Clears and sets a new heartbeat timeout using the value given by the - * server during the handshake. + * This is the transport template for all supported transport methods. * - * @api private + * @constructor + * @api public */ - Socket.prototype.setHeartbeatTimeout = function () { - clearTimeout(this.heartbeatTimeoutTimer); - if(this.transport && !this.transport.heartbeats()) return; - - var self = this; - this.heartbeatTimeoutTimer = setTimeout(function () { - self.transport.onClose(); - }, this.heartbeatTimeout); + function Transport (socket, sessid) { + this.socket = socket; + this.sessid = sessid; }; /** - * Sends a message. - * - * @param {Object} data packet. - * @returns {io.Socket} - * @api public + * Apply EventEmitter mixin. */ - Socket.prototype.packet = function (data) { - if (this.connected && !this.doBuffer) { - this.transport.packet(data); - } else { - this.buffer.push(data); - } + io.util.mixin(Transport, io.EventEmitter); - return this; - }; /** - * Sets buffer state + * Indicates whether heartbeats is enabled for this transport * * @api private */ - Socket.prototype.setBuffer = function (v) { - this.doBuffer = v; - - if (!v && this.connected && this.buffer.length) { - if (!this.options['manualFlush']) { - this.flushBuffer(); - } - } + Transport.prototype.heartbeats = function () { + return true; }; /** - * Flushes the buffer data over the wire. - * To be invoked manually when 'manualFlush' is set to true. + * Handles the response from the server. When a new response is received + * it will automatically update the timeout, decode the message and + * forwards the response to the onMessage function for further processing. * - * @api public + * @param {String} data Response from the server. + * @api private */ - Socket.prototype.flushBuffer = function() { - this.transport.payload(this.buffer); - this.buffer = []; + Transport.prototype.onData = function (data) { + this.clearCloseTimeout(); + + // If the connection in currently open (or in a reopening state) reset the close + // timeout since we have just received data. This check is necessary so + // that we don't reset the timeout on an explicitly disconnected connection. + if (this.socket.connected || this.socket.connecting || this.socket.reconnecting) { + this.setCloseTimeout(); + } + + if (data !== '') { + // todo: we should only do decodePayload for xhr transports + var msgs = io.parser.decodePayload(data); + + if (msgs && msgs.length) { + for (var i = 0, l = msgs.length; i < l; i++) { + this.onPacket(msgs[i]); + } + } + } + + return this; }; - /** - * Disconnect the established connect. + * Handles packets. * - * @returns {io.Socket} - * @api public + * @api private */ - Socket.prototype.disconnect = function () { - if (this.connected || this.connecting) { - if (this.open) { - this.of('').packet({ type: 'disconnect' }); - } + Transport.prototype.onPacket = function (packet) { + this.socket.setHeartbeatTimeout(); - // handle disconnection immediately - this.onDisconnect('booted'); + if (packet.type == 'heartbeat') { + return this.onHeartbeat(); + } + + if (packet.type == 'connect' && packet.endpoint == '') { + this.onConnect(); + } + + if (packet.type == 'error' && packet.advice == 'reconnect') { + this.isOpen = false; } + this.socket.onPacket(packet); + return this; }; /** - * Disconnects the socket with a sync XHR. + * Sets close timeout * * @api private */ - Socket.prototype.disconnectSync = function () { - // ensure disconnection - var xhr = io.util.request(); - var uri = [ - 'http' + (this.options.secure ? 's' : '') + ':/' - , this.options.host + ':' + this.options.port - , this.options.resource - , io.protocol - , '' - , this.sessionid - ].join('/') + '/?disconnect=1'; - - xhr.open('GET', uri, false); - xhr.send(null); + Transport.prototype.setCloseTimeout = function () { + if (!this.closeTimeout) { + var self = this; - // handle disconnection immediately - this.onDisconnect('booted'); + this.closeTimeout = setTimeout(function () { + self.onDisconnect(); + }, this.socket.closeTimeout); + } }; /** - * Check if we need to use cross domain enabled transports. Cross domain would - * be a different port or different domain name. + * Called when transport disconnects. * - * @returns {Boolean} * @api private */ - Socket.prototype.isXDomain = function () { + Transport.prototype.onDisconnect = function () { + if (this.isOpen) this.close(); + this.clearTimeouts(); + this.socket.onDisconnect(); + return this; + }; - var port = global.location.port || - ('https:' == global.location.protocol ? 443 : 80); + /** + * Called when transport connects + * + * @api private + */ - return this.options.host !== global.location.hostname - || this.options.port != port; + Transport.prototype.onConnect = function () { + this.socket.onConnect(); + return this; }; /** - * Called upon handshake. + * Clears close timeout * * @api private */ - Socket.prototype.onConnect = function () { - if (!this.connected) { - this.connected = true; - this.connecting = false; - if (!this.doBuffer) { - // make sure to flush the buffer - this.setBuffer(false); - } - this.emit('connect'); + Transport.prototype.clearCloseTimeout = function () { + if (this.closeTimeout) { + clearTimeout(this.closeTimeout); + this.closeTimeout = null; } }; /** - * Called when the transport opens + * Clear timeouts * * @api private */ - Socket.prototype.onOpen = function () { - this.open = true; + Transport.prototype.clearTimeouts = function () { + this.clearCloseTimeout(); + + if (this.reopenTimeout) { + clearTimeout(this.reopenTimeout); + } }; /** - * Called when the transport closes. + * Sends a packet * + * @param {Object} packet object. * @api private */ - Socket.prototype.onClose = function () { - this.open = false; - clearTimeout(this.heartbeatTimeoutTimer); + Transport.prototype.packet = function (packet) { + this.send(io.parser.encodePacket(packet)); }; /** - * Called when the transport first opens a connection + * Send the received heartbeat message back to server. So the server + * knows we are still connected. * - * @param text + * @param {String} heartbeat Heartbeat response from the server. + * @api private */ - Socket.prototype.onPacket = function (packet) { - this.of(packet.endpoint).onPacket(packet); + Transport.prototype.onHeartbeat = function (heartbeat) { + this.packet({ type: 'heartbeat' }); }; /** - * Handles an error. + * Called when the transport opens. * * @api private */ - Socket.prototype.onError = function (err) { - if (err && err.advice) { - if (err.advice === 'reconnect' && (this.connected || this.connecting)) { - this.disconnect(); - if (this.options.reconnect) { - this.reconnect(); - } - } - } - - this.publish('error', err && err.reason ? err.reason : err); + Transport.prototype.onOpen = function () { + this.isOpen = true; + this.clearCloseTimeout(); + this.socket.onOpen(); }; /** - * Called when the transport disconnects. + * Notifies the base when the connection with the Socket.IO server + * has been disconnected. * * @api private */ - Socket.prototype.onDisconnect = function (reason) { - var wasConnected = this.connected - , wasConnecting = this.connecting; - - this.connected = false; - this.connecting = false; - this.open = false; + Transport.prototype.onClose = function () { + var self = this; - if (wasConnected || wasConnecting) { - this.transport.close(); - this.transport.clearTimeouts(); - if (wasConnected) { - this.publish('disconnect', reason); + /* FIXME: reopen delay causing a infinit loop + this.reopenTimeout = setTimeout(function () { + self.open(); + }, this.socket.options['reopen delay']);*/ - if ('booted' != reason && this.options.reconnect && !this.reconnecting) { - this.reconnect(); - } - } - } + this.isOpen = false; + this.socket.onClose(); + this.onDisconnect(); }; /** - * Called upon reconnection. + * Generates a connection url based on the Socket.IO URL Protocol. + * See for more details. * + * @returns {String} Connection url * @api private */ - Socket.prototype.reconnect = function () { - this.reconnecting = true; - this.reconnectionAttempts = 0; - this.reconnectionDelay = this.options['reconnection delay']; + Transport.prototype.prepareUrl = function () { + var options = this.socket.options; - var self = this - , maxAttempts = this.options['max reconnection attempts'] - , tryMultiple = this.options['try multiple transports'] - , limit = this.options['reconnection limit']; + return this.scheme() + '://' + + options.host + ':' + options.port + '/' + + options.resource + '/' + io.protocol + + '/' + this.name + '/' + this.sessid; + }; - function reset () { - if (self.connected) { - for (var i in self.namespaces) { - if (self.namespaces.hasOwnProperty(i) && '' !== i) { - self.namespaces[i].packet({ type: 'connect' }); - } - } - self.publish('reconnect', self.transport.name, self.reconnectionAttempts); - } + /** + * Checks if the transport is ready to start a connection. + * + * @param {Socket} socket The socket instance that needs a transport + * @param {Function} fn The callback + * @api private + */ - clearTimeout(self.reconnectionTimer); + Transport.prototype.ready = function (socket, fn) { + fn.call(this); + }; +})( + 'undefined' != typeof io ? io : module.exports + , 'undefined' != typeof io ? io : module.parent.exports +); +/** + * socket.io + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ - self.removeListener('connect_failed', maybeReconnect); - self.removeListener('connect', maybeReconnect); +(function (exports, io, global) { - self.reconnecting = false; - - delete self.reconnectionAttempts; - delete self.reconnectionDelay; - delete self.reconnectionTimer; - delete self.redoTransports; - - self.options['try multiple transports'] = tryMultiple; - }; - - function maybeReconnect () { - if (!self.reconnecting) { - return; - } - - if (self.connected) { - return reset(); - }; + /** + * Expose constructor. + */ - if (self.connecting && self.reconnecting) { - return self.reconnectionTimer = setTimeout(maybeReconnect, 1000); - } + exports.Socket = Socket; - if (self.reconnectionAttempts++ >= maxAttempts) { - if (!self.redoTransports) { - self.on('connect_failed', maybeReconnect); - self.options['try multiple transports'] = true; - self.transports = self.origTransports; - self.transport = self.getTransport(); - self.redoTransports = true; - self.connect(); - } else { - self.publish('reconnect_failed'); - reset(); - } - } else { - if (self.reconnectionDelay < limit) { - self.reconnectionDelay *= 2; // exponential back off - } + /** + * Create a new `Socket.IO client` which can establish a persistent + * connection with a Socket.IO enabled server. + * + * @api public + */ - self.connect(); - self.publish('reconnecting', self.reconnectionDelay, self.reconnectionAttempts); - self.reconnectionTimer = setTimeout(maybeReconnect, self.reconnectionDelay); - } + function Socket (options) { + this.options = { + port: 80 + , secure: false + , document: 'document' in global ? document : false + , resource: 'socket.io' + , transports: io.transports + , 'connect timeout': 10000 + , 'try multiple transports': true + , 'reconnect': true + , 'reconnection delay': 500 + , 'reconnection limit': Infinity + , 'reopen delay': 3000 + , 'max reconnection attempts': 10 + , 'sync disconnect on unload': false + , 'auto connect': true + , 'flash policy port': 10843 + , 'manualFlush': false }; - this.options['try multiple transports'] = false; - this.reconnectionTimer = setTimeout(maybeReconnect, this.reconnectionDelay); + io.util.merge(this.options, options); - this.on('connect', maybeReconnect); - }; + this.connected = false; + this.open = false; + this.connecting = false; + this.reconnecting = false; + this.namespaces = {}; + this.buffer = []; + this.doBuffer = false; -})( - 'undefined' != typeof io ? io : module.exports - , 'undefined' != typeof io ? io : module.parent.exports - , this -); -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ + if (this.options['sync disconnect on unload'] && + (!this.isXDomain() || io.util.ua.hasCORS)) { + var self = this; + io.util.on(global, 'beforeunload', function () { + self.disconnectSync(); + }, false); + } -(function (exports, io) { + if (this.options['auto connect']) { + this.connect(); + } +}; /** - * Expose constructor. + * Apply EventEmitter mixin. */ - exports.SocketNamespace = SocketNamespace; + io.util.mixin(Socket, io.EventEmitter); /** - * Socket namespace constructor. + * Returns a namespace listener/emitter for this socket * - * @constructor * @api public */ - function SocketNamespace (socket, name) { - this.socket = socket; - this.name = name || ''; - this.flags = {}; - this.json = new Flag(this, 'json'); - this.ackPackets = 0; - this.acks = {}; - }; + Socket.prototype.of = function (name) { + if (!this.namespaces[name]) { + this.namespaces[name] = new io.SocketNamespace(this, name); - /** - * Apply EventEmitter mixin. - */ + if (name !== '') { + this.namespaces[name].packet({ type: 'connect' }); + } + } - io.util.mixin(SocketNamespace, io.EventEmitter); + return this.namespaces[name]; + }; /** - * Copies emit since we override it + * Emits the given event to the Socket and all namespaces * * @api private */ - SocketNamespace.prototype.$emit = io.EventEmitter.prototype.emit; + Socket.prototype.publish = function () { + this.emit.apply(this, arguments); - /** - * Creates a new namespace, by proxying the request to the socket. This - * allows us to use the synax as we do on the server. - * - * @api public - */ + var nsp; - SocketNamespace.prototype.of = function () { - return this.socket.of.apply(this.socket, arguments); + for (var i in this.namespaces) { + if (this.namespaces.hasOwnProperty(i)) { + nsp = this.of(i); + nsp.$emit.apply(nsp, arguments); + } + } }; /** - * Sends a packet. + * Performs the handshake * * @api private */ - SocketNamespace.prototype.packet = function (packet) { - packet.endpoint = this.name; - this.socket.packet(packet); - this.flags = {}; - return this; - }; + function empty () { }; - /** - * Sends a message - * - * @api public - */ + Socket.prototype.handshake = function (fn) { + var self = this + , options = this.options; - SocketNamespace.prototype.send = function (data, fn) { - var packet = { - type: this.flags.json ? 'json' : 'message' - , data: data + function complete (data) { + if (data instanceof Error) { + self.connecting = false; + self.onError(data.message); + } else { + fn.apply(null, data.split(':')); + } }; - if ('function' == typeof fn) { - packet.id = ++this.ackPackets; - packet.ack = true; - this.acks[packet.id] = fn; - } + var url = [ + 'http' + (options.secure ? 's' : '') + ':/' + , options.host + ':' + options.port + , options.resource + , io.protocol + , io.util.query(this.options.query, 't=' + +new Date) + ].join('/'); - return this.packet(packet); - }; + if (this.isXDomain() && !io.util.ua.hasCORS) { + var insertAt = document.getElementsByTagName('script')[0] + , script = document.createElement('script'); - /** - * Emits an event - * - * @api public - */ - - SocketNamespace.prototype.emit = function (name) { - var args = Array.prototype.slice.call(arguments, 1) - , lastArg = args[args.length - 1] - , packet = { - type: 'event' - , name: name - }; + script.src = url + '&jsonp=' + io.j.length; + insertAt.parentNode.insertBefore(script, insertAt); - if ('function' == typeof lastArg) { - packet.id = ++this.ackPackets; - packet.ack = 'data'; - this.acks[packet.id] = lastArg; - args = args.slice(0, args.length - 1); - } + io.j.push(function (data) { + complete(data); + script.parentNode.removeChild(script); + }); + } else { + var xhr = io.util.request(); - packet.args = args; + xhr.open('GET', url, true); + if (this.isXDomain()) { + xhr.withCredentials = true; + } + xhr.onreadystatechange = function () { + if (xhr.readyState == 4) { + xhr.onreadystatechange = empty; - return this.packet(packet); + if (xhr.status == 200) { + complete(xhr.responseText); + } else if (xhr.status == 403) { + self.onError(xhr.responseText); + } else { + self.connecting = false; + !self.reconnecting && self.onError(xhr.responseText); + } + } + }; + xhr.send(null); + } }; /** - * Disconnects the namespace + * Find an available transport based on the options supplied in the constructor. * * @api private */ - SocketNamespace.prototype.disconnect = function () { - if (this.name === '') { - this.socket.disconnect(); - } else { - this.packet({ type: 'disconnect' }); - this.$emit('disconnect'); - } + Socket.prototype.getTransport = function (override) { + var transports = override || this.transports, match; - return this; + for (var i = 0, transport; transport = transports[i]; i++) { + if (io.Transport[transport] + && io.Transport[transport].check(this) + && (!this.isXDomain() || io.Transport[transport].xdomainCheck(this))) { + return new io.Transport[transport](this, this.sessionid); + } + } + + return null; }; /** - * Handles a packet + * Connects to the server. * - * @api private + * @param {Function} [fn] Callback. + * @returns {io.Socket} + * @api public */ - SocketNamespace.prototype.onPacket = function (packet) { + Socket.prototype.connect = function (fn) { + if (this.connecting) { + return this; + } + var self = this; + self.connecting = true; + + this.handshake(function (sid, heartbeat, close, transports) { + self.sessionid = sid; + self.closeTimeout = close * 1000; + self.heartbeatTimeout = heartbeat * 1000; + if(!self.transports) + self.transports = self.origTransports = (transports ? io.util.intersect( + transports.split(',') + , self.options.transports + ) : self.options.transports); - function ack () { - self.packet({ - type: 'ack' - , args: io.util.toArray(arguments) - , ackId: packet.id - }); - }; + self.setHeartbeatTimeout(); - switch (packet.type) { - case 'connect': - this.$emit('connect'); - break; + function connect (transports){ + if (self.transport) self.transport.clearTimeouts(); - case 'disconnect': - if (this.name === '') { - this.socket.onDisconnect(packet.reason || 'booted'); - } else { - this.$emit('disconnect', packet.reason); - } - break; + self.transport = self.getTransport(transports); + if (!self.transport) return self.publish('connect_failed'); - case 'message': - case 'json': - var params = ['message', packet.data]; + // once the transport is ready + self.transport.ready(self, function () { + self.connecting = true; + self.publish('connecting', self.transport.name); + self.transport.open(); - if (packet.ack == 'data') { - params.push(ack); - } else if (packet.ack) { - this.packet({ type: 'ack', ackId: packet.id }); - } + if (self.options['connect timeout']) { + self.connectTimeoutTimer = setTimeout(function () { + if (!self.connected) { + self.connecting = false; - this.$emit.apply(this, params); - break; + if (self.options['try multiple transports']) { + var remaining = self.transports; - case 'event': - var params = [packet.name].concat(packet.args); + while (remaining.length > 0 && remaining.splice(0,1)[0] != + self.transport.name) {} - if (packet.ack == 'data') - params.push(ack); + if (remaining.length){ + connect(remaining); + } else { + self.publish('connect_failed'); + } + } + } + }, self.options['connect timeout']); + } + }); + } - this.$emit.apply(this, params); - break; + connect(self.transports); - case 'ack': - if (this.acks[packet.ackId]) { - this.acks[packet.ackId].apply(this, packet.args); - delete this.acks[packet.ackId]; - } - break; + self.once('connect', function (){ + clearTimeout(self.connectTimeoutTimer); - case 'error': - if (packet.advice){ - this.socket.onError(packet); - } else { - if (packet.reason == 'unauthorized') { - this.$emit('connect_failed', packet.reason); - } else { - this.$emit('error', packet.reason); - } - } - break; - } + fn && typeof fn == 'function' && fn(); + }); + }); + + return this; }; /** - * Flag interface. + * Clears and sets a new heartbeat timeout using the value given by the + * server during the handshake. * * @api private */ - function Flag (nsp, name) { - this.namespace = nsp; - this.name = name; + Socket.prototype.setHeartbeatTimeout = function () { + clearTimeout(this.heartbeatTimeoutTimer); + if(this.transport && !this.transport.heartbeats()) return; + + var self = this; + this.heartbeatTimeoutTimer = setTimeout(function () { + self.transport.onClose(); + }, this.heartbeatTimeout); }; /** - * Send a message + * Sends a message. * + * @param {Object} data packet. + * @returns {io.Socket} * @api public */ - Flag.prototype.send = function () { - this.namespace.flags[this.name] = true; - this.namespace.send.apply(this.namespace, arguments); + Socket.prototype.packet = function (data) { + if (this.connected && !this.doBuffer) { + this.transport.packet(data); + } else { + this.buffer.push(data); + } + + return this; }; /** - * Emit an event + * Sets buffer state * - * @api public + * @api private */ - Flag.prototype.emit = function () { - this.namespace.flags[this.name] = true; - this.namespace.emit.apply(this.namespace, arguments); - }; - -})( - 'undefined' != typeof io ? io : module.exports - , 'undefined' != typeof io ? io : module.parent.exports -); - -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ + Socket.prototype.setBuffer = function (v) { + this.doBuffer = v; -(function (exports, io, global) { + if (!v && this.connected && this.buffer.length) { + if (!this.options['manualFlush']) { + this.flushBuffer(); + } + } + }; /** - * Expose constructor. + * Flushes the buffer data over the wire. + * To be invoked manually when 'manualFlush' is set to true. + * + * @api public */ - exports.websocket = WS; + Socket.prototype.flushBuffer = function() { + this.transport.payload(this.buffer); + this.buffer = []; + }; + /** - * The WebSocket transport uses the HTML5 WebSocket API to establish an - * persistent connection with the Socket.IO server. This transport will also - * be inherited by the FlashSocket fallback as it provides a API compatible - * polyfill for the WebSockets. + * Disconnect the established connect. * - * @constructor - * @extends {io.Transport} + * @returns {io.Socket} * @api public */ - function WS (socket) { - io.Transport.apply(this, arguments); + Socket.prototype.disconnect = function () { + if (this.connected || this.connecting) { + if (this.open) { + this.of('').packet({ type: 'disconnect' }); + } + + // handle disconnection immediately + this.onDisconnect('booted'); + } + + return this; }; /** - * Inherits from Transport. + * Disconnects the socket with a sync XHR. + * + * @api private */ - io.util.inherit(WS, io.Transport); + Socket.prototype.disconnectSync = function () { + // ensure disconnection + var xhr = io.util.request(); + var uri = [ + 'http' + (this.options.secure ? 's' : '') + ':/' + , this.options.host + ':' + this.options.port + , this.options.resource + , io.protocol + , '' + , this.sessionid + ].join('/') + '/?disconnect=1'; + + xhr.open('GET', uri, false); + xhr.send(null); + + // handle disconnection immediately + this.onDisconnect('booted'); + }; /** - * Transport name + * Check if we need to use cross domain enabled transports. Cross domain would + * be a different port or different domain name. * - * @api public + * @returns {Boolean} + * @api private */ - WS.prototype.name = 'websocket'; + Socket.prototype.isXDomain = function () { + + var port = global.location.port || + ('https:' == global.location.protocol ? 443 : 80); + + return this.options.host !== global.location.hostname + || this.options.port != port; + }; /** - * Initializes a new `WebSocket` connection with the Socket.IO server. We attach - * all the appropriate listeners to handle the responses from the server. + * Called upon handshake. * - * @returns {Transport} - * @api public + * @api private */ - WS.prototype.open = function () { - var query = io.util.query(this.socket.options.query) - , self = this - , Socket - - - if (!Socket) { - Socket = global.MozWebSocket || global.WebSocket; + Socket.prototype.onConnect = function () { + if (!this.connected) { + this.connected = true; + this.connecting = false; + if (!this.doBuffer) { + // make sure to flush the buffer + this.setBuffer(false); + } + this.emit('connect'); } - - this.websocket = new Socket(this.prepareUrl() + query); - - this.websocket.onopen = function () { - self.onOpen(); - self.socket.setBuffer(false); - }; - this.websocket.onmessage = function (ev) { - self.onData(ev.data); - }; - this.websocket.onclose = function () { - self.onClose(); - self.socket.setBuffer(true); - }; - this.websocket.onerror = function (e) { - self.onError(e); - }; - - return this; }; /** - * Send a message to the Socket.IO server. The message will automatically be - * encoded in the correct message format. + * Called when the transport opens * - * @returns {Transport} - * @api public + * @api private */ - // Do to a bug in the current IDevices browser, we need to wrap the send in a - // setTimeout, when they resume from sleeping the browser will crash if - // we don't allow the browser time to detect the socket has been closed - if (io.util.ua.iDevice) { - WS.prototype.send = function (data) { - var self = this; - setTimeout(function() { - self.websocket.send(data); - },0); - return this; - }; - } else { - WS.prototype.send = function (data) { - this.websocket.send(data); - return this; - }; - } + Socket.prototype.onOpen = function () { + this.open = true; + }; /** - * Payload + * Called when the transport closes. * * @api private */ - WS.prototype.payload = function (arr) { - for (var i = 0, l = arr.length; i < l; i++) { - this.packet(arr[i]); - } - return this; + Socket.prototype.onClose = function () { + this.open = false; + clearTimeout(this.heartbeatTimeoutTimer); }; /** - * Disconnect the established `WebSocket` connection. + * Called when the transport first opens a connection * - * @returns {Transport} - * @api public + * @param text */ - WS.prototype.close = function () { - this.websocket.close(); - return this; + Socket.prototype.onPacket = function (packet) { + this.of(packet.endpoint).onPacket(packet); }; /** - * Handle the errors that `WebSocket` might be giving when we - * are attempting to connect or send messages. + * Handles an error. * - * @param {Error} e The error. * @api private */ - WS.prototype.onError = function (e) { - this.socket.onError(e); + Socket.prototype.onError = function (err) { + if (err && err.advice) { + if (err.advice === 'reconnect' && (this.connected || this.connecting)) { + this.disconnect(); + if (this.options.reconnect) { + this.reconnect(); + } + } + } + + this.publish('error', err && err.reason ? err.reason : err); }; /** - * Returns the appropriate scheme for the URI generation. + * Called when the transport disconnects. * * @api private */ - WS.prototype.scheme = function () { - return this.socket.options.secure ? 'wss' : 'ws'; - }; - /** - * Checks if the browser has support for native `WebSockets` and that - * it's not the polyfill created for the FlashSocket transport. - * - * @return {Boolean} - * @api public - */ + Socket.prototype.onDisconnect = function (reason) { + var wasConnected = this.connected + , wasConnecting = this.connecting; - WS.check = function () { - return ('WebSocket' in global && !('__addTask' in WebSocket)) - || 'MozWebSocket' in global; - }; + this.connected = false; + this.connecting = false; + this.open = false; - /** - * Check if the `WebSocket` transport support cross domain communications. - * - * @returns {Boolean} - * @api public - */ + if (wasConnected || wasConnecting) { + this.transport.close(); + this.transport.clearTimeouts(); + if (wasConnected) { + this.publish('disconnect', reason); - WS.xdomainCheck = function () { - return true; + if ('booted' != reason && this.options.reconnect && !this.reconnecting) { + this.reconnect(); + } + } + } }; /** - * Add the transport to your public io.transports array. + * Called upon reconnection. * * @api private */ - io.transports.push('websocket'); + Socket.prototype.reconnect = function () { + this.reconnecting = true; + this.reconnectionAttempts = 0; + this.reconnectionDelay = this.options['reconnection delay']; + + var self = this + , maxAttempts = this.options['max reconnection attempts'] + , tryMultiple = this.options['try multiple transports'] + , limit = this.options['reconnection limit']; + + function reset () { + if (self.connected) { + for (var i in self.namespaces) { + if (self.namespaces.hasOwnProperty(i) && '' !== i) { + self.namespaces[i].packet({ type: 'connect' }); + } + } + self.publish('reconnect', self.transport.name, self.reconnectionAttempts); + } + + clearTimeout(self.reconnectionTimer); + + self.removeListener('connect_failed', maybeReconnect); + self.removeListener('connect', maybeReconnect); + + self.reconnecting = false; + + delete self.reconnectionAttempts; + delete self.reconnectionDelay; + delete self.reconnectionTimer; + delete self.redoTransports; + + self.options['try multiple transports'] = tryMultiple; + }; + + function maybeReconnect () { + if (!self.reconnecting) { + return; + } + + if (self.connected) { + return reset(); + }; + + if (self.connecting && self.reconnecting) { + return self.reconnectionTimer = setTimeout(maybeReconnect, 1000); + } + + if (self.reconnectionAttempts++ >= maxAttempts) { + if (!self.redoTransports) { + self.on('connect_failed', maybeReconnect); + self.options['try multiple transports'] = true; + self.transports = self.origTransports; + self.transport = self.getTransport(); + self.redoTransports = true; + self.connect(); + } else { + self.publish('reconnect_failed'); + reset(); + } + } else { + if (self.reconnectionDelay < limit) { + self.reconnectionDelay *= 2; // exponential back off + } + + self.connect(); + self.publish('reconnecting', self.reconnectionDelay, self.reconnectionAttempts); + self.reconnectionTimer = setTimeout(maybeReconnect, self.reconnectionDelay); + } + }; + + this.options['try multiple transports'] = false; + this.reconnectionTimer = setTimeout(maybeReconnect, this.reconnectionDelay); + + this.on('connect', maybeReconnect); + }; })( - 'undefined' != typeof io ? io.Transport : module.exports + 'undefined' != typeof io ? io : module.exports , 'undefined' != typeof io ? io : module.parent.exports , this ); - /** * socket.io * Copyright(c) 2011 LearnBoost @@ -3213,539 +3329,427 @@ var io = ('undefined' === typeof module ? {} : module.exports); * Expose constructor. */ - exports.flashsocket = Flashsocket; + exports.SocketNamespace = SocketNamespace; /** - * The FlashSocket transport. This is a API wrapper for the HTML5 WebSocket - * specification. It uses a .swf file to communicate with the server. If you want - * to serve the .swf file from a other server than where the Socket.IO script is - * coming from you need to use the insecure version of the .swf. More information - * about this can be found on the github page. + * Socket namespace constructor. * * @constructor - * @extends {io.Transport.websocket} * @api public */ - function Flashsocket () { - io.Transport.websocket.apply(this, arguments); + function SocketNamespace (socket, name) { + this.socket = socket; + this.name = name || ''; + this.flags = {}; + this.json = new Flag(this, 'json'); + this.ackPackets = 0; + this.acks = {}; }; /** - * Inherits from Transport. + * Apply EventEmitter mixin. */ - io.util.inherit(Flashsocket, io.Transport.websocket); + io.util.mixin(SocketNamespace, io.EventEmitter); /** - * Transport name + * Copies emit since we override it * - * @api public + * @api private */ - Flashsocket.prototype.name = 'flashsocket'; + SocketNamespace.prototype.$emit = io.EventEmitter.prototype.emit; /** - * Disconnect the established `FlashSocket` connection. This is done by adding a - * new task to the FlashSocket. The rest will be handled off by the `WebSocket` - * transport. + * Creates a new namespace, by proxying the request to the socket. This + * allows us to use the synax as we do on the server. * - * @returns {Transport} * @api public */ - Flashsocket.prototype.open = function () { - var self = this - , args = arguments; + SocketNamespace.prototype.of = function () { + return this.socket.of.apply(this.socket, arguments); + }; - WebSocket.__addTask(function () { - io.Transport.websocket.prototype.open.apply(self, args); - }); + /** + * Sends a packet. + * + * @api private + */ + + SocketNamespace.prototype.packet = function (packet) { + packet.endpoint = this.name; + this.socket.packet(packet); + this.flags = {}; return this; }; - + /** - * Sends a message to the Socket.IO server. This is done by adding a new - * task to the FlashSocket. The rest will be handled off by the `WebSocket` - * transport. + * Sends a message * - * @returns {Transport} * @api public */ - Flashsocket.prototype.send = function () { - var self = this, args = arguments; - WebSocket.__addTask(function () { - io.Transport.websocket.prototype.send.apply(self, args); - }); - return this; + SocketNamespace.prototype.send = function (data, fn) { + var packet = { + type: this.flags.json ? 'json' : 'message' + , data: data + }; + + if ('function' == typeof fn) { + packet.id = ++this.ackPackets; + packet.ack = true; + this.acks[packet.id] = fn; + } + + return this.packet(packet); }; /** - * Disconnects the established `FlashSocket` connection. + * Emits an event * - * @returns {Transport} * @api public */ + + SocketNamespace.prototype.emit = function (name) { + var args = Array.prototype.slice.call(arguments, 1) + , lastArg = args[args.length - 1] + , packet = { + type: 'event' + , name: name + }; + + if ('function' == typeof lastArg) { + packet.id = ++this.ackPackets; + packet.ack = 'data'; + this.acks[packet.id] = lastArg; + args = args.slice(0, args.length - 1); + } + + packet.args = args; + + return this.packet(packet); + }; + + /** + * Disconnects the namespace + * + * @api private + */ + + SocketNamespace.prototype.disconnect = function () { + if (this.name === '') { + this.socket.disconnect(); + } else { + this.packet({ type: 'disconnect' }); + this.$emit('disconnect'); + } - Flashsocket.prototype.close = function () { - WebSocket.__tasks.length = 0; - io.Transport.websocket.prototype.close.call(this); return this; }; /** - * The WebSocket fall back needs to append the flash container to the body - * element, so we need to make sure we have access to it. Or defer the call - * until we are sure there is a body element. + * Handles a packet * - * @param {Socket} socket The socket instance that needs a transport - * @param {Function} fn The callback * @api private */ - Flashsocket.prototype.ready = function (socket, fn) { - function init () { - var options = socket.options - , port = options['flash policy port'] - , path = [ - 'http' + (options.secure ? 's' : '') + ':/' - , options.host + ':' + options.port - , options.resource - , 'static/flashsocket' - , 'WebSocketMain' + (socket.isXDomain() ? 'Insecure' : '') + '.swf' - ]; + SocketNamespace.prototype.onPacket = function (packet) { + var self = this; - // Only start downloading the swf file when the checked that this browser - // actually supports it - if (!Flashsocket.loaded) { - if (typeof WEB_SOCKET_SWF_LOCATION === 'undefined') { - // Set the correct file based on the XDomain settings - WEB_SOCKET_SWF_LOCATION = path.join('/'); + function ack () { + self.packet({ + type: 'ack' + , args: io.util.toArray(arguments) + , ackId: packet.id + }); + }; + + switch (packet.type) { + case 'connect': + this.$emit('connect'); + break; + + case 'disconnect': + if (this.name === '') { + this.socket.onDisconnect(packet.reason || 'booted'); + } else { + this.$emit('disconnect', packet.reason); } + break; - if (port !== 843) { - WebSocket.loadFlashPolicyFile('xmlsocket://' + options.host + ':' + port); + case 'message': + case 'json': + var params = ['message', packet.data]; + + if (packet.ack == 'data') { + params.push(ack); + } else if (packet.ack) { + this.packet({ type: 'ack', ackId: packet.id }); } - WebSocket.__initialize(); - Flashsocket.loaded = true; - } + this.$emit.apply(this, params); + break; - fn.call(self); - } + case 'event': + var params = [packet.name].concat(packet.args); - var self = this; - if (document.body) return init(); + if (packet.ack == 'data') + params.push(ack); - io.util.load(init); + this.$emit.apply(this, params); + break; + + case 'ack': + if (this.acks[packet.ackId]) { + this.acks[packet.ackId].apply(this, packet.args); + delete this.acks[packet.ackId]; + } + break; + + case 'error': + if (packet.advice){ + this.socket.onError(packet); + } else { + if (packet.reason == 'unauthorized') { + this.$emit('connect_failed', packet.reason); + } else { + this.$emit('error', packet.reason); + } + } + break; + } }; /** - * Check if the FlashSocket transport is supported as it requires that the Adobe - * Flash Player plug-in version `10.0.0` or greater is installed. And also check if - * the polyfill is correctly loaded. + * Flag interface. * - * @returns {Boolean} - * @api public + * @api private */ - Flashsocket.check = function () { - if ( - typeof WebSocket == 'undefined' - || !('__initialize' in WebSocket) || !swfobject - ) return false; - - return swfobject.getFlashPlayerVersion().major >= 10; + function Flag (nsp, name) { + this.namespace = nsp; + this.name = name; }; /** - * Check if the FlashSocket transport can be used as cross domain / cross origin - * transport. Because we can't see which type (secure or insecure) of .swf is used - * we will just return true. + * Send a message * - * @returns {Boolean} * @api public */ - Flashsocket.xdomainCheck = function () { - return true; + Flag.prototype.send = function () { + this.namespace.flags[this.name] = true; + this.namespace.send.apply(this.namespace, arguments); }; /** - * Disable AUTO_INITIALIZATION - */ - - if (typeof window != 'undefined') { - WEB_SOCKET_DISABLE_AUTO_INITIALIZATION = true; - } - - /** - * Add the transport to your public io.transports array. + * Emit an event * - * @api private + * @api public */ - io.transports.push('flashsocket'); + Flag.prototype.emit = function () { + this.namespace.flags[this.name] = true; + this.namespace.emit.apply(this.namespace, arguments); + }; + })( - 'undefined' != typeof io ? io.Transport : module.exports + 'undefined' != typeof io ? io : module.exports , 'undefined' != typeof io ? io : module.parent.exports ); -/* SWFObject v2.2 - is released under the MIT License -*/ -if ('undefined' != typeof window) { -var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O[(['Active'].concat('Object').join('X'))]!=D){try{var ad=new window[(['Active'].concat('Object').join('X'))](W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y0){for(var af=0;af0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad'}}aa.outerHTML='"+af+"";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab -// License: New BSD License -// Reference: http://dev.w3.org/html5/websockets/ -// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol -(function() { - - if ('undefined' == typeof window || window.WebSocket) return; +/** + * socket.io + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ - var console = window.console; - if (!console || !console.log || !console.error) { - console = {log: function(){ }, error: function(){ }}; - } - - if (!swfobject.hasFlashPlayerVersion("10.0.0")) { - console.error("Flash Player >= 10.0.0 is required."); - return; - } - if (location.protocol == "file:") { - console.error( - "WARNING: web-socket-js doesn't work in file:///... URL " + - "unless you set Flash Security Settings properly. " + - "Open the page via Web server i.e. http://..."); - } +(function (exports, io, global) { /** - * This class represents a faux web socket. - * @param {string} url - * @param {array or string} protocols - * @param {string} proxyHost - * @param {int} proxyPort - * @param {string} headers + * Expose constructor. */ - WebSocket = function(url, protocols, proxyHost, proxyPort, headers) { - var self = this; - self.__id = WebSocket.__nextId++; - WebSocket.__instances[self.__id] = self; - self.readyState = WebSocket.CONNECTING; - self.bufferedAmount = 0; - self.__events = {}; - if (!protocols) { - protocols = []; - } else if (typeof protocols == "string") { - protocols = [protocols]; - } - // Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc. - // Otherwise, when onopen fires immediately, onopen is called before it is set. - setTimeout(function() { - WebSocket.__addTask(function() { - WebSocket.__flash.create( - self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null); - }); - }, 0); - }; + + exports.websocket = WS; /** - * Send data to the web socket. - * @param {string} data The data to send to the socket. - * @return {boolean} True for success, false for failure. + * The WebSocket transport uses the HTML5 WebSocket API to establish an + * persistent connection with the Socket.IO server. This transport will also + * be inherited by the FlashSocket fallback as it provides a API compatible + * polyfill for the WebSockets. + * + * @constructor + * @extends {io.Transport} + * @api public */ - WebSocket.prototype.send = function(data) { - if (this.readyState == WebSocket.CONNECTING) { - throw "INVALID_STATE_ERR: Web Socket connection has not been established"; - } - // We use encodeURIComponent() here, because FABridge doesn't work if - // the argument includes some characters. We don't use escape() here - // because of this: - // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions - // But it looks decodeURIComponent(encodeURIComponent(s)) doesn't - // preserve all Unicode characters either e.g. "\uffff" in Firefox. - // Note by wtritch: Hopefully this will not be necessary using ExternalInterface. Will require - // additional testing. - var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data)); - if (result < 0) { // success - return true; - } else { - this.bufferedAmount += result; - return false; - } + + function WS (socket) { + io.Transport.apply(this, arguments); }; /** - * Close this web socket gracefully. + * Inherits from Transport. */ - WebSocket.prototype.close = function() { - if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) { - return; - } - this.readyState = WebSocket.CLOSING; - WebSocket.__flash.close(this.__id); - }; + + io.util.inherit(WS, io.Transport); /** - * Implementation of {@link DOM 2 EventTarget Interface} + * Transport name * - * @param {string} type - * @param {function} listener - * @param {boolean} useCapture - * @return void + * @api public */ - WebSocket.prototype.addEventListener = function(type, listener, useCapture) { - if (!(type in this.__events)) { - this.__events[type] = []; - } - this.__events[type].push(listener); - }; + + WS.prototype.name = 'websocket'; /** - * Implementation of {@link DOM 2 EventTarget Interface} + * Initializes a new `WebSocket` connection with the Socket.IO server. We attach + * all the appropriate listeners to handle the responses from the server. * - * @param {string} type - * @param {function} listener - * @param {boolean} useCapture - * @return void + * @returns {Transport} + * @api public */ - WebSocket.prototype.removeEventListener = function(type, listener, useCapture) { - if (!(type in this.__events)) return; - var events = this.__events[type]; - for (var i = events.length - 1; i >= 0; --i) { - if (events[i] === listener) { - events.splice(i, 1); - break; - } + + WS.prototype.open = function () { + var query = io.util.query(this.socket.options.query) + , self = this + , Socket + + + if (!Socket) { + Socket = global.MozWebSocket || global.WebSocket; } + + this.websocket = new Socket(this.prepareUrl() + query); + + this.websocket.onopen = function () { + self.onOpen(); + self.socket.setBuffer(false); + }; + this.websocket.onmessage = function (ev) { + self.onData(ev.data); + }; + this.websocket.onclose = function () { + self.onClose(); + self.socket.setBuffer(true); + }; + this.websocket.onerror = function (e) { + self.onError(e); + }; + + return this; }; /** - * Implementation of {@link DOM 2 EventTarget Interface} + * Send a message to the Socket.IO server. The message will automatically be + * encoded in the correct message format. * - * @param {Event} event - * @return void + * @returns {Transport} + * @api public */ - WebSocket.prototype.dispatchEvent = function(event) { - var events = this.__events[event.type] || []; - for (var i = 0; i < events.length; ++i) { - events[i](event); - } - var handler = this["on" + event.type]; - if (handler) handler(event); - }; + + // Do to a bug in the current IDevices browser, we need to wrap the send in a + // setTimeout, when they resume from sleeping the browser will crash if + // we don't allow the browser time to detect the socket has been closed + if (io.util.ua.iDevice) { + WS.prototype.send = function (data) { + var self = this; + setTimeout(function() { + self.websocket.send(data); + },0); + return this; + }; + } else { + WS.prototype.send = function (data) { + this.websocket.send(data); + return this; + }; + } /** - * Handles an event from Flash. - * @param {Object} flashEvent + * Payload + * + * @api private */ - WebSocket.prototype.__handleEvent = function(flashEvent) { - if ("readyState" in flashEvent) { - this.readyState = flashEvent.readyState; - } - if ("protocol" in flashEvent) { - this.protocol = flashEvent.protocol; - } - - var jsEvent; - if (flashEvent.type == "open" || flashEvent.type == "error") { - jsEvent = this.__createSimpleEvent(flashEvent.type); - } else if (flashEvent.type == "close") { - // TODO implement jsEvent.wasClean - jsEvent = this.__createSimpleEvent("close"); - } else if (flashEvent.type == "message") { - var data = decodeURIComponent(flashEvent.message); - jsEvent = this.__createMessageEvent("message", data); - } else { - throw "unknown event type: " + flashEvent.type; - } - - this.dispatchEvent(jsEvent); - }; - - WebSocket.prototype.__createSimpleEvent = function(type) { - if (document.createEvent && window.Event) { - var event = document.createEvent("Event"); - event.initEvent(type, false, false); - return event; - } else { - return {type: type, bubbles: false, cancelable: false}; - } - }; - - WebSocket.prototype.__createMessageEvent = function(type, data) { - if (document.createEvent && window.MessageEvent && !window.opera) { - var event = document.createEvent("MessageEvent"); - event.initMessageEvent("message", false, false, data, null, null, window, null); - return event; - } else { - // IE and Opera, the latter one truncates the data parameter after any 0x00 bytes. - return {type: type, data: data, bubbles: false, cancelable: false}; + + WS.prototype.payload = function (arr) { + for (var i = 0, l = arr.length; i < l; i++) { + this.packet(arr[i]); } + return this; }; - + /** - * Define the WebSocket readyState enumeration. + * Disconnect the established `WebSocket` connection. + * + * @returns {Transport} + * @api public */ - WebSocket.CONNECTING = 0; - WebSocket.OPEN = 1; - WebSocket.CLOSING = 2; - WebSocket.CLOSED = 3; - WebSocket.__flash = null; - WebSocket.__instances = {}; - WebSocket.__tasks = []; - WebSocket.__nextId = 0; - + WS.prototype.close = function () { + this.websocket.close(); + return this; + }; + /** - * Load a new flash security policy file. - * @param {string} url + * Handle the errors that `WebSocket` might be giving when we + * are attempting to connect or send messages. + * + * @param {Error} e The error. + * @api private */ - WebSocket.loadFlashPolicyFile = function(url){ - WebSocket.__addTask(function() { - WebSocket.__flash.loadManualPolicyFile(url); - }); + + WS.prototype.onError = function (e) { + this.socket.onError(e); }; /** - * Loads WebSocketMain.swf and creates WebSocketMain object in Flash. + * Returns the appropriate scheme for the URI generation. + * + * @api private */ - WebSocket.__initialize = function() { - if (WebSocket.__flash) return; - - if (WebSocket.__swfLocation) { - // For backword compatibility. - window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation; - } - if (!window.WEB_SOCKET_SWF_LOCATION) { - console.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf"); - return; - } - var container = document.createElement("div"); - container.id = "webSocketContainer"; - // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents - // Flash from loading at least in IE. So we move it out of the screen at (-100, -100). - // But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash - // Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is - // the best we can do as far as we know now. - container.style.position = "absolute"; - if (WebSocket.__isFlashLite()) { - container.style.left = "0px"; - container.style.top = "0px"; - } else { - container.style.left = "-100px"; - container.style.top = "-100px"; - } - var holder = document.createElement("div"); - holder.id = "webSocketFlash"; - container.appendChild(holder); - document.body.appendChild(container); - // See this article for hasPriority: - // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html - swfobject.embedSWF( - WEB_SOCKET_SWF_LOCATION, - "webSocketFlash", - "1" /* width */, - "1" /* height */, - "10.0.0" /* SWF version */, - null, - null, - {hasPriority: true, swliveconnect : true, allowScriptAccess: "always"}, - null, - function(e) { - if (!e.success) { - console.error("[WebSocket] swfobject.embedSWF failed"); - } - }); + WS.prototype.scheme = function () { + return this.socket.options.secure ? 'wss' : 'ws'; }; - + /** - * Called by Flash to notify JS that it's fully loaded and ready - * for communication. + * Checks if the browser has support for native `WebSockets` and that + * it's not the polyfill created for the FlashSocket transport. + * + * @return {Boolean} + * @api public */ - WebSocket.__onFlashInitialized = function() { - // We need to set a timeout here to avoid round-trip calls - // to flash during the initialization process. - setTimeout(function() { - WebSocket.__flash = document.getElementById("webSocketFlash"); - WebSocket.__flash.setCallerUrl(location.href); - WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG); - for (var i = 0; i < WebSocket.__tasks.length; ++i) { - WebSocket.__tasks[i](); - } - WebSocket.__tasks = []; - }, 0); + + WS.check = function () { + return ('WebSocket' in global && !('__addTask' in WebSocket)) + || 'MozWebSocket' in global; }; - + /** - * Called by Flash to notify WebSockets events are fired. + * Check if the `WebSocket` transport support cross domain communications. + * + * @returns {Boolean} + * @api public */ - WebSocket.__onFlashEvent = function() { - setTimeout(function() { - try { - // Gets events using receiveEvents() instead of getting it from event object - // of Flash event. This is to make sure to keep message order. - // It seems sometimes Flash events don't arrive in the same order as they are sent. - var events = WebSocket.__flash.receiveEvents(); - for (var i = 0; i < events.length; ++i) { - WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]); - } - } catch (e) { - console.error(e); - } - }, 0); + + WS.xdomainCheck = function () { return true; }; - - // Called by Flash. - WebSocket.__log = function(message) { - console.log(decodeURIComponent(message)); - }; - - // Called by Flash. - WebSocket.__error = function(message) { - console.error(decodeURIComponent(message)); - }; - - WebSocket.__addTask = function(task) { - if (WebSocket.__flash) { - task(); - } else { - WebSocket.__tasks.push(task); - } - }; - + /** - * Test if the browser is running flash lite. - * @return {boolean} True if flash lite is running, false otherwise. + * Add the transport to your public io.transports array. + * + * @api private */ - WebSocket.__isFlashLite = function() { - if (!window.navigator || !window.navigator.mimeTypes) { - return false; - } - var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"]; - if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) { - return false; - } - return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false; - }; - - if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) { - if (window.addEventListener) { - window.addEventListener("load", function(){ - WebSocket.__initialize(); - }, false); - } else { - window.attachEvent("onload", function(){ - WebSocket.__initialize(); - }); - } - } - -})(); + + io.transports.push('websocket'); + +})( + 'undefined' != typeof io ? io.Transport : module.exports + , 'undefined' != typeof io ? io : module.parent.exports + , this +); /** * socket.io @@ -3753,619 +3757,796 @@ var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="Sho * MIT Licensed */ -(function (exports, io, global) { +(function (exports, io) { /** * Expose constructor. - * - * @api public */ - exports.XHR = XHR; + exports.flashsocket = Flashsocket; /** - * XHR constructor + * The FlashSocket transport. This is a API wrapper for the HTML5 WebSocket + * specification. It uses a .swf file to communicate with the server. If you want + * to serve the .swf file from a other server than where the Socket.IO script is + * coming from you need to use the insecure version of the .swf. More information + * about this can be found on the github page. * - * @costructor + * @constructor + * @extends {io.Transport.websocket} * @api public */ - function XHR (socket) { - if (!socket) return; - - io.Transport.apply(this, arguments); - this.sendBuffer = []; + function Flashsocket () { + io.Transport.websocket.apply(this, arguments); }; /** * Inherits from Transport. */ - io.util.inherit(XHR, io.Transport); + io.util.inherit(Flashsocket, io.Transport.websocket); /** - * Establish a connection + * Transport name * - * @returns {Transport} * @api public */ - XHR.prototype.open = function () { - this.socket.setBuffer(false); - this.onOpen(); - this.get(); + Flashsocket.prototype.name = 'flashsocket'; - // we need to make sure the request succeeds since we have no indication - // whether the request opened or not until it succeeded. - this.setCloseTimeout(); + /** + * Disconnect the established `FlashSocket` connection. This is done by adding a + * new task to the FlashSocket. The rest will be handled off by the `WebSocket` + * transport. + * + * @returns {Transport} + * @api public + */ + + Flashsocket.prototype.open = function () { + var self = this + , args = arguments; + WebSocket.__addTask(function () { + io.Transport.websocket.prototype.open.apply(self, args); + }); return this; }; - + /** - * Check if we need to send data to the Socket.IO server, if we have data in our - * buffer we encode it and forward it to the `post` method. + * Sends a message to the Socket.IO server. This is done by adding a new + * task to the FlashSocket. The rest will be handled off by the `WebSocket` + * transport. * - * @api private + * @returns {Transport} + * @api public */ - XHR.prototype.payload = function (payload) { - var msgs = []; - - for (var i = 0, l = payload.length; i < l; i++) { - msgs.push(io.parser.encodePacket(payload[i])); - } - - this.send(io.parser.encodePayload(msgs)); + Flashsocket.prototype.send = function () { + var self = this, args = arguments; + WebSocket.__addTask(function () { + io.Transport.websocket.prototype.send.apply(self, args); + }); + return this; }; /** - * Send data to the Socket.IO server. + * Disconnects the established `FlashSocket` connection. * - * @param data The message * @returns {Transport} * @api public */ - XHR.prototype.send = function (data) { - this.post(data); + Flashsocket.prototype.close = function () { + WebSocket.__tasks.length = 0; + io.Transport.websocket.prototype.close.call(this); return this; }; /** - * Posts a encoded message to the Socket.IO server. + * The WebSocket fall back needs to append the flash container to the body + * element, so we need to make sure we have access to it. Or defer the call + * until we are sure there is a body element. * - * @param {String} data A encoded message. + * @param {Socket} socket The socket instance that needs a transport + * @param {Function} fn The callback * @api private */ - function empty () { }; + Flashsocket.prototype.ready = function (socket, fn) { + function init () { + var options = socket.options + , port = options['flash policy port'] + , path = [ + 'http' + (options.secure ? 's' : '') + ':/' + , options.host + ':' + options.port + , options.resource + , 'static/flashsocket' + , 'WebSocketMain' + (socket.isXDomain() ? 'Insecure' : '') + '.swf' + ]; - XHR.prototype.post = function (data) { - var self = this; - this.socket.setBuffer(true); - - function stateChange () { - if (this.readyState == 4) { - this.onreadystatechange = empty; - self.posting = false; - - if (this.status == 200){ - self.socket.setBuffer(false); - } else { - self.onClose(); + // Only start downloading the swf file when the checked that this browser + // actually supports it + if (!Flashsocket.loaded) { + if (typeof WEB_SOCKET_SWF_LOCATION === 'undefined') { + // Set the correct file based on the XDomain settings + WEB_SOCKET_SWF_LOCATION = path.join('/'); } - } - } - function onload () { - this.onload = empty; - self.socket.setBuffer(false); - }; + if (port !== 843) { + WebSocket.loadFlashPolicyFile('xmlsocket://' + options.host + ':' + port); + } - this.sendXHR = this.request('POST'); + WebSocket.__initialize(); + Flashsocket.loaded = true; + } - if (global.XDomainRequest && this.sendXHR instanceof XDomainRequest) { - this.sendXHR.onload = this.sendXHR.onerror = onload; - } else { - this.sendXHR.onreadystatechange = stateChange; + fn.call(self); } - this.sendXHR.send(data); - }; - - /** - * Disconnects the established `XHR` connection. - * - * @returns {Transport} - * @api public - */ + var self = this; + if (document.body) return init(); - XHR.prototype.close = function () { - this.onClose(); - return this; + io.util.load(init); }; /** - * Generates a configured XHR request + * Check if the FlashSocket transport is supported as it requires that the Adobe + * Flash Player plug-in version `10.0.0` or greater is installed. And also check if + * the polyfill is correctly loaded. * - * @param {String} url The url that needs to be requested. - * @param {String} method The method the request should use. - * @returns {XMLHttpRequest} - * @api private + * @returns {Boolean} + * @api public */ - XHR.prototype.request = function (method) { - var req = io.util.request(this.socket.isXDomain()) - , query = io.util.query(this.socket.options.query, 't=' + +new Date); - - req.open(method || 'GET', this.prepareUrl() + query, true); - - if (method == 'POST') { - try { - if (req.setRequestHeader) { - req.setRequestHeader('Content-type', 'text/plain;charset=UTF-8'); - } else { - // XDomainRequest - req.contentType = 'text/plain'; - } - } catch (e) {} - } + Flashsocket.check = function () { + if ( + typeof WebSocket == 'undefined' + || !('__initialize' in WebSocket) || !swfobject + ) return false; - return req; + return swfobject.getFlashPlayerVersion().major >= 10; }; /** - * Returns the scheme to use for the transport URLs. + * Check if the FlashSocket transport can be used as cross domain / cross origin + * transport. Because we can't see which type (secure or insecure) of .swf is used + * we will just return true. * - * @api private + * @returns {Boolean} + * @api public */ - XHR.prototype.scheme = function () { - return this.socket.options.secure ? 'https' : 'http'; + Flashsocket.xdomainCheck = function () { + return true; }; /** - * Check if the XHR transports are supported - * - * @param {Boolean} xdomain Check if we support cross domain requests. - * @returns {Boolean} - * @api public + * Disable AUTO_INITIALIZATION */ - XHR.check = function (socket, xdomain) { - try { - var request = io.util.request(xdomain), - usesXDomReq = (global.XDomainRequest && request instanceof XDomainRequest), - socketProtocol = (socket && socket.options && socket.options.secure ? 'https:' : 'http:'), - isXProtocol = (global.location && socketProtocol != global.location.protocol); - if (request && !(usesXDomReq && isXProtocol)) { - return true; - } - } catch(e) {} - - return false; - }; + if (typeof window != 'undefined') { + WEB_SOCKET_DISABLE_AUTO_INITIALIZATION = true; + } /** - * Check if the XHR transport supports cross domain requests. + * Add the transport to your public io.transports array. * - * @returns {Boolean} - * @api public + * @api private */ - XHR.xdomainCheck = function (socket) { - return XHR.check(socket, true); - }; - + io.transports.push('flashsocket'); })( 'undefined' != typeof io ? io.Transport : module.exports , 'undefined' != typeof io ? io : module.parent.exports - , this ); -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ +/* SWFObject v2.2 + is released under the MIT License +*/ +if ('undefined' != typeof window) { +var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O[(['Active'].concat('Object').join('X'))]!=D){try{var ad=new window[(['Active'].concat('Object').join('X'))](W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y0){for(var af=0;af0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad'}}aa.outerHTML='"+af+"";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab +// License: New BSD License +// Reference: http://dev.w3.org/html5/websockets/ +// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol -(function (exports, io) { +(function() { + + if ('undefined' == typeof window || window.WebSocket) return; + + var console = window.console; + if (!console || !console.log || !console.error) { + console = {log: function(){ }, error: function(){ }}; + } + + if (!swfobject.hasFlashPlayerVersion("10.0.0")) { + console.error("Flash Player >= 10.0.0 is required."); + return; + } + if (location.protocol == "file:") { + console.error( + "WARNING: web-socket-js doesn't work in file:///... URL " + + "unless you set Flash Security Settings properly. " + + "Open the page via Web server i.e. http://..."); + } /** - * Expose constructor. + * This class represents a faux web socket. + * @param {string} url + * @param {array or string} protocols + * @param {string} proxyHost + * @param {int} proxyPort + * @param {string} headers */ - - exports.htmlfile = HTMLFile; + WebSocket = function(url, protocols, proxyHost, proxyPort, headers) { + var self = this; + self.__id = WebSocket.__nextId++; + WebSocket.__instances[self.__id] = self; + self.readyState = WebSocket.CONNECTING; + self.bufferedAmount = 0; + self.__events = {}; + if (!protocols) { + protocols = []; + } else if (typeof protocols == "string") { + protocols = [protocols]; + } + // Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc. + // Otherwise, when onopen fires immediately, onopen is called before it is set. + setTimeout(function() { + WebSocket.__addTask(function() { + WebSocket.__flash.create( + self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null); + }); + }, 0); + }; /** - * The HTMLFile transport creates a `forever iframe` based transport - * for Internet Explorer. Regular forever iframe implementations will - * continuously trigger the browsers buzy indicators. If the forever iframe - * is created inside a `htmlfile` these indicators will not be trigged. - * - * @constructor - * @extends {io.Transport.XHR} - * @api public + * Send data to the web socket. + * @param {string} data The data to send to the socket. + * @return {boolean} True for success, false for failure. */ - - function HTMLFile (socket) { - io.Transport.XHR.apply(this, arguments); + WebSocket.prototype.send = function(data) { + if (this.readyState == WebSocket.CONNECTING) { + throw "INVALID_STATE_ERR: Web Socket connection has not been established"; + } + // We use encodeURIComponent() here, because FABridge doesn't work if + // the argument includes some characters. We don't use escape() here + // because of this: + // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions + // But it looks decodeURIComponent(encodeURIComponent(s)) doesn't + // preserve all Unicode characters either e.g. "\uffff" in Firefox. + // Note by wtritch: Hopefully this will not be necessary using ExternalInterface. Will require + // additional testing. + var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data)); + if (result < 0) { // success + return true; + } else { + this.bufferedAmount += result; + return false; + } }; /** - * Inherits from XHR transport. + * Close this web socket gracefully. */ - - io.util.inherit(HTMLFile, io.Transport.XHR); + WebSocket.prototype.close = function() { + if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) { + return; + } + this.readyState = WebSocket.CLOSING; + WebSocket.__flash.close(this.__id); + }; /** - * Transport name + * Implementation of {@link DOM 2 EventTarget Interface} * - * @api public + * @param {string} type + * @param {function} listener + * @param {boolean} useCapture + * @return void */ - - HTMLFile.prototype.name = 'htmlfile'; + WebSocket.prototype.addEventListener = function(type, listener, useCapture) { + if (!(type in this.__events)) { + this.__events[type] = []; + } + this.__events[type].push(listener); + }; /** - * Creates a new Ac...eX `htmlfile` with a forever loading iframe - * that can be used to listen to messages. Inside the generated - * `htmlfile` a reference will be made to the HTMLFile transport. + * Implementation of {@link DOM 2 EventTarget Interface} * - * @api private + * @param {string} type + * @param {function} listener + * @param {boolean} useCapture + * @return void */ - - HTMLFile.prototype.get = function () { - this.doc = new window[(['Active'].concat('Object').join('X'))]('htmlfile'); - this.doc.open(); - this.doc.write(''); - this.doc.close(); - this.doc.parentWindow.s = this; - - var iframeC = this.doc.createElement('div'); - iframeC.className = 'socketio'; - - this.doc.body.appendChild(iframeC); - this.iframe = this.doc.createElement('iframe'); - - iframeC.appendChild(this.iframe); - - var self = this - , query = io.util.query(this.socket.options.query, 't='+ +new Date); - - this.iframe.src = this.prepareUrl() + query; - - io.util.on(window, 'unload', function () { - self.destroy(); - }); + WebSocket.prototype.removeEventListener = function(type, listener, useCapture) { + if (!(type in this.__events)) return; + var events = this.__events[type]; + for (var i = events.length - 1; i >= 0; --i) { + if (events[i] === listener) { + events.splice(i, 1); + break; + } + } }; /** - * The Socket.IO server will write script tags inside the forever - * iframe, this function will be used as callback for the incoming - * information. + * Implementation of {@link DOM 2 EventTarget Interface} * - * @param {String} data The message - * @param {document} doc Reference to the context - * @api private + * @param {Event} event + * @return void */ - - HTMLFile.prototype._ = function (data, doc) { - // unescape all forward slashes. see GH-1251 - data = data.replace(/\\\//g, '/'); - this.onData(data); - try { - var script = doc.getElementsByTagName('script')[0]; - script.parentNode.removeChild(script); - } catch (e) { } + WebSocket.prototype.dispatchEvent = function(event) { + var events = this.__events[event.type] || []; + for (var i = 0; i < events.length; ++i) { + events[i](event); + } + var handler = this["on" + event.type]; + if (handler) handler(event); }; /** - * Destroy the established connection, iframe and `htmlfile`. - * And calls the `CollectGarbage` function of Internet Explorer - * to release the memory. - * - * @api private + * Handles an event from Flash. + * @param {Object} flashEvent */ - - HTMLFile.prototype.destroy = function () { - if (this.iframe){ - try { - this.iframe.src = 'about:blank'; - } catch(e){} - - this.doc = null; - this.iframe.parentNode.removeChild(this.iframe); - this.iframe = null; - - CollectGarbage(); + WebSocket.prototype.__handleEvent = function(flashEvent) { + if ("readyState" in flashEvent) { + this.readyState = flashEvent.readyState; + } + if ("protocol" in flashEvent) { + this.protocol = flashEvent.protocol; + } + + var jsEvent; + if (flashEvent.type == "open" || flashEvent.type == "error") { + jsEvent = this.__createSimpleEvent(flashEvent.type); + } else if (flashEvent.type == "close") { + // TODO implement jsEvent.wasClean + jsEvent = this.__createSimpleEvent("close"); + } else if (flashEvent.type == "message") { + var data = decodeURIComponent(flashEvent.message); + jsEvent = this.__createMessageEvent("message", data); + } else { + throw "unknown event type: " + flashEvent.type; } + + this.dispatchEvent(jsEvent); }; - + + WebSocket.prototype.__createSimpleEvent = function(type) { + if (document.createEvent && window.Event) { + var event = document.createEvent("Event"); + event.initEvent(type, false, false); + return event; + } else { + return {type: type, bubbles: false, cancelable: false}; + } + }; + + WebSocket.prototype.__createMessageEvent = function(type, data) { + if (document.createEvent && window.MessageEvent && !window.opera) { + var event = document.createEvent("MessageEvent"); + event.initMessageEvent("message", false, false, data, null, null, window, null); + return event; + } else { + // IE and Opera, the latter one truncates the data parameter after any 0x00 bytes. + return {type: type, data: data, bubbles: false, cancelable: false}; + } + }; + /** - * Disconnects the established connection. - * - * @returns {Transport} Chaining. - * @api public + * Define the WebSocket readyState enumeration. */ + WebSocket.CONNECTING = 0; + WebSocket.OPEN = 1; + WebSocket.CLOSING = 2; + WebSocket.CLOSED = 3; - HTMLFile.prototype.close = function () { - this.destroy(); - return io.Transport.XHR.prototype.close.call(this); + WebSocket.__flash = null; + WebSocket.__instances = {}; + WebSocket.__tasks = []; + WebSocket.__nextId = 0; + + /** + * Load a new flash security policy file. + * @param {string} url + */ + WebSocket.loadFlashPolicyFile = function(url){ + WebSocket.__addTask(function() { + WebSocket.__flash.loadManualPolicyFile(url); + }); }; /** - * Checks if the browser supports this transport. The browser - * must have an `Ac...eXObject` implementation. - * - * @return {Boolean} - * @api public + * Loads WebSocketMain.swf and creates WebSocketMain object in Flash. */ - - HTMLFile.check = function (socket) { - if (typeof window != "undefined" && (['Active'].concat('Object').join('X')) in window){ - try { - var a = new window[(['Active'].concat('Object').join('X'))]('htmlfile'); - return a && io.Transport.XHR.check(socket); - } catch(e){} + WebSocket.__initialize = function() { + if (WebSocket.__flash) return; + + if (WebSocket.__swfLocation) { + // For backword compatibility. + window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation; } - return false; + if (!window.WEB_SOCKET_SWF_LOCATION) { + console.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf"); + return; + } + var container = document.createElement("div"); + container.id = "webSocketContainer"; + // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents + // Flash from loading at least in IE. So we move it out of the screen at (-100, -100). + // But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash + // Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is + // the best we can do as far as we know now. + container.style.position = "absolute"; + if (WebSocket.__isFlashLite()) { + container.style.left = "0px"; + container.style.top = "0px"; + } else { + container.style.left = "-100px"; + container.style.top = "-100px"; + } + var holder = document.createElement("div"); + holder.id = "webSocketFlash"; + container.appendChild(holder); + document.body.appendChild(container); + // See this article for hasPriority: + // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html + swfobject.embedSWF( + WEB_SOCKET_SWF_LOCATION, + "webSocketFlash", + "1" /* width */, + "1" /* height */, + "10.0.0" /* SWF version */, + null, + null, + {hasPriority: true, swliveconnect : true, allowScriptAccess: "always"}, + null, + function(e) { + if (!e.success) { + console.error("[WebSocket] swfobject.embedSWF failed"); + } + }); }; - + /** - * Check if cross domain requests are supported. - * - * @returns {Boolean} - * @api public + * Called by Flash to notify JS that it's fully loaded and ready + * for communication. */ - - HTMLFile.xdomainCheck = function () { - // we can probably do handling for sub-domains, we should - // test that it's cross domain but a subdomain here - return false; + WebSocket.__onFlashInitialized = function() { + // We need to set a timeout here to avoid round-trip calls + // to flash during the initialization process. + setTimeout(function() { + WebSocket.__flash = document.getElementById("webSocketFlash"); + WebSocket.__flash.setCallerUrl(location.href); + WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG); + for (var i = 0; i < WebSocket.__tasks.length; ++i) { + WebSocket.__tasks[i](); + } + WebSocket.__tasks = []; + }, 0); }; - + /** - * Add the transport to your public io.transports array. - * - * @api private + * Called by Flash to notify WebSockets events are fired. */ - - io.transports.push('htmlfile'); - -})( - 'undefined' != typeof io ? io.Transport : module.exports - , 'undefined' != typeof io ? io : module.parent.exports -); - -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, io, global) { - + WebSocket.__onFlashEvent = function() { + setTimeout(function() { + try { + // Gets events using receiveEvents() instead of getting it from event object + // of Flash event. This is to make sure to keep message order. + // It seems sometimes Flash events don't arrive in the same order as they are sent. + var events = WebSocket.__flash.receiveEvents(); + for (var i = 0; i < events.length; ++i) { + WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]); + } + } catch (e) { + console.error(e); + } + }, 0); + return true; + }; + + // Called by Flash. + WebSocket.__log = function(message) { + console.log(decodeURIComponent(message)); + }; + + // Called by Flash. + WebSocket.__error = function(message) { + console.error(decodeURIComponent(message)); + }; + + WebSocket.__addTask = function(task) { + if (WebSocket.__flash) { + task(); + } else { + WebSocket.__tasks.push(task); + } + }; + /** - * Expose constructor. + * Test if the browser is running flash lite. + * @return {boolean} True if flash lite is running, false otherwise. */ + WebSocket.__isFlashLite = function() { + if (!window.navigator || !window.navigator.mimeTypes) { + return false; + } + var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"]; + if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) { + return false; + } + return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false; + }; + + if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) { + if (window.addEventListener) { + window.addEventListener("load", function(){ + WebSocket.__initialize(); + }, false); + } else { + window.attachEvent("onload", function(){ + WebSocket.__initialize(); + }); + } + } + +})(); - exports['xhr-polling'] = XHRPolling; +/** + * socket.io + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +(function (exports, io, global) { /** - * The XHR-polling transport uses long polling XHR requests to create a - * "persistent" connection with the server. + * Expose constructor. * - * @constructor * @api public */ - function XHRPolling () { - io.Transport.XHR.apply(this, arguments); - }; + exports.XHR = XHR; /** - * Inherits from XHR transport. + * XHR constructor + * + * @costructor + * @api public */ - io.util.inherit(XHRPolling, io.Transport.XHR); + function XHR (socket) { + if (!socket) return; + + io.Transport.apply(this, arguments); + this.sendBuffer = []; + }; /** - * Merge the properties from XHR transport + * Inherits from Transport. */ - io.util.merge(XHRPolling, io.Transport.XHR); + io.util.inherit(XHR, io.Transport); /** - * Transport name + * Establish a connection * + * @returns {Transport} * @api public */ - XHRPolling.prototype.name = 'xhr-polling'; + XHR.prototype.open = function () { + this.socket.setBuffer(false); + this.onOpen(); + this.get(); + + // we need to make sure the request succeeds since we have no indication + // whether the request opened or not until it succeeded. + this.setCloseTimeout(); + + return this; + }; /** - * Indicates whether heartbeats is enabled for this transport + * Check if we need to send data to the Socket.IO server, if we have data in our + * buffer we encode it and forward it to the `post` method. * * @api private */ - XHRPolling.prototype.heartbeats = function () { - return false; + XHR.prototype.payload = function (payload) { + var msgs = []; + + for (var i = 0, l = payload.length; i < l; i++) { + msgs.push(io.parser.encodePacket(payload[i])); + } + + this.send(io.parser.encodePayload(msgs)); }; - /** - * Establish a connection, for iPhone and Android this will be done once the page - * is loaded. + /** + * Send data to the Socket.IO server. * - * @returns {Transport} Chaining. + * @param data The message + * @returns {Transport} * @api public */ - XHRPolling.prototype.open = function () { - var self = this; - - io.Transport.XHR.prototype.open.call(self); - return false; + XHR.prototype.send = function (data) { + this.post(data); + return this; }; /** - * Starts a XHR request to wait for incoming messages. + * Posts a encoded message to the Socket.IO server. * + * @param {String} data A encoded message. * @api private */ - function empty () {}; - - XHRPolling.prototype.get = function () { - if (!this.isOpen) return; + function empty () { }; + XHR.prototype.post = function (data) { var self = this; + this.socket.setBuffer(true); function stateChange () { if (this.readyState == 4) { this.onreadystatechange = empty; + self.posting = false; - if (this.status == 200) { - self.onData(this.responseText); - self.get(); + if (this.status == 200){ + self.socket.setBuffer(false); } else { self.onClose(); } } - }; + } function onload () { this.onload = empty; - this.onerror = empty; - self.retryCounter = 1; - self.onData(this.responseText); - self.get(); - }; - - function onerror () { - self.retryCounter ++; - if(!self.retryCounter || self.retryCounter > 3) { - self.onClose(); - } else { - self.get(); - } + self.socket.setBuffer(false); }; - this.xhr = this.request(); + this.sendXHR = this.request('POST'); - if (global.XDomainRequest && this.xhr instanceof XDomainRequest) { - this.xhr.onload = onload; - this.xhr.onerror = onerror; + if (global.XDomainRequest && this.sendXHR instanceof XDomainRequest) { + this.sendXHR.onload = this.sendXHR.onerror = onload; } else { - this.xhr.onreadystatechange = stateChange; + this.sendXHR.onreadystatechange = stateChange; } - this.xhr.send(null); + this.sendXHR.send(data); }; /** - * Handle the unclean close behavior. + * Disconnects the established `XHR` connection. * - * @api private + * @returns {Transport} + * @api public */ - XHRPolling.prototype.onClose = function () { - io.Transport.XHR.prototype.onClose.call(this); - - if (this.xhr) { - this.xhr.onreadystatechange = this.xhr.onload = this.xhr.onerror = empty; - try { - this.xhr.abort(); - } catch(e){} - this.xhr = null; - } + XHR.prototype.close = function () { + this.onClose(); + return this; }; /** - * Webkit based browsers show a infinit spinner when you start a XHR request - * before the browsers onload event is called so we need to defer opening of - * the transport until the onload event is called. Wrapping the cb in our - * defer method solve this. + * Generates a configured XHR request * - * @param {Socket} socket The socket instance that needs a transport - * @param {Function} fn The callback + * @param {String} url The url that needs to be requested. + * @param {String} method The method the request should use. + * @returns {XMLHttpRequest} * @api private */ - XHRPolling.prototype.ready = function (socket, fn) { - var self = this; + XHR.prototype.request = function (method) { + var req = io.util.request(this.socket.isXDomain()) + , query = io.util.query(this.socket.options.query, 't=' + +new Date); - io.util.defer(function () { - fn.call(self); - }); + req.open(method || 'GET', this.prepareUrl() + query, true); + + if (method == 'POST') { + try { + if (req.setRequestHeader) { + req.setRequestHeader('Content-type', 'text/plain;charset=UTF-8'); + } else { + // XDomainRequest + req.contentType = 'text/plain'; + } + } catch (e) {} + } + + return req; }; /** - * Add the transport to your public io.transports array. + * Returns the scheme to use for the transport URLs. * * @api private */ - io.transports.push('xhr-polling'); - -})( - 'undefined' != typeof io ? io.Transport : module.exports - , 'undefined' != typeof io ? io : module.parent.exports - , this -); - -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ + XHR.prototype.scheme = function () { + return this.socket.options.secure ? 'https' : 'http'; + }; -(function (exports, io, global) { /** - * There is a way to hide the loading indicator in Firefox. If you create and - * remove a iframe it will stop showing the current loading indicator. - * Unfortunately we can't feature detect that and UA sniffing is evil. + * Check if the XHR transports are supported * - * @api private + * @param {Boolean} xdomain Check if we support cross domain requests. + * @returns {Boolean} + * @api public */ - var indicator = global.document && "MozAppearance" in - global.document.documentElement.style; + XHR.check = function (socket, xdomain) { + try { + var request = io.util.request(xdomain), + usesXDomReq = (global.XDomainRequest && request instanceof XDomainRequest), + socketProtocol = (socket && socket.options && socket.options.secure ? 'https:' : 'http:'), + isXProtocol = (global.location && socketProtocol != global.location.protocol); + if (request && !(usesXDomReq && isXProtocol)) { + return true; + } + } catch(e) {} + + return false; + }; + + /** + * Check if the XHR transport supports cross domain requests. + * + * @returns {Boolean} + * @api public + */ + + XHR.xdomainCheck = function (socket) { + return XHR.check(socket, true); + }; + +})( + 'undefined' != typeof io ? io.Transport : module.exports + , 'undefined' != typeof io ? io : module.parent.exports + , this +); +/** + * socket.io + * Copyright(c) 2011 LearnBoost + * MIT Licensed + */ + +(function (exports, io) { /** * Expose constructor. */ - exports['jsonp-polling'] = JSONPPolling; + exports.htmlfile = HTMLFile; /** - * The JSONP transport creates an persistent connection by dynamically - * inserting a script tag in the page. This script tag will receive the - * information of the Socket.IO server. When new information is received - * it creates a new script tag for the new data stream. + * The HTMLFile transport creates a `forever iframe` based transport + * for Internet Explorer. Regular forever iframe implementations will + * continuously trigger the browsers buzy indicators. If the forever iframe + * is created inside a `htmlfile` these indicators will not be trigged. * * @constructor - * @extends {io.Transport.xhr-polling} + * @extends {io.Transport.XHR} * @api public */ - function JSONPPolling (socket) { - io.Transport['xhr-polling'].apply(this, arguments); - - this.index = io.j.length; - - var self = this; - - io.j.push(function (msg) { - self._(msg); - }); + function HTMLFile (socket) { + io.Transport.XHR.apply(this, arguments); }; /** - * Inherits from XHR polling transport. + * Inherits from XHR transport. */ - io.util.inherit(JSONPPolling, io.Transport['xhr-polling']); + io.util.inherit(HTMLFile, io.Transport.XHR); /** * Transport name @@ -4373,187 +4554,124 @@ var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="Sho * @api public */ - JSONPPolling.prototype.name = 'jsonp-polling'; + HTMLFile.prototype.name = 'htmlfile'; /** - * Posts a encoded message to the Socket.IO server using an iframe. - * The iframe is used because script tags can create POST based requests. - * The iframe is positioned outside of the view so the user does not - * notice it's existence. + * Creates a new Ac...eX `htmlfile` with a forever loading iframe + * that can be used to listen to messages. Inside the generated + * `htmlfile` a reference will be made to the HTMLFile transport. * - * @param {String} data A encoded message. * @api private */ - JSONPPolling.prototype.post = function (data) { - var self = this - , query = io.util.query( - this.socket.options.query - , 't='+ (+new Date) + '&i=' + this.index - ); - - if (!this.form) { - var form = document.createElement('form') - , area = document.createElement('textarea') - , id = this.iframeId = 'socketio_iframe_' + this.index - , iframe; - - form.className = 'socketio'; - form.style.position = 'absolute'; - form.style.top = '0px'; - form.style.left = '0px'; - form.style.display = 'none'; - form.target = id; - form.method = 'POST'; - form.setAttribute('accept-charset', 'utf-8'); - area.name = 'd'; - form.appendChild(area); - document.body.appendChild(form); - - this.form = form; - this.area = area; - } - - this.form.action = this.prepareUrl() + query; - - function complete () { - initIframe(); - self.socket.setBuffer(false); - }; - - function initIframe () { - if (self.iframe) { - self.form.removeChild(self.iframe); - } - - try { - // ie6 dynamic iframes with target="" support (thanks Chris Lambacher) - iframe = document.createElement('