diff --git a/package.json b/package.json index bc3775d..3ccef6d 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,6 @@ }, "homepage": "https://github.com/ipfs/js-ipfs-http-response#readme", "dependencies": { - "async": "^2.6.1", "cids": "~0.7.1", "debug": "^4.1.1", "file-type": "^8.0.0", @@ -40,8 +39,8 @@ "ipfs-unixfs": "~0.1.16", "mime-types": "^2.1.21", "multihashes": "~0.4.14", - "promisify-es6": "^1.0.3", - "stream-to-blob": "^1.0.1" + "p-try-each": "^1.0.1", + "stream-to-blob": "^2.0.0" }, "devDependencies": { "aegir": "^18.0.3", diff --git a/src/index.js b/src/index.js index 64377b2..f0625ef 100644 --- a/src/index.js +++ b/src/index.js @@ -19,103 +19,94 @@ const header = (status = 200, statusText = 'OK', headers = {}) => ({ headers }) -const response = (ipfsNode, ipfsPath) => { +const response = async (ipfsNode, ipfsPath) => { // handle hash resolve error (simple hash, test for directory now) - const handleResolveError = (node, path, error) => { + const handleResolveError = async (node, path, error) => { if (error) { const errorString = error.toString() - return new Promise((resolve, reject) => { - // switch case with true feels so wrong. - switch (true) { - case (errorString.includes('dag node is a directory')): - resolver.directory(node, path, error.cid) - .then((content) => { - // dir render - if (typeof content === 'string') { - resolve(new Response(content, header(200, 'OK', { 'Content-Type': 'text/html' }))) - } - - // redirect to dir entry point (index) - resolve(Response.redirect(pathUtils.joinURLParts(path, content[0].Name))) - }) - .catch((error) => { - log(error) - resolve(new Response(errorString, header(500, error.toString()))) - }) - break - case errorString.startsWith('Error: no link named'): - resolve(new Response(errorString, header(404, errorString))) - break - case errorString.startsWith('Error: multihash length inconsistent'): - case errorString.startsWith('Error: Non-base58 character'): - resolve(new Response(errorString, header(400, errorString))) - break - default: - log(error) - resolve(new Response(errorString, header(500, errorString))) + if (errorString.includes('dag node is a directory')) { + try { + const content = await resolver.directory(node, path, error.cid) + // dir render + if (typeof content === 'string') { + return new Response(content, header(200, 'OK', { 'Content-Type': 'text/html' })) + } + + // redirect to dir entry point (index) + return Response.redirect(pathUtils.joinURLParts(path, content[0].Name)) + } catch (error) { + log(error) + return new Response(errorString, header(500, error.toString())) } - }) + } + + if (errorString.startsWith('Error: no link named')) { + return new Response(errorString, header(404, errorString)) + } + + if (errorString.startsWith('Error: multihash length inconsistent') || errorString.startsWith('Error: Non-base58 character')) { + return new Response(errorString, header(400, errorString)) + } + + log(error) + return new Response(errorString, header(500, errorString)) } } - return new Promise((resolve, reject) => { - // remove trailing slash for files if needed - if (ipfsPath.endsWith('/')) { - resolve(Response.redirect(pathUtils.removeTrailingSlash(ipfsPath))) - } + // remove trailing slash for files if needed + if (ipfsPath.endsWith('/')) { + return Response.redirect(pathUtils.removeTrailingSlash(ipfsPath)) + } - resolver.cid(ipfsNode, ipfsPath) - .then((resolvedData) => { - const readableStream = ipfsNode.catReadableStream(resolvedData.cid) - const responseStream = new stream.PassThrough({ highWaterMark: 1 }) - readableStream.pipe(responseStream) + try { + const resolvedData = await resolver.cid(ipfsNode, ipfsPath) - readableStream.once('error', (error) => { - if (error) { - log(error) - resolve(new Response(error.toString(), header(500, 'Error fetching the file'))) - } - }) - - // return only after first chunk being checked - let contentTypeDetected = false - readableStream.on('data', (chunk) => { - // check mime on first chunk - if (contentTypeDetected) { - return - } + const readableStream = ipfsNode.catReadableStream(resolvedData.cid) + const responseStream = new stream.PassThrough({ highWaterMark: 1 }) + readableStream.pipe(responseStream) - contentTypeDetected = true - // return Response with mime type - const contentType = detectContentType(ipfsPath, chunk) - - if (typeof Blob === 'undefined') { - if (contentType) { - resolve(new Response(responseStream, header(200, 'OK', { 'Content-Type': contentType }))) - } else { - resolve(new Response(responseStream, header())) - } - } else { - toBlob(responseStream, (err, blob) => { - if (err) { - resolve(new Response(err.toString(), header(500, 'Error fetching the file'))) - } - - if (contentType) { - resolve(new Response(blob, header(200, 'OK', { 'Content-Type': contentType }))) - } else { - resolve(new Response(blob, header())) - } - }) - } - }) + return new Promise((resolve, reject) => { + readableStream.once('error', (error) => { + if (error) { + log(error) + return resolve(new Response(error.toString(), header(500, 'Error fetching the file'))) + } }) - .catch((error) => { - log(error) - resolve(handleResolveError(ipfsNode, ipfsPath, error)) + + // return only after first chunk being checked + let contentTypeDetected = false + readableStream.on('data', async (chunk) => { + // check mime on first chunk + if (contentTypeDetected) { + return + } + + contentTypeDetected = true + // return Response with mime type + const contentType = detectContentType(ipfsPath, chunk) + + if (typeof Blob === 'undefined') { + return contentType + ? resolve(new Response(responseStream, header(200, 'OK', { 'Content-Type': contentType }))) + : resolve(new Response(responseStream, header())) + } + + try { + const blob = await toBlob(responseStream) + + return contentType + ? resolve(new Response(blob, header(200, 'OK', { 'Content-Type': contentType }))) + : resolve(new Response(blob, header())) + } catch (err) { + return resolve(new Response(err.toString(), header(500, 'Error fetching the file'))) + } }) - }) + }) + } catch (error) { + log(error) + return handleResolveError(ipfsNode, ipfsPath, error) + } } module.exports = { diff --git a/src/resolver.js b/src/resolver.js index 93e4f19..1b93b86 100644 --- a/src/resolver.js +++ b/src/resolver.js @@ -1,11 +1,9 @@ 'use strict' +const pTryEach = require('p-try-each') const mh = require('multihashes') -const promisify = require('promisify-es6') const CID = require('cids') const debug = require('debug') -const tryEach = require('async/tryEach') -const waterfall = require('async/waterfall') const log = debug('jsipfs:http:response:resolver') log.error = debug('jsipfs:http:response:resolver:error') const dirView = require('./dir-view') @@ -16,74 +14,61 @@ const INDEX_HTML_FILES = [ 'index.shtml' ] -const findIndexFile = (ipfs, path, callback) => { - return tryEach(INDEX_HTML_FILES.map(file => { - return (cb) => { - waterfall([ - (cb) => ipfs.files.stat(`${path}/${file}`, cb), - (stats, cb) => cb(null, { - name: file, - cid: new CID(stats.hash) - }) - ], cb) +const findIndexFile = (ipfs, path) => { + return pTryEach(INDEX_HTML_FILES.map(file => { + return async () => { + const stats = await ipfs.files.stat(`${path}/${file}`) + + return { + name: file, + cid: new CID(stats.hash) + } } - }), callback) + })) } -const directory = promisify((ipfs, path, cid, callback) => { +const directory = async (ipfs, path, cid) => { // Test if it is a Website - findIndexFile(ipfs, path, (err, res) => { - if (err) { - if (err.message.includes('does not exist')) { - // not a website, just show a directory listing - return ipfs.dag.get(cid, (err, result) => { - if (err) { - return callback(err) - } - - return callback(null, dirView.render(path, result.value.Links)) - }) - } + try { + const res = await findIndexFile(ipfs, path) - return callback(err) + return [{ Name: res.name }] + } catch (err) { + if (err.message.includes('does not exist')) { + // not a website, just show a directory listing + const result = await ipfs.dag.get(cid) + + return dirView.render(path, result.value.Links) } - callback(err, [{ - Name: res.name - }]) - }) -}) + throw err + } +} -const cid = promisify((ipfs, path, callback) => { - ipfs.files.stat(path, (err, stats) => { - if (err) { - return callback(err) - } +const cid = async (ipfs, path) => { + const stats = await ipfs.files.stat(path) - const cid = new CID(stats.hash) + const cid = new CID(stats.hash) - if (stats.type.includes('directory')) { - const err = new Error('This dag node is a directory') - err.cid = cid - err.fileName = stats.name - err.dagDirType = stats.type + if (stats.type.includes('directory')) { + const err = new Error('This dag node is a directory') + err.cid = cid + err.fileName = stats.name + err.dagDirType = stats.type - return callback(err) - } + throw err + } - callback(err, { - cid - }) - }) -}) + return { cid } +} -const multihash = promisify((ipfs, path, callback) => { +const multihash = async (ipfs, path) => { // deprecated, use 'cid' instead // (left for backward-compatibility) - cid(ipfs, path) - .then((result) => { callback(null, { multihash: mh.toB58String(result.cid.multihash) }) }) - .catch((err) => { callback(err) }) -}) + const result = await cid(ipfs, path) + + return { multihash: mh.toB58String(result.cid.multihash) } +} module.exports = { directory: directory,