5 var utf8 = require('./utf8');
6 var hasBinary = require('has-binary2');
7 var after = require('after');
8 var keys = require('./keys');
11 * Current protocol version.
19 var packets = exports.packets = {
29 var packetslist = keys(packets);
32 * Premade error packet.
35 var err = { type: 'error', data: 'parser error' };
37 const EMPTY_BUFFER = Buffer.concat([]);
42 * <packet type id> [ <data> ]
50 * Binary is encoded in an identical principle
55 exports.encodePacket = function (packet, supportsBinary, utf8encode, callback) {
56 if (typeof supportsBinary === 'function') {
57 callback = supportsBinary;
58 supportsBinary = null;
61 if (typeof utf8encode === 'function') {
62 callback = utf8encode;
66 if (Buffer.isBuffer(packet.data)) {
67 return encodeBuffer(packet, supportsBinary, callback);
68 } else if (packet.data && (packet.data.buffer || packet.data) instanceof ArrayBuffer) {
69 return encodeBuffer({ type: packet.type, data: arrayBufferToBuffer(packet.data) }, supportsBinary, callback);
72 // Sending data as a utf-8 string
73 var encoded = packets[packet.type];
75 // data fragment is optional
76 if (undefined !== packet.data) {
77 encoded += utf8encode ? utf8.encode(String(packet.data), { strict: false }) : String(packet.data);
80 return callback('' + encoded);
87 function encodeBuffer(packet, supportsBinary, callback) {
88 if (!supportsBinary) {
89 return exports.encodeBase64Packet(packet, callback);
92 var data = packet.data;
93 var typeBuffer = Buffer.allocUnsafe(1);
94 typeBuffer[0] = packets[packet.type];
95 return callback(Buffer.concat([typeBuffer, data]));
99 * Encodes a packet with binary data in a base64 string
101 * @param {Object} packet, has `type` and `data`
102 * @return {String} base64 encoded message
105 exports.encodeBase64Packet = function(packet, callback){
106 var data = Buffer.isBuffer(packet.data) ? packet.data : arrayBufferToBuffer(packet.data);
107 var message = 'b' + packets[packet.type];
108 message += data.toString('base64');
109 return callback(message);
113 * Decodes a packet. Data also available as an ArrayBuffer if requested.
115 * @return {Object} with `type` and `data` (if any)
119 exports.decodePacket = function (data, binaryType, utf8decode) {
120 if (data === undefined) {
127 if (typeof data === 'string') {
129 type = data.charAt(0);
132 return exports.decodeBase64Packet(data.substr(1), binaryType);
136 data = tryDecode(data);
137 if (data === false) {
142 if (Number(type) != type || !packetslist[type]) {
146 if (data.length > 1) {
147 return { type: packetslist[type], data: data.substring(1) };
149 return { type: packetslist[type] };
154 if (binaryType === 'arraybuffer') {
155 // wrap Buffer/ArrayBuffer data into an Uint8Array
156 var intArray = new Uint8Array(data);
158 return { type: packetslist[type], data: intArray.buffer.slice(1) };
161 if (data instanceof ArrayBuffer) {
162 data = arrayBufferToBuffer(data);
165 return { type: packetslist[type], data: data.slice(1) };
168 function tryDecode(data) {
170 data = utf8.decode(data, { strict: false });
178 * Decodes a packet encoded in a base64 string.
180 * @param {String} base64 encoded message
181 * @return {Object} with `type` and `data` (if any)
184 exports.decodeBase64Packet = function(msg, binaryType) {
185 var type = packetslist[msg.charAt(0)];
186 var data = Buffer.from(msg.substr(1), 'base64');
187 if (binaryType === 'arraybuffer') {
188 var abv = new Uint8Array(data.length);
189 for (var i = 0; i < abv.length; i++){
194 return { type: type, data: data };
198 * Encodes multiple messages (payload).
206 * If any contents are binary, they will be encoded as base64 strings. Base64
207 * encoded strings are marked with a b before the length specifier
209 * @param {Array} packets
213 exports.encodePayload = function (packets, supportsBinary, callback) {
214 if (typeof supportsBinary === 'function') {
215 callback = supportsBinary;
216 supportsBinary = null;
219 if (supportsBinary && hasBinary(packets)) {
220 return exports.encodePayloadAsBinary(packets, callback);
223 if (!packets.length) {
224 return callback('0:');
227 function encodeOne(packet, doneCallback) {
228 exports.encodePacket(packet, supportsBinary, false, function(message) {
229 doneCallback(null, setLengthHeader(message));
233 map(packets, encodeOne, function(err, results) {
234 return callback(results.join(''));
238 function setLengthHeader(message) {
239 return message.length + ':' + message;
243 * Async array map using after
246 function map(ary, each, done) {
247 var result = new Array(ary.length);
248 var next = after(ary.length, done);
250 for (var i = 0; i < ary.length; i++) {
251 each(ary[i], function(error, msg) {
259 * Decodes data when a payload is maybe expected. Possible binary contents are
260 * decoded from their base64 representation
262 * @param {String} data, callback method
266 exports.decodePayload = function (data, binaryType, callback) {
267 if (typeof data !== 'string') {
268 return exports.decodePayloadAsBinary(data, binaryType, callback);
271 if (typeof binaryType === 'function') {
272 callback = binaryType;
277 // parser error - ignoring payload
278 return callback(err, 0, 1);
281 var length = '', n, msg, packet;
283 for (var i = 0, l = data.length; i < l; i++) {
284 var chr = data.charAt(i);
291 if (length === '' || (length != (n = Number(length)))) {
292 // parser error - ignoring payload
293 return callback(err, 0, 1);
296 msg = data.substr(i + 1, n);
298 if (length != msg.length) {
299 // parser error - ignoring payload
300 return callback(err, 0, 1);
304 packet = exports.decodePacket(msg, binaryType, false);
306 if (err.type === packet.type && err.data === packet.data) {
307 // parser error in individual packet - ignoring payload
308 return callback(err, 0, 1);
311 var more = callback(packet, i + n, l);
312 if (false === more) return;
321 // parser error - ignoring payload
322 return callback(err, 0, 1);
329 * Converts a buffer to a utf8.js encoded string
334 function bufferToString(buffer) {
336 for (var i = 0, l = buffer.length; i < l; i++) {
337 str += String.fromCharCode(buffer[i]);
344 * Converts a utf8.js encoded string to a buffer
349 function stringToBuffer(string) {
350 var buf = Buffer.allocUnsafe(string.length);
351 for (var i = 0, l = string.length; i < l; i++) {
352 buf.writeUInt8(string.charCodeAt(i), i);
359 * Converts an ArrayBuffer to a Buffer
364 function arrayBufferToBuffer(data) {
365 // data is either an ArrayBuffer or ArrayBufferView.
366 var length = data.byteLength || data.length;
367 var offset = data.byteOffset || 0;
369 return Buffer.from(data.buffer || data, offset, length);
373 * Encodes multiple messages (payload) as binary.
375 * <1 = binary, 0 = string><number from 0-9><number from 0-9>[...]<number
379 * 1 3 255 1 2 3, if the binary contents are interpreted as 8 bit integers
381 * @param {Array} packets
382 * @return {Buffer} encoded payload
386 exports.encodePayloadAsBinary = function (packets, callback) {
387 if (!packets.length) {
388 return callback(EMPTY_BUFFER);
391 map(packets, encodeOneBinaryPacket, function(err, results) {
392 return callback(Buffer.concat(results));
396 function encodeOneBinaryPacket(p, doneCallback) {
398 function onBinaryPacketEncode(packet) {
400 var encodingLength = '' + packet.length;
403 if (typeof packet === 'string') {
404 sizeBuffer = Buffer.allocUnsafe(encodingLength.length + 2);
405 sizeBuffer[0] = 0; // is a string (not true binary = 0)
406 for (var i = 0; i < encodingLength.length; i++) {
407 sizeBuffer[i + 1] = parseInt(encodingLength[i], 10);
409 sizeBuffer[sizeBuffer.length - 1] = 255;
410 return doneCallback(null, Buffer.concat([sizeBuffer, stringToBuffer(packet)]));
413 sizeBuffer = Buffer.allocUnsafe(encodingLength.length + 2);
414 sizeBuffer[0] = 1; // is binary (true binary = 1)
415 for (var i = 0; i < encodingLength.length; i++) {
416 sizeBuffer[i + 1] = parseInt(encodingLength[i], 10);
418 sizeBuffer[sizeBuffer.length - 1] = 255;
420 doneCallback(null, Buffer.concat([sizeBuffer, packet]));
423 exports.encodePacket(p, true, true, onBinaryPacketEncode);
429 * Decodes data when a payload is maybe expected. Strings are decoded by
430 * interpreting each byte as a key code for entries marked to start with 0. See
431 * description of encodePayloadAsBinary
433 * @param {Buffer} data, callback method
437 exports.decodePayloadAsBinary = function (data, binaryType, callback) {
438 if (typeof binaryType === 'function') {
439 callback = binaryType;
443 var bufferTail = data;
447 while (bufferTail.length > 0) {
449 var isString = bufferTail[0] === 0;
451 if (bufferTail[i] === 255) break;
452 // 310 = char length of Number.MAX_VALUE
453 if (strLen.length > 310) {
454 return callback(err, 0, 1);
456 strLen += '' + bufferTail[i];
458 bufferTail = bufferTail.slice(strLen.length + 1);
460 var msgLength = parseInt(strLen, 10);
462 var msg = bufferTail.slice(1, msgLength + 1);
463 if (isString) msg = bufferToString(msg);
465 bufferTail = bufferTail.slice(msgLength + 1);
468 var total = buffers.length;
469 for (i = 0; i < total; i++) {
470 var buffer = buffers[i];
471 callback(exports.decodePacket(buffer, binaryType, true), i, total);