diff --git a/nginx/ipfs.kretschmann.io.conf b/nginx/ipfs.kretschmann.io.conf index 7b29f7f..dd61027 100644 --- a/nginx/ipfs.kretschmann.io.conf +++ b/nginx/ipfs.kretschmann.io.conf @@ -13,7 +13,7 @@ server { proxy_cache_bypass $http_upgrade; } - location ~ "^/api/v0/(add|version|id)" { + location ~ "^/api/v0/(add|version|id|ls)" { proxy_pass http://localhost:5001; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; diff --git a/package.json b/package.json index 26c0239..157d8e7 100644 --- a/package.json +++ b/package.json @@ -14,23 +14,23 @@ "dependencies": { "@zeit/next-css": "^1.0.1", "axios": "^0.19.0", - "ipfs-http-client": "^39.0.0", + "ipfs-http-client": "^39.0.2", "next": "9.1.1", - "next-seo": "^2.1.2", + "next-seo": "^2.2.1", "next-svgr": "0.0.2", - "react": "^16.10.2", - "react-dom": "^16.10.2", + "react": "^16.11.0", + "react-dom": "^16.11.0", "react-dropzone": "^10.1.10", "use-dark-mode": "^2.3.1" }, "devDependencies": { "@types/next-seo": "^1.10.0", - "@types/node": "^12.11.1", - "@types/react": "^16.9.9", - "@typescript-eslint/eslint-plugin": "^2.4.0", - "@typescript-eslint/parser": "^2.4.0", + "@types/node": "^12.11.7", + "@types/react": "^16.9.11", + "@typescript-eslint/eslint-plugin": "^2.5.0", + "@typescript-eslint/parser": "^2.5.0", "cssnano": "^4.1.10", - "eslint": "^6.5.1", + "eslint": "^6.6.0", "eslint-config-prettier": "^6.4.0", "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-prettier": "^3.1.1", diff --git a/src/components/Add.tsx b/src/components/Add.tsx index 2a8ca54..cd03e8f 100644 --- a/src/components/Add.tsx +++ b/src/components/Add.tsx @@ -1,11 +1,11 @@ -import React, { useState, useEffect } from 'react' +import React, { useState } from 'react' import { ipfsNodeUri, ipfsGateway } from '../../site.config' -import Dropzone from './Dropzone' +import Dropzone, { FileDropzone } from './Dropzone' import styles from './Add.module.css' import Loader from './Loader' import useIpfsApi from '../hooks/use-ipfs-api' import { IpfsConfig } from '../@types/ipfs' -import { formatBytes, addToIpfs } from '../utils' +import { addToIpfs, FileIpfs } from '../ipfs' const { hostname, port, protocol } = new URL(ipfsNodeUri) @@ -17,33 +17,23 @@ const ipfsConfig: IpfsConfig = { export default function Add() { const { ipfs, isIpfsReady, ipfsError } = useIpfsApi(ipfsConfig) - const [fileHash, setFileHash] = useState() + const [files, setFiles] = useState() const [loading, setLoading] = useState(false) - const [message, setMessage] = useState() + const [message] = useState() const [error, setError] = useState() - const [fileSize, setFileSize] = useState() - const [fileSizeReceived, setFileSizeReceived] = useState('') - useEffect(() => { - setMessage( - `Adding to IPFS
- ${fileSizeReceived || 0}/${fileSize}
` - ) - }, [fileSize, fileSizeReceived]) - - async function handleOnDrop(acceptedFiles: File[]) { - if (!acceptedFiles[0]) return + async function handleOnDrop(acceptedFiles: FileDropzone[]) { + if (!acceptedFiles) return setLoading(true) setError(null) - const totalSize = formatBytes(acceptedFiles[0].size, 0) - setFileSize(totalSize) - try { - const cid = await addToIpfs(acceptedFiles, setFileSizeReceived, ipfs) - if (!cid) return - setFileHash(cid) + const directoryCid = await addToIpfs(ipfs, acceptedFiles) + if (!directoryCid) return + + const fileList = await ipfs.ls(directoryCid) + setFiles(fileList) setLoading(false) } catch (error) { setError(`Adding to IPFS failed: ${error.message}`) @@ -55,17 +45,22 @@ export default function Add() {
{loading ? ( - ) : fileHash ? ( - - ipfs://{fileHash} - + ) : files ? ( + ) : ( {isDragActive && !isDragReject ? ( `Drop it like it's hot!` - ) : multiple ? ( - `Drag 'n' drop some files here, or click to select files.` + ) : multiple === false ? ( + `Drag 'n' drop a file here, or click to select a file.` ) : error ? (
{error}
) : ( - `Drag 'n' drop a file here, or click to select a file.` + `Drag 'n' drop some files here, or click to select files.` )}
) diff --git a/src/ipfs.ts b/src/ipfs.ts new file mode 100644 index 0000000..6791c03 --- /dev/null +++ b/src/ipfs.ts @@ -0,0 +1,41 @@ +import { FileDropzone } from './components/Dropzone' + +export interface FileIpfsAdd { + path: string + content: File | ReadableStream | Buffer | string +} + +export interface FileIpfs { + path: string + hash: string + size: number +} + +export function streamFiles(ipfs: any, ipfsFiles: FileIpfsAdd[]) { + return new Promise((resolve, reject) => { + const stream = ipfs.addReadableStream({ + wrapWithDirectory: true + // progress: (length: number) => setFileSizeReceived(formatBytes(length, 0)) + }) + + stream.on('data', (data: FileIpfs) => { + console.log(`Added ${data.path} hash: ${data.hash}`) + // The last data event will contain the directory hash + if (data.path === '') resolve(data.hash) + }) + + stream.on('error', reject) + ipfsFiles.forEach((file: FileIpfsAdd) => stream.write(file)) + stream.end() + }) +} + +export async function addToIpfs(ipfs: any, files: FileDropzone[]) { + const ipfsFiles = [ + ...files.map((file: FileDropzone) => { + return { path: file.path, content: file } + }) + ] + const directoryCid = await streamFiles(ipfs, ipfsFiles) + return directoryCid +} diff --git a/src/utils.ts b/src/utils.ts index 3a83bf5..6148820 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -9,24 +9,6 @@ export function formatBytes(a: number, b: number) { return parseFloat((a / Math.pow(c, f)).toFixed(d)) + ' ' + e[f] } -export async function addToIpfs( - files: File[], - setFileSizeReceived: (size: string) => void, - ipfs: any -) { - const file = [...files][0] - const fileDetails = { path: file.name, content: file } - - const response = await ipfs.add(fileDetails, { - wrapWithDirectory: true, - progress: (length: number) => setFileSizeReceived(formatBytes(length, 0)) - }) - - // CID of wrapping directory is returned last - const cid = `${response[response.length - 1].hash}/${file.name}` - return cid -} - export async function pingUrl(url: string) { try { const response = await axios(url, { timeout: 5000 })