raspberryGPIOhtmlserver/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};