1+ /*! fetch-blob. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
2+
3+ // 64 KiB (same size chrome slice theirs blob into Uint8array's)
4+ const POOL_SIZE = 65536
5+
6+ /**
7+ * @param {(Blob | Uint8Array)[] } parts
8+ * @param {boolean } clone
9+ * @returns {AsyncIterableIterator<Uint8Array> }
10+ */
11+ async function * toIterator ( parts , clone ) {
12+ for ( const part of parts ) {
13+ if ( ArrayBuffer . isView ( part ) ) {
14+ if ( clone ) {
15+ let position = part . byteOffset
16+ const end = part . byteOffset + part . byteLength
17+ while ( position !== end ) {
18+ const size = Math . min ( end - position , POOL_SIZE )
19+ const chunk = part . buffer . slice ( position , position + size )
20+ position += chunk . byteLength
21+ yield new Uint8Array ( chunk )
22+ }
23+ } else {
24+ yield part
25+ }
26+ } else {
27+ // @ts -ignore TS Think blob.stream() returns a node:stream
28+ yield * part . stream ( )
29+ }
30+ }
31+ }
32+
33+ const _Blob = class Blob {
34+ /** @type {Array.<(Blob|Uint8Array)> } */
35+ #parts = [ ]
36+ #type = ''
37+ #size = 0
38+ #endings = 'transparent'
39+
40+ /**
41+ * The Blob() constructor returns a new Blob object. The content
42+ * of the blob consists of the concatenation of the values given
43+ * in the parameter array.
44+ *
45+ * @param {* } blobParts
46+ * @param {{ type?: string, endings?: string } } [options]
47+ */
48+ constructor ( blobParts = [ ] , options = { } ) {
49+ if ( typeof blobParts !== 'object' || blobParts === null ) {
50+ throw new TypeError ( 'Failed to construct \'Blob\': The provided value cannot be converted to a sequence.' )
51+ }
52+
53+ if ( typeof blobParts [ Symbol . iterator ] !== 'function' ) {
54+ throw new TypeError ( 'Failed to construct \'Blob\': The object must have a callable @@iterator property.' )
55+ }
56+
57+ if ( typeof options !== 'object' && typeof options !== 'function' ) {
58+ throw new TypeError ( 'Failed to construct \'Blob\': parameter 2 cannot convert to dictionary.' )
59+ }
60+
61+ if ( options === null ) options = { }
62+
63+ const encoder = new TextEncoder ( )
64+ for ( const element of blobParts ) {
65+ let part
66+ if ( ArrayBuffer . isView ( element ) ) {
67+ part = new Uint8Array ( element . buffer . slice ( element . byteOffset , element . byteOffset + element . byteLength ) )
68+ } else if ( element instanceof ArrayBuffer ) {
69+ part = new Uint8Array ( element . slice ( 0 ) )
70+ } else if ( element instanceof Blob ) {
71+ part = element
72+ } else {
73+ part = encoder . encode ( `${ element } ` )
74+ }
75+
76+ const size = ArrayBuffer . isView ( part ) ? part . byteLength : part . size
77+ // Avoid pushing empty parts into the array to better GC them
78+ if ( size ) {
79+ this . #size += size
80+ this . #parts. push ( part )
81+ }
82+ }
83+
84+ this . #endings = `${ options . endings === undefined ? 'transparent' : options . endings } `
85+ const type = options . type === undefined ? '' : String ( options . type )
86+ this . #type = / ^ [ \x20 - \x7E ] * $ / . test ( type ) ? type : ''
87+ }
88+
89+ /**
90+ * The Blob interface's size property returns the
91+ * size of the Blob in bytes.
92+ */
93+ get size ( ) {
94+ return this . #size
95+ }
96+
97+ /**
98+ * The type property of a Blob object returns the MIME type of the file.
99+ */
100+ get type ( ) {
101+ return this . #type
102+ }
103+
104+ /**
105+ * The text() method in the Blob interface returns a Promise
106+ * that resolves with a string containing the contents of
107+ * the blob, interpreted as UTF-8.
108+ *
109+ * @return {Promise<string> }
110+ */
111+ async text ( ) {
112+ // More optimized than using this.arrayBuffer()
113+ // that requires twice as much ram
114+ const decoder = new TextDecoder ( )
115+ let str = ''
116+ for await ( const part of toIterator ( this . #parts, false ) ) {
117+ str += decoder . decode ( part , { stream : true } )
118+ }
119+ // Remaining
120+ str += decoder . decode ( )
121+ return str
122+ }
123+
124+ /**
125+ * The arrayBuffer() method in the Blob interface returns a
126+ * Promise that resolves with the contents of the blob as
127+ * binary data contained in an ArrayBuffer.
128+ *
129+ * @return {Promise<ArrayBuffer> }
130+ */
131+ async arrayBuffer ( ) {
132+ const data = new Uint8Array ( this . size )
133+ let offset = 0
134+ for await ( const chunk of toIterator ( this . #parts, false ) ) {
135+ data . set ( chunk , offset )
136+ offset += chunk . length
137+ }
138+
139+ return data . buffer
140+ }
141+
142+ stream ( ) {
143+ const it = toIterator ( this . #parts, true )
144+
145+ return new globalThis . ReadableStream ( {
146+ // @ts -ignore
147+ type : 'bytes' ,
148+ async pull ( ctrl ) {
149+ const chunk = await it . next ( )
150+ chunk . done ? ctrl . close ( ) : ctrl . enqueue ( chunk . value )
151+ } ,
152+
153+ async cancel ( ) {
154+ await it . return ( )
155+ }
156+ } )
157+ }
158+
159+ /**
160+ * The Blob interface's slice() method creates and returns a
161+ * new Blob object which contains data from a subset of the
162+ * blob on which it's called.
163+ *
164+ * @param {number } [start]
165+ * @param {number } [end]
166+ * @param {string } [type]
167+ */
168+ slice ( start = 0 , end = this . size , type = '' ) {
169+ const { size } = this
170+
171+ let relativeStart = start < 0 ? Math . max ( size + start , 0 ) : Math . min ( start , size )
172+ let relativeEnd = end < 0 ? Math . max ( size + end , 0 ) : Math . min ( end , size )
173+
174+ const span = Math . max ( relativeEnd - relativeStart , 0 )
175+ const parts = this . #parts
176+ const blobParts = [ ]
177+ let added = 0
178+
179+ for ( const part of parts ) {
180+ // don't add the overflow to new blobParts
181+ if ( added >= span ) {
182+ break
183+ }
184+
185+ const size = ArrayBuffer . isView ( part ) ? part . byteLength : part . size
186+ if ( relativeStart && size <= relativeStart ) {
187+ // Skip the beginning and change the relative
188+ // start & end position as we skip the unwanted parts
189+ relativeStart -= size
190+ relativeEnd -= size
191+ } else {
192+ let chunk
193+ if ( ArrayBuffer . isView ( part ) ) {
194+ chunk = part . subarray ( relativeStart , Math . min ( size , relativeEnd ) )
195+ added += chunk . byteLength
196+ } else {
197+ chunk = part . slice ( relativeStart , Math . min ( size , relativeEnd ) )
198+ added += chunk . size
199+ }
200+ relativeEnd -= size
201+ blobParts . push ( chunk )
202+ relativeStart = 0 // All next sequential parts should start at 0
203+ }
204+ }
205+
206+ const blob = new Blob ( [ ] , { type : `${ type } ` } )
207+ blob . #size = span
208+ blob . #parts = blobParts
209+
210+ return blob
211+ }
212+
213+ get [ Symbol . toStringTag ] ( ) {
214+ return 'Blob'
215+ }
216+
217+ static [ Symbol . hasInstance ] ( object ) {
218+ return (
219+ object &&
220+ typeof object === 'object' &&
221+ typeof object . constructor === 'function' &&
222+ (
223+ typeof object . stream === 'function' ||
224+ typeof object . arrayBuffer === 'function'
225+ ) &&
226+ / ^ ( B l o b | F i l e ) $ / . test ( object [ Symbol . toStringTag ] )
227+ )
228+ }
229+ }
230+
231+ Object . defineProperties ( _Blob . prototype , {
232+ size : { enumerable : true } ,
233+ type : { enumerable : true } ,
234+ slice : { enumerable : true }
235+ } )
236+
237+ /** @type {typeof globalThis.Blob } */
238+ export const Blob = _Blob
239+
240+ const _File = class File extends Blob {
241+ #lastModified = 0
242+ #name = ''
243+
244+ /**
245+ * @param {*[] } fileBits
246+ * @param {string } fileName
247+ * @param {{lastModified?: number, type?: string} } options
248+ */ // @ts -ignore
249+ constructor ( fileBits , fileName , options = { } ) {
250+ if ( arguments . length < 2 ) {
251+ throw new TypeError ( `Failed to construct 'File': 2 arguments required, but only ${ arguments . length } present.` )
252+ }
253+ super ( fileBits , options )
254+
255+ if ( options === null ) options = { }
256+
257+ // Simulate WebIDL type casting for NaN value in lastModified option.
258+ const lastModified = options . lastModified === undefined ? Date . now ( ) : Number ( options . lastModified )
259+ if ( ! Number . isNaN ( lastModified ) ) {
260+ this . #lastModified = lastModified
261+ }
262+
263+ this . #name = String ( fileName )
264+ }
265+
266+ get name ( ) {
267+ return this . #name
268+ }
269+
270+ get lastModified ( ) {
271+ return this . #lastModified
272+ }
273+
274+ get [ Symbol . toStringTag ] ( ) {
275+ return 'File'
276+ }
277+
278+ static [ Symbol . hasInstance ] ( object ) {
279+ return ! ! object && object instanceof Blob &&
280+ / ^ ( F i l e ) $ / . test ( object [ Symbol . toStringTag ] )
281+ }
282+ }
283+
284+ /** @type {typeof globalThis.File } */ // @ts -ignore
285+ export const File = _File
0 commit comments