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
}