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:
parent
b03d3df2fc
commit
e04041c46b
@ -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
22418
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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%",
|
||||
|
2
src/@types/global.d.ts
vendored
2
src/@types/global.d.ts
vendored
@ -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
11
src/@types/ipfs.d.ts
vendored
@ -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
|
||||
}
|
||||
|
@ -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}
|
||||
|
@ -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.`
|
||||
|
@ -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
|
||||
|
@ -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 }
|
||||
}
|
||||
|
42
src/ipfs.ts
42
src/ipfs.ts
@ -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
|
||||
}
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user