Skip to content

Commit

Permalink
refactor: do not convert Blobs
Browse files Browse the repository at this point in the history
This was needed in a previous version of the parser, which used msgpack
to encode the payload.

Blobs (and Files) will now be included in the array of binary
attachments without any additional transformation.

Breaking change: the encode method is now synchronous

See also 299849b
  • Loading branch information
darrachequesne committed Sep 24, 2020
1 parent fe33ff7 commit 28d4f03
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 142 deletions.
79 changes: 3 additions & 76 deletions lib/binary.ts
@@ -1,19 +1,7 @@
import isBuf from "./is-buffer";

const toString = Object.prototype.toString;
const withNativeBlob =
typeof Blob === "function" ||
(typeof Blob !== "undefined" &&
toString.call(Blob) === "[object BlobConstructor]");
const withNativeFile =
typeof File === "function" ||
(typeof File !== "undefined" &&
toString.call(File) === "[object FileConstructor]");
import isBinary from "./is-binary";

/**
* Replaces every Buffer | ArrayBuffer in packet with a numbered placeholder.
* Anything with blobs or files should be fed through removeBlobs before coming
* here.
* Replaces every Buffer | ArrayBuffer | Blob | File in packet with a numbered placeholder.
*
* @param {Object} packet - socket.io event packet
* @return {Object} with deconstructed packet and list of buffers
Expand All @@ -32,7 +20,7 @@ export function deconstructPacket(packet) {
function _deconstructPacket(data, buffers) {
if (!data) return data;

if (isBuf(data)) {
if (isBinary(data)) {
const placeholder = { _placeholder: true, num: buffers.length };
buffers.push(data);
return placeholder;
Expand Down Expand Up @@ -88,64 +76,3 @@ function _reconstructPacket(data, buffers) {

return data;
}

/**
* Asynchronously removes Blobs or Files from data via
* FileReader's readAsArrayBuffer method. Used before encoding
* data as msgpack. Calls callback with the blobless data.
*
* @param {Object} data
* @param {Function} callback
* @api private
*/

export function removeBlobs(data, callback) {
function _removeBlobs(obj, curKey?, containingObject?) {
if (!obj) return obj;

// convert any blob
if (
(withNativeBlob && obj instanceof Blob) ||
(withNativeFile && obj instanceof File)
) {
pendingBlobs++;

// async filereader
const fileReader = new FileReader();
fileReader.onload = function () {
// this.result == arraybuffer
if (containingObject) {
containingObject[curKey] = this.result;
} else {
bloblessData = this.result;
}

// if nothing pending its callback time
if (!--pendingBlobs) {
callback(bloblessData);
}
};

fileReader.readAsArrayBuffer(obj); // blob -> arraybuffer
} else if (Array.isArray(obj)) {
// handle array
for (let i = 0; i < obj.length; i++) {
_removeBlobs(obj[i], i, obj);
}
} else if (typeof obj === "object" && !isBuf(obj)) {
// and object
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
_removeBlobs(obj[key], key, obj);
}
}
}
}

let pendingBlobs = 0;
let bloblessData = data;
_removeBlobs(bloblessData);
if (!pendingBlobs) {
callback(bloblessData);
}
}
26 changes: 11 additions & 15 deletions lib/index.ts
@@ -1,6 +1,6 @@
import * as Emitter from "component-emitter";
import * as binary from "./binary";
import isBuf from "./is-buffer";
import isBinary from "./is-binary";
import debugModule from "debug";

const debug = debugModule("socket.io-parser");
Expand Down Expand Up @@ -41,20 +41,18 @@ export class Encoder {
* buffer sequence, depending on packet type.
*
* @param {Object} obj - packet object
* @param {Function} callback - function to handle encodings (likely engine.write)
* @return Calls callback with Array of encodings
*/
public encode(obj: Packet, callback: Function) {
public encode(obj: Packet) {
debug("encoding packet %j", obj);

if (
obj.type === PacketType.BINARY_EVENT ||
obj.type === PacketType.BINARY_ACK
) {
this.encodeAsBinary(obj, callback);
return this.encodeAsBinary(obj);
} else {
const encoding = this.encodeAsString(obj);
callback([encoding]);
return [encoding];
}
}

Expand Down Expand Up @@ -100,15 +98,13 @@ export class Encoder {
* a list of buffers.
*/

private encodeAsBinary(obj: Packet, callback: Function) {
binary.removeBlobs(obj, (bloblessData) => {
const deconstruction = binary.deconstructPacket(bloblessData);
const pack = this.encodeAsString(deconstruction.packet);
const buffers = deconstruction.buffers;
private encodeAsBinary(obj: Packet) {
const deconstruction = binary.deconstructPacket(obj);
const pack = this.encodeAsString(deconstruction.packet);
const buffers = deconstruction.buffers;

buffers.unshift(pack); // add packet info to beginning of data list
callback(buffers); // write all the buffers
});
buffers.unshift(pack); // add packet info to beginning of data list
return buffers; // write all the buffers
}
}

Expand Down Expand Up @@ -149,7 +145,7 @@ export class Decoder extends Emitter {
// non-binary full packet
super.emit("decoded", packet);
}
} else if (isBuf(obj) || obj.base64) {
} else if (isBinary(obj) || obj.base64) {
// raw binary data
if (!this.reconstructor) {
throw new Error("got binary data when not reconstructing a packet");
Expand Down
34 changes: 34 additions & 0 deletions lib/is-binary.ts
@@ -0,0 +1,34 @@
const withNativeBuffer: boolean =
typeof Buffer === "function" && typeof Buffer.isBuffer === "function";
const withNativeArrayBuffer: boolean = typeof ArrayBuffer === "function";

const isView = (obj: any) => {
return typeof ArrayBuffer.isView === "function"
? ArrayBuffer.isView(obj)
: obj.buffer instanceof ArrayBuffer;
};

const toString = Object.prototype.toString;
const withNativeBlob =
typeof Blob === "function" ||
(typeof Blob !== "undefined" &&
toString.call(Blob) === "[object BlobConstructor]");
const withNativeFile =
typeof File === "function" ||
(typeof File !== "undefined" &&
toString.call(File) === "[object FileConstructor]");

/**
* Returns true if obj is a Buffer, an ArrayBuffer, a Blob or a File.
*
* @private
*/

export default function isBinary(obj: any) {
return (
(withNativeBuffer && Buffer.isBuffer(obj)) ||
(withNativeArrayBuffer && (obj instanceof ArrayBuffer || isView(obj))) ||
(withNativeBlob && obj instanceof Blob) ||
(withNativeFile && obj instanceof File)
);
}
22 changes: 0 additions & 22 deletions lib/is-buffer.ts

This file was deleted.

18 changes: 9 additions & 9 deletions test/arraybuffer.js
Expand Up @@ -55,16 +55,16 @@ describe('parser', () => {
nsp: '/'
};

encoder.encode(packet, encodedPackets => {
var decoder = new Decoder();
decoder.on('decoded', packet => {
throw new Error("received a packet when not all binary data was sent.");
});
const encodedPackets = encoder.encode(packet);

decoder.add(encodedPackets[0]); // add metadata
decoder.add(encodedPackets[1]); // add first attachment
decoder.destroy(); // destroy before all data added
expect(decoder.reconstructor.buffers.length).to.be(0); // expect that buffer is clean
var decoder = new Decoder();
decoder.on('decoded', packet => {
throw new Error("received a packet when not all binary data was sent.");
});

decoder.add(encodedPackets[0]); // add metadata
decoder.add(encodedPackets[1]); // add first attachment
decoder.destroy(); // destroy before all data added
expect(decoder.reconstructor.buffers.length).to.be(0); // expect that buffer is clean
});
});
40 changes: 20 additions & 20 deletions test/helpers.js
Expand Up @@ -4,33 +4,33 @@ const encoder = new parser.Encoder();

// tests encoding and decoding a single packet
module.exports.test = (obj, done) => {
encoder.encode(obj, encodedPackets => {
const decoder = new parser.Decoder();
decoder.on('decoded', packet => {
expect(packet).to.eql(obj);
done();
});

decoder.add(encodedPackets[0]);
const encodedPackets = encoder.encode(obj);

const decoder = new parser.Decoder();
decoder.on('decoded', packet => {
expect(packet).to.eql(obj);
done();
});

decoder.add(encodedPackets[0]);
}

// tests encoding of binary packets
module.exports.test_bin = (obj, done) => {
const originalData = obj.data;
encoder.encode(obj, encodedPackets => {
const decoder = new parser.Decoder();
decoder.on('decoded', packet => {
obj.data = originalData;
obj.attachments = undefined;
expect(obj).to.eql(packet);
done();
});

for (let i = 0; i < encodedPackets.length; i++) {
decoder.add(encodedPackets[i]);
}
const encodedPackets = encoder.encode(obj);

const decoder = new parser.Decoder();
decoder.on('decoded', packet => {
obj.data = originalData;
obj.attachments = undefined;
expect(obj).to.eql(packet);
done();
});

for (let i = 0; i < encodedPackets.length; i++) {
decoder.add(encodedPackets[i]);
}
}

// array buffer's slice is native code that is not transported across
Expand Down

0 comments on commit 28d4f03

Please sign in to comment.