odin
Version:
Node.js Canvas/WebGL Javascript Game Framework
1,782 lines (1,305 loc) • 525 kB
JavaScript
define(
'core/game/config', [], function() {
function Config() {
this.debug = false;
this.forceCanvas = false;
this.renderer = "";
this.host = "127.0.0.1";
this.port = 3000;
this.MAX_SERVER_STATES = 10;
this.SCENE_SYNC_RATE = 0.5;
this.MIN_DELTA = 0.000001;
this.MAX_DELTA = 1;
}
Config.prototype.fromJSON = function(json) {
for (var key in json)
if (this[key] != undefined) this[key] = json[key];
return this;
};
return new Config;
}
);
define('core/game/log', [
"core/game/config"
],
function(Config) {
var has = Object.prototype.hasOwnProperty;
function Log() {}
Log.prototype.debug = Log.prototype.info = Log.prototype.log = function() {
if (!Config.debug) return;
console.log.apply(console, arguments);
};
Log.prototype.warn = function() {
if (!Config.debug || !Config.warn) return;
console.warn.apply(console, arguments);
};
Log.prototype.error = function() {
if (!Config.debug || !Config.error) return;
console.error.apply(console, arguments);
};
Log.prototype.object = function(obj) {
if (!Config.debug) return "";
var str = "",
key, value, type,
values = arguments[1] || (arguments[1] = []);
for (key in obj) {
if (!has.call(obj, key)) continue;
value = obj[key];
if (~values.indexOf(value)) continue;
type = typeof(value);
if (type === "object") {
values.push(value);
str += "\t" + key + " = " + this.object(value, values);
} else if (type !== "function") {
str += "\t" + key + " = " + value + "\n";
} else {
values.push(value);
}
}
return str;
};
return new Log;
}
);
define('base/audio_ctx', [
"core/game/log"
],
function(Log) {
var w = typeof(window) !== "undefined" ? window : global,
ctx = (
w.AudioContext ||
w.webkitAudioContext ||
w.mozAudioContext ||
w.oAudioContext ||
w.msAudioContext
);
if (!ctx) Log.error("AudioContext not supported by this Browser");
return ctx ? new ctx : false;
}
);
define(
'base/event_emitter', [], function() {
var shift = Array.prototype.shift,
SPLITER = /[ ,]+/;
function EventEmitter() {
this._events = {};
}
EventEmitter.prototype.on = function(type, listener, ctx) {
var types = type.split(SPLITER),
events = this._events,
i;
for (i = types.length; i--;) {
type = types[i];
(events[type] || (events[type] = [])).push({
listener: listener,
ctx: ctx || this
});
}
return this;
};
EventEmitter.prototype.once = function(type, listener, ctx) {
var self = this;
ctx = ctx || this;
function once() {
self.off(type, once);
listener.apply(ctx, arguments);
}
return this.on(type, once, ctx);
};
EventEmitter.prototype.listenTo = function(obj, type, listener, ctx) {
if (!obj.on || !obj.addEventListenerTo) throw "Can't listen to Object, it's not a instance of EventEmitter";
obj.on(type, listener, ctx || this);
return this;
};
EventEmitter.prototype.off = function(type, listener, ctx) {
var types = type.split(SPLITER),
thisEvents = this._events,
events, event,
i, j;
for (i = types.length; i--;) {
type = types[i];
if (!type) {
for (i in thisEvents) thisEvents[i].length = 0;
return this;
}
events = thisEvents[type];
if (!events) return this;
if (!listener) {
events.length = 0;
} else {
ctx = ctx || this;
for (j = events.length; j--;) {
event = events[j];
if (event.listener === listener && event.ctx === ctx) {
events.splice(j, 1);
break;
}
}
}
}
return this;
};
EventEmitter.prototype.emit = function(type) {
var events = this._events[type],
a1, a2, a3, a4,
event,
i;
if (!events || !events.length) return this;
switch (arguments.length) {
case 1:
for (i = events.length; i--;)(event = events[i]).listener.call(event.ctx);
break;
case 2:
a1 = arguments[1];
for (i = events.length; i--;)(event = events[i]).listener.call(event.ctx, a1);
break;
case 3:
a1 = arguments[1];
a2 = arguments[2];
for (i = events.length; i--;)(event = events[i]).listener.call(event.ctx, a1, a2);
break;
case 4:
a1 = arguments[1];
a2 = arguments[2];
a3 = arguments[3];
for (i = events.length; i--;)(event = events[i]).listener.call(event.ctx, a1, a2, a3);
break;
case 5:
a1 = arguments[1];
a2 = arguments[2];
a3 = arguments[3];
a4 = arguments[4];
for (i = events.length; i--;)(event = events[i]).listener.call(event.ctx, a1, a2, a3, a4);
break;
default:
shift.apply(arguments);
for (i = events.length; i--;)(event = events[i]).listener.apply(event.ctx, arguments);
}
return this;
};
EventEmitter.extend = function(child, parent) {
if (!parent) parent = this;
child.prototype = Object.create(parent.prototype);
child.prototype.constructor = child;
child.extend = this.extend;
if (parent.prototype._onExtend) parent.prototype._onExtend(child);
if (child.prototype._onInherit) child.prototype._onInherit(parent);
return child;
};
return EventEmitter;
}
);
define('base/class', [
"base/event_emitter"
],
function(EventEmitter) {
var CLASS_ID = 0,
IS_SERVER = !(typeof(window) !== "undefined" && window.document),
IS_CLIENT = !IS_SERVER,
defineProperty = Object.defineProperty;
function Class() {
EventEmitter.call(this);
this._id = ++CLASS_ID;
this._serverId = -1;
this._SYNC = {};
}
EventEmitter.extend(Class);
defineProperty(Class.prototype, "isServer", {
get: function() {
return IS_SERVER;
}
});
defineProperty(Class.prototype, "isClient", {
get: function() {
return IS_CLIENT;
}
});
Class.prototype.clone = function() {
return new this.constructor().copy(this);
};
Class.prototype.copy = function() {
return this;
};
Class.prototype.toSYNC = function(json) {
json || (json = this._SYNC);
json._id = this._id;
return json;
};
Class.prototype.fromSYNC = function(json) {
this._SYNC = json;
return this;
};
Class.prototype.toJSON = function(json) {
json || (json = {});
json._id = this._id;
return json;
};
Class.prototype.fromJSON = function(json) {
this._serverId = json._id;
return this;
};
return Class;
}
);
define(
'base/device', [], function() {
function Device() {
var userAgent = navigator.userAgent.toLowerCase(),
audio = new Audio,
video = document.createElement("video");
this.userAgent = userAgent;
this.pixelRatio = window.devicePixelRatio || 1;
this.invPixelRatio = 1 / this.pixelRatio;
this.browser = userAgent.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i)[1];
this.touch = "ontouchstart" in window;
this.mobile = /android|webos|iphone|ipad|ipod|blackberry/i.test(userAgent);
this.webgl = (function() {
var canvas = document.createElement("canvas"),
names = ["3d", "moz-webgl", "experimental-webgl", "webkit-3d", "webgl"],
has, i;
for (i = names.length; i--;) {
has = !! canvas.getContext(names[i]);
if (has) break;
}
return has;
}());
this.canvas = (function() {
var canvas = document.createElement("canvas"),
has = !! canvas.getContext("2d");
return has;
}());
this.gamepads = !! navigator.getGamepads || !! navigator.webkitGetGamepads || !! navigator.webkitGamepads;
this.audioMpeg = !! audio.canPlayType("audio/mpeg");
this.audioOgg = !! audio.canPlayType("audio/ogg");
this.audioMp4 = !! audio.canPlayType("audio/mp4");
this.videoWebm = !! video.canPlayType("video/webm");
this.videoOgg = !! video.canPlayType("video/ogg");
this.videoMp4 = !! video.canPlayType("video/mp4");
}
return new Device;
}
);
define('base/dom', [
"core/game/log"
],
function(Log) {
var SPLITER = /[ ,]+/,
regAttribute = /attribute\s+([a-z]+\s+)?([A-Za-z0-9]+)\s+([a-zA-Z_0-9]+)\s*(\[\s*(.+)\s*\])?/,
regUniform = /uniform\s+([a-z]+\s+)?([A-Za-z0-9]+)\s+([a-zA-Z_0-9]+)\s*(\[\s*(.+)\s*\])?/,
WEBGL_NAMES = ["webgl", "webkit-3d", "moz-webgl", "experimental-webgl", "3d"],
WEBGL_ATTRIBUTES = {
alpha: true,
antialias: true,
depth: true,
premultipliedAlpha: true,
preserveDrawingBuffer: false,
stencil: true
};
function Dom() {}
Dom.prototype.addEvent = function(obj, name, callback, ctx) {
var names = name.split(SPLITER),
i,
scope = ctx || obj,
afn = function(e) {
if (callback) callback.call(scope, e || window.event);
};
for (i = names.length; i--;) {
name = names[i];
if (obj.attachEvent) {
obj.attachEvent("on" + name, afn);
} else {
obj.addEventListener(name, afn, false);
}
}
};
Dom.prototype.removeEvent = function(obj, name, callback, ctx) {
var names = name.split(SPLITER),
i, il,
scope = ctx || obj,
afn = function(e) {
if (callback) callback.call(scope, e || window.event);
};
for (i = 0, il = names.length; i < il; i++) {
name = names[i];
if (obj.detachEvent) {
obj.detachEvent("on" + name, afn);
} else {
obj.removeEventListener(name, afn, false);
}
}
};
Dom.prototype.addMeta = function(id, name, content) {
var meta = document.createElement("meta"),
head = document.head;
if (id) meta.id = id;
if (name) meta.name = name;
if (content) meta.content = content;
head.insertBefore(meta, head.firstChild);
};
Dom.prototype.getWebGLContext = function(canvas, attributes) {
var key, gl, i;
attributes || (attributes = {});
for (key in WEBGL_ATTRIBUTES)
if (attributes[key] != undefined) attributes[key] = WEBGL_ATTRIBUTES[key];
for (i = WEBGL_NAMES.length; i--;) {
try {
gl = canvas.getContext(WEBGL_NAMES[i], attributes);
} catch (err) {
Log.warn("Dom.getWebGLContext: could not get a WebGL Context " + err.message || "");
}
if (gl) break;
}
return gl;
};
var createShader = Dom.prototype.createShader = function(gl, source, type) {
var shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
Log.warn("Dom.createShader: problem compiling shader " + gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return undefined;
}
return shader;
};
Dom.prototype.createProgram = function(gl, vertex, fragment) {
var program = gl.createProgram(),
shader;
shader = createShader(gl, vertex, gl.VERTEX_SHADER);
gl.attachShader(program, shader);
gl.deleteShader(shader);
shader = createShader(gl, fragment, gl.FRAGMENT_SHADER);
gl.attachShader(program, shader);
gl.deleteShader(shader);
gl.linkProgram(program);
gl.validateProgram(program);
gl.useProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
Log.warn("Dom.createProgram: problem compiling Program " + gl.getProgramInfoLog(program));
gl.deleteProgram(program);
return undefined;
}
return program;
};
Dom.prototype.parseUniformsAttributes = function(gl, program, vertexShader, fragmentShader, attributes, uniforms) {
var src = vertexShader + fragmentShader,
lines = src.split("\n"),
matchAttributes, matchUniforms,
name, length, line,
i, j;
for (i = lines.length; i--;) {
line = lines[i];
matchAttributes = line.match(regAttribute);
matchUniforms = line.match(regUniform);
if (matchAttributes) {
name = matchAttributes[3];
attributes[name] = gl.getAttribLocation(program, name);
}
if (matchUniforms) {
name = matchUniforms[3];
length = parseInt(matchUniforms[5]);
if (length) {
uniforms[name] = [];
for (j = length; j--;) uniforms[name][j] = gl.getUniformLocation(program, name + "[" + j + "]");
} else {
uniforms[name] = gl.getUniformLocation(program, name);
}
}
}
};
return new Dom;
}
);
define(
'base/object_pool', [], function() {
function ObjectPool(constructor) {
this.pooled = [];
this.objects = [];
this.object = constructor;
}
ObjectPool.prototype.create = function() {
var pooled = this.pooled,
object = pooled.length ? pooled.pop() : new this.object;
this.objects.push(object);
return object;
};
ObjectPool.prototype.removeObject = function(object) {
var objects = this.objects,
pooled = this.pooled,
index = objects.indexOf(object);
if (index > -1) {
pooled.push(object);
objects.splice(index, 1);
}
return this;
};
ObjectPool.prototype.remove = ObjectPool.prototype.removeObjects = function() {
for (var i = arguments.length; i--;) this.removeObject(arguments[i]);
return this;
};
ObjectPool.prototype.clear = function() {
var objects = this.objects,
pooled = this.pooled,
i;
for (i = objects.length; i--;) pooled.push(objects[i]);
objects.length = 0;
return this;
};
return ObjectPool;
}
);
define(
'base/request_animation_frame', [], function() {
var RATE = 1000 / 60,
w = typeof(window) !== "undefined" ? window : global;
return (
w.requestAnimationFrame ||
w.webkitRequestAnimationFrame ||
w.mozRequestAnimationFrame ||
w.oRequestAnimationFrame ||
w.msRequestAnimationFrame ||
function(callback) {
return w.setTimeout(callback, RATE);
}
);
}
);
(function() {
var io = (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;
/**
* Available transports, these will be populated with the available transports
*
* @api public
*/
io.transports = [];
/**
* Keep track of jsonp callbacks.
*
* @api private
*/
io.j = [];
/**
* Keep track of our io.Sockets
*
* @api private
*/
io.sockets = {};
/**
* Manages connections to hosts.
*
* @param {String} uri
* @Param {Boolean} force creation of new socket (defaults to false)
* @api public
*/
io.connect = function(host, details) {
var uri = io.util.parseUri(host),
uuri, socket;
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);
var options = {
host: uri.host,
secure: 'https' == uri.protocol,
port: uri.port || ('https' == uri.protocol ? 443 : 80),
query: uri.query || ''
};
io.util.merge(options, details);
if (options['force new connection'] || !io.sockets[uuri]) {
socket = new io.Socket(options);
}
if (!options['force new connection'] && socket) {
io.sockets[uuri] = socket;
}
socket = socket || io.sockets[uuri];
// if path is different from '' or /
return socket.of(uri.path.length > 1 ? uri.path : '');
};
return io;
})({}, this);
(function(exports, global) {
/**
* Utilities namespace.
*
* @namespace
*/
var util = exports.util = {};
var re = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
var parts = ['source', 'protocol', 'authority', 'userInfo', 'user', 'password',
'host', 'port', 'relative', 'path', 'directory', 'file', 'query',
'anchor'
];
util.parseUri = function(str) {
var m = re.exec(str || ''),
uri = {}, i = 14;
while (i--) {
uri[parts[i]] = m[i] || '';
}
return uri;
};
/**
* Produces a unique url that identifies a Socket.IO connection.
*
* @param {Object} uri
* @api public
*/
util.uniqueUri = function(uri) {
var protocol = uri.protocol,
host = uri.host,
port = uri.port;
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 (protocol || 'http') + '://' + host + ':' + (port || 80);
};
/**
* Mergest 2 query strings in to once unique query string
*
* @param {String} base
* @param {String} addition
* @api public
*/
util.query = function(base, addition) {
var query = util.chunkQuery(base || ''),
components = [];
util.merge(query, util.chunkQuery(addition || ''));
for (var part in query) {
if (query.hasOwnProperty(part)) {
components.push(part + '=' + query[part]);
}
}
return components.length ? '?' + components.join('&') : '';
};
/**
* Transforms a querystring in to an object
*
* @param {String} qs
* @api public
*/
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];
}
}
return query;
};
/**
* Executes the given function when the page is loaded.
*
* io.util.load(function () { console.log('page loaded'); });
*
* @param {Function} fn
* @api public
*/
var pageLoaded = false;
util.load = function(fn) {
if ('document' in global && document.readyState === 'complete' || pageLoaded) {
return fn();
}
util.on(global, 'load', fn, false);
};
/**
* 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);
}
};
/**
* 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
*/
util.request = function(xdomain) {
if (xdomain && 'undefined' != typeof XDomainRequest && !util.ua.hasCORS) {
return new XDomainRequest();
}
if ('undefined' != typeof XMLHttpRequest && (!xdomain || util.ua.hasCORS)) {
return new XMLHttpRequest();
}
if (!xdomain) {
try {
return new window[(['Active'].concat('Object').join('X'))]('Microsoft.XMLHTTP');
} catch (e) {}
}
return null;
};
/**
* XHR based transport constructor.
*
* @constructor
* @api public
*/
/**
* Change the internal pageLoaded value.
*/
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
*/
util.defer = function(fn) {
if (!util.ua.webkit || 'undefined' != typeof importScripts) {
return fn();
}
util.load(function() {
setTimeout(fn, 100);
});
};
/**
* Merges two objects.
*
* @api public
*/
util.merge = function merge(target, additional, deep, lastseen) {
var seen = lastseen || [],
depth = typeof deep == 'undefined' ? 2 : deep,
prop;
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);
}
}
}
return target;
};
/**
* Merges prototypes from objects
*
* @api public
*/
util.mixin = function(ctor, ctor2) {
util.merge(ctor.prototype, ctor2.prototype);
};
/**
* Shortcut for prototypical and static inheritance.
*
* @api private
*/
util.inherit = function(ctor, ctor2) {
function f() {};
f.prototype = ctor2.prototype;
ctor.prototype = new f;
};
/**
* Checks if the given object is an Array.
*
* io.util.isArray([]); // true
* io.util.isArray({}); // false
*
* @param Object obj
* @api public
*/
util.isArray = Array.isArray || function(obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
};
/**
* Intersects values of two arrays into a third
*
* @api public
*/
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]);
}
return ret;
};
/**
* Array indexOf compatibility.
*
* @see bit.ly/a5Dxa2
* @api public
*/
util.indexOf = function(arr, o, i) {
for (var j = arr.length, i = i < 0 ? i + j < 0 ? 0 : i + j : i || 0; i < j && arr[i] !== o; i++) {}
return j <= i ? -1 : i;
};
/**
* Converts enumerables to array.
*
* @api public
*/
util.toArray = function(enu) {
var arr = [];
for (var i = 0, l = enu.length; i < l; i++)
arr.push(enu[i]);
return arr;
};
/**
* UA / engines detection namespace.
*
* @namespace
*/
util.ua = {};
/**
* Whether the UA supports CORS for XHR.
*
* @api public
*/
util.ua.hasCORS = 'undefined' != typeof XMLHttpRequest && (function() {
try {
var a = new XMLHttpRequest();
} catch (e) {
return false;
}
return a.withCredentials != undefined;
})();
/**
* Detect webkit.
*
* @api public
*/
util.ua.webkit = 'undefined' != typeof navigator && /webkit/i.test(navigator.userAgent);
/**
* Detect iPad/iPhone/iPod.
*
* @api public
*/
util.ua.iDevice = 'undefined' != typeof navigator && /iPad|iPhone|iPod/i.test(navigator.userAgent);
})('undefined' != typeof io ? io : module.exports, this);
(function(exports, io) {
/**
* Expose constructor.
*/
exports.EventEmitter = EventEmitter;
/**
* Event emitter constructor.
*
* @api public.
*/
function EventEmitter() {};
/**
* Adds a listener
*
* @api public
*/
EventEmitter.prototype.on = function(name, fn) {
if (!this.$events) {
this.$events = {};
}
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 this;
};
EventEmitter.prototype.addListener = EventEmitter.prototype.on;
/**
* Adds a volatile listener.
*
* @api public
*/
EventEmitter.prototype.once = function(name, fn) {
var self = this;
function on() {
self.removeListener(name, on);
fn.apply(this, arguments);
};
on.listener = fn;
this.on(name, on);
return this;
};
/**
* Removes a listener.
*
* @api public
*/
EventEmitter.prototype.removeListener = function(name, fn) {
if (this.$events && this.$events[name]) {
var list = this.$events[name];
if (io.util.isArray(list)) {
var pos = -1;
for (var i = 0, l = list.length; i < l; i++) {
if (list[i] === fn || (list[i].listener && list[i].listener === fn)) {
pos = i;
break;
}
}
if (pos < 0) {
return this;
}
list.splice(pos, 1);
if (!list.length) {
delete this.$events[name];
}
} else if (list === fn || (list.listener && list.listener === fn)) {
delete this.$events[name];
}
}
return this;
};
/**
* Removes all listeners for an event.
*
* @api public
*/
EventEmitter.prototype.removeAllListeners = function(name) {
if (name === undefined) {
this.$events = {};
return this;
}
if (this.$events && this.$events[name]) {
this.$events[name] = null;
}
return this;
};
/**
* Gets all listeners for a certain event.
*
* @api publci
*/
EventEmitter.prototype.listeners = function(name) {
if (!this.$events) {
this.$events = {};
}
if (!this.$events[name]) {
this.$events[name] = [];
}
if (!io.util.isArray(this.$events[name])) {
this.$events[name] = [this.$events[name]];
}
return this.$events[name];
};
/**
* Emits an event.
*
* @api public
*/
EventEmitter.prototype.emit = function(name) {
if (!this.$events) {
return false;
}
var handler = this.$events[name];
if (!handler) {
return false;
}
var args = Array.prototype.slice.call(arguments, 1);
if ('function' == typeof handler) {
handler.apply(this, args);
} else if (io.util.isArray(handler)) {
var listeners = handler.slice();
for (var i = 0, l = listeners.length; i < l; i++) {
listeners[i].apply(this, args);
}
} else {
return false;
}
return true;
};
})(
'undefined' != typeof io ? io : module.exports, 'undefined' != typeof io ? io : module.parent.exports
);
/**
* Based on JSON2 (http://www.JSON.org/js.html).
*/
(function(exports, nativeJSON) {
// use native JSON if it's available
if (nativeJSON && nativeJSON.parse) {
return exports.JSON = {
parse: nativeJSON.parse,
stringify: nativeJSON.stringify
};
}
var JSON = exports.JSON = {};
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
function date(d) {
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;
};
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
gap,
indent,
meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"': '\\"',
'\\': '\\\\'
},
rep;
function quote(string) {
// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.
escapable.lastIndex = 0;
return escapable.test(string) ? '"' + string.replace(escapable, function(a) {
var c = meta[a];
return typeof c === 'string' ? c :
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' : '"' + string + '"';
}
function str(key, holder) {
// Produce a string from holder[key].
var i, // The loop counter.
k, // The member key.
v, // The member value.
length,
mind = gap,
partial,
value = holder[key];
// If the value has a toJSON method, call it to obtain a replacement value.
if (value instanceof Date) {
value = date(key);
}
// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.
if (typeof rep === 'function') {
value = rep.call(holder, key, value);
}
// What happens next depends on the value's type.
switch (typeof value) {
case 'string':
return quote(value);
case 'number':
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.
return String(value);
// If the type is 'object', we might be dealing with an object or an array or
// null.
case 'object':
// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.
if (!value) {
return 'null';
}
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]') {
// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.
length = value.length;
for (i = 0; i < length; i += 1) {
partial[i] = str(i, value) || 'null';
}
// Join all of the elements together, separated with commas, and wrap them in
// brackets.
v = partial.length === 0 ? '[]' : gap ?
'[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' :
'[' + partial.join(',') + ']';
gap = mind;
return v;
}
// If the replacer is an array, use it to select the members to be stringified.
if (rep && typeof rep === 'object') {
length = rep.length;
for (i = 0; i < length; i += 1) {
if (typeof rep[i] === 'string') {
k = rep[i];
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
} else {
// Otherwise, iterate through all of the keys in the object.
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
}
// Join all of the member texts together, separated with commas,
// and wrap them in braces.
v = partial.length === 0 ? '{}' : gap ?
'{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' :
'{' + partial.join(',') + '}';
gap = mind;
return v;
}
}
// If the JSON object does not yet have a stringify method, give it one.
JSON.stringify = function(value, replacer, space) {
// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.
var i;
gap = '';
indent = '';
// If the space parameter is a number, make an indent string containing that
// many spaces.
if (typeof space === 'number') {
for (i = 0; i < space; i += 1) {
indent += ' ';
}
// If the space parameter is a string, it will be used as the indent string.
} else if (typeof space === 'string') {
indent = space;
}
// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.
rep = replacer;
if (replacer && typeof replacer !== 'function' &&
(typeof replacer !== 'object' ||
typeof replacer.length !== 'number')) {
throw new Error('JSON.stringify');
}
// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.
return str('', {
'': value
});
};
// If the JSON object does not yet have a parse method, give it one.
JSON.parse = function(text, reviver) {
// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.
var j;
function walk(holder, key) {
// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.
var k, v, value = holder[key];
if (value && typeof value === 'object') {
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}
// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.
text = String(text);
cx.lastIndex = 0;
if (cx.test(text)) {
text = text.replace(cx, function(a) {
return '\\u' +
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
});
}
// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.
// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
if (/^[\],:{}\s]*$/
.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.
j = eval('(' + text + ')');
// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.
return typeof reviver === 'function' ?
walk({
'': j
}, '') : j;
}
// If the text is not JSON parseable, then a SyntaxError is thrown.
throw new SyntaxError('JSON.parse');
};
})(
'undefined' != typeof io ? io : module.exports, typeof JSON !== 'undefined' ? JSON : undefined
);
(function(exports, io) {
/**
* Parser namespace.
*
* @namespace
*/
var parser = exports.parser = {};
/**
* Packet types.
*/
var packets = parser.packets = [
'disconnect', 'connect', 'heartbeat', 'message', 'json', 'event', 'ack', 'error', 'noop'
];
/**
* Errors reasons.
*/
var reasons = parser.reasons = [
'transport not supported', 'client not handshaken', 'unauthorized'
];
/**
* Errors advice.
*/
var advice = parser.advice = [
'reconnect'
];
/**
* Shortcuts.
*/
var JSON = io.JSON,
indexOf = io.util.indexOf;
/**
* Encodes a packet.
*
* @api private
*/
parser.encodePacket = function(packet) {
var type = indexOf(packets, packet.type),
id = packet.id || '',
endpoint = packet.endpoint || '',
ack = packet.ack,
data = null;
switch (packet.type) {
case 'error':
var reason = packet.reason ? indexOf(reasons, packet.reason) : '',
adv = packet.advice ? indexOf(advice, packet.advice) : '';
if (reason !== '' || adv !== '')
data = reason + (adv !== '' ? ('+' + adv) : '');
break;
case 'message':
if (packet.data !== '')
data = packet.data;
break;
case 'event':
var ev = {
name: packet.name
};
if (packet.args && packet.args.length) {
ev.args = packet.args;
}
data = JSON.stringify(ev);
break;
case 'json':
data = JSON.stringify(packet.data);
break;
case 'connect':
if (packet.qs)
data = packet.qs;
break;
case 'ack':
data = packet.ackId + (packet.args && packet.args.length ? '+' + JSON.stringify(packet.args) : '');
break;
}
// construct packet with required fragments
var encoded = [
type, id + (ack == 'data' ? '+' : ''), endpoint
];
// data fragment is optional
if (data !== null && data !== undefined)
encoded.push(data);
return encoded.join(':');
};
/**
* Encodes multiple messages (payload).
*
* @param {Array} messages
* @api private
*/
parser.encodePayload = function(packets) {
var decoded = '';
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 decoded;
};
/**
* Decodes a packet
*
* @api private
*/
var regexp = /([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/;
parser.decodePacket = function(data) {
var pieces = data.match(regexp);
if (!pieces) return {};
var id = pieces[2] || '',
data = pieces[5] || '',
packet = {
type: packets[pieces[1]],
endpoint: pieces[4] || ''
};
// whether we need to acknowledge the packet
if (id) {
packet.id = id;
if (piec