Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b2bab5365 | ||
|
|
b75fb395b4 | ||
|
|
3217da9bc7 | ||
|
|
ae6b565ae6 | ||
|
|
26ca0e39b4 | ||
|
|
99388a79bb | ||
|
|
cf379d10e0 | ||
|
|
1d1faebe18 | ||
|
|
903a4c1bb0 | ||
|
|
126b4ec697 | ||
|
|
38639ec4aa | ||
|
|
5608cef75c | ||
|
|
62fc8eab22 | ||
|
|
1a3c424a07 | ||
|
|
f4fa7e5825 | ||
|
|
a1fef8cd0b | ||
|
|
1ec92a63b2 | ||
|
|
fd4f45e70f | ||
|
|
9dc07473c6 | ||
|
|
870be1e2d5 | ||
|
|
960f7e528d | ||
|
|
582880a297 | ||
|
|
c1dd0851d8 | ||
|
|
bec5a3dceb | ||
|
|
6fabfce52f | ||
|
|
4a46def452 | ||
|
|
9a458af871 | ||
|
|
f7e60156a4 | ||
|
|
af2edbd01c | ||
|
|
f4ddd159a4 | ||
|
|
83559d1144 | ||
|
|
8bf6441343 | ||
|
|
1f5258cd79 | ||
|
|
f336a8d182 |
@@ -0,0 +1,6 @@
|
|||||||
|
coverage:
|
||||||
|
round: up
|
||||||
|
precision: 2
|
||||||
|
parsers:
|
||||||
|
javascript:
|
||||||
|
enable_partials: yes
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "**.js"
|
||||||
|
- "package.json"
|
||||||
|
- ".github/workflows/ci.yml"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||||
|
node: ["14", "12", engines]
|
||||||
|
exclude:
|
||||||
|
# On Windows, run tests with only the LTS environments.
|
||||||
|
- os: windows-latest
|
||||||
|
node: engines
|
||||||
|
- os: windows-latest
|
||||||
|
node: "14"
|
||||||
|
# On macOS, run tests with only the LTS environments.
|
||||||
|
- os: macOS-latest
|
||||||
|
node: engines
|
||||||
|
- os: macOS-latest
|
||||||
|
node: "14"
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Get Node.JS version from package.json
|
||||||
|
if: matrix.node == 'engines'
|
||||||
|
id: get-version
|
||||||
|
run: echo ::set-output name=node::$(npx --q minimum-node-version)
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v2-beta
|
||||||
|
if: matrix.node != 'engines'
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node }}
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v2-beta
|
||||||
|
if: matrix.node == 'engines'
|
||||||
|
with:
|
||||||
|
node-version: ${{steps.get-version.outputs.node}}
|
||||||
|
|
||||||
|
- run: npm install
|
||||||
|
- run: npm install domexception
|
||||||
|
|
||||||
|
- run: npm run report -- --colors
|
||||||
|
|
||||||
|
- name: Upload coverage to Codecov
|
||||||
|
uses: codecov/codecov-action@v1
|
||||||
|
with:
|
||||||
|
file: ./coverage/coverage-final.json
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "**.js"
|
||||||
|
- "**eslint**"
|
||||||
|
- "package.json"
|
||||||
|
- ".github/workflows/lint.yml"
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Use Node.js
|
||||||
|
uses: actions/setup-node@v2-beta
|
||||||
|
with:
|
||||||
|
node-version: 14
|
||||||
|
- run: npm install
|
||||||
|
- run: npm run lint
|
||||||
+6
-2
@@ -1,3 +1,8 @@
|
|||||||
|
# Lock files
|
||||||
|
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
@@ -57,5 +62,4 @@ typings/
|
|||||||
# dotenv environment variables file
|
# dotenv environment variables file
|
||||||
.env
|
.env
|
||||||
|
|
||||||
# next.js build output
|
index.d.ts
|
||||||
.next
|
|
||||||
|
|||||||
-11
@@ -1,11 +0,0 @@
|
|||||||
language: node_js
|
|
||||||
node_js:
|
|
||||||
- "8"
|
|
||||||
- "10"
|
|
||||||
- "12"
|
|
||||||
- "node"
|
|
||||||
script:
|
|
||||||
- npm run coverage
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- node_modules
|
|
||||||
+25
-2
@@ -1,14 +1,37 @@
|
|||||||
|
|
||||||
Changelog
|
Changelog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
## v2.1.0
|
||||||
|
- Fix: .slice has an implementation bug (#54).
|
||||||
|
- Added blob backed up by filesystem (#55)
|
||||||
|
|
||||||
|
## v2.0.1
|
||||||
|
|
||||||
|
- Fix: remove upper bound for node engine semver (#49).
|
||||||
|
|
||||||
|
## v2.0.0
|
||||||
|
|
||||||
|
> Note: This release was previously published as `1.0.7`, but as it contains breaking changes, we renamed it to `2.0.0`.
|
||||||
|
|
||||||
|
- **Breaking:** minimum supported Node.js version is now 10.17.
|
||||||
|
- **Breaking:** `buffer` option has been removed.
|
||||||
|
- Enhance: create TypeScript declarations from JSDoc (#45).
|
||||||
|
- Enhance: operate on blob parts (byte sequence) (#44).
|
||||||
|
- Enhance: use a `WeakMap` for private properties (#42) .
|
||||||
|
- Other: update formatting.
|
||||||
|
|
||||||
|
## v1.0.6
|
||||||
|
|
||||||
|
- Enhance: use upstream Blob directly in typings (#38)
|
||||||
|
- Other: update dependencies
|
||||||
|
|
||||||
## v1.0.5
|
## v1.0.5
|
||||||
|
|
||||||
- Other: no change to code, update dev dependency to address vulnerability reports
|
- Other: no change to code, update dev dependency to address vulnerability reports
|
||||||
|
|
||||||
## v1.0.4
|
## v1.0.4
|
||||||
|
|
||||||
- Other: general code revwrite to pass linting, prepare for `node-fetch` release v3
|
- Other: general code rewrite to pass linting, prepare for `node-fetch` release v3
|
||||||
|
|
||||||
## v1.0.3
|
## v1.0.3
|
||||||
|
|
||||||
|
|||||||
@@ -1,38 +1,57 @@
|
|||||||
|
# fetch-blob
|
||||||
fetch-blob
|
|
||||||
==========
|
|
||||||
|
|
||||||
[![npm version][npm-image]][npm-url]
|
[![npm version][npm-image]][npm-url]
|
||||||
[![build status][travis-image]][travis-url]
|
[![build status][ci-image]][ci-url]
|
||||||
[![coverage status][codecov-image]][codecov-url]
|
[![coverage status][codecov-image]][codecov-url]
|
||||||
[![install size][install-size-image]][install-size-url]
|
[![install size][install-size-image]][install-size-url]
|
||||||
|
|
||||||
A Blob implementation on node.js, originally from node-fetch
|
A Blob implementation in Node.js, originally from [node-fetch](https://github.com/node-fetch/node-fetch).
|
||||||
|
|
||||||
## Usage
|
## Installation
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ npm install fetch-blob
|
npm install fetch-blob
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const Blob = require('fetch-blob');
|
const Blob = require('fetch-blob');
|
||||||
const fetch = require('node-fetch');
|
const fetch = require('node-fetch');
|
||||||
|
|
||||||
fetch('https://httpbin.org/post', {
|
fetch('https://httpbin.org/post', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: new Blob(['hello'], { type: 'text/plain' })
|
body: new Blob(['Hello World'], { type: 'text/plain' })
|
||||||
})
|
})
|
||||||
.then(res => res.json()) // expecting a json response
|
.then(res => res.json());
|
||||||
.then(json => console.log(json));
|
.then(json => console.log(json));
|
||||||
```
|
```
|
||||||
|
|
||||||
See [MDN doc](https://developer.mozilla.org/en-US/docs/Web/API/Blob) and [Tests](https://github.com/bitinn/fetch-blob/blob/master/test.js) for more details.
|
### Blob part backed up by filesystem
|
||||||
|
To use, install [domexception](https://github.com/jsdom/domexception).
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install fetch-blob domexception
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
const blobFrom = require('fetch-blob/from.js');
|
||||||
|
const blob1 = blobFrom('./2-GiB-file.bin');
|
||||||
|
const blob2 = blobFrom('./2-GiB-file.bin');
|
||||||
|
|
||||||
|
// Not a 4 GiB memory snapshot, just holds 3 references
|
||||||
|
// points to where data is located on the disk
|
||||||
|
const blob = new Blob([blob1, blob2]);
|
||||||
|
console.log(blob.size) // 4 GiB
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/Blob) and [tests](https://github.com/node-fetch/fetch-blob/blob/master/test.js) for more details.
|
||||||
|
|
||||||
[npm-image]: https://flat.badgen.net/npm/v/fetch-blob
|
[npm-image]: https://flat.badgen.net/npm/v/fetch-blob
|
||||||
[npm-url]: https://www.npmjs.com/package/fetch-blob
|
[npm-url]: https://www.npmjs.com/package/fetch-blob
|
||||||
[travis-image]: https://flat.badgen.net/travis/bitinn/fetch-blob
|
[ci-image]: https://github.com/node-fetch/fetch-blob/workflows/CI/badge.svg
|
||||||
[travis-url]: https://travis-ci.org/bitinn/fetch-blob
|
[ci-url]: https://github.com/node-fetch/fetch-blob/actions
|
||||||
[codecov-image]: https://flat.badgen.net/codecov/c/github/bitinn/fetch-blob/master
|
[codecov-image]: https://flat.badgen.net/codecov/c/github/node-fetch/fetch-blob/master
|
||||||
[codecov-url]: https://codecov.io/gh/bitinn/fetch-blob
|
[codecov-url]: https://codecov.io/gh/node-fetch/fetch-blob
|
||||||
[install-size-image]: https://flat.badgen.net/packagephobia/install/fetch-blob
|
[install-size-image]: https://flat.badgen.net/packagephobia/install/fetch-blob
|
||||||
[install-size-url]: https://packagephobia.now.sh/result?p=fetch-blob
|
[install-size-url]: https://packagephobia.now.sh/result?p=fetch-blob
|
||||||
|
|||||||
@@ -1,135 +0,0 @@
|
|||||||
// Based on https://github.com/tmpvar/jsdom/blob/aa85b2abf07766ff7bf5c1f6daafb3726f2f2db5/lib/jsdom/living/blob.js
|
|
||||||
// (MIT licensed)
|
|
||||||
|
|
||||||
const Stream = require('stream');
|
|
||||||
|
|
||||||
// Fix for "Readable" isn't a named export issue
|
|
||||||
const {Readable} = Stream;
|
|
||||||
|
|
||||||
const BUFFER = Symbol('buffer');
|
|
||||||
const TYPE = Symbol('type');
|
|
||||||
|
|
||||||
class Blob {
|
|
||||||
constructor(...args) {
|
|
||||||
this[TYPE] = '';
|
|
||||||
|
|
||||||
const blobParts = args[0];
|
|
||||||
const options = args[1];
|
|
||||||
|
|
||||||
const buffers = [];
|
|
||||||
/* eslint-disable-next-line no-unused-vars */
|
|
||||||
let size = 0;
|
|
||||||
|
|
||||||
if (blobParts) {
|
|
||||||
blobParts.forEach(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[BUFFER];
|
|
||||||
} else {
|
|
||||||
buffer = Buffer.from(typeof element === 'string' ? element : String(element));
|
|
||||||
}
|
|
||||||
|
|
||||||
size += buffer.length;
|
|
||||||
buffers.push(buffer);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this[BUFFER] = Buffer.concat(buffers);
|
|
||||||
|
|
||||||
const type = options && options.type !== undefined && String(options.type).toLowerCase();
|
|
||||||
if (type && !/[^\u0020-\u007E]/.test(type)) {
|
|
||||||
this[TYPE] = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options && Buffer.isBuffer(options.buffer)) {
|
|
||||||
this[BUFFER] = options.buffer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get size() {
|
|
||||||
return this[BUFFER].length;
|
|
||||||
}
|
|
||||||
|
|
||||||
get type() {
|
|
||||||
return this[TYPE];
|
|
||||||
}
|
|
||||||
|
|
||||||
text() {
|
|
||||||
return Promise.resolve(this[BUFFER].toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
arrayBuffer() {
|
|
||||||
const buf = this[BUFFER];
|
|
||||||
const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
||||||
return Promise.resolve(ab);
|
|
||||||
}
|
|
||||||
|
|
||||||
stream() {
|
|
||||||
const readable = new Readable();
|
|
||||||
readable._read = () => { };
|
|
||||||
readable.push(this[BUFFER]);
|
|
||||||
readable.push(null);
|
|
||||||
return readable;
|
|
||||||
}
|
|
||||||
|
|
||||||
toString() {
|
|
||||||
return '[object Blob]';
|
|
||||||
}
|
|
||||||
|
|
||||||
slice(...args) {
|
|
||||||
const {size} = this;
|
|
||||||
|
|
||||||
const start = args[0];
|
|
||||||
const end = args[1];
|
|
||||||
let relativeStart;
|
|
||||||
let relativeEnd;
|
|
||||||
|
|
||||||
if (start === undefined) {
|
|
||||||
relativeStart = 0;
|
|
||||||
} else if (start < 0) {
|
|
||||||
relativeStart = Math.max(size + start, 0);
|
|
||||||
} else {
|
|
||||||
relativeStart = Math.min(start, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (end === undefined) {
|
|
||||||
relativeEnd = size;
|
|
||||||
} else if (end < 0) {
|
|
||||||
relativeEnd = Math.max(size + end, 0);
|
|
||||||
} else {
|
|
||||||
relativeEnd = Math.min(end, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
const span = Math.max(relativeEnd - relativeStart, 0);
|
|
||||||
|
|
||||||
const buffer = this[BUFFER];
|
|
||||||
const slicedBuffer = buffer.slice(
|
|
||||||
relativeStart,
|
|
||||||
relativeStart + span
|
|
||||||
);
|
|
||||||
const blob = new Blob([], {type: args[2]});
|
|
||||||
blob[BUFFER] = slicedBuffer;
|
|
||||||
return blob;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.defineProperties(Blob.prototype, {
|
|
||||||
size: {enumerable: true},
|
|
||||||
type: {enumerable: true},
|
|
||||||
slice: {enumerable: true}
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.defineProperty(Blob.prototype, Symbol.toStringTag, {
|
|
||||||
value: 'Blob',
|
|
||||||
writable: false,
|
|
||||||
enumerable: false,
|
|
||||||
configurable: true
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = Blob;
|
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
const {statSync, createReadStream} = require('fs');
|
||||||
|
const Blob = require('.');
|
||||||
|
const DOMException = require('domexception');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} path filepath on the disk
|
||||||
|
* @returns {Blob}
|
||||||
|
*/
|
||||||
|
function blobFrom(path) {
|
||||||
|
const {size, mtime} = statSync(path);
|
||||||
|
const blob = new BlobDataItem({path, size, mtime});
|
||||||
|
|
||||||
|
return new Blob([blob]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a blob backed up by a file on the disk
|
||||||
|
* with minium requirement
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
class BlobDataItem {
|
||||||
|
constructor(options) {
|
||||||
|
this.size = options.size;
|
||||||
|
this.path = options.path;
|
||||||
|
this.start = options.start;
|
||||||
|
this.mtime = options.mtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slicing arguments is validated and formated
|
||||||
|
// by Blob.prototype.slice
|
||||||
|
slice(start, end) {
|
||||||
|
return new BlobDataItem({
|
||||||
|
path: this.path,
|
||||||
|
start,
|
||||||
|
mtime: this.mtime,
|
||||||
|
size: end - start
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stream() {
|
||||||
|
if (statSync(this.path).mtime > this.mtime) {
|
||||||
|
throw new DOMException('The requested file could not be read, typically due to permission problems that have occurred after a reference to a file was acquired.', 'NotReadableError');
|
||||||
|
}
|
||||||
|
|
||||||
|
return createReadStream(this.path, {
|
||||||
|
start: this.start,
|
||||||
|
end: this.start + this.size - 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get [Symbol.toStringTag]() {
|
||||||
|
return 'Blob';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = blobFrom;
|
||||||
@@ -0,0 +1,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;
|
||||||
Generated
-5358
File diff suppressed because it is too large
Load Diff
+53
-30
@@ -1,32 +1,55 @@
|
|||||||
{
|
{
|
||||||
"name": "fetch-blob",
|
"name": "fetch-blob",
|
||||||
"version": "1.0.5",
|
"version": "2.1.0",
|
||||||
"description": "A Blob implementation on node.js, originally from node-fetch",
|
"description": "A Blob implementation in Node.js, originally from node-fetch.",
|
||||||
"main": "blob.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"files": [
|
||||||
"test": "ava",
|
"from.js",
|
||||||
"report": "nyc ava",
|
"index.js",
|
||||||
"coverage": "nyc --reporter json --reporter text ava && codecov -f coverage/coverage-final.json"
|
"index.d.ts"
|
||||||
},
|
],
|
||||||
"repository": {
|
"scripts": {
|
||||||
"type": "git",
|
"lint": "xo",
|
||||||
"url": "git+https://github.com/bitinn/fetch-blob.git"
|
"test": "xo && ava",
|
||||||
},
|
"report": "c8 --reporter json --reporter text ava",
|
||||||
"keywords": [
|
"coverage": "c8 --reporter json --reporter text ava && codecov -f coverage/coverage-final.json",
|
||||||
"blob",
|
"prepublishOnly": "tsc --declaration --emitDeclarationOnly --allowJs index.js"
|
||||||
"node-fetch"
|
},
|
||||||
],
|
"repository": "https://github.com/node-fetch/fetch-blob.git",
|
||||||
"author": "David Frank",
|
"keywords": [
|
||||||
"license": "MIT",
|
"blob",
|
||||||
"bugs": {
|
"node-fetch"
|
||||||
"url": "https://github.com/bitinn/fetch-blob/issues"
|
],
|
||||||
},
|
"engines": {
|
||||||
"homepage": "https://github.com/bitinn/fetch-blob#readme",
|
"node": "^10.17.0 || >=12.3.0"
|
||||||
"devDependencies": {
|
},
|
||||||
"ava": "^1.4.1",
|
"author": "David Frank",
|
||||||
"codecov": "^3.6.1",
|
"license": "MIT",
|
||||||
"get-stream": "^5.1.0",
|
"bugs": {
|
||||||
"node-fetch": "^2.6.0",
|
"url": "https://github.com/node-fetch/fetch-blob/issues"
|
||||||
"nyc": "^14.1.1"
|
},
|
||||||
}
|
"homepage": "https://github.com/node-fetch/fetch-blob#readme",
|
||||||
|
"devDependencies": {
|
||||||
|
"ava": "^3.8.2",
|
||||||
|
"c8": "^7.2.0",
|
||||||
|
"codecov": "^3.7.0",
|
||||||
|
"get-stream": "^5.1.0",
|
||||||
|
"node-fetch": "^2.6.0",
|
||||||
|
"typescript": "^3.9.5",
|
||||||
|
"xo": "^0.32.0"
|
||||||
|
},
|
||||||
|
"xo": {
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "test.js",
|
||||||
|
"rules": {
|
||||||
|
"node/no-unsupported-features/es-syntax": 0,
|
||||||
|
"node/no-unsupported-features/node-builtins": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"domexception": "^2.0.1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,87 +1,162 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const test = require('ava');
|
||||||
|
const Blob = require('.');
|
||||||
|
const blobFrom = require('./from');
|
||||||
|
const getStream = require('get-stream');
|
||||||
|
const {Response} = require('node-fetch');
|
||||||
|
const {TextDecoder} = require('util');
|
||||||
|
|
||||||
import test from 'ava';
|
test('new Blob()', t => {
|
||||||
import Blob from './blob';
|
const blob = new Blob(); // eslint-disable-line no-unused-vars
|
||||||
import util from 'util';
|
|
||||||
import stream from 'stream';
|
|
||||||
import getStream from 'get-stream';
|
|
||||||
import { Response } from 'node-fetch';
|
|
||||||
|
|
||||||
const TextDecoder = util.TextDecoder;
|
|
||||||
const Readable = stream.Readable;
|
|
||||||
|
|
||||||
test('Blob ctor', t => {
|
|
||||||
let data = 'a=1';
|
|
||||||
let blob = new Blob([data]);
|
|
||||||
t.pass();
|
t.pass();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('new Blob(parts)', t => {
|
||||||
|
const data = 'a=1';
|
||||||
|
const blob = new Blob([data]); // eslint-disable-line no-unused-vars
|
||||||
|
t.pass();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Blob ctor parts', async t => {
|
||||||
|
const parts = [
|
||||||
|
'a',
|
||||||
|
new Uint8Array([98]),
|
||||||
|
new Uint16Array([25699]),
|
||||||
|
new Uint8Array([101]).buffer,
|
||||||
|
Buffer.from('f'),
|
||||||
|
new Blob(['g']),
|
||||||
|
{}
|
||||||
|
];
|
||||||
|
|
||||||
|
const blob = new Blob(parts);
|
||||||
|
t.is(await blob.text(), 'abcdefg[object Object]');
|
||||||
|
});
|
||||||
|
|
||||||
test('Blob size', t => {
|
test('Blob size', t => {
|
||||||
let data = 'a=1';
|
const data = 'a=1';
|
||||||
let blob = new Blob([data]);
|
const blob = new Blob([data]);
|
||||||
t.is(blob.size, data.length);
|
t.is(blob.size, data.length);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Blob type', t => {
|
test('Blob type', t => {
|
||||||
let data = 'a=1';
|
const type = 'text/plain';
|
||||||
let type = 'text/plain';
|
const blob = new Blob([], {type});
|
||||||
let blob = new Blob([data], { type });
|
|
||||||
t.is(blob.type, type);
|
t.is(blob.type, type);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Blob slice type', t => {
|
||||||
|
const type = 'text/plain';
|
||||||
|
const blob = new Blob().slice(0, 0, type);
|
||||||
|
t.is(blob.type, type);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('invalid Blob type', t => {
|
||||||
|
const blob = new Blob([], {type: '\u001Ftext/plain'});
|
||||||
|
t.is(blob.type, '');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('invalid Blob slice type', t => {
|
||||||
|
const blob = new Blob().slice(0, 0, '\u001Ftext/plain');
|
||||||
|
t.is(blob.type, '');
|
||||||
|
});
|
||||||
|
|
||||||
test('Blob text()', async t => {
|
test('Blob text()', async t => {
|
||||||
let data = 'a=1';
|
const data = 'a=1';
|
||||||
let type = 'text/plain';
|
const type = 'text/plain';
|
||||||
let blob = new Blob([data], { type });
|
const blob = new Blob([data], {type});
|
||||||
t.is(await blob.text(), data);
|
t.is(await blob.text(), data);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Blob arrayBuffer()', async t => {
|
test('Blob arrayBuffer()', async t => {
|
||||||
let data = 'a=1';
|
const data = 'a=1';
|
||||||
let type = 'text/plain';
|
const type = 'text/plain';
|
||||||
let blob = new Blob([data], { type });
|
const blob = new Blob([data], {type});
|
||||||
|
|
||||||
let decoder = new TextDecoder('utf-8');
|
const decoder = new TextDecoder('utf-8');
|
||||||
let buffer = await blob.arrayBuffer();
|
const buffer = await blob.arrayBuffer();
|
||||||
t.is(decoder.decode(buffer), data);
|
t.is(decoder.decode(buffer), data);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Blob stream()', async t => {
|
test('Blob stream()', async t => {
|
||||||
let data = 'a=1';
|
const data = 'a=1';
|
||||||
let type = 'text/plain';
|
const type = 'text/plain';
|
||||||
let blob = new Blob([data], { type });
|
const blob = new Blob([data], {type});
|
||||||
let result = await getStream(blob.stream());
|
const result = await getStream(blob.stream());
|
||||||
t.is(result, data);
|
t.is(result, data);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Blob toString()', t => {
|
test('Blob toString()', t => {
|
||||||
let data = 'a=1';
|
const data = 'a=1';
|
||||||
let type = 'text/plain';
|
const type = 'text/plain';
|
||||||
let blob = new Blob([data], { type });
|
const blob = new Blob([data], {type});
|
||||||
t.is(blob.toString(), '[object Blob]');
|
t.is(blob.toString(), '[object Blob]');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Blob slice()', async t => {
|
test('Blob slice()', async t => {
|
||||||
let data = 'a=1';
|
const data = 'abcdefgh';
|
||||||
let type = 'text/plain';
|
const blob = new Blob([data]).slice();
|
||||||
let blob = new Blob([data], { type });
|
t.is(await blob.text(), data);
|
||||||
let blob2 = blob.slice(0, 1);
|
});
|
||||||
t.is(await blob2.text(), data.slice(0, 1));
|
|
||||||
|
test('Blob slice(0, 1)', async t => {
|
||||||
|
const data = 'abcdefgh';
|
||||||
|
const blob = new Blob([data]).slice(0, 1);
|
||||||
|
t.is(await blob.text(), 'a');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Blob slice(-1)', async t => {
|
||||||
|
const data = 'abcdefgh';
|
||||||
|
const blob = new Blob([data]).slice(-1);
|
||||||
|
t.is(await blob.text(), 'h');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Blob slice(0, -1)', async t => {
|
||||||
|
const data = 'abcdefgh';
|
||||||
|
const blob = new Blob([data]).slice(0, -1);
|
||||||
|
t.is(await blob.text(), 'abcdefg');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Blob(["hello ", "world"]).slice(5)', async t => {
|
||||||
|
const parts = ['hello ', 'world'];
|
||||||
|
const blob = new Blob(parts);
|
||||||
|
t.is(await blob.slice(5).text(), ' world');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('throw away unwanted parts', async t => {
|
||||||
|
const blob = new Blob(['a', 'b', 'c']).slice(1, 2);
|
||||||
|
t.is(await blob.text(), 'b');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Blob works with node-fetch Response.blob()', async t => {
|
test('Blob works with node-fetch Response.blob()', async t => {
|
||||||
let data = 'a=1';
|
const data = 'a=1';
|
||||||
let type = 'text/plain';
|
const type = 'text/plain';
|
||||||
let blob = new Blob([data], { type });
|
const blob = new Blob([data], {type});
|
||||||
let res = new Response(blob);
|
const response = new Response(blob);
|
||||||
let blob2 = await res.blob();
|
const blob2 = await response.blob();
|
||||||
t.is(await blob2.text(), data);
|
t.is(await blob2.text(), data);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Blob works with node-fetch Response.text()', async t => {
|
test('Blob works with node-fetch Response.text()', async t => {
|
||||||
let data = 'a=1';
|
const data = 'a=1';
|
||||||
let type = 'text/plain';
|
const type = 'text/plain';
|
||||||
let blob = new Blob([data], { type });
|
const blob = new Blob([data], {type});
|
||||||
let res = new Response(blob);
|
const response = new Response(blob);
|
||||||
let text = await res.text();
|
const text = await response.text();
|
||||||
t.is(text, data);
|
t.is(text, data);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('blob part backed up by filesystem', async t => {
|
||||||
|
const blob = blobFrom('./LICENSE');
|
||||||
|
t.is(await blob.slice(0, 3).text(), 'MIT');
|
||||||
|
t.is(await blob.slice(4, 11).text(), 'License');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Reading after modified should fail', async t => {
|
||||||
|
const blob = blobFrom('./LICENSE');
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
const now = new Date();
|
||||||
|
// Change modified time
|
||||||
|
fs.utimesSync('./LICENSE', now, now);
|
||||||
|
const error = await blob.text().catch(error => error);
|
||||||
|
t.is(error.name, 'NotReadableError');
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user