From 2277a3584c2ea02aeb0c6aa122978b42f48fa32d Mon Sep 17 00:00:00 2001 From: Dima Date: Thu, 20 Aug 2020 20:28:32 +0300 Subject: [PATCH] Client blueprint, service incapsulation (#940) --- package-lock.json | 31 +++++- package.json | 6 +- src/blueprint.ts | 35 ++++++ src/fluence.ts | 2 +- src/{fluence_client.ts => fluenceClient.ts} | 104 +++++++++--------- ...nce_connection.ts => fluenceConnection.ts} | 6 +- src/{function_call.ts => functionCall.ts} | 14 +-- src/{services.ts => localServices.ts} | 4 +- src/service.ts | 39 +++++++ src/subscriptions.ts | 2 +- src/test/address.spec.ts | 18 ++- src/trust/trust_graph.ts | 2 +- 12 files changed, 182 insertions(+), 81 deletions(-) create mode 100644 src/blueprint.ts rename src/{fluence_client.ts => fluenceClient.ts} (86%) rename src/{fluence_connection.ts => fluenceConnection.ts} (97%) rename src/{function_call.ts => functionCall.ts} (90%) rename src/{services.ts => localServices.ts} (94%) create mode 100644 src/service.ts diff --git a/package-lock.json b/package-lock.json index 40047571..fa80115b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "fluence", - "version": "0.5.3", + "version": "0.7.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -134,6 +134,12 @@ "source-map": "^0.6.1" } }, + "@types/uuid": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==", + "dev": true + }, "@types/webpack": { "version": "4.41.12", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.12.tgz", @@ -5957,6 +5963,14 @@ "faye-websocket": "^0.10.0", "uuid": "^3.4.0", "websocket-driver": "0.6.5" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + } } }, "sockjs-client": { @@ -6769,10 +6783,9 @@ "dev": true }, "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==" }, "v8-compile-cache": { "version": "2.0.3", @@ -7873,6 +7886,14 @@ "requires": { "ansi-colors": "^3.0.0", "uuid": "^3.3.2" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + } } }, "webpack-sources": { diff --git a/package.json b/package.json index 913c8b59..33a0bfe1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fluence", - "version": "0.7.3", + "version": "0.7.6", "description": "the browser js-libp2p client for the Fluence network", "main": "./dist/fluence.js", "typings": "./dist/fluence.d.ts", @@ -26,11 +26,13 @@ "libp2p-secio": "0.12.5", "libp2p-websockets": "0.13.6", "peer-id": "0.13.12", - "peer-info": "0.17.5" + "peer-info": "0.17.5", + "uuid": "8.3.0" }, "devDependencies": { "@types/base64-js": "1.2.5", "@types/bs58": "4.0.1", + "@types/uuid": "8.3.0", "@types/chai": "4.2.11", "@types/mocha": "7.0.2", "assert": "2.0.0", diff --git a/src/blueprint.ts b/src/blueprint.ts new file mode 100644 index 00000000..ba5a5c42 --- /dev/null +++ b/src/blueprint.ts @@ -0,0 +1,35 @@ +/* + * 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. + */ + +export interface Blueprint { + dependencies: string[], + id: string, + name: string +} + +export function checkBlueprint(b: any): b is Blueprint { + if (!b.id) throw new Error(`There is no 'id' field in Blueprint struct: ${JSON.stringify(b)}`) + if (b.dependencies) { + b.dependencies.forEach((dep: any) => { + if ((typeof dep) !== 'string') { + throw Error(`'dependencies' should be an array of strings: ${JSON.stringify(b)}`) + } + }); + } + + return true; + +} diff --git a/src/fluence.ts b/src/fluence.ts index 061dd770..f34f721a 100644 --- a/src/fluence.ts +++ b/src/fluence.ts @@ -17,7 +17,7 @@ import * as PeerInfo from "peer-info"; import * as PeerId from "peer-id"; import Multiaddr from "multiaddr" -import {FluenceClient} from "./fluence_client"; +import {FluenceClient} from "./fluenceClient"; export default class Fluence { diff --git a/src/fluence_client.ts b/src/fluenceClient.ts similarity index 86% rename from src/fluence_client.ts rename to src/fluenceClient.ts index 61973cf7..ae3c533d 100644 --- a/src/fluence_client.ts +++ b/src/fluenceClient.ts @@ -22,21 +22,22 @@ import { ProtocolType, addressToString } from "./address"; -import {callToString, FunctionCall, genUUID, makeFunctionCall,} from "./function_call"; +import {callToString, FunctionCall, genUUID, makeFunctionCall,} from "./functionCall"; import * as PeerId from "peer-id"; -import {Services} from "./services"; +import {LocalServices} from "./localServices"; import Multiaddr from "multiaddr" import {Subscriptions} from "./subscriptions"; import * as PeerInfo from "peer-info"; -import {FluenceConnection} from "./fluence_connection"; +import {FluenceConnection} from "./fluenceConnection"; import {checkInterface, Interface} from "./Interface"; +import {Service} from "./service"; +import {Blueprint, checkBlueprint} from "./blueprint"; /** * @param target receiver * @param args message in the call * @param moduleId module name * @param fname function name - * @param context list of modules to use with the request * @param name common field for debug purposes * @param msgId hash that will be added to replyTo address */ @@ -46,7 +47,6 @@ interface Call { moduleId?: string, fname?: string, msgId?: string, - context?: string[], name?: string } @@ -57,7 +57,7 @@ export class FluenceClient { private connection: FluenceConnection; - private services: Services = new Services(); + private services: LocalServices = new LocalServices(); private subscriptions: Subscriptions = new Subscriptions(); @@ -105,30 +105,13 @@ export class FluenceClient { return (args: any, target: Address) => target.hash && target.hash === msgId; } - /** - * Send call and wait a response. - * - * @param target receiver - * @param args message in the call - * @param moduleId module name - * @param fname functin name - */ - async sendCallWaitResponse(target: Address, args: any, moduleId?: string, fname?: string): Promise { - let msgId = genUUID(); - let predicate = this.getPredicate(msgId); - await this.sendCall({target: target, args: args, moduleId: moduleId, fname: fname, msgId: msgId}); - return this.waitResponse(predicate, false); - } - - - /** * Send call and forget. * */ async sendCall(call: Call) { if (this.connection && this.connection.isConnected()) { - await this.connection.sendFunctionCall(call.target, call.args, call.moduleId, call.fname, call.msgId, call.context, call.name); + await this.connection.sendFunctionCall(call.target, call.args, call.moduleId, call.fname, call.msgId, call.name); } else { throw Error("client is not connected") } @@ -158,10 +141,9 @@ export class FluenceClient { * @param addr node address * @param args message to the service * @param fname function name - * @param context * @param name debug info */ - async callPeer(moduleId: string, args: any, fname?: string, addr?: string, context?: string[], name?: string): Promise { + async callPeer(moduleId: string, args: any, fname?: string, addr?: string, name?: string): Promise { let msgId = genUUID(); let predicate = this.getPredicate(msgId); @@ -172,7 +154,7 @@ export class FluenceClient { address = createPeerAddress(this.nodePeerIdStr); } - await this.sendCall({target: address, args: args, moduleId: moduleId, fname: fname, msgId: msgId, context: context, name: name}) + await this.sendCall({target: address, args: args, moduleId: moduleId, fname: fname, msgId: msgId, name: name}) return await this.waitResponse(predicate, false); } @@ -186,6 +168,10 @@ export class FluenceClient { return await this.waitResponse(predicate, false); } + getService(peerId: string, serviceId: string): Service { + return new Service(this, peerId, serviceId); + } + /** * Handle incoming call. * If FunctionCall returns - we should send it as a response. @@ -280,17 +266,32 @@ export class FluenceClient { /** * Sends a call to create a service on remote node. */ - async createService(peerId: string, context: string[]): Promise { - let resp = await this.callPeer("create", {}, undefined, peerId, context); + async createService(peerId: string, blueprint: string): Promise { + let resp = await this.callPeer("create", {blueprint_id: blueprint}, undefined, peerId); - if (resp.result && resp.result.service_id) { - return resp.result.service_id + if (resp && resp.service_id) { + return resp.service_id } else { console.error("Unknown response type on `createService`: ", resp) throw new Error("Unknown response type on `createService`"); } } + async addBlueprint(peerId: string, name: string, dependencies: string[]): Promise { + + let id = genUUID(); + let blueprint = { + name: name, + id: id, + dependencies: dependencies + }; + let msg_id = genUUID(); + + await this.callPeer("add_blueprint", {msg_id, blueprint}, undefined, peerId); + + return id; + } + async getInterface(serviceId: string, peerId?: string): Promise { let resp; resp = await this.callPeer("get_interface", {service_id: serviceId}, undefined, peerId) @@ -303,13 +304,27 @@ export class FluenceClient { } } - async getActiveInterfaces(peerId?: string): Promise { - let resp; - if (peerId) { - resp = await this.sendCallWaitResponse(createPeerAddress(peerId), {}, "get_active_interfaces"); + async getAvailableBlueprints(peerId?: string): Promise { + let resp = await this.callPeer("get_available_blueprints", {}, undefined); + + let blueprints = resp.available_blueprints; + + if (blueprints && blueprints instanceof Array) { + return blueprints.map((b: any) => { + if (checkBlueprint(b)) { + return b; + } else { + throw new Error("Unexpected"); + } + }); } else { - resp = await this.callPeer("get_active_interfaces", {}, undefined, peerId); + throw new Error("Unexpected. 'get_active_interfaces' should return an array of interfaces."); } + } + + async getActiveInterfaces(peerId?: string): Promise { + let resp = await this.callPeer("get_active_interfaces", {}, undefined); + let interfaces = resp.active_interfaces; if (interfaces && interfaces instanceof Array) { return interfaces.map((i: any) => { @@ -320,18 +335,12 @@ export class FluenceClient { } }); } else { - throw new Error("Unexpected"); + throw new Error("Unexpected. 'get_active_interfaces' should return an array pf interfaces."); } } async getAvailableModules(peerId?: string): Promise { - let resp; - if (peerId) { - resp = await this.sendCallWaitResponse(createPeerAddress(peerId), {}, "get_available_modules"); - } else { - resp = await this.callPeer("get_available_modules", {}, undefined, peerId); - } - + let resp = await this.callPeer("get_available_modules", {}, undefined, peerId); return resp.available_modules; } @@ -357,12 +366,7 @@ export class FluenceClient { preopened_files: preopened_files } } - let resp; - if (peerId) { - resp = await this.sendCallWaitResponse(createPeerAddress(peerId), {bytes: bytes, config: config}, "add_module"); - } else { - resp = await this.callPeer("add_module", {bytes: bytes, config: config}, undefined, peerId); - } + let resp = await this.callPeer("add_module", {bytes: bytes, config: config}, undefined, peerId); return resp.available_modules; } diff --git a/src/fluence_connection.ts b/src/fluenceConnection.ts similarity index 97% rename from src/fluence_connection.ts rename to src/fluenceConnection.ts index a28e6566..56e02af6 100644 --- a/src/fluence_connection.ts +++ b/src/fluenceConnection.ts @@ -22,7 +22,7 @@ import { makeFunctionCall, makeProvideMessage, parseFunctionCall -} from "./function_call"; +} from "./functionCall"; import * as PeerId from "peer-id"; import * as PeerInfo from "peer-info"; import Websockets from "libp2p-websockets"; @@ -162,13 +162,13 @@ export class FluenceConnection { /** * Send FunctionCall to the connected node. */ - async sendFunctionCall(target: Address, args: any, moduleId?: string, fname?: string, msgId?: string, context?: string[], name?: string) { + async sendFunctionCall(target: Address, args: any, moduleId?: string, fname?: string, msgId?: string, name?: string) { this.checkConnectedOrThrow(); let replyTo; if (msgId) replyTo = this.makeReplyTo(msgId); - let call = makeFunctionCall(genUUID(), target, this.sender, args, moduleId, fname, replyTo, context, name); + let call = makeFunctionCall(genUUID(), target, this.sender, args, moduleId, fname, replyTo, name); await this.sendCall(call); } diff --git a/src/function_call.ts b/src/functionCall.ts similarity index 90% rename from src/function_call.ts rename to src/functionCall.ts index df578e2d..7a9f322a 100644 --- a/src/function_call.ts +++ b/src/functionCall.ts @@ -15,11 +15,9 @@ */ import { - createPeerAddress, - createRelayAddress, Address, addressToString, parseAddress } from "./address"; -import * as PeerId from "peer-id"; +import { v4 as uuidv4 } from 'uuid'; export interface FunctionCall { uuid: string, @@ -29,7 +27,6 @@ export interface FunctionCall { "module"?: string, fname?: string, arguments: any, - context?: string[], name?: string, action: "FunctionCall" } @@ -47,7 +44,7 @@ export function callToString(call: FunctionCall) { return JSON.stringify(obj) } -export function makeFunctionCall(uuid: string, target: Address, sender: Address, args: object, moduleId?: string, fname?: string, replyTo?: Address, context?: string[], name?: string): FunctionCall { +export function makeFunctionCall(uuid: string, target: Address, sender: Address, args: object, moduleId?: string, fname?: string, replyTo?: Address, name?: string): FunctionCall { return { uuid: uuid, @@ -57,7 +54,6 @@ export function makeFunctionCall(uuid: string, target: Address, sender: Address, "module": moduleId, fname: fname, arguments: args, - context: context, name: name, action: "FunctionCall" } @@ -83,7 +79,6 @@ export function parseFunctionCall(str: string): FunctionCall { reply_to: replyTo, sender: sender, arguments: json.arguments, - context: json.context, "module": json.module, fname: json.fname, name: json.name, @@ -92,15 +87,14 @@ export function parseFunctionCall(str: string): FunctionCall { } export function genUUID() { - let date = new Date(); - return date.toISOString() + return uuidv4(); } /** * Message to provide new name. */ export async function makeProvideMessage(name: string, target: Address, sender: Address): Promise { - return makeFunctionCall(genUUID(), target, sender, {name: name, address: addressToString(sender)}, "provide", undefined, sender, undefined, "provide service_id"); + return makeFunctionCall(genUUID(), target, sender, {name: name, address: addressToString(sender)}, "provide", undefined, sender, "provide service_id"); } // TODO uncomment when this will be implemented in Fluence network diff --git a/src/services.ts b/src/localServices.ts similarity index 94% rename from src/services.ts rename to src/localServices.ts index 828721ab..65b1e3a0 100644 --- a/src/services.ts +++ b/src/localServices.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import {FunctionCall} from "./function_call"; +import {FunctionCall} from "./functionCall"; -export class Services { +export class LocalServices { private services: Map void> = new Map(); diff --git a/src/service.ts b/src/service.ts new file mode 100644 index 00000000..7b8e6078 --- /dev/null +++ b/src/service.ts @@ -0,0 +1,39 @@ +/* + * 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"; + +export class Service { + private readonly client: FluenceClient; + private readonly serviceId: string; + private readonly peerId: string; + + constructor(client: FluenceClient, peerId: string, serviceId: string) { + this.client = client; + this.serviceId = serviceId; + this.peerId = peerId; + } + + /** + * + * @param moduleId wich module in service to call + * @param args parameters to call service + * @param fname function name if existed + */ + async call(moduleId: string, args: any, fname?: string): Promise { + return this.client.callService(this.peerId, this.serviceId, moduleId, args, fname); + } +} diff --git a/src/subscriptions.ts b/src/subscriptions.ts index f6c85b6b..b776dc8a 100644 --- a/src/subscriptions.ts +++ b/src/subscriptions.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import {FunctionCall} from "./function_call"; +import {FunctionCall} from "./functionCall"; import {Address} from "./address"; export class Subscriptions { diff --git a/src/test/address.spec.ts b/src/test/address.spec.ts index 63e703ea..489da4dc 100644 --- a/src/test/address.spec.ts +++ b/src/test/address.spec.ts @@ -10,7 +10,7 @@ import {expect} from 'chai'; import 'mocha'; import {encode} from "bs58" import * as PeerId from "peer-id"; -import {callToString, genUUID, makeFunctionCall, parseFunctionCall} from "../function_call"; +import {callToString, genUUID, makeFunctionCall, parseFunctionCall} from "../functionCall"; import Fluence from "../fluence"; import {certificateFromString, certificateToString, issue} from "../trust/certificate"; import {TrustGraph} from "../trust/trust_graph"; @@ -70,7 +70,6 @@ describe("Typescript usage suite", () => { "mm", "fff", addr, - undefined, "2444" ); @@ -197,10 +196,16 @@ export async function testUploadWasm() { let peerId1 = "12D3KooWPnLxnY71JDxvB3zbjKu9k1BCYNthGZw6iGrLYsR1RnWM" - let serviceId = await cl1.createService(peerId1, [moduleName]); + let blueprintId = await cl1.addBlueprint(peerId1, "some test blueprint", [moduleName]) + let blueprints = await cl1.getAvailableBlueprints(peerId1) + console.log(blueprints); + + let serviceId = await cl1.createService(peerId1, blueprintId); + + let service = cl1.getService(peerId1, serviceId); let argName = genUUID(); - let resp = await cl1.callService(peerId1, serviceId, moduleName, {name: argName}, "greeting") + let resp = await service.call(moduleName, {name: argName}, "greeting") expect(resp.result).to.be.equal(`Hi, ${argName}`) } @@ -215,9 +220,10 @@ export async function testServicesAndInterfaces() { let peerId1 = "12D3KooWPnLxnY71JDxvB3zbjKu9k1BCYNthGZw6iGrLYsR1RnWM" - let serviceId = await cl2.createService(peerId1, ["ipfs_node.wasm"]); + let blueprintId = await cl1.addBlueprint(peerId1, "some test blueprint", ["ipfs_node"]) + let serviceId = await cl2.createService(peerId1, blueprintId); - let resp = await cl2.callService(peerId1, serviceId, "ipfs_node.wasm", {}, "get_address") + let resp = await cl2.callService(peerId1, serviceId, "ipfs_node", {}, "get_address") console.log(resp) let interfaces = await cl1.getActiveInterfaces(); diff --git a/src/trust/trust_graph.ts b/src/trust/trust_graph.ts index fddb9f6a..be01dcfb 100644 --- a/src/trust/trust_graph.ts +++ b/src/trust/trust_graph.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import {FluenceClient} from "../fluence_client"; +import {FluenceClient} from "../fluenceClient"; import {Certificate, certificateFromString, certificateToString} from "./certificate"; // The client to interact with the Fluence trust graph API