From 67fdec20726e48ba3a934cb25bb30d47ec4a4f29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yaroslav=20De=20La=20Pe=C3=B1a=20Smirnov?= Date: Wed, 29 Nov 2017 11:44:34 +0300 Subject: Initial commit, version 0.5.3 --- node_modules/engine.io-parser/lib/browser.js | 606 +++++++++++++++++++++++++++ 1 file changed, 606 insertions(+) create mode 100644 node_modules/engine.io-parser/lib/browser.js (limited to 'node_modules/engine.io-parser/lib/browser.js') diff --git a/node_modules/engine.io-parser/lib/browser.js b/node_modules/engine.io-parser/lib/browser.js new file mode 100644 index 0000000..5a5e506 --- /dev/null +++ b/node_modules/engine.io-parser/lib/browser.js @@ -0,0 +1,606 @@ +/** + * Module dependencies. + */ + +var keys = require('./keys'); +var hasBinary = require('has-binary2'); +var sliceBuffer = require('arraybuffer.slice'); +var after = require('after'); +var utf8 = require('./utf8'); + +var base64encoder; +if (global && global.ArrayBuffer) { + base64encoder = require('base64-arraybuffer'); +} + +/** + * Check if we are running an android browser. That requires us to use + * ArrayBuffer with polling transports... + * + * http://ghinda.net/jpeg-blob-ajax-android/ + */ + +var isAndroid = typeof navigator !== 'undefined' && /Android/i.test(navigator.userAgent); + +/** + * Check if we are running in PhantomJS. + * Uploading a Blob with PhantomJS does not work correctly, as reported here: + * https://github.com/ariya/phantomjs/issues/11395 + * @type boolean + */ +var isPhantomJS = typeof navigator !== 'undefined' && /PhantomJS/i.test(navigator.userAgent); + +/** + * When true, avoids using Blobs to encode payloads. + * @type boolean + */ +var dontSendBlobs = isAndroid || isPhantomJS; + +/** + * Current protocol version. + */ + +exports.protocol = 3; + +/** + * Packet types. + */ + +var packets = exports.packets = { + open: 0 // non-ws + , close: 1 // non-ws + , ping: 2 + , pong: 3 + , message: 4 + , upgrade: 5 + , noop: 6 +}; + +var packetslist = keys(packets); + +/** + * Premade error packet. + */ + +var err = { type: 'error', data: 'parser error' }; + +/** + * Create a blob api even for blob builder when vendor prefixes exist + */ + +var Blob = require('blob'); + +/** + * Encodes a packet. + * + * [ ] + * + * Example: + * + * 5hello world + * 3 + * 4 + * + * Binary is encoded in an identical principle + * + * @api private + */ + +exports.encodePacket = function (packet, supportsBinary, utf8encode, callback) { + if (typeof supportsBinary === 'function') { + callback = supportsBinary; + supportsBinary = false; + } + + if (typeof utf8encode === 'function') { + callback = utf8encode; + utf8encode = null; + } + + var data = (packet.data === undefined) + ? undefined + : packet.data.buffer || packet.data; + + if (global.ArrayBuffer && data instanceof ArrayBuffer) { + return encodeArrayBuffer(packet, supportsBinary, callback); + } else if (Blob && data instanceof global.Blob) { + return encodeBlob(packet, supportsBinary, callback); + } + + // might be an object with { base64: true, data: dataAsBase64String } + if (data && data.base64) { + return encodeBase64Object(packet, callback); + } + + // Sending data as a utf-8 string + var encoded = packets[packet.type]; + + // data fragment is optional + if (undefined !== packet.data) { + encoded += utf8encode ? utf8.encode(String(packet.data), { strict: false }) : String(packet.data); + } + + return callback('' + encoded); + +}; + +function encodeBase64Object(packet, callback) { + // packet data is an object { base64: true, data: dataAsBase64String } + var message = 'b' + exports.packets[packet.type] + packet.data.data; + return callback(message); +} + +/** + * Encode packet helpers for binary types + */ + +function encodeArrayBuffer(packet, supportsBinary, callback) { + if (!supportsBinary) { + return exports.encodeBase64Packet(packet, callback); + } + + var data = packet.data; + var contentArray = new Uint8Array(data); + var resultBuffer = new Uint8Array(1 + data.byteLength); + + resultBuffer[0] = packets[packet.type]; + for (var i = 0; i < contentArray.length; i++) { + resultBuffer[i+1] = contentArray[i]; + } + + return callback(resultBuffer.buffer); +} + +function encodeBlobAsArrayBuffer(packet, supportsBinary, callback) { + if (!supportsBinary) { + return exports.encodeBase64Packet(packet, callback); + } + + var fr = new FileReader(); + fr.onload = function() { + packet.data = fr.result; + exports.encodePacket(packet, supportsBinary, true, callback); + }; + return fr.readAsArrayBuffer(packet.data); +} + +function encodeBlob(packet, supportsBinary, callback) { + if (!supportsBinary) { + return exports.encodeBase64Packet(packet, callback); + } + + if (dontSendBlobs) { + return encodeBlobAsArrayBuffer(packet, supportsBinary, callback); + } + + var length = new Uint8Array(1); + length[0] = packets[packet.type]; + var blob = new Blob([length.buffer, packet.data]); + + return callback(blob); +} + +/** + * Encodes a packet with binary data in a base64 string + * + * @param {Object} packet, has `type` and `data` + * @return {String} base64 encoded message + */ + +exports.encodeBase64Packet = function(packet, callback) { + var message = 'b' + exports.packets[packet.type]; + if (Blob && packet.data instanceof global.Blob) { + var fr = new FileReader(); + fr.onload = function() { + var b64 = fr.result.split(',')[1]; + callback(message + b64); + }; + return fr.readAsDataURL(packet.data); + } + + var b64data; + try { + b64data = String.fromCharCode.apply(null, new Uint8Array(packet.data)); + } catch (e) { + // iPhone Safari doesn't let you apply with typed arrays + var typed = new Uint8Array(packet.data); + var basic = new Array(typed.length); + for (var i = 0; i < typed.length; i++) { + basic[i] = typed[i]; + } + b64data = String.fromCharCode.apply(null, basic); + } + message += global.btoa(b64data); + return callback(message); +}; + +/** + * Decodes a packet. Changes format to Blob if requested. + * + * @return {Object} with `type` and `data` (if any) + * @api private + */ + +exports.decodePacket = function (data, binaryType, utf8decode) { + if (data === undefined) { + return err; + } + // String data + if (typeof data === 'string') { + if (data.charAt(0) === 'b') { + return exports.decodeBase64Packet(data.substr(1), binaryType); + } + + if (utf8decode) { + data = tryDecode(data); + if (data === false) { + return err; + } + } + var type = data.charAt(0); + + if (Number(type) != type || !packetslist[type]) { + return err; + } + + if (data.length > 1) { + return { type: packetslist[type], data: data.substring(1) }; + } else { + return { type: packetslist[type] }; + } + } + + var asArray = new Uint8Array(data); + var type = asArray[0]; + var rest = sliceBuffer(data, 1); + if (Blob && binaryType === 'blob') { + rest = new Blob([rest]); + } + return { type: packetslist[type], data: rest }; +}; + +function tryDecode(data) { + try { + data = utf8.decode(data, { strict: false }); + } catch (e) { + return false; + } + return data; +} + +/** + * Decodes a packet encoded in a base64 string + * + * @param {String} base64 encoded message + * @return {Object} with `type` and `data` (if any) + */ + +exports.decodeBase64Packet = function(msg, binaryType) { + var type = packetslist[msg.charAt(0)]; + if (!base64encoder) { + return { type: type, data: { base64: true, data: msg.substr(1) } }; + } + + var data = base64encoder.decode(msg.substr(1)); + + if (binaryType === 'blob' && Blob) { + data = new Blob([data]); + } + + return { type: type, data: data }; +}; + +/** + * Encodes multiple messages (payload). + * + * :data + * + * Example: + * + * 11:hello world2:hi + * + * If any contents are binary, they will be encoded as base64 strings. Base64 + * encoded strings are marked with a b before the length specifier + * + * @param {Array} packets + * @api private + */ + +exports.encodePayload = function (packets, supportsBinary, callback) { + if (typeof supportsBinary === 'function') { + callback = supportsBinary; + supportsBinary = null; + } + + var isBinary = hasBinary(packets); + + if (supportsBinary && isBinary) { + if (Blob && !dontSendBlobs) { + return exports.encodePayloadAsBlob(packets, callback); + } + + return exports.encodePayloadAsArrayBuffer(packets, callback); + } + + if (!packets.length) { + return callback('0:'); + } + + function setLengthHeader(message) { + return message.length + ':' + message; + } + + function encodeOne(packet, doneCallback) { + exports.encodePacket(packet, !isBinary ? false : supportsBinary, false, function(message) { + doneCallback(null, setLengthHeader(message)); + }); + } + + map(packets, encodeOne, function(err, results) { + return callback(results.join('')); + }); +}; + +/** + * Async array map using after + */ + +function map(ary, each, done) { + var result = new Array(ary.length); + var next = after(ary.length, done); + + var eachWithIndex = function(i, el, cb) { + each(el, function(error, msg) { + result[i] = msg; + cb(error, result); + }); + }; + + for (var i = 0; i < ary.length; i++) { + eachWithIndex(i, ary[i], next); + } +} + +/* + * Decodes data when a payload is maybe expected. Possible binary contents are + * decoded from their base64 representation + * + * @param {String} data, callback method + * @api public + */ + +exports.decodePayload = function (data, binaryType, callback) { + if (typeof data !== 'string') { + return exports.decodePayloadAsBinary(data, binaryType, callback); + } + + if (typeof binaryType === 'function') { + callback = binaryType; + binaryType = null; + } + + var packet; + if (data === '') { + // parser error - ignoring payload + return callback(err, 0, 1); + } + + var length = '', n, msg; + + for (var i = 0, l = data.length; i < l; i++) { + var chr = data.charAt(i); + + if (chr !== ':') { + length += chr; + continue; + } + + if (length === '' || (length != (n = Number(length)))) { + // parser error - ignoring payload + return callback(err, 0, 1); + } + + msg = data.substr(i + 1, n); + + if (length != msg.length) { + // parser error - ignoring payload + return callback(err, 0, 1); + } + + if (msg.length) { + packet = exports.decodePacket(msg, binaryType, false); + + if (err.type === packet.type && err.data === packet.data) { + // parser error in individual packet - ignoring payload + return callback(err, 0, 1); + } + + var ret = callback(packet, i + n, l); + if (false === ret) return; + } + + // advance cursor + i += n; + length = ''; + } + + if (length !== '') { + // parser error - ignoring payload + return callback(err, 0, 1); + } + +}; + +/** + * Encodes multiple messages (payload) as binary. + * + * <1 = binary, 0 = string>[...] + * + * Example: + * 1 3 255 1 2 3, if the binary contents are interpreted as 8 bit integers + * + * @param {Array} packets + * @return {ArrayBuffer} encoded payload + * @api private + */ + +exports.encodePayloadAsArrayBuffer = function(packets, callback) { + if (!packets.length) { + return callback(new ArrayBuffer(0)); + } + + function encodeOne(packet, doneCallback) { + exports.encodePacket(packet, true, true, function(data) { + return doneCallback(null, data); + }); + } + + map(packets, encodeOne, function(err, encodedPackets) { + var totalLength = encodedPackets.reduce(function(acc, p) { + var len; + if (typeof p === 'string'){ + len = p.length; + } else { + len = p.byteLength; + } + return acc + len.toString().length + len + 2; // string/binary identifier + separator = 2 + }, 0); + + var resultArray = new Uint8Array(totalLength); + + var bufferIndex = 0; + encodedPackets.forEach(function(p) { + var isString = typeof p === 'string'; + var ab = p; + if (isString) { + var view = new Uint8Array(p.length); + for (var i = 0; i < p.length; i++) { + view[i] = p.charCodeAt(i); + } + ab = view.buffer; + } + + if (isString) { // not true binary + resultArray[bufferIndex++] = 0; + } else { // true binary + resultArray[bufferIndex++] = 1; + } + + var lenStr = ab.byteLength.toString(); + for (var i = 0; i < lenStr.length; i++) { + resultArray[bufferIndex++] = parseInt(lenStr[i]); + } + resultArray[bufferIndex++] = 255; + + var view = new Uint8Array(ab); + for (var i = 0; i < view.length; i++) { + resultArray[bufferIndex++] = view[i]; + } + }); + + return callback(resultArray.buffer); + }); +}; + +/** + * Encode as Blob + */ + +exports.encodePayloadAsBlob = function(packets, callback) { + function encodeOne(packet, doneCallback) { + exports.encodePacket(packet, true, true, function(encoded) { + var binaryIdentifier = new Uint8Array(1); + binaryIdentifier[0] = 1; + if (typeof encoded === 'string') { + var view = new Uint8Array(encoded.length); + for (var i = 0; i < encoded.length; i++) { + view[i] = encoded.charCodeAt(i); + } + encoded = view.buffer; + binaryIdentifier[0] = 0; + } + + var len = (encoded instanceof ArrayBuffer) + ? encoded.byteLength + : encoded.size; + + var lenStr = len.toString(); + var lengthAry = new Uint8Array(lenStr.length + 1); + for (var i = 0; i < lenStr.length; i++) { + lengthAry[i] = parseInt(lenStr[i]); + } + lengthAry[lenStr.length] = 255; + + if (Blob) { + var blob = new Blob([binaryIdentifier.buffer, lengthAry.buffer, encoded]); + doneCallback(null, blob); + } + }); + } + + map(packets, encodeOne, function(err, results) { + return callback(new Blob(results)); + }); +}; + +/* + * Decodes data when a payload is maybe expected. Strings are decoded by + * interpreting each byte as a key code for entries marked to start with 0. See + * description of encodePayloadAsBinary + * + * @param {ArrayBuffer} data, callback method + * @api public + */ + +exports.decodePayloadAsBinary = function (data, binaryType, callback) { + if (typeof binaryType === 'function') { + callback = binaryType; + binaryType = null; + } + + var bufferTail = data; + var buffers = []; + + while (bufferTail.byteLength > 0) { + var tailArray = new Uint8Array(bufferTail); + var isString = tailArray[0] === 0; + var msgLength = ''; + + for (var i = 1; ; i++) { + if (tailArray[i] === 255) break; + + // 310 = char length of Number.MAX_VALUE + if (msgLength.length > 310) { + return callback(err, 0, 1); + } + + msgLength += tailArray[i]; + } + + bufferTail = sliceBuffer(bufferTail, 2 + msgLength.length); + msgLength = parseInt(msgLength); + + var msg = sliceBuffer(bufferTail, 0, msgLength); + if (isString) { + try { + msg = String.fromCharCode.apply(null, new Uint8Array(msg)); + } catch (e) { + // iPhone Safari doesn't let you apply to typed arrays + var typed = new Uint8Array(msg); + msg = ''; + for (var i = 0; i < typed.length; i++) { + msg += String.fromCharCode(typed[i]); + } + } + } + + buffers.push(msg); + bufferTail = sliceBuffer(bufferTail, msgLength); + } + + var total = buffers.length; + buffers.forEach(function(buffer, i) { + callback(exports.decodePacket(buffer, binaryType, true), i, total); + }); +}; -- cgit v1.2.3