diff --git a/index.js b/index.js index aaf70f3..6dd75a0 100644 --- a/index.js +++ b/index.js @@ -3,66 +3,56 @@ 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); } @@ -70,7 +60,7 @@ class Blob { stream() { const readable = new ReadableStream(); readable._read = () => { }; - readable.push(this[BUFFER]); + readable.push(wm.get(this).buffer); readable.push(null); return readable; } @@ -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; } } diff --git a/test.js b/test.js index a25be73..115835f 100644 --- a/test.js +++ b/test.js @@ -3,12 +3,32 @@ 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]); @@ -16,12 +36,27 @@ test('Blob size', t => { }); 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'; @@ -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 => {