rc-web@69: rc-web@69: /*! rc-web@69: * socket.io-node rc-web@69: * Copyright(c) 2011 LearnBoost rc-web@69: * MIT Licensed rc-web@69: */ rc-web@69: rc-web@69: /** rc-web@69: * Module dependencies. rc-web@69: */ rc-web@69: rc-web@69: /** rc-web@69: * Packet types. rc-web@69: */ rc-web@69: rc-web@69: var packets = exports.packets = { rc-web@69: 'disconnect': 0 rc-web@69: , 'connect': 1 rc-web@69: , 'heartbeat': 2 rc-web@69: , 'message': 3 rc-web@69: , 'json': 4 rc-web@69: , 'event': 5 rc-web@69: , 'ack': 6 rc-web@69: , 'error': 7 rc-web@69: , 'noop': 8 rc-web@69: } rc-web@69: , packetslist = Object.keys(packets); rc-web@69: rc-web@69: /** rc-web@69: * Errors reasons. rc-web@69: */ rc-web@69: rc-web@69: var reasons = exports.reasons = { rc-web@69: 'transport not supported': 0 rc-web@69: , 'client not handshaken': 1 rc-web@69: , 'unauthorized': 2 rc-web@69: } rc-web@69: , reasonslist = Object.keys(reasons); rc-web@69: rc-web@69: /** rc-web@69: * Errors advice. rc-web@69: */ rc-web@69: rc-web@69: var advice = exports.advice = { rc-web@69: 'reconnect': 0 rc-web@69: } rc-web@69: , advicelist = Object.keys(advice); rc-web@69: rc-web@69: /** rc-web@69: * Encodes a packet. rc-web@69: * rc-web@69: * @api private rc-web@69: */ rc-web@69: rc-web@69: exports.encodePacket = function (packet) { rc-web@69: var type = packets[packet.type] rc-web@69: , id = packet.id || '' rc-web@69: , endpoint = packet.endpoint || '' rc-web@69: , ack = packet.ack rc-web@69: , data = null; rc-web@69: rc-web@69: switch (packet.type) { rc-web@69: case 'message': rc-web@69: if (packet.data !== '') rc-web@69: data = packet.data; rc-web@69: break; rc-web@69: rc-web@69: case 'event': rc-web@69: var ev = { name: packet.name }; rc-web@69: rc-web@69: if (packet.args && packet.args.length) { rc-web@69: ev.args = packet.args; rc-web@69: } rc-web@69: rc-web@69: data = JSON.stringify(ev); rc-web@69: break; rc-web@69: rc-web@69: case 'json': rc-web@69: data = JSON.stringify(packet.data); rc-web@69: break; rc-web@69: rc-web@69: case 'ack': rc-web@69: data = packet.ackId rc-web@69: + (packet.args && packet.args.length rc-web@69: ? '+' + JSON.stringify(packet.args) : ''); rc-web@69: break; rc-web@69: rc-web@69: case 'connect': rc-web@69: if (packet.qs) rc-web@69: data = packet.qs; rc-web@69: break; rc-web@69: rc-web@69: case 'error': rc-web@69: var reason = packet.reason ? reasons[packet.reason] : '' rc-web@69: , adv = packet.advice ? advice[packet.advice] : '' rc-web@69: rc-web@69: if (reason !== '' || adv !== '') rc-web@69: data = reason + (adv !== '' ? ('+' + adv) : '') rc-web@69: rc-web@69: break; rc-web@69: } rc-web@69: rc-web@69: // construct packet with required fragments rc-web@69: var encoded = type + ':' + id + (ack == 'data' ? '+' : '') + ':' + endpoint; rc-web@69: rc-web@69: // data fragment is optional rc-web@69: if (data !== null && data !== undefined) rc-web@69: encoded += ':' + data; rc-web@69: rc-web@69: return encoded; rc-web@69: }; rc-web@69: rc-web@69: /** rc-web@69: * Encodes multiple messages (payload). rc-web@69: * rc-web@69: * @param {Array} messages rc-web@69: * @api private rc-web@69: */ rc-web@69: rc-web@69: exports.encodePayload = function (packets) { rc-web@69: var decoded = ''; rc-web@69: rc-web@69: if (packets.length == 1) rc-web@69: return packets[0]; rc-web@69: rc-web@69: for (var i = 0, l = packets.length; i < l; i++) { rc-web@69: var packet = packets[i]; rc-web@69: decoded += '\ufffd' + packet.length + '\ufffd' + packets[i] rc-web@69: } rc-web@69: rc-web@69: return decoded; rc-web@69: }; rc-web@69: rc-web@69: /** rc-web@69: * Decodes a packet rc-web@69: * rc-web@69: * @api private rc-web@69: */ rc-web@69: rc-web@69: var regexp = /([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/; rc-web@69: rc-web@69: /** rc-web@69: * Wrap the JSON.parse in a seperate function the crankshaft optimizer will rc-web@69: * only punish this function for the usage for try catch rc-web@69: * rc-web@69: * @api private rc-web@69: */ rc-web@69: rc-web@69: function parse (data) { rc-web@69: try { return JSON.parse(data) } rc-web@69: catch (e) { return false } rc-web@69: } rc-web@69: rc-web@69: exports.decodePacket = function (data) { rc-web@69: var pieces = data.match(regexp); rc-web@69: rc-web@69: if (!pieces) return {}; rc-web@69: rc-web@69: var id = pieces[2] || '' rc-web@69: , data = pieces[5] || '' rc-web@69: , packet = { rc-web@69: type: packetslist[pieces[1]] rc-web@69: , endpoint: pieces[4] || '' rc-web@69: }; rc-web@69: rc-web@69: // whether we need to acknowledge the packet rc-web@69: if (id) { rc-web@69: packet.id = id; rc-web@69: if (pieces[3]) rc-web@69: packet.ack = 'data'; rc-web@69: else rc-web@69: packet.ack = true; rc-web@69: } rc-web@69: rc-web@69: // handle different packet types rc-web@69: switch (packet.type) { rc-web@69: case 'message': rc-web@69: packet.data = data || ''; rc-web@69: break; rc-web@69: rc-web@69: case 'event': rc-web@69: pieces = parse(data); rc-web@69: if (pieces) { rc-web@69: packet.name = pieces.name; rc-web@69: packet.args = pieces.args; rc-web@69: } rc-web@69: rc-web@69: packet.args = packet.args || []; rc-web@69: break; rc-web@69: rc-web@69: case 'json': rc-web@69: packet.data = parse(data); rc-web@69: break; rc-web@69: rc-web@69: case 'connect': rc-web@69: packet.qs = data || ''; rc-web@69: break; rc-web@69: rc-web@69: case 'ack': rc-web@69: pieces = data.match(/^([0-9]+)(\+)?(.*)/); rc-web@69: if (pieces) { rc-web@69: packet.ackId = pieces[1]; rc-web@69: packet.args = []; rc-web@69: rc-web@69: if (pieces[3]) { rc-web@69: packet.args = parse(pieces[3]) || []; rc-web@69: } rc-web@69: } rc-web@69: break; rc-web@69: rc-web@69: case 'error': rc-web@69: pieces = data.split('+'); rc-web@69: packet.reason = reasonslist[pieces[0]] || ''; rc-web@69: packet.advice = advicelist[pieces[1]] || ''; rc-web@69: } rc-web@69: rc-web@69: return packet; rc-web@69: }; rc-web@69: rc-web@69: /** rc-web@69: * Decodes data payload. Detects multiple messages rc-web@69: * rc-web@69: * @return {Array} messages rc-web@69: * @api public rc-web@69: */ rc-web@69: rc-web@69: exports.decodePayload = function (data) { rc-web@69: if (undefined == data || null == data) { rc-web@69: return []; rc-web@69: } rc-web@69: rc-web@69: if (data[0] == '\ufffd') { rc-web@69: var ret = []; rc-web@69: rc-web@69: for (var i = 1, length = ''; i < data.length; i++) { rc-web@69: if (data[i] == '\ufffd') { rc-web@69: ret.push(exports.decodePacket(data.substr(i + 1, length))); rc-web@69: i += Number(length) + 1; rc-web@69: length = ''; rc-web@69: } else { rc-web@69: length += data[i]; rc-web@69: } rc-web@69: } rc-web@69: rc-web@69: return ret; rc-web@69: } else { rc-web@69: return [exports.decodePacket(data)]; rc-web@69: } rc-web@69: };