Created
June 5, 2012 17:02
-
-
Save euforic/2876251 to your computer and use it in GitHub Desktop.
Socket.io-client Titanium
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! Socket.IO.js build:0.9.6, development. Copyright(c) 2011 LearnBoost <[email protected]> MIT Licensed */ | |
/** | |
* Originally Ported to titanium by Jordi Domenech <[email protected]> | |
* source: https://github.com/iamyellow/socket.io-client | |
*/ | |
this.io = {}; | |
module.exports = this.io; | |
/** | |
* socket.io | |
* Copyright(c) 2011 LearnBoost <[email protected]> | |
* MIT Licensed | |
*/ | |
(function (exports, global) { | |
/** | |
* IO namespace. | |
* | |
* @namespace | |
*/ | |
var io = exports; | |
/** | |
* Socket.IO version | |
* | |
* @api public | |
*/ | |
io.version = '0.9.6'; | |
/** | |
* 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; | |
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 : ''); | |
}; | |
})(this.io, this);/** | |
* socket.io | |
* Copyright(c) 2011 LearnBoost <[email protected]> | |
* MIT Licensed | |
*/ | |
(function (exports, global) { | |
/** | |
* Utilities namespace. | |
* | |
* @namespace | |
*/ | |
var util = exports.util = {}; | |
/** | |
* Parses an URI | |
* | |
* @author Steven Levithan <stevenlevithan.com> (MIT license) | |
* @api public | |
*/ | |
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) { | |
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); | |
/** | |
* socket.io | |
* Copyright(c) 2011 LearnBoost <[email protected]> | |
* MIT Licensed | |
*/ | |
(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) { | |
// TODO: enable this when node 0.5 is stable | |
//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 | |
); | |
/** | |
* socket.io | |
* Copyright(c) 2011 LearnBoost <[email protected]> | |
* MIT Licensed | |
*/ | |
/** | |
* Based on JSON2 (http://www.JSON.org/js.html). | |
*/ | |
(function (exports, nativeJSON) { | |
"use strict"; | |
return exports.JSON = { | |
parse: nativeJSON.parse, | |
stringify: nativeJSON.stringify | |
}; | |
})( | |
'undefined' != typeof io ? io : module.exports | |
, typeof JSON !== 'undefined' ? JSON : undefined | |
); | |
/** | |
* socket.io | |
* Copyright(c) 2011 LearnBoost <[email protected]> | |
* MIT Licensed | |
*/ | |
(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 (pieces[3]) | |
packet.ack = 'data'; | |
else | |
packet.ack = true; | |
} | |
// handle different packet types | |
switch (packet.type) { | |
case 'error': | |
var pieces = data.split('+'); | |
packet.reason = reasons[pieces[0]] || ''; | |
packet.advice = advice[pieces[1]] || ''; | |
break; | |
case 'message': | |
packet.data = data || ''; | |
break; | |
case 'event': | |
try { | |
var opts = JSON.parse(data); | |
packet.name = opts.name; | |
packet.args = opts.args; | |
} catch (e) { } | |
packet.args = packet.args || []; | |
break; | |
case 'json': | |
try { | |
packet.data = JSON.parse(data); | |
} catch (e) { } | |
break; | |
case 'connect': | |
packet.qs = data || ''; | |
break; | |
case 'ack': | |
var pieces = data.match(/^([0-9]+)(\+)?(.*)/); | |
if (pieces) { | |
packet.ackId = pieces[1]; | |
packet.args = []; | |
if (pieces[3]) { | |
try { | |
packet.args = pieces[3] ? JSON.parse(pieces[3]) : []; | |
} catch (e) { } | |
} | |
} | |
break; | |
case 'disconnect': | |
case 'heartbeat': | |
break; | |
}; | |
return packet; | |
}; | |
/** | |
* Decodes data payload. Detects multiple messages | |
* | |
* @return {Array} messages | |
* @api public | |
*/ | |
parser.decodePayload = function (data) { | |
// IE doesn't like data[i] for unicode chars, charAt works fine | |
if (data.charAt(0) == '\ufffd') { | |
var ret = []; | |
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); | |
} | |
} | |
return ret; | |
} else { | |
return [parser.decodePacket(data)]; | |
} | |
}; | |
})( | |
'undefined' != typeof io ? io : module.exports | |
, 'undefined' != typeof io ? io : module.parent.exports | |
); | |
/** | |
* socket.io | |
* Copyright(c) 2011 LearnBoost <[email protected]> | |
* MIT Licensed | |
*/ | |
(function (exports, io) { | |
/** | |
* Expose constructor. | |
*/ | |
exports.Transport = Transport; | |
/** | |
* This is the transport template for all supported transport methods. | |
* | |
* @constructor | |
* @api public | |
*/ | |
function Transport (socket, sessid) { | |
this.socket = socket; | |
this.sessid = sessid; | |
}; | |
/** | |
* Apply EventEmitter mixin. | |
*/ | |
io.util.mixin(Transport, io.EventEmitter); | |
/** | |
* 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 | |
*/ | |
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; | |
}; | |
/** | |
* Handles packets. | |
* | |
* @api private | |
*/ | |
Transport.prototype.onPacket = function (packet) { | |
this.socket.setHeartbeatTimeout(); | |
if (packet.type == 'heartbeat') { | |
return this.onHeartbeat(); | |
} | |
if (packet.type == 'connect' && packet.endpoint == '') { | |
this.onConnect(); | |
} | |
if (packet.type == 'error' && packet.advice == 'reconnect') { | |
this.open = false; | |
} | |
this.socket.onPacket(packet); | |
return this; | |
}; | |
/** | |
* Sets close timeout | |
* | |
* @api private | |
*/ | |
Transport.prototype.setCloseTimeout = function () { | |
if (!this.closeTimeout) { | |
var self = this; | |
this.closeTimeout = setTimeout(function () { | |
self.onDisconnect(); | |
}, this.socket.closeTimeout); | |
} | |
}; | |
/** | |
* Called when transport disconnects. | |
* | |
* @api private | |
*/ | |
Transport.prototype.onDisconnect = function () { | |
if (this.close && this.open) this.close(); | |
this.clearTimeouts(); | |
this.socket.onDisconnect(); | |
return this; | |
}; | |
/** | |
* Called when transport connects | |
* | |
* @api private | |
*/ | |
Transport.prototype.onConnect = function () { | |
this.socket.onConnect(); | |
return this; | |
} | |
/** | |
* Clears close timeout | |
* | |
* @api private | |
*/ | |
Transport.prototype.clearCloseTimeout = function () { | |
if (this.closeTimeout && typeof this.closeTimeout === 'number') { | |
clearTimeout(this.closeTimeout); | |
this.closeTimeout = null; | |
} | |
}; | |
/** | |
* Clear timeouts | |
* | |
* @api private | |
*/ | |
Transport.prototype.clearTimeouts = function () { | |
this.clearCloseTimeout(); | |
if (this.reopenTimeout && typeof this.reopenTimeout === 'number') { | |
clearTimeout(this.reopenTimeout); | |
} | |
}; | |
/** | |
* Sends a packet | |
* | |
* @param {Object} packet object. | |
* @api private | |
*/ | |
Transport.prototype.packet = function (packet) { | |
this.send(io.parser.encodePacket(packet)); | |
}; | |
/** | |
* 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 | |
*/ | |
Transport.prototype.onHeartbeat = function (heartbeat) { | |
this.packet({ type: 'heartbeat' }); | |
}; | |
/** | |
* Called when the transport opens. | |
* | |
* @api private | |
*/ | |
Transport.prototype.onOpen = function () { | |
this.open = true; | |
this.clearCloseTimeout(); | |
this.socket.onOpen(); | |
}; | |
/** | |
* Notifies the base when the connection with the Socket.IO server | |
* has been disconnected. | |
* | |
* @api private | |
*/ | |
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.open = false; | |
this.socket.onClose(); | |
this.onDisconnect(); | |
}; | |
/** | |
* Generates a connection url based on the Socket.IO URL Protocol. | |
* See <https://github.com/learnboost/socket.io-node/> for more details. | |
* | |
* @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; | |
}; | |
/** | |
* 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 | |
*/ | |
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 <[email protected]> | |
* MIT Licensed | |
*/ | |
(function (exports, io, global) { | |
/** | |
* Expose constructor. | |
*/ | |
exports.Socket = Socket; | |
/** | |
* Create a new `Socket.IO client` which can establish a persistent | |
* connection with a Socket.IO enabled server. | |
* | |
* @api public | |
*/ | |
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': true | |
, 'auto connect': true | |
, 'flash policy port': 10843 | |
}; | |
io.util.merge(this.options, options); | |
this.connected = false; | |
this.open = false; | |
this.connecting = false; | |
this.reconnecting = false; | |
this.namespaces = {}; | |
this.buffer = []; | |
this.doBuffer = false; | |
if (this.options['sync disconnect on unload'] && | |
(!this.isXDomain() || io.util.ua.hasCORS)) { | |
var self = this; | |
io.util.on(global, 'unload', function () { | |
self.disconnectSync(); | |
}, false); | |
} | |
if (this.options['auto connect']) { | |
this.connect(); | |
} | |
}; | |
/** | |
* Apply EventEmitter mixin. | |
*/ | |
io.util.mixin(Socket, io.EventEmitter); | |
/** | |
* Returns a namespace listener/emitter for this socket | |
* | |
* @api public | |
*/ | |
Socket.prototype.of = function (name) { | |
if (!this.namespaces[name]) { | |
this.namespaces[name] = new io.SocketNamespace(this, name); | |
if (name !== '') { | |
this.namespaces[name].packet({ type: 'connect' }); | |
} | |
} | |
return this.namespaces[name]; | |
}; | |
/** | |
* Emits the given event to the Socket and all namespaces | |
* | |
* @api private | |
*/ | |
Socket.prototype.publish = function () { | |
this.emit.apply(this, arguments); | |
var nsp; | |
for (var i in this.namespaces) { | |
if (this.namespaces.hasOwnProperty(i)) { | |
nsp = this.of(i); | |
nsp.$emit.apply(nsp, arguments); | |
} | |
} | |
}; | |
/** | |
* Performs the handshake | |
* | |
* @api private | |
*/ | |
function empty () { }; | |
Socket.prototype.handshake = function (fn) { | |
var self = this | |
, options = this.options; | |
function complete (data) { | |
if (data instanceof Error) { | |
self.connecting = false; | |
self.onError(data.message); | |
} else { | |
fn.apply(null, data.split(':')); | |
} | |
}; | |
var url = [ | |
'http' + (options.secure ? 's' : '') + ':/' | |
, options.host + ':' + options.port | |
, options.resource | |
, io.protocol | |
, io.util.query(this.options.query, 't=' + +new Date) | |
].join('/'); | |
var xhr = Ti.Network.createHTTPClient(); | |
xhr.open('GET', url, true); | |
xhr.withCredentials = true; | |
xhr.onreadystatechange = function () { | |
if (xhr.readyState == 4) { | |
xhr.onreadystatechange = empty; | |
if (xhr.status == 200) { | |
complete(xhr.responseText); | |
} else { | |
self.connecting = false; | |
!self.reconnecting && self.onError(xhr.responseText); | |
} | |
} | |
}; | |
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; | |
for (var i = 0, transport; transport = transports[i]; i++) { | |
if (io.Transport[transport] | |
&& io.Transport[transport].check(this) | |
&& (!this.isXDomain() || io.Transport[transport].xdomainCheck())) { | |
return new io.Transport[transport](this, this.sessionid); | |
} | |
} | |
return null; | |
}; | |
/** | |
* Connects to the server. | |
* | |
* @param {Function} [fn] Callback. | |
* @returns {io.Socket} | |
* @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; | |
self.transports = 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']) { | |
if (!self.remainingTransports) { | |
self.remainingTransports = self.transports.slice(0); | |
} | |
var remaining = self.remainingTransports; | |
while (remaining.length > 0 && remaining.splice(0,1)[0] != | |
self.transport.name) {} | |
if (remaining.length){ | |
connect(remaining); | |
} else { | |
self.publish('connect_failed'); | |
} | |
} | |
} | |
}, self.options['connect timeout']); | |
} | |
}); | |
} | |
connect(self.transports); | |
self.once('connect', function (){ | |
if (typeof self.connectTimeoutTimer === 'number') { | |
clearTimeout(self.connectTimeoutTimer); | |
} | |
fn && typeof fn == 'function' && fn(); | |
}); | |
}); | |
return this; | |
}; | |
/** | |
* Clears and sets a new heartbeat timeout using the value given by the | |
* server during the handshake. | |
* | |
* @api private | |
*/ | |
Socket.prototype.setHeartbeatTimeout = function () { | |
if (typeof this.heartbeatTimeoutTimer === 'number') { | |
clearTimeout(this.heartbeatTimeoutTimer); | |
} | |
var self = this; | |
this.heartbeatTimeoutTimer = setTimeout(function () { | |
self.transport.onClose(); | |
}, this.heartbeatTimeout); | |
}; | |
/** | |
* Sends a message. | |
* | |
* @param {Object} data packet. | |
* @returns {io.Socket} | |
* @api public | |
*/ | |
Socket.prototype.packet = function (data) { | |
if (this.connected && !this.doBuffer) { | |
this.transport.packet(data); | |
} else { | |
this.buffer.push(data); | |
} | |
return this; | |
}; | |
/** | |
* Sets buffer state | |
* | |
* @api private | |
*/ | |
Socket.prototype.setBuffer = function (v) { | |
this.doBuffer = v; | |
if (!v && this.connected && this.buffer.length) { | |
this.transport.payload(this.buffer); | |
this.buffer = []; | |
} | |
}; | |
/** | |
* Disconnect the established connect. | |
* | |
* @returns {io.Socket} | |
* @api public | |
*/ | |
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; | |
}; | |
/** | |
* Disconnects the socket with a sync XHR. | |
* | |
* @api private | |
*/ | |
Socket.prototype.disconnectSync = function () { | |
// ensure disconnection | |
var xhr = Ti.Network.createHTTPClient() | |
, uri = this.resource + '/' + io.protocol + '/' + this.sessionid; | |
xhr.open('GET', uri, true); | |
// handle disconnection immediately | |
this.onDisconnect('booted'); | |
}; | |
/** | |
* Check if we need to use cross domain enabled transports. Cross domain would | |
* be a different port or different domain name. | |
* | |
* @returns {Boolean} | |
* @api private | |
*/ | |
Socket.prototype.isXDomain = function () { | |
return false; | |
}; | |
/** | |
* Called upon handshake. | |
* | |
* @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'); | |
} | |
}; | |
/** | |
* Called when the transport opens | |
* | |
* @api private | |
*/ | |
Socket.prototype.onOpen = function () { | |
this.open = true; | |
}; | |
/** | |
* Called when the transport closes. | |
* | |
* @api private | |
*/ | |
Socket.prototype.onClose = function () { | |
this.open = false; | |
if (typeof this.heartbeatTimeoutTimer === 'number') { | |
clearTimeout(this.heartbeatTimeoutTimer); | |
} | |
}; | |
/** | |
* Called when the transport first opens a connection | |
* | |
* @param text | |
*/ | |
Socket.prototype.onPacket = function (packet) { | |
this.of(packet.endpoint).onPacket(packet); | |
}; | |
/** | |
* Handles an error. | |
* | |
* @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); | |
}; | |
/** | |
* Called when the transport disconnects. | |
* | |
* @api private | |
*/ | |
Socket.prototype.onDisconnect = function (reason) { | |
var wasConnected = this.connected | |
, wasConnecting = this.connecting; | |
this.connected = false; | |
this.connecting = false; | |
this.open = false; | |
if (wasConnected || wasConnecting) { | |
this.transport.close(); | |
this.transport.clearTimeouts(); | |
if (wasConnected) { | |
this.publish('disconnect', reason); | |
if ('booted' != reason && this.options.reconnect && !this.reconnecting) { | |
this.reconnect(); | |
} | |
} | |
} | |
}; | |
/** | |
* Called upon reconnection. | |
* | |
* @api private | |
*/ | |
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); | |
} | |
if (typeof self.reconnectionTimer === 'number') { | |
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.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 : module.exports | |
, 'undefined' != typeof io ? io : module.parent.exports | |
, this | |
); | |
/** | |
* socket.io | |
* Copyright(c) 2011 LearnBoost <[email protected]> | |
* MIT Licensed | |
*/ | |
(function (exports, io) { | |
/** | |
* Expose constructor. | |
*/ | |
exports.SocketNamespace = SocketNamespace; | |
/** | |
* Socket namespace constructor. | |
* | |
* @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 = {}; | |
}; | |
/** | |
* Apply EventEmitter mixin. | |
*/ | |
io.util.mixin(SocketNamespace, io.EventEmitter); | |
/** | |
* Copies emit since we override it | |
* | |
* @api private | |
*/ | |
SocketNamespace.prototype.$emit = io.EventEmitter.prototype.emit; | |
/** | |
* 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 | |
*/ | |
SocketNamespace.prototype.of = function () { | |
return this.socket.of.apply(this.socket, arguments); | |
}; | |
/** | |
* 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 | |
* | |
* @api public | |
*/ | |
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); | |
}; | |
/** | |
* 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 | |
}; | |
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'); | |
} | |
return this; | |
}; | |
/** | |
* Handles a packet | |
* | |
* @api private | |
*/ | |
SocketNamespace.prototype.onPacket = function (packet) { | |
var self = this; | |
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; | |
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 }); | |
} | |
this.$emit.apply(this, params); | |
break; | |
case 'event': | |
var params = [packet.name].concat(packet.args); | |
if (packet.ack == 'data') | |
params.push(ack); | |
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; | |
} | |
}; | |
/** | |
* Flag interface. | |
* | |
* @api private | |
*/ | |
function Flag (nsp, name) { | |
this.namespace = nsp; | |
this.name = name; | |
}; | |
/** | |
* Send a message | |
* | |
* @api public | |
*/ | |
Flag.prototype.send = function () { | |
this.namespace.flags[this.name] = true; | |
this.namespace.send.apply(this.namespace, arguments); | |
}; | |
/** | |
* Emit an event | |
* | |
* @api public | |
*/ | |
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 <[email protected]> | |
* MIT Licensed | |
*/ | |
(function (exports, io, global) { | |
/** | |
* Expose constructor. | |
*/ | |
exports.websocket = WS; | |
/** | |
* 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 | |
*/ | |
function WS (socket) { | |
io.Transport.apply(this, arguments); | |
}; | |
/** | |
* Inherits from Transport. | |
*/ | |
io.util.inherit(WS, io.Transport); | |
/** | |
* Transport name | |
* | |
* @api public | |
*/ | |
WS.prototype.name = 'websocket'; | |
/** | |
* Initializes a new `WebSocket` connection with the Socket.IO server. We attach | |
* all the appropriate listeners to handle the responses from the server. | |
* | |
* @returns {Transport} | |
* @api public | |
*/ | |
WS.prototype.open = function () { | |
var query = io.util.query(this.socket.options.query) | |
, self = this | |
, Socket | |
this.websocket = new WebSocket(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. | |
* | |
* @returns {Transport} | |
* @api public | |
*/ | |
// 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; | |
}; | |
} | |
/** | |
* Payload | |
* | |
* @api private | |
*/ | |
WS.prototype.payload = function (arr) { | |
for (var i = 0, l = arr.length; i < l; i++) { | |
this.packet(arr[i]); | |
} | |
return this; | |
}; | |
/** | |
* Disconnect the established `WebSocket` connection. | |
* | |
* @returns {Transport} | |
* @api public | |
*/ | |
WS.prototype.close = function () { | |
this.websocket.close(); | |
return this; | |
}; | |
/** | |
* Handle the errors that `WebSocket` might be giving when we | |
* are attempting to connect or send messages. | |
* | |
* @param {Error} e The error. | |
* @api private | |
*/ | |
WS.prototype.onError = function (e) { | |
this.socket.onError(e); | |
}; | |
/** | |
* Returns the appropriate scheme for the URI generation. | |
* | |
* @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 | |
*/ | |
WS.check = function () { | |
return true; | |
}; | |
/** | |
* Check if the `WebSocket` transport support cross domain communications. | |
* | |
* @returns {Boolean} | |
* @api public | |
*/ | |
WS.xdomainCheck = function () { | |
return true; | |
}; | |
/** | |
* Add the transport to your public io.transports array. | |
* | |
* @api private | |
*/ | |
io.transports.push('websocket'); | |
})( | |
'undefined' != typeof io ? io.Transport : module.exports | |
, 'undefined' != typeof io ? io : module.parent.exports | |
, this | |
); | |
var SHA1 = (function(){var exports={};/* | |
* Modified by Yuichiro MASUI <[email protected]> | |
* Tested on nodejs and Titanium Mobile | |
* | |
* The JavaScript implementation of the Secure Hash Algorithm 1 | |
* | |
* Copyright (c) 2008 Takanori Ishikawa <[email protected]> | |
* All rights reserved. | |
* | |
* Redistribution and use in source and binary forms, with or without | |
* modification, are permitted provided that the following conditions | |
* are met: | |
* | |
* 1. Redistributions of source code must retain the above copyright | |
* notice, this list of conditions and the following disclaimer. | |
* | |
* 2. Redistributions in binary form must reproduce the above copyright | |
* notice, this list of conditions and the following disclaimer in the | |
* documentation and/or other materials provided with the distribution. | |
* | |
* 3. Neither the name of the authors nor the names of its contributors | |
* may be used to endorse or promote products derived from this | |
* software without specific prior written permission. | |
* | |
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED | |
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
*/ | |
/** | |
* This is the javascript file for code which implements | |
* the Secure Hash Algorithm 1 as defined in FIPS 180-1 published April 17, 1995. | |
* | |
* Author: Takanori Ishikawa <[email protected]> | |
* Copyright: Takanori Ishikawa 2008 | |
* License: BSD License (see above) | |
* | |
* NOTE: | |
* Only 8-bit string is supported, please use encodeURIComponent() function | |
* if you want to hash multibyte string. | |
* | |
* Supported Browsers: | |
* [Win] IE 6, Firefox 2 | |
* [Mac] Safari 3, Firefox 2 | |
* | |
* Usage: | |
* var hexdigest = new SHA1("Hello.").hexdigest(); // "9b56d519ccd9e1e5b2a725e186184cdc68de0731" | |
* | |
* See Also: | |
* FIPS 180-1 - Secure Hash Standard | |
* http://www.itl.nist.gov/fipspubs/fip180-1.htm | |
* | |
*/ | |
var SHA1 = (function(){ | |
/** | |
* Spec is the BDD style test utilities. | |
*/ | |
var Spec; | |
Spec = { | |
/** Replace the Spec.describe function with empty function if false. */ | |
enabled: true, | |
/** Indicates whether object 'a' is "equal to" 'b'. */ | |
equals: function(a, b) { | |
var i; | |
if (a instanceof Array && b instanceof Array) { | |
if (a.length !== b.length) { return false; } | |
for (i = 0; i < a.length; i++) { if (!Spec.equals(a[i], b[i])) { return false; } } | |
return true; | |
} | |
if ((a !== null && b !== null) && (typeof a === "object" && typeof b === "object")) { | |
for (i in a) { if(a.hasOwnProperty(i)) { if (!Spec.equals(a[i], b[i])) { return false; } } } | |
return true; | |
} | |
return (a === b); | |
}, | |
/** equivalent to xUint's assert */ | |
should: function(expection, message) { | |
Spec.currentIndicator++; | |
if (!expection) { | |
var warning = [ | |
"[Spec failed", | |
Spec.currentTitle ? " (" + Spec.currentTitle + ")] " : "] ", | |
(message || (Spec.currentMessage + " " + Spec.currentIndicator) || "") | |
].join(""); | |
alert(warning); | |
throw warning; | |
} | |
return !!expection; | |
}, | |
/** Write your specification by using describe method. */ | |
describe: function(title, spec) { | |
Spec.currentTitle = title; | |
var name; | |
for (name in spec) { | |
if (spec.hasOwnProperty(name)) { | |
Spec.currentMessage = name; | |
Spec.currentIndicator = 0; | |
spec[name](); | |
Spec.currentIndicator = null; | |
} | |
} | |
Spec.currentMessage = Spec.currentTitle = null; | |
}, | |
Version: "0.1" | |
}; | |
// Other BDD style stuffs. | |
Spec.should.equal = function(a, b, message) { return Spec.should(Spec.equals(a, b), message); }; | |
Spec.should.not = function(a, message) { return Spec.should(!a, message); }; | |
Spec.should.not.equal = function(a, b, message) { return Spec.should(!Spec.equals(a, b), message); }; | |
if (!Spec.enabled) { Spec.describe = function(){}; } | |
// self test | |
Spec.describe("Spec object", { | |
"should": function() { | |
Spec.should(true); | |
Spec.should(1); | |
}, | |
"should.not": function() { | |
Spec.should.not(false); | |
Spec.should.not(0); | |
}, | |
"should.equal": function() { | |
Spec.should.equal(null, null); | |
Spec.should.equal("", ""); | |
Spec.should.equal(12345, 12345); | |
Spec.should.equal([0,1,2], [0,1,2]); | |
Spec.should.equal([0,1,[0,1,2]], [0,1,[0,1,2]]); | |
Spec.should.equal({}, {}); | |
Spec.should.equal({x:1}, {x:1}); | |
Spec.should.equal({x:[1]}, {x:[1]}); | |
}, | |
"should.not.equal": function() { | |
Spec.should.not.equal([1,2,3], [1,2,3,4]); | |
Spec.should.not.equal({x:1}, [1,2,3,4]); | |
} | |
}); | |
// ----------------------------------------------------------- | |
// Utilities | |
// ----------------------------------------------------------- | |
// int32 -> hexdigits string (e.g. 0x123 -> '00000123') | |
function strfhex32(i32) { | |
i32 &= 0xffffffff; | |
if (i32 < 0) { i32 += 0x100000000; } | |
var hex = Number(i32).toString(16); | |
if (hex.length < 8) { hex = "00000000".substr(0, 8 - hex.length) + hex; } | |
return hex; | |
} | |
Spec.describe("sha1", { | |
"strfhex32": function() { | |
Spec.should.equal(strfhex32(0x0), "00000000"); | |
Spec.should.equal(strfhex32(0x123), "00000123"); | |
Spec.should.equal(strfhex32(0xffffffff), "ffffffff"); | |
} | |
}); | |
/* | |
// int32 -> string (e.g. 123 -> '00000000 00000000 00000000 01111011') | |
function strfbits(i32) { | |
if (typeof arguments.callee.ZERO32 === 'undefined') { | |
arguments.callee.ZERO32 = new Array(33).join("0"); | |
} | |
var bits = Number(i32).toString(2); | |
// '0' padding | |
if (bits.length < 32) bits = arguments.callee.ZERO32.substr(0, 32 - bits.length) + bits; | |
// split by 8 bits | |
return bits.replace(/(¥d{8})/g, '$1 ') | |
.replace(/^¥s*(.*?)¥s*$/, '$1'); | |
} | |
Spec.describe("sha1", { | |
"strfbits": function() { | |
Ti.API.info(strfbits(0)); | |
Ti.API.info(strfbits(1)); | |
Ti.API.info(strfbits(123)); | |
Spec.should.equal(strfbits(0), "00000000 00000000 00000000 00000000"); | |
Spec.should.equal(strfbits(1), "00000000 00000000 00000000 00000001"); | |
Spec.should.equal(strfbits(123), "00000000 00000000 00000000 01111011"); | |
} | |
}); | |
*/ | |
// ----------------------------------------------------------- | |
// SHA-1 | |
// ----------------------------------------------------------- | |
// Returns Number(32bit unsigned integer) array size to fit for blocks (512-bit strings) | |
function padding_size(nbits) { | |
var n = nbits + 1 + 64; | |
return 512 * Math.ceil(n / 512) / 32; | |
} | |
Spec.describe("sha1", { | |
"padding_size": function() { | |
Spec.should.equal(padding_size(0), 16); | |
Spec.should.equal(padding_size(1), 16); | |
Spec.should.equal(padding_size(512 - 64 - 1), 16); | |
Spec.should.equal(padding_size(512 - 64), 32); | |
} | |
}); | |
// 8bit string -> uint32[] | |
function word_array(m) { | |
var nchar = m.length; | |
var size = padding_size(nchar * 8); | |
var words = new Array(size); | |
var i; | |
for (i = 0, j = 0; i < nchar; ) { | |
words[j++] = ((m.charCodeAt(i++) & 0xff) << 24) | | |
((m.charCodeAt(i++) & 0xff) << 16) | | |
((m.charCodeAt(i++) & 0xff) << 8) | | |
((m.charCodeAt(i++) & 0xff)); | |
} | |
while (j < size) { words[j++] = 0; } | |
return words; | |
} | |
Spec.describe("sha1", { | |
"word_array": function() { | |
Spec.should.equal(word_array(""), [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); | |
Spec.should.equal(word_array("1234")[0], 0x31323334); | |
} | |
}); | |
function write_nbits(words, length, nbits) { | |
if (nbits > 0xffffffff) { | |
var lo = nbits & 0xffffffff; | |
if (lo < 0) { lo += 0x100000000; } | |
words[length - 1] = lo; | |
words[length - 2] = (nbits - lo) / 0x100000000; | |
} else { | |
words[length - 1] = nbits; | |
words[length - 2] = 0x0; | |
} | |
return words; | |
} | |
Spec.describe("sha1", { | |
"write_nbits": function() { | |
Spec.should.equal(write_nbits([0, 0], 2, 1), [0, 1]); | |
Spec.should.equal(write_nbits([0, 0], 2, 0xffffffff), [0, 0xffffffff]); | |
Spec.should.equal(write_nbits([0, 0], 2, 0x100000000), [1, 0]); | |
Spec.should.equal(write_nbits([0, 0], 2, 0x1ffffffff), [1, 0xffffffff]); | |
Spec.should.equal(write_nbits([0, 0], 2, 0x12300000000), [0x123, 0]); | |
Spec.should.equal(write_nbits([0, 0], 2, 0x123abcdef12), [0x123, 0xabcdef12]); | |
} | |
}); | |
function padding(words, nbits) { | |
var i = Math.floor(nbits / 32); | |
words[i] |= (1 << (((i + 1) * 32) - nbits - 1)); | |
write_nbits(words, padding_size(nbits), nbits); | |
return words; | |
} | |
function digest(words) { | |
var i = 0, t = 0; | |
var H = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0]; | |
while (i < words.length) { | |
var W = new Array(80); | |
// (a) | |
for (t = 0; t < 16; t++) { W[t] = words[i++]; } | |
// (b) | |
for (t = 16; t < 80; t++) { | |
var w = W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16]; | |
W[t] = (w << 1) | (w >>> 31); | |
} | |
// (c) | |
var A = H[0], B = H[1], C = H[2], D = H[3], E = H[4]; | |
// (d) TEMP = S5(A) + ft(B,C,D) + E + Wt + Kt; | |
// E = D; D = C; C = S30(B); B = A; A = TEMP; | |
for (t = 0; t < 80; t++) { | |
var tmp = ((A << 5) | (A >>> 27)) + E + W[t]; | |
if (t >= 0 && t <= 19) { tmp += ((B & C) | ((~B) & D)) + 0x5a827999; } | |
else if (t >= 20 && t <= 39) { tmp += (B ^ C ^ D) + 0x6ed9eba1; } | |
else if (t >= 40 && t <= 59) { tmp += ((B & C) | (B & D) | (C & D)) + 0x8f1bbcdc; } | |
else if (t >= 60 && t <= 79) { tmp += (B ^ C ^ D) + 0xca62c1d6; } | |
E = D; D = C; C = ((B << 30) | (B >>> 2)); B = A; A = tmp; | |
} | |
// (e) H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E. | |
H[0] = (H[0] + A) & 0xffffffff; | |
H[1] = (H[1] + B) & 0xffffffff; | |
H[2] = (H[2] + C) & 0xffffffff; | |
H[3] = (H[3] + D) & 0xffffffff; | |
H[4] = (H[4] + E) & 0xffffffff; | |
if (H[0] < 0) { H[0] += 0x100000000; } | |
if (H[1] < 0) { H[1] += 0x100000000; } | |
if (H[2] < 0) { H[2] += 0x100000000; } | |
if (H[3] < 0) { H[3] += 0x100000000; } | |
if (H[4] < 0) { H[4] += 0x100000000; } | |
} | |
return H; | |
} | |
// message: 8bit string | |
var SHA1 = function(message) { | |
this.message = message; | |
}; | |
function strfhex8(i8) { | |
i8 &= 0xff; | |
if (i8 < 0) { i8 += 0x100; } | |
var hex = Number(i8).toString(16); | |
if (hex.length < 2) { hex = "00".substr(0, 2 - hex.length) + hex; } | |
return hex; | |
} | |
_base64_keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; | |
SHA1.prototype = { | |
digest: function() { | |
var nbits = this.message.length * 8; | |
var words = padding(word_array(this.message), nbits); | |
return digest(words); | |
}, | |
base64digest: function() { | |
var hex = this.hexdigest(); | |
var output = ""; | |
var chr1, chr2, chr3, enc1, enc2, enc3, enc4; | |
var i = 0; | |
while (i < hex.length) { | |
chr1 = parseInt(hex.substring(i, i+2), 16); | |
chr2 = parseInt(hex.substring(i+2, i+4), 16); | |
chr3 = parseInt(hex.substring(i+4, i+6), 16); | |
enc1 = chr1 >> 2; | |
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); | |
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); | |
enc4 = chr3 & 63; | |
if (isNaN(chr2)) { | |
enc3 = enc4 = 64; | |
} else if (isNaN(chr3)) { | |
enc4 = 64; | |
} | |
output = output + | |
_base64_keyStr.charAt(enc1) + _base64_keyStr.charAt(enc2) + | |
_base64_keyStr.charAt(enc3) + _base64_keyStr.charAt(enc4); | |
i += 6; | |
} | |
return output; | |
}, | |
hexdigest: function() { | |
var digest = this.digest(); | |
var i; | |
for (i = 0; i < digest.length; i++) { digest[i] = strfhex32(digest[i]); } | |
return digest.join(""); | |
} | |
}; | |
Spec.describe("sha1", { | |
"SHA1#hexdigest": function() { | |
Spec.should.equal(new SHA1("").hexdigest(), "da39a3ee5e6b4b0d3255bfef95601890afd80709"); | |
Spec.should.equal(new SHA1("1").hexdigest(), "356a192b7913b04c54574d18c28d46e6395428ab"); | |
Spec.should.equal(new SHA1("Hello.").hexdigest(), "9b56d519ccd9e1e5b2a725e186184cdc68de0731"); | |
Spec.should.equal(new SHA1("9b56d519ccd9e1e5b2a725e186184cdc68de0731").hexdigest(), "f042dc98a62cbad68dbe21f11bbc1e9d416d2bf6"); | |
Spec.should.equal(new SHA1("MD5abZRVSXZVRcasdfasdddddddddddddddds+BNRJFSLKJFN+SEONBBJFJXLKCJFSE)RUNVXDLILKVJRN)#NVFJ)WVFWRW#)NVS$Q=$dddddddddddddWV;no9wurJFSE)RUNVXDLILKVJRN)#NVFJ)WVFWRW#)NVS$Q=$dddddddddddddWV;no9wurJFSE)RUNVXDLILKVJRN)#NVFJ)WVFWRW#)NVS$Q=$dddddddddddddWV;no9wurJFSE)RUNVXDLILKVJRN)#NVFJ)WVFWRW#)NVS$Q=$dddddddddddddWV;no9wuraddddddasdfasdfd").hexdigest(), "662dbf4ebc9cdb4224766e87634e5ba9e6de672b"); | |
} | |
}); | |
return SHA1; | |
}()); | |
exports.SHA1 = SHA1; // add for node.js | |
return exports;}()).SHA1; | |
var Utils = (function(){var exports={}; | |
exports.read_byte = function(buffer, position) { | |
var data = Ti.Codec.decodeNumber({ | |
source: buffer, | |
position: position || 0, | |
type: Ti.Codec.TYPE_BYTE, | |
byteOrder: Ti.Codec.BIG_ENDIAN | |
}); | |
if(data < 0) { data += 256; } //2**8; | |
return data; | |
}; | |
exports.read_2byte = function(buffer, position) { | |
var data = Ti.Codec.decodeNumber({ | |
source: buffer, | |
position: position || 0, | |
type: Ti.Codec.TYPE_SHORT, | |
byteOrder: Ti.Codec.BIG_ENDIAN | |
}); | |
if(data < 0) { data += 65536; } // 2**16 | |
return data; | |
}; | |
exports.read_8byte = function(buffer, position) { | |
var data = Ti.Codec.decodeNumber({ | |
source: buffer, | |
position: position || 0, | |
type: Ti.Codec.TYPE_LONG, | |
byteOrder: Ti.Codec.BIG_ENDIAN | |
}); | |
if(data < 0) { data += 18446744073709551616; } // 2**64 | |
return data; | |
}; | |
exports.byte_length = function(str) { | |
var buffer = Ti.createBuffer({length: 65536}); | |
var length = Ti.Codec.encodeString({ | |
source: str, | |
dest: buffer | |
}); | |
return length; | |
}; | |
exports.trim = function(str) { | |
return String(str).replace(/^\s+|\s+$/g, ""); | |
}; | |
return exports;}()); | |
var events = (function(){var exports={};// Copyright Joyent, Inc. and other Node contributors. | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a | |
// copy of this software and associated documentation files (the | |
// "Software"), to deal in the Software without restriction, including | |
// without limitation the rights to use, copy, modify, merge, publish, | |
// distribute, sublicense, and/or sell copies of the Software, and to permit | |
// persons to whom the Software is furnished to do so, subject to the | |
// following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included | |
// in all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN | |
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | |
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | |
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE | |
// USE OR OTHER DEALINGS IN THE SOFTWARE. | |
var isArray = Array.isArray; | |
function EventEmitter() { } | |
exports.EventEmitter = EventEmitter; | |
// By default EventEmitters will print a warning if more than | |
// 10 listeners are added to it. This is a useful default which | |
// helps finding memory leaks. | |
// | |
// Obviously not all Emitters should be limited to 10. This function allows | |
// that to be increased. Set to zero for unlimited. | |
var defaultMaxListeners = 10; | |
EventEmitter.prototype.setMaxListeners = function(n) { | |
if (!this._events) { this._events = {}; } | |
this._maxListeners = n; | |
}; | |
EventEmitter.prototype.emit = function() { | |
var type = arguments[0]; | |
if (!this._events) { return false; } | |
var handler = this._events[type]; | |
if (!handler) { return false; } | |
var args, l, i; | |
if (typeof handler === 'function') { | |
switch (arguments.length) { | |
// fast cases | |
case 1: | |
handler.call(this); | |
break; | |
case 2: | |
handler.call(this, arguments[1]); | |
break; | |
case 3: | |
handler.call(this, arguments[1], arguments[2]); | |
break; | |
// slower | |
default: | |
l = arguments.length; | |
args = new Array(l - 1); | |
for (i = 1; i < l; i++) { args[i - 1] = arguments[i]; } | |
handler.apply(this, args); | |
} | |
return true; | |
} else if (isArray(handler)) { | |
l = arguments.length; | |
args = new Array(l - 1); | |
for (i = 1; i < l; i++) { args[i - 1] = arguments[i]; } | |
var listeners = handler.slice(); | |
for (i = 0, l = listeners.length; i < l; i++) { | |
listeners[i].apply(this, args); | |
} | |
return true; | |
} else { | |
return false; | |
} | |
}; | |
// EventEmitter is defined in src/node_events.cc | |
// EventEmitter.prototype.emit() is also defined there. | |
EventEmitter.prototype.addListener = function(type, listener) { | |
if ('function' !== typeof listener) { | |
throw new Error('addListener only takes instances of Function'); | |
} | |
if (!this._events) { this._events = {}; } | |
// To avoid recursion in the case that type === "newListeners"! Before | |
// adding it to the listeners, first emit "newListeners". | |
this.emit('newListener', type, listener); | |
if (!this._events[type]) { | |
// Optimize the case of one listener. Don't need the extra array object. | |
this._events[type] = listener; | |
} else if (isArray(this._events[type])) { | |
// If we've already got an array, just append. | |
this._events[type].push(listener); | |
// Check for listener leak | |
if (!this._events[type].warned) { | |
var m; | |
if (this._maxListeners !== undefined) { | |
m = this._maxListeners; | |
} else { | |
m = defaultMaxListeners; | |
} | |
if (m && m > 0 && this._events[type].length > m) { | |
this._events[type].warned = true; | |
console.error('(node) warning: possible EventEmitter memory ' + | |
'leak detected. %d listeners added. ' + | |
'Use emitter.setMaxListeners() to increase limit.', | |
this._events[type].length); | |
console.trace(); | |
} | |
} | |
} else { | |
// Adding the second element, need to change to array. | |
this._events[type] = [this._events[type], listener]; | |
} | |
return this; | |
}; | |
EventEmitter.prototype.on = EventEmitter.prototype.addListener; | |
EventEmitter.prototype.once = function(type, listener) { | |
if ('function' !== typeof listener) { | |
throw new Error('.once only takes instances of Function'); | |
} | |
var self = this; | |
function g() { | |
self.removeListener(type, g); | |
listener.apply(this, arguments); | |
} | |
g.listener = listener; | |
self.on(type, g); | |
return this; | |
}; | |
EventEmitter.prototype.removeListener = function(type, listener) { | |
if ('function' !== typeof listener) { | |
throw new Error('removeListener only takes instances of Function'); | |
} | |
// does not use listeners(), so no side effect of creating _events[type] | |
if (!this._events || !this._events[type]) { return this; } | |
var list = this._events[type]; | |
if (isArray(list)) { | |
var i, position = -1; | |
for (i = 0, length = list.length; i < length; i++) { | |
if (list[i] === listener || | |
(list[i].listener && list[i].listener === listener)) | |
{ | |
position = i; | |
break; | |
} | |
} | |
if (position < 0) { return this; } | |
list.splice(position, 1); | |
if (list.length === 0) { | |
delete this._events[type]; | |
} | |
} else if (list === listener || | |
(list.listener && list.listener === listener)) | |
{ | |
delete this._events[type]; | |
} | |
return this; | |
}; | |
EventEmitter.prototype.removeAllListeners = function(type) { | |
if (arguments.length === 0) { | |
this._events = {}; | |
return this; | |
} | |
// does not use listeners(), so no side effect of creating _events[type] | |
if (type && this._events && this._events[type]) { this._events[type] = null; } | |
return this; | |
}; | |
EventEmitter.prototype.listeners = function(type) { | |
if (!this._events) { this._events = {}; } | |
if (!this._events[type]) { this._events[type] = []; } | |
if (!isArray(this._events[type])) { | |
this._events[type] = [this._events[type]]; | |
} | |
return this._events[type]; | |
}; | |
return exports;}()); | |
var debug = function(str) { | |
Ti.API.debug(str); | |
}; | |
var CONNECTING = 0; | |
var OPEN = 1; | |
var CLOSING = 2; | |
var CLOSED = 3; | |
var BUFFER_SIZE = 65536; | |
var CLOSING_TIMEOUT = 1000; | |
var WebSocket = function(url, protocols, origin, extensions) { | |
this.url = url; | |
if(!this._parse_url()) { | |
throw "Wrong url scheme for WebSocket: " + this.url; | |
} | |
this.origin = origin || String.format("http://%s:%s/", this._host, this._port); | |
this.protocols = protocols; | |
this.extensions = extensions; | |
this.readyState = CONNECTING; | |
this._masking_disabled = false; | |
this._headers = []; | |
this._pong_received = false; | |
this._readBuffer = ''; | |
this._socketReadBuffer = undefined; | |
this._closingTimer = undefined; | |
this._handshake = undefined; | |
this._socket = undefined; | |
this._connect(); | |
}; | |
exports.WebSocket = WebSocket; | |
WebSocket.prototype = new events.EventEmitter(); | |
WebSocket.prototype.onopen = function() { | |
// NO OP | |
}; | |
WebSocket.prototype.onmessage = function() { | |
// NO OP | |
}; | |
WebSocket.prototype.onerror = function() { | |
// NO OP | |
}; | |
WebSocket.prototype.onclose = function() { | |
// NO OP | |
}; | |
WebSocket.prototype._parse_url = function() { | |
var parsed = this.url.match(/^([a-z]+):\/\/([\w.]+)(:(\d+)|)(.*)/i); | |
if(!parsed || parsed[1] !== 'ws') { | |
return false; | |
} | |
this._host = parsed[2]; | |
this._port = parsed[4] || 80; | |
this._path = parsed[5]; | |
return true; | |
}; | |
var make_handshake_key = function() { | |
var i, key = ""; | |
for(i=0; i<16; ++i) { | |
key += String.fromCharCode(Math.random()*255+1); | |
} | |
return Utils.trim(Ti.Utils.base64encode(key)); | |
}; | |
var make_handshake = function(host, path, origin, protocols, extensions, handshake) { | |
str = "GET " + path + " HTTP/1.1\r\n"; | |
str += "Host: " + host + "\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n"; | |
str += "Sec-WebSocket-Key: " + handshake + "\r\n"; | |
str += "Origin: " + origin + "\r\n"; | |
str += "Sec-WebSocket-Origin: " + origin + "\r\n"; | |
str += "Sec-WebSocket-Version: 7\r\n"; | |
if(protocols && protocols.length > 0) { | |
str += "Sec-WebSocket-Protocol: " + protocols.join(',') + "\r\n"; | |
} | |
if(extensions && extensions.length > 0) { | |
str += "Sec-WebSocket-Extensions: " + extensions.join(',') + "\r\n"; | |
} | |
// TODO: compression | |
//if @compression | |
// extensions << "deflate-application-data" | |
//end | |
return str + "\r\n"; | |
}; | |
WebSocket.prototype._send_handshake = function() { | |
this._handshake = make_handshake_key(); | |
var handshake = make_handshake(this._host, this._path, this.origin, this.protocols, this.extensions, this._handshake); | |
return this._socket.write(Ti.createBuffer({ value: handshake })) > 0; | |
}; | |
WebSocket.prototype._read_http_headers = function() { | |
var string = ""; | |
var buffer = Ti.createBuffer({ length: BUFFER_SIZE }); | |
var counter = 10; | |
while(true) { | |
var bytesRead = this._socket.read(buffer); | |
if(bytesRead > 0) { | |
var lastStringLen = string.length; | |
string += Ti.Codec.decodeString({ | |
source: buffer, | |
charset: Ti.Codec.CHARSET_ASCII | |
}); | |
var eoh = string.match(/\r\n\r\n/); | |
if(eoh) { | |
var offset = (eoh.index + 4) - lastStringLen; | |
string = string.substring(0, offset-2); | |
this.buffer = Ti.createBuffer({ length: BUFFER_SIZE }); | |
this.bufferSize = bytesRead - offset; | |
this.buffer.copy(buffer, 0, offset, this.bufferSize); | |
break; | |
} | |
} | |
else { | |
debug("read_http_headers: timeout"); | |
--counter; | |
if(counter < 0) { | |
return false; // Timeout | |
} | |
} | |
buffer.clear(); // clear the buffer before the next read | |
} | |
buffer.clear(); | |
this.headers = string.split("\r\n"); | |
return true; | |
}; | |
var extract_headers = function(headers) { | |
var result = {}; | |
headers.forEach(function(line) { | |
var index = line.indexOf(":"); | |
if(index > 0) { | |
var key = Utils.trim(line.slice(0, index)); | |
var value = Utils.trim(line.slice(index + 1)); | |
result[key] = value; | |
} | |
}); | |
return result; | |
}; | |
var handshake_reponse = function(handshake) { | |
return (new SHA1(handshake + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")).base64digest(); | |
}; | |
WebSocket.prototype._check_handshake_response = function() { | |
var version = this.headers.shift(); | |
if(version !== "HTTP/1.1 101 Switching Protocols") { | |
// Mismatch protocol version | |
debug("mismatch protocol version"); | |
return false; | |
} | |
var h = extract_headers(this.headers); | |
if(!h.Upgrade || !h.Connection || !h['Sec-WebSocket-Accept']) { | |
return false; | |
} | |
if(h.Upgrade.toLowerCase() !== 'websocket' || h.Connection.toLowerCase() !== 'upgrade' || h['Sec-WebSocket-Accept'] !== handshake_reponse(this._handshake)) { | |
return false; | |
} | |
// TODO: compression | |
// if h.has_key?('Sec-WebSocket-Extensions') and h['Sec-WebSocket-Extensions'] === 'deflate-application-data' | |
// if @compression | |
// @zout = Zlib::Deflate.new(Zlib::BEST_SPEED, Zlib::MAX_WBITS, 8, 1) | |
// @zin = Zlib::Inflate.new | |
// end | |
// else | |
// @compression = false | |
// end | |
this.readyState = OPEN; | |
return true; | |
}; | |
WebSocket.prototype._create_frame = function(opcode, d, last_frame) { | |
if(typeof last_frame === 'undefined') { | |
last_frame = true; | |
} | |
if(last_frame === false && opcode >= 0x8 && opcode <= 0xf) { | |
return false; | |
} | |
var data = d || ''; //compress(d) // TODO | |
var length = Utils.byte_length(data); | |
var header_length = 2; | |
var mask_size = 6; | |
if(125 < length && length <= BUFFER_SIZE){ | |
header_length += 2; | |
} else if(BUFFER_SIZE < length){ | |
header_length += 8; | |
} | |
if(!this._masking_disabled){ | |
header_length += 4; | |
} | |
// apply per frame compression | |
var out = Ti.createBuffer({ length: length + header_length + mask_size }); | |
var outIndex = 0; | |
var byte1 = opcode; | |
if(last_frame) { | |
byte1 = byte1 | 0x80; | |
} | |
Ti.Codec.encodeNumber({ | |
source: byte1, | |
dest: out, | |
position: outIndex++, | |
type: Ti.Codec.TYPE_BYTE | |
}); | |
if(length <= 125) { | |
var byte2 = length; | |
if(!this._masking_disabled) { | |
byte2 = (byte2 | 0x80); // # set masking bit | |
} | |
Ti.Codec.encodeNumber({ | |
source: byte2, | |
dest: out, | |
position: outIndex++, | |
type: Ti.Codec.TYPE_BYTE | |
}); | |
} | |
/* | |
else if(length < BUFFER_SIZE) { // # write 2 byte length | |
Ti.Codec.encodeNumber({ | |
source: (126 | 0x80), | |
dest: out, | |
position: outIndex++, | |
type: Ti.Codec.TYPE_BYTE | |
}); | |
Ti.Codec.encodeNumber({ | |
source: length, | |
dest: out, | |
position: outIndex++, | |
type: Ti.Codec.TYPE_SHORT, | |
byteOrder: Ti.Codec.BIG_ENDIAN | |
}); | |
outIndex += 2; | |
} | |
*/ | |
else { // # write 8 byte length | |
Ti.Codec.encodeNumber({ | |
source: (127 | 0x80), | |
dest: out, | |
position: outIndex++, | |
type: Ti.Codec.TYPE_BYTE | |
}); | |
Ti.Codec.encodeNumber({ | |
source: length, | |
dest: out, | |
position: outIndex, | |
type: Ti.Codec.TYPE_LONG, | |
byteOrder: Ti.Codec.BIG_ENDIAN | |
}); | |
outIndex += 8; | |
} | |
//# mask data | |
outIndex = this._mask_payload(out, outIndex, data); | |
out.length = outIndex; | |
return out; | |
}; | |
WebSocket.prototype._mask_payload = function(out, outIndex, payload) { | |
if(!this._masking_disabled) { | |
var i, masking_key = []; | |
for(i = 0; i < 4; ++i) { | |
var key = Math.floor(Math.random()*255) & 0xff; | |
masking_key.push(key); | |
Ti.Codec.encodeNumber({ | |
source: key, | |
dest: out, | |
position: outIndex++, | |
type: Ti.Codec.TYPE_BYTE | |
}); | |
} | |
var buffer = Ti.createBuffer({ length: BUFFER_SIZE }); | |
var length = Ti.Codec.encodeString({ | |
source: payload, | |
dest: buffer | |
}); | |
buffer.length = length; | |
var string = Ti.Codec.decodeString({ | |
source: buffer, | |
charset: Ti.Codec.CHARSET_ASCII | |
}); | |
if(out.length < length){ | |
out.length = string.length; | |
} | |
for(i = 0; i < string.length; ++i) { | |
Ti.Codec.encodeNumber({ | |
source: string.charCodeAt(i) ^ masking_key[i % 4], | |
dest: out, | |
position: outIndex++, | |
type: Ti.Codec.TYPE_BYTE | |
}); | |
} | |
return outIndex; | |
} | |
else { | |
var len = Ti.Codec.encodeString({ | |
source: payload, | |
dest: out, | |
destPosition: outIndex | |
}); | |
return len + outIndex; | |
} | |
}; | |
var parse_frame = function(buffer, size) { | |
if(size < 3) { | |
return undefined; | |
} | |
var byte1 = Utils.read_byte(buffer, 0); | |
var fin = !!(byte1 & 0x80); | |
var opcode = byte1 & 0x0f; | |
var byte2 = Utils.read_byte(buffer, 1); | |
var mask = !!(byte2 & 0x80); | |
var len = byte2 & 0x7f; | |
var offset = 2; | |
switch(len) { | |
case 126: | |
len = Utils.read_2byte(buffer, offset); | |
offset += 2; | |
break; | |
case 127: | |
// too large I felt | |
len = Utils.read_8byte(buffer, offset); | |
offset += 8; | |
break; | |
} | |
if(len + offset > size) { | |
return undefined; | |
} | |
var string = Ti.Codec.decodeString({ | |
source: buffer, | |
position: offset, | |
length: len, | |
charset: Ti.Codec.CHARSET_UTF8 | |
}); | |
return({fin: fin, opcode: opcode, payload: string, size: len + offset}); | |
}; | |
WebSocket.prototype.send = function(data) { | |
if(data && this.readyState === OPEN) { | |
var buffer = Ti.createBuffer({ value: data }); | |
var string = Ti.Codec.decodeString({ | |
source: buffer, | |
charset: Ti.Codec.CHARSET_ASCII | |
}); | |
var stringLength = string.length; | |
if(stringLength < BUFFER_SIZE){ | |
var frame = this._create_frame(0x01, string); | |
var bytesWritten = this._socket.write(frame); | |
return 0 < bytesWritten; | |
} | |
// | |
// fragment message | |
// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07#section-4.7 | |
// | |
var isFirstFragment = true; | |
var offset = 0; | |
while(offset < stringLength){ | |
if(stringLength < (offset + BUFFER_SIZE)){ | |
break; | |
} | |
fragment = string.substring(offset, BUFFER_SIZE); | |
if(fragment.length < 1){ | |
return true; | |
} | |
// opcode:: fragment(0x80), text(0x01) | |
var opcode = 0x80; | |
if(isFirstFragment){ | |
opcode = 0x01; | |
isFirstFragment = false; | |
} | |
frame = this._create_frame(opcode, fragment, false); | |
if(this._socket.write(frame) < 1){ | |
return false; | |
} | |
offset += BUFFER_SIZE; | |
} | |
fragment = string.substring(offset, stringLength); | |
if(fragment.length < 1){ | |
return true; | |
} | |
// last frame | |
frame = this._create_frame(0x80, fragment, true); | |
if(this._socket.write(frame) < 1){ | |
return false; | |
} | |
return true; | |
} | |
else { | |
return false; | |
} | |
}; | |
WebSocket.prototype._socket_close = function() { | |
if(this._closingTimer) { | |
clearTimeout(this._closingTimer); | |
} | |
this._closingTimer = undefined; | |
this._readBuffer = ''; | |
this._socketReadBuffer = undefined; | |
var ev; | |
if(this.readyState === CLOSING) { | |
this.readyState = CLOSED; | |
this._socket.close(); | |
ev = { | |
code: 1000, | |
wasClean: true, | |
reason: "" | |
}; | |
this.emit("close", ev); | |
this.onclose(ev); | |
} | |
else if(this.readyState !== CLOSED) { | |
this._socket.close(); | |
this.readyState = CLOSED; | |
ev = { | |
advice: "reconnect" | |
}; | |
this.emit("error", ev); | |
this.onerror(ev); | |
} | |
this._socket = undefined; | |
}; | |
WebSocket.prototype._read_callback = function(e) { | |
var self = this; | |
var nextTick = function() { | |
self._socketReadBuffer.clear(); | |
Ti.Stream.read(self._socket, self._socketReadBuffer, function(e) { self._read_callback(e); }); | |
}; | |
if('undefined' !== typeof e) { | |
if (0 === e.bytesProcessed) { | |
return nextTick(); | |
} | |
if(-1 === e.bytesProcessed) { // EOF | |
this._socket_close(); | |
return undefined; | |
} | |
if('undefined' === typeof this.buffer) { | |
this.buffer = this._socketReadBuffer.clone(); | |
this.bufferSize = e.bytesProcessed; | |
} | |
else { | |
this.buffer.copy(this._socketReadBuffer, this.bufferSize, 0, e.bytesProcessed); | |
this.bufferSize += e.bytesProcessed; | |
this.buffer.length += e.bytesProcessed; | |
this._socketReadBuffer.clear(); | |
} | |
} | |
var frame = parse_frame(this.buffer, this.bufferSize); | |
if('undefined' === typeof frame) { | |
return nextTick(); | |
} | |
else { | |
if(frame.size < this.bufferSize) { | |
var nextBuffer = Ti.createBuffer({ length: BUFFER_SIZE }); | |
if(this.bufferSize - frame.size > 0) { | |
nextBuffer.copy(this.buffer, 0, frame.size, this.bufferSize - frame.size); | |
} | |
this.buffer.clear(); | |
this.buffer = nextBuffer; | |
this.bufferSize -= frame.size; | |
} | |
else { | |
this.buffer.clear(); | |
this.bufferSize = 0; | |
} | |
switch(frame.opcode) { | |
case 0x00: // continuation frame | |
case 0x01: // text frame | |
case 0x02: // binary frame | |
if(frame.fin) { | |
this.emit("message", {data: this._readBuffer + frame.payload}); | |
this.onmessage({data: this._readBuffer + frame.payload}); | |
this._readBuffer = ''; | |
} | |
else { | |
this._readBuffer += frame.payload; | |
} | |
break; | |
case 0x08: // connection close | |
if(this.readyState === CLOSING) { | |
this._socket_close(); | |
} | |
else { | |
this.readyState = CLOSING; | |
this._socket.write(this._create_frame(0x08)); | |
this._closingTimer = setTimeout(function() { | |
self._socket_close(); | |
}, CLOSING_TIMEOUT); | |
} | |
break; | |
case 0x09: // ping | |
this._socket.write(this._create_frame(0x0a, frame.payload)); | |
break; | |
case 0x0a: // pong | |
this._pong_received = true; | |
break; | |
} | |
this._read_callback(); | |
} | |
}; | |
WebSocket.prototype._error = function(code, reason) { | |
if(this.buffer) { | |
this.buffer.clear(); | |
} | |
this.buffer = undefined; | |
this.bufferSize = 0; | |
this.readyState = CLOSED; | |
if(this._socket) { | |
try { | |
this._socket.close(); | |
} | |
catch(e) { } | |
this._socket = undefined; | |
} | |
var ev = { | |
wasClean: true, | |
code: ('undefined' === typeof code) ? 1000 : code, | |
advice: "reconnect", | |
reason: reason | |
}; | |
this.emit("error", ev); | |
this.onerror(ev); | |
}; | |
WebSocket.prototype._raise_protocol_error = function(reason) { | |
this._error(1002, reason); | |
}; | |
WebSocket.prototype.close = function(code, message) { | |
if(this.readyState === OPEN) { | |
this.readyState = CLOSING; | |
var buffer = Ti.createBuffer({ length: BUFFER_SIZE }); | |
Ti.Codec.encodeNumber({ | |
source: code || 1000, | |
dest: buffer, | |
position: 0, | |
type: Ti.Codec.TYPE_SHORT, | |
byteOrder: Ti.Codec.BIG_ENDIAN | |
}); | |
if(message) { | |
var length = Ti.Codec.encodeString({ | |
source: message, | |
dest: buffer, | |
destPosition: 2 | |
}); | |
buffer.length = 2 + length; | |
} | |
else { | |
buffer.length = 2; | |
} | |
var payload = Ti.Codec.decodeString({ | |
source: buffer, | |
charset: Ti.Codec.CHARSET_ASCII | |
}); | |
this._socket.write(this._create_frame(0x08, payload)); | |
var self = this; | |
this._closingTimer = setTimeout(function() { | |
self._socket_close(); | |
}, CLOSING_TIMEOUT); | |
} | |
}; | |
WebSocket.prototype._connect = function() { | |
if(this.readyState === OPEN || this.readyState === CLOSING) { | |
return false; | |
} | |
var self = this; | |
this._socket = Ti.Network.Socket.createTCP({ | |
host: this._host, | |
port: this._port, | |
mode: Ti.Network.READ_WRITE_MODE, | |
connected: function(e) { | |
var result; | |
result = self._send_handshake(); | |
if(!result) { | |
return self._raise_protocol_error("send handshake"); | |
} | |
result = self._read_http_headers(); | |
if(!result) { | |
return self._raise_protocol_error("parse http header"); | |
} | |
result = self._check_handshake_response(); | |
if(!result) { | |
return self._raise_protocol_error("wrong handshake"); | |
} | |
self._readBuffer = ''; | |
self._socketReadBuffer = Ti.createBuffer({ length: BUFFER_SIZE }); | |
self.readyState = OPEN; | |
self.emit("open"); | |
self.onopen(); | |
self._read_callback(); | |
}, | |
closed: function() { | |
self._socket_close(); | |
if(self.buffer) { | |
self.buffer.clear(); | |
} | |
self.buffer = undefined; | |
self.bufferSize = 0; | |
}, | |
error: function(e) { | |
var reason; | |
if('undefined' !== typeof e) { | |
reason = e.error; | |
} | |
self._error(1000, reason); | |
} | |
}); | |
this._socket.connect(); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello!
thanks for this code, i'm currently using in on my app. i've just found a problem, when building the app with "Ad hoc" certificate or distribution, the app crashes, due to a erron on socket.io class.
i've found this error on similar code ( nowelium/socket.io-titanium#10 ) and this is also present on your code,
i've just modified line 1311
from: +new Date
to: new Date().getTime()
this solve the problem, at least my app don crashes now