Getting started intro examples (#13)
23
aqua-examples/getting-started-browser/.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
147
aqua-examples/getting-started-browser/README.md
Normal file
@ -0,0 +1,147 @@
|
||||
# Getting Started with Fluence
|
||||
|
||||
This sample project demonstrates how fluence network can be accessed from the browser. As an example it retrieves the timestamp of the current time from the relay node. The project is based on an create-react-app template with slight modifications to integrate Fluence. The primary focus is the integration itself, i.e React could be swapped with a framework of your choice.
|
||||
|
||||
## Getting started
|
||||
|
||||
Run aqua compiler in watch mode:
|
||||
|
||||
```bash
|
||||
npm run watch-aqua
|
||||
```
|
||||
|
||||
Start the application
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
The browser window with `localhost:3000` should open
|
||||
|
||||
## How it works
|
||||
|
||||
The application can be split into two main building blocks: the runtime provided by the `@fluencelabs/fluence` package and the compiler for the `Aqua` language. The workflow is as follows:
|
||||
|
||||
1. You write aqua code
|
||||
2. Aqua gets compiled into the typescript file
|
||||
3. The typescript is build by the webpack (or any other tool of you choice) into js bunlde.
|
||||
|
||||
## Project structure
|
||||
|
||||
```
|
||||
aqua (1)
|
||||
┗ getting-started.aqua (3)
|
||||
node_modules
|
||||
public
|
||||
src
|
||||
┣ _aqua (2)
|
||||
┃ ┗ getting-started.ts (4)
|
||||
┣ App.scss
|
||||
┣ App.tsx
|
||||
┣ index.css
|
||||
┣ index.tsx
|
||||
┣ logo.svg
|
||||
┗ react-app-env.d.ts
|
||||
package-lock.json
|
||||
package.json
|
||||
tsconfig.json
|
||||
```
|
||||
|
||||
The project structure is based on the create-react-app template with some minor differences:
|
||||
|
||||
* `aqua` (1) contains the Aqua source code files. The complier picks them up and generate corresponding typescript file. See `getting-started.aqua` (3) and `getting-started.ts` respectively
|
||||
* `src/_aqua` (2) is where the generated target files are places. The target directory is conveniently placed inside the sources directory which makes it easy to import typescript functions from the application source code
|
||||
|
||||
## npm packages and scripts
|
||||
|
||||
The following npm packages are used:
|
||||
|
||||
* `@fluencelabs/fluence` - is the client for Fluence Network running inside the browser. See https://github.com/fluencelabs/fluence-js for additional information
|
||||
* `@fluencelabs/fluence-network-environment` - is the maintained list of Fluence networks and nodes to connect to.
|
||||
* `@fluencelabs/aqua-cli` - is the command line interface for Aqua compiler. See https://github.com/fluencelabs/aqua for more information
|
||||
* `@fluencelabs/aqua-lib` - Aqua language standard library
|
||||
* `chokidar-cli` - A tool to watch for aqua file changes and compile them on the fly
|
||||
|
||||
The compilation of aqua code is implemented with these scripts:
|
||||
|
||||
```
|
||||
scripts: {
|
||||
...
|
||||
"compile-aqua": "aqua-cli -i ./aqua/ -o ./src/_aqua",
|
||||
"watch-aqua": "chokidar \"**/*.aqua\" -c \"npm run compile-aqua\""
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
The interface is pretty straightforward: you just specify the input and output directories for the compiler.
|
||||
|
||||
## Aqua code
|
||||
|
||||
```
|
||||
import "@fluencelabs/aqua-lib/builtin.aqua"
|
||||
|
||||
func getRelayTime(relayPeerId: PeerId) -> u64: (1)
|
||||
on relayPeerId: (2)
|
||||
ts <- Peer.timestamp_ms() (3)
|
||||
<- ts (4)
|
||||
|
||||
```
|
||||
|
||||
The code above defines a function which retrieves the current timestamp from the relay node. The function works as following:
|
||||
|
||||
1. The function definition, specifying arguments and return value types
|
||||
2. Shift the execution to the peer with id equal to `relayPeerId`
|
||||
3. Calls built-in function on the current peer and stores the result into a variable
|
||||
4. Returns the result
|
||||
|
||||
The function gets compiled into typescript and can be called from the application code (see next section)
|
||||
|
||||
## Application code
|
||||
|
||||
Let's take a look at how we can use Fluence from typecript.
|
||||
|
||||
First, we need to import the relevant packages:
|
||||
|
||||
```typescript
|
||||
import { createClient, FluenceClient } from "@fluencelabs/fluence";
|
||||
import { krasnodar } from "@fluencelabs/fluence-network-environment";
|
||||
import { getRelayTime } from "./_aqua/getting-started";
|
||||
```
|
||||
|
||||
Please notice that the function defined in Aqua has been compiled into typescript and can be directly imported. Using the code generated by the compiler is as easy as calling a function. The compiler generates all the boilerplate needed to send a particle into the network and wraps it into a single call. Note that all the type information and therefore type checking and code completion facilities are there!
|
||||
|
||||
Next we initialize the client:
|
||||
|
||||
```typescript
|
||||
const relayNode = krasnodar[0];
|
||||
|
||||
function App() {
|
||||
const [client, setClient] = useState<FluenceClient | null>(null);
|
||||
|
||||
...
|
||||
|
||||
useEffect(() => {
|
||||
createClient(relayNode)
|
||||
.then((client) => setClient(client))
|
||||
.catch((err) => console.log("Client initialization failed", err));
|
||||
}, [client]);
|
||||
```
|
||||
|
||||
Every peer running in the browser must connect to the network through a relay node. We use the first node of the krasnodar network there. In our example we store the client using React `useState` facilities. Feel free to store wherever you store other application state.
|
||||
|
||||
Executing Aqua is as easy as calling a function in typesctipt:
|
||||
|
||||
```typescript
|
||||
const doGetRelayTime = async () => {
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
|
||||
const time = await getRelayTime(client, relayNode.peerId);
|
||||
setRelayTime(new Date(time));
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,6 @@
|
||||
import "@fluencelabs/aqua-lib/builtin.aqua"
|
||||
|
||||
func getRelayTime(relayPeerId: PeerId) -> u64:
|
||||
on relayPeerId:
|
||||
ts <- Peer.timestamp_ms()
|
||||
<- ts
|
19834
aqua-examples/getting-started-browser/package-lock.json
generated
Normal file
53
aqua-examples/getting-started-browser/package.json
Normal file
@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "getting-started-browser",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@fluencelabs/fluence": "0.9.53",
|
||||
"@fluencelabs/fluence-network-environment": "1.0.10",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^11.2.7",
|
||||
"@testing-library/user-event": "^12.8.3",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@types/node": "^12.20.16",
|
||||
"@types/react": "^17.0.14",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-scripts": "4.0.3",
|
||||
"typescript": "^4.3.5",
|
||||
"web-vitals": "^1.1.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"compile-aqua": "aqua-cli -i ./aqua/ -o ./src/_aqua",
|
||||
"watch-aqua": "chokidar \"**/*.aqua\" -c \"npm run compile-aqua\""
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fluencelabs/aqua-cli": "0.1.9-162",
|
||||
"@fluencelabs/aqua-lib": "0.1.9",
|
||||
"chokidar-cli": "^2.1.0",
|
||||
"node-sass": "^6.0.1"
|
||||
}
|
||||
}
|
BIN
aqua-examples/getting-started-browser/public/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
42
aqua-examples/getting-started-browser/public/index.html
Normal file
@ -0,0 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Fluence getting started</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
|
||||
</html>
|
BIN
aqua-examples/getting-started-browser/public/logo192.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
aqua-examples/getting-started-browser/public/logo512.png
Normal file
After Width: | Height: | Size: 14 KiB |
25
aqua-examples/getting-started-browser/public/manifest.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
3
aqua-examples/getting-started-browser/public/robots.txt
Normal file
@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
63
aqua-examples/getting-started-browser/src/App.scss
Normal file
@ -0,0 +1,63 @@
|
||||
$color1: black;
|
||||
$color2: rgb(214, 214, 214);
|
||||
$accent-color: rgb(225, 30, 90);
|
||||
|
||||
.logo {
|
||||
height: 15vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.mono {
|
||||
font-family: monospace, monospace;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
header {
|
||||
margin-top: 10vmin;
|
||||
}
|
||||
|
||||
header,
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 800px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.btn {
|
||||
height: 26px;
|
||||
border: 1px solid;
|
||||
border-color: $color2;
|
||||
|
||||
background-color: transparent;
|
||||
|
||||
margin: 5px;
|
||||
|
||||
font-size: 16px;
|
||||
|
||||
color: $color1;
|
||||
|
||||
&::placeholder {
|
||||
color: $color2;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
outline: 1px solid white;
|
||||
border-color: $accent-color;
|
||||
color: $accent-color;
|
||||
}
|
||||
}
|
54
aqua-examples/getting-started-browser/src/App.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import logo from "./logo.svg";
|
||||
import "./App.scss";
|
||||
|
||||
import { createClient, FluenceClient } from "@fluencelabs/fluence";
|
||||
import { krasnodar } from "@fluencelabs/fluence-network-environment";
|
||||
import { getRelayTime } from "./_aqua/getting-started";
|
||||
|
||||
const relayNode = krasnodar[0];
|
||||
|
||||
function App() {
|
||||
const [client, setClient] = useState<FluenceClient | null>(null);
|
||||
const [relayTime, setRelayTime] = useState<Date | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
createClient(relayNode)
|
||||
.then((client) => setClient(client))
|
||||
.catch((err) => console.log("Client initialization failed", err));
|
||||
}, [client]);
|
||||
|
||||
const doGetRelayTime = async () => {
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
|
||||
const time = await getRelayTime(client, relayNode.peerId);
|
||||
setRelayTime(new Date(time));
|
||||
};
|
||||
|
||||
const isConnected = client !== null;
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<header>
|
||||
<img src={logo} className="logo" alt="logo" />
|
||||
</header>
|
||||
|
||||
<div className="content">
|
||||
<h1>Status: {isConnected ? "Connected" : "Disconnected"}</h1>
|
||||
<button className="btn" onClick={doGetRelayTime}>
|
||||
Get relay time
|
||||
</button>
|
||||
{relayTime && (
|
||||
<>
|
||||
<h2>Relay time:</h2>
|
||||
<div>{relayTime?.toLocaleString() || ""}</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
@ -0,0 +1,81 @@
|
||||
/**
|
||||
*
|
||||
* This file is auto-generated. Do not edit manually: changes may be erased.
|
||||
* Generated by Aqua compiler: https://github.com/fluencelabs/aqua/.
|
||||
* If you find any bugs, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues
|
||||
* Aqua version: 0.1.9-162
|
||||
*
|
||||
*/
|
||||
import { FluenceClient, PeerIdB58 } from '@fluencelabs/fluence';
|
||||
import { RequestFlowBuilder } from '@fluencelabs/fluence/dist/api.unstable';
|
||||
import { RequestFlow } from '@fluencelabs/fluence/dist/internal/RequestFlow';
|
||||
|
||||
|
||||
|
||||
export async function getRelayTime(client: FluenceClient, relayPeerId: string, config?: {ttl?: number}): Promise<number> {
|
||||
let request: RequestFlow;
|
||||
const promise = new Promise<number>((resolve, reject) => {
|
||||
const r = new RequestFlowBuilder()
|
||||
.disableInjections()
|
||||
.withRawScript(
|
||||
`
|
||||
(xor
|
||||
(seq
|
||||
(seq
|
||||
(seq
|
||||
(seq
|
||||
(seq
|
||||
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
|
||||
(call %init_peer_id% ("getDataSrv" "relayPeerId") [] relayPeerId)
|
||||
)
|
||||
(call -relay- ("op" "noop") [])
|
||||
)
|
||||
(xor
|
||||
(call relayPeerId ("peer" "timestamp_ms") [] ts)
|
||||
(seq
|
||||
(call -relay- ("op" "noop") [])
|
||||
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 1])
|
||||
)
|
||||
)
|
||||
)
|
||||
(call -relay- ("op" "noop") [])
|
||||
)
|
||||
(xor
|
||||
(call %init_peer_id% ("callbackSrv" "response") [ts])
|
||||
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 2])
|
||||
)
|
||||
)
|
||||
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 3])
|
||||
)
|
||||
|
||||
`,
|
||||
)
|
||||
.configHandler((h) => {
|
||||
h.on('getDataSrv', '-relay-', () => {
|
||||
return client.relayPeerId!;
|
||||
});
|
||||
h.on('getDataSrv', 'relayPeerId', () => {return relayPeerId;});
|
||||
h.onEvent('callbackSrv', 'response', (args) => {
|
||||
const [res] = args;
|
||||
resolve(res);
|
||||
});
|
||||
|
||||
h.onEvent('errorHandlingSrv', 'error', (args) => {
|
||||
// assuming error is the single argument
|
||||
const [err] = args;
|
||||
reject(err);
|
||||
});
|
||||
})
|
||||
.handleScriptError(reject)
|
||||
.handleTimeout(() => {
|
||||
reject('Request timed out for getRelayTime');
|
||||
})
|
||||
if(config?.ttl) {
|
||||
r.withTTL(config.ttl)
|
||||
}
|
||||
request = r.build();
|
||||
});
|
||||
await client.initiateFlow(request!);
|
||||
return promise;
|
||||
}
|
||||
|
13
aqua-examples/getting-started-browser/src/index.css
Normal file
@ -0,0 +1,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
12
aqua-examples/getting-started-browser/src/index.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
17
aqua-examples/getting-started-browser/src/logo.svg
Normal file
After Width: | Height: | Size: 11 KiB |
1
aqua-examples/getting-started-browser/src/react-app-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="react-scripts" />
|
26
aqua-examples/getting-started-browser/tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
23
intro/1-hello-world/.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
109
intro/1-hello-world/README.md
Normal file
@ -0,0 +1,109 @@
|
||||
# Getting Started with Fluence
|
||||
|
||||
This sample project demonstrates how fluence network can be accessed from the browser. As an example it retrieves the timestamp of the current time from the relay node. The project is based on an create-react-app template with slight modifications to integrate Fluence. The primary focus is the integration itself, i.e React could be swapped with a framework of your choice.
|
||||
|
||||
## Getting started
|
||||
|
||||
Run aqua compiler in watch mode:
|
||||
|
||||
```bash
|
||||
npm run watch-aqua
|
||||
```
|
||||
|
||||
Start the application
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
The browser window with `localhost:3000` should open
|
||||
|
||||
## How it works
|
||||
|
||||
The application can be split into two main building blocks: the runtime provided by the `@fluencelabs/fluence` package and the compiler for the `Aqua` language. The workflow is as follows:
|
||||
|
||||
1. You write aqua code
|
||||
2. Aqua gets compiled into the typescript file
|
||||
3. The typescript is build by the webpack (or any other tool of you choice) into js bunlde.
|
||||
|
||||
## Project structure
|
||||
|
||||
```
|
||||
aqua (1)
|
||||
┗ getting-started.aqua (3)
|
||||
node_modules
|
||||
public
|
||||
src
|
||||
┣ _aqua (2)
|
||||
┃ ┗ getting-started.ts (4)
|
||||
┣ App.scss
|
||||
┣ App.tsx
|
||||
┣ index.css
|
||||
┣ index.tsx
|
||||
┣ logo.svg
|
||||
┗ react-app-env.d.ts
|
||||
package-lock.json
|
||||
package.json
|
||||
tsconfig.json
|
||||
```
|
||||
|
||||
The project structure is based on the create-react-app template with some minor differences:
|
||||
|
||||
- `aqua` (1) contains the Aqua source code files. The complier picks them up and generate corresponding typescript file. See `getting-started.aqua` (3) and `getting-started.ts` respectively
|
||||
- `src/_aqua` (2) is where the generated target files are places. The target directory is conveniently placed inside the sources directory which makes it easy to import typescript functions from the application source code
|
||||
|
||||
## npm packages and scripts
|
||||
|
||||
The following npm packages are used:
|
||||
|
||||
- `@fluencelabs/fluence` - is the client for Fluence Network running inside the browser. See https://github.com/fluencelabs/fluence-js for additional information
|
||||
- `@fluencelabs/fluence-network-environment` - is the maintained list of Fluence networks and nodes to connect to.
|
||||
- `@fluencelabs/aqua-cli` - is the command line interface for Aqua compiler. See https://github.com/fluencelabs/aqua for more information
|
||||
- `@fluencelabs/aqua-lib` - Aqua language standard library
|
||||
- `chokidar-cli` - A tool to watch for aqua file changes and compile them on the fly
|
||||
|
||||
The compilation of aqua code is implemented with these scripts:
|
||||
|
||||
```
|
||||
scripts: {
|
||||
...
|
||||
"compile-aqua": "aqua-cli -i ./aqua/ -o ./src/_aqua",
|
||||
"watch-aqua": "chokidar \"**/*.aqua\" -c \"npm run compile-aqua\""
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
The interface is pretty straightforward: you just specify the input and output directories for the compiler.
|
||||
|
||||
## Aqua code
|
||||
|
||||
```
|
||||
import "@fluencelabs/aqua-lib/builtin.aqua"
|
||||
|
||||
service HelloWorld("HelloWorld"): (1)
|
||||
recieveHello(from: PeerId) -> string
|
||||
|
||||
func sayHello(peerId: PeerId, relayPeerId: PeerId) -> string: (2)
|
||||
on peerId via relayPeerId:
|
||||
res <- HelloWorld.recieveHello(%init_peer_id%)
|
||||
<- res
|
||||
|
||||
```
|
||||
|
||||
The code above defines a function which retrieves the current timestamp from the relay node. The function works as following:
|
||||
|
||||
|
||||
|
||||
1. The function definition, specifying arguments and return value types
|
||||
|
||||
2. Shift the execution to the peer with id equal to `relayPeerId`
|
||||
|
||||
3. Calls built-in function on the current peer and stores the result into a variable
|
||||
|
||||
4. Returns the result
|
||||
|
||||
|
||||
|
||||
The function gets compiled into typescript and can be called from the application code (see next section)
|
||||
|
||||
## Application code
|
9
intro/1-hello-world/aqua/getting-started.aqua
Normal file
@ -0,0 +1,9 @@
|
||||
import "@fluencelabs/aqua-lib/builtin.aqua"
|
||||
|
||||
service HelloWorld("HelloWorld"):
|
||||
recieveHello(from: PeerId) -> string
|
||||
|
||||
func sayHello(peerId: PeerId, relayPeerId: PeerId) -> string:
|
||||
on peerId via relayPeerId:
|
||||
res <- HelloWorld.recieveHello(%init_peer_id%)
|
||||
<- res
|
19834
intro/1-hello-world/package-lock.json
generated
Normal file
53
intro/1-hello-world/package.json
Normal file
@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "getting-started-browser",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@fluencelabs/fluence": "0.9.53",
|
||||
"@fluencelabs/fluence-network-environment": "1.0.10",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^11.2.7",
|
||||
"@testing-library/user-event": "^12.8.3",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@types/node": "^12.20.16",
|
||||
"@types/react": "^17.0.14",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-scripts": "4.0.3",
|
||||
"typescript": "^4.3.5",
|
||||
"web-vitals": "^1.1.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"compile-aqua": "aqua-cli -i ./aqua/ -o ./src/_aqua",
|
||||
"watch-aqua": "chokidar \"**/*.aqua\" -c \"npm run compile-aqua\""
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fluencelabs/aqua-cli": "0.1.9-162",
|
||||
"@fluencelabs/aqua-lib": "0.1.9",
|
||||
"chokidar-cli": "^2.1.0",
|
||||
"node-sass": "^6.0.1"
|
||||
}
|
||||
}
|
BIN
intro/1-hello-world/public/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
39
intro/1-hello-world/public/index.html
Normal file
@ -0,0 +1,39 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Fluence getting started</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
BIN
intro/1-hello-world/public/logo192.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
intro/1-hello-world/public/logo512.png
Normal file
After Width: | Height: | Size: 14 KiB |
25
intro/1-hello-world/public/manifest.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
3
intro/1-hello-world/public/robots.txt
Normal file
@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
160
intro/1-hello-world/src/App.scss
Normal file
@ -0,0 +1,160 @@
|
||||
$color1: black;
|
||||
$color2: rgb(214, 214, 214);
|
||||
$accent-color: rgb(225, 30, 90);
|
||||
|
||||
.logo {
|
||||
height: 15vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.mono {
|
||||
font-family: monospace, monospace;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
header {
|
||||
margin-top: 10vmin;
|
||||
}
|
||||
|
||||
header,
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 800px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
ul,
|
||||
li {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.p {
|
||||
width: 550px;
|
||||
}
|
||||
|
||||
.btn-clipboard {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: $accent-color;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
height: 26px;
|
||||
border: 1px solid;
|
||||
border-color: $color2;
|
||||
|
||||
background-color: transparent;
|
||||
|
||||
margin: 5px;
|
||||
|
||||
font-size: 16px;
|
||||
|
||||
color: $color1;
|
||||
|
||||
&::placeholder {
|
||||
color: $color2;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
outline: 1px solid white;
|
||||
border-color: $accent-color;
|
||||
color: $accent-color;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-hello {
|
||||
width: 200px;
|
||||
display: inline;
|
||||
float: right;
|
||||
}
|
||||
|
||||
table {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 70px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 500px;
|
||||
height: 26px;
|
||||
box-sizing: border-box;
|
||||
|
||||
margin: 5px;
|
||||
border: 1px solid;
|
||||
border-color: $color2;
|
||||
|
||||
color: $color1;
|
||||
|
||||
&::placeholder {
|
||||
color: $color2;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
outline: 1px solid white;
|
||||
border-color: $accent-color;
|
||||
}
|
||||
}
|
||||
|
||||
.gg-clipboard {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
display: block;
|
||||
transform: scale(var(--ggs, 1));
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 2px solid;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.gg-clipboard::after,
|
||||
.gg-clipboard::before {
|
||||
content: "";
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
border-radius: 2px;
|
||||
width: 10px;
|
||||
left: 2px;
|
||||
}
|
||||
.gg-clipboard::before {
|
||||
border: 2px solid;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
top: -2px;
|
||||
height: 6px;
|
||||
}
|
||||
.gg-clipboard::after {
|
||||
height: 2px;
|
||||
background: currentColor;
|
||||
box-shadow: 0 -4px 0 0;
|
||||
bottom: 2px;
|
||||
}
|
142
intro/1-hello-world/src/App.tsx
Normal file
@ -0,0 +1,142 @@
|
||||
import React, { useState } from "react";
|
||||
import logo from "./logo.svg";
|
||||
import "./App.scss";
|
||||
|
||||
import { createClient, FluenceClient } from "@fluencelabs/fluence";
|
||||
import { krasnodar } from "@fluencelabs/fluence-network-environment";
|
||||
import { sayHello } from "./_aqua/getting-started";
|
||||
|
||||
const relayNodes = [krasnodar[0], krasnodar[1], krasnodar[2]];
|
||||
|
||||
const copyToClipboard = (text: string) => {
|
||||
navigator.clipboard.writeText(text);
|
||||
};
|
||||
|
||||
function App() {
|
||||
const [client, setClient] = useState<FluenceClient | null>(null);
|
||||
const [helloFrom, setHelloFrom] = useState<string | null>(null);
|
||||
|
||||
const [peerIdInput, setPeerIdInput] = useState<string>("");
|
||||
const [relayPeerIdInput, setRelayPeerIdInput] = useState<string>("");
|
||||
|
||||
const isConnected = client !== null;
|
||||
|
||||
const connect = (relayPeerId: string) => {
|
||||
createClient(relayPeerId)
|
||||
.then((client) => {
|
||||
// Register handler for this call in aqua:
|
||||
// HelloWorld.recieveHello(%init_peer_id%)
|
||||
client.callServiceHandler.on("HelloWorld", "recieveHello", (args) => {
|
||||
const [from] = args;
|
||||
setHelloFrom("Hello from: \n" + from);
|
||||
return "Hello back to you, \n" + from;
|
||||
});
|
||||
setClient(client);
|
||||
})
|
||||
.catch((err) => console.log("Client initialization failed", err));
|
||||
};
|
||||
|
||||
const doSayHello = async () => {
|
||||
if (client === null) {
|
||||
return;
|
||||
}
|
||||
const res = await sayHello(client!, peerIdInput, relayPeerIdInput);
|
||||
setHelloFrom(res);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<header>
|
||||
<img src={logo} className="logo" alt="logo" />
|
||||
</header>
|
||||
|
||||
<div className="content">
|
||||
{isConnected ? (
|
||||
<>
|
||||
<h1>Connected</h1>
|
||||
<table>
|
||||
<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>
|
||||
</table>
|
||||
|
||||
<div>
|
||||
<h2>Say hello!</h2>
|
||||
<p className="p">
|
||||
Now try opening a new tab with the same application. Copy paste
|
||||
the peer id and relay from the second tab and say hello!
|
||||
</p>
|
||||
<div className="row">
|
||||
<label className="label bold">Peer id</label>
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
onChange={(e) => setPeerIdInput(e.target.value)}
|
||||
value={peerIdInput}
|
||||
/>
|
||||
</div>
|
||||
<div className="row">
|
||||
<label className="label bold">Relay</label>
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
onChange={(e) => setRelayPeerIdInput(e.target.value)}
|
||||
value={relayPeerIdInput}
|
||||
/>
|
||||
</div>
|
||||
<div className="row">
|
||||
<button className="btn btn-hello" onClick={doSayHello}>
|
||||
say hello
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<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>
|
||||
</>
|
||||
)}
|
||||
|
||||
{helloFrom && (
|
||||
<>
|
||||
<h2>Hello from</h2>
|
||||
<div> {helloFrom} </div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
97
intro/1-hello-world/src/_aqua/getting-started.ts
Normal file
@ -0,0 +1,97 @@
|
||||
/**
|
||||
*
|
||||
* This file is auto-generated. Do not edit manually: changes may be erased.
|
||||
* Generated by Aqua compiler: https://github.com/fluencelabs/aqua/.
|
||||
* If you find any bugs, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues
|
||||
* Aqua version: 0.1.9-162
|
||||
*
|
||||
*/
|
||||
import { FluenceClient, PeerIdB58 } from '@fluencelabs/fluence';
|
||||
import { RequestFlowBuilder } from '@fluencelabs/fluence/dist/api.unstable';
|
||||
import { RequestFlow } from '@fluencelabs/fluence/dist/internal/RequestFlow';
|
||||
|
||||
|
||||
|
||||
export async function sayHello(client: FluenceClient, peerId: string, relayPeerId: string, config?: {ttl?: number}): Promise<string> {
|
||||
let request: RequestFlow;
|
||||
const promise = new Promise<string>((resolve, reject) => {
|
||||
const r = new RequestFlowBuilder()
|
||||
.disableInjections()
|
||||
.withRawScript(
|
||||
`
|
||||
(xor
|
||||
(seq
|
||||
(seq
|
||||
(seq
|
||||
(seq
|
||||
(seq
|
||||
(seq
|
||||
(seq
|
||||
(seq
|
||||
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
|
||||
(call %init_peer_id% ("getDataSrv" "peerId") [] peerId)
|
||||
)
|
||||
(call %init_peer_id% ("getDataSrv" "relayPeerId") [] relayPeerId)
|
||||
)
|
||||
(call -relay- ("op" "noop") [])
|
||||
)
|
||||
(call relayPeerId ("op" "noop") [])
|
||||
)
|
||||
(xor
|
||||
(call peerId ("HelloWorld" "recieveHello") [%init_peer_id%] res)
|
||||
(seq
|
||||
(seq
|
||||
(seq
|
||||
(call relayPeerId ("op" "noop") [])
|
||||
(call -relay- ("op" "noop") [])
|
||||
)
|
||||
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 1])
|
||||
)
|
||||
(call -relay- ("op" "noop") [])
|
||||
)
|
||||
)
|
||||
)
|
||||
(call relayPeerId ("op" "noop") [])
|
||||
)
|
||||
(call -relay- ("op" "noop") [])
|
||||
)
|
||||
(xor
|
||||
(call %init_peer_id% ("callbackSrv" "response") [res])
|
||||
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 2])
|
||||
)
|
||||
)
|
||||
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 3])
|
||||
)
|
||||
|
||||
`,
|
||||
)
|
||||
.configHandler((h) => {
|
||||
h.on('getDataSrv', '-relay-', () => {
|
||||
return client.relayPeerId!;
|
||||
});
|
||||
h.on('getDataSrv', 'peerId', () => {return peerId;});
|
||||
h.on('getDataSrv', 'relayPeerId', () => {return relayPeerId;});
|
||||
h.onEvent('callbackSrv', 'response', (args) => {
|
||||
const [res] = args;
|
||||
resolve(res);
|
||||
});
|
||||
|
||||
h.onEvent('errorHandlingSrv', 'error', (args) => {
|
||||
// assuming error is the single argument
|
||||
const [err] = args;
|
||||
reject(err);
|
||||
});
|
||||
})
|
||||
.handleScriptError(reject)
|
||||
.handleTimeout(() => {
|
||||
reject('Request timed out for sayHello');
|
||||
})
|
||||
if(config?.ttl) {
|
||||
r.withTTL(config.ttl)
|
||||
}
|
||||
request = r.build();
|
||||
});
|
||||
await client.initiateFlow(request!);
|
||||
return promise;
|
||||
}
|
||||
|
13
intro/1-hello-world/src/index.css
Normal file
@ -0,0 +1,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
12
intro/1-hello-world/src/index.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
17
intro/1-hello-world/src/logo.svg
Normal file
After Width: | Height: | Size: 11 KiB |
1
intro/1-hello-world/src/react-app-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="react-scripts" />
|
26
intro/1-hello-world/tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
7
intro/2-marine/.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
.DS_Store
|
||||
.repl_history
|
||||
/target
|
||||
**/**.bak
|
||||
**/**.bk
|
||||
/artifacts
|
||||
keypair.json
|
17
intro/2-marine/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "hello_world_compute"
|
||||
version = "0.1.0"
|
||||
authors = ["Fluence Labs"]
|
||||
description = "Hello world string computation"
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
name = "hello_world_compute"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
marine-rs-sdk = "0.6.10"
|
||||
|
||||
[dev-dependencies]
|
||||
marine-rs-sdk-test = "0.1.9"
|
6
intro/2-marine/Config.toml
Normal file
@ -0,0 +1,6 @@
|
||||
modules_dir = "artifacts/"
|
||||
|
||||
[[module]]
|
||||
name = "hello_world_compute"
|
||||
mem_pages_count = 1
|
||||
logger_enabled = false
|
18
intro/2-marine/app_config.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"services": {
|
||||
"hello_world_compute": {
|
||||
"dependencies": ["hello_world_compute"],
|
||||
"node": "12D3KooWHLxVhUQyAuZe6AHMB29P7wkvTNMn7eDMcsqimJYLKREf"
|
||||
}
|
||||
},
|
||||
"modules": {
|
||||
"hello_world_compute": {
|
||||
"file": "artifacts/hello_world_compute.wasm",
|
||||
"config": {
|
||||
"preopened_files": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"scripts": {},
|
||||
"script_storage": {}
|
||||
}
|
26
intro/2-marine/app_config_res.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"services": {
|
||||
"hello_world_compute": {
|
||||
"dependencies": [
|
||||
"hello_world_compute"
|
||||
],
|
||||
"node": "12D3KooWHLxVhUQyAuZe6AHMB29P7wkvTNMn7eDMcsqimJYLKREf",
|
||||
"hashDependencies": [
|
||||
"hash:65267bf45b564992ceec5a5f5c36eb6f17dbbb9f0a452419ffa049d2457eeb89"
|
||||
],
|
||||
"blueprint_id": "81e837fc93b970155b5c2d44e2a18d79ca88a9917db9797a271a342c0ed6516e",
|
||||
"id": "ba24be5b-9789-48ac-b38a-82c9d3eb0d34"
|
||||
}
|
||||
},
|
||||
"modules": {
|
||||
"hello_world_compute": {
|
||||
"file": "artifacts/hello_world_compute.wasm",
|
||||
"config": {
|
||||
"preopened_files": []
|
||||
},
|
||||
"hash": "65267bf45b564992ceec5a5f5c36eb6f17dbbb9f0a452419ffa049d2457eeb89"
|
||||
}
|
||||
},
|
||||
"scripts": {},
|
||||
"script_storage": {}
|
||||
}
|
10
intro/2-marine/build.sh
Normal file
@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
set -o errexit -o nounset -o pipefail
|
||||
|
||||
# This script builds all subprojects and puts all created Wasm modules in one dir
|
||||
cargo update
|
||||
marine build --release
|
||||
|
||||
mkdir -p artifacts
|
||||
rm -f artifacts/*.wasm
|
||||
cp target/wasm32-wasi/release/hello_world_compute.wasm artifacts/
|
53
intro/2-marine/src/main.rs
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2021 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use marine_rs_sdk::marine;
|
||||
use marine_rs_sdk::module_manifest;
|
||||
|
||||
module_manifest!();
|
||||
|
||||
#[marine]
|
||||
pub struct HelloComputation {
|
||||
pub msg: String,
|
||||
pub reply: String,
|
||||
}
|
||||
|
||||
pub fn main() {}
|
||||
|
||||
#[marine]
|
||||
pub fn hello_world(from: String) -> HelloComputation {
|
||||
HelloComputation {
|
||||
msg: format!("Hello from: \n{}", from),
|
||||
reply: format!("Hello back to you, \n{}", from)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use marine_rs_sdk_test::marine_test;
|
||||
|
||||
#[marine_test(config_path = "../Config.toml", modules_dir = "../artifacts")]
|
||||
fn empty_string() {
|
||||
let actual = greeting.greeting(String::new());
|
||||
assert_eq!(actual, "Hi, ");
|
||||
}
|
||||
|
||||
#[marine_test(config_path = "../Config.toml", modules_dir = "../artifacts")]
|
||||
fn non_empty_string() {
|
||||
let actual = greeting.greeting("name".to_string());
|
||||
assert_eq!(actual, "Hi, name");
|
||||
}
|
||||
}
|
23
intro/3-hello-world-web3/.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
109
intro/3-hello-world-web3/README.md
Normal file
@ -0,0 +1,109 @@
|
||||
# Getting Started with Fluence
|
||||
|
||||
This sample project demonstrates how fluence network can be accessed from the browser. As an example it retrieves the timestamp of the current time from the relay node. The project is based on an create-react-app template with slight modifications to integrate Fluence. The primary focus is the integration itself, i.e React could be swapped with a framework of your choice.
|
||||
|
||||
## Getting started
|
||||
|
||||
Run aqua compiler in watch mode:
|
||||
|
||||
```bash
|
||||
npm run watch-aqua
|
||||
```
|
||||
|
||||
Start the application
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
The browser window with `localhost:3000` should open
|
||||
|
||||
## How it works
|
||||
|
||||
The application can be split into two main building blocks: the runtime provided by the `@fluencelabs/fluence` package and the compiler for the `Aqua` language. The workflow is as follows:
|
||||
|
||||
1. You write aqua code
|
||||
2. Aqua gets compiled into the typescript file
|
||||
3. The typescript is build by the webpack (or any other tool of you choice) into js bunlde.
|
||||
|
||||
## Project structure
|
||||
|
||||
```
|
||||
aqua (1)
|
||||
┗ getting-started.aqua (3)
|
||||
node_modules
|
||||
public
|
||||
src
|
||||
┣ _aqua (2)
|
||||
┃ ┗ getting-started.ts (4)
|
||||
┣ App.scss
|
||||
┣ App.tsx
|
||||
┣ index.css
|
||||
┣ index.tsx
|
||||
┣ logo.svg
|
||||
┗ react-app-env.d.ts
|
||||
package-lock.json
|
||||
package.json
|
||||
tsconfig.json
|
||||
```
|
||||
|
||||
The project structure is based on the create-react-app template with some minor differences:
|
||||
|
||||
- `aqua` (1) contains the Aqua source code files. The complier picks them up and generate corresponding typescript file. See `getting-started.aqua` (3) and `getting-started.ts` respectively
|
||||
- `src/_aqua` (2) is where the generated target files are places. The target directory is conveniently placed inside the sources directory which makes it easy to import typescript functions from the application source code
|
||||
|
||||
## npm packages and scripts
|
||||
|
||||
The following npm packages are used:
|
||||
|
||||
- `@fluencelabs/fluence` - is the client for Fluence Network running inside the browser. See https://github.com/fluencelabs/fluence-js for additional information
|
||||
- `@fluencelabs/fluence-network-environment` - is the maintained list of Fluence networks and nodes to connect to.
|
||||
- `@fluencelabs/aqua-cli` - is the command line interface for Aqua compiler. See https://github.com/fluencelabs/aqua for more information
|
||||
- `@fluencelabs/aqua-lib` - Aqua language standard library
|
||||
- `chokidar-cli` - A tool to watch for aqua file changes and compile them on the fly
|
||||
|
||||
The compilation of aqua code is implemented with these scripts:
|
||||
|
||||
```
|
||||
scripts: {
|
||||
...
|
||||
"compile-aqua": "aqua-cli -i ./aqua/ -o ./src/_aqua",
|
||||
"watch-aqua": "chokidar \"**/*.aqua\" -c \"npm run compile-aqua\""
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
The interface is pretty straightforward: you just specify the input and output directories for the compiler.
|
||||
|
||||
## Aqua code
|
||||
|
||||
```
|
||||
import "@fluencelabs/aqua-lib/builtin.aqua"
|
||||
|
||||
service HelloWorld("HelloWorld"): (1)
|
||||
recieveHello(from: PeerId) -> string
|
||||
|
||||
func sayHello(peerId: PeerId, relayPeerId: PeerId) -> string: (2)
|
||||
on peerId via relayPeerId:
|
||||
res <- HelloWorld.recieveHello(%init_peer_id%)
|
||||
<- res
|
||||
|
||||
```
|
||||
|
||||
The code above defines a function which retrieves the current timestamp from the relay node. The function works as following:
|
||||
|
||||
|
||||
|
||||
1. The function definition, specifying arguments and return value types
|
||||
|
||||
2. Shift the execution to the peer with id equal to `relayPeerId`
|
||||
|
||||
3. Calls built-in function on the current peer and stores the result into a variable
|
||||
|
||||
4. Returns the result
|
||||
|
||||
|
||||
|
||||
The function gets compiled into typescript and can be called from the application code (see next section)
|
||||
|
||||
## Application code
|
24
intro/3-hello-world-web3/aqua/getting-started.aqua
Normal file
@ -0,0 +1,24 @@
|
||||
import "@fluencelabs/aqua-lib/builtin.aqua"
|
||||
|
||||
const helloServiceNode ?= "12D3KooWHLxVhUQyAuZe6AHMB29P7wkvTNMn7eDMcsqimJYLKREf"
|
||||
const helloServiceId ?= "ba24be5b-9789-48ac-b38a-82c9d3eb0d34"
|
||||
|
||||
data HelloComputation:
|
||||
msg: string
|
||||
reply: string
|
||||
|
||||
service Hello_world_compute:
|
||||
hello_world(from: string) -> HelloComputation
|
||||
|
||||
service HelloWorld("HelloWorld"):
|
||||
recieveHello(from: PeerId) -> string
|
||||
|
||||
func sayHello(peerId: PeerId, relayPeerId: PeerId) -> string:
|
||||
on helloServiceNode:
|
||||
Hello_world_compute helloServiceId
|
||||
comp <- Hello_world_compute.hello_world(%init_peer_id%)
|
||||
|
||||
co on peerId via relayPeerId:
|
||||
HelloWorld.recieveHello(comp.msg)
|
||||
|
||||
<- comp.reply
|
19834
intro/3-hello-world-web3/package-lock.json
generated
Normal file
53
intro/3-hello-world-web3/package.json
Normal file
@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "getting-started-browser",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@fluencelabs/fluence": "0.9.53",
|
||||
"@fluencelabs/fluence-network-environment": "1.0.10",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^11.2.7",
|
||||
"@testing-library/user-event": "^12.8.3",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@types/node": "^12.20.16",
|
||||
"@types/react": "^17.0.14",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-scripts": "4.0.3",
|
||||
"typescript": "^4.3.5",
|
||||
"web-vitals": "^1.1.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"compile-aqua": "aqua-cli -i ./aqua/ -o ./src/_aqua",
|
||||
"watch-aqua": "chokidar \"**/*.aqua\" -c \"npm run compile-aqua\""
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fluencelabs/aqua-cli": "0.1.9-162",
|
||||
"@fluencelabs/aqua-lib": "0.1.9",
|
||||
"chokidar-cli": "^2.1.0",
|
||||
"node-sass": "^6.0.1"
|
||||
}
|
||||
}
|
BIN
intro/3-hello-world-web3/public/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
39
intro/3-hello-world-web3/public/index.html
Normal file
@ -0,0 +1,39 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Fluence getting started</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
BIN
intro/3-hello-world-web3/public/logo192.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
intro/3-hello-world-web3/public/logo512.png
Normal file
After Width: | Height: | Size: 14 KiB |
25
intro/3-hello-world-web3/public/manifest.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
3
intro/3-hello-world-web3/public/robots.txt
Normal file
@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
160
intro/3-hello-world-web3/src/App.scss
Normal file
@ -0,0 +1,160 @@
|
||||
$color1: black;
|
||||
$color2: rgb(214, 214, 214);
|
||||
$accent-color: rgb(225, 30, 90);
|
||||
|
||||
.logo {
|
||||
height: 15vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.mono {
|
||||
font-family: monospace, monospace;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
header {
|
||||
margin-top: 10vmin;
|
||||
}
|
||||
|
||||
header,
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 800px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
ul,
|
||||
li {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.p {
|
||||
width: 550px;
|
||||
}
|
||||
|
||||
.btn-clipboard {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: $accent-color;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
height: 26px;
|
||||
border: 1px solid;
|
||||
border-color: $color2;
|
||||
|
||||
background-color: transparent;
|
||||
|
||||
margin: 5px;
|
||||
|
||||
font-size: 16px;
|
||||
|
||||
color: $color1;
|
||||
|
||||
&::placeholder {
|
||||
color: $color2;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
outline: 1px solid white;
|
||||
border-color: $accent-color;
|
||||
color: $accent-color;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-hello {
|
||||
width: 200px;
|
||||
display: inline;
|
||||
float: right;
|
||||
}
|
||||
|
||||
table {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 70px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 500px;
|
||||
height: 26px;
|
||||
box-sizing: border-box;
|
||||
|
||||
margin: 5px;
|
||||
border: 1px solid;
|
||||
border-color: $color2;
|
||||
|
||||
color: $color1;
|
||||
|
||||
&::placeholder {
|
||||
color: $color2;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
outline: 1px solid white;
|
||||
border-color: $accent-color;
|
||||
}
|
||||
}
|
||||
|
||||
.gg-clipboard {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
display: block;
|
||||
transform: scale(var(--ggs, 1));
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 2px solid;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.gg-clipboard::after,
|
||||
.gg-clipboard::before {
|
||||
content: "";
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
border-radius: 2px;
|
||||
width: 10px;
|
||||
left: 2px;
|
||||
}
|
||||
.gg-clipboard::before {
|
||||
border: 2px solid;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
top: -2px;
|
||||
height: 6px;
|
||||
}
|
||||
.gg-clipboard::after {
|
||||
height: 2px;
|
||||
background: currentColor;
|
||||
box-shadow: 0 -4px 0 0;
|
||||
bottom: 2px;
|
||||
}
|
145
intro/3-hello-world-web3/src/App.tsx
Normal file
@ -0,0 +1,145 @@
|
||||
import React, { useState } from "react";
|
||||
import logo from "./logo.svg";
|
||||
import "./App.scss";
|
||||
|
||||
import { createClient, FluenceClient } from "@fluencelabs/fluence";
|
||||
import { krasnodar } from "@fluencelabs/fluence-network-environment";
|
||||
import { sayHello } from "./_aqua/getting-started";
|
||||
|
||||
const relayNodes = [krasnodar[0], krasnodar[1], krasnodar[2]];
|
||||
|
||||
const copyToClipboard = (text: string) => {
|
||||
navigator.clipboard.writeText(text);
|
||||
};
|
||||
|
||||
function App() {
|
||||
const [client, setClient] = useState<FluenceClient | null>(null);
|
||||
const [helloFrom, setHelloFrom] = useState<string | null>(null);
|
||||
|
||||
const [peerIdInput, setPeerIdInput] = useState<string>("");
|
||||
const [relayPeerIdInput, setRelayPeerIdInput] = useState<string>("");
|
||||
|
||||
const isConnected = client !== null;
|
||||
|
||||
const connect = (relayPeerId: string) => {
|
||||
createClient(relayPeerId)
|
||||
.then((client) => {
|
||||
// Register handler for this call in aqua:
|
||||
// HelloWorld.recieveHello(%init_peer_id%)
|
||||
client.callServiceHandler.onEvent(
|
||||
"HelloWorld",
|
||||
"recieveHello",
|
||||
(args) => {
|
||||
const [msg] = args;
|
||||
setHelloFrom(msg);
|
||||
}
|
||||
);
|
||||
setClient(client);
|
||||
})
|
||||
.catch((err) => console.log("Client initialization failed", err));
|
||||
};
|
||||
|
||||
const doSayHello = async () => {
|
||||
if (client === null) {
|
||||
return;
|
||||
}
|
||||
const res = await sayHello(client!, peerIdInput, relayPeerIdInput);
|
||||
setHelloFrom(res);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<header>
|
||||
<img src={logo} className="logo" alt="logo" />
|
||||
</header>
|
||||
|
||||
<div className="content">
|
||||
{isConnected ? (
|
||||
<>
|
||||
<h1>Connected</h1>
|
||||
<table>
|
||||
<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>
|
||||
</table>
|
||||
|
||||
<div>
|
||||
<h2>Say hello!</h2>
|
||||
<p className="p">
|
||||
Now try opening a new tab with the same application. Copy paste
|
||||
the peer id and relay from the second tab and say hello!
|
||||
</p>
|
||||
<div className="row">
|
||||
<label className="label bold">Peer id</label>
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
onChange={(e) => setPeerIdInput(e.target.value)}
|
||||
value={peerIdInput}
|
||||
/>
|
||||
</div>
|
||||
<div className="row">
|
||||
<label className="label bold">Relay</label>
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
onChange={(e) => setRelayPeerIdInput(e.target.value)}
|
||||
value={relayPeerIdInput}
|
||||
/>
|
||||
</div>
|
||||
<div className="row">
|
||||
<button className="btn btn-hello" onClick={doSayHello}>
|
||||
say hello
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<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>
|
||||
</>
|
||||
)}
|
||||
|
||||
{helloFrom && (
|
||||
<>
|
||||
<h2>Hello from</h2>
|
||||
<div> {helloFrom} </div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
106
intro/3-hello-world-web3/src/_aqua/getting-started.ts
Normal file
@ -0,0 +1,106 @@
|
||||
/**
|
||||
*
|
||||
* This file is auto-generated. Do not edit manually: changes may be erased.
|
||||
* Generated by Aqua compiler: https://github.com/fluencelabs/aqua/.
|
||||
* If you find any bugs, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues
|
||||
* Aqua version: 0.1.9-162
|
||||
*
|
||||
*/
|
||||
import { FluenceClient, PeerIdB58 } from '@fluencelabs/fluence';
|
||||
import { RequestFlowBuilder } from '@fluencelabs/fluence/dist/api.unstable';
|
||||
import { RequestFlow } from '@fluencelabs/fluence/dist/internal/RequestFlow';
|
||||
|
||||
|
||||
|
||||
export async function sayHello(client: FluenceClient, peerId: string, relayPeerId: string, config?: {ttl?: number}): Promise<string> {
|
||||
let request: RequestFlow;
|
||||
const promise = new Promise<string>((resolve, reject) => {
|
||||
const r = new RequestFlowBuilder()
|
||||
.disableInjections()
|
||||
.withRawScript(
|
||||
`
|
||||
(xor
|
||||
(seq
|
||||
(seq
|
||||
(seq
|
||||
(seq
|
||||
(seq
|
||||
(seq
|
||||
(seq
|
||||
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
|
||||
(call %init_peer_id% ("getDataSrv" "peerId") [] peerId)
|
||||
)
|
||||
(call %init_peer_id% ("getDataSrv" "relayPeerId") [] relayPeerId)
|
||||
)
|
||||
(call -relay- ("op" "noop") [])
|
||||
)
|
||||
(xor
|
||||
(seq
|
||||
(call -relay- ("op" "noop") [])
|
||||
(call "12D3KooWHLxVhUQyAuZe6AHMB29P7wkvTNMn7eDMcsqimJYLKREf" ("ba24be5b-9789-48ac-b38a-82c9d3eb0d34" "hello_world") [%init_peer_id%] comp)
|
||||
)
|
||||
(seq
|
||||
(call -relay- ("op" "noop") [])
|
||||
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 1])
|
||||
)
|
||||
)
|
||||
)
|
||||
(call -relay- ("op" "noop") [])
|
||||
)
|
||||
(par
|
||||
(seq
|
||||
(call relayPeerId ("op" "noop") [])
|
||||
(xor
|
||||
(call peerId ("HelloWorld" "recieveHello") [comp.$.msg!])
|
||||
(seq
|
||||
(seq
|
||||
(call relayPeerId ("op" "noop") [])
|
||||
(call -relay- ("op" "noop") [])
|
||||
)
|
||||
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 2])
|
||||
)
|
||||
)
|
||||
)
|
||||
(null)
|
||||
)
|
||||
)
|
||||
(xor
|
||||
(call %init_peer_id% ("callbackSrv" "response") [comp.$.reply!])
|
||||
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 3])
|
||||
)
|
||||
)
|
||||
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 4])
|
||||
)
|
||||
|
||||
`,
|
||||
)
|
||||
.configHandler((h) => {
|
||||
h.on('getDataSrv', '-relay-', () => {
|
||||
return client.relayPeerId!;
|
||||
});
|
||||
h.on('getDataSrv', 'peerId', () => {return peerId;});
|
||||
h.on('getDataSrv', 'relayPeerId', () => {return relayPeerId;});
|
||||
h.onEvent('callbackSrv', 'response', (args) => {
|
||||
const [res] = args;
|
||||
resolve(res);
|
||||
});
|
||||
|
||||
h.onEvent('errorHandlingSrv', 'error', (args) => {
|
||||
// assuming error is the single argument
|
||||
const [err] = args;
|
||||
reject(err);
|
||||
});
|
||||
})
|
||||
.handleScriptError(reject)
|
||||
.handleTimeout(() => {
|
||||
reject('Request timed out for sayHello');
|
||||
})
|
||||
if(config?.ttl) {
|
||||
r.withTTL(config.ttl)
|
||||
}
|
||||
request = r.build();
|
||||
});
|
||||
await client.initiateFlow(request!);
|
||||
return promise;
|
||||
}
|
||||
|
13
intro/3-hello-world-web3/src/index.css
Normal file
@ -0,0 +1,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
12
intro/3-hello-world-web3/src/index.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
17
intro/3-hello-world-web3/src/logo.svg
Normal file
After Width: | Height: | Size: 11 KiB |
1
intro/3-hello-world-web3/src/react-app-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="react-scripts" />
|
26
intro/3-hello-world-web3/tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|