This repository was archived by the owner on Feb 12, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat: non-bufferring multipart body encoder #3151
Closed
Closed
Changes from 11 commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
3e7baf7
feat: bufferring free multipart body encoder
Gozala 9686628
fix: add support for String instances
Gozala 1596609
fix: browser module paths overrides
Gozala e402018
fix: multipartRequest so body does not emit blobs
Gozala 58b8d2c
Merge branch 'master' into blobity-blob
Gozala c1c05d0
fix: encode filename once
Gozala 567b738
fix: add \r\n after each part of form-data
Gozala c9fc232
chore: write blob tests
Gozala 39464aa
fix: incorrect header used for nsecs
Gozala 908d99e
fix: use native blobs in elector renderer
Gozala bfe012f
fix: prefer native File over polyfill (in elector)
Gozala 0dbd5af
fix: error in number of arguments that were passed
Gozala f015af9
chore: add comment to explain name normalization
Gozala 499b4ef
fix: typos
Gozala ced65e2
chore: use normalise instead of normalize
Gozala ad9d617
fix: ensure that FileStream content is valid
Gozala 09c86b9
fix: preserve file metadata
Gozala 205fde7
fix: ensure Iterable<Bytes> instead of assuming
Gozala 9af1bf1
fix: properly handle `null` input.
Gozala 35d6eb3
chore: test that streams aren't used unnecessarily
Gozala 3bdc52b
fix: file api compatiblity
Gozala ee74c82
chore: add file API tests
Gozala d44352a
Merge remote-tracking branch 'upstream/master' into blobity-blob
Gozala 5d8ff81
chore: remove unnecessary browser entry
Gozala c43faf7
chore: factor out blob and file into separate libs
Gozala 631ebf3
Merge remote-tracking branch 'upstream/master' into blobity-blob
Gozala 2b92e4c
fix: update test to account for lastModified field
Gozala 6421e24
chore: disable test requiring mtime support in go
Gozala 2fc990c
fix: example test to account lastModified field
Gozala 1859549
chore: revert changes to handle File's lastModifed
Gozala dcedb66
fix: reflect removed lastModified->mtime in tests
Gozala File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| // @ts-check | ||
| 'use strict' | ||
| /* eslint-env browser */ | ||
|
|
||
| exports.Blob = Blob | ||
|
|
||
| /** | ||
| * Universal blob reading function | ||
| * @param {Blob} blob | ||
| * @returns {AsyncIterable<Uint8Array>} | ||
| */ | ||
| const readBlob = async function * (blob) { | ||
| const { body } = new Response(blob) | ||
| const reader = body.getReader() | ||
| while (true) { | ||
| const next = await reader.read() | ||
| if (next.done) { | ||
| return | ||
| } else { | ||
| yield next.value | ||
| } | ||
| } | ||
| } | ||
| exports.readBlob = readBlob |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| // @ts-check | ||
| 'use strict' | ||
|
|
||
| // Electron in renderer process has native `Blob` but it would not pick up | ||
| // browser override. Therefor we do the runtime check and pick browser verison | ||
| // if native Blob is available and node polyfill otherwise. | ||
| if (typeof Blob === 'function') { | ||
| module.exports = require('./blob.browser') | ||
| } else { | ||
| module.exports = require('./blob.node') | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,198 @@ | ||
| // @ts-check | ||
| 'use strict' | ||
|
|
||
| const { TextEncoder, TextDecoder } = require('util') | ||
|
|
||
| class Blob { | ||
| /** | ||
| * | ||
| * @param {BlobPart[]} [init] | ||
| * @param {Object} [options] | ||
| * @param {string} [options.type] | ||
| * | ||
| */ | ||
| constructor (init = [], options = {}) { | ||
| /** @type {Uint8Array[]} */ | ||
| const parts = [] | ||
|
|
||
| let size = 0 | ||
| for (const part of init) { | ||
| if (typeof part === 'string') { | ||
| const bytes = new TextEncoder().encode(part) | ||
| parts.push(bytes) | ||
| size += bytes.byteLength | ||
| } else if (part instanceof Blob) { | ||
| size += part.size | ||
| // @ts-ignore - `_parts` is marked private so TS will complain about | ||
| // accessing it. | ||
| parts.push(...part._parts) | ||
| } else if (part instanceof ArrayBuffer) { | ||
| parts.push(new Uint8Array(part)) | ||
| size += part.byteLength | ||
| } else if (part instanceof Uint8Array) { | ||
| parts.push(part) | ||
| size += part.byteLength | ||
| } else if (ArrayBuffer.isView(part)) { | ||
| const { buffer, byteOffset, byteLength } = part | ||
| parts.push(new Uint8Array(buffer, byteOffset, byteLength)) | ||
| size += byteLength | ||
| } else { | ||
| throw new TypeError(`Can not convert ${part} value to a BlobPart`) | ||
| } | ||
| } | ||
|
|
||
| /** @private */ | ||
| this._size = size | ||
| /** @private */ | ||
| this._type = readType(options.type) | ||
| /** @private */ | ||
| this._parts = parts | ||
| } | ||
|
|
||
| /** | ||
| * A string indicating the MIME type of the data contained in the Blob. | ||
| * If the type is unknown, this string is empty. | ||
| * @type {string} | ||
| */ | ||
| get type () { | ||
| return this._type | ||
| } | ||
|
|
||
| /** | ||
| * The size, in bytes, of the data contained in the Blob object. | ||
| * @type {number} | ||
| */ | ||
| get size () { | ||
| return this._size | ||
| } | ||
|
|
||
| /** | ||
| * Returns a new Blob object containing the data in the specified range of | ||
| * bytes of the blob on which it's called. | ||
| * @param {number} [start=0] - An index into the Blob indicating the first | ||
| * byte to include in the new Blob. If you specify a negative value, it's | ||
| * treated as an offset from the end of the Blob toward the beginning. For | ||
| * example, `-10` would be the 10th from last byte in the Blob. The default | ||
| * value is `0`. If you specify a value for start that is larger than the | ||
| * size of the source Blob, the returned Blob has size 0 and contains no | ||
| * data. | ||
| * @param {number} [end] - An index into the `Blob` indicating the first byte | ||
| * that will *not* be included in the new `Blob` (i.e. the byte exactly at | ||
| * this index is not included). If you specify a negative value, it's treated | ||
| * as an offset from the end of the Blob toward the beginning. For example, | ||
| * `-10` would be the 10th from last byte in the `Blob`. The default value is | ||
| * size. | ||
| * @param {string} [type] - The content type to assign to the new Blob; | ||
| * this will be the value of its type property. The default value is an empty | ||
| * string. | ||
| * @returns {Blob} | ||
| */ | ||
| slice (start = 0, end = this.size, type = '') { | ||
| const { size, _parts } = this | ||
| let offset = start < 0 | ||
| ? Math.max(size + start, 0) | ||
| : Math.min(start, size) | ||
|
|
||
| let limit = (end < 0 ? Math.max(size + end, 0) : Math.min(end, size)) | ||
| const span = Math.max(limit - offset, 0) | ||
| const blob = new Blob([], { type }) | ||
|
|
||
| if (span === 0) { | ||
| return blob | ||
| } | ||
|
|
||
| let blobSize = 0 | ||
| const blobParts = [] | ||
| for (const part of _parts) { | ||
| const { byteLength } = part | ||
| if (offset > 0 && byteLength <= offset) { | ||
| offset -= byteLength | ||
| limit -= byteLength | ||
| } else { | ||
| const chunk = part.subarray(offset, Math.min(byteLength, limit)) | ||
| blobParts.push(chunk) | ||
| blobSize += chunk.byteLength | ||
| // no longer need to take that into account | ||
| offset = 0 | ||
|
|
||
| // don't add the overflow to new blobParts | ||
| if (blobSize >= span) { | ||
| break | ||
| } | ||
| } | ||
| } | ||
|
|
||
| blob._parts = blobParts | ||
| blob._size = blobSize | ||
|
|
||
| return blob | ||
| } | ||
|
|
||
| /** | ||
| * Returns a promise that resolves with an ArrayBuffer containing the entire | ||
| * contents of the Blob as binary data. | ||
| * @returns {Promise<ArrayBuffer>} | ||
| */ | ||
| // eslint-disable-next-line require-await | ||
| async arrayBuffer () { | ||
| const buffer = new ArrayBuffer(this.size) | ||
| const bytes = new Uint8Array(buffer) | ||
| let offset = 0 | ||
| for (const part of this._parts) { | ||
| bytes.set(part, offset) | ||
| offset += part.byteLength | ||
| } | ||
| return buffer | ||
| } | ||
|
|
||
| /** | ||
| * Returns a promise that resolves with a USVString containing the entire | ||
| * contents of the Blob interpreted as UTF-8 text. | ||
| * @returns {Promise<string>} | ||
| */ | ||
| // eslint-disable-next-line require-await | ||
| async text () { | ||
| const decoder = new TextDecoder() | ||
| let text = '' | ||
| for (const part of this._parts) { | ||
| text += decoder.decode(part) | ||
| } | ||
| return text | ||
| } | ||
|
|
||
| /** | ||
| * @returns {never} | ||
| */ | ||
| // eslint-disable-next-line valid-jsdoc | ||
| stream () { | ||
| throw Error('Not implemented') | ||
| } | ||
|
|
||
| get [Symbol.toStringTag] () { | ||
| return 'Blob' | ||
| } | ||
| } | ||
|
|
||
| // Marking export as a DOM File object instead of custom class. | ||
| /** @type {typeof window.Blob} */ | ||
| exports.Blob = Blob | ||
|
|
||
| /** | ||
| * @param {string} [input] | ||
| * @returns {string} | ||
| */ | ||
| const readType = (input = '') => { | ||
| const type = String(input).toLowerCase() | ||
| return /[^\u0020-\u007E]/.test(type) ? '' : type | ||
| } | ||
| /** | ||
| * Universal blob reading function | ||
| * @param {InstanceType<typeof window.Blob>} blob | ||
| * @returns {AsyncIterable<Uint8Array>} | ||
| */ | ||
| // eslint-disable-next-line require-await | ||
| const readBlob = async function * BlobParts (blob) { | ||
| // @ts-ignore - accessing private property | ||
| yield * blob._parts | ||
| } | ||
| exports.readBlob = readBlob |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| 'use strict' | ||
| /* eslint-env browser */ | ||
|
|
||
| exports.File = File |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| // @ts-check | ||
| 'use strict' | ||
|
|
||
| // Electron in renderer process has native `File` but it would not pick up | ||
| // browser override. Therefor we do the runtime check and pick the browser | ||
| // verison if native `File` is available and node polyfill otherwise. | ||
| if (typeof File === 'function') { | ||
| module.exports = require('./file.browser') | ||
| } else { | ||
| module.exports = require('./file.node') | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| // @ts-check | ||
| 'use strict' | ||
|
|
||
| const { Blob } = require('./blob') | ||
|
|
||
| class File extends Blob { | ||
| /** | ||
| * | ||
| * @param {BlobPart[]} init | ||
| * @param {string} name - A USVString representing the file name or the path | ||
| * to the file. | ||
| * @param {Object} [options] | ||
| * @param {string} [options.type] - A DOMString representing the MIME type | ||
| * of the content that will be put into the file. Defaults to a value of "". | ||
| * @param {number} [options.lastModified] - A number representing the number | ||
| * of milliseconds between the Unix time epoch and when the file was last | ||
| * modified. Defaults to a value of Date.now(). | ||
| */ | ||
| constructor (init, name, options = {}) { | ||
| super(init, options) | ||
| /** @private */ | ||
| this._name = name.replace(/\//g, ':') | ||
| this._lastModified = options.lastModified || Date.now() | ||
| } | ||
|
|
||
| /** | ||
| * The name of the file referenced by the File object. | ||
| * @type {string} | ||
| */ | ||
| get name () { | ||
| return this._name | ||
| } | ||
|
|
||
| /** | ||
| * The path the URL of the File is relative to. | ||
| * @type {string} | ||
| */ | ||
| get webkitRelativePath () { | ||
| return '' | ||
| } | ||
|
|
||
| /** | ||
| * Returns the last modified time of the file, in millisecond since the UNIX | ||
| * epoch (January 1st, 1970 at Midnight). | ||
| * @returns {number} | ||
| */ | ||
| get lastModified () { | ||
| return this._lastModified | ||
| } | ||
|
|
||
| get [Symbol.toStringTag] () { | ||
| return 'File' | ||
| } | ||
| } | ||
|
|
||
| // Marking export as a DOM File object instead of custom class. | ||
| /** @type {typeof window.File} */ | ||
| exports.File = File | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.