Skip to content

Commit 870be1e

Browse files
authored
Use a WeakMap for private properties (#42)
* options.buffer isn't a valid option * truly private internal properties * 100% test coverage
1 parent 960f7e5 commit 870be1e

File tree

2 files changed

+100
-60
lines changed

2 files changed

+100
-60
lines changed

index.js

Lines changed: 41 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -3,74 +3,64 @@
33

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

6-
const BUFFER = Symbol('buffer');
7-
const TYPE = Symbol('type');
6+
const wm = new WeakMap();
87

98
class Blob {
10-
constructor(...args) {
11-
this[TYPE] = '';
12-
13-
const blobParts = args[0];
14-
const options = args[1];
15-
9+
constructor(blobParts = [], options = {type: ''}) {
1610
const buffers = [];
17-
/* eslint-disable-next-line no-unused-vars */
1811
let size = 0;
1912

20-
if (blobParts) {
21-
blobParts.forEach(element => {
22-
let buffer;
23-
if (element instanceof Buffer) {
24-
buffer = element;
25-
} else if (ArrayBuffer.isView(element)) {
26-
buffer = Buffer.from(element.buffer, element.byteOffset, element.byteLength);
27-
} else if (element instanceof ArrayBuffer) {
28-
buffer = Buffer.from(element);
29-
} else if (element instanceof Blob) {
30-
buffer = element[BUFFER];
31-
} else {
32-
buffer = Buffer.from(typeof element === 'string' ? element : String(element));
33-
}
34-
35-
size += buffer.length;
36-
buffers.push(buffer);
37-
});
38-
}
39-
40-
this[BUFFER] = Buffer.concat(buffers);
41-
42-
const type = options && options.type !== undefined && String(options.type).toLowerCase();
43-
if (type && !/[^\u0020-\u007E]/.test(type)) {
44-
this[TYPE] = type;
45-
}
46-
47-
if (options && Buffer.isBuffer(options.buffer)) {
48-
this[BUFFER] = options.buffer;
49-
}
13+
blobParts.forEach(element => {
14+
let buffer;
15+
if (element instanceof Buffer) {
16+
buffer = element;
17+
} else if (ArrayBuffer.isView(element)) {
18+
buffer = Buffer.from(element.buffer, element.byteOffset, element.byteLength);
19+
} else if (element instanceof ArrayBuffer) {
20+
buffer = Buffer.from(element);
21+
} else if (element instanceof Blob) {
22+
buffer = wm.get(element).buffer;
23+
} else {
24+
buffer = Buffer.from(typeof element === 'string' ? element : String(element));
25+
}
26+
27+
size += buffer.length;
28+
buffers.push(buffer);
29+
});
30+
31+
const buffer = Buffer.concat(buffers, size);
32+
33+
const type = options.type === undefined ? '' : String(options.type).toLowerCase();
34+
35+
wm.set(this, {
36+
type: /[^\u0020-\u007E]/.test(type) ? '' : type,
37+
size,
38+
buffer
39+
});
5040
}
5141

5242
get size() {
53-
return this[BUFFER].length;
43+
return wm.get(this).size;
5444
}
5545

5646
get type() {
57-
return this[TYPE];
47+
return wm.get(this).type;
5848
}
5949

6050
text() {
61-
return Promise.resolve(this[BUFFER].toString());
51+
return Promise.resolve(wm.get(this).buffer.toString());
6252
}
6353

6454
arrayBuffer() {
65-
const buf = this[BUFFER];
55+
const buf = wm.get(this).buffer;
6656
const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
6757
return Promise.resolve(ab);
6858
}
6959

7060
stream() {
7161
const readable = new ReadableStream();
7262
readable._read = () => { };
73-
readable.push(this[BUFFER]);
63+
readable.push(wm.get(this).buffer);
7464
readable.push(null);
7565
return readable;
7666
}
@@ -88,30 +78,29 @@ class Blob {
8878
let relativeEnd;
8979

9080
if (start === undefined) {
91-
relativeStart = 0;
81+
relativeStart = 0; //
9282
} else if (start < 0) {
93-
relativeStart = Math.max(size + start, 0);
83+
relativeStart = Math.max(size + start, 0); //
9484
} else {
9585
relativeStart = Math.min(start, size);
9686
}
9787

9888
if (end === undefined) {
99-
relativeEnd = size;
89+
relativeEnd = size; //
10090
} else if (end < 0) {
101-
relativeEnd = Math.max(size + end, 0);
91+
relativeEnd = Math.max(size + end, 0); //
10292
} else {
10393
relativeEnd = Math.min(end, size);
10494
}
10595

10696
const span = Math.max(relativeEnd - relativeStart, 0);
107-
108-
const buffer = this[BUFFER];
109-
const slicedBuffer = buffer.slice(
97+
const slicedBuffer = wm.get(this).buffer.slice(
11098
relativeStart,
11199
relativeStart + span
112100
);
113101
const blob = new Blob([], {type: args[2]});
114-
blob[BUFFER] = slicedBuffer;
102+
const _ = wm.get(blob);
103+
_.buffer = slicedBuffer;
115104
return blob;
116105
}
117106
}

test.js

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,60 @@ const Blob = require('.');
33
const getStream = require('get-stream');
44
const {Response} = require('node-fetch');
55

6-
test('Blob ctor', t => {
6+
test('new Blob()', t => {
7+
const blob = new Blob(); // eslint-disable-line no-unused-vars
8+
t.pass();
9+
});
10+
11+
test('new Blob(parts)', t => {
712
const data = 'a=1';
813
const blob = new Blob([data]); // eslint-disable-line no-unused-vars
914
t.pass();
1015
});
1116

17+
test('Blob ctor parts', async t => {
18+
const parts = [
19+
'a',
20+
new Uint8Array([98]),
21+
new Uint16Array([25699]),
22+
new Uint8Array([101]).buffer,
23+
Buffer.from('f'),
24+
new Blob(['g']),
25+
{}
26+
];
27+
28+
const blob = new Blob(parts);
29+
t.is(await blob.text(), 'abcdefg[object Object]');
30+
});
31+
1232
test('Blob size', t => {
1333
const data = 'a=1';
1434
const blob = new Blob([data]);
1535
t.is(blob.size, data.length);
1636
});
1737

1838
test('Blob type', t => {
19-
const data = 'a=1';
2039
const type = 'text/plain';
21-
const blob = new Blob([data], {type});
40+
const blob = new Blob([], {type});
41+
t.is(blob.type, type);
42+
});
43+
44+
test('Blob slice type', t => {
45+
const type = 'text/plain';
46+
const blob = new Blob().slice(0, 0, type);
2247
t.is(blob.type, type);
2348
});
2449

50+
test('invalid Blob type', t => {
51+
const blob = new Blob([], {type: '\u001Ftext/plain'});
52+
t.is(blob.type, '');
53+
});
54+
55+
test('invalid Blob slice type', t => {
56+
const blob = new Blob().slice(0, 0, '\u001Ftext/plain');
57+
t.is(blob.type, '');
58+
});
59+
2560
test('Blob text()', async t => {
2661
const data = 'a=1';
2762
const type = 'text/plain';
@@ -55,11 +90,27 @@ test('Blob toString()', t => {
5590
});
5691

5792
test('Blob slice()', async t => {
58-
const data = 'a=1';
59-
const type = 'text/plain';
60-
const blob = new Blob([data], {type});
61-
const blob2 = blob.slice(0, 1);
62-
t.is(await blob2.text(), data.slice(0, 1));
93+
const data = 'abcdefgh';
94+
const blob = new Blob([data]).slice();
95+
t.is(await blob.text(), data);
96+
});
97+
98+
test('Blob slice(0, 1)', async t => {
99+
const data = 'abcdefgh';
100+
const blob = new Blob([data]).slice(0, 1);
101+
t.is(await blob.text(), 'a');
102+
});
103+
104+
test('Blob slice(-1)', async t => {
105+
const data = 'abcdefgh';
106+
const blob = new Blob([data]).slice(-1);
107+
t.is(await blob.text(), 'h');
108+
});
109+
110+
test('Blob slice(0, -1)', async t => {
111+
const data = 'abcdefgh';
112+
const blob = new Blob([data]).slice(0, -1);
113+
t.is(await blob.text(), 'abcdefg');
63114
});
64115

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

0 commit comments

Comments
 (0)