Skip to content
This repository was archived by the owner on Sep 28, 2021. It is now read-only.

feat: initial implementation #1

Merged
merged 1 commit into from
May 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
dist/

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Typescript v1 declaration files
typings/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env

# while testing npm5
package-lock.json
yarn.lock
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,22 @@
# ipfs-http-response
# js-ipfs-http-response

> Creates an HTTP response from an IPFS Hash

### Installation

> TODO

## Usage

This project consists on creating a HTTP response from an IPFS Hash. This response can be a file, a directory list view or the entry point of a web page.

```js
const ipfsHttpResponse = require('ipfs-http-response')

ipfsHttpResponse(ipfsNode, ipfsPath)
.then((response) => {
...
})
```

![ipfs-http-response usage](docs/ipfs-http-response.png "ipfs-http-response usage")
2 changes: 2 additions & 0 deletions ci/Jenkinsfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Warning: This file is automatically synced from https://github.com/ipfs/ci-sync so if you want to change it, please change it there and ask someone to sync all repositories.
javascript()
Binary file added docs/ipfs-http-response.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 50 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"name": "js-ipfs-http-response",
"version": "0.1.0",
"description": "Creates an HTTP response from an IPFS Hash",
"main": "src/index.js",
"scripts": {
"lint": "aegir lint",
"release": "aegir release",
"build": "aegir build",
"test": "aegir test -t node"
},
"pre-push": [
"lint",
"test"
],
"repository": {
"type": "git",
"url": "git+https://github.com/ipfs/js-ipfs-http-response.git"
},
"keywords": [
"ipfs",
"http",
"response"
],
"author": "Vasco Santos <[email protected]>",
"license": "MIT",
"bugs": {
"url": "https://github.com/ipfs/js-ipfs-http-response/issues"
},
"homepage": "https://github.com/ipfs/js-ipfs-http-response#readme",
"dependencies": {
"async": "^2.6.0",
"cids": "^0.5.3",
"debug": "^3.1.0",
"file-type": "^8.0.0",
"filesize": "^3.6.1",
"ipfs-unixfs": "^0.1.14",
"mime-types": "^2.1.18",
"multihashes": "^0.4.13",
"promisify-es6": "^1.0.3",
"readable-stream-node-to-web": "^1.0.1"
},
"devDependencies": {
"aegir": "^13.1.0",
"chai": "^4.1.2",
"dirty-chai": "^2.0.1",
"ipfs": "^0.28.2",
"ipfsd-ctl": "^0.36.0"
}
}
86 changes: 86 additions & 0 deletions src/dir-view/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
'use strict'

const filesize = require('filesize')

const mainStyle = require('./style')
const pathUtil = require('../utils/path')

function getParentDirectoryURL (originalParts) {
const parts = originalParts.slice()

if (parts.length > 1) {
parts.pop()
}

return [ '', 'ipfs' ].concat(parts).join('/')
}

function buildFilesList (path, links) {
const rows = links.map((link) => {
let row = [
`<div class="ipfs-icon ipfs-_blank">&nbsp;</div>`,
`<a href="${pathUtil.joinURLParts(path, link.name)}">${link.name}</a>`,
filesize(link.size)
]

row = row.map((cell) => `<td>${cell}</td>`).join('')

return `<tr>${row}</tr>`
})

return rows.join('')
}

function buildTable (path, links) {
const parts = pathUtil.splitPath(path)
const parentDirectoryURL = getParentDirectoryURL(parts)

return `
<table class="table table-striped">
<tbody>
<tr>
<td class="narrow">
<div class="ipfs-icon ipfs-_blank">&nbsp;</div>
</td>
<td class="padding">
<a href="${parentDirectoryURL}">..</a>
</td>
<td></td>
</tr>
${buildFilesList(path, links)}
</tbody>
</table>
`
}

function render (path, links) {
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>${path}</title>
<style>${mainStyle}</style>
</head>
<body>
<div id="header" class="row">
<div class="col-xs-2">
<div id="logo" class="ipfs-logo"></div>
</div>
</div>
<br>
<div class="col-xs-12">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Index of ${path}</strong>
</div>
${buildTable(path, links)}
</div>
</div>
</body>
</html>
`
}

exports = module.exports
exports.render = render
16 changes: 16 additions & 0 deletions src/dir-view/style.js

Large diffs are not rendered by default.

102 changes: 102 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/* global Response */

'use strict'

const fileType = require('file-type')
const mimeTypes = require('mime-types')
const stream = require('stream')
const nodeToWebStream = require('readable-stream-node-to-web')

const resolver = require('./resolver')
const pathUtils = require('./utils/path')

const header = (status = 200, statusText = 'OK', headers = {}) => ({
status,
statusText,
headers
})

module.exports = (ipfsNode, ipfsPath) => {
// handle hash resolve error (simple hash, test for directory now)
const handleResolveError = (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 === 'Error: This dag node is a directory'):
resolver.directory(node, path, error.fileName)
.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) => {
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:
resolve(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)))
}

resolver.multihash(ipfsNode, ipfsPath)
.then((resolvedData) => {
const readableStream = ipfsNode.files.catReadableStream(resolvedData.multihash)
const responseStream = new stream.PassThrough({ highWaterMark: 1 })
readableStream.pipe(responseStream)

readableStream.once('error', (error) => {
if (error) {
resolve(new Response(error.toString(), header(500, 'Service Worker Error')))
}
})

// return only after first chunk being checked
let filetypeChecked = false
readableStream.on('data', (chunk) => {
// check mime on first chunk
if (filetypeChecked) {
return
}
filetypeChecked = true
// return Response with mime type
const fileSignature = fileType(chunk)
const mimeType = mimeTypes.lookup(fileSignature ? fileSignature.ext : null)

if (mimeType) {
resolve(
new Response(typeof ReadableStream === 'function' ? nodeToWebStream(responseStream) : responseStream,
header(200, 'OK', { 'Content-Type': mimeTypes.contentType(mimeType) }))
)
} else {
resolve(new Response(typeof ReadableStream === 'function' ? nodeToWebStream(responseStream) : responseStream,
header()))
}
})
})
.catch((error) => {
resolve(handleResolveError(ipfsNode, ipfsPath, error))
})
})
}
Loading