mirror of
https://github.com/fluencelabs/fluence-js.git
synced 2024-12-04 18:00:18 +00:00
Remove frank (#81)
This commit is contained in:
commit
96370bb26f
12
.npmignore
Normal file
12
.npmignore
Normal file
@ -0,0 +1,12 @@
|
||||
.idea
|
||||
.gitignore
|
||||
node_modules
|
||||
types
|
||||
|
||||
src
|
||||
|
||||
tsconfig.json
|
||||
webpack.config.js
|
||||
|
||||
*.js.map
|
||||
bundle
|
52
README.md
Normal file
52
README.md
Normal file
@ -0,0 +1,52 @@
|
||||
# Fluence browser client
|
||||
Browser client for the Fluence network based on the js-libp2p.
|
||||
|
||||
## How to build
|
||||
|
||||
With `npm` installed building could be done as follows:
|
||||
|
||||
```bash
|
||||
npm install fluence
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
Shows how to register and call new service in Fluence network.
|
||||
|
||||
|
||||
Generate new peer ids for clients.
|
||||
```typescript
|
||||
let peerId1 = await Fluence.generatePeerId();
|
||||
let peerId2 = await Fluence.generatePeerId();
|
||||
```
|
||||
|
||||
Establish connections to predefined nodes.
|
||||
|
||||
```typescript
|
||||
let client1 = await Fluence.connect("12D3KooWBUJifCTgaxAUrcM9JysqCcS4CS8tiYH5hExbdWCAoNwb", "104.248.25.59", 9003, peerId1);
|
||||
let client2 = await Fluence.connect("12D3KooWHk9BjDQBUqnavciRPhAYFvqKBe4ZiPPvde7vDaqgn5er", "104.248.25.59", 9002, peerId2);
|
||||
```
|
||||
|
||||
Create a new unique service by the first client that will calculate the sum of two numbers.
|
||||
```typescript
|
||||
let serviceId = "sum-calculator-" + genUUID();
|
||||
|
||||
await client1.registerService(serviceId, async (req) => {
|
||||
let message = {msgId: req.arguments.msgId, result: req.arguments.one + req.arguments.two};
|
||||
|
||||
await client1.sendCall(req.reply_to, message);
|
||||
});
|
||||
```
|
||||
|
||||
Send a request by the second client and print a result. The predicate is required to match a request and a response by `msgId`.
|
||||
```typescript
|
||||
let msgId = "calculate-it-for-me" + genUUID();
|
||||
let req = {one: 12, two: 23, msgId: msgId};
|
||||
|
||||
let predicate = (args: any) => args.msgId && args.msgId === msgId;
|
||||
|
||||
let response = await client2.sendServiceCallWaitResponse(serviceId, req, predicate);
|
||||
|
||||
let result = response.result;
|
||||
console.log(`calculation result is: ${result}`);
|
||||
```
|
9343
package-lock.json
generated
Normal file
9343
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
48
package.json
Normal file
48
package.json
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "fluence",
|
||||
"version": "0.4.9",
|
||||
"description": "the browser js-libp2p client for the Fluence network",
|
||||
"main": "./dist/fluence.js",
|
||||
"typings": "./dist/fluence.d.ts",
|
||||
"scripts": {
|
||||
"test": "mocha -r ts-node/register src/**/*.spec.ts",
|
||||
"test-ts": "ts-mocha -p tsconfig.json src/**/*.spec.ts",
|
||||
"package:build": "NODE_ENV=production webpack && tsc",
|
||||
"compile": "tsc",
|
||||
"start": "webpack-dev-server",
|
||||
"build": "webpack"
|
||||
},
|
||||
"author": "Fluence Labs",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"async": "3.2.0",
|
||||
"base64-js": "1.3.1",
|
||||
"bs58": "4.0.1",
|
||||
"cids": "0.8.0",
|
||||
"it-length-prefixed": "3.0.1",
|
||||
"it-pipe": "1.1.0",
|
||||
"libp2p": "0.27.4",
|
||||
"libp2p-mplex": "0.9.5",
|
||||
"libp2p-secio": "0.12.5",
|
||||
"libp2p-websockets": "0.13.6",
|
||||
"peer-id": "0.13.11",
|
||||
"peer-info": "0.17.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/base64-js": "1.2.5",
|
||||
"@types/bs58": "4.0.1",
|
||||
"@types/chai": "4.2.11",
|
||||
"@types/mocha": "7.0.2",
|
||||
"assert": "2.0.0",
|
||||
"chai": "4.2.0",
|
||||
"clean-webpack-plugin": "3.0.0",
|
||||
"libp2p-ts": "https://github.com/fluencelabs/libp2p-ts.git",
|
||||
"mocha": "7.1.1",
|
||||
"ts-loader": "6.2.2",
|
||||
"ts-mocha": "^7.0.0",
|
||||
"typescript": "3.8.3",
|
||||
"webpack": "4.42.1",
|
||||
"webpack-cli": "3.3.11",
|
||||
"webpack-dev-server": "3.10.3"
|
||||
}
|
||||
}
|
150
src/address.ts
Normal file
150
src/address.ts
Normal file
@ -0,0 +1,150 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020 Fluence Labs Limited
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import * as PeerId from "peer-id";
|
||||
import {encode} from "bs58"
|
||||
|
||||
export interface Address {
|
||||
protocols: Protocol[]
|
||||
}
|
||||
|
||||
export interface Protocol {
|
||||
protocol: ProtocolType,
|
||||
value?: string
|
||||
}
|
||||
|
||||
export enum ProtocolType {
|
||||
Service = "service",
|
||||
Peer = "peer",
|
||||
Signature = "signature",
|
||||
Client = "client"
|
||||
}
|
||||
|
||||
const PROTOCOL = "fluence:";
|
||||
|
||||
export function addressToString(address: Address): string {
|
||||
let addressStr = PROTOCOL;
|
||||
|
||||
for (let addr of address.protocols) {
|
||||
addressStr = addressStr + "/" + addr.protocol;
|
||||
if (addr.value) {
|
||||
addressStr = addressStr + "/" + addr.value;
|
||||
}
|
||||
}
|
||||
|
||||
return addressStr;
|
||||
}
|
||||
|
||||
function protocolWithValue(protocol: ProtocolType, protocolIterator: IterableIterator<[number, string]>): Protocol {
|
||||
|
||||
let protocolValue = protocolIterator.next().value;
|
||||
|
||||
if (!protocolValue || !protocolValue[1]) {
|
||||
throw Error(`protocol '${protocol}' should be with a value`)
|
||||
}
|
||||
|
||||
return {protocol: protocol, value: protocolValue[1]};
|
||||
}
|
||||
|
||||
|
||||
export function parseProtocol(protocol: string, protocolIterator: IterableIterator<[number, string]>): Protocol {
|
||||
protocol = protocol.toLocaleLowerCase();
|
||||
|
||||
switch (protocol) {
|
||||
case ProtocolType.Service:
|
||||
return protocolWithValue(protocol, protocolIterator);
|
||||
case ProtocolType.Client:
|
||||
return protocolWithValue(protocol, protocolIterator);
|
||||
case ProtocolType.Peer:
|
||||
return protocolWithValue(protocol, protocolIterator);
|
||||
case ProtocolType.Signature:
|
||||
return protocolWithValue(protocol, protocolIterator);
|
||||
default:
|
||||
throw Error("cannot parse protocol. Should be 'service|peer|client|signature'");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export async function createRelayAddress(relay: string, peerId: PeerId, withSig: boolean): Promise<Address> {
|
||||
|
||||
let protocols = [
|
||||
{protocol: ProtocolType.Peer, value: relay},
|
||||
{protocol: ProtocolType.Client, value: peerId.toB58String()}
|
||||
];
|
||||
|
||||
if (withSig) {
|
||||
let str = addressToString({protocols: protocols}).replace(PROTOCOL, "");
|
||||
let signature = await peerId.privKey.sign(Buffer.from(str));
|
||||
let signatureStr = encode(signature);
|
||||
|
||||
protocols.push({protocol: ProtocolType.Signature, value: signatureStr});
|
||||
}
|
||||
|
||||
return {
|
||||
protocols: protocols
|
||||
}
|
||||
}
|
||||
|
||||
export function createServiceAddress(service: string): Address {
|
||||
|
||||
let protocol = {protocol: ProtocolType.Service, value: service};
|
||||
|
||||
return {
|
||||
protocols: [protocol]
|
||||
}
|
||||
}
|
||||
|
||||
export function createPeerAddress(peer: string): Address {
|
||||
let protocol = {protocol: ProtocolType.Peer, value: peer};
|
||||
|
||||
return {
|
||||
protocols: [protocol]
|
||||
}
|
||||
}
|
||||
|
||||
export function parseAddress(str: string): Address {
|
||||
str = str.replace("fluence:", "");
|
||||
|
||||
// delete leading slashes
|
||||
str = str.replace(/^\/+/, '');
|
||||
|
||||
let parts = str.split("/");
|
||||
if (parts.length < 1) {
|
||||
throw Error("address parts should not be empty")
|
||||
}
|
||||
|
||||
let protocols: Protocol[] = [];
|
||||
let partsEntries: IterableIterator<[number, string]> = parts.entries();
|
||||
|
||||
while (true) {
|
||||
let result = partsEntries.next();
|
||||
if (result.done) break;
|
||||
let protocol = parseProtocol(result.value[1], partsEntries);
|
||||
protocols.push(protocol);
|
||||
}
|
||||
|
||||
return {
|
||||
protocols: protocols
|
||||
}
|
||||
}
|
55
src/fluence.ts
Normal file
55
src/fluence.ts
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020 Fluence Labs Limited
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import * as PeerInfo from "peer-info";
|
||||
import * as PeerId from "peer-id";
|
||||
import Multiaddr from "multiaddr"
|
||||
import {FluenceClient} from "./fluence_client";
|
||||
|
||||
|
||||
export default class Fluence {
|
||||
|
||||
/**
|
||||
* Generates new peer id with Ed25519 private key.
|
||||
*/
|
||||
static async generatePeerId(): Promise<PeerId> {
|
||||
return await PeerId.create({keyType: "Ed25519"});
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to Fluence node.
|
||||
*
|
||||
* @param multiaddr must contain host peer id
|
||||
* @param peerId your peer id. Should be with a private key. Could be generated by `generatePeerId()` function
|
||||
*/
|
||||
static async connect(multiaddr: string | Multiaddr, peerId: PeerId): Promise<FluenceClient> {
|
||||
let peerInfo = await PeerInfo.create(peerId);
|
||||
|
||||
let client = new FluenceClient(peerInfo);
|
||||
|
||||
await client.connect(multiaddr);
|
||||
|
||||
return client;
|
||||
}
|
||||
}
|
265
src/fluence_client.ts
Normal file
265
src/fluence_client.ts
Normal file
@ -0,0 +1,265 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020 Fluence Labs Limited
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import {Address, createRelayAddress, ProtocolType} from "./address";
|
||||
import {callToString, FunctionCall, genUUID, makeFunctionCall,} from "./function_call";
|
||||
import * as PeerId from "peer-id";
|
||||
import {Services} from "./services";
|
||||
import Multiaddr from "multiaddr"
|
||||
import {Subscriptions} from "./subscriptions";
|
||||
import * as PeerInfo from "peer-info";
|
||||
import {FluenceConnection} from "./fluence_connection";
|
||||
|
||||
export class FluenceClient {
|
||||
private readonly selfPeerInfo: PeerInfo;
|
||||
readonly selfPeerIdStr: string;
|
||||
|
||||
private connection: FluenceConnection;
|
||||
|
||||
private services: Services = new Services();
|
||||
|
||||
private subscriptions: Subscriptions = new Subscriptions();
|
||||
|
||||
constructor(selfPeerInfo: PeerInfo) {
|
||||
this.selfPeerInfo = selfPeerInfo;
|
||||
this.selfPeerIdStr = selfPeerInfo.id.toB58String();
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes call with response from function. Without reply_to field.
|
||||
*/
|
||||
private static responseCall(target: Address, args: any): FunctionCall {
|
||||
return makeFunctionCall(genUUID(), target, args, undefined, "response");
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits a response that match the predicate.
|
||||
*
|
||||
* @param predicate will be applied to each incoming call until it matches
|
||||
*/
|
||||
waitResponse(predicate: (args: any, target: Address, replyTo: Address) => (boolean | undefined)): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// subscribe for responses, to handle response
|
||||
// TODO if there's no conn, reject
|
||||
this.subscribe((args: any, target: Address, replyTo: Address) => {
|
||||
if (predicate(args, target, replyTo)) {
|
||||
resolve(args);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send call and wait a response.
|
||||
*
|
||||
* @param target receiver
|
||||
* @param args message in the call
|
||||
* @param predicate will be applied to each incoming call until it matches
|
||||
*/
|
||||
async sendCallWaitResponse(target: Address, args: any, predicate: (args: any, target: Address, replyTo: Address) => (boolean | undefined)): Promise<any> {
|
||||
await this.sendCall(target, args, true);
|
||||
return this.waitResponse(predicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send call and forget.
|
||||
*
|
||||
* @param target receiver
|
||||
* @param args message in the call
|
||||
* @param reply add a `replyTo` field or not
|
||||
* @param name common field for debug purposes
|
||||
*/
|
||||
async sendCall(target: Address, args: any, reply?: boolean, name?: string) {
|
||||
if (this.connection && this.connection.isConnected()) {
|
||||
await this.connection.sendFunctionCall(target, args, reply, name);
|
||||
} else {
|
||||
throw Error("client is not connected")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send call to the service.
|
||||
*
|
||||
* @param serviceId
|
||||
* @param args message to the service
|
||||
* @param name common field for debug purposes
|
||||
*/
|
||||
async sendServiceCall(serviceId: string, args: any, name?: string) {
|
||||
if (this.connection && this.connection.isConnected()) {
|
||||
await this.connection.sendServiceCall(serviceId, args, name);
|
||||
} else {
|
||||
throw Error("client is not connected")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send call to the service and wait a response matches predicate.
|
||||
*
|
||||
* @param serviceId
|
||||
* @param args message to the service
|
||||
* @param predicate will be applied to each incoming call until it matches
|
||||
*/
|
||||
async sendServiceCallWaitResponse(serviceId: string, args: any, predicate: (args: any, target: Address, replyTo: Address) => (boolean | undefined)): Promise<any> {
|
||||
await this.sendServiceCall(serviceId, args);
|
||||
return await this.waitResponse(predicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle incoming call.
|
||||
* If FunctionCall returns - we should send it as a response.
|
||||
*/
|
||||
handleCall(): (call: FunctionCall) => FunctionCall | undefined {
|
||||
|
||||
let _this = this;
|
||||
|
||||
return (call: FunctionCall) => {
|
||||
console.log("FunctionCall received:");
|
||||
|
||||
// if other side return an error - handle it
|
||||
// TODO do it in the protocol
|
||||
/*if (call.arguments.error) {
|
||||
this.handleError(call);
|
||||
} else {
|
||||
|
||||
}*/
|
||||
|
||||
let target = call.target;
|
||||
|
||||
// the tail of addresses should be you or your service
|
||||
let lastProtocol = target.protocols[target.protocols.length - 1];
|
||||
|
||||
// call all subscriptions for a new call
|
||||
_this.subscriptions.applyToSubscriptions(call);
|
||||
|
||||
switch (lastProtocol.protocol) {
|
||||
case ProtocolType.Service:
|
||||
try {
|
||||
// call of the service, service should handle response sending, error handling, requests to other services
|
||||
let applied = _this.services.applyToService(lastProtocol.value, call);
|
||||
|
||||
// if the request hasn't been applied, there is no such service. Return an error.
|
||||
if (!applied) {
|
||||
console.log(`there is no service ${lastProtocol.value}`);
|
||||
return FluenceClient.responseCall(call.reply_to, {
|
||||
reason: `there is no such service`,
|
||||
msg: call
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// if service throw an error, return it to the sender
|
||||
return FluenceClient.responseCall(call.reply_to, {reason: `error on execution: ${e}`, msg: call});
|
||||
}
|
||||
|
||||
return undefined;
|
||||
case ProtocolType.Client:
|
||||
if (lastProtocol.value === _this.selfPeerIdStr) {
|
||||
console.log(`relay call: ${call}`);
|
||||
} else {
|
||||
console.warn(`this relay call is not for me: ${callToString(call)}`);
|
||||
return FluenceClient.responseCall(call.reply_to, {reason: `this relay call is not for me`, msg: call});
|
||||
}
|
||||
return undefined;
|
||||
case ProtocolType.Peer:
|
||||
if (lastProtocol.value === this.selfPeerIdStr) {
|
||||
console.log(`peer call: ${call}`);
|
||||
} else {
|
||||
console.warn(`this peer call is not for me: ${callToString(call)}`);
|
||||
return FluenceClient.responseCall(call.reply_to, {reason: `this relay call is not for me`, msg: call});
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends a call to register the service_id.
|
||||
*/
|
||||
async registerService(serviceId: string, fn: (req: FunctionCall) => void) {
|
||||
await this.connection.registerService(serviceId);
|
||||
|
||||
this.services.addService(serviceId, fn)
|
||||
}
|
||||
|
||||
// subscribe new hook for every incoming call, to handle in-service responses and other different cases
|
||||
// the hook will be deleted if it will return `true`
|
||||
subscribe(predicate: (args: any, target: Address, replyTo: Address) => (boolean | undefined)) {
|
||||
this.subscriptions.subscribe(predicate)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sends a call to unregister the service_id.
|
||||
*/
|
||||
async unregisterService(serviceId: string) {
|
||||
if (this.services.deleteService(serviceId)) {
|
||||
console.warn("unregister is not implemented yet (service: ${serviceId}")
|
||||
// TODO unregister in fluence network when it will be supported
|
||||
// let regMsg = makeRegisterMessage(serviceId, PeerId.createFromB58String(this.nodePeerId));
|
||||
// await this.sendFunctionCall(regMsg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Establish a connection to the node. If the connection is already established, disconnect and reregister all services in a new connection.
|
||||
*
|
||||
* @param multiaddr
|
||||
*/
|
||||
async connect(multiaddr: string | Multiaddr): Promise<void> {
|
||||
|
||||
multiaddr = Multiaddr(multiaddr);
|
||||
|
||||
let nodePeerId = multiaddr.getPeerId();
|
||||
|
||||
if (!nodePeerId) {
|
||||
throw Error("'multiaddr' did not contain a valid peer id")
|
||||
}
|
||||
|
||||
let firstConnection: boolean = true;
|
||||
if (this.connection) {
|
||||
firstConnection = false;
|
||||
await this.connection.disconnect();
|
||||
}
|
||||
|
||||
let peerId = PeerId.createFromB58String(nodePeerId);
|
||||
let relayAddress = await createRelayAddress(nodePeerId, this.selfPeerInfo.id, true);
|
||||
let connection = new FluenceConnection(multiaddr, peerId, this.selfPeerInfo, relayAddress, this.handleCall());
|
||||
|
||||
await connection.connect();
|
||||
|
||||
this.connection = connection;
|
||||
|
||||
// if the client already had a connection, it will reregister all services after establishing a new connection.
|
||||
if (!firstConnection) {
|
||||
for (let service of this.services.getAllServices().keys()) {
|
||||
await this.connection.registerService(service);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
206
src/fluence_connection.ts
Normal file
206
src/fluence_connection.ts
Normal file
@ -0,0 +1,206 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020 Fluence Labs Limited
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import {Address} from "./address";
|
||||
import {
|
||||
callToString,
|
||||
FunctionCall,
|
||||
genUUID,
|
||||
makeCall,
|
||||
makeFunctionCall,
|
||||
makePeerCall,
|
||||
makeRegisterMessage,
|
||||
makeRelayCall,
|
||||
parseFunctionCall
|
||||
} from "./function_call";
|
||||
import * as PeerId from "peer-id";
|
||||
import * as PeerInfo from "peer-info";
|
||||
import Websockets from "libp2p-websockets";
|
||||
import Mplex from "libp2p-mplex";
|
||||
import SECIO from "libp2p-secio";
|
||||
import Peer from "libp2p";
|
||||
import {decode, encode} from "it-length-prefixed";
|
||||
import pipe from "it-pipe";
|
||||
import Multiaddr from "multiaddr";
|
||||
|
||||
export const PROTOCOL_NAME = '/fluence/faas/1.0.0';
|
||||
|
||||
enum Status {
|
||||
Initializing = "Initializing",
|
||||
Connected = "Connected",
|
||||
Disconnected = "Disconnected"
|
||||
}
|
||||
|
||||
export class FluenceConnection {
|
||||
|
||||
private readonly selfPeerInfo: PeerInfo;
|
||||
readonly replyToAddress: Address;
|
||||
private node: LibP2p;
|
||||
private readonly address: Multiaddr;
|
||||
private readonly nodePeerId: PeerId;
|
||||
private readonly selfPeerId: string;
|
||||
private readonly handleCall: (call: FunctionCall) => FunctionCall | undefined;
|
||||
|
||||
constructor(multiaddr: Multiaddr, hostPeerId: PeerId, selfPeerInfo: PeerInfo, replyToAddress: Address, handleCall: (call: FunctionCall) => FunctionCall | undefined) {
|
||||
this.selfPeerInfo = selfPeerInfo;
|
||||
this.handleCall = handleCall;
|
||||
this.selfPeerId = selfPeerInfo.id.toB58String();
|
||||
this.address = multiaddr;
|
||||
this.nodePeerId = hostPeerId;
|
||||
this.replyToAddress = replyToAddress
|
||||
}
|
||||
|
||||
async connect() {
|
||||
let peerInfo = this.selfPeerInfo;
|
||||
this.node = await Peer.create({
|
||||
peerInfo,
|
||||
config: {},
|
||||
modules: {
|
||||
transport: [Websockets],
|
||||
streamMuxer: [Mplex],
|
||||
connEncryption: [SECIO],
|
||||
peerDiscovery: []
|
||||
},
|
||||
});
|
||||
|
||||
await this.startReceiving();
|
||||
}
|
||||
|
||||
isConnected() {
|
||||
return this.status === Status.Connected
|
||||
}
|
||||
|
||||
// connection status. If `Disconnected`, it cannot be reconnected
|
||||
private status: Status = Status.Initializing;
|
||||
|
||||
/**
|
||||
* Sends remote service_id call.
|
||||
*/
|
||||
async sendServiceCall(serviceId: string, args: any, name?: string) {
|
||||
let regMsg = makeCall(serviceId, args, this.replyToAddress, name);
|
||||
await this.sendCall(regMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends custom message to the peer.
|
||||
*/
|
||||
async sendPeerCall(peer: string, msg: any, name?: string) {
|
||||
let regMsg = makePeerCall(PeerId.createFromB58String(peer), msg, this.replyToAddress, name);
|
||||
await this.sendCall(regMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends custom message to the peer through relay.
|
||||
*/
|
||||
async sendRelayCall(peer: string, relay: string, msg: any, name?: string) {
|
||||
let regMsg = await makeRelayCall(PeerId.createFromB58String(peer), PeerId.createFromB58String(relay), msg, this.replyToAddress, name);
|
||||
await this.sendCall(regMsg);
|
||||
}
|
||||
|
||||
private async startReceiving() {
|
||||
if (this.status === Status.Initializing) {
|
||||
await this.node.start();
|
||||
|
||||
console.log("dialing to the node with address: " + this.node.peerInfo.id.toB58String());
|
||||
|
||||
await this.node.dial(this.address);
|
||||
|
||||
let _this = this;
|
||||
|
||||
this.node.handle([PROTOCOL_NAME], async ({connection, stream}) => {
|
||||
pipe(
|
||||
stream.source,
|
||||
decode(),
|
||||
async function (source: AsyncIterable<string>) {
|
||||
for await (const msg of source) {
|
||||
try {
|
||||
console.log(_this.selfPeerId);
|
||||
let call = parseFunctionCall(msg);
|
||||
let response = _this.handleCall(call);
|
||||
|
||||
// send a response if it exists, do nothing otherwise
|
||||
if (response) {
|
||||
await _this.sendCall(response);
|
||||
}
|
||||
} catch(e) {
|
||||
console.log("error on handling a new incoming message: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
this.status = Status.Connected;
|
||||
} else {
|
||||
throw Error(`can't start receiving. Status: ${this.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
private checkConnectedOrThrow() {
|
||||
if (this.status !== Status.Connected) {
|
||||
throw Error(`connection is in ${this.status} state`)
|
||||
}
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
await this.node.stop();
|
||||
this.status = Status.Disconnected;
|
||||
}
|
||||
|
||||
private async sendCall(call: FunctionCall) {
|
||||
let callStr = callToString(call);
|
||||
console.log("send function call: " + callStr);
|
||||
console.log(call);
|
||||
|
||||
// create outgoing substream
|
||||
const conn = await this.node.dialProtocol(this.address, PROTOCOL_NAME) as {stream: Stream; protocol: string};
|
||||
|
||||
pipe(
|
||||
[callStr],
|
||||
// at first, make a message varint
|
||||
encode(),
|
||||
conn.stream.sink,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send FunctionCall to the connected node.
|
||||
*/
|
||||
async sendFunctionCall(target: Address, args: any, reply?: boolean, name?: string) {
|
||||
this.checkConnectedOrThrow();
|
||||
|
||||
let replyTo;
|
||||
if (reply) replyTo = this.replyToAddress;
|
||||
|
||||
let call = makeFunctionCall(genUUID(), target, args, replyTo, name);
|
||||
|
||||
await this.sendCall(call);
|
||||
}
|
||||
|
||||
async registerService(serviceId: string) {
|
||||
let regMsg = await makeRegisterMessage(serviceId, this.nodePeerId, this.selfPeerInfo.id);
|
||||
await this.sendCall(regMsg);
|
||||
}
|
||||
}
|
134
src/function_call.ts
Normal file
134
src/function_call.ts
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020 Fluence Labs Limited
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import {
|
||||
createPeerAddress,
|
||||
createRelayAddress,
|
||||
createServiceAddress,
|
||||
Address, addressToString, parseAddress
|
||||
} from "./address";
|
||||
import * as PeerId from "peer-id";
|
||||
|
||||
export interface FunctionCall {
|
||||
uuid: string,
|
||||
target: Address,
|
||||
reply_to?: Address,
|
||||
arguments: any,
|
||||
name?: string,
|
||||
action: "FunctionCall"
|
||||
}
|
||||
|
||||
export function callToString(call: FunctionCall) {
|
||||
let obj: any = {...call};
|
||||
|
||||
if (obj.reply_to) {
|
||||
obj.reply_to = addressToString(obj.reply_to);
|
||||
}
|
||||
|
||||
obj.target = addressToString(obj.target);
|
||||
|
||||
return JSON.stringify(obj)
|
||||
}
|
||||
|
||||
export function makeFunctionCall(uuid: string, target: Address, args: object, replyTo?: Address, name?: string): FunctionCall {
|
||||
|
||||
return {
|
||||
uuid: uuid,
|
||||
target: target,
|
||||
reply_to: replyTo,
|
||||
arguments: args,
|
||||
name: name,
|
||||
action: "FunctionCall"
|
||||
}
|
||||
}
|
||||
|
||||
export function parseFunctionCall(str: string): FunctionCall {
|
||||
let json = JSON.parse(str);
|
||||
console.log(JSON.stringify(json, undefined, 2));
|
||||
|
||||
let replyTo: Address;
|
||||
if (json.reply_to) replyTo = parseAddress(json.reply_to);
|
||||
|
||||
if (!json.uuid) throw Error(`there is no 'uuid' field in json.\n${str}`);
|
||||
if (!json.target) throw Error(`there is no 'uuid' field in json.\n${str}`);
|
||||
|
||||
let target = parseAddress(json.target);
|
||||
|
||||
return {
|
||||
uuid: json.uuid,
|
||||
target: target,
|
||||
reply_to: replyTo,
|
||||
arguments: json.arguments,
|
||||
name: json.name,
|
||||
action: "FunctionCall"
|
||||
}
|
||||
}
|
||||
|
||||
export function genUUID() {
|
||||
let date = new Date();
|
||||
return date.toISOString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Message to peer through relay
|
||||
*/
|
||||
export async function makeRelayCall(client: PeerId, relay: PeerId, msg: any, replyTo?: Address, name?: string): Promise<FunctionCall> {
|
||||
let relayAddress = await createRelayAddress(relay.toB58String(), client, false);
|
||||
|
||||
return makeFunctionCall(genUUID(), relayAddress, msg, replyTo, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Message to peer
|
||||
*/
|
||||
export function makePeerCall(client: PeerId, msg: any, replyTo?: Address, name?: string): FunctionCall {
|
||||
let peerAddress = createPeerAddress(client.toB58String());
|
||||
|
||||
return makeFunctionCall(genUUID(), peerAddress, msg, replyTo, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Message to call remote service_id
|
||||
*/
|
||||
export function makeCall(functionId: string, args: any, replyTo?: Address, name?: string): FunctionCall {
|
||||
let target = createServiceAddress(functionId);
|
||||
|
||||
return makeFunctionCall(genUUID(), target, args, replyTo, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Message to register new service_id.
|
||||
*/
|
||||
export async function makeRegisterMessage(serviceId: string, relayPeerId: PeerId, selfPeerId: PeerId): Promise<FunctionCall> {
|
||||
let target = createServiceAddress("provide");
|
||||
let replyTo = await createRelayAddress(relayPeerId.toB58String(), selfPeerId, true);
|
||||
|
||||
return makeFunctionCall(genUUID(), target, {service_id: serviceId}, replyTo, "provide service_id");
|
||||
}
|
||||
|
||||
export function makeUnregisterMessage(serviceId: string, peerId: PeerId): FunctionCall {
|
||||
let target = createPeerAddress(peerId.toB58String());
|
||||
|
||||
return makeFunctionCall(genUUID(), target, {key: serviceId}, undefined, "unregister");
|
||||
}
|
57
src/services.ts
Normal file
57
src/services.ts
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020 Fluence Labs Limited
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import {FunctionCall} from "./function_call";
|
||||
|
||||
export class Services {
|
||||
|
||||
private services: Map<string, (req: FunctionCall) => void> = new Map();
|
||||
|
||||
constructor() {}
|
||||
|
||||
addService(serviceId: string, callback: (req: FunctionCall) => void): void {
|
||||
this.services.set(serviceId, callback);
|
||||
}
|
||||
|
||||
getAllServices(): Map<string, (req: FunctionCall) => void> {
|
||||
return this.services;
|
||||
}
|
||||
|
||||
deleteService(serviceId: string): boolean {
|
||||
return this.services.delete(serviceId)
|
||||
}
|
||||
|
||||
// could throw error from service callback
|
||||
// returns true if the call was applied
|
||||
applyToService(serviceId: string, call: FunctionCall): boolean {
|
||||
let service = this.services.get(serviceId);
|
||||
if (service) {
|
||||
service(call);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
50
src/subscriptions.ts
Normal file
50
src/subscriptions.ts
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020 Fluence Labs Limited
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import {FunctionCall} from "./function_call";
|
||||
import {Address} from "./address";
|
||||
|
||||
export class Subscriptions {
|
||||
private subscriptions: ((args: any, target: Address, replyTo: Address) => (boolean | undefined))[] = [];
|
||||
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* Subscriptions will be applied to all peer and relay messages.
|
||||
* If subscription returns true, delete subscription.
|
||||
* @param f
|
||||
*/
|
||||
subscribe(f: (args: any, target: Address, replyTo: Address) => (boolean | undefined)) {
|
||||
this.subscriptions.push(f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply call to all subscriptions and delete subscriptions that return `true`.
|
||||
* @param call
|
||||
*/
|
||||
applyToSubscriptions(call: FunctionCall) {
|
||||
// if subscription return false - delete it from subscriptions
|
||||
this.subscriptions = this.subscriptions.filter(callback => !callback(call.arguments, call.target, call.reply_to))
|
||||
}
|
||||
}
|
152
src/test/address.spec.ts
Normal file
152
src/test/address.spec.ts
Normal file
@ -0,0 +1,152 @@
|
||||
import {
|
||||
createPeerAddress,
|
||||
createRelayAddress,
|
||||
createServiceAddress,
|
||||
addressToString,
|
||||
parseAddress
|
||||
} from "../address";
|
||||
import {expect} from 'chai';
|
||||
|
||||
import 'mocha';
|
||||
import * as PeerId from "peer-id";
|
||||
import {callToString, genUUID, makeFunctionCall, parseFunctionCall} from "../function_call";
|
||||
import Fluence from "../fluence";
|
||||
|
||||
describe("Typescript usage suite", () => {
|
||||
|
||||
it("should throw an error, if protocol will be without value", () => {
|
||||
expect(() => parseAddress("/peer/")).to.throw(Error);
|
||||
});
|
||||
|
||||
it("should be able to convert service_id address to and from string", () => {
|
||||
let addr = createServiceAddress("service_id-1");
|
||||
let str = addressToString(addr);
|
||||
let parsed = parseAddress(str);
|
||||
|
||||
expect(parsed).to.deep.equal(addr)
|
||||
});
|
||||
|
||||
it("should be able to convert peer address to and from string", () => {
|
||||
let pid = PeerId.createFromB58String("QmXduoWjhgMdx3rMZXR3fmkHKdUCeori9K1XkKpqeF5DrU");
|
||||
let addr = createPeerAddress(pid.toB58String());
|
||||
let str = addressToString(addr);
|
||||
let parsed = parseAddress(str);
|
||||
|
||||
expect(parsed).to.deep.equal(addr)
|
||||
});
|
||||
|
||||
it("should be able to convert relay address to and from string", async () => {
|
||||
let pid = await PeerId.create();
|
||||
let relayid = await PeerId.create();
|
||||
let addr = await createRelayAddress(relayid.toB58String(), pid, true);
|
||||
let str = addressToString(addr);
|
||||
let parsed = parseAddress(str);
|
||||
|
||||
expect(parsed).to.deep.equal(addr)
|
||||
});
|
||||
|
||||
it("should be able to convert function call to and from string", async () => {
|
||||
let pid = await PeerId.create();
|
||||
let relayid = await PeerId.create();
|
||||
let addr = await createRelayAddress(relayid.toB58String(), pid, true);
|
||||
|
||||
let pid2 = await PeerId.create();
|
||||
let addr2 = createPeerAddress(pid.toB58String());
|
||||
|
||||
let functionCall = makeFunctionCall(
|
||||
"123",
|
||||
addr2,
|
||||
{
|
||||
arg1: "123",
|
||||
arg2: 3,
|
||||
arg4: [1, 2, 3]
|
||||
},
|
||||
addr,
|
||||
"2444"
|
||||
);
|
||||
|
||||
let str = callToString(functionCall);
|
||||
|
||||
let parsed = parseFunctionCall(str);
|
||||
|
||||
expect(parsed).to.deep.equal(functionCall);
|
||||
|
||||
let functionCallWithOptional = makeFunctionCall(
|
||||
"123",
|
||||
addr,
|
||||
{
|
||||
arg1: "123",
|
||||
arg2: 3,
|
||||
arg4: [1, 2, 3]
|
||||
}
|
||||
);
|
||||
|
||||
let str2 = callToString(functionCallWithOptional);
|
||||
|
||||
let parsed2 = parseFunctionCall(str2);
|
||||
|
||||
expect(parsed2).to.deep.equal(functionCallWithOptional)
|
||||
|
||||
});
|
||||
|
||||
it("integration test", async function () {
|
||||
this.timeout(5000);
|
||||
await testCalculator();
|
||||
});
|
||||
});
|
||||
|
||||
const delay = (ms: number) => new Promise(res => setTimeout(res, ms));
|
||||
|
||||
// Shows how to register and call new service in Fluence network
|
||||
export async function testCalculator() {
|
||||
|
||||
let key1 = await Fluence.generatePeerId();
|
||||
let key2 = await Fluence.generatePeerId();
|
||||
|
||||
// connect to two different nodes
|
||||
let cl1 = await Fluence.connect("/dns4/104.248.25.59/tcp/9003/ws/p2p/12D3KooWBUJifCTgaxAUrcM9JysqCcS4CS8tiYH5hExbdWCAoNwb", key1);
|
||||
let cl2 = await Fluence.connect("/ip4/104.248.25.59/tcp/9002/ws/p2p/12D3KooWHk9BjDQBUqnavciRPhAYFvqKBe4ZiPPvde7vDaqgn5er", key2);
|
||||
|
||||
// service name that we will register with one connection and call with another
|
||||
let serviceId = "sum-calculator-" + genUUID();
|
||||
|
||||
// register service that will add two numbers and send a response with calculation result
|
||||
await cl1.registerService(serviceId, async (req) => {
|
||||
console.log("message received");
|
||||
console.log(req);
|
||||
|
||||
console.log("send response");
|
||||
|
||||
let message = {msgId: req.arguments.msgId, result: req.arguments.one + req.arguments.two};
|
||||
|
||||
await cl1.sendCall(req.reply_to, message);
|
||||
});
|
||||
|
||||
|
||||
// msgId is to identify response
|
||||
let msgId = "calculate-it-for-me";
|
||||
|
||||
let req = {one: 12, two: 23, msgId: msgId};
|
||||
|
||||
|
||||
let predicate: (args: any) => boolean | undefined = (args: any) => args.msgId && args.msgId === msgId;
|
||||
|
||||
// send call to `sum-calculator` service with two numbers
|
||||
let response = await cl2.sendServiceCallWaitResponse(serviceId, req, predicate);
|
||||
|
||||
let result = response.result;
|
||||
console.log(`calculation result is: ${result}`);
|
||||
|
||||
await cl1.connect("/dns4/relay01.fluence.dev/tcp/19001/wss/p2p/12D3KooWEXNUbCXooUwHrHBbrmjsrpHXoEphPwbjQXEGyzbqKnE9");
|
||||
|
||||
await delay(1000);
|
||||
|
||||
// send call to `sum-calculator` service with two numbers
|
||||
await cl2.sendServiceCall(serviceId, req, "calculator request");
|
||||
|
||||
let response2 = await cl2.sendServiceCallWaitResponse(serviceId, req, predicate);
|
||||
|
||||
let result2 = await response2.result;
|
||||
console.log(`calculation result AFTER RECONNECT is: ${result2}`);
|
||||
}
|
||||
|
30
tsconfig.json
Normal file
30
tsconfig.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/libp2p-ts/types",
|
||||
"./types"
|
||||
],
|
||||
"outDir": "./dist/",
|
||||
"sourceMap": true,
|
||||
"noImplicitAny": true,
|
||||
"strictFunctionTypes": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
"pretty": true,
|
||||
"target": "esnext",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"declaration": true,
|
||||
"strict": true,
|
||||
"strictNullChecks": false,
|
||||
"esModuleInterop": true,
|
||||
"baseUrl": "."
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"bundle"
|
||||
],
|
||||
"include": ["src/**/*"]
|
||||
}
|
27
types/ipfs-only-hash/index.d.ts
vendored
Normal file
27
types/ipfs-only-hash/index.d.ts
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020 Fluence Labs Limited
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
declare module 'ipfs-only-hash' {
|
||||
export function of(data: Buffer): Promise<string>
|
||||
}
|
28
types/it-length-prefixed/index.d.ts
vendored
Normal file
28
types/it-length-prefixed/index.d.ts
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020 Fluence Labs Limited
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
declare module 'it-length-prefixed' {
|
||||
export function decode(): any
|
||||
export function encode(): any
|
||||
}
|
50
webpack.config.js
Normal file
50
webpack.config.js
Normal file
@ -0,0 +1,50 @@
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||
|
||||
const production = (process.env.NODE_ENV === 'production');
|
||||
|
||||
const config = {
|
||||
entry: {
|
||||
app: ['./src/fluence.ts']
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
loader: 'ts-loader',
|
||||
exclude: /node_modules/
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
extensions: [ '.tsx', '.ts', '.js']
|
||||
},
|
||||
output: {
|
||||
filename: 'bundle.js',
|
||||
path: path.resolve(__dirname, 'bundle'),
|
||||
},
|
||||
node: {
|
||||
fs: 'empty'
|
||||
},
|
||||
plugins: [
|
||||
new CleanWebpackPlugin(),
|
||||
]
|
||||
};
|
||||
|
||||
if (production) {
|
||||
config.mode = 'production';
|
||||
} else {
|
||||
config.mode = 'development';
|
||||
config.devtool = 'inline-source-map';
|
||||
config.devServer = {
|
||||
contentBase: './bundle',
|
||||
hot: false
|
||||
};
|
||||
config.plugins = [
|
||||
...config.plugins,
|
||||
new webpack.HotModuleReplacementPlugin()
|
||||
];
|
||||
}
|
||||
|
||||
module.exports = config;
|
Loading…
Reference in New Issue
Block a user