-
Notifications
You must be signed in to change notification settings - Fork 29
/
index.js
178 lines (154 loc) · 4.43 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
const {Readable} = require('stream');
/**
* @type {WeakMap<Blob, {type: string, size: number, parts: (Blob | Buffer)[] }>}
*/
const wm = new WeakMap();
async function * read(parts) {
for (const part of parts) {
if ('stream' in part) {
yield * part.stream();
} else {
yield part;
}
}
}
class Blob {
/**
* The Blob() constructor returns a new Blob object. The content
* of the blob consists of the concatenation of the values given
* in the parameter array.
*
* @param {(ArrayBufferLike | ArrayBufferView | Blob | Buffer | string)[]} blobParts
* @param {{ type?: string }} [options]
*/
constructor(blobParts = [], options = {type: ''}) {
let size = 0;
const parts = blobParts.map(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;
} else {
buffer = Buffer.from(typeof element === 'string' ? element : String(element));
}
size += buffer.length || buffer.size || 0;
return buffer;
});
const type = options.type === undefined ? '' : String(options.type).toLowerCase();
wm.set(this, {
type: /[^\u0020-\u007E]/.test(type) ? '' : type,
size,
parts
});
}
/**
* The Blob interface's size property returns the
* size of the Blob in bytes.
*/
get size() {
return wm.get(this).size;
}
/**
* The type property of a Blob object returns the MIME type of the file.
*/
get type() {
return wm.get(this).type;
}
/**
* The text() method in the Blob interface returns a Promise
* that resolves with a string containing the contents of
* the blob, interpreted as UTF-8.
*
* @return {Promise<string>}
*/
async text() {
return Buffer.from(await this.arrayBuffer()).toString();
}
/**
* The arrayBuffer() method in the Blob interface returns a
* Promise that resolves with the contents of the blob as
* binary data contained in an ArrayBuffer.
*
* @return {Promise<ArrayBuffer>}
*/
async arrayBuffer() {
const data = new Uint8Array(this.size);
let offset = 0;
for await (const chunk of this.stream()) {
data.set(chunk, offset);
offset += chunk.length;
}
return data.buffer;
}
/**
* The Blob interface's stream() method is difference from native
* and uses node streams instead of whatwg streams.
*
* @returns {Readable} Node readable stream
*/
stream() {
return Readable.from(read(wm.get(this).parts));
}
/**
* The Blob interface's slice() method creates and returns a
* new Blob object which contains data from a subset of the
* blob on which it's called.
*
* @param {number} [start]
* @param {number} [end]
* @param {string} [type]
*/
slice(start = 0, end = this.size, type = '') {
const {size} = this;
let relativeStart = start < 0 ? Math.max(size + start, 0) : Math.min(start, size);
let relativeEnd = end < 0 ? Math.max(size + end, 0) : Math.min(end, size);
const span = Math.max(relativeEnd - relativeStart, 0);
const parts = wm.get(this).parts.values();
const blobParts = [];
let added = 0;
for (const part of parts) {
const size = ArrayBuffer.isView(part) ? part.byteLength : part.size;
if (relativeStart && size <= relativeStart) {
// Skip the beginning and change the relative
// start & end position as we skip the unwanted parts
relativeStart -= size;
relativeEnd -= size;
} else {
const chunk = part.slice(relativeStart, Math.min(size, relativeEnd));
blobParts.push(chunk);
added += ArrayBuffer.isView(chunk) ? chunk.byteLength : chunk.size;
relativeStart = 0; // All next sequental parts should start at 0
// don't add the overflow to new blobParts
if (added >= span) {
break;
}
}
}
const blob = new Blob([], {type});
Object.assign(wm.get(blob), {size: span, parts: blobParts});
return blob;
}
get [Symbol.toStringTag]() {
return 'Blob';
}
static [Symbol.hasInstance](object) {
return (
typeof object === 'object' &&
typeof object.stream === 'function' &&
object.stream.length === 0 &&
typeof object.constructor === 'function' &&
/^(Blob|File)$/.test(object[Symbol.toStringTag])
);
}
}
Object.defineProperties(Blob.prototype, {
size: {enumerable: true},
type: {enumerable: true},
slice: {enumerable: true}
});
module.exports = Blob;