ipfs-utils
Version:
Package to aggregate shared logic and dependencies for the IPFS ecosystem
101 lines (93 loc) • 2.94 kB
JavaScript
'use strict'
// @ts-expect-error Request, Response and Headers are global types but concrete in implementations
const { Request, Response, Headers, default: defaultFetch, fetch: fetchFetch } = require('../fetch')
// @ts-ignore
const toStream = require('it-to-stream')
const { Buffer } = require('buffer')
/**
* @typedef {import('stream').Readable} NodeReadableStream
*
* @typedef {import('../types').FetchOptions} FetchOptions
* @typedef {import('../types').ProgressFn} ProgressFn
*/
// undici and node-fetch have different exports
const nativeFetch = defaultFetch ?? fetchFetch
/**
* @param {string|Request} url
* @param {FetchOptions} [options]
* @returns {Promise<Response>}
*/
const fetch = (url, options = {}) =>
// @ts-ignore
nativeFetch(url, withUploadProgress(options))
/**
* Takes fetch options and wraps request body to track upload progress if
* `onUploadProgress` is supplied. Otherwise returns options as is.
*
* @param {FetchOptions} options
* @returns {FetchOptions}
*/
const withUploadProgress = (options) => {
const { onUploadProgress, body } = options
if (onUploadProgress && body) {
// This works around the fact that electron-fetch serializes `Uint8Array`s
// and `ArrayBuffer`s to strings.
const content = normalizeBody(body)
// @ts-expect-error this is node-fetch
const rsp = new Response(content)
// @ts-expect-error this is node-fetch
const source = iterateBodyWithProgress(/** @type {NodeReadableStream} */(rsp.body), onUploadProgress)
return {
...options,
body: toStream.readable(source)
}
} else {
return options
}
}
/**
* @param {BodyInit | NodeReadableStream} input
*/
const normalizeBody = (input) => {
if (input instanceof ArrayBuffer) {
return Buffer.from(input)
} else if (ArrayBuffer.isView(input)) {
return Buffer.from(input.buffer, input.byteOffset, input.byteLength)
} else if (typeof input === 'string') {
return Buffer.from(input)
}
return input
}
/**
* Takes body from native-fetch response as body and `onUploadProgress` handler
* and returns async iterable that emits body chunks and emits
* `onUploadProgress`.
*
* @param {NodeReadableStream | null} body
* @param {ProgressFn} onUploadProgress
* @returns {AsyncIterable<Buffer>}
*/
const iterateBodyWithProgress = async function * (body, onUploadProgress) {
if (body == null) {
onUploadProgress({ total: 0, loaded: 0, lengthComputable: true })
} else if (Buffer.isBuffer(body)) {
const total = body.byteLength
const lengthComputable = true
yield body
onUploadProgress({ total, loaded: total, lengthComputable })
} else {
const total = 0
const lengthComputable = false
let loaded = 0
for await (const chunk of body) {
loaded += chunk.byteLength
yield chunk
onUploadProgress({ total, loaded, lengthComputable })
}
}
}
module.exports = {
fetch,
Request,
Headers
}