Skip to content

Commit

Permalink
Use a WeakMap for private properties (#42)
Browse files Browse the repository at this point in the history
* options.buffer isn't a valid option

* truly private internal properties

* 100% test coverage
  • Loading branch information
jimmywarting authored Jun 6, 2020
1 parent 960f7e5 commit 870be1e
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 60 deletions.
93 changes: 41 additions & 52 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,74 +3,64 @@

const {Readable: ReadableStream} = require('stream');

const BUFFER = Symbol('buffer');
const TYPE = Symbol('type');
const wm = new WeakMap();

class Blob {
constructor(...args) {
this[TYPE] = '';

const blobParts = args[0];
const options = args[1];

constructor(blobParts = [], options = {type: ''}) {
const buffers = [];
/* eslint-disable-next-line no-unused-vars */
let size = 0;

if (blobParts) {
blobParts.forEach(element => {
let buffer;
if (element instanceof Buffer) {
buffer = element;
} else if (ArrayBuffer.isView(element)) {
buffer = Buffer.from(element.buffer, element.byteOffset, element.byteLength);
} else if (element instanceof ArrayBuffer) {
buffer = Buffer.from(element);
} else if (element instanceof Blob) {
buffer = element[BUFFER];
} else {
buffer = Buffer.from(typeof element === 'string' ? element : String(element));
}

size += buffer.length;
buffers.push(buffer);
});
}

this[BUFFER] = Buffer.concat(buffers);

const type = options && options.type !== undefined && String(options.type).toLowerCase();
if (type && !/[^\u0020-\u007E]/.test(type)) {
this[TYPE] = type;
}

if (options && Buffer.isBuffer(options.buffer)) {
this[BUFFER] = options.buffer;
}
blobParts.forEach(element => {
let buffer;
if (element instanceof Buffer) {
buffer = element;
} else if (ArrayBuffer.isView(element)) {
buffer = Buffer.from(element.buffer, element.byteOffset, element.byteLength);
} else if (element instanceof ArrayBuffer) {
buffer = Buffer.from(element);
} else if (element instanceof Blob) {
buffer = wm.get(element).buffer;
} else {
buffer = Buffer.from(typeof element === 'string' ? element : String(element));
}

size += buffer.length;
buffers.push(buffer);
});

const buffer = Buffer.concat(buffers, size);

const type = options.type === undefined ? '' : String(options.type).toLowerCase();

wm.set(this, {
type: /[^\u0020-\u007E]/.test(type) ? '' : type,
size,
buffer
});
}

get size() {
return this[BUFFER].length;
return wm.get(this).size;
}

get type() {
return this[TYPE];
return wm.get(this).type;
}

text() {
return Promise.resolve(this[BUFFER].toString());
return Promise.resolve(wm.get(this).buffer.toString());
}

arrayBuffer() {
const buf = this[BUFFER];
const buf = wm.get(this).buffer;
const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
return Promise.resolve(ab);
}

stream() {
const readable = new ReadableStream();
readable._read = () => { };
readable.push(this[BUFFER]);
readable.push(wm.get(this).buffer);
readable.push(null);
return readable;
}
Expand All @@ -88,30 +78,29 @@ class Blob {
let relativeEnd;

if (start === undefined) {
relativeStart = 0;
relativeStart = 0; //
} else if (start < 0) {
relativeStart = Math.max(size + start, 0);
relativeStart = Math.max(size + start, 0); //
} else {
relativeStart = Math.min(start, size);
}

if (end === undefined) {
relativeEnd = size;
relativeEnd = size; //
} else if (end < 0) {
relativeEnd = Math.max(size + end, 0);
relativeEnd = Math.max(size + end, 0); //
} else {
relativeEnd = Math.min(end, size);
}

const span = Math.max(relativeEnd - relativeStart, 0);

const buffer = this[BUFFER];
const slicedBuffer = buffer.slice(
const slicedBuffer = wm.get(this).buffer.slice(
relativeStart,
relativeStart + span
);
const blob = new Blob([], {type: args[2]});
blob[BUFFER] = slicedBuffer;
const _ = wm.get(blob);
_.buffer = slicedBuffer;
return blob;
}
}
Expand Down
67 changes: 59 additions & 8 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,60 @@ const Blob = require('.');
const getStream = require('get-stream');
const {Response} = require('node-fetch');

test('Blob ctor', t => {
test('new Blob()', t => {
const blob = new Blob(); // eslint-disable-line no-unused-vars
t.pass();
});

test('new Blob(parts)', t => {
const data = 'a=1';
const blob = new Blob([data]); // eslint-disable-line no-unused-vars
t.pass();
});

test('Blob ctor parts', async t => {
const parts = [
'a',
new Uint8Array([98]),
new Uint16Array([25699]),
new Uint8Array([101]).buffer,
Buffer.from('f'),
new Blob(['g']),
{}
];

const blob = new Blob(parts);
t.is(await blob.text(), 'abcdefg[object Object]');
});

test('Blob size', t => {
const data = 'a=1';
const blob = new Blob([data]);
t.is(blob.size, data.length);
});

test('Blob type', t => {
const data = 'a=1';
const type = 'text/plain';
const blob = new Blob([data], {type});
const blob = new Blob([], {type});
t.is(blob.type, type);
});

test('Blob slice type', t => {
const type = 'text/plain';
const blob = new Blob().slice(0, 0, type);
t.is(blob.type, type);
});

test('invalid Blob type', t => {
const blob = new Blob([], {type: '\u001Ftext/plain'});
t.is(blob.type, '');
});

test('invalid Blob slice type', t => {
const blob = new Blob().slice(0, 0, '\u001Ftext/plain');
t.is(blob.type, '');
});

test('Blob text()', async t => {
const data = 'a=1';
const type = 'text/plain';
Expand Down Expand Up @@ -55,11 +90,27 @@ test('Blob toString()', t => {
});

test('Blob slice()', async t => {
const data = 'a=1';
const type = 'text/plain';
const blob = new Blob([data], {type});
const blob2 = blob.slice(0, 1);
t.is(await blob2.text(), data.slice(0, 1));
const data = 'abcdefgh';
const blob = new Blob([data]).slice();
t.is(await blob.text(), data);
});

test('Blob slice(0, 1)', async t => {
const data = 'abcdefgh';
const blob = new Blob([data]).slice(0, 1);
t.is(await blob.text(), 'a');
});

test('Blob slice(-1)', async t => {
const data = 'abcdefgh';
const blob = new Blob([data]).slice(-1);
t.is(await blob.text(), 'h');
});

test('Blob slice(0, -1)', async t => {
const data = 'abcdefgh';
const blob = new Blob([data]).slice(0, -1);
t.is(await blob.text(), 'abcdefg');
});

test('Blob works with node-fetch Response.blob()', async t => {
Expand Down

0 comments on commit 870be1e

Please sign in to comment.