arnaucube
arnaucube/comunicationLeapPublic Self-hosted repository
comunicationLeap/node_modules/engine.io-parser/lib/browser.js
node_modules/engine.io-parser/lib/browser.jsRaw
  1/**
  2 * Module dependencies.
  3 */
  4
  5var keys = require('./keys');
  6var sliceBuffer = require('arraybuffer.slice');
  7var base64encoder = require('base64-arraybuffer');
  8var after = require('after');
  9var utf8 = require('utf8');
 10
 11/**
 12 * Check if we are running an android browser. That requires us to use
 13 * ArrayBuffer with polling transports...
 14 *
 15 * http://ghinda.net/jpeg-blob-ajax-android/
 16 */
 17
 18var isAndroid = navigator.userAgent.match(/Android/i);
 19
 20/**
 21 * Current protocol version.
 22 */
 23
 24exports.protocol = 3;
 25
 26/**
 27 * Packet types.
 28 */
 29
 30var packets = exports.packets = {
 31    open:     0    // non-ws
 32  , close:    1    // non-ws
 33  , ping:     2
 34  , pong:     3
 35  , message:  4
 36  , upgrade:  5
 37  , noop:     6
 38};
 39
 40var packetslist = keys(packets);
 41
 42/**
 43 * Premade error packet.
 44 */
 45
 46var err = { type: 'error', data: 'parser error' };
 47
 48/**
 49 * Create a blob api even for blob builder when vendor prefixes exist
 50 */
 51
 52var Blob = require('blob');
 53
 54/**
 55 * Encodes a packet.
 56 *
 57 *     <packet type id> [ <data> ]
 58 *
 59 * Example:
 60 *
 61 *     5hello world
 62 *     3
 63 *     4
 64 *
 65 * Binary is encoded in an identical principle
 66 *
 67 * @api private
 68 */
 69
 70exports.encodePacket = function (packet, supportsBinary, utf8encode, callback) {
 71  if ('function' == typeof supportsBinary) {
 72    callback = supportsBinary;
 73    supportsBinary = false;
 74  }
 75
 76  if ('function' == typeof utf8encode) {
 77    callback = utf8encode;
 78    utf8encode = null;
 79  }
 80
 81  var data = (packet.data === undefined)
 82    ? undefined
 83    : packet.data.buffer || packet.data;
 84
 85  if (global.ArrayBuffer && data instanceof ArrayBuffer) {
 86    return encodeArrayBuffer(packet, supportsBinary, callback);
 87  } else if (Blob && data instanceof global.Blob) {
 88    return encodeBlob(packet, supportsBinary, callback);
 89  }
 90
 91  // Sending data as a utf-8 string
 92  var encoded = packets[packet.type];
 93
 94  // data fragment is optional
 95  if (undefined !== packet.data) {
 96    encoded += utf8encode ? utf8.encode(String(packet.data)) : String(packet.data);
 97  }
 98
 99  return callback('' + encoded);
100
101};
102
103/**
104 * Encode packet helpers for binary types
105 */
106
107function encodeArrayBuffer(packet, supportsBinary, callback) {
108  if (!supportsBinary) {
109    return exports.encodeBase64Packet(packet, callback);
110  }
111
112  var data = packet.data;
113  var contentArray = new Uint8Array(data);
114  var resultBuffer = new Uint8Array(1 + data.byteLength);
115
116  resultBuffer[0] = packets[packet.type];
117  for (var i = 0; i < contentArray.length; i++) {
118    resultBuffer[i+1] = contentArray[i];
119  }
120
121  return callback(resultBuffer.buffer);
122}
123
124function encodeBlobAsArrayBuffer(packet, supportsBinary, callback) {
125  if (!supportsBinary) {
126    return exports.encodeBase64Packet(packet, callback);
127  }
128
129  var fr = new FileReader();
130  fr.onload = function() {
131    packet.data = fr.result;
132    exports.encodePacket(packet, supportsBinary, true, callback);
133  };
134  return fr.readAsArrayBuffer(packet.data);
135}
136
137function encodeBlob(packet, supportsBinary, callback) {
138  if (!supportsBinary) {
139    return exports.encodeBase64Packet(packet, callback);
140  }
141
142  if (isAndroid) {
143    return encodeBlobAsArrayBuffer(packet, supportsBinary, callback);
144  }
145
146  var length = new Uint8Array(1);
147  length[0] = packets[packet.type];
148  var blob = new Blob([length.buffer, packet.data]);
149
150  return callback(blob);
151}
152
153/**
154 * Encodes a packet with binary data in a base64 string
155 *
156 * @param {Object} packet, has `type` and `data`
157 * @return {String} base64 encoded message
158 */
159
160exports.encodeBase64Packet = function(packet, callback) {
161  var message = 'b' + exports.packets[packet.type];
162  if (Blob && packet.data instanceof Blob) {
163    var fr = new FileReader();
164    fr.onload = function() {
165      var b64 = fr.result.split(',')[1];
166      callback(message + b64);
167    };
168    return fr.readAsDataURL(packet.data);
169  }
170
171  var b64data;
172  try {
173    b64data = String.fromCharCode.apply(null, new Uint8Array(packet.data));
174  } catch (e) {
175    // iPhone Safari doesn't let you apply with typed arrays
176    var typed = new Uint8Array(packet.data);
177    var basic = new Array(typed.length);
178    for (var i = 0; i < typed.length; i++) {
179      basic[i] = typed[i];
180    }
181    b64data = String.fromCharCode.apply(null, basic);
182  }
183  message += global.btoa(b64data);
184  return callback(message);
185};
186
187/**
188 * Decodes a packet. Changes format to Blob if requested.
189 *
190 * @return {Object} with `type` and `data` (if any)
191 * @api private
192 */
193
194exports.decodePacket = function (data, binaryType, utf8decode) {
195  // String data
196  if (typeof data == 'string' || data === undefined) {
197    if (data.charAt(0) == 'b') {
198      return exports.decodeBase64Packet(data.substr(1), binaryType);
199    }
200
201    if (utf8decode) {
202      try {
203        data = utf8.decode(data);
204      } catch (e) {
205        return err;
206      }
207    }
208    var type = data.charAt(0);
209
210    if (Number(type) != type || !packetslist[type]) {
211      return err;
212    }
213
214    if (data.length > 1) {
215      return { type: packetslist[type], data: data.substring(1) };
216    } else {
217      return { type: packetslist[type] };
218    }
219  }
220
221  var asArray = new Uint8Array(data);
222  var type = asArray[0];
223  var rest = sliceBuffer(data, 1);
224  if (Blob && binaryType === 'blob') {
225    rest = new Blob([rest]);
226  }
227  return { type: packetslist[type], data: rest };
228};
229
230/**
231 * Decodes a packet encoded in a base64 string
232 *
233 * @param {String} base64 encoded message
234 * @return {Object} with `type` and `data` (if any)
235 */
236
237exports.decodeBase64Packet = function(msg, binaryType) {
238  var type = packetslist[msg.charAt(0)];
239  if (!global.ArrayBuffer) {
240    return { type: type, data: { base64: true, data: msg.substr(1) } };
241  }
242
243  var data = base64encoder.decode(msg.substr(1));
244
245  if (binaryType === 'blob' && Blob) {
246    data = new Blob([data]);
247  }
248
249  return { type: type, data: data };
250};
251
252/**
253 * Encodes multiple messages (payload).
254 *
255 *     <length>:data
256 *
257 * Example:
258 *
259 *     11:hello world2:hi
260 *
261 * If any contents are binary, they will be encoded as base64 strings. Base64
262 * encoded strings are marked with a b before the length specifier
263 *
264 * @param {Array} packets
265 * @api private
266 */
267
268exports.encodePayload = function (packets, supportsBinary, callback) {
269  if (typeof supportsBinary == 'function') {
270    callback = supportsBinary;
271    supportsBinary = null;
272  }
273
274  if (supportsBinary) {
275    if (Blob && !isAndroid) {
276      return exports.encodePayloadAsBlob(packets, callback);
277    }
278
279    return exports.encodePayloadAsArrayBuffer(packets, callback);
280  }
281
282  if (!packets.length) {
283    return callback('0:');
284  }
285
286  function setLengthHeader(message) {
287    return message.length + ':' + message;
288  }
289
290  function encodeOne(packet, doneCallback) {
291    exports.encodePacket(packet, supportsBinary, true, function(message) {
292      doneCallback(null, setLengthHeader(message));
293    });
294  }
295
296  map(packets, encodeOne, function(err, results) {
297    return callback(results.join(''));
298  });
299};
300
301/**
302 * Async array map using after
303 */
304
305function map(ary, each, done) {
306  var result = new Array(ary.length);
307  var next = after(ary.length, done);
308
309  var eachWithIndex = function(i, el, cb) {
310    each(el, function(error, msg) {
311      result[i] = msg;
312      cb(error, result);
313    });
314  };
315
316  for (var i = 0; i < ary.length; i++) {
317    eachWithIndex(i, ary[i], next);
318  }
319}
320
321/*
322 * Decodes data when a payload is maybe expected. Possible binary contents are
323 * decoded from their base64 representation
324 *
325 * @param {String} data, callback method
326 * @api public
327 */
328
329exports.decodePayload = function (data, binaryType, callback) {
330  if (typeof data != 'string') {
331    return exports.decodePayloadAsBinary(data, binaryType, callback);
332  }
333
334  if (typeof binaryType === 'function') {
335    callback = binaryType;
336    binaryType = null;
337  }
338
339  var packet;
340  if (data == '') {
341    // parser error - ignoring payload
342    return callback(err, 0, 1);
343  }
344
345  var length = ''
346    , n, msg;
347
348  for (var i = 0, l = data.length; i < l; i++) {
349    var chr = data.charAt(i);
350
351    if (':' != chr) {
352      length += chr;
353    } else {
354      if ('' == length || (length != (n = Number(length)))) {
355        // parser error - ignoring payload
356        return callback(err, 0, 1);
357      }
358
359      msg = data.substr(i + 1, n);
360
361      if (length != msg.length) {
362        // parser error - ignoring payload
363        return callback(err, 0, 1);
364      }
365
366      if (msg.length) {
367        packet = exports.decodePacket(msg, binaryType, true);
368
369        if (err.type == packet.type && err.data == packet.data) {
370          // parser error in individual packet - ignoring payload
371          return callback(err, 0, 1);
372        }
373
374        var ret = callback(packet, i + n, l);
375        if (false === ret) return;
376      }
377
378      // advance cursor
379      i += n;
380      length = '';
381    }
382  }
383
384  if (length != '') {
385    // parser error - ignoring payload
386    return callback(err, 0, 1);
387  }
388
389};
390
391/**
392 * Encodes multiple messages (payload) as binary.
393 *
394 * <1 = binary, 0 = string><number from 0-9><number from 0-9>[...]<number
395 * 255><data>
396 *
397 * Example:
398 * 1 3 255 1 2 3, if the binary contents are interpreted as 8 bit integers
399 *
400 * @param {Array} packets
401 * @return {ArrayBuffer} encoded payload
402 * @api private
403 */
404
405exports.encodePayloadAsArrayBuffer = function(packets, callback) {
406  if (!packets.length) {
407    return callback(new ArrayBuffer(0));
408  }
409
410  function encodeOne(packet, doneCallback) {
411    exports.encodePacket(packet, true, true, function(data) {
412      return doneCallback(null, data);
413    });
414  }
415
416  map(packets, encodeOne, function(err, encodedPackets) {
417    var totalLength = encodedPackets.reduce(function(acc, p) {
418      var len;
419      if (typeof p === 'string'){
420        len = p.length;
421      } else {
422        len = p.byteLength;
423      }
424      return acc + len.toString().length + len + 2; // string/binary identifier + separator = 2
425    }, 0);
426
427    var resultArray = new Uint8Array(totalLength);
428
429    var bufferIndex = 0;
430    encodedPackets.forEach(function(p) {
431      var isString = typeof p === 'string';
432      var ab = p;
433      if (isString) {
434        var view = new Uint8Array(p.length);
435        for (var i = 0; i < p.length; i++) {
436          view[i] = p.charCodeAt(i);
437        }
438        ab = view.buffer;
439      }
440
441      if (isString) { // not true binary
442        resultArray[bufferIndex++] = 0;
443      } else { // true binary
444        resultArray[bufferIndex++] = 1;
445      }
446
447      var lenStr = ab.byteLength.toString();
448      for (var i = 0; i < lenStr.length; i++) {
449        resultArray[bufferIndex++] = parseInt(lenStr[i]);
450      }
451      resultArray[bufferIndex++] = 255;
452
453      var view = new Uint8Array(ab);
454      for (var i = 0; i < view.length; i++) {
455        resultArray[bufferIndex++] = view[i];
456      }
457    });
458
459    return callback(resultArray.buffer);
460  });
461};
462
463/**
464 * Encode as Blob
465 */
466
467exports.encodePayloadAsBlob = function(packets, callback) {
468  function encodeOne(packet, doneCallback) {
469    exports.encodePacket(packet, true, true, function(encoded) {
470      var binaryIdentifier = new Uint8Array(1);
471      binaryIdentifier[0] = 1;
472      if (typeof encoded === 'string') {
473        var view = new Uint8Array(encoded.length);
474        for (var i = 0; i < encoded.length; i++) {
475          view[i] = encoded.charCodeAt(i);
476        }
477        encoded = view.buffer;
478        binaryIdentifier[0] = 0;
479      }
480
481      var len = (encoded instanceof ArrayBuffer)
482        ? encoded.byteLength
483        : encoded.size;
484
485      var lenStr = len.toString();
486      var lengthAry = new Uint8Array(lenStr.length + 1);
487      for (var i = 0; i < lenStr.length; i++) {
488        lengthAry[i] = parseInt(lenStr[i]);
489      }
490      lengthAry[lenStr.length] = 255;
491
492      if (Blob) {
493        var blob = new Blob([binaryIdentifier.buffer, lengthAry.buffer, encoded]);
494        doneCallback(null, blob);
495      }
496    });
497  }
498
499  map(packets, encodeOne, function(err, results) {
500    return callback(new Blob(results));
501  });
502};
503
504/*
505 * Decodes data when a payload is maybe expected. Strings are decoded by
506 * interpreting each byte as a key code for entries marked to start with 0. See
507 * description of encodePayloadAsBinary
508 *
509 * @param {ArrayBuffer} data, callback method
510 * @api public
511 */
512
513exports.decodePayloadAsBinary = function (data, binaryType, callback) {
514  if (typeof binaryType === 'function') {
515    callback = binaryType;
516    binaryType = null;
517  }
518
519  var bufferTail = data;
520  var buffers = [];
521
522  var numberTooLong = false;
523  while (bufferTail.byteLength > 0) {
524    var tailArray = new Uint8Array(bufferTail);
525    var isString = tailArray[0] === 0;
526    var msgLength = '';
527
528    for (var i = 1; ; i++) {
529      if (tailArray[i] == 255) break;
530
531      if (msgLength.length > 310) {
532        numberTooLong = true;
533        break;
534      }
535
536      msgLength += tailArray[i];
537    }
538
539    if(numberTooLong) return callback(err, 0, 1);
540
541    bufferTail = sliceBuffer(bufferTail, 2 + msgLength.length);
542    msgLength = parseInt(msgLength);
543
544    var msg = sliceBuffer(bufferTail, 0, msgLength);
545    if (isString) {
546      try {
547        msg = String.fromCharCode.apply(null, new Uint8Array(msg));
548      } catch (e) {
549        // iPhone Safari doesn't let you apply to typed arrays
550        var typed = new Uint8Array(msg);
551        msg = '';
552        for (var i = 0; i < typed.length; i++) {
553          msg += String.fromCharCode(typed[i]);
554        }
555      }
556    }
557
558    buffers.push(msg);
559    bufferTail = sliceBuffer(bufferTail, msgLength);
560  }
561
562  var total = buffers.length;
563  buffers.forEach(function(buffer, i) {
564    callback(exports.decodePacket(buffer, binaryType, true), i, total);
565  });
566};