diff --git a/package.json b/package.json index 1908c7de1..acd90bb63 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "stream": "readable-stream", "ky-universal": "ky/umd", "./src/add/form-data.js": "./src/add/form-data.browser.js", - "./src/add-from-fs/index.js": "./src/add-from-fs/index.browser.js" + "./src/add-from-fs/index.js": "./src/add-from-fs/index.browser.js", + "./src/lib/buffer-to-form-data.js": "./src/lib/buffer-to-form-data.browser.js" }, "repository": "github:ipfs/js-ipfs-http-client", "scripts": { diff --git a/src/block/get.js b/src/block/get.js index 327b9e7ff..6e28643f3 100644 --- a/src/block/get.js +++ b/src/block/get.js @@ -1,62 +1,25 @@ 'use strict' -const promisify = require('promisify-es6') const Block = require('ipfs-block') const CID = require('cids') -const streamToValue = require('../utils/stream-to-value') +const { Buffer } = require('buffer') +const configure = require('../lib/configure') -module.exports = (send) => { - return promisify((args, opts, callback) => { - if (typeof opts === 'function') { - callback = opts - opts = {} - } +module.exports = configure(({ ky }) => { + return async (cid, options) => { + cid = new CID(cid) + options = options || {} - // TODO this needs to be adjusted with the new go-ipfs http-api - let cid - try { - if (CID.isCID(args)) { - cid = args - args = cid.toBaseEncodedString() - } else if (Buffer.isBuffer(args)) { - cid = new CID(args) - args = cid.toBaseEncodedString() - } else if (typeof args === 'string') { - cid = new CID(args) - } else { - return callback(new Error('invalid argument')) - } - } catch (err) { - return callback(err) - } + const searchParams = new URLSearchParams(options.searchParams) + searchParams.set('arg', `${cid}`) - // Transform the response from Buffer or a Stream to a Block - const transform = (res, callback) => { - if (Buffer.isBuffer(res)) { - callback(null, new Block(res, cid)) - // For empty blocks, concat-stream can't infer the encoding so we are - // passed back an empty array - } else if (Array.isArray(res) && res.length === 0) { - callback(null, new Block(Buffer.alloc(0), cid)) - } else { - streamToValue(res, (err, data) => { - if (err) { - return callback(err) - } - // For empty blocks, concat-stream can't infer the encoding so we are - // passed back an empty array - if (!data.length) data = Buffer.alloc(0) - callback(null, new Block(data, cid)) - }) - } - } + const data = await ky.get('block/get', { + timeout: options.timeout, + signal: options.signal, + headers: options.headers, + searchParams + }).arrayBuffer() - const request = { - path: 'block/get', - args: args, - qs: opts - } - - send.andTransform(request, transform, callback) - }) -} + return new Block(Buffer.from(data), cid) + } +}) diff --git a/src/block/index.js b/src/block/index.js index 645f47df6..8c683dd83 100644 --- a/src/block/index.js +++ b/src/block/index.js @@ -1,17 +1,16 @@ 'use strict' const nodeify = require('promise-nodeify') -const moduleConfig = require('../utils/module-config') +const callbackify = require('callbackify') const { collectify } = require('../lib/converters') -module.exports = (arg, config) => { - const send = moduleConfig(arg) +module.exports = config => { const rm = require('./rm-async-iterator')(config) return { - get: require('./get')(send), - stat: require('./stat')(send), - put: require('./put')(send), + get: callbackify.variadic(require('./get')(config)), + stat: callbackify.variadic(require('./stat')(config)), + put: callbackify.variadic(require('./put')(config)), rm: (input, options, callback) => { if (typeof options === 'function') { callback = options diff --git a/src/block/put.js b/src/block/put.js index 03f6824db..39e3b6f1a 100644 --- a/src/block/put.js +++ b/src/block/put.js @@ -1,74 +1,68 @@ 'use strict' -const promisify = require('promisify-es6') const Block = require('ipfs-block') const CID = require('cids') const multihash = require('multihashes') -const SendOneFile = require('../utils/send-one-file') - -module.exports = (send) => { - const sendOneFile = SendOneFile(send, 'block/put') - - return promisify((block, options, callback) => { - if (typeof options === 'function') { - callback = options - options = {} - } +const configure = require('../lib/configure') +const toFormData = require('../lib/buffer-to-form-data') +module.exports = configure(({ ky }) => { + async function put (data, options) { options = options || {} - if (Array.isArray(block)) { - return callback(new Error('block.put accepts only one block')) - } - - if (Buffer.isBuffer(block)) { - block = { data: block } - } - - if (!block || !block.data) { - return callback(new Error('invalid block arg')) + if (Block.isBlock(data)) { + const { name, length } = multihash.decode(data.cid.multihash) + options = { + ...options, + format: data.cid.codec, + mhtype: name, + mhlen: length, + version: data.cid.version + } + data = data.data + } else if (options.cid) { + const cid = new CID(options.cid) + const { name, length } = multihash.decode(cid.multihash) + options = { + ...options, + format: cid.codec, + mhtype: name, + mhlen: length, + version: cid.version + } + delete options.cid } - const qs = {} + const searchParams = new URLSearchParams(options.searchParams) + if (options.format) searchParams.set('format', options.format) + if (options.mhtype) searchParams.set('mhtype', options.mhtype) + if (options.mhlen) searchParams.set('mhlen', options.mhlen) + if (options.pin != null) searchParams.set('pin', options.pin) + if (options.version != null) searchParams.set('version', options.version) - if (block.cid || options.cid) { - let cid - - try { - cid = new CID(block.cid || options.cid) - } catch (err) { - return callback(err) + let res + try { + res = await ky.post('block/put', { + timeout: options.timeout, + signal: options.signal, + headers: options.headers, + searchParams, + body: toFormData(data) + }).json() + } catch (err) { + // Retry with "protobuf"/"cbor" format for go-ipfs + // TODO: remove when https://github.com/ipfs/go-cid/issues/75 resolved + if (options.format === 'dag-pb') { + return put(data, { ...options, format: 'protobuf' }) + } else if (options.format === 'dag-cbor') { + return put(data, { ...options, format: 'cbor' }) } - const { name, length } = multihash.decode(cid.multihash) - - qs.format = cid.codec - qs.mhtype = name - qs.mhlen = length - qs.version = cid.version - } else { - if (options.format) qs.format = options.format - if (options.mhtype) qs.mhtype = options.mhtype - if (options.mhlen) qs.mhlen = options.mhlen - if (options.version != null) qs.version = options.version + throw err } - sendOneFile(block.data, { qs }, (err, result) => { - if (err) { - // Retry with "protobuf"/"cbor" format for go-ipfs - // TODO: remove when https://github.com/ipfs/go-cid/issues/75 resolved - if (qs.format === 'dag-pb' || qs.format === 'dag-cbor') { - qs.format = qs.format === 'dag-pb' ? 'protobuf' : 'cbor' - return sendOneFile(block.data, { qs }, (err, result) => { - if (err) return callback(err) - callback(null, new Block(block.data, new CID(result.Key))) - }) - } - - return callback(err) - } + return new Block(data, new CID(res.Key)) + } - callback(null, new Block(block.data, new CID(result.Key))) - }) - }) -} + return put +}) diff --git a/src/block/stat.js b/src/block/stat.js index 9d44ac9f8..4d0b82787 100644 --- a/src/block/stat.js +++ b/src/block/stat.js @@ -1,35 +1,28 @@ 'use strict' -const promisify = require('promisify-es6') const CID = require('cids') -const multihash = require('multihashes') +const { Buffer } = require('buffer') +const configure = require('../lib/configure') +const toCamel = require('../lib/object-to-camel') -module.exports = (send) => { - return promisify((args, opts, callback) => { - // TODO this needs to be adjusted with the new go-ipfs http-api - if (args && CID.isCID(args)) { - args = multihash.toB58String(args.multihash) - } +module.exports = configure(({ ky }) => { + return async (cid, options) => { + options = options || {} - if (typeof (opts) === 'function') { - callback = opts - opts = {} + if (Buffer.isBuffer(cid)) { + cid = new CID(cid) } - const request = { - path: 'block/stat', - args: args, - qs: opts - } + const searchParams = new URLSearchParams(options.searchParams) + searchParams.set('arg', `${cid}`) - // Transform the response from { Key, Size } objects to { key, size } objects - const transform = (stats, callback) => { - callback(null, { - key: stats.Key, - size: stats.Size - }) - } + const res = await ky.get('block/stat', { + timeout: options.timeout, + signal: options.signal, + headers: options.headers, + searchParams + }).json() - send.andTransform(request, transform, callback) - }) -} + return toCamel(res) + } +}) diff --git a/src/dag/get.js b/src/dag/get.js index 771c38bf4..93847b2e6 100644 --- a/src/dag/get.js +++ b/src/dag/get.js @@ -3,10 +3,7 @@ const dagPB = require('ipld-dag-pb') const dagCBOR = require('ipld-dag-cbor') const raw = require('ipld-raw') -const promisify = require('promisify-es6') -const CID = require('cids') -const waterfall = require('async/waterfall') -const block = require('../block') +const configure = require('../lib/configure') const resolvers = { 'dag-cbor': dagCBOR.resolver, @@ -14,56 +11,31 @@ const resolvers = { raw: raw.resolver } -module.exports = (send) => { - return promisify((cid, path, options, callback) => { - if (typeof path === 'function') { - callback = path - path = undefined - } - - if (typeof options === 'function') { - callback = options - options = {} - } +module.exports = config => { + const getBlock = require('../block/get')(config) + const dagResolve = require('./resolve')(config) - options = options || {} - path = path || '' - - if (CID.isCID(cid)) { - cid = cid.toBaseEncodedString() - } - - waterfall([ - cb => { - send({ - path: 'dag/resolve', - args: cid + '/' + path, - qs: options - }, cb) - }, - (resolved, cb) => { - block(send).get(new CID(resolved.Cid['/']), (err, ipfsBlock) => { - cb(err, ipfsBlock, resolved.RemPath) - }) - }, - (ipfsBlock, path, cb) => { - const dagResolver = resolvers[ipfsBlock.cid.codec] + return configure(({ ky }) => { + return async (cid, path, options) => { + if (typeof path === 'object') { + options = path + path = null + } - if (!dagResolver) { - const error = new Error(`Missing IPLD format "${ipfsBlock.cid.codec}"`) - error.missingMulticodec = ipfsBlock.cid.codec - return cb(error) - } + options = options || {} - let res - try { - res = dagResolver.resolve(ipfsBlock.data, path) - } catch (err) { - return cb(err) - } + const resolved = await dagResolve(cid, path, options) + const block = await getBlock(resolved.cid, options) + const dagResolver = resolvers[block.cid.codec] - cb(null, res) + if (!dagResolver) { + throw Object.assign( + new Error(`Missing IPLD format "${block.cid.codec}"`), + { missingMulticodec: cid.codec } + ) } - ], callback) - }) + + return dagResolver.resolve(block.data, resolved.remPath) + } + })(config) } diff --git a/src/dag/index.js b/src/dag/index.js index bb6b1333c..06194e4a8 100644 --- a/src/dag/index.js +++ b/src/dag/index.js @@ -1,12 +1,9 @@ 'use strict' -const moduleConfig = require('../utils/module-config') +const callbackify = require('callbackify') -module.exports = (arg) => { - const send = moduleConfig(arg) - - return { - get: require('./get')(send), - put: require('./put')(send) - } -} +module.exports = config => ({ + get: callbackify.variadic(require('./get')(config)), + put: callbackify.variadic(require('./put')(config)), + resolve: callbackify.variadic(require('./resolve')(config)) +}) diff --git a/src/dag/put.js b/src/dag/put.js index 36da9376a..d26f8ba9e 100644 --- a/src/dag/put.js +++ b/src/dag/put.js @@ -1,19 +1,13 @@ 'use strict' const dagCBOR = require('ipld-dag-cbor') -const promisify = require('promisify-es6') const CID = require('cids') const multihash = require('multihashes') -const SendOneFile = require('../utils/send-one-file') - -module.exports = (send) => { - const sendOneFile = SendOneFile(send, 'dag/put') - - return promisify((dagNode, options, callback) => { - if (typeof options === 'function') { - callback = options - } +const configure = require('../lib/configure') +const toFormData = require('../lib/buffer-to-form-data') +module.exports = configure(({ ky }) => { + return async (dagNode, options) => { options = options || {} if (options.hash) { @@ -22,65 +16,53 @@ module.exports = (send) => { } if (options.cid && (options.format || options.hashAlg)) { - return callback(new Error('Can\'t put dag node. Please provide either `cid` OR `format` and `hash` options.')) + throw new Error('Failed to put DAG node. Provide either `cid` OR `format` and `hashAlg` options') } else if ((options.format && !options.hashAlg) || (!options.format && options.hashAlg)) { - return callback(new Error('Can\'t put dag node. Please provide `format` AND `hash` options.')) + throw new Error('Failed to put DAG node. Provide `format` AND `hashAlg` options') } if (options.cid) { - let cid - - try { - cid = new CID(options.cid) - } catch (err) { - return callback(err) + const cid = new CID(options.cid) + options = { + ...options, + format: cid.codec, + hashAlg: multihash.decode(cid.multihash).name } - - options.format = cid.codec - options.hashAlg = multihash.decode(cid.multihash).name delete options.cid } - const optionDefaults = { + options = { format: 'dag-cbor', hashAlg: 'sha2-256', - inputEnc: 'raw' + inputEnc: 'raw', + ...options } - options = Object.assign(optionDefaults, options) - let serialized - try { - if (options.format === 'dag-cbor') { - serialized = dagCBOR.util.serialize(dagNode) - } else if (options.format === 'dag-pb') { - serialized = dagNode.serialize() - } else { - // FIXME Hopefully already serialized...can we use IPLD to serialise instead? - serialized = dagNode - } - } catch (err) { - return callback(err) + if (options.format === 'dag-cbor') { + serialized = dagCBOR.util.serialize(dagNode) + } else if (options.format === 'dag-pb') { + serialized = dagNode.serialize() + } else { + // FIXME Hopefully already serialized...can we use IPLD to serialise instead? + serialized = dagNode } - const sendOptions = { - qs: { - hash: options.hashAlg, - format: options.format, - 'input-enc': options.inputEnc - } - } + const searchParams = new URLSearchParams(options.searchParams) + searchParams.set('format', options.format) + searchParams.set('hash', options.hashAlg) + searchParams.set('input-enc', options.inputEnc) + if (options.pin != null) searchParams.set('pin', options.pin) - sendOneFile(serialized, sendOptions, (err, result) => { - if (err) { - return callback(err) - } - if (result.Cid) { - return callback(null, new CID(result.Cid['/'])) - } else { - return callback(result) - } - }) - }) -} + const res = await ky.post('dag/put', { + timeout: options.timeout, + signal: options.signal, + headers: options.headers, + searchParams, + body: toFormData(serialized) + }).json() + + return new CID(res.Cid['/']) + } +}) diff --git a/src/dag/resolve.js b/src/dag/resolve.js new file mode 100644 index 000000000..f0a83d69d --- /dev/null +++ b/src/dag/resolve.js @@ -0,0 +1,31 @@ +'use strict' + +const CID = require('cids') +const configure = require('../lib/configure') + +module.exports = configure(({ ky }) => { + return async (cid, path, options) => { + if (typeof path === 'object') { + options = path + path = null + } + + options = options || {} + + const cidPath = path + ? [cid, path].join(path.startsWith('/') ? '' : '/') + : `${cid}` + + const searchParams = new URLSearchParams(options.searchParams) + searchParams.set('arg', cidPath) + + const res = await ky.get('dag/resolve', { + timeout: options.timeout, + signal: options.signal, + headers: options.headers, + searchParams + }).json() + + return { cid: new CID(res.Cid['/']), remPath: res.RemPath } + } +}) diff --git a/src/lib/buffer-to-form-data.browser.js b/src/lib/buffer-to-form-data.browser.js new file mode 100644 index 000000000..dab8da9ce --- /dev/null +++ b/src/lib/buffer-to-form-data.browser.js @@ -0,0 +1,8 @@ +'use strict' +/* eslint-env browser */ + +module.exports = buf => { + const formData = new FormData() + formData.append('file', new Blob([buf], { type: 'application/octet-stream' })) + return formData +} diff --git a/src/lib/buffer-to-form-data.js b/src/lib/buffer-to-form-data.js new file mode 100644 index 000000000..41f03383e --- /dev/null +++ b/src/lib/buffer-to-form-data.js @@ -0,0 +1,17 @@ +'use strict' + +const FormData = require('form-data') +const { isElectronRenderer } = require('ipfs-utils/src/env') + +module.exports = buf => { + const formData = new FormData() + formData.append('file', buf) + return formData +} + +// TODO remove this when upstream fix for ky-universal is merged +// https://github.com/sindresorhus/ky-universal/issues/9 +// also this should only be necessary when nodeIntegration is false in electron renderer +if (isElectronRenderer) { + module.exports = require('./buffer-to-form-data.browser') +} diff --git a/src/utils/load-commands.js b/src/utils/load-commands.js index d4295205c..242b52463 100644 --- a/src/utils/load-commands.js +++ b/src/utils/load-commands.js @@ -89,7 +89,9 @@ function requireCommands (send, config) { refsPullStream: pullify.source(refs), _refsAsyncIterator: refs, getEndpointConfig: require('../get-endpoint-config')(config), - bitswap: require('../bitswap')(config) + bitswap: require('../bitswap')(config), + block: require('../block')(config), + dag: require('../dag')(config) } Object.assign(cmds.refs, { @@ -103,11 +105,7 @@ function requireCommands (send, config) { // Files MFS (Mutable Filesystem) files: require('../files'), - // Block - block: require('../block'), - // Graph - dag: require('../dag'), object: require('../object'), pin: require('../pin'),