Skip to content
This repository has been archived by the owner on Feb 12, 2024. It is now read-only.

Commit

Permalink
chore: address review comments
Browse files Browse the repository at this point in the history
  • Loading branch information
Gozala committed Nov 13, 2020
1 parent e3d1629 commit 6050c6f
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 69 deletions.
2 changes: 2 additions & 0 deletions packages/ipfs-core-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
"it-all": "^1.0.4",
"it-map": "^1.0.4",
"it-peekable": "^1.0.1",
"multiaddr": "^8.0.0",
"multiaddr-to-uri": "^6.0.0",
"uint8arrays": "^1.1.0"
},
"devDependencies": {
Expand Down
23 changes: 23 additions & 0 deletions packages/ipfs-core-utils/src/to-url-string.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict'

const multiaddr = require('multiaddr')
const multiAddrToUri = require('multiaddr-to-uri')

/**
* @param {string|Multiaddr|URL} url - A string, multiaddr or URL to convert to a url string
* @returns {string}
*/
module.exports = (url) => {
try {
// @ts-expect-error
url = multiAddrToUri(multiaddr(url))
} catch (err) { }

url = url.toString()

return url
}

/**
* @typedef {import('multiaddr')} Multiaddr
*/
2 changes: 0 additions & 2 deletions packages/ipfs-http-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,13 @@
"ipld-dag-cbor": "^0.17.0",
"ipld-dag-pb": "^0.20.0",
"ipld-raw": "^6.0.0",
"iso-url": "^1.0.0",
"it-last": "^1.0.4",
"it-map": "^1.0.4",
"it-tar": "^1.2.2",
"it-to-buffer": "^1.0.2",
"it-to-stream": "^0.1.2",
"merge-options": "^2.0.0",
"multiaddr": "^8.0.0",
"multiaddr-to-uri": "^6.0.0",
"multibase": "^3.0.0",
"multicodec": "^2.0.1",
"multihashes": "^3.0.1",
Expand Down
46 changes: 29 additions & 17 deletions packages/ipfs-http-client/src/add-all.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ module.exports = configure((api) => {
// allow aborting requests on body errors
const controller = new AbortController()
const signal = anySignal([controller.signal, options.signal])
const { headers, body, total, lengthComputable } =
const { headers, body, total, parts } =
await multipartRequest(source, controller, options.headers)

// In browser response body only starts streaming once upload is
Expand All @@ -25,7 +25,7 @@ module.exports = configure((api) => {
// `{ total, loaded}` passed to `onUploadProgress` and `multipart.total`
// in which case we disable progress updates to be written out.
const [progressFn, onUploadProgress] = typeof options.progress === 'function'
? createProgressHandler(lengthComputable, total, options.progress)
? createProgressHandler(total, parts, options.progress)
: [null, null]

const res = await api.post('add', {
Expand Down Expand Up @@ -58,27 +58,42 @@ module.exports = configure((api) => {
* Returns simple progress callback when content length isn't computable or a
* progress event handler that inerpolates progress from upload progress events.
*
* @param {boolean} lengthComputable
* @param {number} total
* @param {(n:number) => void} progress
* @param {{name:string, start:number, end:number}[]|null} parts
* @param {(n:number, name:string) => void} progress
*/
const createProgressHandler = (lengthComputable, total, progress) =>
lengthComputable ? [null, createOnUploadPrgress(total, progress)] : [progress, null]
const createProgressHandler = (total, parts, progress) =>
parts ? [null, createOnUploadPrgress(total, parts, progress)] : [progress, null]

/**
* Creates a progress handler that interpolates progress from upload progress
* events and total size of the content that is added.
*
* @param {number} size - actual content size
* @param {(n:number) => void} progress
* @returns {(event:{total:number, loaded: number}) => progress}
*/
const createOnUploadPrgress = (size, progress) => ({ loaded, total }) =>
progress(Math.floor(loaded / total * size))

/**
* @typedef {import('../../ipfs/src/core/components/add-all').UnixFSEntry} UnixFSEntry
* @param {{name:string, start:number, end:number}[]} parts
* @param {(n:number, name:string) => void} progress
* @returns {(event:{total:number, loaded: number}) => void}
*/
const createOnUploadPrgress = (size, parts, progress) => {
let index = 0
return ({ loaded, total }) => {
// Derive position from the current progress.
const position = Math.floor(loaded / total * size)
while (true) {
const { start, end, name } = parts[index]
// If within current part range reporst progress and break the loop
if (position < end) {
progress(position - start, name)
break
// If passed current part range report final byte for the chunk and
// move to next one.
} else {
progress(end - start, name)
index += 1
}
}
}
}

/**
* @param {any} input
Expand Down Expand Up @@ -108,7 +123,4 @@ function toCoreInterface ({ name, hash, size, mode, mtime, mtimeNsecs }) {

/**
* @typedef {import('ipfs-core/src/components/add-all/index').UnixFSEntry} UnixFSEntry
* @typedef {import('./index').HttpOptions} HttpOptions
* @typedef {Object} HttpAddOptions
* @property {}
*/
77 changes: 39 additions & 38 deletions packages/ipfs-http-client/src/lib/core.js
Original file line number Diff line number Diff line change
@@ -1,58 +1,59 @@
'use strict'
/* eslint-env browser */
const Multiaddr = require('multiaddr')
const toUri = require('multiaddr-to-uri')
const { isBrowser, isWebWorker } = require('ipfs-utils/src/env')
const { URL } = require('iso-url')
const parseDuration = require('parse-duration').default
const log = require('debug')('ipfs-http-client:lib:error-handler')
const HTTP = require('ipfs-utils/src/http')
const merge = require('merge-options')
const toUrlString = require('ipfs-core-utils/src/to-url-string')

/**
* @param {any} input
* @returns {input is Multiaddr}
*/
const isMultiaddr = (input) => {
try {
Multiaddr(input) // eslint-disable-line no-new
return true
} catch (e) {
return false
}
}
const DEFAULT_PROTOCOL = isBrowser || isWebWorker ? location.protocol : 'http'
const DEFAULT_HOST = isBrowser || isWebWorker ? location.hostname : 'localhost'
const DEFAULT_PORT = isBrowser || isWebWorker ? location.port : '5001'

/**
* @param {any} options
* @param {ClientOptions|URL|Multiaddr|string} [options]
* @returns {ClientOptions}
*/
const normalizeInput = (options = {}) => {
if (isMultiaddr(options)) {
options = { url: toUri(options) }
} else if (typeof options === 'string') {
options = { url: options }
const normalizeOptions = (options = {}) => {
let url
let opts = {}

if (typeof options === 'string' || Multiaddr.isMultiaddr(options)) {
url = new URL(toUrlString(options))
} else if (options instanceof URL) {
url = options
} else if (typeof options.url === 'string' || Multiaddr.isMultiaddr(options.url)) {
url = new URL(toUrlString(options.url))
opts = options
} else if (options.url instanceof URL) {
url = options.url
opts = options
} else {
opts = options || {}

const protocol = (opts.protocol || DEFAULT_PROTOCOL).replace(':', '')
const host = (opts.host || DEFAULT_HOST).split(':')[0]
const port = (opts.port || DEFAULT_PORT)

url = new URL(`${protocol}://${host}:${port}`)
}

const url = new URL(options.url)
if (options.apiPath) {
url.pathname = options.apiPath
if (opts.apiPath) {
url.pathname = opts.apiPath
} else if (url.pathname === '/' || url.pathname === undefined) {
url.pathname = 'api/v0'
}
if (!options.url) {
if (isBrowser || isWebWorker) {
url.protocol = options.protocol || location.protocol
url.hostname = options.host || location.hostname
url.port = options.port || location.port
} else {
url.hostname = options.host || 'localhost'
url.port = options.port || '5001'
url.protocol = options.protocol || 'http'
}
}
options.url = url

return options
return {
...opts,
host: url.host,
protocol: url.protocol.replace(':', ''),
port: Number(url.port),
apiPath: url.pathname,
url
}
}

const errorHandler = async (response) => {
Expand Down Expand Up @@ -111,7 +112,7 @@ const parseTimeout = (value) => {
* @property {Headers|Record<string, string>} [headers] - Request headers.
* @property {number|string} [timeout] - Amount of time until request should timeout in ms or humand readable. https://www.npmjs.com/package/parse-duration for valid string values.
* @property {string} [apiPath] - Path to the API.
* @property {URL|string} [url] - Full API URL.
* @property {URL|string|Multiaddr} [url] - Full API URL.
* @property {object} [ipld]
* @property {any[]} [ipld.formats] - An array of additional [IPLD formats](https://github.com/ipld/interface-ipld-format) to support
* @property {(format: string) => Promise<any>} [ipld.loadFormat] - an async function that takes the name of an [IPLD format](https://github.com/ipld/interface-ipld-format) as a string and should return the implementation of that codec
Expand All @@ -121,7 +122,7 @@ class Client extends HTTP {
* @param {ClientOptions|URL|Multiaddr|string} [options]
*/
constructor (options = {}) {
const opts = normalizeInput(options)
const opts = normalizeOptions(options)

super({
timeout: parseTimeout(opts.timeout) || 60000 * 20,
Expand Down
15 changes: 9 additions & 6 deletions packages/ipfs-http-client/src/lib/multipart-request.browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const mtimeToObject = require('./mtime-to-object')
const { File, FormData } = require('ipfs-utils/src/globalthis')

async function multipartRequest (source = '', abortController, headers = {}) {
const parts = []
const formData = new FormData()
let index = 0
let total = 0
Expand All @@ -27,10 +28,9 @@ async function multipartRequest (source = '', abortController, headers = {}) {
qs.push(`mode=${modeToString(mode)}`)
}

if (mtime != null) {
const {
secs, nsecs
} = mtimeToObject(mtime)
const time = mtimeToObject(mtime)
if (time != null) {
const { secs, nsecs } = time

qs.push(`mtime=${secs}`)

Expand All @@ -45,17 +45,20 @@ async function multipartRequest (source = '', abortController, headers = {}) {

if (content) {
formData.set(fieldName, content, encodeURIComponent(path))
total += content.size
const end = total + content.size
parts.push({ name: path, start: total, end })
total = end
} else {
formData.set(fieldName, new File([''], encodeURIComponent(path), { type: 'application/x-directory' }))
parts.push({ name: path, start: total, end: total })
}

index++
}

return {
lengthComputable: true,
total,
parts,
headers,
body: formData
}
Expand Down
17 changes: 12 additions & 5 deletions packages/ipfs-http-client/src/lib/multipart-request.node.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ const mtimeToObject = require('./mtime-to-object')
const merge = require('merge-options').bind({ ignoreUndefined: true })
const toStream = require('it-to-stream')

/**
*
* @param {Object} source
* @param {AbortController} abortController
* @param {Headers|Record<string, string>} [headers]
* @param {string} [boundary]
*/
async function multipartRequest (source = '', abortController, headers = {}, boundary = `-----------------------------${nanoid()}`) {
async function * streamFiles (source) {
try {
Expand All @@ -29,10 +36,9 @@ async function multipartRequest (source = '', abortController, headers = {}, bou
qs.push(`mode=${modeToString(mode)}`)
}

if (mtime != null) {
const {
secs, nsecs
} = mtimeToObject(mtime)
const time = mtimeToObject(mtime)
if (time != null) {
const { secs, nsecs } = time

qs.push(`mtime=${secs}`)

Expand All @@ -58,14 +64,15 @@ async function multipartRequest (source = '', abortController, headers = {}, bou
}
} catch (err) {
// workaround for https://github.com/node-fetch/node-fetch/issues/753
// @ts-ignore - abort does not expect an arguments
abortController.abort(err)
} finally {
yield `\r\n--${boundary}--\r\n`
}
}

return {
lengthComputable: false,
parts: null,
total: -1,
headers: merge(headers, {
'Content-Type': `multipart/form-data; boundary=${boundary}`
Expand Down
Loading

0 comments on commit 6050c6f

Please sign in to comment.