1
0
mirror of https://github.com/kremalicious/ipfs.git synced 2024-11-21 17:27:06 +01:00

migrate to new APIs, fix adding files

This commit is contained in:
Matthias Kretschmann 2021-09-12 16:41:30 +02:00
parent b03d3df2fc
commit e04041c46b
Signed by: m
GPG Key ID: 606EEEF3C479A91F
11 changed files with 21878 additions and 750 deletions

View File

@ -22,7 +22,8 @@
"prettier/@typescript-eslint",
"prettier/react",
"plugin:prettier/recommended",
"plugin:react/recommended"
"plugin:react/recommended",
"plugin:react-hooks/recommended"
],
"plugins": ["@typescript-eslint", "react"],
"rules": {

22418
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,7 @@
"license": "MIT",
"dependencies": {
"axios": "^0.21.1",
"ipfs-http-client": "^48.1.3",
"ipfs-http-client": "^52.0.3",
"next": "^10.0.7",
"next-seo": "^4.20.0",
"next-svgr": "^0.0.2",
@ -41,6 +41,7 @@
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-react": "^7.22.0",
"eslint-plugin-react-hooks": "^4.2.0",
"jest": "^26.6.3",
"postcss-preset-env": "^6.7.0",
"prettier": "^2.2.1",
@ -48,7 +49,7 @@
"typescript": "^4.2.2"
},
"engines": {
"node": "10.x"
"node": "14.x"
},
"browserslist": [
">0.2%",

View File

@ -11,5 +11,3 @@ declare module '*.svg' {
const src: string
export default src
}
declare module 'ipfs-http-client'

11
src/@types/ipfs.d.ts vendored
View File

@ -1,5 +1,8 @@
export interface IpfsConfig {
protocol: string
host: string
port: string
import { CID } from 'ipfs-http-client'
export interface FileIpfs {
path: string
cid: CID
size: number
mode?: number
}

View File

@ -4,40 +4,37 @@ 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 { addToIpfs, FileIpfs } from '../ipfs'
import { FileIpfs } from '../@types/ipfs'
const { hostname, port, protocol } = new URL(ipfsNodeUri)
const ipfsConfig: IpfsConfig = {
const ipfsConfig = {
protocol: protocol.replace(':', ''),
host: hostname,
port: port || '443'
port: Number(port) || 443
}
export default function Add(): ReactElement {
const { ipfs, isIpfsReady, ipfsError } = useIpfsApi(ipfsConfig)
const [files, setFiles] = useState([])
const { ipfs, isIpfsReady, ipfsError, addFiles } = useIpfsApi(ipfsConfig)
const [files, setFiles] = useState<FileIpfs[]>()
const [loading, setLoading] = useState(false)
const [message] = useState()
const [error, setError] = useState('')
const [error, setError] = useState<string>()
async function handleOnDrop(acceptedFiles: FileDropzone[]): Promise<any> {
if (!acceptedFiles) return
async function handleOnDrop(acceptedFiles: FileDropzone[]): Promise<void> {
if (!acceptedFiles || !ipfs || !isIpfsReady) return
setLoading(true)
setError('')
setError(undefined)
try {
const directoryCid = await addToIpfs(ipfs, acceptedFiles)
if (!directoryCid) return
const fileList = await ipfs.ls(directoryCid)
setFiles(fileList)
const addedFiles = await addFiles(acceptedFiles)
if (!addedFiles) return
setFiles(addedFiles)
setLoading(false)
} catch (error) {
setError(`Adding to IPFS failed: ${error.message}`)
return null
setError(`Adding to IPFS failed: ${(error as Error).message}`)
return
}
}
@ -45,14 +42,14 @@ export default function Add(): ReactElement {
<div className={styles.add}>
{loading ? (
<Loader message={message} />
) : files.length ? (
) : files?.length ? (
<ul style={{ textAlign: 'left' }}>
{files.map((file: FileIpfs) => (
{files?.map((file: FileIpfs) => (
<li key={file.path}>
<a
target="_blank"
rel="noopener noreferrer"
href={`${ipfsGateway}/ipfs/${file.path}`}
href={`${ipfsGateway}/ipfs/${file.cid.toString()}`}
>
ipfs://{file.path}
</a>
@ -61,6 +58,7 @@ export default function Add(): ReactElement {
</ul>
) : (
<Dropzone
multiple
handleOnDrop={handleOnDrop}
disabled={!isIpfsReady}
error={error || ipfsError}

View File

@ -43,7 +43,7 @@ export default function Dropzone({
`Drop it like it's hot!`
) : multiple === false ? (
`Drag 'n' drop a file here, or click to select a file.`
) : error !== '' ? (
) : error !== undefined ? (
<div className={styles.error}>{error}</div>
) : (
`Drag 'n' drop some files here, or click to select files.`

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, ReactElement } from 'react'
import React, { useState, useEffect, ReactElement, useCallback } from 'react'
import { pingUrl } from '../utils'
import { ipfsGateway, ipfsNodeUri } from '../../site.config'
import styles from './Status.module.css'
@ -7,7 +7,7 @@ export default function Status({ type }: { type: string }): ReactElement {
const [isOnline, setIsOnline] = useState(false)
const [isLoading, setIsLoading] = useState(true)
async function ping() {
const ping = useCallback(async () => {
const url =
type === 'gateway'
? `${ipfsGateway}/ipfs/QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG/readme`
@ -15,8 +15,8 @@ export default function Status({ type }: { type: string }): ReactElement {
const ping = await pingUrl(url)
setIsLoading(false)
isOnline !== ping && setIsOnline(ping)
}
setIsOnline(ping)
}, [type])
useEffect(() => {
ping()
@ -30,7 +30,7 @@ export default function Status({ type }: { type: string }): ReactElement {
setIsOnline(false)
setIsLoading(false)
}
}, [])
}, [ping])
const classes = isLoading
? styles.loading

View File

@ -1,53 +1,76 @@
import { useEffect, useState } from 'react'
import ipfsClient from 'ipfs-http-client'
import { parseHTML } from '../utils'
import { IpfsConfig } from '../@types/ipfs'
import { useCallback, useEffect, useState } from 'react'
import { create, IPFSHTTPClient, Options } from 'ipfs-http-client'
import { formatBytes } from '../utils'
import { FileDropzone } from '../components/Dropzone'
import { FileIpfs } from '../@types/ipfs'
let ipfs: any = null
let ipfsVersion = ''
export interface IpfsApiValue {
ipfs: IPFSHTTPClient | undefined
version: string | undefined
isIpfsReady: boolean
ipfsError: string | undefined
addFiles: (files: FileDropzone[]) => Promise<FileIpfs[] | undefined>
}
export default function useIpfsApi(config: IpfsConfig) {
export default function useIpfsApi(config: Options): IpfsApiValue {
const [ipfs, setIpfs] = useState<IPFSHTTPClient>()
const [version, setVersion] = useState<string>()
const [isIpfsReady, setIpfsReady] = useState(Boolean(ipfs))
const [ipfsError, setIpfsError] = useState('')
const [ipfsError, setIpfsError] = useState<string>()
const addFiles = useCallback(
async (files: FileDropzone[]): Promise<FileIpfs[] | undefined> => {
if (!ipfs || !files?.length) return
const ipfsFiles = [
...files.map((file: FileDropzone) => {
return { path: file.path, content: file }
})
]
const options = {
wrapWithDirectory: true,
progress: (length: number) => formatBytes(length, 0)
}
const results: FileIpfs[] = []
for await (const result of ipfs.addAll(ipfsFiles, options)) {
console.log(result)
results.push(result)
}
return results
},
[ipfs]
)
useEffect(() => {
if (ipfs) return
async function initIpfs() {
if (ipfs !== null) return
ipfs = await ipfsClient(config)
try {
const ipfs = create(config)
setIpfs(ipfs)
const version = await ipfs.version()
ipfsVersion = version.version
} catch (error) {
let { message } = error
if (!error.status) {
const htmlData = parseHTML(error)
message = htmlData.item(0)
message = message.textContent
}
setIpfsError(`IPFS connection error: ${message}`)
setVersion(version.version)
setIpfsReady(Boolean(ipfs && (await ipfs.id())))
} catch (e) {
setIpfsError(`IPFS connection error: ${(e as Error).message}`)
setIpfsReady(false)
return
}
setIpfsReady(Boolean(await ipfs.id()))
}
initIpfs()
}, [config])
useEffect(() => {
// just like componentWillUnmount()
return function cleanup() {
return () => {
if (ipfs) {
setIpfsReady(false)
ipfs = null
ipfsVersion = ''
setIpfsError('')
setIpfs(undefined)
setVersion(undefined)
setIpfsError(undefined)
}
}
}, [])
}, [config, ipfs])
return { ipfs, ipfsVersion, isIpfsReady, ipfsError }
return { ipfs, version, isIpfsReady, ipfsError, addFiles }
}

View File

@ -1,42 +0,0 @@
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))
})
console.log(stream)
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
}

View File

@ -1,6 +1,6 @@
import axios from 'axios'
export function formatBytes(a: number, b: number) {
export function formatBytes(a: number, b: number): string {
if (a === 0) return '0 Bytes'
const c = 1024
const d = b || 2
@ -9,18 +9,18 @@ export function formatBytes(a: number, b: number) {
return parseFloat((a / Math.pow(c, f)).toFixed(d)) + ' ' + e[f]
}
export async function pingUrl(url: string) {
export async function pingUrl(url: string): Promise<boolean> {
try {
const response = await axios(url, { timeout: 5000 })
if (!response || response.status !== 200) return false
return true
} catch (error) {
console.error(error.message)
console.error((error as Error).message)
return false
}
}
export function parseHTML(str: string) {
export function parseHTML(str: string): HTMLCollection {
const tmp = document.implementation.createHTMLDocument()
tmp.body.innerHTML = str
return tmp.body.children