React で Web アプリケーションを実装し、HTML の FILE API で指定されたファイルを IPFS にアップロードする機能を実現します。
> mkdir IPFS > cd IPFS IPFS> npx create-react-app ipfs-web --template typescript IPFS> cd .\ipfs-web\ IPFS\ipfs-web> npm install --save ipfs-core ipfs-core-types util
「ipfs-core」は JavaScript で IPFS を操作するためのライブラリ js-ipfs の一部です。
IPFS にファイルをアップロードする機能を提供する IPFSUploader.tsx コンポーネントを実装します。
IPFS\ipfs-web> mkdir src\components IPFS\ipfs-web> code src\components\IPFSUploader.tsx
import React, { useState, useEffect } from 'react'; import type { IPFS } from 'ipfs-core-types'; import * as IPFSCore from 'ipfs-core'; let ipfs: IPFS; function IPFSUploader() { const [isIpfsReady, setIpfsReady] = useState(Boolean(ipfs)) const [version, setVersion] = useState(""); const [cid, setCID] = useState(""); useEffect(() => { startIpfs(); return function cleanup () { if (ipfs && ipfs.stop) { console.log('Stopping IPFS'); ipfs.stop().catch((err: any) => console.error(err)); //ipfs = undefined; setIpfsReady(false); } } }, []); const startIpfs = async () => { if(ipfs){ console.log('IPFS already started'); /* } else if (window.ipfs && window.ipfs.enable) { console.log('Found window.ipfs') ipfs = await window.ipfs.enable({ commands: ['id'] }) */ } else { ipfs = await IPFSCore.create(); console.log("IPFS Started"); const _version = await ipfs.version(); setVersion(_version.version); } setIpfsReady(Boolean(ipfs)); }; const fileSelected = async (event: React.ChangeEvent<HTMLInputElement>) => { if(!isIpfsReady) return; const targetFile = event.target.files?.[0]; if(targetFile){ const _result = await ipfs.add(targetFile); //const _result = await ipfs.add('Hello world'); setCID(_result.path); } }; return ( <div> <h1>IPFS File Uploader</h1> <p>IPFS(ipfs-core) Version: { version }</p> <input id="input-file" type="file" onChange={fileSelected} disabled={!isIpfsReady}></input> <div> <p>Uploaded File CID: { cid }</p> <p><a href={`ipfs://${cid}`} target="_blank">open browser</a></p> <p><a href={`https://ipfs.io/ipfs/${cid}`} target="_blank">open ipfs.io public gateway</a></p> </div> </div> ); } export default IPFSUploader;
IPFS\ipfs-web> code src\App.tsx
不要な記述を削除し、IPFSUploader コンポーネントを追加します。
import React from 'react'; import './App.css'; import IPFSUploader from './components/IPFSUploader'; function App() { return ( <div className="App"> <header className="App-header"> <IPFSUploader/> </header> </div> ); } export default App;
IPFS\ipfs-web> npm start
MFS を利用してファイルをアップロードする。
IPFS\ipfs-web> code src\components\IPFSDirectoryUploader.tsx
import React, { useState, useEffect } from 'react'; import type { IPFS } from 'ipfs-core-types'; import * as IPFSCore from 'ipfs-core'; import { MFSEntry } from 'ipfs-core-types/dist/src/files/index'; let ipfs: IPFS; function IPFSDirectoryUploader() { const [isIpfsReady, setIpfsReady] = useState(Boolean(ipfs)) const [version, setVersion] = useState(""); const [MFSEntlies, setMFSEntlies] = useState<MFSEntry[]>(); useEffect(() => { startIpfs(); return function cleanup () { if (ipfs && ipfs.stop) { console.log('Stopping IPFS'); ipfs.stop().catch((err: any) => console.error(err)); //ipfs = undefined; setIpfsReady(false); } } }, []); const startIpfs = async () => { if(ipfs){ console.log('IPFS already started'); /* } else if (window.ipfs && window.ipfs.enable) { console.log('Found window.ipfs') ipfs = await window.ipfs.enable({ commands: ['id'] }) */ } else { ipfs = await IPFSCore.create(); console.log("IPFS Started"); const _version = await ipfs.version(); setVersion(_version.version); const lsResult = await ipfs.files.ls("/"); let uploadDirectoryExist = false; for await (const file of lsResult){ if(file.name === "upload-directory"){ console.log("/upload-directory is already exist."); uploadDirectoryExist = true; } } if(!uploadDirectoryExist){ console.log("mkdir /upload-directory"); await ipfs.files.mkdir("/upload-directory"); } } setIpfsReady(Boolean(ipfs)); }; const fileSelected = async (event: React.ChangeEvent<HTMLInputElement>) => { if(!isIpfsReady) return; const targetFile = event.target.files?.[0]; if(targetFile){ const fileReader = new window.FileReader(); fileReader.readAsArrayBuffer(targetFile); fileReader.onloadend = async () => { let ab = fileReader.result as ArrayBuffer; const u8a = new Uint8Array(ab); console.log(`upload /upload-directory/${targetFile.name}`); await ipfs.files.write(`/upload-directory/${targetFile.name}`, u8a, {create: true}); await ls("/upload-directory"); }; } }; const ls = async (path: string) => { const lsResult = await ipfs.files.ls(path); for await (const file of lsResult){ console.log(`${file.name}: ${file.cid.toString()}`); } }; const MFSStat = async () => { if(!isIpfsReady) return; const lsResult = await ipfs.files.ls('/upload-directory'); let _mfs_entlies: MFSEntry[] = []; for await (const file of lsResult){ console.log(`${file.name}: ${file.cid.toString()}`); _mfs_entlies.push(file); } setMFSEntlies(_mfs_entlies); }; return ( <div> <h1>IPFS File Uploader</h1> <p>IPFS(ipfs-core) Version: { version }</p> <input id="input-file" type="file" onChange={fileSelected} disabled={!isIpfsReady}></input> <div> <p><button onClick={MFSStat}>MFS Stat</button></p> <p> { MFSEntlies?.map((file) => ( <div> <p>{file.name}: {file.cid.toString()}</p> <p> <a href={`https://ipfs.io/ipfs/${file.cid.toString()}`} target="_blank" rel="noreferrer">Open</a> <img src={`https://ipfs.io/ipfs/${file.cid.toString()}`} alt="IPFS img link."/> </p> </div> ))} </p> </div> </div> ); } export default IPFSDirectoryUploader;
App.tsx に IPFSDirectoryUploader コンポーネントを記述する。