mirror of
https://github.com/fluencelabs/examples
synced 2025-06-18 12:31: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",
|
"version": "0.1.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
@ -1275,7 +1275,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node-fetch": {
|
"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",
|
"resolved": "https://registry.npmjs.org/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||||
"integrity": "sha512-iTASGs+HTFK5E4ZqcMsHmeJ4zodyq8L38lZV33jwqcBJYoUt3HjN4+ot+O9/0b+ke8ddE7UgOtVuZN/OkV19/g=="
|
"integrity": "sha512-iTASGs+HTFK5E4ZqcMsHmeJ4zodyq8L38lZV33jwqcBJYoUt3HjN4+ot+O9/0b+ke8ddE7UgOtVuZN/OkV19/g=="
|
||||||
},
|
},
|
||||||
@ -1414,7 +1414,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node-fetch": {
|
"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",
|
"resolved": "https://registry.npmjs.org/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||||
"integrity": "sha512-iTASGs+HTFK5E4ZqcMsHmeJ4zodyq8L38lZV33jwqcBJYoUt3HjN4+ot+O9/0b+ke8ddE7UgOtVuZN/OkV19/g=="
|
"integrity": "sha512-iTASGs+HTFK5E4ZqcMsHmeJ4zodyq8L38lZV33jwqcBJYoUt3HjN4+ot+O9/0b+ke8ddE7UgOtVuZN/OkV19/g=="
|
||||||
},
|
},
|
||||||
@ -1566,7 +1566,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node-fetch": {
|
"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",
|
"resolved": "https://registry.npmjs.org/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||||
"integrity": "sha512-iTASGs+HTFK5E4ZqcMsHmeJ4zodyq8L38lZV33jwqcBJYoUt3HjN4+ot+O9/0b+ke8ddE7UgOtVuZN/OkV19/g=="
|
"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"
|
"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": {
|
"handle-thing": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz",
|
||||||
@ -21786,6 +21791,14 @@
|
|||||||
"ms": "^2.1.1"
|
"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": {
|
"recursive-readdir": {
|
||||||
"version": "2.2.2",
|
"version": "2.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz",
|
||||||
|
@ -4,10 +4,9 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fluencelabs/aqua-ipfs": "0.1.8",
|
"@fluencelabs/aqua-ipfs": "0.1.8",
|
||||||
"@fluencelabs/ipfs-execution": "file:../aqua",
|
|
||||||
"@fluencelabs/fluence": "0.9.53",
|
"@fluencelabs/fluence": "0.9.53",
|
||||||
"@fluencelabs/fluence-network-environment": "1.0.10",
|
"@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/jest-dom": "^5.14.1",
|
||||||
"@testing-library/react": "^11.2.7",
|
"@testing-library/react": "^11.2.7",
|
||||||
"@testing-library/user-event": "^12.8.3",
|
"@testing-library/user-event": "^12.8.3",
|
||||||
@ -15,15 +14,17 @@
|
|||||||
"@types/node": "^12.20.16",
|
"@types/node": "^12.20.16",
|
||||||
"@types/react": "^17.0.14",
|
"@types/react": "^17.0.14",
|
||||||
"@types/react-dom": "^17.0.9",
|
"@types/react-dom": "^17.0.9",
|
||||||
|
"ipfs-http-client": "^50.1.2",
|
||||||
|
"multiaddr": "^10.0.0",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-scripts": "4.0.3",
|
"react-scripts": "4.0.3",
|
||||||
|
"recoil": "^0.3.1",
|
||||||
"typescript": "^4.3.5",
|
"typescript": "^4.3.5",
|
||||||
"web-vitals": "^1.1.2",
|
"web-vitals": "^1.1.2"
|
||||||
"multiaddr": "^10.0.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prestart": "(cd ../aqua; npm run build)",
|
"_prestart": "(cd ../aqua; npm run build)",
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
"build": "react-scripts build",
|
"build": "react-scripts build",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
|
@ -1,143 +1,25 @@
|
|||||||
import React, { useState } from "react";
|
import React from "react";
|
||||||
import logo from "./logo.svg";
|
import logo from "./logo.svg";
|
||||||
import "./App.scss";
|
import "./App.scss";
|
||||||
|
|
||||||
import { createClient, FluenceClient } from "@fluencelabs/fluence";
|
import { gotRpcAddrState, isConnectedState, isDeployedState } from "./state";
|
||||||
import { get_external_api_multiaddr } from "@fluencelabs/aqua-ipfs";
|
import { useRecoilValue } from "recoil";
|
||||||
import { stage } from "@fluencelabs/fluence-network-environment";
|
import { ConnectedInfo } from "./Components/ConnectedInfo";
|
||||||
import {
|
import { ConnectionForm } from "./Components/ConnectionForm";
|
||||||
deploy_service,
|
import { IpfsForm } from "./Components/IpfsForm";
|
||||||
put_file_size,
|
import { IpfsDeploymentInfo } from "./Components/IpfsDeploymentInfo";
|
||||||
remove_service,
|
import { SizeCalcForm } from "./Components/SizeCalcForm";
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [client, setClient] = useState<FluenceClient | null>(null);
|
const isConnected = useRecoilValue(isConnectedState);
|
||||||
const [serviceId, setServiceId] = useState<string | null>(null);
|
const gotRpcAddr = useRecoilValue(gotRpcAddrState);
|
||||||
|
const isDeployed = useRecoilValue(isDeployedState);
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
"isConnected gotRpcAddr deployed\n",
|
"isConnected gotRpcAddr deployed\n",
|
||||||
isConnected,
|
isConnected,
|
||||||
gotRpcAddr,
|
gotRpcAddr,
|
||||||
deployed
|
isDeployed
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -147,33 +29,13 @@ function App() {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="content">
|
<div className="content">
|
||||||
{!isConnected && <ConnectionForm connect={connect} />}
|
{!isConnected && <ConnectionForm />}
|
||||||
{isConnected && <Connected client={client!} />}
|
{isConnected && <ConnectedInfo />}
|
||||||
{isConnected && gotRpcAddr && !deployed && (
|
{isConnected && gotRpcAddr && !isDeployed && <IpfsForm />}
|
||||||
<IpfsForm
|
{isDeployed && (
|
||||||
rpcAddr={rpcAddr}
|
|
||||||
setRpcAddr={setRpcAddr}
|
|
||||||
wasm={wasm}
|
|
||||||
setWasm={setWasm}
|
|
||||||
deployService={deployService}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{deployed && (
|
|
||||||
<>
|
<>
|
||||||
<IfsDeployed
|
<IpfsDeploymentInfo />
|
||||||
wasm={wasm}
|
<SizeCalcForm />
|
||||||
serviceId={serviceId}
|
|
||||||
removeService={removeService}
|
|
||||||
/>
|
|
||||||
<Deployed
|
|
||||||
setRpcAddr={setRpcAddr}
|
|
||||||
rpcAddr={rpcAddr}
|
|
||||||
setFileCID={setFileCID}
|
|
||||||
fileCID={fileCID}
|
|
||||||
fileSize={fileSize}
|
|
||||||
fileSizeCID={fileSizeCID}
|
|
||||||
getFileSize={getFileSize}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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;
|
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 React from "react";
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from "react-dom";
|
||||||
import './index.css';
|
import "./index.css";
|
||||||
import App from './App';
|
import App from "./App";
|
||||||
|
import { RecoilRoot } from "recoil";
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
<RecoilRoot>
|
||||||
<App />
|
<App />
|
||||||
|
</RecoilRoot>
|
||||||
</React.StrictMode>,
|
</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