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/@typescript-eslint",
|
||||||
"prettier/react",
|
"prettier/react",
|
||||||
"plugin:prettier/recommended",
|
"plugin:prettier/recommended",
|
||||||
"plugin:react/recommended"
|
"plugin:react/recommended",
|
||||||
|
"plugin:react-hooks/recommended"
|
||||||
],
|
],
|
||||||
"plugins": ["@typescript-eslint", "react"],
|
"plugins": ["@typescript-eslint", "react"],
|
||||||
"rules": {
|
"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",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"ipfs-http-client": "^48.1.3",
|
"ipfs-http-client": "^52.0.3",
|
||||||
"next": "^10.0.7",
|
"next": "^10.0.7",
|
||||||
"next-seo": "^4.20.0",
|
"next-seo": "^4.20.0",
|
||||||
"next-svgr": "^0.0.2",
|
"next-svgr": "^0.0.2",
|
||||||
@ -41,6 +41,7 @@
|
|||||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||||
"eslint-plugin-prettier": "^3.3.1",
|
"eslint-plugin-prettier": "^3.3.1",
|
||||||
"eslint-plugin-react": "^7.22.0",
|
"eslint-plugin-react": "^7.22.0",
|
||||||
|
"eslint-plugin-react-hooks": "^4.2.0",
|
||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
"postcss-preset-env": "^6.7.0",
|
"postcss-preset-env": "^6.7.0",
|
||||||
"prettier": "^2.2.1",
|
"prettier": "^2.2.1",
|
||||||
@ -48,7 +49,7 @@
|
|||||||
"typescript": "^4.2.2"
|
"typescript": "^4.2.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "10.x"
|
"node": "14.x"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
">0.2%",
|
">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
|
const src: string
|
||||||
export default src
|
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 {
|
import { CID } from 'ipfs-http-client'
|
||||||
protocol: string
|
|
||||||
host: string
|
export interface FileIpfs {
|
||||||
port: string
|
path: string
|
||||||
|
cid: CID
|
||||||
|
size: number
|
||||||
|
mode?: number
|
||||||
}
|
}
|
||||||
|
@ -4,40 +4,37 @@ import Dropzone, { FileDropzone } from './Dropzone'
|
|||||||
import styles from './Add.module.css'
|
import styles from './Add.module.css'
|
||||||
import Loader from './Loader'
|
import Loader from './Loader'
|
||||||
import useIpfsApi from '../hooks/use-ipfs-api'
|
import useIpfsApi from '../hooks/use-ipfs-api'
|
||||||
import { IpfsConfig } from '../@types/ipfs'
|
import { FileIpfs } from '../@types/ipfs'
|
||||||
import { addToIpfs, FileIpfs } from '../ipfs'
|
|
||||||
|
|
||||||
const { hostname, port, protocol } = new URL(ipfsNodeUri)
|
const { hostname, port, protocol } = new URL(ipfsNodeUri)
|
||||||
|
|
||||||
const ipfsConfig: IpfsConfig = {
|
const ipfsConfig = {
|
||||||
protocol: protocol.replace(':', ''),
|
protocol: protocol.replace(':', ''),
|
||||||
host: hostname,
|
host: hostname,
|
||||||
port: port || '443'
|
port: Number(port) || 443
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Add(): ReactElement {
|
export default function Add(): ReactElement {
|
||||||
const { ipfs, isIpfsReady, ipfsError } = useIpfsApi(ipfsConfig)
|
const { ipfs, isIpfsReady, ipfsError, addFiles } = useIpfsApi(ipfsConfig)
|
||||||
const [files, setFiles] = useState([])
|
const [files, setFiles] = useState<FileIpfs[]>()
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [message] = useState()
|
const [message] = useState()
|
||||||
const [error, setError] = useState('')
|
const [error, setError] = useState<string>()
|
||||||
|
|
||||||
async function handleOnDrop(acceptedFiles: FileDropzone[]): Promise<any> {
|
async function handleOnDrop(acceptedFiles: FileDropzone[]): Promise<void> {
|
||||||
if (!acceptedFiles) return
|
if (!acceptedFiles || !ipfs || !isIpfsReady) return
|
||||||
|
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
setError('')
|
setError(undefined)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const directoryCid = await addToIpfs(ipfs, acceptedFiles)
|
const addedFiles = await addFiles(acceptedFiles)
|
||||||
if (!directoryCid) return
|
if (!addedFiles) return
|
||||||
|
setFiles(addedFiles)
|
||||||
const fileList = await ipfs.ls(directoryCid)
|
|
||||||
setFiles(fileList)
|
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(`Adding to IPFS failed: ${error.message}`)
|
setError(`Adding to IPFS failed: ${(error as Error).message}`)
|
||||||
return null
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,14 +42,14 @@ export default function Add(): ReactElement {
|
|||||||
<div className={styles.add}>
|
<div className={styles.add}>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Loader message={message} />
|
<Loader message={message} />
|
||||||
) : files.length ? (
|
) : files?.length ? (
|
||||||
<ul style={{ textAlign: 'left' }}>
|
<ul style={{ textAlign: 'left' }}>
|
||||||
{files.map((file: FileIpfs) => (
|
{files?.map((file: FileIpfs) => (
|
||||||
<li key={file.path}>
|
<li key={file.path}>
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
href={`${ipfsGateway}/ipfs/${file.path}`}
|
href={`${ipfsGateway}/ipfs/${file.cid.toString()}`}
|
||||||
>
|
>
|
||||||
ipfs://{file.path}
|
ipfs://{file.path}
|
||||||
</a>
|
</a>
|
||||||
@ -61,6 +58,7 @@ export default function Add(): ReactElement {
|
|||||||
</ul>
|
</ul>
|
||||||
) : (
|
) : (
|
||||||
<Dropzone
|
<Dropzone
|
||||||
|
multiple
|
||||||
handleOnDrop={handleOnDrop}
|
handleOnDrop={handleOnDrop}
|
||||||
disabled={!isIpfsReady}
|
disabled={!isIpfsReady}
|
||||||
error={error || ipfsError}
|
error={error || ipfsError}
|
||||||
|
@ -43,7 +43,7 @@ export default function Dropzone({
|
|||||||
`Drop it like it's hot!`
|
`Drop it like it's hot!`
|
||||||
) : multiple === false ? (
|
) : multiple === false ? (
|
||||||
`Drag 'n' drop a file here, or click to select a file.`
|
`Drag 'n' drop a file here, or click to select a file.`
|
||||||
) : error !== '' ? (
|
) : error !== undefined ? (
|
||||||
<div className={styles.error}>{error}</div>
|
<div className={styles.error}>{error}</div>
|
||||||
) : (
|
) : (
|
||||||
`Drag 'n' drop some files here, or click to select files.`
|
`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 { pingUrl } from '../utils'
|
||||||
import { ipfsGateway, ipfsNodeUri } from '../../site.config'
|
import { ipfsGateway, ipfsNodeUri } from '../../site.config'
|
||||||
import styles from './Status.module.css'
|
import styles from './Status.module.css'
|
||||||
@ -7,7 +7,7 @@ export default function Status({ type }: { type: string }): ReactElement {
|
|||||||
const [isOnline, setIsOnline] = useState(false)
|
const [isOnline, setIsOnline] = useState(false)
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
|
|
||||||
async function ping() {
|
const ping = useCallback(async () => {
|
||||||
const url =
|
const url =
|
||||||
type === 'gateway'
|
type === 'gateway'
|
||||||
? `${ipfsGateway}/ipfs/QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG/readme`
|
? `${ipfsGateway}/ipfs/QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG/readme`
|
||||||
@ -15,8 +15,8 @@ export default function Status({ type }: { type: string }): ReactElement {
|
|||||||
|
|
||||||
const ping = await pingUrl(url)
|
const ping = await pingUrl(url)
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
isOnline !== ping && setIsOnline(ping)
|
setIsOnline(ping)
|
||||||
}
|
}, [type])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ping()
|
ping()
|
||||||
@ -30,7 +30,7 @@ export default function Status({ type }: { type: string }): ReactElement {
|
|||||||
setIsOnline(false)
|
setIsOnline(false)
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [ping])
|
||||||
|
|
||||||
const classes = isLoading
|
const classes = isLoading
|
||||||
? styles.loading
|
? styles.loading
|
||||||
|
@ -1,53 +1,76 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import ipfsClient from 'ipfs-http-client'
|
import { create, IPFSHTTPClient, Options } from 'ipfs-http-client'
|
||||||
import { parseHTML } from '../utils'
|
import { formatBytes } from '../utils'
|
||||||
import { IpfsConfig } from '../@types/ipfs'
|
import { FileDropzone } from '../components/Dropzone'
|
||||||
|
import { FileIpfs } from '../@types/ipfs'
|
||||||
|
|
||||||
let ipfs: any = null
|
export interface IpfsApiValue {
|
||||||
let ipfsVersion = ''
|
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 [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(() => {
|
useEffect(() => {
|
||||||
|
if (ipfs) return
|
||||||
|
|
||||||
async function initIpfs() {
|
async function initIpfs() {
|
||||||
if (ipfs !== null) return
|
|
||||||
ipfs = await ipfsClient(config)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const ipfs = create(config)
|
||||||
|
setIpfs(ipfs)
|
||||||
const version = await ipfs.version()
|
const version = await ipfs.version()
|
||||||
ipfsVersion = version.version
|
setVersion(version.version)
|
||||||
} catch (error) {
|
setIpfsReady(Boolean(ipfs && (await ipfs.id())))
|
||||||
let { message } = error
|
} catch (e) {
|
||||||
|
setIpfsError(`IPFS connection error: ${(e as Error).message}`)
|
||||||
if (!error.status) {
|
|
||||||
const htmlData = parseHTML(error)
|
|
||||||
message = htmlData.item(0)
|
|
||||||
message = message.textContent
|
|
||||||
}
|
|
||||||
|
|
||||||
setIpfsError(`IPFS connection error: ${message}`)
|
|
||||||
setIpfsReady(false)
|
setIpfsReady(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setIpfsReady(Boolean(await ipfs.id()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initIpfs()
|
initIpfs()
|
||||||
}, [config])
|
|
||||||
|
|
||||||
useEffect(() => {
|
return () => {
|
||||||
// just like componentWillUnmount()
|
|
||||||
return function cleanup() {
|
|
||||||
if (ipfs) {
|
if (ipfs) {
|
||||||
setIpfsReady(false)
|
setIpfsReady(false)
|
||||||
ipfs = null
|
setIpfs(undefined)
|
||||||
ipfsVersion = ''
|
setVersion(undefined)
|
||||||
setIpfsError('')
|
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'
|
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'
|
if (a === 0) return '0 Bytes'
|
||||||
const c = 1024
|
const c = 1024
|
||||||
const d = b || 2
|
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]
|
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 {
|
try {
|
||||||
const response = await axios(url, { timeout: 5000 })
|
const response = await axios(url, { timeout: 5000 })
|
||||||
if (!response || response.status !== 200) return false
|
if (!response || response.status !== 200) return false
|
||||||
return true
|
return true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error.message)
|
console.error((error as Error).message)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseHTML(str: string) {
|
export function parseHTML(str: string): HTMLCollection {
|
||||||
const tmp = document.implementation.createHTMLDocument()
|
const tmp = document.implementation.createHTMLDocument()
|
||||||
tmp.body.innerHTML = str
|
tmp.body.innerHTML = str
|
||||||
return tmp.body.children
|
return tmp.body.children
|
||||||
|
Loading…
Reference in New Issue
Block a user