mphttpx 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,15 +1,69 @@
1
1
  # MPHTTPX
2
2
 
3
- The `request` function of Mini Programs does not support invocation in a Promise style
4
- and differs significantly from the request style in browsers. This results in the
5
- need to write two different sets of code logic for the same request in Mini Programs
6
- versus browsers. Therefore, the `mphttpx` library aims to make requests in Mini Programs
7
- as consistent as possible with those in browsers. This project implements a subset of
8
- standard browser APIs, and these implemented features are sufficient to send requests
9
- using the full `XMLHttpRequest` and `fetch` in Mini Programs.
3
+ The `mphttpx` library aims to provide a more ES6-styled [Blob.js][0],
4
+ along with a `fetch` polyfill that works seamlessly with the Blob-polyfill.
5
+ This allows web code to be reused in other environments (such as mini-programs).
10
6
 
11
7
  ## Table of Contents
12
8
 
9
+ - [MPHTTPX](#mphttpx)
10
+ - [Table of Contents](#table-of-contents)
11
+ - [Features](#features)
12
+ - [Installation](#installation)
13
+ - [Mini-Program Support](#mini-program-support)
14
+ - [Usage](#usage)
15
+ - [TextEncoder](#textencoder)
16
+ - [Compatibility](#compatibility)
17
+ - [TextDecoder](#textdecoder)
18
+ - [Compatibility](#compatibility-1)
19
+ - [Blob](#blob)
20
+ - [Compatibility](#compatibility-2)
21
+ - [File](#file)
22
+ - [Compatibility](#compatibility-3)
23
+ - [FileReader](#filereader)
24
+ - [Compatibility](#compatibility-4)
25
+ - [FormData](#formdata)
26
+ - [Compatibility](#compatibility-5)
27
+ - [URLSearchParams](#urlsearchparams)
28
+ - [Compatibility](#compatibility-6)
29
+ - [fetch](#fetch)
30
+ - [Compatibility](#compatibility-7)
31
+ - [Headers](#headers)
32
+ - [Compatibility](#compatibility-8)
33
+ - [Request](#request)
34
+ - [Compatibility](#compatibility-9)
35
+ - [Response](#response)
36
+ - [Compatibility](#compatibility-10)
37
+ - [AbortController](#abortcontroller)
38
+ - [Compatibility](#compatibility-11)
39
+ - [AbortSignal](#abortsignal)
40
+ - [Compatibility](#compatibility-12)
41
+ - [EventTarget](#eventtarget)
42
+ - [Compatibility](#compatibility-13)
43
+ - [XMLHttpRequest (mini-programs)](#xmlhttprequest-mini-programs)
44
+ - [Compatibility](#compatibility-14)
45
+ - [UniApp \& Taro](#uniapp--taro)
46
+ - [Node.js](#nodejs)
47
+ - [License](#license)
48
+
49
+ ## Features
50
+
51
+ - **TextEncoder**
52
+ - **TextDecoder**
53
+ - **Blob**
54
+ - **File**
55
+ - **FileReader**
56
+ - **FormData**
57
+ - **URLSearchParams**
58
+ - **fetch**
59
+ - **Headers**
60
+ - **Request**
61
+ - **Response**
62
+ - **AbortController**
63
+ - **AbortSignal**
64
+ - **EventTarget**
65
+ - **XMLHttpRequest (mini-programs)**
66
+
13
67
  ## Installation
14
68
 
15
69
  ```
@@ -22,11 +76,550 @@ npm install mphttpx
22
76
  |:------:|:------:|:-----:|:---------:|:--:|:----:|:--:|:-------:|
23
77
  | Latest ✔ | Latest ✔ | Latest ✔ | Latest ✔ | Latest ✔ | Latest ✔ | Latest ✔ | Latest ✔ |
24
78
 
79
+ Note: Modern browsers such as Chrome, Firefox, Edge, and Safari do not need these polyfills;
80
+ the relevant implementations imported from the mphttpx library will directly return the native functions of the browser.
81
+
25
82
  ## Usage
26
83
 
27
- ### XMLHttpRequest
84
+ ### TextEncoder
85
+
86
+ ```javascript
87
+ import { TextEncoder } from "mphttpx";
88
+
89
+ const encoder = new TextEncoder();
90
+ const view = encoder.encode("€");
91
+ console.log(view); // Uint8Array(3) [226, 130, 172]
92
+ ```
93
+
94
+ #### Compatibility
95
+
96
+ | Property | Available | Description |
97
+ | --------- | --------- | -------------|
98
+ | encoding | ✔ | utf-8 |
99
+
100
+ | Method | Available | Description |
101
+ | ------- | --------- | -------------|
102
+ | encode(string) | ✔ |
103
+ | encodeInto(string, uint8Array) | ✔ |
104
+
105
+ ### TextDecoder
106
+
107
+ ```javascript
108
+ import { TextDecoder } from "mphttpx";
109
+
110
+ const utf8decoder = new TextDecoder(); // default 'utf-8'
111
+ const encodedText = new Uint8Array([240, 160, 174, 183]);
112
+
113
+ console.log(utf8decoder.decode(encodedText));
114
+ ```
115
+
116
+ #### Compatibility
117
+
118
+ | Property | Available | Description |
119
+ | --------- | --------- | -------------|
120
+ | encoding | ✔ | utf-8 |
121
+ | fatal | ✔ |
122
+ | ignoreBOM | ✔ |
123
+
124
+ | Method | Available | Description |
125
+ | ------- | --------- | -------------|
126
+ | decode() | ✔ |
127
+ | decode(buffer) | ✔ |
128
+ | decode(buffer, options) | ✔ |
129
+
130
+ ### Blob
131
+
132
+ Creating a blob
133
+
134
+ ```javascript
135
+ import { Blob } from "mphttpx";
136
+
137
+ const obj = { hello: "world" };
138
+ const blob = new Blob([JSON.stringify(obj, null, 2)], {
139
+ type: "application/json",
140
+ });
141
+ ```
142
+
143
+ Extracting data from a blob
144
+
145
+ ```javascript
146
+ import { Blob, FileReader } from "mphttpx";
147
+
148
+ const reader = new FileReader();
149
+ reader.addEventListener("loadend", () => {
150
+ // reader.result contains the contents of blob as a typed array
151
+ });
152
+ reader.readAsArrayBuffer(blob);
153
+ ```
154
+
155
+ #### Compatibility
156
+
157
+ | Property | Available | Description |
158
+ | --------- | --------- | -------------|
159
+ | size | ✔ |
160
+ | type | ✔ |
161
+
162
+ | Method | Available | Description |
163
+ | ------- | --------- | -------------|
164
+ | arrayBuffer() | ✔ |
165
+ | bytes() | ✔ |
166
+ | slice() | ✔ |
167
+ | slice(start) | ✔ |
168
+ | slice(start, end) | ✔ |
169
+ | slice(start, end, contentType) | ✔ |
170
+ | stream() | ✖ |
171
+ | text() | ✔ |
172
+
173
+ ### File
174
+
175
+ ```javascript
176
+ import { File } from "mphttpx";
177
+
178
+ const file = new File(["foo"], "foo.txt", {
179
+ type: "text/plain",
180
+ });
181
+ ```
182
+
183
+ #### Compatibility
184
+
185
+ | Property | Available | Description |
186
+ | --------- | --------- | -------------|
187
+ | lastModified | ✔ |
188
+ | name | ✔ |
189
+ | webkitRelativePath | ✖ |
190
+
191
+ ### FileReader
192
+
193
+ ```javascript
194
+ import { File, FileReader } from "mphttpx";
195
+
196
+ // Read the file
197
+ const reader = new FileReader();
198
+ reader.onload = () => {
199
+ console.log(reader.result);
200
+ };
201
+ reader.readAsText(file);
202
+ ```
203
+
204
+ #### Compatibility
205
+
206
+ | Property | Available | Description |
207
+ | --------- | --------- | -------------|
208
+ | error | ✔ |
209
+ | readyState | ✔ |
210
+ | result | ✔ |
211
+
212
+ | Method | Available | Description |
213
+ | ------- | --------- | -------------|
214
+ | abort() | ✔ | simulated |
215
+ | readAsArrayBuffer() | ✔ |
216
+ | readAsBinaryString() | ✔ |
217
+ | readAsDataURL() | ✔ |
218
+ | readAsText() | ✔ |
219
+
220
+ ### FormData
221
+
222
+ ```javascript
223
+ import { FormData } from "mphttpx";
224
+
225
+ const formData = new FormData();
226
+ formData.append("username", "Chris");
227
+ ```
228
+
229
+ #### Compatibility
230
+
231
+ | Constructor | Available | Description |
232
+ | ----------- | --------- | -------------|
233
+ | new FormData() | ✔ |
234
+ | new FormData(form) | ✖ |
235
+ | new FormData(form, submitter) | ✖ |
236
+
237
+ | Method | Available | Description |
238
+ | ------- | --------- | -------------|
239
+ | append(name, value) | ✔ |
240
+ | append(name, value, filename) | ✔ |
241
+ | delete(name) | ✔ |
242
+ | entries() | ✔ |
243
+ | get(name) | ✔ |
244
+ | getAll(name) | ✔ |
245
+ | has(name) | ✔ |
246
+ | keys() | ✔ |
247
+ | set(name, value) | ✔ |
248
+ | set(name, value, filename) | ✔ |
249
+ | values() | ✔ |
250
+
251
+ ### URLSearchParams
252
+
253
+ ```javascript
254
+ import { URLSearchParams } from "mphttpx";
255
+
256
+ const paramsString = "q=URLUtils.searchParams&topic=api";
257
+ const searchParams = new URLSearchParams(paramsString);
258
+
259
+ // Iterating the search parameters
260
+ for (const p of searchParams) {
261
+ console.log(p);
262
+ }
263
+
264
+ console.log(searchParams.has("topic")); // true
265
+ console.log(searchParams.has("topic", "fish")); // false
266
+ console.log(searchParams.get("topic") === "api"); // true
267
+ console.log(searchParams.getAll("topic")); // ["api"]
268
+ console.log(searchParams.get("foo") === null); // true
269
+ console.log(searchParams.append("topic", "webdev"));
270
+ console.log(searchParams.toString()); // "q=URLUtils.searchParams&topic=api&topic=webdev"
271
+ console.log(searchParams.set("topic", "More webdev"));
272
+ console.log(searchParams.toString()); // "q=URLUtils.searchParams&topic=More+webdev"
273
+ console.log(searchParams.delete("topic"));
274
+ console.log(searchParams.toString()); // "q=URLUtils.searchParams"
275
+ ```
276
+
277
+ Search parameters can also be an object.
278
+
279
+ ```javascript
280
+ import { URLSearchParams } from "mphttpx";
281
+
282
+ const paramsObj = { foo: "bar", baz: "bar" };
283
+ const searchParams = new URLSearchParams(paramsObj);
284
+
285
+ console.log(searchParams.toString()); // "foo=bar&baz=bar"
286
+ console.log(searchParams.has("foo")); // true
287
+ console.log(searchParams.get("foo")); // "bar"
288
+ ```
289
+
290
+ #### Compatibility
291
+
292
+ | Property | Available | Description |
293
+ | --------- | --------- | -------------|
294
+ | size | ✔ |
295
+
296
+ | Method | Available | Description |
297
+ | ------- | --------- | -------------|
298
+ | append(name, value) | ✔ |
299
+ | delete(name) | ✔ |
300
+ | delete(name, value) | ✔ |
301
+ | entries() | ✔ |
302
+ | forEach(callback) | ✔ |
303
+ | forEach(callback, thisArg) | ✔ |
304
+ | get(name) | ✔ |
305
+ | getAll(name) | ✔ |
306
+ | has(name) | ✔ |
307
+ | has(name, value) | ✔ |
308
+ | keys() | ✔ |
309
+ | set(name, value) | ✔ |
310
+ | sort() | ✔ |
311
+ | toString() | ✔ |
312
+ | values() | ✔ |
313
+
314
+ ### fetch
315
+
316
+ ```javascript
317
+ import { fetch } from "mphttpx";
318
+
319
+ fetch("http://example.com/movies.json")
320
+ .then((response) => response.json())
321
+ .then((data) => console.log(data));
322
+ ```
323
+
324
+ Using fetch() to POST JSON data
325
+
326
+ ```javascript
327
+ import { fetch } from "mphttpx";
328
+
329
+ const data = { username: "example" };
330
+
331
+ fetch("https://example.com/profile", {
332
+ method: "POST", // or 'PUT'
333
+ headers: {
334
+ "Content-Type": "application/json",
335
+ },
336
+ body: JSON.stringify(data),
337
+ })
338
+ .then((response) => response.json())
339
+ .then((data) => {
340
+ console.log("Success:", data);
341
+ })
342
+ .catch((error) => {
343
+ console.error("Error:", error);
344
+ });
345
+ ```
346
+
347
+ Uploading files
348
+
349
+ ```javascript
350
+ import { fetch, File } from "mphttpx";
351
+
352
+ const formData = new FormData();
28
353
 
29
- #### Example: GET
354
+ formData.append("username", "abc123");
355
+ formData.append("file", new File(["foo"], "foo.txt", { type: "text/plain" }));
356
+
357
+ fetch("https://example.com/profile/avatar", {
358
+ method: "PUT",
359
+ body: formData,
360
+ })
361
+ .then((response) => response.json())
362
+ .then((result) => {
363
+ console.log("Success:", result);
364
+ })
365
+ .catch((error) => {
366
+ console.error("Error:", error);
367
+ });
368
+ ```
369
+
370
+ | Syntax | Available | Description |
371
+ | ------- | --------- | -------------|
372
+ | fetch(resource) | ✔ |
373
+ | fetch(resource, options) | ✔ |
374
+
375
+ #### Compatibility
376
+
377
+ Refer to Request below.
378
+
379
+ ### Headers
380
+
381
+ ```javascript
382
+ import { Headers } from "mphttpx";
383
+
384
+ const myHeaders = new Headers();
385
+
386
+ myHeaders.append("Content-Type", "text/plain");
387
+ myHeaders.get("Content-Type"); // should return 'text/plain'
388
+ ```
389
+
390
+ The same can be achieved by passing an array of arrays or an object literal to the constructor:
391
+
392
+ ```javascript
393
+ import { Headers } from "mphttpx";
394
+
395
+ let myHeaders = new Headers({
396
+ "Content-Type": "text/plain",
397
+ });
398
+
399
+ // or, using an array of arrays:
400
+ myHeaders = new Headers([["Content-Type", "text/plain"]]);
401
+
402
+ myHeaders.get("Content-Type"); // should return 'text/plain'
403
+ ```
404
+
405
+ #### Compatibility
406
+
407
+ | Method | Available | Description |
408
+ | ------- | --------- | -------------|
409
+ | append(name, value) | ✔ |
410
+ | delete(name) | ✔ |
411
+ | entries() | ✔ |
412
+ | forEach(callbackFn) | ✔ |
413
+ | forEach(callbackFn, thisArg) | ✔ |
414
+ | get(name) | ✔ |
415
+ | getSetCookie() | ✔ |
416
+ | has(name) | ✔ |
417
+ | keys() | ✔ |
418
+ | set(name, value) | ✔ |
419
+ | values() | ✔ |
420
+
421
+ ### Request
422
+
423
+ ```javascript
424
+ import { fetch, Request } from "mphttpx";
425
+
426
+ const request = new Request("https://www.mozilla.org/favicon.ico");
427
+
428
+ const url = request.url;
429
+ const method = request.method;
430
+ const credentials = request.credentials;
431
+
432
+ fetch(request)
433
+ .then((response) => response.blob())
434
+ .then((blob) => {
435
+ console.log(blob);
436
+ });
437
+ ```
438
+
439
+ ```javascript
440
+ import { fetch, Request } from "mphttpx";
441
+
442
+ const request = new Request("https://example.com", {
443
+ method: "POST",
444
+ body: '{"foo": "bar"}',
445
+ });
446
+
447
+ const url = request.url;
448
+ const method = request.method;
449
+ const credentials = request.credentials;
450
+ const bodyUsed = request.bodyUsed;
451
+
452
+ fetch(request)
453
+ .then((response) => {
454
+ if (response.status === 200) {
455
+ return response.json();
456
+ } else {
457
+ throw new Error("Something went wrong on API server!");
458
+ }
459
+ })
460
+ .then((response) => {
461
+ console.debug(response);
462
+ // …
463
+ })
464
+ .catch((error) => {
465
+ console.error(error);
466
+ });
467
+ ```
468
+
469
+ #### Compatibility
470
+
471
+ | Property | Available | Description |
472
+ | --------- | --------- | -------------|
473
+ | body | ✖ |
474
+ | bodyUsed | ✔ |
475
+ | cache | ✔ |
476
+ | credentials | ✔ |
477
+ | destination | ✖ |
478
+ | headers | ✔ |
479
+ | integrity | ✖ |
480
+ | keepalive | ✖ |
481
+ | method | ✔ |
482
+ | mode | ✖ |
483
+ | redirect | ✖ |
484
+ | referrer | ✖ |
485
+ | referrerPolicy | ✖ |
486
+ | signal | ✔ |
487
+ | url | ✔ |
488
+
489
+ | Method | Available | Description |
490
+ | ------- | --------- | -------------|
491
+ | arrayBuffer() | ✔ |
492
+ | blob() | ✔ |
493
+ | bytes() | ✔ |
494
+ | clone() | ✔ |
495
+ | formData() | ✔ |
496
+ | json() | ✔ |
497
+ | text() | ✔ |
498
+
499
+ ### Response
500
+
501
+ ```javascript
502
+ import { Response, Blob, fetch } from "mphttpx";
503
+
504
+ const myBlob = new Blob();
505
+ const myOptions = { status: 200, statusText: "SuperSmashingGreat!" };
506
+ const myResponse = new Response(myBlob, myOptions);
507
+ ```
508
+
509
+ #### Compatibility
510
+
511
+ | Property | Available | Description |
512
+ | --------- | --------- | -------------|
513
+ | body | ✖ |
514
+ | bodyUsed | ✔ |
515
+ | headers | ✔ |
516
+ | ok | ✔ |
517
+ | redirected | ✖ |
518
+ | status | ✔ |
519
+ | statusText | ✔ |
520
+ | type | ✖ |
521
+ | url | ✔ |
522
+
523
+ | Method | Available | Description |
524
+ | ------- | --------- | -------------|
525
+ | arrayBuffer() | ✔ |
526
+ | blob() | ✔ |
527
+ | bytes() | ✔ |
528
+ | clone() | ✔ |
529
+ | formData() | ✔ |
530
+ | json() | ✔ |
531
+ | text() | ✔ |
532
+
533
+ ### AbortController
534
+
535
+ ```javascript
536
+ import { AbortController } from "mphttpx";
537
+
538
+ const controller = new AbortController();
539
+ ```
540
+
541
+ #### Compatibility
542
+
543
+ | Property | Available | Description |
544
+ | --------- | --------- | -------------|
545
+ | signal | ✔ |
546
+
547
+ | Method | Available | Description |
548
+ | ------- | --------- | -------------|
549
+ | abort() | ✔ |
550
+ | abort(reason) | ✔ |
551
+
552
+ ### AbortSignal
553
+
554
+ ```javascript
555
+ import { AbortController, AbortSignal, Request, fetch } from "mphttpx";
556
+
557
+ async function get() {
558
+ const controller = new AbortController();
559
+ const request = new Request("https://example.org/get", {
560
+ signal: controller.signal,
561
+ });
562
+
563
+ const response = await fetch(request);
564
+ controller.abort();
565
+ // The next line will throw `AbortError`
566
+ const text = await response.text();
567
+ console.log(text);
568
+ }
569
+ ```
570
+
571
+ #### Compatibility
572
+
573
+ | Property | Available | Description |
574
+ | --------- | --------- | -------------|
575
+ | aborted | ✔ |
576
+ | reason | ✔ |
577
+
578
+ | Method | Available | Description |
579
+ | ------- | --------- | -------------|
580
+ | throwIfAborted() | ✔ |
581
+
582
+ ### EventTarget
583
+
584
+ ```javascript
585
+ import { EventTarget, Event, CustomEvent } from "mphttpx";
586
+
587
+ const target = new EventTarget();
588
+
589
+ target.addEventListener("foo", function (evt) {
590
+ console.log(evt);
591
+ });
592
+
593
+ const evt = new Event("foo");
594
+ target.dispatchEvent(evt);
595
+
596
+ target.addEventListener("animalfound", function (evt) {
597
+ console.log(evt.detail.name);
598
+ });
599
+
600
+ const catFound = new CustomEvent("animalfound", {
601
+ detail: {
602
+ name: "cat",
603
+ },
604
+ });
605
+ target.dispatchEvent(catFound);
606
+ ```
607
+
608
+ #### Compatibility
609
+
610
+ | Method | Available | Description |
611
+ | ------- | --------- | -------------|
612
+ | addEventListener(type, listener) | ✔ |
613
+ | addEventListener(type, listener, options) | ✔ |
614
+ | addEventListener(type, listener, useCapture) | ✔ |
615
+ | dispatchEvent(event) | ✔ |
616
+ | removeEventListener(type, listener) | ✔ |
617
+ | removeEventListener(type, listener, options) | ✔ |
618
+ | removeEventListener(type, listener, useCapture) | ✔ |
619
+
620
+ ### XMLHttpRequest (mini-programs)
621
+
622
+ GET
30
623
 
31
624
  ```javascript
32
625
  import { XMLHttpRequest } from "mphttpx";
@@ -41,7 +634,7 @@ xhr.onload = () => {
41
634
  xhr.send();
42
635
  ```
43
636
 
44
- #### Example: POST
637
+ POST
45
638
 
46
639
  ```javascript
47
640
  import { XMLHttpRequest } from "mphttpx";
@@ -66,14 +659,58 @@ xhr.send(JSON.stringify({ foo: "bar", lorem: "ipsum" }));
66
659
 
67
660
  | Property | Available | Description |
68
661
  | -------- | --------- | -------------|
69
- | readyState | ✔ | States 2: HEADERS_RECEIVED and 3: LOADING are simulated; in reality, the request has just completed at this point. |
70
- | response | ✔ |
71
- | responseText | ✔ |
72
- | responseType | ✔ | The `"document"` is not supported, as the `Document` does not exist in mini-programs. |
73
- | responseURL | ✔ | Since mini-programs cannot obtain the URL after redirection, the `responseURL` returns the URL used in the original request. |
74
- | responseXML | ✖ |
75
- | status | ✔ |
76
- | statusText | ✔ |
77
- | timeout | ✔ |
78
- | upload | ✔ | The `upload` is simulated; in reality, regular requests in mini-programs cannot track the upload progress of the request body. |
79
- | withCredentials | | Related to Cookies, setting `withCredentials` has no effect at present; support may be added in the future. |
662
+ | readyState | ✔ | 2, 3: simulated |
663
+ | response | ✔ |
664
+ | responseText | ✔ |
665
+ | responseType | ✔ | The `"document"` is not supported |
666
+ | responseURL | ✔ | The `responseURL` returns the URL used in the original request. |
667
+ | responseXML | ✖ |
668
+ | status | ✔ |
669
+ | statusText | ✔ |
670
+ | timeout | ✔ | This value must be less than the default timeout of mini-programs. |
671
+ | upload | ✔ | simulated |
672
+ | withCredentials | |
673
+
674
+ | Method | Available | Description |
675
+ | ------- | --------- | -------------|
676
+ | abort() | ✔ |
677
+ | getAllResponseHeaders() | ✔ |
678
+ | getResponseHeader(headerName) | ✔ |
679
+ | open(method, url) | ✔ |
680
+ | open(method, url, async) | ✔ |
681
+ | open(method, url, async, user) | ✔ |
682
+ | open(method, url, async, user, password) | ✔ |
683
+ | overrideMimeType(mimeType) | ✖ |
684
+ | send() | ✔ |
685
+ | send(body) | ✔ |
686
+ | setRequestHeader(header, value) | ✔ |
687
+
688
+ ## UniApp & Taro
689
+
690
+ ```javascript
691
+ import { setRequest } from "mphttpx";
692
+
693
+ setRequest(uni.request);
694
+ // setRequest(Taro.request);
695
+ ```
696
+
697
+ Note: When using in UniApp or Taro, if `fetch` or `XMLHttpRequest` fails to work, try explicitly setting the request function.
698
+
699
+ ## Node.js
700
+
701
+ ```bash
702
+ npm install xhr2
703
+ ```
704
+
705
+ ```javascript
706
+ import XMLHttpRequest from "xhr2";
707
+ import { setXMLHttpRequest } from "mphttpx";
708
+
709
+ setXMLHttpRequest(XMLHttpRequest);
710
+ ```
711
+
712
+ ## License
713
+
714
+ MIT
715
+
716
+ [0]: https://github.com/eligrey/Blob.js