mirror of
https://github.com/fluencelabs/fluence-js.git
synced 2024-12-04 18:00:18 +00:00
Update JS SDK API to the new version (#61)
* FluenceClient renamed to FluencePeer. * Using Aqua compiler is now the recommended way for all interaction with the network, including services registration and sending requests * Old API (sendParticle etc) has been removed * Opaque seed format replaced with 32 byte ed25519 private key. KeyPair introduced * Documentation update
This commit is contained in:
parent
c7ab9d56ee
commit
6436cd5684
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,6 +9,7 @@ lerna-debug.log*
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
bundle/
|
||||
docs/
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
|
102
README.md
102
README.md
@ -2,105 +2,17 @@
|
||||
|
||||
[![npm](https://img.shields.io/npm/v/@fluencelabs/fluence)](https://www.npmjs.com/package/@fluencelabs/fluence)
|
||||
|
||||
Official SDK for building web-based applications for Fluence
|
||||
|
||||
## About Fluence
|
||||
|
||||
Fluence is an open application platform where apps can build on each other, share data and users
|
||||
|
||||
| Layer | Tech | Scale | State | Based on |
|
||||
| :-------------------: | :-------------------------------------------------------------------------------------------------------------------------------: | :------------------------------: | :-------------------------------: | :-----------------------------------------------------------------------------------------------------------: |
|
||||
| Execution | [FCE](https://github.com/fluencelabs/fce) | Single peer | Disk, network, external processes | Wasm, [IT](https://github.com/fluencelabs/interface-types), [Wasmer\*](https://github.com/fluencelabs/wasmer) |
|
||||
| Composition | [Aquamarine](https://github.com/fluencelabs/aquamarine) | Involved peers | Results and signatures | ⇅, π-calculus |
|
||||
| Topology | [TrustGraph](https://github.com/fluencelabs/fluence/tree/master/trust-graph), [DHT\*](https://github.com/fluencelabs/rust-libp2p) | Distributed with Kademlia\* algo | Actual state of the network | [libp2p](https://github.com/libp2p/rust-libp2p) |
|
||||
| Security & Accounting | Blockchain | Whole network | Licenses & payments | substrate? |
|
||||
|
||||
<img alt="aquamarine scheme" align="center" src="doc/stack.png"/>
|
||||
|
||||
## Installation
|
||||
|
||||
With npm
|
||||
|
||||
```bash
|
||||
npm install @fluencelabs/fluence
|
||||
```
|
||||
|
||||
With yarn
|
||||
|
||||
```bash
|
||||
yarn add @fluencelabs/fluence
|
||||
```
|
||||
Official SDK providing javascript-based implementation of the Fluence Peer.
|
||||
|
||||
## Getting started
|
||||
|
||||
Pick a node to connect to the Fluence network. The easiest way to do so is by using [fluence-network-environment](https://github.com/fluencelabs/fluence-network-environment) package
|
||||
To start developing applications with JS SDK refer to the official [gitbook page](https://doc.fluence.dev/docs/js-sdk)
|
||||
|
||||
```typescript
|
||||
import { dev } from '@fluencelabs/fluence-network-environment';
|
||||
## Contributing
|
||||
|
||||
export const relayNode = dev[0];
|
||||
```
|
||||
While the project is still in the early stages of development, you are welcome to track progress and contribute. As the project is undergoing rapid changes, interested contributors should contact the team before embarking on larger pieces of work. All contributors should consult with and agree to our [basic contributing rules](CONTRIBUTING.md).
|
||||
|
||||
Initialize client
|
||||
|
||||
```typescript
|
||||
import { createClient, FluenceClient } from '@fluencelabs/fluence';
|
||||
|
||||
const client = await createClient(relayNode);
|
||||
```
|
||||
|
||||
Respond to service function calls
|
||||
|
||||
```typescript
|
||||
subscribeToEvent(client, 'helloService', 'helloFunction', (args) => {
|
||||
const [networkInfo] = args;
|
||||
console.log(networkInfo);
|
||||
});
|
||||
```
|
||||
|
||||
Make a particle
|
||||
|
||||
```typescript
|
||||
const particle = new Particle(
|
||||
`
|
||||
(seq
|
||||
(call myRelay ("peer" "identify") [] result)
|
||||
(call %init_peer_id% ("helloService" "helloFunction") [result])
|
||||
)`,
|
||||
{
|
||||
myRelay: client.relayPeerId,
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
Send it to the network
|
||||
|
||||
```typescript
|
||||
await sendParticle(client, particle);
|
||||
```
|
||||
|
||||
Observe the result in browser console
|
||||
|
||||
```json
|
||||
{
|
||||
"external_addresses": ["/ip4/1.2.3.4/tcp/7777", "/dns4/dev.fluence.dev/tcp/19002"]
|
||||
}
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Guide on building applications: [doc.fluence.dev](https://doc.fluence.dev/docs/tutorials_tutorials/building-a-frontend-with-js-sdk)
|
||||
|
||||
Sample applications:
|
||||
|
||||
- [FluentPad](https://github.com/fluencelabs/fluent-pad): a collaborative text editor with users online status synchronization
|
||||
- [Examples](https://github.com/fluencelabs/examples): examples of using the Aqua programming language
|
||||
|
||||
About [Fluence](https://fluence.network/)
|
||||
|
||||
## Developing
|
||||
|
||||
### Setting up Dev
|
||||
### Setting up dev environment
|
||||
|
||||
Install node packages
|
||||
|
||||
@ -140,10 +52,6 @@ To run all tests
|
||||
npm run test:all
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
While the project is still in the early stages of development, you are welcome to track progress and contribute. As the project is undergoing rapid changes, interested contributors should contact the team before embarking on larger pieces of work. All contributors should consult with and agree to our [basic contributing rules](CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
[Apache 2.0](LICENSE)
|
||||
|
BIN
doc/stack.png
BIN
doc/stack.png
Binary file not shown.
Before Width: | Height: | Size: 83 KiB |
1957
package-lock.json
generated
1957
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@ -13,14 +13,15 @@
|
||||
"test:node:all": "jest --env=node",
|
||||
"test:node:unit": "jest --env=node --testPathPattern=src/__test__/unit",
|
||||
"test:node:integration": "jest --env=node --testPathPattern=src/__test__/integration",
|
||||
"build": "tsc"
|
||||
"build": "tsc",
|
||||
"build:docs": "typedoc --out docs --excludePrivate --hideBreadcrumbs --publicPath js-sdk/6_reference/ src/index.ts"
|
||||
},
|
||||
"repository": "https://github.com/fluencelabs/fluence-js",
|
||||
"author": "Fluence Labs",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@chainsafe/libp2p-noise": "4.0.0",
|
||||
"@fluencelabs/avm": "0.14.3",
|
||||
"@fluencelabs/avm": "0.14.4",
|
||||
"async": "3.2.0",
|
||||
"base64-js": "1.5.1",
|
||||
"bs58": "4.0.1",
|
||||
@ -33,6 +34,7 @@
|
||||
"libp2p-websockets": "0.16.1",
|
||||
"loglevel": "1.7.0",
|
||||
"multiaddr": "10.0.0",
|
||||
"noble-ed25519": "^1.2.5",
|
||||
"peer-id": "0.15.3",
|
||||
"uuid": "8.3.0"
|
||||
},
|
||||
@ -40,6 +42,8 @@
|
||||
"@types/jest": "^26.0.22",
|
||||
"jest": "^26.6.3",
|
||||
"ts-jest": "^26.5.4",
|
||||
"typescript": "^3.9.5"
|
||||
"typedoc": "^0.21.9",
|
||||
"typedoc-plugin-markdown": "^3.10.4",
|
||||
"typescript": "^4.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,150 +0,0 @@
|
||||
import log from 'loglevel';
|
||||
import { Multiaddr } from 'multiaddr';
|
||||
import PeerId, { isPeerId } from 'peer-id';
|
||||
|
||||
import { CallServiceHandler } from './internal/CallServiceHandler';
|
||||
import { ClientImpl } from './internal/ClientImpl';
|
||||
import { PeerIdB58 } from './internal/commonTypes';
|
||||
import { FluenceConnectionOptions } from './internal/FluenceConnection';
|
||||
import { generatePeerId, seedToPeerId } from './internal/peerIdUtils';
|
||||
import { RequestFlow } from './internal/RequestFlow';
|
||||
import { RequestFlowBuilder } from './internal/RequestFlowBuilder';
|
||||
|
||||
/**
|
||||
* The class represents interface to Fluence Platform. To create a client use @see {@link createClient} function.
|
||||
*/
|
||||
export interface FluenceClient {
|
||||
/**
|
||||
* { string } Gets the base58 representation of the current peer id. Read only
|
||||
*/
|
||||
readonly relayPeerId: PeerIdB58 | undefined;
|
||||
|
||||
/**
|
||||
* { string } Gets the base58 representation of the connected relay's peer id. Read only
|
||||
*/
|
||||
readonly selfPeerId: PeerIdB58;
|
||||
|
||||
/**
|
||||
* { string } True if the client is connected to network. False otherwise. Read only
|
||||
*/
|
||||
readonly isConnected: boolean;
|
||||
|
||||
/**
|
||||
* The base handler which is used by every RequestFlow executed by this FluenceClient.
|
||||
* Please note, that the handler is combined with the handler from RequestFlow before the execution occures.
|
||||
* After this combination, middlewares from RequestFlow are executed before client handler's middlewares.
|
||||
*/
|
||||
readonly callServiceHandler: CallServiceHandler;
|
||||
|
||||
/**
|
||||
* Disconnects the client from the network
|
||||
*/
|
||||
disconnect(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Establish a connection to the node. If the connection is already established, disconnect and reregister all services in a new connection.
|
||||
*
|
||||
* @param multiaddr
|
||||
*/
|
||||
connect(multiaddr: string | Multiaddr): Promise<void>;
|
||||
|
||||
/**
|
||||
* Initiates RequestFlow execution @see { @link RequestFlow }
|
||||
* @param { RequestFlow } [ request ] - RequestFlow to start the execution of
|
||||
*/
|
||||
initiateFlow(request: RequestFlow): Promise<void>;
|
||||
}
|
||||
|
||||
type Node = {
|
||||
peerId: string;
|
||||
multiaddr: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a Fluence client. If the `connectTo` is specified connects the client to the network
|
||||
* @param { string | Multiaddr | Node } [connectTo] - Node in Fluence network to connect to. If not specified client will not be connected to the n
|
||||
* @param { PeerId | string } [peerIdOrSeed] - The Peer Id of the created client. Specified either as PeerId structure or as seed string. Will be generated randomly if not specified
|
||||
* @param { FluenceConnectionOptions } [options] - additional configuraton options for Fluence Connection made with the client
|
||||
* @returns { Promise<FluenceClient> } Promise which will be resolved with the created FluenceClient
|
||||
*/
|
||||
export const createClient = async (
|
||||
connectTo?: string | Multiaddr | Node,
|
||||
peerIdOrSeed?: PeerId | string,
|
||||
options?: FluenceConnectionOptions,
|
||||
): Promise<FluenceClient> => {
|
||||
let peerId;
|
||||
if (!peerIdOrSeed) {
|
||||
peerId = await generatePeerId();
|
||||
} else if (isPeerId(peerIdOrSeed)) {
|
||||
// keep unchanged
|
||||
peerId = peerIdOrSeed;
|
||||
} else {
|
||||
// peerId is string, therefore seed
|
||||
peerId = await seedToPeerId(peerIdOrSeed);
|
||||
}
|
||||
|
||||
const client = new ClientImpl(peerId);
|
||||
await client.initAirInterpreter();
|
||||
|
||||
if (connectTo) {
|
||||
let theAddress: Multiaddr;
|
||||
let fromNode = (connectTo as any).multiaddr;
|
||||
if (fromNode) {
|
||||
theAddress = new Multiaddr(fromNode);
|
||||
} else {
|
||||
theAddress = new Multiaddr(connectTo as string);
|
||||
}
|
||||
|
||||
await client.connect(theAddress, options);
|
||||
|
||||
if (options?.skipCheckConnection) {
|
||||
if (!(await checkConnection(client, options.checkConnectionTTL))) {
|
||||
throw new Error(
|
||||
'Connection check failed. Check if the node is working or try to connect to another node',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return client;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks the network connection by sending a ping-like request to relat node
|
||||
* @param { FluenceClient } client - The Fluence Client instance.
|
||||
*/
|
||||
export const checkConnection = async (client: FluenceClient, ttl?: number): Promise<boolean> => {
|
||||
if (!client.isConnected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const msg = Math.random().toString(36).substring(7);
|
||||
const callbackFn = 'checkConnection';
|
||||
const callbackService = '_callback';
|
||||
|
||||
const [request, promise] = new RequestFlowBuilder()
|
||||
.withRawScript(
|
||||
`(seq
|
||||
(call init_relay ("op" "identity") [msg] result)
|
||||
(call %init_peer_id% ("${callbackService}" "${callbackFn}") [result])
|
||||
)`,
|
||||
)
|
||||
.withTTL(ttl)
|
||||
.withVariables({
|
||||
msg,
|
||||
})
|
||||
.buildAsFetch<[string]>(callbackService, callbackFn);
|
||||
|
||||
await client.initiateFlow(request);
|
||||
|
||||
try {
|
||||
const [result] = await promise;
|
||||
if (result != msg) {
|
||||
log.warn("unexpected behavior. 'identity' must return the passed arguments.");
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
log.error('Error on establishing connection: ', e);
|
||||
return false;
|
||||
}
|
||||
};
|
50
src/__test__/integration/avm.spec.ts
Normal file
50
src/__test__/integration/avm.spec.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { FluencePeer } from '../../index';
|
||||
import { RequestFlowBuilder } from '../../internal/RequestFlowBuilder';
|
||||
|
||||
const peer = new FluencePeer();
|
||||
|
||||
describe('Avm spec', () => {
|
||||
afterEach(async () => {
|
||||
if (peer) {
|
||||
await peer.uninit();
|
||||
}
|
||||
});
|
||||
|
||||
it('Par execution should work', async () => {
|
||||
// arrange
|
||||
await peer.init();
|
||||
|
||||
let request;
|
||||
const promise = new Promise<string[]>((resolve) => {
|
||||
let res = [];
|
||||
request = new RequestFlowBuilder()
|
||||
.withRawScript(
|
||||
`
|
||||
(seq
|
||||
(par
|
||||
(call %init_peer_id% ("print" "print") ["1"])
|
||||
(null)
|
||||
)
|
||||
(call %init_peer_id% ("print" "print") ["2"])
|
||||
)
|
||||
`,
|
||||
)
|
||||
.configHandler((h) => {
|
||||
h.onEvent('print', 'print', async (args) => {
|
||||
res.push(args[0]);
|
||||
if (res.length == 2) {
|
||||
resolve(res);
|
||||
}
|
||||
});
|
||||
})
|
||||
.build();
|
||||
});
|
||||
|
||||
// act
|
||||
await peer.internals.initiateFlow(request);
|
||||
const res = await promise;
|
||||
|
||||
// assert
|
||||
expect(res).toStrictEqual(['1', '2']);
|
||||
});
|
||||
});
|
@ -1,122 +0,0 @@
|
||||
import {
|
||||
addBlueprint,
|
||||
addScript,
|
||||
createService,
|
||||
getBlueprints,
|
||||
getInterfaces,
|
||||
getModules,
|
||||
removeScript,
|
||||
uploadModule,
|
||||
} from '../../internal/builtins';
|
||||
import { ModuleConfig } from '../../internal/moduleConfig';
|
||||
import { createClient, FluenceClient } from '../../FluenceClient';
|
||||
import { nodes } from '../connection';
|
||||
|
||||
let client: FluenceClient;
|
||||
|
||||
describe('Builtins usage suite', () => {
|
||||
afterEach(async () => {
|
||||
if (client) {
|
||||
await client.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
jest.setTimeout(10000);
|
||||
|
||||
it('get_modules', async function () {
|
||||
client = await createClient(nodes[0].multiaddr);
|
||||
|
||||
let modulesList = await getModules(client);
|
||||
|
||||
expect(modulesList).not.toBeUndefined;
|
||||
});
|
||||
|
||||
it('get_interfaces', async function () {
|
||||
client = await createClient(nodes[0].multiaddr);
|
||||
|
||||
let interfaces = await getInterfaces(client);
|
||||
|
||||
expect(interfaces).not.toBeUndefined;
|
||||
});
|
||||
|
||||
it('get_blueprints', async function () {
|
||||
client = await createClient(nodes[0].multiaddr);
|
||||
|
||||
let bpList = await getBlueprints(client);
|
||||
|
||||
expect(bpList).not.toBeUndefined;
|
||||
});
|
||||
|
||||
it('upload_modules', async function () {
|
||||
client = await createClient(nodes[0].multiaddr);
|
||||
|
||||
let config: ModuleConfig = {
|
||||
name: 'test_broken_module',
|
||||
mem_pages_count: 100,
|
||||
logger_enabled: true,
|
||||
wasi: {
|
||||
envs: { a: 'b' },
|
||||
preopened_files: ['a', 'b'],
|
||||
mapped_dirs: { c: 'd' },
|
||||
},
|
||||
mounted_binaries: { e: 'f' },
|
||||
};
|
||||
|
||||
let base64 = 'MjNy';
|
||||
|
||||
await uploadModule(client, 'test_broken_module', base64, config, 10000);
|
||||
});
|
||||
|
||||
it('add_blueprint', async function () {
|
||||
client = await createClient(nodes[0].multiaddr);
|
||||
|
||||
let bpId = 'some';
|
||||
|
||||
let bpIdReturned = await addBlueprint(client, 'test_broken_blueprint', ['test_broken_module'], bpId);
|
||||
let allBps = await getBlueprints(client);
|
||||
const allBpIds = allBps.map((x) => x.id);
|
||||
|
||||
expect(allBpIds).toContain(bpIdReturned);
|
||||
});
|
||||
|
||||
it('create broken blueprint', async function () {
|
||||
client = await createClient(nodes[0].multiaddr);
|
||||
|
||||
let promise = createService(client, 'test_broken_blueprint');
|
||||
|
||||
await expect(promise).rejects.toMatchObject({
|
||||
msg: expect.stringContaining("Blueprint 'test_broken_blueprint' wasn't found"),
|
||||
instruction: expect.stringContaining('blueprint_id'),
|
||||
});
|
||||
});
|
||||
|
||||
it('add and remove script', async function () {
|
||||
client = await createClient(nodes[0].multiaddr);
|
||||
|
||||
let script = `
|
||||
(seq
|
||||
(call "${client.relayPeerId}" ("op" "identity") [])
|
||||
(call "${client.selfPeerId}" ("test" "test1") ["1" "2" "3"] result)
|
||||
)
|
||||
`;
|
||||
|
||||
let resMakingPromise = new Promise((resolve) => {
|
||||
client.callServiceHandler.on('test', 'test1', (args, _) => {
|
||||
resolve([...args]);
|
||||
return {};
|
||||
});
|
||||
});
|
||||
|
||||
let scriptId = await addScript(client, script);
|
||||
|
||||
await resMakingPromise
|
||||
.then((args) => {
|
||||
expect(args as string[]).toEqual(['1', '2', '3']);
|
||||
})
|
||||
.finally(() => {
|
||||
removeScript(client, scriptId);
|
||||
});
|
||||
|
||||
expect(scriptId).not.toBeUndefined;
|
||||
});
|
||||
});
|
161
src/__test__/integration/compiler/compiler.spec.ts
Normal file
161
src/__test__/integration/compiler/compiler.spec.ts
Normal file
@ -0,0 +1,161 @@
|
||||
import { FluencePeer } from '../../..';
|
||||
import { RequestFlowBuilder } from '../../../internal/RequestFlowBuilder';
|
||||
import { callMeBack, registerHelloWorld } from './gen1';
|
||||
|
||||
describe('Compiler support infrastructure tests', () => {
|
||||
it('Compiled code for function should work', async () => {
|
||||
// arrange
|
||||
await FluencePeer.default.init();
|
||||
|
||||
// act
|
||||
const res = new Promise((resolve) => {
|
||||
callMeBack((arg0, arg1, params) => {
|
||||
resolve({
|
||||
arg0: arg0,
|
||||
arg1: arg1,
|
||||
arg0Tetraplet: params.tetraplets.arg0[0], // completion should work here
|
||||
arg1Tetraplet: params.tetraplets.arg1[0], // completion should work here
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// assert
|
||||
expect(await res).toMatchObject({
|
||||
arg0: 'hello, world',
|
||||
arg1: 42,
|
||||
|
||||
arg0Tetraplet: {
|
||||
function_name: '',
|
||||
json_path: '',
|
||||
// peer_pk: '12D3KooWMwDDVRPEn5YGrN5LvVFLjNuBmokaeKfpLUgxsSkqRwwv',
|
||||
service_id: '',
|
||||
},
|
||||
|
||||
arg1Tetraplet: {
|
||||
function_name: '',
|
||||
json_path: '',
|
||||
// peer_pk: '12D3KooWMwDDVRPEn5YGrN5LvVFLjNuBmokaeKfpLUgxsSkqRwwv',
|
||||
service_id: '',
|
||||
},
|
||||
});
|
||||
|
||||
await FluencePeer.default.uninit();
|
||||
});
|
||||
|
||||
it('Compiled code for service should work', async () => {
|
||||
// arrange
|
||||
await FluencePeer.default.init();
|
||||
|
||||
// act
|
||||
const helloPromise = new Promise((resolve) => {
|
||||
registerHelloWorld('hello_world', {
|
||||
sayHello: (s, params) => {
|
||||
const tetrapelt = params.tetraplets.s; // completion should work here
|
||||
resolve(s);
|
||||
},
|
||||
getNumber: (params) => {
|
||||
// ctx.tetraplets should be {}
|
||||
return 42;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const [request, getNumberPromise] = new RequestFlowBuilder()
|
||||
.withRawScript(
|
||||
`(seq
|
||||
(seq
|
||||
(call %init_peer_id% ("hello_world" "sayHello") ["hello world!"])
|
||||
(call %init_peer_id% ("hello_world" "getNumber") [] result)
|
||||
)
|
||||
(call %init_peer_id% ("callback" "callback") [result])
|
||||
)`,
|
||||
)
|
||||
.buildAsFetch<[string]>('callback', 'callback');
|
||||
await FluencePeer.default.internals.initiateFlow(request);
|
||||
|
||||
// assert
|
||||
expect(await helloPromise).toBe('hello world!');
|
||||
expect(await getNumberPromise).toStrictEqual([42]);
|
||||
|
||||
await FluencePeer.default.uninit();
|
||||
});
|
||||
|
||||
it('Compiled code for function should work with another peer', async () => {
|
||||
// arrange
|
||||
const peer = new FluencePeer();
|
||||
await peer.init();
|
||||
|
||||
// act
|
||||
const res = new Promise((resolve) => {
|
||||
callMeBack(peer, (arg0, arg1, params) => {
|
||||
resolve({
|
||||
arg0: arg0,
|
||||
arg1: arg1,
|
||||
arg0Tetraplet: params.tetraplets.arg0[0], // completion should work here
|
||||
arg1Tetraplet: params.tetraplets.arg1[0], // completion should work here
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// assert
|
||||
expect(await res).toMatchObject({
|
||||
arg0: 'hello, world',
|
||||
arg1: 42,
|
||||
|
||||
arg0Tetraplet: {
|
||||
function_name: '',
|
||||
json_path: '',
|
||||
// peer_pk: '12D3KooWMwDDVRPEn5YGrN5LvVFLjNuBmokaeKfpLUgxsSkqRwwv',
|
||||
service_id: '',
|
||||
},
|
||||
|
||||
arg1Tetraplet: {
|
||||
function_name: '',
|
||||
json_path: '',
|
||||
// peer_pk: '12D3KooWMwDDVRPEn5YGrN5LvVFLjNuBmokaeKfpLUgxsSkqRwwv',
|
||||
service_id: '',
|
||||
},
|
||||
});
|
||||
|
||||
await peer.uninit();
|
||||
});
|
||||
|
||||
it('Compiled code for service should work another peer', async () => {
|
||||
// arrange
|
||||
const peer = new FluencePeer();
|
||||
await peer.init();
|
||||
|
||||
// act
|
||||
const helloPromise = new Promise((resolve) => {
|
||||
registerHelloWorld(peer, 'hello_world', {
|
||||
sayHello: (s, params) => {
|
||||
const tetrapelt = params.tetraplets.s; // completion should work here
|
||||
resolve(s);
|
||||
},
|
||||
getNumber: (params) => {
|
||||
// ctx.tetraplets should be {}
|
||||
return 42;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const [request, getNumberPromise] = new RequestFlowBuilder()
|
||||
.withRawScript(
|
||||
`(seq
|
||||
(seq
|
||||
(call %init_peer_id% ("hello_world" "sayHello") ["hello world!"])
|
||||
(call %init_peer_id% ("hello_world" "getNumber") [] result)
|
||||
)
|
||||
(call %init_peer_id% ("callback" "callback") [result])
|
||||
)`,
|
||||
)
|
||||
.buildAsFetch<[string]>('callback', 'callback');
|
||||
await peer.internals.initiateFlow(request);
|
||||
|
||||
// assert
|
||||
expect(await helloPromise).toBe('hello world!');
|
||||
expect(await getNumberPromise).toStrictEqual([42]);
|
||||
|
||||
await peer.uninit();
|
||||
});
|
||||
});
|
177
src/__test__/integration/compiler/gen1.ts
Normal file
177
src/__test__/integration/compiler/gen1.ts
Normal file
@ -0,0 +1,177 @@
|
||||
import { ResultCodes, RequestFlow, RequestFlowBuilder, CallParams } from '../../../internal/compilerSupport/v1';
|
||||
import { FluencePeer } from '../../../index';
|
||||
|
||||
/*
|
||||
|
||||
-- file to generate functions below from
|
||||
|
||||
service HelloWorld("default"):
|
||||
sayHello(s: string)
|
||||
getNumber() -> i32
|
||||
|
||||
func callMeBack(callback: string, i32 -> ()):
|
||||
callback("hello, world", 42)
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* 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.2.2-SNAPSHOT
|
||||
*
|
||||
*/
|
||||
|
||||
// Services
|
||||
|
||||
export interface HelloWorldDef {
|
||||
getNumber: (callParams: CallParams<null>) => number;
|
||||
sayHello: (s: string, callParams: CallParams<'s'>) => void;
|
||||
}
|
||||
|
||||
export function registerHelloWorld(service: HelloWorldDef): void;
|
||||
export function registerHelloWorld(serviceId: string, service: HelloWorldDef): void;
|
||||
export function registerHelloWorld(peer: FluencePeer, service: HelloWorldDef): void;
|
||||
export function registerHelloWorld(peer: FluencePeer, serviceId: string, service: HelloWorldDef): void;
|
||||
export function registerHelloWorld(...args) {
|
||||
let peer: FluencePeer;
|
||||
let serviceId;
|
||||
let service;
|
||||
if (args[0] instanceof FluencePeer) {
|
||||
peer = args[0];
|
||||
} else {
|
||||
peer = FluencePeer.default;
|
||||
}
|
||||
|
||||
if (typeof args[0] === 'string') {
|
||||
serviceId = args[0];
|
||||
} else if (typeof args[1] === 'string') {
|
||||
serviceId = args[1];
|
||||
} else {
|
||||
serviceId = 'default';
|
||||
}
|
||||
|
||||
if (!(args[0] instanceof FluencePeer) && typeof args[0] === 'object') {
|
||||
service = args[0];
|
||||
} else if (typeof args[1] === 'object') {
|
||||
service = args[1];
|
||||
} else {
|
||||
service = args[2];
|
||||
}
|
||||
|
||||
peer.internals.callServiceHandler.use((req, resp, next) => {
|
||||
if (req.serviceId !== serviceId) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.fnName === 'getNumber') {
|
||||
const callParams = {
|
||||
...req.particleContext,
|
||||
tetraplets: {},
|
||||
};
|
||||
resp.retCode = ResultCodes.success;
|
||||
resp.result = service.getNumber(callParams);
|
||||
}
|
||||
|
||||
if (req.fnName === 'sayHello') {
|
||||
const callParams = {
|
||||
...req.particleContext,
|
||||
tetraplets: {
|
||||
s: req.tetraplets[0],
|
||||
},
|
||||
};
|
||||
resp.retCode = ResultCodes.success;
|
||||
service.sayHello(req.args[0], callParams);
|
||||
resp.result = {};
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
// Functions
|
||||
|
||||
export function callMeBack(
|
||||
callback: (arg0: string, arg1: number, callParams: CallParams<'arg0' | 'arg1'>) => void,
|
||||
config?: { ttl?: number },
|
||||
): Promise<void>;
|
||||
export function callMeBack(
|
||||
peer: FluencePeer,
|
||||
callback: (arg0: string, arg1: number, callParams: CallParams<'arg0' | 'arg1'>) => void,
|
||||
config?: { ttl?: number },
|
||||
): Promise<void>;
|
||||
export function callMeBack(...args) {
|
||||
let peer: FluencePeer;
|
||||
let callback;
|
||||
let config;
|
||||
if (args[0] instanceof FluencePeer) {
|
||||
peer = args[0];
|
||||
callback = args[1];
|
||||
config = args[2];
|
||||
} else {
|
||||
peer = FluencePeer.default;
|
||||
callback = args[0];
|
||||
config = args[1];
|
||||
}
|
||||
|
||||
let request: RequestFlow;
|
||||
const promise = new Promise<void>((resolve, reject) => {
|
||||
const r = new RequestFlowBuilder()
|
||||
.disableInjections()
|
||||
.withRawScript(
|
||||
`
|
||||
(xor
|
||||
(seq
|
||||
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
|
||||
(xor
|
||||
(call %init_peer_id% ("callbackSrv" "callback") ["hello, world" 42])
|
||||
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 1])
|
||||
)
|
||||
)
|
||||
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 2])
|
||||
)
|
||||
|
||||
`,
|
||||
)
|
||||
.configHandler((h) => {
|
||||
h.on('getDataSrv', '-relay-', () => {
|
||||
return peer.connectionInfo.connectedRelay || null;
|
||||
});
|
||||
|
||||
h.use((req, resp, next) => {
|
||||
if (req.serviceId === 'callbackSrv' && req.fnName === 'callback') {
|
||||
const callParams = {
|
||||
...req.particleContext,
|
||||
tetraplets: {
|
||||
arg0: req.tetraplets[0],
|
||||
arg1: req.tetraplets[1],
|
||||
},
|
||||
};
|
||||
resp.retCode = ResultCodes.success;
|
||||
callback(req.args[0], req.args[1], callParams);
|
||||
resp.result = {};
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
h.onEvent('callbackSrv', 'response', (args) => {});
|
||||
|
||||
h.onEvent('errorHandlingSrv', 'error', (args) => {
|
||||
const [err] = args;
|
||||
reject(err);
|
||||
});
|
||||
})
|
||||
.handleScriptError(reject)
|
||||
.handleTimeout(() => {
|
||||
reject('Request timed out for callMeBack');
|
||||
});
|
||||
if (config && config.ttl) {
|
||||
r.withTTL(config.ttl);
|
||||
}
|
||||
request = r.build();
|
||||
});
|
||||
peer.internals.initiateFlow(request!);
|
||||
return Promise.race([promise, Promise.resolve()]);
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
import { Particle, sendParticle, registerServiceFunction, subscribeToEvent, sendParticleAsFetch } from '../../api';
|
||||
import { FluenceClient, createClient } from '../../FluenceClient';
|
||||
import { nodes } from '../connection';
|
||||
|
||||
let client: FluenceClient;
|
||||
|
||||
describe('Legacy api suite', () => {
|
||||
afterEach(async () => {
|
||||
if (client) {
|
||||
await client.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
it('sendParticle', async () => {
|
||||
client = await createClient(nodes[0]);
|
||||
|
||||
const result = new Promise((resolve) => {
|
||||
subscribeToEvent(client, 'callback', 'callback', (args) => {
|
||||
resolve(args[0]);
|
||||
});
|
||||
});
|
||||
|
||||
const script = `(seq
|
||||
(call init_relay ("op" "identity") [])
|
||||
(call %init_peer_id% ("callback" "callback") [arg])
|
||||
)`;
|
||||
|
||||
const data = {
|
||||
arg: 'hello world!',
|
||||
};
|
||||
|
||||
await sendParticle(client, new Particle(script, data, 7000));
|
||||
|
||||
expect(await result).toBe('hello world!');
|
||||
});
|
||||
|
||||
it('sendParticle Error', async () => {
|
||||
client = await createClient(nodes[0]);
|
||||
|
||||
const script = `
|
||||
(call init_relay ("incorrect" "service") [])
|
||||
`;
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
sendParticle(client, new Particle(script), reject);
|
||||
});
|
||||
|
||||
await expect(promise).rejects.toMatchObject({
|
||||
msg: expect.stringContaining("Service with id 'incorrect' not found"),
|
||||
instruction: expect.stringContaining('incorrect'),
|
||||
});
|
||||
});
|
||||
|
||||
it('sendParticleAsFetch', async () => {
|
||||
client = await createClient(nodes[0]);
|
||||
|
||||
const script = `(seq
|
||||
(call init_relay ("op" "identity") [])
|
||||
(call %init_peer_id% ("service" "fn") [arg])
|
||||
)`;
|
||||
|
||||
const data = {
|
||||
arg: 'hello world!',
|
||||
};
|
||||
|
||||
const [result] = await sendParticleAsFetch<[string]>(client, new Particle(script, data, 7000), 'fn', 'service');
|
||||
|
||||
expect(result).toBe('hello world!');
|
||||
});
|
||||
|
||||
it('sendParticleAsFetch Error', async () => {
|
||||
client = await createClient(nodes[0]);
|
||||
|
||||
const script = `
|
||||
(call init_relay ("incorrect" "service") [])
|
||||
`;
|
||||
|
||||
const promise = sendParticleAsFetch<[string]>(client, new Particle(script), 'fn', 'service');
|
||||
|
||||
await expect(promise).rejects.toMatchObject({
|
||||
msg: expect.stringContaining("Service with id 'incorrect' not found"),
|
||||
instruction: expect.stringContaining('incorrect'),
|
||||
});
|
||||
});
|
||||
|
||||
it('registerServiceFunction', async () => {
|
||||
client = await createClient(nodes[0]);
|
||||
|
||||
registerServiceFunction(client, 'service', 'fn', (args) => {
|
||||
return { res: args[0] + ' world!' };
|
||||
});
|
||||
|
||||
const script = `(seq
|
||||
(call %init_peer_id% ("service" "fn") ["hello"] result)
|
||||
(call %init_peer_id% ("callback" "callback") [result])
|
||||
)`;
|
||||
|
||||
const [result] = await sendParticleAsFetch<[string]>(
|
||||
client,
|
||||
new Particle(script, {}, 7000),
|
||||
'callback',
|
||||
'callback',
|
||||
);
|
||||
|
||||
expect(result).toEqual({ res: 'hello world!' });
|
||||
});
|
||||
|
||||
it('subscribeToEvent', async () => {
|
||||
client = await createClient(nodes[0]);
|
||||
|
||||
const promise = new Promise((resolve) => {
|
||||
subscribeToEvent(client, 'service', 'fn', (args) => {
|
||||
resolve(args[0] + ' world!');
|
||||
});
|
||||
});
|
||||
|
||||
const script = `
|
||||
(call %init_peer_id% ("service" "fn") ["hello"])
|
||||
`;
|
||||
|
||||
await sendParticle(client, new Particle(script, {}, 7000));
|
||||
|
||||
const result = await promise;
|
||||
expect(result).toBe('hello world!');
|
||||
});
|
||||
});
|
@ -1,22 +1,22 @@
|
||||
import { checkConnection, createClient, FluenceClient } from '../../FluenceClient';
|
||||
import { Multiaddr } from 'multiaddr';
|
||||
import { nodes } from '../connection';
|
||||
import { RequestFlowBuilder } from '../../internal/RequestFlowBuilder';
|
||||
import log from 'loglevel';
|
||||
import { FluencePeer } from '../../index';
|
||||
import { checkConnection } from '../../internal/utils';
|
||||
|
||||
let client: FluenceClient;
|
||||
const peer = new FluencePeer();
|
||||
|
||||
describe('Typescript usage suite', () => {
|
||||
afterEach(async () => {
|
||||
if (client) {
|
||||
await client.disconnect();
|
||||
if (peer) {
|
||||
await peer.uninit();
|
||||
}
|
||||
});
|
||||
|
||||
it('should make a call through network', async () => {
|
||||
// arrange
|
||||
client = await createClient();
|
||||
await client.connect(nodes[0].multiaddr);
|
||||
await peer.init({ connectTo: nodes[0] });
|
||||
|
||||
// act
|
||||
const [request, promise] = new RequestFlowBuilder()
|
||||
@ -27,7 +27,8 @@ describe('Typescript usage suite', () => {
|
||||
)`,
|
||||
)
|
||||
.buildAsFetch<[string]>('callback', 'callback');
|
||||
await client.initiateFlow(request);
|
||||
await peer.internals.initiateFlow(request);
|
||||
console.log(request.getParticle().script);
|
||||
|
||||
// assert
|
||||
const [result] = await promise;
|
||||
@ -35,30 +36,30 @@ describe('Typescript usage suite', () => {
|
||||
});
|
||||
|
||||
it('check connection should work', async function () {
|
||||
client = await createClient();
|
||||
await client.connect(nodes[0].multiaddr);
|
||||
await peer.init({ connectTo: nodes[0] });
|
||||
|
||||
let isConnected = await checkConnection(client);
|
||||
let isConnected = await checkConnection(peer);
|
||||
|
||||
expect(isConnected).toEqual(true);
|
||||
});
|
||||
|
||||
it('check connection should work with ttl', async function () {
|
||||
client = await createClient();
|
||||
await client.connect(nodes[0].multiaddr);
|
||||
await peer.init({ connectTo: nodes[0] });
|
||||
|
||||
let isConnected = await checkConnection(client, 10000);
|
||||
let isConnected = await checkConnection(peer, 10000);
|
||||
|
||||
expect(isConnected).toEqual(true);
|
||||
});
|
||||
|
||||
it('two clients should work inside the same time browser', async () => {
|
||||
// arrange
|
||||
const client1 = await createClient(nodes[0].multiaddr);
|
||||
const client2 = await createClient(nodes[0].multiaddr);
|
||||
const peer1 = new FluencePeer();
|
||||
await peer1.init({ connectTo: nodes[0] });
|
||||
const peer2 = new FluencePeer();
|
||||
await peer2.init({ connectTo: nodes[0] });
|
||||
|
||||
let resMakingPromise = new Promise((resolve) => {
|
||||
client2.callServiceHandler.onEvent('test', 'test', (args, _) => {
|
||||
peer2.internals.callServiceHandler.onEvent('test', 'test', (args, _) => {
|
||||
resolve([...args]);
|
||||
return {};
|
||||
});
|
||||
@ -66,8 +67,8 @@ describe('Typescript usage suite', () => {
|
||||
|
||||
let script = `
|
||||
(seq
|
||||
(call "${client1.relayPeerId}" ("op" "identity") [])
|
||||
(call "${client2.selfPeerId}" ("test" "test") [a b c d])
|
||||
(call "${peer1.connectionInfo.connectedRelay}" ("op" "identity") [])
|
||||
(call "${peer2.connectionInfo.selfPeerId}" ("test" "test") [a b c d])
|
||||
)
|
||||
`;
|
||||
|
||||
@ -77,23 +78,23 @@ describe('Typescript usage suite', () => {
|
||||
data.set('c', 'some c');
|
||||
data.set('d', 'some d');
|
||||
|
||||
await client1.initiateFlow(new RequestFlowBuilder().withRawScript(script).withVariables(data).build());
|
||||
await peer1.internals.initiateFlow(new RequestFlowBuilder().withRawScript(script).withVariables(data).build());
|
||||
|
||||
let res = await resMakingPromise;
|
||||
expect(res).toEqual(['some a', 'some b', 'some c', 'some d']);
|
||||
|
||||
await client1.disconnect();
|
||||
await client2.disconnect();
|
||||
await peer1.uninit();
|
||||
await peer2.uninit();
|
||||
});
|
||||
|
||||
describe('should make connection to network', () => {
|
||||
it('address as string', async () => {
|
||||
// arrange
|
||||
const addr = nodes[0].multiaddr;
|
||||
const addr = nodes[0];
|
||||
|
||||
// act
|
||||
client = await createClient(addr);
|
||||
const isConnected = await checkConnection(client);
|
||||
await peer.init({ connectTo: addr });
|
||||
const isConnected = await checkConnection(peer);
|
||||
|
||||
// assert
|
||||
expect(isConnected).toBeTruthy;
|
||||
@ -104,8 +105,8 @@ describe('Typescript usage suite', () => {
|
||||
const addr = new Multiaddr(nodes[0].multiaddr);
|
||||
|
||||
// act
|
||||
client = await createClient(addr);
|
||||
const isConnected = await checkConnection(client);
|
||||
await peer.init({ connectTo: addr });
|
||||
const isConnected = await checkConnection(peer);
|
||||
|
||||
// assert
|
||||
expect(isConnected).toBeTruthy;
|
||||
@ -116,8 +117,8 @@ describe('Typescript usage suite', () => {
|
||||
const addr = nodes[0];
|
||||
|
||||
// act
|
||||
client = await createClient(addr);
|
||||
const isConnected = await checkConnection(client);
|
||||
await peer.init({ connectTo: addr });
|
||||
const isConnected = await checkConnection(peer);
|
||||
|
||||
// assert
|
||||
expect(isConnected).toBeTruthy;
|
||||
@ -125,11 +126,11 @@ describe('Typescript usage suite', () => {
|
||||
|
||||
it('peerid as peer id', async () => {
|
||||
// arrange
|
||||
const addr = nodes[0].multiaddr;
|
||||
const addr = nodes[0];
|
||||
|
||||
// act
|
||||
client = await createClient(addr);
|
||||
const isConnected = await checkConnection(client);
|
||||
await peer.init({ connectTo: addr });
|
||||
const isConnected = await checkConnection(peer);
|
||||
|
||||
// assert
|
||||
expect(isConnected).toBeTruthy;
|
||||
@ -137,11 +138,11 @@ describe('Typescript usage suite', () => {
|
||||
|
||||
it('peerid as seed', async () => {
|
||||
// arrange
|
||||
const addr = nodes[0].multiaddr;
|
||||
const addr = nodes[0];
|
||||
|
||||
// act
|
||||
client = await createClient(addr);
|
||||
const isConnected = await checkConnection(client);
|
||||
await peer.init({ connectTo: addr });
|
||||
const isConnected = await checkConnection(peer);
|
||||
|
||||
// assert
|
||||
expect(isConnected).toBeTruthy;
|
||||
@ -149,11 +150,11 @@ describe('Typescript usage suite', () => {
|
||||
|
||||
it('With connection options: dialTimeout', async () => {
|
||||
// arrange
|
||||
const addr = nodes[0].multiaddr;
|
||||
const addr = nodes[0];
|
||||
|
||||
// act
|
||||
client = await createClient(addr, undefined, { dialTimeout: 100000 });
|
||||
const isConnected = await checkConnection(client);
|
||||
await peer.init({ connectTo: addr, dialTimeoutMs: 100000 });
|
||||
const isConnected = await checkConnection(peer);
|
||||
|
||||
// assert
|
||||
expect(isConnected).toBeTruthy;
|
||||
@ -161,11 +162,11 @@ describe('Typescript usage suite', () => {
|
||||
|
||||
it('With connection options: skipCheckConnection', async () => {
|
||||
// arrange
|
||||
const addr = nodes[0].multiaddr;
|
||||
const addr = nodes[0];
|
||||
|
||||
// act
|
||||
client = await createClient(addr, undefined, { skipCheckConnection: true });
|
||||
const isConnected = await checkConnection(client);
|
||||
await peer.init({ connectTo: addr, skipCheckConnection: true });
|
||||
const isConnected = await checkConnection(peer);
|
||||
|
||||
// assert
|
||||
expect(isConnected).toBeTruthy;
|
||||
@ -173,11 +174,11 @@ describe('Typescript usage suite', () => {
|
||||
|
||||
it('With connection options: checkConnectionTTL', async () => {
|
||||
// arrange
|
||||
const addr = nodes[0].multiaddr;
|
||||
const addr = nodes[0];
|
||||
|
||||
// act
|
||||
client = await createClient(addr, undefined, { checkConnectionTTL: 1000 });
|
||||
const isConnected = await checkConnection(client);
|
||||
await peer.init({ connectTo: addr, checkConnectionTimeoutMs: 1000 });
|
||||
const isConnected = await checkConnection(peer);
|
||||
|
||||
// assert
|
||||
expect(isConnected).toBeTruthy;
|
||||
@ -198,8 +199,8 @@ describe('Typescript usage suite', () => {
|
||||
.buildWithErrorHandling();
|
||||
|
||||
// act
|
||||
client = await createClient(nodes[0].multiaddr);
|
||||
await client.initiateFlow(request);
|
||||
await peer.init({ connectTo: nodes[0] });
|
||||
await peer.internals.initiateFlow(request);
|
||||
|
||||
// assert
|
||||
await expect(promise).rejects.toMatchObject({
|
||||
@ -225,19 +226,19 @@ describe('Typescript usage suite', () => {
|
||||
.buildWithErrorHandling();
|
||||
|
||||
// act
|
||||
client = await createClient();
|
||||
await client.initiateFlow(request);
|
||||
await peer.init();
|
||||
await peer.internals.initiateFlow(request);
|
||||
|
||||
// assert
|
||||
await expect(promise).rejects.toMatch('service failed internally');
|
||||
});
|
||||
|
||||
it('Should throw correct message when calling non existing local service', async function () {
|
||||
it.skip('Should throw correct message when calling non existing local service', async function () {
|
||||
// arrange
|
||||
client = await createClient();
|
||||
await peer.init();
|
||||
|
||||
// act
|
||||
const res = callIdentifyOnInitPeerId(client);
|
||||
const res = callIdentifyOnInitPeerId(peer);
|
||||
|
||||
// assert
|
||||
await expect(res).rejects.toMatchObject({
|
||||
@ -250,7 +251,7 @@ describe('Typescript usage suite', () => {
|
||||
|
||||
it('Should not crash if undefined is passed as a variable', async () => {
|
||||
// arrange
|
||||
client = await createClient();
|
||||
await peer.init();
|
||||
const [request, promise] = new RequestFlowBuilder()
|
||||
.withRawScript(
|
||||
`
|
||||
@ -264,7 +265,7 @@ describe('Typescript usage suite', () => {
|
||||
.buildAsFetch<any[]>('return', 'return');
|
||||
|
||||
// act
|
||||
await client.initiateFlow(request);
|
||||
await peer.internals.initiateFlow(request);
|
||||
const [res] = await promise;
|
||||
|
||||
// assert
|
||||
@ -273,14 +274,14 @@ describe('Typescript usage suite', () => {
|
||||
|
||||
it('Should throw correct error when the client tries to send a particle not to the relay', async () => {
|
||||
// arrange
|
||||
client = await createClient();
|
||||
await peer.init();
|
||||
|
||||
// act
|
||||
const [req, promise] = new RequestFlowBuilder()
|
||||
.withRawScript('(call "incorrect_peer_id" ("any" "service") [])')
|
||||
.buildWithErrorHandling();
|
||||
|
||||
await client.initiateFlow(req);
|
||||
await peer.internals.initiateFlow(req);
|
||||
|
||||
// assert
|
||||
await expect(promise).rejects.toMatch(
|
||||
@ -289,7 +290,7 @@ describe('Typescript usage suite', () => {
|
||||
});
|
||||
});
|
||||
|
||||
async function callIdentifyOnInitPeerId(client: FluenceClient): Promise<string[]> {
|
||||
async function callIdentifyOnInitPeerId(peer: FluencePeer): Promise<string[]> {
|
||||
let request;
|
||||
const promise = new Promise<string[]>((resolve, reject) => {
|
||||
request = new RequestFlowBuilder()
|
||||
@ -301,6 +302,6 @@ async function callIdentifyOnInitPeerId(client: FluenceClient): Promise<string[]
|
||||
.handleScriptError(reject)
|
||||
.build();
|
||||
});
|
||||
await client.initiateFlow(request);
|
||||
await peer.internals.initiateFlow(request);
|
||||
return promise;
|
||||
}
|
@ -1,12 +1,17 @@
|
||||
import { CallServiceHandler, errorHandler, ResultCodes } from '../../internal/CallServiceHandler';
|
||||
import { CallServiceData, CallServiceHandler, ResultCodes } from '../../internal/CallServiceHandler';
|
||||
import { errorHandler } from '../../internal/defaultMiddlewares';
|
||||
|
||||
const req = () => ({
|
||||
const req = (): CallServiceData => ({
|
||||
serviceId: 'service',
|
||||
fnName: 'fn name',
|
||||
args: [],
|
||||
tetraplets: [],
|
||||
particleContext: {
|
||||
particleId: 'id',
|
||||
initPeerId: 'init peer id',
|
||||
timestamp: 595951200,
|
||||
ttl: 595961200,
|
||||
signature: 'sig',
|
||||
},
|
||||
});
|
||||
|
||||
@ -102,7 +107,7 @@ describe('Call service handler tests', () => {
|
||||
// assert
|
||||
expect(res).toMatchObject({
|
||||
retCode: ResultCodes.exceptionInHandler,
|
||||
result: 'Error: some error',
|
||||
result: 'Handler failed. fnName="fn name" serviceId="service" error: Error: some error',
|
||||
});
|
||||
});
|
||||
|
32
src/__test__/unit/KeyPair.spec.ts
Normal file
32
src/__test__/unit/KeyPair.spec.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { encode } from 'bs58';
|
||||
import * as base64 from 'base64-js';
|
||||
import PeerId from 'peer-id';
|
||||
import { KeyPair } from '../../internal/KeyPair';
|
||||
|
||||
describe('KeyPair tests', () => {
|
||||
it('should create private key from seed and back', async function () {
|
||||
// arrange
|
||||
const sk = 'z1x3cVXhk9nJKE1pZaX9KxccUBzxu3aGlaUjDdAB2oY=';
|
||||
|
||||
// act
|
||||
const keyPair = await KeyPair.fromEd25519SK(sk);
|
||||
const sk2 = peerIdToEd25519SK(keyPair.Libp2pPeerId);
|
||||
|
||||
// assert
|
||||
expect(sk2).toBe(sk);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Converts peer id into base64 string contatining the 32 byte Ed25519S secret key
|
||||
* @returns - base64 of Ed25519S secret key
|
||||
*/
|
||||
export const peerIdToEd25519SK = (peerId: PeerId): string => {
|
||||
// export as [...private, ...public] array
|
||||
const privateAndPublicKeysArray = peerId.privKey.marshal();
|
||||
// extract the private key
|
||||
const pk = privateAndPublicKeysArray.slice(0, 32);
|
||||
// serialize private key as base64
|
||||
const b64 = base64.fromByteArray(pk);
|
||||
return b64;
|
||||
};
|
@ -1,16 +1,16 @@
|
||||
import { seedToPeerId } from '../../internal/peerIdUtils';
|
||||
import { KeyPair } from '../../internal/KeyPair';
|
||||
import { RequestFlow } from '../../internal/RequestFlow';
|
||||
|
||||
describe('Request flow tests', () => {
|
||||
it('particle initiation should work', async () => {
|
||||
// arrange
|
||||
jest.useFakeTimers();
|
||||
const seed = '4vzv3mg6cnjpEK24TXXLA3Ye7QrvKWPKqfbDvAKAyLK6';
|
||||
const sk = 'z1x3cVXhk9nJKE1pZaX9KxccUBzxu3aGlaUjDdAB2oY=';
|
||||
const mockDate = new Date(Date.UTC(2021, 2, 14)).valueOf();
|
||||
Date.now = jest.fn(() => mockDate);
|
||||
|
||||
const request = RequestFlow.createLocal('(null)', 10000);
|
||||
const peerId = await seedToPeerId(seed);
|
||||
const peerId = await (await KeyPair.fromEd25519SK(sk)).Libp2pPeerId;
|
||||
|
||||
// act
|
||||
await request.initState(peerId);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { FluenceConnection } from '../../internal/FluenceConnection';
|
||||
import Peer from 'libp2p';
|
||||
import { Multiaddr } from 'multiaddr';
|
||||
import { generatePeerId } from '../../internal/peerIdUtils';
|
||||
import { KeyPair } from '../../internal/KeyPair';
|
||||
|
||||
describe('Ws Transport', () => {
|
||||
// TODO:: fix test
|
||||
@ -10,7 +10,7 @@ describe('Ws Transport', () => {
|
||||
let multiaddr = new Multiaddr(
|
||||
'/ip4/127.0.0.1/tcp/1234/ws/p2p/12D3KooWMJ78GJrtCxVUpjLEedbPtnLDxkFQJ2wuefEdrxq6zwSs',
|
||||
);
|
||||
let peerId = await generatePeerId();
|
||||
let peerId = (await KeyPair.randomEd25519()).Libp2pPeerId;
|
||||
const connection = new FluenceConnection(multiaddr, peerId, peerId, (_) => {});
|
||||
await (connection as any).createPeer();
|
||||
let node = (connection as any).node as Peer;
|
||||
|
@ -1,197 +0,0 @@
|
||||
import { createClient, FluenceClient } from '../../FluenceClient';
|
||||
import { RequestFlow } from '../../internal/RequestFlow';
|
||||
import { RequestFlowBuilder } from '../../internal/RequestFlowBuilder';
|
||||
|
||||
let client: FluenceClient;
|
||||
|
||||
describe('== AIR suite', () => {
|
||||
afterEach(async () => {
|
||||
if (client) {
|
||||
await client.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
it('check init_peer_id', async function () {
|
||||
// arrange
|
||||
const serviceId = 'test_service';
|
||||
const fnName = 'return_first_arg';
|
||||
const script = `(call %init_peer_id% ("${serviceId}" "${fnName}") [%init_peer_id%])`;
|
||||
|
||||
// prettier-ignore
|
||||
const [request, promise] = new RequestFlowBuilder()
|
||||
.withRawScript(script)
|
||||
.buildAsFetch<string[]>(serviceId, fnName);
|
||||
|
||||
// act
|
||||
client = await createClient();
|
||||
await client.initiateFlow(request);
|
||||
const [result] = await promise;
|
||||
|
||||
// assert
|
||||
expect(result).toBe(client.selfPeerId);
|
||||
});
|
||||
|
||||
it('call local function', async function () {
|
||||
// arrange
|
||||
const serviceId = 'test_service';
|
||||
const fnName = 'return_first_arg';
|
||||
|
||||
client = await createClient();
|
||||
|
||||
let res;
|
||||
client.callServiceHandler.on(serviceId, fnName, (args, _) => {
|
||||
res = args[0];
|
||||
return res;
|
||||
});
|
||||
|
||||
// act
|
||||
const arg = 'hello';
|
||||
const script = `(call %init_peer_id% ("${serviceId}" "${fnName}") ["${arg}"])`;
|
||||
await client.initiateFlow(RequestFlow.createLocal(script));
|
||||
|
||||
// assert
|
||||
expect(res).toEqual(arg);
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
it('call broken script', async function () {
|
||||
// arrange
|
||||
const script = `(incorrect)`;
|
||||
// prettier-ignore
|
||||
const [request, error] = new RequestFlowBuilder()
|
||||
.withRawScript(script)
|
||||
.buildWithErrorHandling();
|
||||
|
||||
// act
|
||||
client = await createClient();
|
||||
await client.initiateFlow(request);
|
||||
|
||||
// assert
|
||||
await expect(error).rejects.toContain("air can't be parsed");
|
||||
});
|
||||
|
||||
it('call script without ttl', async function () {
|
||||
// arrange
|
||||
const script = `(null)`;
|
||||
// prettier-ignore
|
||||
const [request, promise] = new RequestFlowBuilder()
|
||||
.withTTL(1)
|
||||
.withRawScript(script)
|
||||
.buildAsFetch();
|
||||
|
||||
// act
|
||||
client = await createClient();
|
||||
await client.initiateFlow(request);
|
||||
|
||||
// assert
|
||||
await expect(promise).rejects.toContain('Timed out after');
|
||||
});
|
||||
});
|
||||
|
||||
it('check particle arguments', async function () {
|
||||
// arrange
|
||||
const serviceId = 'test_service';
|
||||
const fnName = 'return_first_arg';
|
||||
const script = `(call %init_peer_id% ("${serviceId}" "${fnName}") [arg1])`;
|
||||
|
||||
// prettier-ignore
|
||||
const [request, promise] = new RequestFlowBuilder()
|
||||
.withRawScript(script)
|
||||
.withVariable('arg1', 'hello')
|
||||
.buildAsFetch<string[]>(serviceId, fnName);
|
||||
|
||||
// act
|
||||
client = await createClient();
|
||||
await client.initiateFlow(request);
|
||||
const [result] = await promise;
|
||||
|
||||
// assert
|
||||
expect(result).toEqual('hello');
|
||||
});
|
||||
|
||||
it('check security tetraplet', async function () {
|
||||
// arrange
|
||||
const makeDataServiceId = 'make_data_service';
|
||||
const makeDataFnName = 'make_data';
|
||||
const getDataServiceId = 'get_data_service';
|
||||
const getDataFnName = 'get_data';
|
||||
|
||||
client = await createClient();
|
||||
|
||||
client.callServiceHandler.on(makeDataServiceId, makeDataFnName, (args, _) => {
|
||||
return {
|
||||
field: 42,
|
||||
};
|
||||
});
|
||||
let res;
|
||||
client.callServiceHandler.on(getDataServiceId, getDataFnName, (args, tetraplets) => {
|
||||
res = {
|
||||
args: args,
|
||||
tetraplets: tetraplets,
|
||||
};
|
||||
return args[0];
|
||||
});
|
||||
|
||||
// act
|
||||
const script = `
|
||||
(seq
|
||||
(call %init_peer_id% ("${makeDataServiceId}" "${makeDataFnName}") [] result)
|
||||
(call %init_peer_id% ("${getDataServiceId}" "${getDataFnName}") [result.$.field])
|
||||
)`;
|
||||
await client.initiateFlow(new RequestFlowBuilder().withRawScript(script).build());
|
||||
|
||||
// assert
|
||||
const tetraplet = res.tetraplets[0][0];
|
||||
expect(tetraplet).toMatchObject({
|
||||
service_id: 'make_data_service',
|
||||
function_name: 'make_data',
|
||||
json_path: '$.field',
|
||||
});
|
||||
});
|
||||
|
||||
it('check chain of services work properly', async function () {
|
||||
// arrange
|
||||
client = await createClient();
|
||||
|
||||
const serviceId1 = 'check1';
|
||||
const fnName1 = 'fn1';
|
||||
let res1;
|
||||
client.callServiceHandler.on(serviceId1, fnName1, (args, _) => {
|
||||
res1 = args[0];
|
||||
return res1;
|
||||
});
|
||||
|
||||
const serviceId2 = 'check2';
|
||||
const fnName2 = 'fn2';
|
||||
let res2;
|
||||
client.callServiceHandler.on(serviceId2, fnName2, (args, _) => {
|
||||
res2 = args[0];
|
||||
return res2;
|
||||
});
|
||||
|
||||
const serviceId3 = 'check3';
|
||||
const fnName3 = 'fn3';
|
||||
let res3;
|
||||
client.callServiceHandler.on(serviceId3, fnName3, (args, _) => {
|
||||
res3 = args;
|
||||
return res3;
|
||||
});
|
||||
|
||||
const arg1 = 'arg1';
|
||||
const arg2 = 'arg2';
|
||||
|
||||
// act
|
||||
const script = `(seq
|
||||
(seq
|
||||
(call %init_peer_id% ("${serviceId1}" "${fnName1}") ["${arg1}"] result1)
|
||||
(call %init_peer_id% ("${serviceId2}" "${fnName2}") ["${arg2}"] result2))
|
||||
(call %init_peer_id% ("${serviceId3}" "${fnName3}") [result1 result2]))
|
||||
`;
|
||||
await client.initiateFlow(new RequestFlowBuilder().withRawScript(script).build());
|
||||
|
||||
// assert
|
||||
expect(res1).toEqual(arg1);
|
||||
expect(res2).toEqual(arg2);
|
||||
expect(res3).toEqual([res1, res2]);
|
||||
});
|
||||
});
|
@ -1,44 +0,0 @@
|
||||
import { AirInterpreter } from '@fluencelabs/avm';
|
||||
|
||||
describe('== AST parsing suite', () => {
|
||||
it('parse simple script and return ast', async function () {
|
||||
const interpreter = await AirInterpreter.create(
|
||||
undefined as any,
|
||||
undefined as any,
|
||||
undefined as any,
|
||||
undefined as any,
|
||||
);
|
||||
let ast = interpreter.parseAir(`
|
||||
(call "node" ("service" "function") [1 2 3] output)
|
||||
`);
|
||||
|
||||
ast = JSON.parse(ast);
|
||||
|
||||
expect(ast).toEqual({
|
||||
Call: {
|
||||
peer_part: { PeerPk: { Literal: 'node' } },
|
||||
function_part: { ServiceIdWithFuncName: [{ Literal: 'service' }, { Literal: 'function' }] },
|
||||
args: [
|
||||
{
|
||||
Number: {
|
||||
Int: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Number: {
|
||||
Int: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
Number: {
|
||||
Int: 3,
|
||||
},
|
||||
},
|
||||
],
|
||||
output: {
|
||||
Variable: { Scalar: 'output' },
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
@ -43,6 +43,10 @@ describe('Tests for default handler', () => {
|
||||
tetraplets: [],
|
||||
particleContext: {
|
||||
particleId: 'some',
|
||||
initPeerId: 'init peer id',
|
||||
timestamp: 595951200,
|
||||
ttl: 595961200,
|
||||
signature: 'sig',
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -1,13 +0,0 @@
|
||||
import { encode } from 'bs58';
|
||||
import { peerIdToSeed, seedToPeerId } from '../..';
|
||||
|
||||
describe('Peer Id utils', () => {
|
||||
it('should create private key from seed and back', async function () {
|
||||
// prettier-ignore
|
||||
let seed = [46, 188, 245, 171, 145, 73, 40, 24, 52, 233, 215, 163, 54, 26, 31, 221, 159, 179, 126, 106, 27, 199, 189, 194, 80, 133, 235, 42, 42, 247, 80, 201];
|
||||
let seedStr = encode(seed);
|
||||
|
||||
let pid = await seedToPeerId(seedStr);
|
||||
expect(peerIdToSeed(pid)).toEqual(seedStr);
|
||||
});
|
||||
});
|
159
src/api.ts
159
src/api.ts
@ -1,159 +0,0 @@
|
||||
import { RequestFlowBuilder } from './internal/RequestFlowBuilder';
|
||||
import { FluenceClient } from './FluenceClient';
|
||||
import { CallServiceResultType } from './internal/CallServiceHandler';
|
||||
import { SecurityTetraplet } from '@fluencelabs/avm';
|
||||
|
||||
/**
|
||||
* The class representing Particle - a data structure used to perform operations on Fluence Network. It originates on some peer in the network, travels the network through a predefined path, triggering function execution along its way.
|
||||
*/
|
||||
export class Particle {
|
||||
script: string;
|
||||
data: Map<string, any>;
|
||||
ttl: number;
|
||||
|
||||
/**
|
||||
* Creates a particle with specified parameters.
|
||||
* @param { String }script - Air script which defines the execution of a particle – its path, functions it triggers on peers, and so on.
|
||||
* @param { Map<string, any> | Record<string, any> } data - Variables passed to the particle in the form of either JS Map or JS object with keys representing variable names and values representing values correspondingly
|
||||
* @param { [Number]=7000 } ttl - Time to live, a timout after which the particle execution is stopped by AVM.
|
||||
*/
|
||||
constructor(script: string, data?: Map<string, any> | Record<string, any>, ttl?: number) {
|
||||
this.script = script;
|
||||
if (data === undefined) {
|
||||
this.data = new Map();
|
||||
} else if (data instanceof Map) {
|
||||
this.data = data;
|
||||
} else {
|
||||
this.data = new Map();
|
||||
for (let k in data) {
|
||||
this.data.set(k, data[k]);
|
||||
}
|
||||
}
|
||||
|
||||
this.ttl = ttl ?? 7000;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a particle to Fluence Network using the specified Fluence Client.
|
||||
* @param { FluenceClient } client - The Fluence Client instance.
|
||||
* @param { Particle } particle - The particle to send.
|
||||
*/
|
||||
export const sendParticle = async (
|
||||
client: FluenceClient,
|
||||
particle: Particle,
|
||||
onError?: (err) => void,
|
||||
): Promise<string> => {
|
||||
const [req, errorPromise] = new RequestFlowBuilder()
|
||||
.withRawScript(particle.script)
|
||||
.withVariables(particle.data)
|
||||
.withTTL(particle.ttl)
|
||||
.buildWithErrorHandling();
|
||||
|
||||
errorPromise.catch(onError);
|
||||
|
||||
await client.initiateFlow(req);
|
||||
return req.id;
|
||||
};
|
||||
|
||||
/*
|
||||
This map stores functions which unregister callbacks registered by registerServiceFunction
|
||||
The key sould be created with makeKey. The value is the unresitration function
|
||||
This is only needed to support legacy api
|
||||
*/
|
||||
const handlersUnregistratorsMap = new Map();
|
||||
const makeKey = (client: FluenceClient, serviceId: string, fnName: string) => {
|
||||
const pid = client.selfPeerId || '';
|
||||
return `${pid}/${serviceId}/${fnName}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers a function which can be called on the client from AVM. The registration is per client basis.
|
||||
* @param { FluenceClient } client - The Fluence Client instance.
|
||||
* @param { string } serviceId - The identifier of service which would be used to make calls from AVM
|
||||
* @param { string } fnName - The identifier of function which would be used to make calls from AVM
|
||||
* @param { (args: any[], tetraplets: SecurityTetraplet[][]) => object | boolean | number | string } handler - The handler which would be called by AVM. The result is any object passed back to AVM
|
||||
*/
|
||||
export const registerServiceFunction = (
|
||||
client: FluenceClient,
|
||||
serviceId: string,
|
||||
fnName: string,
|
||||
handler: (args: any[], tetraplets: SecurityTetraplet[][]) => CallServiceResultType,
|
||||
) => {
|
||||
const unregister = client.callServiceHandler.on(serviceId, fnName, handler);
|
||||
handlersUnregistratorsMap.set(makeKey(client, serviceId, fnName), unregister);
|
||||
};
|
||||
|
||||
// prettier-ignore
|
||||
/**
|
||||
* Removes registers for the function previously registered with {@link registerServiceFunction}
|
||||
* @param { FluenceClient } client - The Fluence Client instance.
|
||||
* @param { string } serviceId - The identifier of service used in {@link registerServiceFunction} call
|
||||
* @param { string } fnName - The identifier of function used in {@link registerServiceFunction} call
|
||||
*/
|
||||
export const unregisterServiceFunction = (
|
||||
client: FluenceClient,
|
||||
serviceId: string,
|
||||
fnName: string
|
||||
) => {
|
||||
const key = makeKey(client, serviceId, fnName);
|
||||
const unuse = handlersUnregistratorsMap.get(key);
|
||||
if(unuse) {
|
||||
unuse();
|
||||
}
|
||||
handlersUnregistratorsMap.delete(key);
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers an event-like handler for all calls to the specific service\function pair from AVM. The registration is per client basis. Return a function which when called removes the subscription.
|
||||
* Same as registerServiceFunction which immediately returns empty object.
|
||||
* @param { FluenceClient } client - The Fluence Client instance.
|
||||
* @param { string } serviceId - The identifier of service calls to which from AVM are transformed into events.
|
||||
* @param { string } fnName - The identifier of function calls to which from AVM are transformed into events.
|
||||
* @param { (args: any[], tetraplets: SecurityTetraplet[][]) => object } handler - The handler which would be called by AVM
|
||||
* @returns { Function } - A function which when called removes the subscription.
|
||||
*/
|
||||
export const subscribeToEvent = (
|
||||
client: FluenceClient,
|
||||
serviceId: string,
|
||||
fnName: string,
|
||||
handler: (args: any[], tetraplets: SecurityTetraplet[][]) => void,
|
||||
): Function => {
|
||||
const realHandler = (args: any[], tetraplets: SecurityTetraplet[][]) => {
|
||||
// dont' block
|
||||
setTimeout(() => {
|
||||
handler(args, tetraplets);
|
||||
}, 0);
|
||||
|
||||
return {};
|
||||
};
|
||||
registerServiceFunction(client, serviceId, fnName, realHandler);
|
||||
return () => {
|
||||
unregisterServiceFunction(client, serviceId, fnName);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a particle with a fetch-like semantics. In order to for this to work you have to you have to make a call to the same callbackServiceId\callbackFnName pair from Air script as specified by the parameters. The arguments of the call are returned as the resolve value of promise
|
||||
* @param { FluenceClient } client - The Fluence Client instance.
|
||||
* @param { Particle } particle - The particle to send.
|
||||
* @param { string } callbackFnName - The identifier of function which should be used in Air script to pass the data to fetch "promise"
|
||||
* @param { [string]='_callback' } callbackServiceId - The service identifier which should be used in Air script to pass the data to fetch "promise"
|
||||
* @returns { Promise<T> } - A promise which would be resolved with the data returned from AVM
|
||||
*/
|
||||
export const sendParticleAsFetch = async <T>(
|
||||
client: FluenceClient,
|
||||
particle: Particle,
|
||||
callbackFnName: string,
|
||||
callbackServiceId: string = '_callback',
|
||||
): Promise<T> => {
|
||||
const [request, promise] = new RequestFlowBuilder()
|
||||
.withRawScript(particle.script)
|
||||
.withVariables(particle.data)
|
||||
.withTTL(particle.ttl)
|
||||
.buildAsFetch<T>(callbackServiceId, callbackFnName);
|
||||
|
||||
await client.initiateFlow(request);
|
||||
|
||||
return promise;
|
||||
};
|
@ -1,2 +0,0 @@
|
||||
export { RequestFlowBuilder } from './internal/RequestFlowBuilder';
|
||||
export * from './internal/CallServiceHandler';
|
10
src/index.ts
10
src/index.ts
@ -14,14 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { seedToPeerId, peerIdToSeed, generatePeerId } from './internal/peerIdUtils';
|
||||
export { PeerIdB58 } from './internal/commonTypes';
|
||||
export { SecurityTetraplet } from '@fluencelabs/avm';
|
||||
export * from './api';
|
||||
export * from './FluenceClient';
|
||||
export * from './internal/builtins';
|
||||
import log, { LogLevelDesc } from 'loglevel';
|
||||
|
||||
export { KeyPair } from './internal/KeyPair';
|
||||
export { FluencePeer, AvmLoglevel } from './internal/FluencePeer';
|
||||
export { PeerIdB58, CallParams } from './internal/commonTypes';
|
||||
|
||||
export const setLogLevel = (level: LogLevelDesc) => {
|
||||
log.setLevel(level);
|
||||
};
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { SecurityTetraplet } from '@fluencelabs/avm';
|
||||
import { PeerIdB58 } from './commonTypes';
|
||||
|
||||
export enum ResultCodes {
|
||||
success = 0,
|
||||
noServiceFound = 1,
|
||||
unkownError = 1,
|
||||
exceptionInHandler = 2,
|
||||
unkownError = 1024,
|
||||
}
|
||||
|
||||
/**
|
||||
@ -15,7 +15,10 @@ interface ParticleContext {
|
||||
* The particle ID
|
||||
*/
|
||||
particleId: string;
|
||||
[x: string]: any;
|
||||
initPeerId: PeerIdB58;
|
||||
timestamp: number;
|
||||
ttl: number;
|
||||
signature: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,6 +85,20 @@ export interface CallServiceResult {
|
||||
*/
|
||||
export type Middleware = (req: CallServiceData, resp: CallServiceResult, next: Function) => void;
|
||||
|
||||
export class CallServiceArg<T> {
|
||||
val: T;
|
||||
tetraplet: SecurityTetraplet[];
|
||||
|
||||
constructor(val: T, tetraplet: SecurityTetraplet[]) {
|
||||
this.val = val;
|
||||
this.tetraplet = tetraplet;
|
||||
}
|
||||
}
|
||||
|
||||
type CallParams = ParticleContext & {
|
||||
wrappedArgs: CallServiceArg<any>[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Convenience middleware factory. Registeres a handler for a pair of 'serviceId/fnName'.
|
||||
* The return value of the handler is passed back to AVM
|
||||
@ -92,11 +109,11 @@ export type Middleware = (req: CallServiceData, resp: CallServiceResult, next: F
|
||||
export const fnHandler = (
|
||||
serviceId: string,
|
||||
fnName: string,
|
||||
handler: (args: any[], tetraplets: SecurityTetraplet[][]) => CallServiceResultType,
|
||||
handler: (args: any[], callParams: CallParams) => CallServiceResultType,
|
||||
) => {
|
||||
return (req: CallServiceData, resp: CallServiceResult, next: Function): void => {
|
||||
if (req.fnName === fnName && req.serviceId === serviceId) {
|
||||
const res = handler(req.args, req.tetraplets);
|
||||
const res = handler(req.args, { ...req.particleContext, wrappedArgs: req.wrappedArgs });
|
||||
resp.retCode = ResultCodes.success;
|
||||
resp.result = res;
|
||||
}
|
||||
@ -112,14 +129,14 @@ export const fnHandler = (
|
||||
* @param { (args: any[], tetraplets: SecurityTetraplet[][]) => void } handler - The handler which should handle the call.
|
||||
*/
|
||||
export const fnAsEventHandler = (
|
||||
serviceId: string,
|
||||
serviceId: string, // force format
|
||||
fnName: string,
|
||||
handler: (args: any[], tetraplets: SecurityTetraplet[][]) => void,
|
||||
handler: (args: any[], callParams: CallParams) => void,
|
||||
) => {
|
||||
return (req: CallServiceData, resp: CallServiceResult, next: Function): void => {
|
||||
if (req.fnName === fnName && req.serviceId === serviceId) {
|
||||
setTimeout(() => {
|
||||
handler(req.args, req.tetraplets);
|
||||
handler(req.args, { ...req.particleContext, wrappedArgs: req.wrappedArgs });
|
||||
}, 0);
|
||||
|
||||
resp.retCode = ResultCodes.success;
|
||||
@ -129,18 +146,6 @@ export const fnAsEventHandler = (
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Error catching middleware
|
||||
*/
|
||||
export const errorHandler: Middleware = (req: CallServiceData, resp: CallServiceResult, next: Function): void => {
|
||||
try {
|
||||
next();
|
||||
} catch (e) {
|
||||
resp.retCode = ResultCodes.exceptionInHandler;
|
||||
resp.result = e.toString();
|
||||
}
|
||||
};
|
||||
|
||||
type CallServiceFunction = (req: CallServiceData, resp: CallServiceResult) => void;
|
||||
|
||||
/**
|
||||
@ -188,9 +193,9 @@ export class CallServiceHandler {
|
||||
* Convinience method for registring @see { @link fnHandler } middleware
|
||||
*/
|
||||
on(
|
||||
serviceId: string,
|
||||
serviceId: string, // force format
|
||||
fnName: string,
|
||||
handler: (args: any[], tetraplets: SecurityTetraplet[][]) => CallServiceResultType,
|
||||
handler: (args: any[], callParams: CallParams) => CallServiceResultType,
|
||||
): Function {
|
||||
const mw = fnHandler(serviceId, fnName, handler);
|
||||
this.use(mw);
|
||||
@ -203,9 +208,9 @@ export class CallServiceHandler {
|
||||
* Convinience method for registring @see { @link fnAsEventHandler } middleware
|
||||
*/
|
||||
onEvent(
|
||||
serviceId: string,
|
||||
serviceId: string, // force format
|
||||
fnName: string,
|
||||
handler: (args: any[], tetraplets: SecurityTetraplet[][]) => void,
|
||||
handler: (args: any[], callParams: CallParams) => void,
|
||||
): Function {
|
||||
const mw = fnAsEventHandler(serviceId, fnName, handler);
|
||||
this.use(mw);
|
||||
|
@ -1,236 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
|
||||
import * as PeerId from 'peer-id';
|
||||
import { Multiaddr } from 'multiaddr';
|
||||
import { FluenceConnection, FluenceConnectionOptions } from './FluenceConnection';
|
||||
|
||||
import { PeerIdB58 } from './commonTypes';
|
||||
import { FluenceClient } from '../FluenceClient';
|
||||
import { RequestFlow } from './RequestFlow';
|
||||
import { CallServiceHandler } from './CallServiceHandler';
|
||||
import { loadRelayFn, loadVariablesService } from './RequestFlowBuilder';
|
||||
import { logParticle, Particle } from './particle';
|
||||
import log from 'loglevel';
|
||||
import {
|
||||
AirInterpreter,
|
||||
ParticleHandler,
|
||||
SecurityTetraplet,
|
||||
CallServiceResult,
|
||||
LogLevel as AvmLogLevel,
|
||||
} from '@fluencelabs/avm';
|
||||
import makeDefaultClientHandler from './defaultClientHandler';
|
||||
|
||||
const createClient = (handler, peerId): Promise<AirInterpreter> => {
|
||||
let logLevel: AvmLogLevel = 'off';
|
||||
switch (log.getLevel()) {
|
||||
case 0: // 'TRACE'
|
||||
logLevel = 'trace';
|
||||
break;
|
||||
case 1: // 'DEBUG'
|
||||
logLevel = 'debug';
|
||||
break;
|
||||
case 2: // 'INFO'
|
||||
logLevel = 'info';
|
||||
break;
|
||||
case 3: // 'WARN'
|
||||
logLevel = 'warn';
|
||||
break;
|
||||
case 4: // 'ERROR'
|
||||
logLevel = 'error';
|
||||
break;
|
||||
case 5: // 'SILENT'
|
||||
logLevel = 'off';
|
||||
break;
|
||||
}
|
||||
const logFn = (level: AvmLogLevel, msg: string) => {
|
||||
switch (level) {
|
||||
case 'error':
|
||||
log.error(msg);
|
||||
break;
|
||||
|
||||
case 'warn':
|
||||
log.warn(msg);
|
||||
break;
|
||||
|
||||
case 'info':
|
||||
log.info(msg);
|
||||
break;
|
||||
|
||||
case 'debug':
|
||||
case 'trace':
|
||||
log.log(msg);
|
||||
break;
|
||||
}
|
||||
};
|
||||
return AirInterpreter.create(handler, peerId, logLevel, logFn);
|
||||
};
|
||||
|
||||
export class ClientImpl implements FluenceClient {
|
||||
readonly selfPeerIdFull: PeerId;
|
||||
|
||||
private requests: Map<string, RequestFlow> = new Map();
|
||||
private currentRequestId: string | null = null;
|
||||
private watchDog;
|
||||
|
||||
get relayPeerId(): PeerIdB58 | undefined {
|
||||
return this.connection?.nodePeerId.toB58String();
|
||||
}
|
||||
|
||||
get selfPeerId(): PeerIdB58 {
|
||||
return this.selfPeerIdFull.toB58String();
|
||||
}
|
||||
|
||||
get isConnected(): boolean {
|
||||
return this.connection?.isConnected();
|
||||
}
|
||||
|
||||
private connection: FluenceConnection;
|
||||
private interpreter: AirInterpreter;
|
||||
|
||||
constructor(selfPeerIdFull: PeerId) {
|
||||
this.selfPeerIdFull = selfPeerIdFull;
|
||||
this.callServiceHandler = makeDefaultClientHandler();
|
||||
}
|
||||
|
||||
callServiceHandler: CallServiceHandler;
|
||||
|
||||
async disconnect(): Promise<void> {
|
||||
if (this.connection) {
|
||||
await this.connection.disconnect();
|
||||
}
|
||||
this.clearWathcDog();
|
||||
this.requests.forEach((r) => {
|
||||
r.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
async initAirInterpreter(): Promise<void> {
|
||||
this.interpreter = await createClient(this.interpreterCallback.bind(this), this.selfPeerId);
|
||||
}
|
||||
|
||||
async connect(multiaddr: string | Multiaddr, options?: FluenceConnectionOptions): Promise<void> {
|
||||
multiaddr = new Multiaddr(multiaddr);
|
||||
|
||||
const nodePeerId = multiaddr.getPeerId();
|
||||
if (!nodePeerId) {
|
||||
throw Error("'multiaddr' did not contain a valid peer id");
|
||||
}
|
||||
|
||||
if (this.connection) {
|
||||
await this.connection.disconnect();
|
||||
}
|
||||
|
||||
const node = PeerId.createFromB58String(nodePeerId);
|
||||
const connection = new FluenceConnection(
|
||||
multiaddr,
|
||||
node,
|
||||
this.selfPeerIdFull,
|
||||
this.executeIncomingParticle.bind(this),
|
||||
);
|
||||
await connection.connect(options);
|
||||
this.connection = connection;
|
||||
this.initWatchDog();
|
||||
}
|
||||
|
||||
async initiateFlow(request: RequestFlow): Promise<void> {
|
||||
// setting `relayVariableName` here. If the client is not connected (i.e it is created as local) then there is no relay
|
||||
request.handler.on(loadVariablesService, loadRelayFn, () => {
|
||||
return this.relayPeerId || '';
|
||||
});
|
||||
await request.initState(this.selfPeerIdFull);
|
||||
|
||||
logParticle(log.debug, 'executing local particle', request.getParticle());
|
||||
request.handler.combineWith(this.callServiceHandler);
|
||||
this.requests.set(request.id, request);
|
||||
|
||||
this.processRequest(request);
|
||||
}
|
||||
|
||||
async executeIncomingParticle(particle: Particle) {
|
||||
logParticle(log.debug, 'external particle received', particle);
|
||||
|
||||
let request = this.requests.get(particle.id);
|
||||
if (request) {
|
||||
request.receiveUpdate(particle);
|
||||
} else {
|
||||
request = RequestFlow.createExternal(particle);
|
||||
request.handler.combineWith(this.callServiceHandler);
|
||||
}
|
||||
this.requests.set(request.id, request);
|
||||
|
||||
await this.processRequest(request);
|
||||
}
|
||||
|
||||
private processRequest(request: RequestFlow) {
|
||||
try {
|
||||
this.currentRequestId = request.id;
|
||||
request.execute(this.interpreter, this.connection, this.relayPeerId);
|
||||
} catch (err) {
|
||||
log.error('particle processing failed: ' + err);
|
||||
} finally {
|
||||
this.currentRequestId = null;
|
||||
}
|
||||
}
|
||||
|
||||
private interpreterCallback: ParticleHandler = (
|
||||
serviceId: string,
|
||||
fnName: string,
|
||||
args: any[],
|
||||
tetraplets: SecurityTetraplet[][],
|
||||
): CallServiceResult => {
|
||||
if (this.currentRequestId === null) {
|
||||
throw Error('current request can`t be null here');
|
||||
}
|
||||
|
||||
const request = this.requests.get(this.currentRequestId);
|
||||
const res = request.handler.execute({
|
||||
serviceId,
|
||||
fnName,
|
||||
args,
|
||||
tetraplets,
|
||||
particleContext: {
|
||||
particleId: request.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (res.result === undefined) {
|
||||
log.error(
|
||||
`Call to serviceId=${serviceId} fnName=${fnName} unexpectedly returned undefined result, falling back to null`,
|
||||
);
|
||||
res.result = null;
|
||||
}
|
||||
|
||||
return {
|
||||
ret_code: res.retCode,
|
||||
result: JSON.stringify(res.result),
|
||||
};
|
||||
};
|
||||
|
||||
private initWatchDog() {
|
||||
this.watchDog = setInterval(() => {
|
||||
for (let key in this.requests.keys) {
|
||||
if (this.requests.get(key).hasExpired()) {
|
||||
this.requests.delete(key);
|
||||
}
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
private clearWathcDog() {
|
||||
clearInterval(this.watchDog);
|
||||
}
|
||||
}
|
@ -118,7 +118,8 @@ export class FluenceConnection {
|
||||
|
||||
try {
|
||||
await this.node.dial(this.address);
|
||||
} catch (e) {
|
||||
} catch (e1) {
|
||||
const e = e1 as any;
|
||||
if (e.name === 'AggregateError' && e._errors.length === 1) {
|
||||
const error = e._errors[0];
|
||||
throw `Error dialing node ${this.address}:\n${error.code}\n${error.message}`;
|
||||
|
325
src/internal/FluencePeer.ts
Normal file
325
src/internal/FluencePeer.ts
Normal file
@ -0,0 +1,325 @@
|
||||
import { AirInterpreter, CallServiceResult, LogLevel, ParticleHandler, SecurityTetraplet } from '@fluencelabs/avm';
|
||||
import log from 'loglevel';
|
||||
import { Multiaddr } from 'multiaddr';
|
||||
import PeerId from 'peer-id';
|
||||
import { CallServiceHandler } from './CallServiceHandler';
|
||||
import { PeerIdB58 } from './commonTypes';
|
||||
import makeDefaultClientHandler from './defaultClientHandler';
|
||||
import { FluenceConnection, FluenceConnectionOptions } from './FluenceConnection';
|
||||
import { logParticle, Particle } from './particle';
|
||||
import { KeyPair } from './KeyPair';
|
||||
import { RequestFlow } from './RequestFlow';
|
||||
import { loadRelayFn, loadVariablesService } from './RequestFlowBuilder';
|
||||
import { createInterpreter } from './utils';
|
||||
|
||||
/**
|
||||
* Node of the Fluence detwork specified as a pair of node's multiaddr and it's peer id
|
||||
*/
|
||||
type Node = {
|
||||
peerId: PeerIdB58;
|
||||
multiaddr: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enum representing the log level used in Aqua VM.
|
||||
* Possible values: 'info', 'trace', 'debug', 'info', 'warn', 'error', 'off';
|
||||
*/
|
||||
export type AvmLoglevel = LogLevel;
|
||||
|
||||
/**
|
||||
* Configuration used when initiating Fluence Peer
|
||||
*/
|
||||
export interface PeerConfig {
|
||||
/**
|
||||
* Node in Fluence network to connect to.
|
||||
* Can be in the form of:
|
||||
* - string: multiaddr in string format
|
||||
* - Multiaddr: multiaddr object, @see https://github.com/multiformats/js-multiaddr
|
||||
* - Node: node structure, @see Node
|
||||
* If not specified the will work locally and would not be able to send or receive particles.
|
||||
*/
|
||||
connectTo?: string | Multiaddr | Node;
|
||||
|
||||
avmLogLevel?: AvmLoglevel;
|
||||
|
||||
/**
|
||||
* Specify the KeyPair to be used to identify the Fluence Peer.
|
||||
* Will be generated randomly if not specified
|
||||
*/
|
||||
KeyPair?: KeyPair;
|
||||
|
||||
/**
|
||||
* When the peer established the connection to the network it sends a ping-like message to check if it works correctly.
|
||||
* The options allows to specify the timeout for that message in milliseconds.
|
||||
* If not specified the default timeout will be used
|
||||
*/
|
||||
checkConnectionTimeoutMs?: number;
|
||||
|
||||
/**
|
||||
* When the peer established the connection to the network it sends a ping-like message to check if it works correctly.
|
||||
* If set to true, the ping-like message will be skipped
|
||||
* Default: false
|
||||
*/
|
||||
skipCheckConnection?: boolean;
|
||||
|
||||
/**
|
||||
* The dialing timeout in milliseconds
|
||||
*/
|
||||
dialTimeoutMs?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about Fluence Peer connection
|
||||
*/
|
||||
interface ConnectionInfo {
|
||||
/**
|
||||
* Is the peer connected to network or not
|
||||
*/
|
||||
isConnected: Boolean;
|
||||
|
||||
/**
|
||||
* The Peer's identification in the Fluence network
|
||||
*/
|
||||
selfPeerId: PeerIdB58;
|
||||
|
||||
/**
|
||||
* The relays's peer id to which the peer is connected to
|
||||
*/
|
||||
connectedRelay: PeerIdB58 | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class implements the Fluence protocol for javascript-based environments.
|
||||
* It provides all the necessary features to communicate with Fluence network
|
||||
*/
|
||||
export class FluencePeer {
|
||||
/**
|
||||
* Creates a new Fluence Peer instance. Does not start the workflows.
|
||||
* In order to work with the Peer it has to be initialized with the `init` method
|
||||
*/
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* Get the information about Fluence Peer connections
|
||||
*/
|
||||
get connectionInfo(): ConnectionInfo {
|
||||
const isConnected = this._connection?.isConnected();
|
||||
return {
|
||||
isConnected: isConnected,
|
||||
selfPeerId: this._selfPeerId,
|
||||
connectedRelay: this._relayPeerId || null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the peer: starts the Aqua VM, initializes the default call service handlers
|
||||
* and (optionally) connect to the Fluence network
|
||||
* @param config - object specifying peer configuration
|
||||
*/
|
||||
async init(config?: PeerConfig): Promise<void> {
|
||||
if (config?.KeyPair) {
|
||||
this._keyPair = config!.KeyPair;
|
||||
} else {
|
||||
this._keyPair = await KeyPair.randomEd25519();
|
||||
}
|
||||
|
||||
await this._initAirInterpreter(config?.avmLogLevel || 'off');
|
||||
|
||||
this._callServiceHandler = makeDefaultClientHandler();
|
||||
|
||||
if (config?.connectTo) {
|
||||
let theAddress: Multiaddr;
|
||||
let fromNode = (config.connectTo as any).multiaddr;
|
||||
if (fromNode) {
|
||||
theAddress = new Multiaddr(fromNode);
|
||||
} else {
|
||||
theAddress = new Multiaddr(config.connectTo as string);
|
||||
}
|
||||
|
||||
await this._connect(theAddress);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninitializes the peer: stops all the underltying workflows, stops the Aqua VM
|
||||
* and disconnects from the Fluence network
|
||||
*/
|
||||
async uninit() {
|
||||
await this._disconnect();
|
||||
this._callServiceHandler = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default Fluence peer instance. The default peer is used automatically in all the functions generated
|
||||
* by the Aqua compiler if not specified otherwise.
|
||||
*/
|
||||
static get default(): FluencePeer {
|
||||
return this._default;
|
||||
}
|
||||
|
||||
// internal api
|
||||
|
||||
/**
|
||||
* Does not intended to be used manually. Subject to change
|
||||
*/
|
||||
get internals() {
|
||||
return {
|
||||
initiateFlow: this._initiateFlow.bind(this),
|
||||
callServiceHandler: this._callServiceHandler,
|
||||
};
|
||||
}
|
||||
|
||||
// private
|
||||
|
||||
private async _initiateFlow(request: RequestFlow): Promise<void> {
|
||||
// setting `relayVariableName` here. If the client is not connected (i.e it is created as local) then there is no relay
|
||||
request.handler.on(loadVariablesService, loadRelayFn, () => {
|
||||
return this._relayPeerId || '';
|
||||
});
|
||||
await request.initState(this._keyPair.Libp2pPeerId);
|
||||
|
||||
logParticle(log.debug, 'executing local particle', request.getParticle());
|
||||
request.handler.combineWith(this._callServiceHandler);
|
||||
this._requests.set(request.id, request);
|
||||
|
||||
this._processRequest(request);
|
||||
}
|
||||
|
||||
private _callServiceHandler: CallServiceHandler;
|
||||
|
||||
private static _default: FluencePeer = new FluencePeer();
|
||||
|
||||
private _keyPair: KeyPair;
|
||||
private _requests: Map<string, RequestFlow> = new Map();
|
||||
private _currentRequestId: string | null = null;
|
||||
private _watchdog;
|
||||
|
||||
private _connection: FluenceConnection;
|
||||
private _interpreter: AirInterpreter;
|
||||
|
||||
private async _initAirInterpreter(logLevel: AvmLoglevel): Promise<void> {
|
||||
this._interpreter = await createInterpreter(this._interpreterCallback.bind(this), this._selfPeerId, logLevel);
|
||||
}
|
||||
|
||||
private async _connect(multiaddr: Multiaddr, options?: FluenceConnectionOptions): Promise<void> {
|
||||
const nodePeerId = multiaddr.getPeerId();
|
||||
if (!nodePeerId) {
|
||||
throw Error("'multiaddr' did not contain a valid peer id");
|
||||
}
|
||||
|
||||
if (this._connection) {
|
||||
await this._connection.disconnect();
|
||||
}
|
||||
|
||||
const node = PeerId.createFromB58String(nodePeerId);
|
||||
const connection = new FluenceConnection(
|
||||
multiaddr,
|
||||
node,
|
||||
this._keyPair.Libp2pPeerId,
|
||||
this._executeIncomingParticle.bind(this),
|
||||
);
|
||||
await connection.connect(options);
|
||||
this._connection = connection;
|
||||
this._initWatchDog();
|
||||
}
|
||||
|
||||
private async _disconnect(): Promise<void> {
|
||||
if (this._connection) {
|
||||
await this._connection.disconnect();
|
||||
}
|
||||
this._clearWathcDog();
|
||||
this._requests.forEach((r) => {
|
||||
r.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
private get _selfPeerId(): PeerIdB58 {
|
||||
return this._keyPair.Libp2pPeerId.toB58String();
|
||||
}
|
||||
|
||||
private get _relayPeerId(): PeerIdB58 | undefined {
|
||||
return this._connection?.nodePeerId.toB58String();
|
||||
}
|
||||
|
||||
private async _executeIncomingParticle(particle: Particle) {
|
||||
logParticle(log.debug, 'incoming particle received', particle);
|
||||
|
||||
let request = this._requests.get(particle.id);
|
||||
if (request) {
|
||||
await request.receiveUpdate(particle);
|
||||
} else {
|
||||
request = RequestFlow.createExternal(particle);
|
||||
request.handler.combineWith(this._callServiceHandler);
|
||||
}
|
||||
this._requests.set(request.id, request);
|
||||
|
||||
await this._processRequest(request);
|
||||
}
|
||||
|
||||
private _processRequest(request: RequestFlow) {
|
||||
try {
|
||||
this._currentRequestId = request.id;
|
||||
request.execute(this._interpreter, this._connection, this._relayPeerId);
|
||||
} catch (err) {
|
||||
log.error('particle processing failed: ' + err);
|
||||
} finally {
|
||||
this._currentRequestId = null;
|
||||
}
|
||||
}
|
||||
|
||||
private _interpreterCallback: ParticleHandler = (
|
||||
serviceId: string,
|
||||
fnName: string,
|
||||
args: any[],
|
||||
tetraplets: SecurityTetraplet[][],
|
||||
): CallServiceResult => {
|
||||
if (this._currentRequestId === null) {
|
||||
throw Error('current request can`t be null here');
|
||||
}
|
||||
|
||||
const request = this._requests.get(this._currentRequestId);
|
||||
const particle = request.getParticle();
|
||||
if (particle === null) {
|
||||
throw new Error("particle can't be null here, current request id: " + this._currentRequestId);
|
||||
}
|
||||
const res = request.handler.execute({
|
||||
serviceId,
|
||||
fnName,
|
||||
args,
|
||||
tetraplets,
|
||||
particleContext: {
|
||||
particleId: request.id,
|
||||
initPeerId: particle.init_peer_id,
|
||||
timestamp: particle.timestamp,
|
||||
ttl: particle.ttl,
|
||||
signature: particle.signature,
|
||||
},
|
||||
});
|
||||
|
||||
if (res.result === undefined) {
|
||||
log.error(
|
||||
`Call to serviceId=${serviceId} fnName=${fnName} unexpectedly returned undefined result, falling back to null. Particle id=${request.id}`,
|
||||
);
|
||||
res.result = null;
|
||||
}
|
||||
|
||||
return {
|
||||
ret_code: res.retCode,
|
||||
result: JSON.stringify(res.result),
|
||||
};
|
||||
};
|
||||
|
||||
private _initWatchDog() {
|
||||
this._watchdog = setInterval(() => {
|
||||
for (let key in this._requests.keys) {
|
||||
if (this._requests.get(key).hasExpired()) {
|
||||
this._requests.delete(key);
|
||||
}
|
||||
}
|
||||
}, 5000); // TODO: make configurable
|
||||
}
|
||||
|
||||
private _clearWathcDog() {
|
||||
clearInterval(this._watchdog);
|
||||
}
|
||||
}
|
60
src/internal/KeyPair.ts
Normal file
60
src/internal/KeyPair.ts
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
|
||||
import * as PeerId from 'peer-id';
|
||||
import * as base64 from 'base64-js';
|
||||
import * as ed from 'noble-ed25519';
|
||||
import { keys } from 'libp2p-crypto';
|
||||
|
||||
export class KeyPair {
|
||||
/**
|
||||
* @deprecated
|
||||
* Key pair in libp2p format. Used for backward compatibility with the current FluencePeer implementation
|
||||
*/
|
||||
public Libp2pPeerId: PeerId;
|
||||
|
||||
/**
|
||||
* Generates a new KeyPair from base64 string contatining the 32 byte Ed25519 secret key
|
||||
* @returns - Promise with the created KeyPair
|
||||
*/
|
||||
static async fromEd25519SK(sk: string): Promise<KeyPair> {
|
||||
// deserialize secret key from base64
|
||||
const bytes = base64.toByteArray(sk);
|
||||
// calculate ed25519 public key
|
||||
const publicKey = await ed.getPublicKey(bytes);
|
||||
// concatenate secret + public because that's what libp2p-crypto expects
|
||||
const privateAndPublicKeysArray = new Uint8Array([...bytes, ...publicKey]);
|
||||
// deserialize keys.supportedKeys.Ed25519PrivateKey
|
||||
const privateKey = await keys.supportedKeys.ed25519.unmarshalEd25519PrivateKey(privateAndPublicKeysArray);
|
||||
// serialize it to protobuf encoding because that's what PeerId expects
|
||||
const protobuf = keys.marshalPrivateKey(privateKey);
|
||||
// deserialize PeerId from protobuf encoding
|
||||
const lib2p2Pid = await PeerId.createFromPrivKey(protobuf);
|
||||
const res = new KeyPair();
|
||||
res.Libp2pPeerId = lib2p2Pid;
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new KeyPair with random secret key
|
||||
* @returns - Promise with the created KeyPair
|
||||
*/
|
||||
static async randomEd25519(): Promise<KeyPair> {
|
||||
const res = new KeyPair();
|
||||
res.Libp2pPeerId = await PeerId.create({ keyType: 'Ed25519' });
|
||||
return res;
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import { CallServiceHandler } from './CallServiceHandler';
|
||||
import { PeerIdB58 } from './commonTypes';
|
||||
import { FluenceConnection } from './FluenceConnection';
|
||||
import { Particle, genUUID, logParticle } from './particle';
|
||||
import { ParticleDataToString } from './utils';
|
||||
|
||||
export const DEFAULT_TTL = 7000;
|
||||
|
||||
|
@ -1,342 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
|
||||
import { FluenceClient } from '../FluenceClient';
|
||||
import { ModuleConfig } from './moduleConfig';
|
||||
import { RequestFlowBuilder } from './RequestFlowBuilder';
|
||||
|
||||
const nodeIdentityCall = (client: FluenceClient): string => {
|
||||
return `(call "${client.relayPeerId}" ("op" "identity") [])`;
|
||||
};
|
||||
|
||||
const requestResponse = async <T>(
|
||||
client: FluenceClient,
|
||||
name: string,
|
||||
call: (nodeId: string) => string,
|
||||
returnValue: string,
|
||||
data: Map<string, any>,
|
||||
handleResponse: (args: any[]) => T,
|
||||
nodeId?: string,
|
||||
ttl?: number,
|
||||
): Promise<T> => {
|
||||
if (!ttl) {
|
||||
ttl = 10000;
|
||||
}
|
||||
|
||||
if (!nodeId) {
|
||||
nodeId = client.relayPeerId;
|
||||
}
|
||||
|
||||
let serviceCall = call(nodeId);
|
||||
|
||||
let script = `(seq
|
||||
${nodeIdentityCall(client)}
|
||||
(seq
|
||||
(seq
|
||||
${serviceCall}
|
||||
${nodeIdentityCall(client)}
|
||||
)
|
||||
(call "${client.selfPeerId}" ("_callback" "${name}") [${returnValue}])
|
||||
)
|
||||
)
|
||||
`;
|
||||
|
||||
const [request, promise] = new RequestFlowBuilder()
|
||||
.withRawScript(script)
|
||||
.withVariables(data)
|
||||
.withTTL(ttl)
|
||||
.buildAsFetch<any[]>('_callback', name);
|
||||
await client.initiateFlow(request);
|
||||
const res = await promise;
|
||||
return handleResponse(res);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all available modules hosted on a connected relay. @deprecated prefer using raw Particles instead
|
||||
* @param { FluenceClient } client - The Fluence Client instance.
|
||||
* @returns { Array<string> } - list of available modules on the connected relay
|
||||
*/
|
||||
export const getModules = async (client: FluenceClient, ttl?: number): Promise<string[]> => {
|
||||
let callbackFn = 'getModules';
|
||||
const [req, promise] = new RequestFlowBuilder()
|
||||
.withRawScript(
|
||||
`
|
||||
(seq
|
||||
(call __relay ("dist" "list_modules") [] result)
|
||||
(call myPeerId ("_callback" "${callbackFn}") [result])
|
||||
)
|
||||
`,
|
||||
)
|
||||
.withVariables({
|
||||
__relay: client.relayPeerId,
|
||||
myPeerId: client.selfPeerId,
|
||||
})
|
||||
.withTTL(ttl)
|
||||
.buildAsFetch<[string[]]>('_callback', callbackFn);
|
||||
client.initiateFlow(req);
|
||||
|
||||
const [res] = await promise;
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all available interfaces hosted on a connected relay. @deprecated prefer using raw Particles instead
|
||||
* @param { FluenceClient } client - The Fluence Client instance.
|
||||
* @returns { Array<string> } - list of available interfaces on the connected relay
|
||||
*/
|
||||
export const getInterfaces = async (client: FluenceClient, ttl?: number): Promise<string[]> => {
|
||||
let callbackFn = 'getInterfaces';
|
||||
const [req, promise] = new RequestFlowBuilder()
|
||||
.withRawScript(
|
||||
`
|
||||
(seq
|
||||
(seq
|
||||
(seq
|
||||
(call relay ("srv" "list") [] services)
|
||||
(call relay ("op" "identity") [] $interfaces)
|
||||
)
|
||||
(fold services s
|
||||
(seq
|
||||
(call relay ("srv" "get_interface") [s.$.id!] $interfaces)
|
||||
(next s)
|
||||
)
|
||||
)
|
||||
)
|
||||
(call myPeerId ("_callback" "${callbackFn}") [$interfaces])
|
||||
)
|
||||
`,
|
||||
)
|
||||
.withVariables({
|
||||
relay: client.relayPeerId,
|
||||
myPeerId: client.selfPeerId,
|
||||
})
|
||||
.withTTL(ttl)
|
||||
.buildAsFetch<[string[]]>('_callback', callbackFn);
|
||||
|
||||
client.initiateFlow(req);
|
||||
|
||||
const [res] = await promise;
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a script to add module to a relay. Waiting for a response from a relay. @deprecated prefer using raw Particles instead
|
||||
* @param { FluenceClient } client - The Fluence Client instance.
|
||||
* @param { string } name - Name of the uploaded module
|
||||
* @param { string } moduleBase64 - Base64 content of the module
|
||||
* @param { ModuleConfig } config - Module config
|
||||
*/
|
||||
export const uploadModule = async (
|
||||
client: FluenceClient,
|
||||
name: string,
|
||||
moduleBase64: string,
|
||||
config?: ModuleConfig,
|
||||
ttl?: number,
|
||||
): Promise<void> => {
|
||||
if (!config) {
|
||||
config = {
|
||||
name: name,
|
||||
mem_pages_count: 100,
|
||||
logger_enabled: true,
|
||||
wasi: {
|
||||
envs: {},
|
||||
preopened_files: ['/tmp'],
|
||||
mapped_dirs: {},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
let data = new Map();
|
||||
data.set('module_bytes', moduleBase64);
|
||||
data.set('module_config', config);
|
||||
data.set('__relay', client.relayPeerId);
|
||||
data.set('myPeerId', client.selfPeerId);
|
||||
|
||||
const [req, promise] = new RequestFlowBuilder()
|
||||
.withRawScript(
|
||||
`
|
||||
(seq
|
||||
(call __relay ("dist" "add_module") [module_bytes module_config] result)
|
||||
(call myPeerId ("_callback" "getModules") [result])
|
||||
|
||||
)
|
||||
`,
|
||||
)
|
||||
.withVariables(data)
|
||||
.withTTL(ttl)
|
||||
.buildAsFetch<[string[]]>('_callback', 'getModules');
|
||||
|
||||
await client.initiateFlow(req);
|
||||
await promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a script to add module to a relay. Waiting for a response from a relay. @deprecated prefer using raw Particles instead
|
||||
* @param { FluenceClient } client - The Fluence Client instance.
|
||||
* @param { string } name - Name of the blueprint
|
||||
* @param { Array<string> } dependencies - Array of it's dependencies
|
||||
* @param {[string]} blueprintId - Optional blueprint ID
|
||||
* @param {[string]} nodeId - Optional node peer id to deploy blueprint to
|
||||
* @param {[number]} ttl - Optional ttl for the particle which does the job
|
||||
* @returns { string } - Created blueprint ID
|
||||
*/
|
||||
export const addBlueprint = async (
|
||||
client: FluenceClient,
|
||||
name: string,
|
||||
dependencies: string[],
|
||||
blueprintId?: string,
|
||||
nodeId?: string,
|
||||
ttl?: number,
|
||||
): Promise<string> => {
|
||||
let returnValue = 'blueprint_id';
|
||||
let call = (nodeId: string) => `(call "${nodeId}" ("dist" "add_blueprint") [blueprint] ${returnValue})`;
|
||||
|
||||
let data = new Map();
|
||||
data.set('blueprint', { name: name, dependencies: dependencies, id: blueprintId });
|
||||
|
||||
return requestResponse(
|
||||
client,
|
||||
'addBlueprint',
|
||||
call,
|
||||
returnValue,
|
||||
data,
|
||||
(args: any[]) => args[0] as string,
|
||||
nodeId,
|
||||
ttl,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a script to create a service on the connected relay. Waiting for a response from the relay. @deprecated prefer using raw Particles instead
|
||||
* @param { FluenceClient } client - The Fluence Client instance.
|
||||
* @param {string} blueprintId - The blueprint of the service
|
||||
* @param {[string]} nodeId - Optional node peer id to deploy service to
|
||||
* @param {[number]} ttl - Optional ttl for the particle which does the job
|
||||
* @returns { string } - Created service ID
|
||||
*/
|
||||
export const createService = async (
|
||||
client: FluenceClient,
|
||||
blueprintId: string,
|
||||
nodeId?: string,
|
||||
ttl?: number,
|
||||
): Promise<string> => {
|
||||
let returnValue = 'service_id';
|
||||
let call = (nodeId: string) => `(call "${nodeId}" ("srv" "create") [blueprint_id] ${returnValue})`;
|
||||
|
||||
let data = new Map();
|
||||
data.set('blueprint_id', blueprintId);
|
||||
|
||||
return requestResponse(
|
||||
client,
|
||||
'createService',
|
||||
call,
|
||||
returnValue,
|
||||
data,
|
||||
(args: any[]) => args[0] as string,
|
||||
nodeId,
|
||||
ttl,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all available blueprints hosted on a connected relay. @deprecated prefer using raw Particles instead
|
||||
* @param { FluenceClient } client - The Fluence Client instance.
|
||||
* @param {[string]} nodeId - Optional node peer id to get available blueprints from
|
||||
* @param {[string]} nodeId - Optional node peer id to deploy service to
|
||||
* @param {[number]} ttl - Optional ttl for the particle which does the job
|
||||
* @returns { Array<object> } - List of available blueprints
|
||||
*/
|
||||
export const getBlueprints = async (
|
||||
client: FluenceClient,
|
||||
nodeId?: string,
|
||||
ttl?: number,
|
||||
): Promise<[{ dependencies; id: string; name: string }]> => {
|
||||
let returnValue = 'blueprints';
|
||||
let call = (nodeId: string) => `(call "${nodeId}" ("dist" "list_blueprints") [] ${returnValue})`;
|
||||
|
||||
return requestResponse(
|
||||
client,
|
||||
'getBlueprints',
|
||||
call,
|
||||
returnValue,
|
||||
new Map(),
|
||||
(args: any[]) => args[0],
|
||||
nodeId,
|
||||
ttl,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get relays neighborhood. @deprecated prefer using raw Particles instead
|
||||
* @param { FluenceClient } client - The Fluence Client instance.
|
||||
* @param {[string]} nodeId - Optional node peer id to get neighborhood from
|
||||
* @param {[number]} ttl - Optional ttl for the particle which does the job
|
||||
* @returns { Array<string> } - List of peer ids of neighbors of the node
|
||||
*/
|
||||
export const neighborhood = async (client: FluenceClient, nodeId?: string, ttl?: number): Promise<string[]> => {
|
||||
let returnValue = 'neighborhood';
|
||||
let call = (nodeId: string) => `(call "${nodeId}" ("dht" "neighborhood") [node] ${returnValue})`;
|
||||
|
||||
let data = new Map();
|
||||
if (nodeId) data.set('node', nodeId);
|
||||
|
||||
return requestResponse(client, 'neighborhood', call, returnValue, data, (args) => args[0] as string[], nodeId, ttl);
|
||||
};
|
||||
|
||||
/**
|
||||
* Upload an AIR script, that will be runned in a loop on a node. @deprecated prefer using raw Particles instead
|
||||
* @param { FluenceClient } client - The Fluence Client instance.
|
||||
* @param {[string]} script - script to upload
|
||||
* @param period how often start script processing, in seconds
|
||||
* @param {[string]} nodeId - Optional node peer id to get neighborhood from
|
||||
* @param {[number]} ttl - Optional ttl for the particle which does the job
|
||||
* @returns {[string]} - script id
|
||||
*/
|
||||
export const addScript = async (
|
||||
client: FluenceClient,
|
||||
script: string,
|
||||
period?: number,
|
||||
nodeId?: string,
|
||||
ttl?: number,
|
||||
): Promise<string> => {
|
||||
let returnValue = 'id';
|
||||
let periodV = '';
|
||||
if (period) periodV = period.toString();
|
||||
let call = (nodeId: string) => `(call "${nodeId}" ("script" "add") [script ${periodV}] ${returnValue})`;
|
||||
|
||||
let data = new Map();
|
||||
data.set('script', script);
|
||||
if (period) data.set('period', period);
|
||||
|
||||
return requestResponse(client, 'addScript', call, returnValue, data, (args) => args[0] as string, nodeId, ttl);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove an AIR script from a node. @deprecated prefer using raw Particles instead
|
||||
* @param { FluenceClient } client - The Fluence Client instance.
|
||||
* @param {[string]} id - id of a script
|
||||
* @param {[string]} nodeId - Optional node peer id to get neighborhood from
|
||||
* @param {[number]} ttl - Optional ttl for the particle which does the job
|
||||
*/
|
||||
export const removeScript = async (client: FluenceClient, id: string, nodeId?: string, ttl?: number): Promise<void> => {
|
||||
let returnValue = 'empty';
|
||||
let call = (nodeId: string) => `(call "${nodeId}" ("script" "remove") [script_id] ${returnValue})`;
|
||||
|
||||
let data = new Map();
|
||||
data.set('script_id', id);
|
||||
|
||||
return requestResponse(client, 'removeScript', call, returnValue, data, (args) => {}, nodeId, ttl);
|
||||
};
|
@ -14,4 +14,44 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { SecurityTetraplet } from '@fluencelabs/avm';
|
||||
|
||||
/**
|
||||
* Peer ID's id as a base58 string (multihash/CIDv0).
|
||||
*/
|
||||
export type PeerIdB58 = string;
|
||||
|
||||
/**
|
||||
* Additional information about a service call
|
||||
*/
|
||||
export interface CallParams<ArgName extends string | null> {
|
||||
/**
|
||||
* The identifier of particle which triggered the call
|
||||
*/
|
||||
particleId: string;
|
||||
|
||||
/**
|
||||
* The peer id which created the particle
|
||||
*/
|
||||
initPeerId: PeerIdB58;
|
||||
|
||||
/**
|
||||
* Particle's timestamp when it was created
|
||||
*/
|
||||
timeStamp: number;
|
||||
|
||||
/**
|
||||
* Time to live in milliseconds. The time after the particle should be expired
|
||||
*/
|
||||
ttl: number;
|
||||
|
||||
/**
|
||||
* Particle's signature
|
||||
*/
|
||||
signature: string;
|
||||
|
||||
/**
|
||||
* Security tetraplets
|
||||
*/
|
||||
tetraplets: { [key in ArgName]: SecurityTetraplet[] };
|
||||
}
|
||||
|
5
src/internal/compilerSupport/v1.ts
Normal file
5
src/internal/compilerSupport/v1.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export { FluencePeer } from '../FluencePeer';
|
||||
export { ResultCodes } from '../../internal/CallServiceHandler';
|
||||
export { RequestFlow } from '../../internal/RequestFlow';
|
||||
export { RequestFlowBuilder } from '../../internal/RequestFlowBuilder';
|
||||
export { CallParams } from '../commonTypes';
|
@ -4,9 +4,9 @@ import {
|
||||
CallServiceHandler,
|
||||
CallServiceResult,
|
||||
CallServiceResultType,
|
||||
errorHandler,
|
||||
Middleware,
|
||||
} from './CallServiceHandler';
|
||||
import { errorHandler } from './defaultMiddlewares';
|
||||
|
||||
const makeDefaultClientHandler = (): CallServiceHandler => {
|
||||
const success = (resp: CallServiceResult, result: CallServiceResultType) => {
|
||||
|
14
src/internal/defaultMiddlewares.ts
Normal file
14
src/internal/defaultMiddlewares.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { CallServiceArg, CallServiceData, CallServiceResult, Middleware, ResultCodes } from './CallServiceHandler';
|
||||
|
||||
/**
|
||||
* Error catching middleware
|
||||
*/
|
||||
export const errorHandler: Middleware = (req: CallServiceData, resp: CallServiceResult, next: Function): void => {
|
||||
try {
|
||||
next();
|
||||
} catch (e) {
|
||||
resp.retCode = ResultCodes.exceptionInHandler;
|
||||
resp.result = `Handler failed. fnName="${req.fnName}" serviceId="${req.serviceId}" error: ${e.toString()}`;
|
||||
}
|
||||
};
|
||||
1;
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
|
||||
import * as PeerId from 'peer-id';
|
||||
import { decode, encode } from 'bs58';
|
||||
import { keys } from 'libp2p-crypto';
|
||||
|
||||
/**
|
||||
* Converts seed string which back to peer id. Seed string can be obtained by using @see {@link peerIdToSeed} function
|
||||
* @param { string } seed - Seed to convert to peer id
|
||||
* @returns { PeerId } - Peer id
|
||||
*/
|
||||
export const seedToPeerId = async (seed: string): Promise<PeerId> => {
|
||||
const seedArr = decode(seed);
|
||||
const privateKey = await keys.generateKeyPairFromSeed('Ed25519', Uint8Array.from(seedArr), 256);
|
||||
return await PeerId.createFromPrivKey(privateKey.bytes);
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts peer id to a string which can be used to restore back to peer id format with. @see {@link seedToPeerId}
|
||||
* @param { PeerId } peerId - Peer id to convert to seed
|
||||
* @returns { string } - Seed string
|
||||
*/
|
||||
export const peerIdToSeed = (peerId: PeerId): string => {
|
||||
const seedBuf = peerId.privKey.marshal().subarray(0, 32);
|
||||
return encode(seedBuf);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a new peer id with random private key
|
||||
* @returns { Promise<PeerId> } - Promise with the created Peer Id
|
||||
*/
|
||||
export const generatePeerId = async (): Promise<PeerId> => {
|
||||
return await PeerId.create({ keyType: 'Ed25519' });
|
||||
};
|
72
src/internal/utils.ts
Normal file
72
src/internal/utils.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { AirInterpreter, LogLevel as AvmLogLevel } from '@fluencelabs/avm';
|
||||
import log from 'loglevel';
|
||||
import { AvmLoglevel, FluencePeer } from './FluencePeer';
|
||||
import { RequestFlowBuilder } from './RequestFlowBuilder';
|
||||
|
||||
export const createInterpreter = (handler, peerId, logLevel: AvmLoglevel): Promise<AirInterpreter> => {
|
||||
const logFn = (level: AvmLogLevel, msg: string) => {
|
||||
switch (level) {
|
||||
case 'error':
|
||||
log.error(msg);
|
||||
break;
|
||||
|
||||
case 'warn':
|
||||
log.warn(msg);
|
||||
break;
|
||||
|
||||
case 'info':
|
||||
log.info(msg);
|
||||
break;
|
||||
|
||||
case 'debug':
|
||||
case 'trace':
|
||||
log.log(msg);
|
||||
break;
|
||||
}
|
||||
};
|
||||
return AirInterpreter.create(handler, peerId, logLevel, logFn);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks the network connection by sending a ping-like request to relat node
|
||||
* @param { FluenceClient } peer - The Fluence Client instance.
|
||||
*/
|
||||
export const checkConnection = async (peer: FluencePeer, ttl?: number): Promise<boolean> => {
|
||||
if (!peer.connectionInfo.isConnected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const msg = Math.random().toString(36).substring(7);
|
||||
const callbackFn = 'checkConnection';
|
||||
const callbackService = '_callback';
|
||||
|
||||
const [request, promise] = new RequestFlowBuilder()
|
||||
.withRawScript(
|
||||
`(seq
|
||||
(call init_relay ("op" "identity") [msg] result)
|
||||
(call %init_peer_id% ("${callbackService}" "${callbackFn}") [result])
|
||||
)`,
|
||||
)
|
||||
.withTTL(ttl)
|
||||
.withVariables({
|
||||
msg,
|
||||
})
|
||||
.buildAsFetch<[string]>(callbackService, callbackFn);
|
||||
|
||||
await peer.internals.initiateFlow(request);
|
||||
|
||||
try {
|
||||
const [result] = await promise;
|
||||
if (result != msg) {
|
||||
log.warn("unexpected behavior. 'identity' must return the passed arguments.");
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
log.error('Error on establishing connection: ', e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const ParticleDataToString = (data: Uint8Array): string => {
|
||||
return new TextDecoder().decode(Buffer.from(data));
|
||||
};
|
@ -6,6 +6,7 @@
|
||||
],
|
||||
"outDir": "./dist/",
|
||||
"baseUrl": ".",
|
||||
"downlevelIteration": true,
|
||||
"sourceMap": true,
|
||||
"inlineSources": true,
|
||||
"strictFunctionTypes": true,
|
||||
|
Loading…
Reference in New Issue
Block a user