twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

index.js (8041B)


      1 
      2 /**
      3  * Module dependencies.
      4  */
      5 
      6 var debug = require('debug')('socket.io-parser');
      7 var Emitter = require('component-emitter');
      8 var binary = require('./binary');
      9 var isArray = require('isarray');
     10 var isBuf = require('./is-buffer');
     11 
     12 /**
     13  * Protocol version.
     14  *
     15  * @api public
     16  */
     17 
     18 exports.protocol = 4;
     19 
     20 /**
     21  * Packet types.
     22  *
     23  * @api public
     24  */
     25 
     26 exports.types = [
     27   'CONNECT',
     28   'DISCONNECT',
     29   'EVENT',
     30   'ACK',
     31   'ERROR',
     32   'BINARY_EVENT',
     33   'BINARY_ACK'
     34 ];
     35 
     36 /**
     37  * Packet type `connect`.
     38  *
     39  * @api public
     40  */
     41 
     42 exports.CONNECT = 0;
     43 
     44 /**
     45  * Packet type `disconnect`.
     46  *
     47  * @api public
     48  */
     49 
     50 exports.DISCONNECT = 1;
     51 
     52 /**
     53  * Packet type `event`.
     54  *
     55  * @api public
     56  */
     57 
     58 exports.EVENT = 2;
     59 
     60 /**
     61  * Packet type `ack`.
     62  *
     63  * @api public
     64  */
     65 
     66 exports.ACK = 3;
     67 
     68 /**
     69  * Packet type `error`.
     70  *
     71  * @api public
     72  */
     73 
     74 exports.ERROR = 4;
     75 
     76 /**
     77  * Packet type 'binary event'
     78  *
     79  * @api public
     80  */
     81 
     82 exports.BINARY_EVENT = 5;
     83 
     84 /**
     85  * Packet type `binary ack`. For acks with binary arguments.
     86  *
     87  * @api public
     88  */
     89 
     90 exports.BINARY_ACK = 6;
     91 
     92 /**
     93  * Encoder constructor.
     94  *
     95  * @api public
     96  */
     97 
     98 exports.Encoder = Encoder;
     99 
    100 /**
    101  * Decoder constructor.
    102  *
    103  * @api public
    104  */
    105 
    106 exports.Decoder = Decoder;
    107 
    108 /**
    109  * A socket.io Encoder instance
    110  *
    111  * @api public
    112  */
    113 
    114 function Encoder() {}
    115 
    116 var ERROR_PACKET = exports.ERROR + '"encode error"';
    117 
    118 /**
    119  * Encode a packet as a single string if non-binary, or as a
    120  * buffer sequence, depending on packet type.
    121  *
    122  * @param {Object} obj - packet object
    123  * @param {Function} callback - function to handle encodings (likely engine.write)
    124  * @return Calls callback with Array of encodings
    125  * @api public
    126  */
    127 
    128 Encoder.prototype.encode = function(obj, callback){
    129   debug('encoding packet %j', obj);
    130 
    131   if (exports.BINARY_EVENT === obj.type || exports.BINARY_ACK === obj.type) {
    132     encodeAsBinary(obj, callback);
    133   } else {
    134     var encoding = encodeAsString(obj);
    135     callback([encoding]);
    136   }
    137 };
    138 
    139 /**
    140  * Encode packet as string.
    141  *
    142  * @param {Object} packet
    143  * @return {String} encoded
    144  * @api private
    145  */
    146 
    147 function encodeAsString(obj) {
    148 
    149   // first is type
    150   var str = '' + obj.type;
    151 
    152   // attachments if we have them
    153   if (exports.BINARY_EVENT === obj.type || exports.BINARY_ACK === obj.type) {
    154     str += obj.attachments + '-';
    155   }
    156 
    157   // if we have a namespace other than `/`
    158   // we append it followed by a comma `,`
    159   if (obj.nsp && '/' !== obj.nsp) {
    160     str += obj.nsp + ',';
    161   }
    162 
    163   // immediately followed by the id
    164   if (null != obj.id) {
    165     str += obj.id;
    166   }
    167 
    168   // json data
    169   if (null != obj.data) {
    170     var payload = tryStringify(obj.data);
    171     if (payload !== false) {
    172       str += payload;
    173     } else {
    174       return ERROR_PACKET;
    175     }
    176   }
    177 
    178   debug('encoded %j as %s', obj, str);
    179   return str;
    180 }
    181 
    182 function tryStringify(str) {
    183   try {
    184     return JSON.stringify(str);
    185   } catch(e){
    186     return false;
    187   }
    188 }
    189 
    190 /**
    191  * Encode packet as 'buffer sequence' by removing blobs, and
    192  * deconstructing packet into object with placeholders and
    193  * a list of buffers.
    194  *
    195  * @param {Object} packet
    196  * @return {Buffer} encoded
    197  * @api private
    198  */
    199 
    200 function encodeAsBinary(obj, callback) {
    201 
    202   function writeEncoding(bloblessData) {
    203     var deconstruction = binary.deconstructPacket(bloblessData);
    204     var pack = encodeAsString(deconstruction.packet);
    205     var buffers = deconstruction.buffers;
    206 
    207     buffers.unshift(pack); // add packet info to beginning of data list
    208     callback(buffers); // write all the buffers
    209   }
    210 
    211   binary.removeBlobs(obj, writeEncoding);
    212 }
    213 
    214 /**
    215  * A socket.io Decoder instance
    216  *
    217  * @return {Object} decoder
    218  * @api public
    219  */
    220 
    221 function Decoder() {
    222   this.reconstructor = null;
    223 }
    224 
    225 /**
    226  * Mix in `Emitter` with Decoder.
    227  */
    228 
    229 Emitter(Decoder.prototype);
    230 
    231 /**
    232  * Decodes an encoded packet string into packet JSON.
    233  *
    234  * @param {String} obj - encoded packet
    235  * @return {Object} packet
    236  * @api public
    237  */
    238 
    239 Decoder.prototype.add = function(obj) {
    240   var packet;
    241   if (typeof obj === 'string') {
    242     packet = decodeString(obj);
    243     if (exports.BINARY_EVENT === packet.type || exports.BINARY_ACK === packet.type) { // binary packet's json
    244       this.reconstructor = new BinaryReconstructor(packet);
    245 
    246       // no attachments, labeled binary but no binary data to follow
    247       if (this.reconstructor.reconPack.attachments === 0) {
    248         this.emit('decoded', packet);
    249       }
    250     } else { // non-binary full packet
    251       this.emit('decoded', packet);
    252     }
    253   } else if (isBuf(obj) || obj.base64) { // raw binary data
    254     if (!this.reconstructor) {
    255       throw new Error('got binary data when not reconstructing a packet');
    256     } else {
    257       packet = this.reconstructor.takeBinaryData(obj);
    258       if (packet) { // received final buffer
    259         this.reconstructor = null;
    260         this.emit('decoded', packet);
    261       }
    262     }
    263   } else {
    264     throw new Error('Unknown type: ' + obj);
    265   }
    266 };
    267 
    268 /**
    269  * Decode a packet String (JSON data)
    270  *
    271  * @param {String} str
    272  * @return {Object} packet
    273  * @api private
    274  */
    275 
    276 function decodeString(str) {
    277   var i = 0;
    278   // look up type
    279   var p = {
    280     type: Number(str.charAt(0))
    281   };
    282 
    283   if (null == exports.types[p.type]) {
    284     return error('unknown packet type ' + p.type);
    285   }
    286 
    287   // look up attachments if type binary
    288   if (exports.BINARY_EVENT === p.type || exports.BINARY_ACK === p.type) {
    289     var buf = '';
    290     while (str.charAt(++i) !== '-') {
    291       buf += str.charAt(i);
    292       if (i == str.length) break;
    293     }
    294     if (buf != Number(buf) || str.charAt(i) !== '-') {
    295       throw new Error('Illegal attachments');
    296     }
    297     p.attachments = Number(buf);
    298   }
    299 
    300   // look up namespace (if any)
    301   if ('/' === str.charAt(i + 1)) {
    302     p.nsp = '';
    303     while (++i) {
    304       var c = str.charAt(i);
    305       if (',' === c) break;
    306       p.nsp += c;
    307       if (i === str.length) break;
    308     }
    309   } else {
    310     p.nsp = '/';
    311   }
    312 
    313   // look up id
    314   var next = str.charAt(i + 1);
    315   if ('' !== next && Number(next) == next) {
    316     p.id = '';
    317     while (++i) {
    318       var c = str.charAt(i);
    319       if (null == c || Number(c) != c) {
    320         --i;
    321         break;
    322       }
    323       p.id += str.charAt(i);
    324       if (i === str.length) break;
    325     }
    326     p.id = Number(p.id);
    327   }
    328 
    329   // look up json data
    330   if (str.charAt(++i)) {
    331     var payload = tryParse(str.substr(i));
    332     var isPayloadValid = payload !== false && (p.type === exports.ERROR || isArray(payload));
    333     if (isPayloadValid) {
    334       p.data = payload;
    335     } else {
    336       return error('invalid payload');
    337     }
    338   }
    339 
    340   debug('decoded %s as %j', str, p);
    341   return p;
    342 }
    343 
    344 function tryParse(str) {
    345   try {
    346     return JSON.parse(str);
    347   } catch(e){
    348     return false;
    349   }
    350 }
    351 
    352 /**
    353  * Deallocates a parser's resources
    354  *
    355  * @api public
    356  */
    357 
    358 Decoder.prototype.destroy = function() {
    359   if (this.reconstructor) {
    360     this.reconstructor.finishedReconstruction();
    361   }
    362 };
    363 
    364 /**
    365  * A manager of a binary event's 'buffer sequence'. Should
    366  * be constructed whenever a packet of type BINARY_EVENT is
    367  * decoded.
    368  *
    369  * @param {Object} packet
    370  * @return {BinaryReconstructor} initialized reconstructor
    371  * @api private
    372  */
    373 
    374 function BinaryReconstructor(packet) {
    375   this.reconPack = packet;
    376   this.buffers = [];
    377 }
    378 
    379 /**
    380  * Method to be called when binary data received from connection
    381  * after a BINARY_EVENT packet.
    382  *
    383  * @param {Buffer | ArrayBuffer} binData - the raw binary data received
    384  * @return {null | Object} returns null if more binary data is expected or
    385  *   a reconstructed packet object if all buffers have been received.
    386  * @api private
    387  */
    388 
    389 BinaryReconstructor.prototype.takeBinaryData = function(binData) {
    390   this.buffers.push(binData);
    391   if (this.buffers.length === this.reconPack.attachments) { // done with buffer list
    392     var packet = binary.reconstructPacket(this.reconPack, this.buffers);
    393     this.finishedReconstruction();
    394     return packet;
    395   }
    396   return null;
    397 };
    398 
    399 /**
    400  * Cleans up binary packet reconstruction variables.
    401  *
    402  * @api private
    403  */
    404 
    405 BinaryReconstructor.prototype.finishedReconstruction = function() {
    406   this.reconPack = null;
    407   this.buffers = [];
    408 };
    409 
    410 function error(msg) {
    411   return {
    412     type: exports.ERROR,
    413     data: 'parser error: ' + msg
    414   };
    415 }