├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── .npmignore ├── Blob.js ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── bower.json ├── eslint.config.mjs ├── package.json └── test └── index.mjs /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [18, 20, 22] 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: npm i 28 | - run: npm run build --if-present 29 | - run: npm run lint 30 | - run: npm test 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .eslintrc.js 2 | .github 3 | test 4 | -------------------------------------------------------------------------------- /Blob.js: -------------------------------------------------------------------------------- 1 | /* Blob.js 2 | * A Blob, File, FileReader & URL implementation. 3 | * 2020-02-01 4 | * 5 | * By Eli Grey, https://eligrey.com 6 | * By Jimmy Wärting, https://github.com/jimmywarting 7 | * License: MIT 8 | * See https://github.com/eligrey/Blob.js/blob/master/LICENSE.md 9 | */ 10 | 11 | function array2base64 (input) { 12 | var byteToCharMap = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 13 | 14 | var output = []; 15 | 16 | for (var i = 0; i < input.length; i += 3) { 17 | var byte1 = input[i]; 18 | var haveByte2 = i + 1 < input.length; 19 | var byte2 = haveByte2 ? input[i + 1] : 0; 20 | var haveByte3 = i + 2 < input.length; 21 | var byte3 = haveByte3 ? input[i + 2] : 0; 22 | 23 | var outByte1 = byte1 >> 2; 24 | var outByte2 = ((byte1 & 0x03) << 4) | (byte2 >> 4); 25 | var outByte3 = ((byte2 & 0x0F) << 2) | (byte3 >> 6); 26 | var outByte4 = byte3 & 0x3F; 27 | 28 | if (!haveByte3) { 29 | outByte4 = 64; 30 | 31 | if (!haveByte2) { 32 | outByte3 = 64; 33 | } 34 | } 35 | 36 | output.push( 37 | byteToCharMap[outByte1], byteToCharMap[outByte2], 38 | byteToCharMap[outByte3], byteToCharMap[outByte4] 39 | ); 40 | } 41 | 42 | return output.join(""); 43 | } 44 | 45 | (function(global) { 46 | (function (factory) { 47 | if (typeof define === "function" && define.amd) { 48 | // AMD. Register as an anonymous module. 49 | define(["exports"], factory); 50 | } else if (typeof exports === "object" && typeof exports.nodeName !== "string") { 51 | // CommonJS 52 | factory(exports); 53 | } else { 54 | // Browser globals 55 | factory(global); 56 | } 57 | })(function (exports) { 58 | "use strict"; 59 | 60 | var BlobBuilder = global.BlobBuilder 61 | || global.WebKitBlobBuilder 62 | || global.MSBlobBuilder 63 | || global.MozBlobBuilder; 64 | 65 | var URL = global.URL || global.webkitURL || function (href, a) { 66 | a = document.createElement("a"); 67 | a.href = href; 68 | return a; 69 | }; 70 | 71 | var origBlob = global.Blob; 72 | var createObjectURL = URL.createObjectURL; 73 | var revokeObjectURL = URL.revokeObjectURL; 74 | var strTag = global.Symbol && global.Symbol.toStringTag; 75 | var blobSupported = false; 76 | var blobSupportsArrayBufferView = false; 77 | var blobBuilderSupported = BlobBuilder 78 | && BlobBuilder.prototype.append 79 | && BlobBuilder.prototype.getBlob; 80 | 81 | try { 82 | // Check if Blob constructor is supported 83 | blobSupported = new Blob(["ä"]).size === 2; 84 | 85 | // Check if Blob constructor supports ArrayBufferViews 86 | // Fails in Safari 6, so we need to map to ArrayBuffers there. 87 | blobSupportsArrayBufferView = new Blob([new Uint8Array([1, 2])]).size === 2; 88 | } catch (e) {/* eslint-disable-line no-unused-vars */} 89 | 90 | // Helper function that maps ArrayBufferViews to ArrayBuffers 91 | // Used by BlobBuilder constructor and old browsers that didn't 92 | // support it in the Blob constructor. 93 | function mapArrayBufferViews (ary) { 94 | return ary.map(function (chunk) { 95 | if (chunk.buffer instanceof ArrayBuffer) { 96 | var buf = chunk.buffer; 97 | 98 | // if this is a subarray, make a copy so we only 99 | // include the subarray region from the underlying buffer 100 | if (chunk.byteLength !== buf.byteLength) { 101 | var copy = new Uint8Array(chunk.byteLength); 102 | copy.set(new Uint8Array(buf, chunk.byteOffset, chunk.byteLength)); 103 | buf = copy.buffer; 104 | } 105 | 106 | return buf; 107 | } 108 | 109 | return chunk; 110 | }); 111 | } 112 | 113 | function BlobBuilderConstructor (ary, options) { 114 | options = options || {}; 115 | 116 | var bb = new BlobBuilder(); 117 | mapArrayBufferViews(ary).forEach(function (part) { 118 | bb.append(part); 119 | }); 120 | 121 | return options.type ? bb.getBlob(options.type) : bb.getBlob(); 122 | } 123 | 124 | function BlobConstructor (ary, options) { 125 | return new origBlob(mapArrayBufferViews(ary), options || {}); 126 | } 127 | 128 | if (global.Blob) { 129 | BlobBuilderConstructor.prototype = global.Blob.prototype; 130 | BlobConstructor.prototype = global.Blob.prototype; 131 | } 132 | 133 | /********************************************************/ 134 | /* String Encoder fallback */ 135 | /********************************************************/ 136 | function stringEncode (string) { 137 | var pos = 0; 138 | var len = string.length; 139 | var Arr = global.Uint8Array || Array; // Use byte array when possible 140 | 141 | var at = 0; // output position 142 | var tlen = Math.max(32, len + (len >> 1) + 7); // 1.5x size 143 | var target = new Arr((tlen >> 3) << 3); // ... but at 8 byte offset 144 | 145 | while (pos < len) { 146 | var value = string.charCodeAt(pos++); 147 | if (value >= 0xd800 && value <= 0xdbff) { 148 | // high surrogate 149 | if (pos < len) { 150 | var extra = string.charCodeAt(pos); 151 | if ((extra & 0xfc00) === 0xdc00) { 152 | ++pos; 153 | value = ((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000; 154 | } 155 | } 156 | if (value >= 0xd800 && value <= 0xdbff) { 157 | continue; // drop lone surrogate 158 | } 159 | } 160 | 161 | // expand the buffer if we couldn't write 4 bytes 162 | if (at + 4 > target.length) { 163 | tlen += 8; // minimum extra 164 | tlen *= (1.0 + (pos / string.length) * 2); // take 2x the remaining 165 | tlen = (tlen >> 3) << 3; // 8 byte offset 166 | 167 | var update = new Uint8Array(tlen); 168 | update.set(target); 169 | target = update; 170 | } 171 | 172 | if ((value & 0xffffff80) === 0) { // 1-byte 173 | target[at++] = value; // ASCII 174 | continue; 175 | } else if ((value & 0xfffff800) === 0) { // 2-byte 176 | target[at++] = ((value >> 6) & 0x1f) | 0xc0; 177 | } else if ((value & 0xffff0000) === 0) { // 3-byte 178 | target[at++] = ((value >> 12) & 0x0f) | 0xe0; 179 | target[at++] = ((value >> 6) & 0x3f) | 0x80; 180 | } else if ((value & 0xffe00000) === 0) { // 4-byte 181 | target[at++] = ((value >> 18) & 0x07) | 0xf0; 182 | target[at++] = ((value >> 12) & 0x3f) | 0x80; 183 | target[at++] = ((value >> 6) & 0x3f) | 0x80; 184 | } else { 185 | // FIXME: do we care 186 | continue; 187 | } 188 | 189 | target[at++] = (value & 0x3f) | 0x80; 190 | } 191 | 192 | return target.slice(0, at); 193 | } 194 | 195 | /********************************************************/ 196 | /* String Decoder fallback */ 197 | /********************************************************/ 198 | function stringDecode (buf) { 199 | var end = buf.length; 200 | var res = []; 201 | 202 | var i = 0; 203 | while (i < end) { 204 | var firstByte = buf[i]; 205 | var codePoint = null; 206 | var bytesPerSequence = (firstByte > 0xEF) ? 4 207 | : (firstByte > 0xDF) ? 3 208 | : (firstByte > 0xBF) ? 2 209 | : 1; 210 | 211 | if (i + bytesPerSequence <= end) { 212 | var secondByte, thirdByte, fourthByte, tempCodePoint; 213 | 214 | switch (bytesPerSequence) { 215 | case 1: 216 | if (firstByte < 0x80) { 217 | codePoint = firstByte; 218 | } 219 | break; 220 | case 2: 221 | secondByte = buf[i + 1]; 222 | if ((secondByte & 0xC0) === 0x80) { 223 | tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F); 224 | if (tempCodePoint > 0x7F) { 225 | codePoint = tempCodePoint; 226 | } 227 | } 228 | break; 229 | case 3: 230 | secondByte = buf[i + 1]; 231 | thirdByte = buf[i + 2]; 232 | if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) { 233 | tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F); 234 | if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) { 235 | codePoint = tempCodePoint; 236 | } 237 | } 238 | break; 239 | case 4: 240 | secondByte = buf[i + 1]; 241 | thirdByte = buf[i + 2]; 242 | fourthByte = buf[i + 3]; 243 | if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) { 244 | tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F); 245 | if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) { 246 | codePoint = tempCodePoint; 247 | } 248 | } 249 | } 250 | } 251 | 252 | if (codePoint === null) { 253 | // we did not generate a valid codePoint so insert a 254 | // replacement char (U+FFFD) and advance only 1 byte 255 | codePoint = 0xFFFD; 256 | bytesPerSequence = 1; 257 | } else if (codePoint > 0xFFFF) { 258 | // encode to utf16 (surrogate pair dance) 259 | codePoint -= 0x10000; 260 | res.push(codePoint >>> 10 & 0x3FF | 0xD800); 261 | codePoint = 0xDC00 | codePoint & 0x3FF; 262 | } 263 | 264 | res.push(codePoint); 265 | i += bytesPerSequence; 266 | } 267 | 268 | var len = res.length; 269 | var str = ""; 270 | var j = 0; 271 | 272 | while (j < len) { 273 | str += String.fromCharCode.apply(String, res.slice(j, j += 0x1000)); 274 | } 275 | 276 | return str; 277 | } 278 | 279 | // string -> buffer 280 | var textEncode = typeof TextEncoder === "function" 281 | ? TextEncoder.prototype.encode.bind(new TextEncoder()) 282 | : stringEncode; 283 | 284 | // buffer -> string 285 | var textDecode = typeof TextDecoder === "function" 286 | ? TextDecoder.prototype.decode.bind(new TextDecoder()) 287 | : stringDecode; 288 | 289 | function FakeBlobBuilder () { 290 | function bufferClone (buf) { 291 | var view = new Array(buf.byteLength); 292 | var array = new Uint8Array(buf); 293 | var i = view.length; 294 | while (i--) { 295 | view[i] = array[i]; 296 | } 297 | return view; 298 | } 299 | 300 | var create = Object.create || function (a) { 301 | function c () {} 302 | c.prototype = a; 303 | return new c(); 304 | }; 305 | 306 | function getObjectTypeName (o) { 307 | return Object.prototype.toString.call(o).slice(8, -1); 308 | } 309 | 310 | function isPrototypeOf(c, o) { 311 | return typeof c === "object" && Object.prototype.isPrototypeOf.call(c.prototype, o); 312 | } 313 | 314 | function isDataView (o) { 315 | return getObjectTypeName(o) === "DataView" || isPrototypeOf(global.DataView, o); 316 | } 317 | 318 | var arrayBufferClassNames = [ 319 | "Int8Array", 320 | "Uint8Array", 321 | "Uint8ClampedArray", 322 | "Int16Array", 323 | "Uint16Array", 324 | "Int32Array", 325 | "Uint32Array", 326 | "Float32Array", 327 | "Float64Array", 328 | "ArrayBuffer" 329 | ]; 330 | 331 | function includes(a, v) { 332 | return a.indexOf(v) !== -1; 333 | } 334 | 335 | function isArrayBuffer(o) { 336 | return includes(arrayBufferClassNames, getObjectTypeName(o)) || isPrototypeOf(global.ArrayBuffer, o); 337 | } 338 | 339 | function concatTypedarrays (chunks) { 340 | var size = 0; 341 | var j = chunks.length; 342 | while (j--) { size += chunks[j].length; } 343 | var b = new Uint8Array(size); 344 | var offset = 0; 345 | for (var i = 0; i < chunks.length; i++) { 346 | var chunk = chunks[i]; 347 | b.set(chunk, offset); 348 | offset += chunk.byteLength || chunk.length; 349 | } 350 | 351 | return b; 352 | } 353 | 354 | /********************************************************/ 355 | /* Blob constructor */ 356 | /********************************************************/ 357 | function Blob (chunks, opts) { 358 | chunks = chunks ? chunks.slice() : []; 359 | opts = opts == null ? {} : opts; 360 | for (var i = 0, len = chunks.length; i < len; i++) { 361 | var chunk = chunks[i]; 362 | if (chunk instanceof Blob) { 363 | chunks[i] = chunk._buffer; 364 | } else if (typeof chunk === "string") { 365 | chunks[i] = textEncode(chunk); 366 | } else if (isDataView(chunk)) { 367 | chunks[i] = bufferClone(chunk.buffer); 368 | } else if (isArrayBuffer(chunk)) { 369 | chunks[i] = bufferClone(chunk); 370 | } else { 371 | chunks[i] = textEncode(String(chunk)); 372 | } 373 | } 374 | 375 | this._buffer = global.Uint8Array 376 | ? concatTypedarrays(chunks) 377 | : [].concat.apply([], chunks); 378 | this.size = this._buffer.length; 379 | 380 | this.type = opts.type || ""; 381 | if (/[^\u0020-\u007E]/.test(this.type)) { 382 | this.type = ""; 383 | } else { 384 | this.type = this.type.toLowerCase(); 385 | } 386 | } 387 | Blob.isPolyfill = true; 388 | 389 | Blob.prototype.arrayBuffer = function () { 390 | return Promise.resolve(this._buffer.buffer || this._buffer); 391 | }; 392 | 393 | Blob.prototype.text = function () { 394 | return Promise.resolve(textDecode(this._buffer)); 395 | }; 396 | 397 | Blob.prototype.slice = function (start, end, type) { 398 | var slice = this._buffer.slice(start || 0, end || this._buffer.length); 399 | return new Blob([slice], {type: type}); 400 | }; 401 | 402 | Blob.prototype.toString = function () { 403 | return "[object Blob]"; 404 | }; 405 | 406 | /********************************************************/ 407 | /* File constructor */ 408 | /********************************************************/ 409 | function File (chunks, name, opts) { 410 | opts = opts || {}; 411 | var a = Blob.call(this, chunks, opts) || this; 412 | a.name = name.replace(/\//g, ":"); 413 | a.lastModifiedDate = opts.lastModified ? new Date(opts.lastModified) : new Date(); 414 | a.lastModified = +a.lastModifiedDate; 415 | 416 | return a; 417 | } 418 | 419 | File.isPolyfill = true; 420 | File.prototype = create(Blob.prototype); 421 | File.prototype.constructor = File; 422 | 423 | if (Object.setPrototypeOf) { 424 | Object.setPrototypeOf(File, Blob); 425 | } else { 426 | try { 427 | File.__proto__ = Blob; 428 | } catch (e) {/* eslint-disable-line no-unused-vars */} 429 | } 430 | 431 | File.prototype.toString = function () { 432 | return "[object File]"; 433 | }; 434 | 435 | /********************************************************/ 436 | /* URL */ 437 | /********************************************************/ 438 | 439 | URL.createObjectURL = function (blob) { 440 | return blob instanceof Blob 441 | ? "data:" + blob.type + ";base64," + array2base64(blob._buffer) 442 | : createObjectURL.call(URL, blob); 443 | }; 444 | URL.createObjectURL.isPolyfill = true; 445 | 446 | URL.revokeObjectURL = function (url) { 447 | revokeObjectURL && revokeObjectURL.call(URL, url); 448 | }; 449 | URL.revokeObjectURL.isPolyfill = true; 450 | 451 | /********************************************************/ 452 | /* XHR */ 453 | /********************************************************/ 454 | var _send = global.XMLHttpRequest && global.XMLHttpRequest.prototype.send; 455 | if (_send) { 456 | XMLHttpRequest.prototype.send = function (data) { 457 | if (data instanceof Blob) { 458 | this.setRequestHeader("Content-Type", data.type); 459 | _send.call(this, textDecode(data._buffer)); 460 | } else { 461 | _send.call(this, data); 462 | } 463 | }; 464 | } 465 | 466 | exports.Blob = Blob; 467 | exports.File = File; 468 | } 469 | 470 | function fixFileAndXHR () { 471 | var isIE = !!global.ActiveXObject || (typeof document !== "undefined" && 472 | "-ms-scroll-limit" in document.documentElement.style && 473 | "-ms-ime-align" in document.documentElement.style 474 | ); 475 | 476 | // Monkey patched 477 | // IE doesn't set Content-Type header on XHR whose body is a typed Blob 478 | // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/6047383 479 | var _send = global.XMLHttpRequest && global.XMLHttpRequest.prototype.send; 480 | if (isIE && _send) { 481 | XMLHttpRequest.prototype.send = function (data) { 482 | if (data instanceof Blob) { 483 | this.setRequestHeader("Content-Type", data.type); 484 | _send.call(this, data); 485 | } else { 486 | _send.call(this, data); 487 | } 488 | }; 489 | } 490 | 491 | try { 492 | new File([], ""); 493 | exports.File = global.File; 494 | } catch (e) { // eslint-disable-line no-unused-vars 495 | try { 496 | exports.File = new Function("class File extends Blob {" + 497 | "constructor(chunks, name, opts) {" + 498 | "opts = opts || {};" + 499 | "super(chunks, opts || {});" + 500 | "this.name = name.replace(/\\//g, \":\");" + 501 | "this.lastModifiedDate = opts.lastModified ? new Date(opts.lastModified) : new Date();" + 502 | "this.lastModified = +this.lastModifiedDate;" + 503 | "}};" + 504 | "return new File([], \"\"), File" 505 | )(); 506 | } catch (e) { // eslint-disable-line no-unused-vars 507 | exports.File = function File(b, d, c) { 508 | var blob = new Blob(b, c); 509 | var t = c && void 0 !== c.lastModified ? new Date(c.lastModified) : new Date(); 510 | 511 | blob.name = d.replace(/\//g, ":"); 512 | blob.lastModifiedDate = t; 513 | blob.lastModified = +t; 514 | blob.toString = function () { 515 | return "[object File]"; 516 | }; 517 | 518 | if (strTag) { 519 | blob[strTag] = "File"; 520 | } 521 | 522 | return blob; 523 | }; 524 | } 525 | exports.File.isPolyfill = true; 526 | } 527 | } 528 | 529 | if (blobSupported) { 530 | fixFileAndXHR(); 531 | exports.Blob = blobSupportsArrayBufferView ? global.Blob : BlobConstructor; 532 | } else if (blobBuilderSupported) { 533 | fixFileAndXHR(); 534 | exports.Blob = BlobBuilderConstructor; 535 | } else { 536 | FakeBlobBuilder(); 537 | } 538 | 539 | /********************************************************/ 540 | /* FileReader constructor */ 541 | /********************************************************/ 542 | function FileReader () { 543 | if (!(this instanceof FileReader)) { 544 | throw new TypeError("Failed to construct 'FileReader': Please use the 'new' operator, this DOM object constructor cannot be called as a function."); 545 | } 546 | 547 | var delegate = document.createDocumentFragment(); 548 | this.addEventListener = delegate.addEventListener; 549 | this.dispatchEvent = function (evt) { 550 | var local = this["on" + evt.type]; 551 | if (typeof local === "function") local(evt); 552 | delegate.dispatchEvent(evt); 553 | }; 554 | this.removeEventListener = delegate.removeEventListener; 555 | } 556 | 557 | function _read (fr, blob, kind) { 558 | if (!(blob instanceof exports.Blob)) { 559 | throw new TypeError("Failed to execute '" + kind + "' on 'FileReader': parameter 1 is not of type 'Blob'."); 560 | } 561 | 562 | fr.result = ""; 563 | 564 | setTimeout(function () { 565 | this.readyState = FileReader.LOADING; 566 | fr.dispatchEvent(new Event("load")); 567 | fr.dispatchEvent(new Event("loadend")); 568 | }); 569 | } 570 | 571 | FileReader.EMPTY = 0; 572 | FileReader.LOADING = 1; 573 | FileReader.DONE = 2; 574 | FileReader.prototype.error = null; 575 | FileReader.prototype.onabort = null; 576 | FileReader.prototype.onerror = null; 577 | FileReader.prototype.onload = null; 578 | FileReader.prototype.onloadend = null; 579 | FileReader.prototype.onloadstart = null; 580 | FileReader.prototype.onprogress = null; 581 | 582 | FileReader.prototype.readAsDataURL = function (blob) { 583 | _read(this, blob, "readAsDataURL"); 584 | this.result = "data:" + blob.type + ";base64," + array2base64(blob._buffer); 585 | }; 586 | 587 | FileReader.prototype.readAsText = function (blob) { 588 | _read(this, blob, "readAsText"); 589 | this.result = textDecode(blob._buffer); 590 | }; 591 | 592 | FileReader.prototype.readAsArrayBuffer = function (blob) { 593 | _read(this, blob, "readAsText"); 594 | // return ArrayBuffer when possible 595 | this.result = (blob._buffer.buffer || blob._buffer).slice(); 596 | }; 597 | 598 | FileReader.prototype.abort = function () {}; 599 | 600 | exports.FileReader = global.FileReader || FileReader; 601 | exports.URL = global.URL || URL; 602 | 603 | if (strTag) { 604 | if (!exports.File.prototype[strTag]) exports.File.prototype[strTag] = "File"; 605 | if (!exports.Blob.prototype[strTag]) exports.Blob.prototype[strTag] = "Blob"; 606 | if (!exports.FileReader.prototype[strTag]) exports.FileReader.prototype[strTag] = "FileReader"; 607 | } 608 | 609 | var blob = exports.Blob.prototype; 610 | var stream; 611 | 612 | try { 613 | new ReadableStream({ type: "bytes" }); 614 | stream = function stream() { 615 | var position = 0; 616 | var blob = this; 617 | 618 | return new ReadableStream({ 619 | type: "bytes", 620 | autoAllocateChunkSize: 524288, 621 | 622 | pull: function (controller) { 623 | var v = controller.byobRequest.view; 624 | var chunk = blob.slice(position, position + v.byteLength); 625 | return chunk.arrayBuffer() 626 | .then(function (buffer) { 627 | var uint8array = new Uint8Array(buffer); 628 | var bytesRead = uint8array.byteLength; 629 | 630 | position += bytesRead; 631 | v.set(uint8array); 632 | controller.byobRequest.respond(bytesRead); 633 | 634 | if(position >= blob.size) 635 | controller.close(); 636 | }); 637 | } 638 | }); 639 | }; 640 | } catch (e) { // eslint-disable-line no-unused-vars 641 | try { 642 | new ReadableStream({}); 643 | stream = function stream(blob){ 644 | var position = 0; 645 | 646 | return new ReadableStream({ 647 | pull: function (controller) { 648 | var chunk = blob.slice(position, position + 524288); 649 | 650 | return chunk.arrayBuffer().then(function (buffer) { 651 | position += buffer.byteLength; 652 | var uint8array = new Uint8Array(buffer); 653 | controller.enqueue(uint8array); 654 | 655 | if (position == blob.size) 656 | controller.close(); 657 | }); 658 | } 659 | }); 660 | }; 661 | } catch (e) { // eslint-disable-line no-unused-vars 662 | try { 663 | new Response("").body.getReader().read(); 664 | stream = function stream() { 665 | return (new Response(this)).body; 666 | }; 667 | } catch (e) { // eslint-disable-line no-unused-vars 668 | stream = function stream() { 669 | throw new Error("Include https://github.com/MattiasBuelens/web-streams-polyfill"); 670 | }; 671 | } 672 | } 673 | } 674 | 675 | function promisify(obj) { 676 | return new Promise(function(resolve, reject) { 677 | obj.onload = obj.onerror = function(evt) { 678 | obj.onload = obj.onerror = null; 679 | 680 | evt.type === "load" ? 681 | resolve(obj.result || obj) : 682 | reject(new Error("Failed to read the blob/file")); 683 | }; 684 | }); 685 | } 686 | 687 | if (!blob.arrayBuffer) { 688 | blob.arrayBuffer = function arrayBuffer() { 689 | var fr = new exports.FileReader(); 690 | fr.readAsArrayBuffer(this); 691 | return promisify(fr); 692 | }; 693 | } 694 | 695 | if (!blob.text) { 696 | blob.text = function text() { 697 | var fr = new exports.FileReader(); 698 | fr.readAsText(this); 699 | return promisify(fr); 700 | }; 701 | } 702 | 703 | if (!blob.stream) { 704 | blob.stream = stream; 705 | } 706 | }); 707 | })( 708 | typeof self !== "undefined" && self || 709 | typeof window !== "undefined" && window || 710 | typeof global !== "undefined" && global || 711 | this 712 | ); 713 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # `blob-polyfill` CHANGELOG 2 | 3 | ## v9.0.20240710 4 | * [Blob.js] Use exported FileReader (@luke-stead-sonocent) 5 | * [test] Test is now a module (@bjornstar) 6 | * [README.md] Add badge for `master` branch build status (@bjornstar) 7 | * [package.json] Update devDependencies: `@sindresorhus/is`, `eslint`, & `mocha` (@bjornstar) 8 | * [bower.json] Match current version (@bjornstar) 9 | * [.eslintrc.js] Change to `eslint.config.mjs` for eslint@9 (@bjornstar) 10 | 11 | ## v8.0.20240630 12 | * [Blob.js] Change Blob.prototype to global.Blob.prototype (@tmisirpash) 13 | * [Blob.js] Make it work in environments where global.Blob exists, but global.FileReader does not (@bjornstar) 14 | * [Blob.js] Add `isPolyfill` property to the polyfilled versions so we can differentiate them (@bjornstar) 15 | * [test] Unskip tests and update to work in environments with global.Blob & global.File & global.URL (@bjornstar) 16 | * [.github] Update action versions and test node v12-v22 (@bjornstar) 17 | 18 | ## v7.0.20220408 19 | * [Blob.js] Do not modify array that is passed into constructor (@zyrong) 20 | * [.github] Start automated tests on github (@bjornstar) 21 | * [.travis.yml] Remove travis-ci integration (@bjornstar) 22 | * [.npmignore] Ignore .github, remove .travis.yml (@bjornstar) 23 | * [devDependencies] Update test dependencies (@bjornstar) 24 | 25 | ## v6.0.20211015 26 | * [Blob.js] Check object class names when determining Object types (@coclauso) 27 | * [Blob.js] Reduce redundancies in getting class names and prototype checks (@bjornstar) 28 | * [test] Add a test for round tripping data in ArrayBuffers (@coclauso) 29 | 30 | ## v5.0.20210201 31 | * [Blob.js] Blob.arrayBuffer() should return a promise that resolves with an ArrayBuffer (@bjornstar) 32 | * [test] Add a test for Blob.arrayBuffer (@bjornstar) 33 | * [package.json] Update devDependencies: `eslint` & `mocha` (@bjornstar) 34 | * [package.json] Add devDependency: `@sindresorhus/is` (@bjornstar) 35 | 36 | ## v4.0.20200601 37 | * [Blob.js] Populate File and FileReader in exports after confirming File is supported (@bjornstar) 38 | 39 | ## v4.0.20200531 40 | * [Blob.js] Do not attempt to set readonly property Symbols (@bjornstar) 41 | * [Blob.js] Do not use prototype built-ins (@bjornstar) 42 | * [.travis.yml] Drop testing for node v6 and v8 (@bjornstar) 43 | * [.travis.yml] Add testing for node v14 (@bjornstar) 44 | * [package.json] Update devDependencies: `eslint` & `mocha` (@bjornstar) 45 | * [.gitignore] Add `npm-debug.log` (@bjornstar) 46 | * [README] Add usage examples to encourage non-global use of Blob (@bjornstar) 47 | 48 | ## v4.0.20190430 49 | * A complete rewrite of Blob.js (@jimmywarting) 50 | * Restore the UMD wrapper (@bjornstar) 51 | * Add some tests for File, FileReader, and URL (@bjornstar) 52 | 53 | ## v3.0.20180112 54 | * Resolve conflict from upstream based on date version change (@bjornstar) 55 | * Remove `this.content` to match upstream changes (@bjornstar) 56 | * Added some very basic tests (@bjornstar) 57 | * Added linting through eslint (@bjornstar) 58 | * Start using travis-ci to verify basic functionality isn't breaking (@bjornstar) 59 | 60 | ## v2.0.20171115 61 | * Add UMD wrapper to allow non-global polluting usage in Node (@jscinoz) 62 | * License clarification (@eligrey) 63 | * Clarified browser support in the readme (@eligrey) 64 | * Add CHANGELOG (@bjornstar) 65 | 66 | ## v1.0.20150320 67 | * Add support for PhantomJS (@mitar) 68 | * Add package.json (@bjornstar) 69 | * Add bower.json (@bjornstar) 70 | * Release on npm as blob-polyfill (@bjornstar) 71 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright © 2014 [Eli Grey][1]. 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | 25 | [1]: https://eligrey.com 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blob Polyfill 2 | 3 | [![npm](https://img.shields.io/npm/v/blob-polyfill.svg)](https://www.npmjs.com/package/blob-polyfill) 4 | [![npm](https://img.shields.io/npm/dm/blob-polyfill.svg)](https://www.npmjs.com/package/blob-polyfill) 5 | [![build status](https://github.com/bjornstar/blob-polyfill/actions/workflows/node.js.yml/badge.svg?branch=master)](https://github.com/bjornstar/blob-polyfill/actions/workflows/node.js.yml?query=branch%3Amaster) 6 | 7 | ## Purpose 8 | 9 | Blob Polyfill serves [Blob.js][0] over npm. 10 | 11 | Blob.js implements the W3C [`Blob`][1] interface in browsers that do not natively support it. 12 | 13 | ## Changelog 14 | 15 | [Please read the changelog](CHANGELOG.md) 16 | 17 | ## Installation 18 | 19 | To install this library, run: 20 | 21 | ```bash 22 | $ npm install blob-polyfill --save 23 | ``` 24 | 25 | ## Usage 26 | 27 | CommonJS: 28 | ```js 29 | var Blob = require('blob-polyfill').Blob; 30 | ``` 31 | 32 | AMD 33 | ```js 34 | import { Blob } from 'blob-polyfill'; 35 | ``` 36 | 37 | ## Supported browsers 38 | 39 | Blob.js shares the [same supported browsers as FileSaver.js][2]. 40 | 41 | ## License 42 | 43 | [MIT](LICENSE.md) 44 | 45 | ![Tracking image](https://in.getclicky.com/212712ns.gif) 46 | 47 | [0]: https://github.com/eligrey/Blob.js 48 | [1]: https://developer.mozilla.org/en-US/docs/Web/API/Blob 49 | [2]: https://github.com/eligrey/FileSaver.js#supported-browsers 50 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blob-polyfill", 3 | "version": "9.0.20240710", 4 | "homepage": "https://github.com/bjornstar/blob-polyfill", 5 | "authors": [ 6 | "Eli Grey " 7 | ], 8 | "description": "Blob.js implements the W3C Blob interface in browsers that do not natively support it.", 9 | "main": "Blob.js", 10 | "moduleType": [ 11 | "amd", 12 | "node" 13 | ], 14 | "keywords": [ 15 | "blob", 16 | "polyfill" 17 | ], 18 | "license": "MIT", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "tests" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js"; 2 | import globals from "globals"; 3 | 4 | export default [ 5 | js.configs.recommended, 6 | { 7 | languageOptions: { 8 | globals: { 9 | ...globals.amd, 10 | ...globals.browser, 11 | ...globals.node, 12 | } 13 | }, 14 | rules: { 15 | indent: [ 16 | "error", 17 | "tab" 18 | ], 19 | "linebreak-style": [ 20 | "error", 21 | "unix" 22 | ], 23 | quotes: [ 24 | "error", 25 | "double" 26 | ], 27 | semi: [ 28 | "error", 29 | "always" 30 | ], 31 | } 32 | }, 33 | { 34 | files: ["test/*.mjs"], 35 | languageOptions: { 36 | globals: { 37 | ...globals.mocha, 38 | ...globals.node, 39 | } 40 | } 41 | } 42 | ]; 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blob-polyfill", 3 | "version": "9.0.20240710", 4 | "description": "Blob.js implements the W3C Blob interface in browsers that do not natively support it.", 5 | "main": "Blob.js", 6 | "scripts": { 7 | "lint": "eslint .", 8 | "test": "mocha test" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/bjornstar/blob-polyfill.git" 13 | }, 14 | "keywords": [ 15 | "blob", 16 | "polyfill" 17 | ], 18 | "author": "Eli Grey (https://eligrey.com)", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/bjornstar/blob-polyfill/issues" 22 | }, 23 | "homepage": "https://github.com/bjornstar/blob-polyfill", 24 | "devDependencies": { 25 | "@sindresorhus/is": "^7.0.0", 26 | "eslint": "^9.6.0", 27 | "mocha": "^10.6.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/index.mjs: -------------------------------------------------------------------------------- 1 | import assert from "node:assert/strict"; 2 | import * as is from "@sindresorhus/is"; 3 | 4 | import BlobPolyfill from "../Blob.js"; 5 | 6 | const Blob = BlobPolyfill.Blob; 7 | const File = BlobPolyfill.File; 8 | const FileReader = BlobPolyfill.FileReader; 9 | const URL = BlobPolyfill.URL; 10 | 11 | describe("blob-polyfill", () => { 12 | describe("Blob", () => { 13 | it("Does not pollute the global Blob definition", () => { 14 | if (typeof global.Blob === "function") { 15 | assert(Blob === global.Blob); 16 | assert.strictEqual(Blob.isPolyfill, undefined); 17 | } else { 18 | assert.strictEqual(typeof global.Blob, "undefined"); 19 | assert.throws(() => { 20 | new global.Blob(); 21 | }, TypeError, "global.Blob should not be a constructor"); 22 | assert.strictEqual(Blob.isPolyfill, true); 23 | } 24 | }); 25 | 26 | it("At the very least, we can instantiate an empty Blob", () => { 27 | const blob = new Blob(); 28 | 29 | assert.strictEqual(blob.size, 0); 30 | assert.strictEqual(blob.type, ""); 31 | }); 32 | 33 | it("We can instantiate a json Blob", () => { 34 | const example = { hello: "world" }; 35 | const blob = new Blob([JSON.stringify(example, null, 2)], { type : "application/json" }); 36 | 37 | assert.strictEqual(blob.size, 22); 38 | assert.strictEqual(blob.type, "application/json"); 39 | }); 40 | 41 | it("We can instantiate a binary Blob", () => { 42 | const blob = new Blob([ new Uint8Array([1, 2, 3]) ], { type: "application/octet-binary" }); 43 | assert.strictEqual(blob.size, 3); 44 | assert.strictEqual(blob.type, "application/octet-binary"); 45 | }); 46 | 47 | it("Symbol is Blob", () => { 48 | assert.strictEqual(Blob.prototype[Symbol.toStringTag], "Blob"); 49 | }); 50 | 51 | it("Blob.arrayBuffer() returns a promise that resolves with an ArrayBuffer", () => { 52 | const blob = new Blob(); 53 | 54 | is.assert.promise(blob.arrayBuffer()); 55 | 56 | return blob.arrayBuffer().then(function (value) { 57 | is.assert.arrayBuffer(value); 58 | }); 59 | }); 60 | 61 | it("Blob can be instantiated with ArrayBuffer, data can be recovered", () => { 62 | const testString = "Testing..."; 63 | const arrayBuffer = stringToArrayBuffer(testString); 64 | const blob = new Blob([arrayBuffer]); 65 | return blob.arrayBuffer().then(function (value) { 66 | const testStringRecovered = arrayBufferToString(value); 67 | assert.strictEqual(testString, testStringRecovered); 68 | }); 69 | }); 70 | 71 | it("Does not modify the source array", () => { 72 | const array = ["mutation"]; 73 | const clone = array.slice(); 74 | new Blob(array); 75 | assert.deepStrictEqual(array, clone); 76 | }); 77 | }); 78 | 79 | describe("File", () => { 80 | it("Does not pollute the global File definition", () => { 81 | if (typeof global.File === "function") { 82 | assert.strictEqual(File, global.File); 83 | assert.strictEqual(File.isPolyfill, undefined); 84 | } else { 85 | assert.strictEqual(typeof global.File, "undefined"); 86 | assert.throws(() => { 87 | new global.File(); 88 | }, TypeError, "global.File should be undefined"); 89 | } 90 | }); 91 | 92 | it("We can instantiate a File", () => { 93 | const file = new File([], ""); 94 | 95 | assert.strictEqual(file.size, 0); 96 | assert.strictEqual(file.type, ""); 97 | assert.strictEqual(file.name, ""); 98 | }); 99 | 100 | it("Symbol is File or Blob", () => { 101 | assert.ok(["Blob", "File"].includes(File.prototype[Symbol.toStringTag])); 102 | }); 103 | }); 104 | 105 | describe("FileReader", () => { 106 | it("Does not pollute the global FileReader definition", () => { 107 | assert.strictEqual(typeof global.FileReader, "undefined"); 108 | assert.throws(() => { 109 | new global.FileReader(); 110 | }, TypeError, "global.FileReader should be undefined"); 111 | }); 112 | 113 | // As it stands, the FileReader does not work in node. 114 | it.skip("We can instantiate a FileReader", () => { 115 | const fileReader = new FileReader(); 116 | 117 | assert.ok(fileReader); 118 | }); 119 | 120 | it("Symbol is FileReader", () => { 121 | assert.strictEqual(FileReader.prototype[Symbol.toStringTag], "FileReader"); 122 | }); 123 | }); 124 | 125 | describe("URL", () => { 126 | it("Modifies the global URL to always create Blobs if Blobs are not native", () => { 127 | assert.strictEqual(typeof global.URL, "function"); 128 | assert.strictEqual(URL, global.URL); 129 | if (typeof global.Blob === "function") { 130 | assert.strictEqual(global.URL.createObjectURL.isPolyfill, undefined); 131 | assert.strictEqual(global.URL.revokeObjectURL.isPolyfill, undefined); 132 | } else { 133 | assert.strictEqual(typeof global.URL, "function"); 134 | assert.strictEqual(URL, global.URL); 135 | assert.strictEqual(global.URL.createObjectURL.isPolyfill, true); 136 | assert.strictEqual(global.URL.revokeObjectURL.isPolyfill, true); 137 | } 138 | }); 139 | it("We can call URL.createObjectUrl", () => { 140 | if (typeof global.Blob !== "function") { 141 | const polyfilledUrl = URL.createObjectURL(new File(["hello world"], "hello.txt", { type: "application/plain-text" })); 142 | assert.strictEqual(typeof polyfilledUrl, "string"); 143 | assert.strictEqual(polyfilledUrl, "data:application/plain-text;base64,aGVsbG8gd29ybGQ="); 144 | } else { 145 | const nodeUrl = URL.createObjectURL(new File(["hello world"], "hello.txt", { type: "application/plain-text" })); 146 | assert.strictEqual(typeof nodeUrl, "string"); 147 | assert.match(nodeUrl, /blob:nodedata:[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/); 148 | } 149 | }); 150 | }); 151 | }); 152 | 153 | 154 | function stringToArrayBuffer(string) { 155 | const buf = new ArrayBuffer(string.length * 2); // 2 bytes for each char 156 | const bufView = new Uint16Array(buf); 157 | for (let i = 0; i < string.length; i+= 1) { 158 | bufView[i] = string.charCodeAt(i); 159 | } 160 | return buf; 161 | } 162 | 163 | function arrayBufferToString(buffer) { 164 | const array = new Uint16Array(buffer); 165 | return String.fromCharCode.apply(null, array); 166 | } 167 | --------------------------------------------------------------------------------