From 3284ae2050a196ac7b50420347af65ad11f5373e Mon Sep 17 00:00:00 2001 From: Pedro Santos Date: Thu, 3 Oct 2019 15:55:44 +0100 Subject: [PATCH 1/4] chore: convert getResponse to async/await syntax --- package.json | 2 +- src/index.js | 159 ++++++++++++++++++++++++--------------------------- 2 files changed, 76 insertions(+), 85 deletions(-) diff --git a/package.json b/package.json index bc3775d..b9c8f4e 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "mime-types": "^2.1.21", "multihashes": "~0.4.14", "promisify-es6": "^1.0.3", - "stream-to-blob": "^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 = { From 018d7ca13a8399e94c60dc7ff209db8940b1cfda Mon Sep 17 00:00:00 2001 From: Pedro Santos Date: Fri, 4 Oct 2019 11:59:41 +0100 Subject: [PATCH 2/4] chore: convert resolver to async/await syntax --- package.json | 2 - src/resolver.js | 98 +++++++++++++++++------------------------ src/utils/p-try-each.js | 25 +++++++++++ 3 files changed, 66 insertions(+), 59 deletions(-) create mode 100644 src/utils/p-try-each.js diff --git a/package.json b/package.json index b9c8f4e..cfe5a9b 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,7 +39,6 @@ "ipfs-unixfs": "~0.1.16", "mime-types": "^2.1.21", "multihashes": "~0.4.14", - "promisify-es6": "^1.0.3", "stream-to-blob": "^2.0.0" }, "devDependencies": { diff --git a/src/resolver.js b/src/resolver.js index 93e4f19..e2ca3c7 100644 --- a/src/resolver.js +++ b/src/resolver.js @@ -1,14 +1,12 @@ 'use strict' 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') +const pTryEeach = require('./utils/p-try-each') const INDEX_HTML_FILES = [ 'index.html', @@ -16,74 +14,60 @@ 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 pTryEeach(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) => { - // 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)) - }) - } +const directory = async (ipfs, path, cid) => { + 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, diff --git a/src/utils/p-try-each.js b/src/utils/p-try-each.js new file mode 100644 index 0000000..5c67f19 --- /dev/null +++ b/src/utils/p-try-each.js @@ -0,0 +1,25 @@ +'use strict' + +const pTryEeach = async (iterable) => { + let error + + for (const element of iterable) { + const type = typeof element + + if (type !== 'function') { + throw new TypeError(`Expected element to be a \`Function\`, received \`${type}\` instead`) + } + + try { + const res = await element() + + return res + } catch (err) { + error = err + } + } + + throw error +} + +module.exports = pTryEeach From e4e672c78ab80ec89024af40d891508a94545672 Mon Sep 17 00:00:00 2001 From: Pedro Santos Date: Mon, 7 Oct 2019 14:37:11 +0100 Subject: [PATCH 3/4] fix: code review changes --- src/resolver.js | 5 +++-- src/utils/p-try-each.js | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/resolver.js b/src/resolver.js index e2ca3c7..9444336 100644 --- a/src/resolver.js +++ b/src/resolver.js @@ -6,7 +6,7 @@ const debug = require('debug') const log = debug('jsipfs:http:response:resolver') log.error = debug('jsipfs:http:response:resolver:error') const dirView = require('./dir-view') -const pTryEeach = require('./utils/p-try-each') +const pTryEach = require('./utils/p-try-each') const INDEX_HTML_FILES = [ 'index.html', @@ -15,7 +15,7 @@ const INDEX_HTML_FILES = [ ] const findIndexFile = (ipfs, path) => { - return pTryEeach(INDEX_HTML_FILES.map(file => { + return pTryEach(INDEX_HTML_FILES.map(file => { return async () => { const stats = await ipfs.files.stat(`${path}/${file}`) @@ -28,6 +28,7 @@ const findIndexFile = (ipfs, path) => { } const directory = async (ipfs, path, cid) => { + // Test if it is a Website try { const res = await findIndexFile(ipfs, path) diff --git a/src/utils/p-try-each.js b/src/utils/p-try-each.js index 5c67f19..c2ffa4c 100644 --- a/src/utils/p-try-each.js +++ b/src/utils/p-try-each.js @@ -1,6 +1,6 @@ 'use strict' -const pTryEeach = async (iterable) => { +const pTryEach = async (iterable) => { let error for (const element of iterable) { @@ -22,4 +22,4 @@ const pTryEeach = async (iterable) => { throw error } -module.exports = pTryEeach +module.exports = pTryEach From c8b364ee79afcded0e3f03fd798fb052ee9d9185 Mon Sep 17 00:00:00 2001 From: Pedro Santos Date: Mon, 14 Oct 2019 10:36:55 +0100 Subject: [PATCH 4/4] chore: add p-try-each module --- package.json | 1 + src/resolver.js | 2 +- src/utils/p-try-each.js | 25 ------------------------- 3 files changed, 2 insertions(+), 26 deletions(-) delete mode 100644 src/utils/p-try-each.js diff --git a/package.json b/package.json index cfe5a9b..3ccef6d 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "ipfs-unixfs": "~0.1.16", "mime-types": "^2.1.21", "multihashes": "~0.4.14", + "p-try-each": "^1.0.1", "stream-to-blob": "^2.0.0" }, "devDependencies": { diff --git a/src/resolver.js b/src/resolver.js index 9444336..1b93b86 100644 --- a/src/resolver.js +++ b/src/resolver.js @@ -1,12 +1,12 @@ 'use strict' +const pTryEach = require('p-try-each') const mh = require('multihashes') const CID = require('cids') const debug = require('debug') const log = debug('jsipfs:http:response:resolver') log.error = debug('jsipfs:http:response:resolver:error') const dirView = require('./dir-view') -const pTryEach = require('./utils/p-try-each') const INDEX_HTML_FILES = [ 'index.html', diff --git a/src/utils/p-try-each.js b/src/utils/p-try-each.js deleted file mode 100644 index c2ffa4c..0000000 --- a/src/utils/p-try-each.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict' - -const pTryEach = async (iterable) => { - let error - - for (const element of iterable) { - const type = typeof element - - if (type !== 'function') { - throw new TypeError(`Expected element to be a \`Function\`, received \`${type}\` instead`) - } - - try { - const res = await element() - - return res - } catch (err) { - error = err - } - } - - throw error -} - -module.exports = pTryEach