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