diff --git a/packages/ipfs-core-utils/package.json b/packages/ipfs-core-utils/package.json index b59e21115d..08b4b554c0 100644 --- a/packages/ipfs-core-utils/package.json +++ b/packages/ipfs-core-utils/package.json @@ -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": { diff --git a/packages/ipfs-core-utils/src/to-url-string.js b/packages/ipfs-core-utils/src/to-url-string.js new file mode 100644 index 0000000000..c74f0e601a --- /dev/null +++ b/packages/ipfs-core-utils/src/to-url-string.js @@ -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 + */ diff --git a/packages/ipfs-http-client/package.json b/packages/ipfs-http-client/package.json index dc8dba97ff..599229b116 100644 --- a/packages/ipfs-http-client/package.json +++ b/packages/ipfs-http-client/package.json @@ -61,7 +61,6 @@ "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", @@ -69,7 +68,6 @@ "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", diff --git a/packages/ipfs-http-client/src/lib/core.js b/packages/ipfs-http-client/src/lib/core.js index a2f957a892..e4e09662c1 100644 --- a/packages/ipfs-http-client/src/lib/core.js +++ b/packages/ipfs-http-client/src/lib/core.js @@ -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) => { @@ -111,7 +112,7 @@ const parseTimeout = (value) => { * @property {Headers|Record} [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} [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 @@ -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, diff --git a/packages/ipfs-http-client/test/constructor.spec.js b/packages/ipfs-http-client/test/constructor.spec.js index 9b579839da..004dcb2984 100644 --- a/packages/ipfs-http-client/test/constructor.spec.js +++ b/packages/ipfs-http-client/test/constructor.spec.js @@ -28,6 +28,15 @@ describe('ipfs-http-client constructor tests', () => { expectConfig(ipfs, { host, port, protocol }) }) + it('opts with URL components from URL', () => { + const host = 'wizard.world' + const port = '999' + const protocol = 'https' + const url = new URL(`${protocol}://${host}:${port}`) + const ipfs = ipfsClient({ host: url.host, port: url.port, protocol: url.protocol }) + expectConfig(ipfs, { host, port, protocol }) + }) + it('multiaddr dns4 string (implicit http)', () => { const host = 'foo.com' const port = '1001' @@ -79,6 +88,22 @@ describe('ipfs-http-client constructor tests', () => { expectConfig(ipfs, { host, port }) }) + it('URL as string', () => { + const host = '10.100.100.255' + const port = '9999' + const apiPath = '/future/api/v1/' + const ipfs = ipfsClient(`http://${host}:${port}${apiPath}`) + expectConfig(ipfs, { host, port, apiPath }) + }) + + it('URL as URL', () => { + const host = '10.100.100.255' + const port = '9999' + const apiPath = '/future/api/v1/' + const ipfs = ipfsClient(new URL(`http://${host}:${port}${apiPath}`)) + expectConfig(ipfs, { host, port, apiPath }) + }) + it('host, port and api path', () => { const host = '10.100.100.255' const port = '9999' @@ -87,13 +112,57 @@ describe('ipfs-http-client constructor tests', () => { expectConfig(ipfs, { host, port, apiPath }) }) - it('url', () => { + it('options.url as URL string', () => { const host = '10.100.100.255' const port = '9999' const apiPath = '/future/api/v1/' const ipfs = ipfsClient({ url: `http://${host}:${port}${apiPath}` }) expectConfig(ipfs, { host, port, apiPath }) }) + + it('options.url as URL', () => { + const host = '10.100.100.255' + const port = '9999' + const apiPath = '/future/api/v1/' + const ipfs = ipfsClient({ url: new URL(`http://${host}:${port}${apiPath}`) }) + expectConfig(ipfs, { host, port, apiPath }) + }) + + it('options.url as multiaddr (implicit http)', () => { + const host = 'foo.com' + const port = '1001' + const protocol = 'http' // default to http if not specified in multiaddr + const addr = `/dns4/${host}/tcp/${port}` + const ipfs = ipfsClient({ url: multiaddr(addr) }) + expectConfig(ipfs, { host, port, protocol }) + }) + + it('options.url as multiaddr (explicit https)', () => { + const host = 'foo.com' + const port = '1001' + const protocol = 'https' + const addr = `/dns4/${host}/tcp/${port}/https` + const ipfs = ipfsClient({ url: multiaddr(addr) }) + expectConfig(ipfs, { host, port, protocol }) + }) + + it('options.url as multiaddr string (implicit http)', () => { + const host = 'foo.com' + const port = '1001' + const protocol = 'http' // default to http if not specified in multiaddr + const addr = `/dns4/${host}/tcp/${port}` + const ipfs = ipfsClient({ url: addr }) + expectConfig(ipfs, { host, port, protocol }) + }) + + it('options.url as multiaddr string (explicit https)', () => { + const host = 'foo.com' + const port = '1001' + const protocol = 'https' + const addr = `/dns4/${host}/tcp/${port}/https` + const ipfs = ipfsClient({ url: addr }) + expectConfig(ipfs, { host, port, protocol }) + }) }) describe('integration', () => {