mirror of
https://github.com/fluencelabs/examples
synced 2025-06-17 20:11:21 +00:00
Split application into components
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "ipfs-execution",
|
||||
"name": "@fluencelabs/ipfs-execution",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
@ -1275,7 +1275,7 @@
|
||||
}
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "npm:@achingbrain/node-fetch@2.6.7",
|
||||
"version": "npm:node-fetch@2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-iTASGs+HTFK5E4ZqcMsHmeJ4zodyq8L38lZV33jwqcBJYoUt3HjN4+ot+O9/0b+ke8ddE7UgOtVuZN/OkV19/g=="
|
||||
},
|
||||
@ -1414,7 +1414,7 @@
|
||||
}
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "npm:@achingbrain/node-fetch@2.6.7",
|
||||
"version": "npm:node-fetch@2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-iTASGs+HTFK5E4ZqcMsHmeJ4zodyq8L38lZV33jwqcBJYoUt3HjN4+ot+O9/0b+ke8ddE7UgOtVuZN/OkV19/g=="
|
||||
},
|
||||
@ -1566,7 +1566,7 @@
|
||||
}
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "npm:@achingbrain/node-fetch@2.6.7",
|
||||
"version": "npm:node-fetch@2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-iTASGs+HTFK5E4ZqcMsHmeJ4zodyq8L38lZV33jwqcBJYoUt3HjN4+ot+O9/0b+ke8ddE7UgOtVuZN/OkV19/g=="
|
||||
},
|
||||
|
13
intro/4-ipfs-code-execution/web/package-lock.json
generated
13
intro/4-ipfs-code-execution/web/package-lock.json
generated
@ -12679,6 +12679,11 @@
|
||||
"pify": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"hamt_plus": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz",
|
||||
"integrity": "sha1-4hwlKWjH4zsg9qGwlM2FeHomVgE="
|
||||
},
|
||||
"handle-thing": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz",
|
||||
@ -21786,6 +21791,14 @@
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"recoil": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/recoil/-/recoil-0.3.1.tgz",
|
||||
"integrity": "sha512-KNA3DRqgxX4rRC8E7fc6uIw7BACmMPuraIYy+ejhE8tsw7w32CetMm8w7AMZa34wzanKKkev3vl3H7Z4s0QSiA==",
|
||||
"requires": {
|
||||
"hamt_plus": "1.0.2"
|
||||
}
|
||||
},
|
||||
"recursive-readdir": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz",
|
||||
|
@ -4,10 +4,9 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@fluencelabs/aqua-ipfs": "0.1.8",
|
||||
"@fluencelabs/ipfs-execution": "file:../aqua",
|
||||
"@fluencelabs/fluence": "0.9.53",
|
||||
"@fluencelabs/fluence-network-environment": "1.0.10",
|
||||
"ipfs-http-client": "^50.1.2",
|
||||
"@fluencelabs/ipfs-execution": "file:../aqua",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^11.2.7",
|
||||
"@testing-library/user-event": "^12.8.3",
|
||||
@ -15,15 +14,17 @@
|
||||
"@types/node": "^12.20.16",
|
||||
"@types/react": "^17.0.14",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"ipfs-http-client": "^50.1.2",
|
||||
"multiaddr": "^10.0.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-scripts": "4.0.3",
|
||||
"recoil": "^0.3.1",
|
||||
"typescript": "^4.3.5",
|
||||
"web-vitals": "^1.1.2",
|
||||
"multiaddr": "^10.0.0"
|
||||
"web-vitals": "^1.1.2"
|
||||
},
|
||||
"scripts": {
|
||||
"prestart": "(cd ../aqua; npm run build)",
|
||||
"_prestart": "(cd ../aqua; npm run build)",
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
|
@ -1,143 +1,25 @@
|
||||
import React, { useState } from "react";
|
||||
import React from "react";
|
||||
import logo from "./logo.svg";
|
||||
import "./App.scss";
|
||||
|
||||
import { createClient, FluenceClient } from "@fluencelabs/fluence";
|
||||
import { get_external_api_multiaddr } from "@fluencelabs/aqua-ipfs";
|
||||
import { stage } from "@fluencelabs/fluence-network-environment";
|
||||
import {
|
||||
deploy_service,
|
||||
put_file_size,
|
||||
remove_service,
|
||||
provideFile,
|
||||
} from "@fluencelabs/ipfs-execution";
|
||||
import { Multiaddr, protocols } from "multiaddr";
|
||||
const { create, globSource, urlSource, CID } = require("ipfs-http-client");
|
||||
|
||||
const relayNodes = [stage[0], stage[1], stage[2]];
|
||||
|
||||
const copyToClipboard = (text: string) => {
|
||||
navigator.clipboard.writeText(text);
|
||||
};
|
||||
|
||||
function fromOption<T>(opt: T | T[] | null): T | null {
|
||||
if (Array.isArray(opt)) {
|
||||
if (opt.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
opt = opt[0];
|
||||
}
|
||||
if (opt === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return opt;
|
||||
}
|
||||
|
||||
function decapsulateP2P(rpcAddr: string): string {
|
||||
return new Multiaddr(rpcAddr)
|
||||
.decapsulateCode(protocols.names.p2p.code)
|
||||
.toString();
|
||||
}
|
||||
import { gotRpcAddrState, isConnectedState, isDeployedState } from "./state";
|
||||
import { useRecoilValue } from "recoil";
|
||||
import { ConnectedInfo } from "./Components/ConnectedInfo";
|
||||
import { ConnectionForm } from "./Components/ConnectionForm";
|
||||
import { IpfsForm } from "./Components/IpfsForm";
|
||||
import { IpfsDeploymentInfo } from "./Components/IpfsDeploymentInfo";
|
||||
import { SizeCalcForm } from "./Components/SizeCalcForm";
|
||||
|
||||
function App() {
|
||||
const [client, setClient] = useState<FluenceClient | null>(null);
|
||||
const [serviceId, setServiceId] = useState<string | null>(null);
|
||||
|
||||
const [wasm, setWasm] = useState<string>(
|
||||
"Qmf8fH2cDZXGKS9uDGBcHxv5uQ51ChrigdZKe3QxS2C1AF"
|
||||
);
|
||||
const [rpcAddr, setRpcAddr] = useState<string>("");
|
||||
const [fileCID, setFileCID] = useState<string>("");
|
||||
const [fileSize, setFileSize] = useState<string>("");
|
||||
const [fileSizeCID, setFileSizeCID] = useState<string>("");
|
||||
|
||||
const isConnected = client !== null;
|
||||
const gotRpcAddr = rpcAddr !== null;
|
||||
const deployed = serviceId !== null;
|
||||
|
||||
const connect = async (relayPeerId: string) => {
|
||||
try {
|
||||
let client = await createClient(relayPeerId);
|
||||
setClient(client);
|
||||
await getRpcAddr(client);
|
||||
} catch (err) {
|
||||
console.log("Client initialization failed", err);
|
||||
}
|
||||
};
|
||||
|
||||
const getRpcAddr = async (client: FluenceClient) => {
|
||||
if (client === null) {
|
||||
console.log("getRpcAddr client is null");
|
||||
return;
|
||||
}
|
||||
|
||||
let result = await get_external_api_multiaddr(client, client.relayPeerId!);
|
||||
console.log("getRpcAddr result", result);
|
||||
let rpcAddr = result.multiaddr;
|
||||
setRpcAddr(decapsulateP2P(rpcAddr));
|
||||
};
|
||||
|
||||
const deployService = async () => {
|
||||
console.log("wasm %s rpcAddr %s", wasm, rpcAddr);
|
||||
if (client === null || wasm === null || rpcAddr === null) {
|
||||
return;
|
||||
}
|
||||
var service_id = await deploy_service(
|
||||
client,
|
||||
client.relayPeerId!,
|
||||
wasm,
|
||||
rpcAddr,
|
||||
(msg, value) => console.log(msg, value),
|
||||
{ ttl: 10000 }
|
||||
);
|
||||
service_id = fromOption(service_id);
|
||||
setServiceId(service_id);
|
||||
};
|
||||
|
||||
const getFileSize = async () => {
|
||||
if (client === null || serviceId === null || rpcAddr === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var putResult = await put_file_size(
|
||||
client,
|
||||
client.relayPeerId!,
|
||||
fileCID,
|
||||
rpcAddr,
|
||||
serviceId,
|
||||
(size) => setFileSize(size.toString()),
|
||||
(label, error) => setFileSize("Error: " + label + ": " + error),
|
||||
{ ttl: 10000 }
|
||||
);
|
||||
putResult = fromOption(putResult);
|
||||
if (putResult === null) {
|
||||
return;
|
||||
}
|
||||
if (putResult.success) {
|
||||
setFileSizeCID(putResult.hash);
|
||||
} else {
|
||||
setFileSizeCID("Error: " + putResult.error);
|
||||
}
|
||||
};
|
||||
|
||||
const removeService = async () => {
|
||||
if (client === null || serviceId === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
await remove_service(client, client.relayPeerId!, serviceId, {
|
||||
ttl: 10000,
|
||||
});
|
||||
setServiceId(null);
|
||||
};
|
||||
const isConnected = useRecoilValue(isConnectedState);
|
||||
const gotRpcAddr = useRecoilValue(gotRpcAddrState);
|
||||
const isDeployed = useRecoilValue(isDeployedState);
|
||||
|
||||
console.log(
|
||||
"isConnected gotRpcAddr deployed\n",
|
||||
isConnected,
|
||||
gotRpcAddr,
|
||||
deployed
|
||||
isDeployed
|
||||
);
|
||||
|
||||
return (
|
||||
@ -147,33 +29,13 @@ function App() {
|
||||
</header>
|
||||
|
||||
<div className="content">
|
||||
{!isConnected && <ConnectionForm connect={connect} />}
|
||||
{isConnected && <Connected client={client!} />}
|
||||
{isConnected && gotRpcAddr && !deployed && (
|
||||
<IpfsForm
|
||||
rpcAddr={rpcAddr}
|
||||
setRpcAddr={setRpcAddr}
|
||||
wasm={wasm}
|
||||
setWasm={setWasm}
|
||||
deployService={deployService}
|
||||
/>
|
||||
)}
|
||||
{deployed && (
|
||||
{!isConnected && <ConnectionForm />}
|
||||
{isConnected && <ConnectedInfo />}
|
||||
{isConnected && gotRpcAddr && !isDeployed && <IpfsForm />}
|
||||
{isDeployed && (
|
||||
<>
|
||||
<IfsDeployed
|
||||
wasm={wasm}
|
||||
serviceId={serviceId}
|
||||
removeService={removeService}
|
||||
/>
|
||||
<Deployed
|
||||
setRpcAddr={setRpcAddr}
|
||||
rpcAddr={rpcAddr}
|
||||
setFileCID={setFileCID}
|
||||
fileCID={fileCID}
|
||||
fileSize={fileSize}
|
||||
fileSizeCID={fileSizeCID}
|
||||
getFileSize={getFileSize}
|
||||
/>
|
||||
<IpfsDeploymentInfo />
|
||||
<SizeCalcForm />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@ -181,207 +43,4 @@ function App() {
|
||||
);
|
||||
}
|
||||
|
||||
const Deployed = (props: {
|
||||
setRpcAddr: React.Dispatch<React.SetStateAction<string>>;
|
||||
rpcAddr: string;
|
||||
setFileCID: React.Dispatch<React.SetStateAction<string>>;
|
||||
fileCID: string;
|
||||
fileSize: string;
|
||||
fileSizeCID: string;
|
||||
getFileSize: () => Promise<void>;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<h2>Get file size</h2>
|
||||
<p className="p">
|
||||
Upload any file to IPFS node
|
||||
<p>
|
||||
<b>{props.rpcAddr}</b>
|
||||
</p>
|
||||
paste CID here and get the size of that file via ProcessFiles service
|
||||
you have just deployed
|
||||
</p>
|
||||
<div className="row">
|
||||
<label className="label bold">IPFS CID</label>
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
onChange={(e) => props.setFileCID(e.target.value)}
|
||||
value={props.fileCID}
|
||||
/>
|
||||
</div>
|
||||
<div className="row">
|
||||
<button className="btn btn-hello" onClick={props.getFileSize}>
|
||||
get size
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<label className="label bold">File Size:</label>
|
||||
<label className="mono">{props.fileSize}</label>
|
||||
</div>
|
||||
<div className="row">
|
||||
<label className="label bold">
|
||||
File size is uploaded to IPFS as CID:
|
||||
</label>
|
||||
<label className="mono">{props.fileSizeCID}</label>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const IpfsForm = (props: {
|
||||
rpcAddr: string;
|
||||
setRpcAddr: React.Dispatch<React.SetStateAction<string>>;
|
||||
wasm: string;
|
||||
setWasm: React.Dispatch<React.SetStateAction<string>>;
|
||||
deployService: () => Promise<void>;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<h2>Ipfs</h2>
|
||||
<p>
|
||||
process_files.wasm will be downloaded via IPFS to the Fluence node, and
|
||||
then a service will be dynamically created from it! After that, you will
|
||||
be able to use that service to get sizes of IPFS files!
|
||||
<br />
|
||||
To do so, please specify IPFS RPC address to download process_files.wasm
|
||||
from
|
||||
<br />
|
||||
And specify CID of WebAssembly module.
|
||||
</p>
|
||||
|
||||
<TextInput
|
||||
text={"IPFS RPC address"}
|
||||
value={props.rpcAddr}
|
||||
setValue={props.setRpcAddr}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
text={"process_files.wasm module CID"}
|
||||
value={props.wasm}
|
||||
setValue={props.setWasm}
|
||||
/>
|
||||
<div className="row">
|
||||
<button className="btn btn-hello" onClick={props.deployService}>
|
||||
deploy service
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const IfsDeployed = (props: {
|
||||
wasm: string;
|
||||
serviceId: string | null;
|
||||
removeService: () => {};
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<h2>Deployed</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<td className="bold">process_files.wasm CID:</td>
|
||||
<td className="mono">{props.wasm}</td>
|
||||
<td>
|
||||
<button
|
||||
className="btn-clipboard"
|
||||
onClick={() => copyToClipboard(props.wasm)}
|
||||
>
|
||||
<i className="gg-clipboard"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="bold">ProcessFiles service ID:</td>
|
||||
<td className="mono">{props.serviceId}</td>
|
||||
<button
|
||||
className="btn-clipboard"
|
||||
onClick={() => props.removeService()}
|
||||
>
|
||||
<i className="gg-trash"></i>
|
||||
</button>
|
||||
</tr>
|
||||
</table>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const ConnectionForm = (props: {
|
||||
connect: (multiaddr: string) => Promise<void>;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<h1>Pick a relay</h1>
|
||||
<ul>
|
||||
{relayNodes.map((x) => (
|
||||
<li key={x.peerId}>
|
||||
<span className="mono">{x.peerId}</span>
|
||||
<button
|
||||
className="btn"
|
||||
onClick={async () => await props.connect(x.multiaddr)}
|
||||
>
|
||||
Connect
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Connected = (props: { client: FluenceClient }) => {
|
||||
return (
|
||||
<>
|
||||
<h1>Connected</h1>
|
||||
<table>
|
||||
<tr>
|
||||
<td className="bold">Peer id:</td>
|
||||
<td className="mono">{props.client.selfPeerId}</td>
|
||||
<td>
|
||||
<button
|
||||
className="btn-clipboard"
|
||||
onClick={() => copyToClipboard(props.client.selfPeerId)}
|
||||
>
|
||||
<i className="gg-clipboard"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="bold">Relay peer id:</td>
|
||||
<td className="mono">{props.client.relayPeerId}</td>
|
||||
<td>
|
||||
<button
|
||||
className="btn-clipboard"
|
||||
onClick={() => copyToClipboard(props.client.relayPeerId!)}
|
||||
>
|
||||
<i className="gg-clipboard"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const TextInput = (props: {
|
||||
text: string;
|
||||
value: string;
|
||||
setValue: React.Dispatch<React.SetStateAction<string>>;
|
||||
}) => {
|
||||
return (
|
||||
<div className="row">
|
||||
<label className="label bold">{props.text}</label>
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
onChange={(e) => props.setValue(e.target.value)}
|
||||
value={props.value}
|
||||
required={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
@ -0,0 +1,44 @@
|
||||
import { useRecoilValue } from "recoil";
|
||||
import { clientState } from "../state";
|
||||
import { copyToClipboard } from "../util";
|
||||
|
||||
export const ConnectedInfo = () => {
|
||||
const client = useRecoilValue(clientState);
|
||||
if (client === null) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>Connected</h1>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td className="bold">Peer id:</td>
|
||||
<td className="mono">{client.selfPeerId}</td>
|
||||
<td>
|
||||
<button
|
||||
className="btn-clipboard"
|
||||
onClick={() => copyToClipboard(client.selfPeerId)}
|
||||
>
|
||||
<i className="gg-clipboard"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="bold">Relay peer id:</td>
|
||||
<td className="mono">{client.relayPeerId}</td>
|
||||
<td>
|
||||
<button
|
||||
className="btn-clipboard"
|
||||
onClick={() => copyToClipboard(client.relayPeerId!)}
|
||||
>
|
||||
<i className="gg-clipboard"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</>
|
||||
);
|
||||
};
|
@ -0,0 +1,21 @@
|
||||
import { relayNodes, useClientConnect } from "../state";
|
||||
|
||||
export const ConnectionForm = () => {
|
||||
const connect = useClientConnect();
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>Pick a relay</h1>
|
||||
<ul>
|
||||
{relayNodes.map((x) => (
|
||||
<li key={x.peerId}>
|
||||
<span className="mono">{x.peerId}</span>
|
||||
<button className="btn" onClick={() => connect(x.multiaddr)}>
|
||||
Connect
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
};
|
@ -0,0 +1,36 @@
|
||||
import { useRecoilValue } from "recoil";
|
||||
import { serviceIdState, useRemoveServuce, wasmState } from "../state";
|
||||
import { copyToClipboard } from "../util";
|
||||
|
||||
export const IpfsDeploymentInfo = () => {
|
||||
const wasm = useRecoilValue(wasmState);
|
||||
const serviceId = useRecoilValue(serviceIdState);
|
||||
const removeService = useRemoveServuce;
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>Deployed</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<td className="bold">process_files.wasm CID:</td>
|
||||
<td className="mono">{wasm}</td>
|
||||
<td>
|
||||
<button
|
||||
className="btn-clipboard"
|
||||
onClick={() => copyToClipboard(wasm)}
|
||||
>
|
||||
<i className="gg-clipboard"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="bold">ProcessFiles service ID:</td>
|
||||
<td className="mono">{serviceId}</td>
|
||||
<button className="btn-clipboard" onClick={removeService}>
|
||||
<i className="gg-trash"></i>
|
||||
</button>
|
||||
</tr>
|
||||
</table>
|
||||
</>
|
||||
);
|
||||
};
|
42
intro/4-ipfs-code-execution/web/src/Components/IpfsForm.tsx
Normal file
42
intro/4-ipfs-code-execution/web/src/Components/IpfsForm.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import { useRecoilState, useResetRecoilState } from "recoil";
|
||||
import { rpcAddrState, useDeployService, wasmState } from "../state";
|
||||
import { TextInput } from "./TextInput";
|
||||
|
||||
export const IpfsForm = () => {
|
||||
const [rpcAddr, setRpcAddr] = useRecoilState(rpcAddrState);
|
||||
const [wasm, setWasm] = useRecoilState(wasmState);
|
||||
const deployService = useDeployService();
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>Ipfs</h2>
|
||||
<p>
|
||||
process_files.wasm will be downloaded via IPFS to the Fluence node, and
|
||||
then a service will be dynamically created from it! After that, you will
|
||||
be able to use that service to get sizes of IPFS files!
|
||||
<br />
|
||||
To do so, please specify IPFS RPC address to download process_files.wasm
|
||||
from
|
||||
<br />
|
||||
And specify CID of WebAssembly module.
|
||||
</p>
|
||||
|
||||
<TextInput
|
||||
text={"IPFS RPC address"}
|
||||
value={rpcAddr}
|
||||
setValue={setRpcAddr}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
text={"process_files.wasm module CID"}
|
||||
value={wasm}
|
||||
setValue={setWasm}
|
||||
/>
|
||||
<div className="row">
|
||||
<button className="btn btn-hello" onClick={deployService}>
|
||||
deploy service
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
@ -0,0 +1,56 @@
|
||||
import { useRecoilState, useRecoilValue } from "recoil";
|
||||
import {
|
||||
fileCIDState,
|
||||
fileSizeCIDState,
|
||||
fileSizeState,
|
||||
rpcAddrState,
|
||||
useGetFileSize,
|
||||
} from "../state";
|
||||
|
||||
export const SizeCalcForm = () => {
|
||||
const rpcAddr = useRecoilValue(rpcAddrState);
|
||||
const [fileCID, setFileCID] = useRecoilState(fileCIDState);
|
||||
const fileSize = useRecoilValue(fileSizeState);
|
||||
const fileSizeCID = useRecoilValue(fileSizeCIDState);
|
||||
const getFileSize = useGetFileSize();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<h2>Get file size</h2>
|
||||
<p className="p">
|
||||
Upload any file to IPFS node
|
||||
<p>
|
||||
<b>{rpcAddr}</b>
|
||||
</p>
|
||||
paste CID here and get the size of that file via ProcessFiles service
|
||||
you have just deployed
|
||||
</p>
|
||||
<div className="row">
|
||||
<label className="label bold">IPFS CID</label>
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
onChange={(e) => setFileCID(e.target.value)}
|
||||
value={fileCID}
|
||||
/>
|
||||
</div>
|
||||
<div className="row">
|
||||
<button className="btn btn-hello" onClick={getFileSize}>
|
||||
get size
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<label className="label bold">File Size:</label>
|
||||
<label className="mono">{fileSize}</label>
|
||||
</div>
|
||||
<div className="row">
|
||||
<label className="label bold">
|
||||
File size is uploaded to IPFS as CID:
|
||||
</label>
|
||||
<label className="mono">{fileSizeCID}</label>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
20
intro/4-ipfs-code-execution/web/src/Components/TextInput.tsx
Normal file
20
intro/4-ipfs-code-execution/web/src/Components/TextInput.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import React from "react";
|
||||
|
||||
export const TextInput = (props: {
|
||||
text: string;
|
||||
value: string | null;
|
||||
setValue: (val: string) => void;
|
||||
}) => {
|
||||
return (
|
||||
<div className="row">
|
||||
<label className="label bold">{props.text}</label>
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
onChange={(e) => props.setValue(e.target.value)}
|
||||
value={props.value || ""}
|
||||
required={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,12 +1,14 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import "./index.css";
|
||||
import App from "./App";
|
||||
import { RecoilRoot } from "recoil";
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<RecoilRoot>
|
||||
<App />
|
||||
</RecoilRoot>
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
document.getElementById("root")
|
||||
);
|
||||
|
||||
|
187
intro/4-ipfs-code-execution/web/src/state.ts
Normal file
187
intro/4-ipfs-code-execution/web/src/state.ts
Normal file
@ -0,0 +1,187 @@
|
||||
import { get_external_api_multiaddr } from "@fluencelabs/aqua-ipfs";
|
||||
import {
|
||||
deploy_service,
|
||||
put_file_size,
|
||||
remove_service,
|
||||
} from "@fluencelabs/ipfs-execution";
|
||||
import { createClient, FluenceClient } from "@fluencelabs/fluence";
|
||||
import { stage } from "@fluencelabs/fluence-network-environment";
|
||||
import {
|
||||
atom,
|
||||
selector,
|
||||
useRecoilState,
|
||||
useRecoilValue,
|
||||
useSetRecoilState,
|
||||
} from "recoil";
|
||||
import { decapsulateP2P, fromOption } from "./util";
|
||||
|
||||
export const relayNodes = [stage[0], stage[1], stage[2]];
|
||||
|
||||
export const clientState = atom<FluenceClient | null>({
|
||||
key: "clientState",
|
||||
default: null,
|
||||
dangerouslyAllowMutability: true,
|
||||
});
|
||||
|
||||
export const serviceIdState = atom<string | null>({
|
||||
key: "serviceIdState",
|
||||
default: null,
|
||||
});
|
||||
|
||||
export const wasmState = atom<string>({
|
||||
key: "serviceState",
|
||||
default: "Qmf8fH2cDZXGKS9uDGBcHxv5uQ51ChrigdZKe3QxS2C1AF",
|
||||
});
|
||||
|
||||
export const rpcAddrState = atom<string | null>({
|
||||
key: "rpcAddrState",
|
||||
default: null,
|
||||
});
|
||||
|
||||
export const fileCIDState = atom<string>({
|
||||
key: "fileCIDState",
|
||||
default: "",
|
||||
});
|
||||
|
||||
export const fileSizeState = atom<string>({
|
||||
key: "fileSizeState",
|
||||
default: "",
|
||||
});
|
||||
|
||||
export const fileSizeCIDState = atom<string>({
|
||||
key: "fileSizeCIDState",
|
||||
default: "",
|
||||
});
|
||||
|
||||
export const isConnectedState = selector({
|
||||
key: "isConnectedState",
|
||||
get: ({ get }) => {
|
||||
const client = get(clientState);
|
||||
|
||||
return client !== null && client.isConnected;
|
||||
},
|
||||
dangerouslyAllowMutability: true,
|
||||
});
|
||||
|
||||
export const gotRpcAddrState = selector({
|
||||
key: "getRpcAddrState",
|
||||
get: ({ get }) => {
|
||||
const rpcAddr = get(rpcAddrState);
|
||||
|
||||
return rpcAddr !== null;
|
||||
},
|
||||
});
|
||||
|
||||
export const isDeployedState = selector({
|
||||
key: "isDeployedState",
|
||||
get: ({ get }) => {
|
||||
const serviceId = get(serviceIdState);
|
||||
|
||||
return serviceId !== null;
|
||||
},
|
||||
});
|
||||
|
||||
const getRpcAddr = async (client: FluenceClient) => {
|
||||
if (client === null) {
|
||||
console.log("getRpcAddr client is null");
|
||||
return;
|
||||
}
|
||||
|
||||
let result = await get_external_api_multiaddr(client, client.relayPeerId!);
|
||||
console.log("getRpcAddr result", result);
|
||||
let rpcAddr = result.multiaddr;
|
||||
return decapsulateP2P(rpcAddr);
|
||||
};
|
||||
|
||||
export const useClientConnect = () => {
|
||||
const setClient = useSetRecoilState(clientState);
|
||||
const setRpcAddr = useSetRecoilState(rpcAddrState);
|
||||
|
||||
const connect = async (relayPeerId: string) => {
|
||||
try {
|
||||
const client = await createClient(relayPeerId);
|
||||
const addr = await getRpcAddr(client);
|
||||
setRpcAddr(addr!);
|
||||
setClient(client);
|
||||
} catch (err) {
|
||||
console.log("Client initialization failed", err);
|
||||
}
|
||||
};
|
||||
|
||||
return connect;
|
||||
};
|
||||
|
||||
export const useDeployService = () => {
|
||||
const wasm = useRecoilValue(wasmState);
|
||||
const rpcAddr = useRecoilValue(rpcAddrState);
|
||||
const client = useRecoilValue(clientState);
|
||||
const setServiceId = useSetRecoilState(serviceIdState);
|
||||
|
||||
return async () => {
|
||||
console.log("wasm %s rpcAddr %s", wasm, rpcAddr);
|
||||
if (client === null || wasm === null || rpcAddr === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var service_id = await deploy_service(
|
||||
client,
|
||||
client.relayPeerId!,
|
||||
wasm,
|
||||
rpcAddr,
|
||||
(msg, value) => console.log(msg, value),
|
||||
{ ttl: 10000 }
|
||||
);
|
||||
service_id = fromOption(service_id);
|
||||
setServiceId(service_id);
|
||||
};
|
||||
};
|
||||
|
||||
export const useGetFileSize = () => {
|
||||
const rpcAddr = useRecoilValue(rpcAddrState);
|
||||
const client = useRecoilValue(clientState);
|
||||
const serviceId = useRecoilValue(serviceIdState);
|
||||
const setFileSize = useSetRecoilState(fileSizeState);
|
||||
const [fileCID, setFileSizeCID] = useRecoilState(fileCIDState);
|
||||
|
||||
return async () => {
|
||||
if (client === null || serviceId === null || rpcAddr === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var putResult = await put_file_size(
|
||||
client,
|
||||
client.relayPeerId!,
|
||||
fileCID,
|
||||
rpcAddr,
|
||||
serviceId,
|
||||
(size) => setFileSize(size.toString()),
|
||||
(label, error) => setFileSize("Error: " + label + ": " + error),
|
||||
{ ttl: 10000 }
|
||||
);
|
||||
putResult = fromOption(putResult);
|
||||
if (putResult === null) {
|
||||
return;
|
||||
}
|
||||
if (putResult.success) {
|
||||
setFileSizeCID(putResult.hash);
|
||||
} else {
|
||||
setFileSizeCID("Error: " + putResult.error);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const useRemoveServuce = () => {
|
||||
const client = useRecoilValue(clientState);
|
||||
const [serviceId, setServiceId] = useRecoilState(serviceIdState);
|
||||
|
||||
return async () => {
|
||||
if (client === null || serviceId === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
await remove_service(client, client.relayPeerId!, serviceId, {
|
||||
ttl: 10000,
|
||||
});
|
||||
setServiceId(null);
|
||||
};
|
||||
};
|
26
intro/4-ipfs-code-execution/web/src/util.ts
Normal file
26
intro/4-ipfs-code-execution/web/src/util.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { Multiaddr, protocols } from "multiaddr";
|
||||
|
||||
export const decapsulateP2P = (rpcAddr: string) => {
|
||||
return new Multiaddr(rpcAddr)
|
||||
.decapsulateCode(protocols.names.p2p.code)
|
||||
.toString();
|
||||
};
|
||||
|
||||
export const copyToClipboard = (text: string) => {
|
||||
navigator.clipboard.writeText(text);
|
||||
};
|
||||
|
||||
export const fromOption = <T>(opt: T | T[] | null): T | null => {
|
||||
if (Array.isArray(opt)) {
|
||||
if (opt.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
opt = opt[0];
|
||||
}
|
||||
if (opt === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return opt;
|
||||
};
|
Reference in New Issue
Block a user