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.tsximport 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 startMFS を利用してファイルをアップロードする。
IPFS\ipfs-web> code src\components\IPFSDirectoryUploader.tsximport 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 コンポーネントを記述する。