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