Stepper integration impl (#955)

This commit is contained in:
Dima 2020-10-12 14:07:28 +03:00 committed by GitHub
parent 0a49f84d08
commit 2792dcfc93
21 changed files with 569 additions and 136 deletions

View File

@ -3,9 +3,11 @@
node_modules node_modules
types types
src src/
aqua/
tsconfig.json tsconfig.json
webpack.config.js webpack.config.js
bundle bundle
pkg

14
aqua/index.d.ts vendored Normal file
View File

@ -0,0 +1,14 @@
/* tslint:disable */
/* eslint-disable */
/**
* @param wasm
* @param {string} init_user_id
* @param {string} aqua
* @param {string} data
* @returns {string}
*/
export function invoke(wasm: any, init_user_id: string, aqua: string, data: string): string;
export function getStringFromWasm0(wasm: any, arg1: any, arg2: any): string
export function getInt32Memory0(wasm: any): number[]
export function passStringToWasm0(wasm: any, arg: any, malloc: any, realloc: any): number
export let WASM_VECTOR_LEN: number;

1
aqua/index.js Normal file
View File

@ -0,0 +1 @@
export * from "./index_bg.js";

115
aqua/index_bg.js Normal file
View File

@ -0,0 +1,115 @@
/**
*
* This is generated and patched code. All functions are using local wasm as an argument for now, not a global wasm file.
*
*/
export function main(wasm) {
wasm.main();
}
export let WASM_VECTOR_LEN = 0;
let cachegetUint8Memory0 = null;
export function getUint8Memory0(wasm) {
if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) {
cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
}
return cachegetUint8Memory0;
}
let cachedTextEncoder = new TextEncoder('utf-8');
const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
? function (arg, view) {
return cachedTextEncoder.encodeInto(arg, view);
}
: function (arg, view) {
const buf = cachedTextEncoder.encode(arg);
view.set(buf);
return {
read: arg.length,
written: buf.length
};
});
export function passStringToWasm0(wasm, arg, malloc, realloc) {
if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length);
getUint8Memory0(wasm).subarray(ptr, ptr + buf.length).set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}
let len = arg.length;
let ptr = malloc(len);
const mem = getUint8Memory0(wasm);
let offset = 0;
for (; offset < len; offset++) {
const code = arg.charCodeAt(offset);
if (code > 0x7F) break;
mem[ptr + offset] = code;
}
if (offset !== len) {
if (offset !== 0) {
arg = arg.slice(offset);
}
ptr = realloc(ptr, len, len = offset + arg.length * 3);
const view = getUint8Memory0(wasm).subarray(ptr + offset, ptr + len);
const ret = encodeString(arg, view);
offset += ret.written;
}
WASM_VECTOR_LEN = offset;
return ptr;
}
let cachegetInt32Memory0 = null;
export function getInt32Memory0(wasm) {
if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) {
cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer);
}
return cachegetInt32Memory0;
}
let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
cachedTextDecoder.decode();
export function getStringFromWasm0(wasm, ptr, len) {
return cachedTextDecoder.decode(getUint8Memory0(wasm).subarray(ptr, ptr + len));
}
/**
* @param {any} wasm
* @param {string} init_user_id
* @param {string} aqua
* @param {string} data
* @returns {string}
*/
export function invoke(wasm, init_user_id, aqua, data) {
try {
const retptr = wasm.__wbindgen_export_0.value - 16;
wasm.__wbindgen_export_0.value = retptr;
var ptr0 = passStringToWasm0(wasm, init_user_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
var ptr1 = passStringToWasm0(wasm, aqua, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len1 = WASM_VECTOR_LEN;
var ptr2 = passStringToWasm0(wasm, data, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len2 = WASM_VECTOR_LEN;
wasm.invoke(retptr, ptr0, len0, ptr1, len1, ptr2, len2);
var r0 = getInt32Memory0(wasm)[retptr / 4 + 0];
var r1 = getInt32Memory0(wasm)[retptr / 4 + 1];
return getStringFromWasm0(wasm, r0, r1);
} finally {
wasm.__wbindgen_export_0.value += 16;
wasm.__wbindgen_free(r0, r1);
}
}

110
package-lock.json generated
View File

@ -196,6 +196,44 @@
"watchpack": "^1.6.0" "watchpack": "^1.6.0"
} }
}, },
"@wasmer/wasi": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@wasmer/wasi/-/wasi-0.12.0.tgz",
"integrity": "sha512-FJhLZKAfLWm/yjQI7eCRHNbA8ezmb7LSpUYFkHruZXs2mXk2+DaQtSElEtOoNrVQ4vApTyVaAd5/b7uEu8w6wQ==",
"requires": {
"browser-process-hrtime": "^1.0.0",
"buffer-es6": "^4.9.3",
"path-browserify": "^1.0.0",
"randomfill": "^1.0.4"
},
"dependencies": {
"path-browserify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="
}
}
},
"@wasmer/wasm-transformer": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@wasmer/wasm-transformer/-/wasm-transformer-0.12.0.tgz",
"integrity": "sha512-pz4hvrJhqjxsmrMgxIH6jaEJuaL4/1iPNUg0lSIdpWeszgoCYjpQUimzh7ekKCIxl8WuqKOmm2f5hX+Vt8Sx8w==",
"dev": true,
"requires": {
"wasm-feature-detect": "^1.2.2"
}
},
"@wasmer/wasmfs": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@wasmer/wasmfs/-/wasmfs-0.12.0.tgz",
"integrity": "sha512-m1ftchyQ1DfSenm5XbbdGIpb6KJHH5z0gODo3IZr6lATkj4WXfX/UeBTZ0aG9YVShBp+kHLdUHvOkqjy6p/GWw==",
"dev": true,
"requires": {
"memfs": "3.0.4",
"pako": "^1.0.11",
"tar-stream": "^2.1.0"
}
},
"@webassemblyjs/ast": { "@webassemblyjs/ast": {
"version": "1.9.0", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
@ -876,6 +914,11 @@
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
}, },
"browser-process-hrtime": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz",
"integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow=="
},
"browser-stdout": { "browser-stdout": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
@ -986,6 +1029,11 @@
"ieee754": "^1.1.4" "ieee754": "^1.1.4"
} }
}, },
"buffer-es6": {
"version": "4.9.3",
"resolved": "https://registry.npmjs.org/buffer-es6/-/buffer-es6-4.9.3.tgz",
"integrity": "sha1-8mNHuC33b9N+GLy1KIxJcM/VxAQ="
},
"buffer-from": { "buffer-from": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
@ -2335,6 +2383,12 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true "dev": true
}, },
"fast-extend": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/fast-extend/-/fast-extend-1.0.2.tgz",
"integrity": "sha512-XXA9RmlPatkFKUzqVZAFth18R4Wo+Xug/S+C7YlYA3xrXwfPlW3dqNwOb4hvQo7wZJ2cNDYhrYuPzVOfHy5/uQ==",
"dev": true
},
"fast-fifo": { "fast-fifo": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.0.0.tgz",
@ -2690,6 +2744,12 @@
} }
} }
}, },
"fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"dev": true
},
"fs-extra": { "fs-extra": {
"version": "9.0.1", "version": "9.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
@ -2701,6 +2761,12 @@
"universalify": "^1.0.0" "universalify": "^1.0.0"
} }
}, },
"fs-monkey": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-0.3.3.tgz",
"integrity": "sha512-FNUvuTAJ3CqCQb5ELn+qCbGR/Zllhf2HtwsdAtBi59s1WeCjKMT81fHcSu7dwIskqGVK+MmOrb7VOBlq3/SItw==",
"dev": true
},
"fs-write-stream-atomic": { "fs-write-stream-atomic": {
"version": "1.0.10", "version": "1.0.10",
"resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
@ -4824,6 +4890,16 @@
"p-is-promise": "^2.0.0" "p-is-promise": "^2.0.0"
} }
}, },
"memfs": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/memfs/-/memfs-3.0.4.tgz",
"integrity": "sha512-OcZEzwX9E5AoY8SXjuAvw0DbIAYwUzV/I236I8Pqvrlv7sL/Y0E9aRCon05DhaV8pg1b32uxj76RgW0s5xjHBA==",
"dev": true,
"requires": {
"fast-extend": "1.0.2",
"fs-monkey": "0.3.3"
}
},
"memory-fs": { "memory-fs": {
"version": "0.5.0", "version": "0.5.0",
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz",
@ -6170,7 +6246,6 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
"dev": true,
"requires": { "requires": {
"safe-buffer": "^5.1.0" "safe-buffer": "^5.1.0"
} }
@ -6179,7 +6254,6 @@
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz",
"integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==",
"dev": true,
"requires": { "requires": {
"randombytes": "^2.0.5", "randombytes": "^2.0.5",
"safe-buffer": "^5.1.0" "safe-buffer": "^5.1.0"
@ -7257,6 +7331,32 @@
"integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==",
"dev": true "dev": true
}, },
"tar-stream": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz",
"integrity": "sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==",
"dev": true,
"requires": {
"bl": "^4.0.3",
"end-of-stream": "^1.4.1",
"fs-constants": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1"
},
"dependencies": {
"bl": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz",
"integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==",
"dev": true,
"requires": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
}
}
}
},
"terser": { "terser": {
"version": "4.7.0", "version": "4.7.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.7.0.tgz", "resolved": "https://registry.npmjs.org/terser/-/terser-4.7.0.tgz",
@ -7823,6 +7923,12 @@
"integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==",
"dev": true "dev": true
}, },
"wasm-feature-detect": {
"version": "1.2.9",
"resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.2.9.tgz",
"integrity": "sha512-2E9/gtLVLpv2wnZDyYv8WY2dR9gHbmyv5uhZsnOcMSzqc78aGZpKQORPNcnrPwAU23nFUo7GAwKuoTAWRgsJ7Q==",
"dev": true
},
"watchpack": { "watchpack": {
"version": "1.7.2", "version": "1.7.2",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.2.tgz", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.2.tgz",

View File

@ -1,13 +1,14 @@
{ {
"name": "fluence", "name": "fluence",
"version": "0.7.24", "version": "0.7.38",
"description": "the browser js-libp2p client for the Fluence network", "description": "the browser js-libp2p client for the Fluence network",
"main": "./dist/fluence.js", "main": "./dist/fluence.js",
"typings": "./dist/fluence.d.ts", "typings": "./dist/fluence.d.ts",
"scripts": { "scripts": {
"test": "mocha -r ts-node/register src/**/*.spec.ts", "test": "mocha -r ts-node/register src/**/*.spec.ts",
"test-ts": "ts-mocha -p tsconfig.json src/**/*.spec.ts", "test-ts": "ts-mocha -p tsconfig.json src/**/*.spec.ts",
"package:build": "NODE_ENV=production webpack && tsc", "package:build": "NODE_ENV=production webpack && npm run package",
"package": "tsc && cp -r aqua dist",
"compile": "tsc", "compile": "tsc",
"start": "webpack-dev-server -p", "start": "webpack-dev-server -p",
"build": "webpack" "build": "webpack"
@ -16,7 +17,6 @@
"license": "Apache 2.0", "license": "Apache 2.0",
"dependencies": { "dependencies": {
"async": "3.2.0", "async": "3.2.0",
"loglevel": "1.7.0",
"base64-js": "1.3.1", "base64-js": "1.3.1",
"bs58": "4.0.1", "bs58": "4.0.1",
"cids": "0.8.1", "cids": "0.8.1",
@ -26,6 +26,7 @@
"libp2p-mplex": "0.9.5", "libp2p-mplex": "0.9.5",
"libp2p-secio": "0.12.5", "libp2p-secio": "0.12.5",
"libp2p-websockets": "0.13.6", "libp2p-websockets": "0.13.6",
"loglevel": "1.7.0",
"peer-id": "0.13.12", "peer-id": "0.13.12",
"uuid": "8.3.0" "uuid": "8.3.0"
}, },

93
src/callService.ts Normal file
View File

@ -0,0 +1,93 @@
/*
* 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 {getService, registerService} from "./globalState";
export interface CallServiceResult {
ret_code: number,
result: string
}
export class Service {
serviceId: string;
functions: Map<string, (args: any[]) => object> = new Map();
constructor(serviceId: string) {
this.serviceId = serviceId;
}
registerFunction(fnName: string, fn: (args: any[]) => object) {
this.functions.set(fnName, fn);
}
call(fnName: string, args: any[]): CallServiceResult {
let fn = this.functions.get(fnName)
if (fn) {
try {
let result = fn(args)
return {
ret_code: 0,
result: JSON.stringify(result)
}
} catch (err) {
return {
ret_code: 1,
result: JSON.stringify(err)
}
}
} else {
let errorMsg = `Error. There is no function ${fnName}`
return {
ret_code: 1,
result: JSON.stringify(errorMsg)
}
}
}
}
let logger = new Service("")
logger.registerFunction("", (args: any[]) => {
console.log("logger service: " + args)
return { result: "done" }
})
registerService(logger);
export function callService(service_id: string, fn_name: string, args: string): CallServiceResult {
try {
let argsObject = JSON.parse(args)
if (!Array.isArray(argsObject)) {
throw new Error("args is not an array")
}
let service = getService(service_id)
if (service) {
return service.call(fn_name, argsObject)
} else {
return {
result: JSON.stringify(`Error. There is no service: ${service_id}`),
ret_code: 0
}
}
} catch (err) {
console.error("Cannot parse arguments: " + JSON.stringify(err))
return {
result: JSON.stringify("Cannot parse arguments: " + JSON.stringify(err)),
ret_code: 1
}
}
}

View File

@ -1,61 +0,0 @@
import {getService, registerService} from "./globalState";
interface CallServiceResult {
ret_code: number,
result: string
}
export class Service {
serviceId: string;
functions: Map<string, (args: string) => string> = new Map();
constructor(serviceId: string) {
this.serviceId = serviceId;
}
registerFunction(fnName: string, fn: (args: string) => string) {
this.functions.set(fnName, fn);
}
call(fnName: string, args: string): CallServiceResult {
let fn = this.functions.get(fnName)
if (fn) {
try {
let result = fn(args)
return {
ret_code: 0,
result
}
} catch (err) {
return {
ret_code: 1,
result: JSON.stringify(err)
}
}
} else {
let errorMsg = `Error. There is no function ${fnName}`
return {
ret_code: 1,
result: errorMsg
}
}
}
}
let logger = new Service("logger")
logger.registerFunction("logger", (args: string) => {
console.log("logger service: " + args)
return ""
})
registerService(logger);
export function call_service(service_id: string, fn_name: string, args: string): string {
let service = getService(service_id)
if (service) {
return JSON.stringify(service.call(fn_name, args))
} else {
return `Error. There is no service ${service_id}`
}
}

View File

@ -48,4 +48,12 @@ export default class Fluence {
return client; return client;
} }
} }
declare global {
interface Window {
Fluence: Fluence;
}
}
window.Fluence = Fluence;

View File

@ -16,13 +16,14 @@
import {Particle} from "./particle"; import {Particle} from "./particle";
import {StepperOutcome} from "./stepperOutcome";
import * as PeerId from "peer-id"; import * as PeerId from "peer-id";
import Multiaddr from "multiaddr" import Multiaddr from "multiaddr"
import {FluenceConnection} from "./fluenceConnection"; import {FluenceConnection} from "./fluenceConnection";
import {Subscriptions} from "./subscriptions"; import {Subscriptions} from "./subscriptions";
import * as stepper from "../stepper"; import {addParticle, getCurrentParticleId, popParticle, setCurrentParticleId} from "./globalState";
import {instantiateStepper, Stepper} from "./stepper";
const WASM = stepper.loadWasm(); import log from "loglevel";
export class FluenceClient { export class FluenceClient {
readonly selfPeerId: PeerId; readonly selfPeerId: PeerId;
@ -30,6 +31,7 @@ export class FluenceClient {
private nodePeerIdStr: string; private nodePeerIdStr: string;
private subscriptions = new Subscriptions(); private subscriptions = new Subscriptions();
private stepper: Stepper = undefined;
connection: FluenceConnection; connection: FluenceConnection;
@ -39,38 +41,63 @@ export class FluenceClient {
} }
/** /**
* Waits a response that match the predicate. * Pass a particle to a stepper and send a result to other services.
*
* @param id
* @param ttl
*/ */
waitResponse(id: string, ttl: number): Promise<Particle> { private handleParticle(particle: Particle): void {
return new Promise((resolve, reject) => {
// subscribe for responses, to handle response // if a current particle is processing, add new particle to the queue
// TODO if there's no conn, reject if (getCurrentParticleId() !== undefined) {
this.subscriptions.subscribe(id, (particle: Particle) => { addParticle(particle);
resolve(particle); } else {
}, ttl); if (this.stepper === undefined) {
}) throw new Error("Undefined. Stepper is not initialized. User 'Fluence.connect' to create a client.")
}
// start particle processing if queue is empty
try {
let stepperOutcomeStr = this.stepper(particle.init_peer_id, particle.script, JSON.stringify(particle.data))
let stepperOutcome: StepperOutcome = JSON.parse(stepperOutcomeStr);
log.info("inner stepper outcome:");
log.info(stepperOutcome);
// do nothing if there is no `next_peer_pks`
if (stepperOutcome.next_peer_pks.length > 0) {
let newParticle: Particle = {...particle};
newParticle.data = JSON.parse(stepperOutcome.data);
this.connection.sendParticle(newParticle).catch((reason) => {
console.error(`Error on sending particle with id ${particle.id}: ${reason}`)
});
}
} finally {
// get last particle from the queue
let nextParticle = popParticle();
// start the processing of a new particle if it exists
if (nextParticle) {
// update current particle
setCurrentParticleId(nextParticle.id);
this.handleParticle(nextParticle)
} else {
// wait for a new call (do nothing) if there is no new particle in a queue
setCurrentParticleId(undefined);
}
}
}
} }
/** /**
* Handle incoming call. * Handle incoming particle from a relay.
* If FunctionCall returns - we should send it as a response.
*/ */
handleParticle(): (particle: Particle) => void { private handleExternalParticle(): (particle: Particle) => void {
let _this = this; let _this = this;
return (particle: Particle) => { return (particle: Particle) => {
// call all subscriptions for a new call let now = Date.now();
if (!_this.subscriptions.applyToSubscriptions(particle)) { if (particle.timestamp + particle.ttl > now) {
// if there is no subscription, use Stepper _this.handleParticle(particle);
WASM.then((w) => { } else {
let stepperOutcomeStr = w.invoke(particle.init_peer_id, particle.script, JSON.stringify(particle.data)) console.log(`Particle expired. Now: ${now}, ttl: ${particle.ttl}, ts: ${particle.timestamp}`)
let stepperOutcome: StepperOutcome = JSON.parse(stepperOutcomeStr);
console.log(stepperOutcome)
})
} }
} }
} }
@ -102,15 +129,19 @@ export class FluenceClient {
} }
let peerId = PeerId.createFromB58String(nodePeerId); let peerId = PeerId.createFromB58String(nodePeerId);
let connection = new FluenceConnection(multiaddr, peerId, this.selfPeerId, this.handleParticle());
this.stepper = await instantiateStepper(this.selfPeerId);
let connection = new FluenceConnection(multiaddr, peerId, this.selfPeerId, this.handleExternalParticle());
await connection.connect(); await connection.connect();
this.connection = connection; this.connection = connection;
} }
async sendParticle(particle: Particle): Promise<Particle> { sendParticle(particle: Particle): string {
await this.connection.sendParticle(particle); this.handleParticle(particle);
return this.waitResponse(particle.id, particle.ttl); this.subscriptions.subscribe(particle.id, particle.ttl);
return particle.id
} }
} }

View File

@ -36,7 +36,6 @@ enum Status {
export class FluenceConnection { export class FluenceConnection {
private readonly selfPeerId: PeerId; private readonly selfPeerId: PeerId;
readonly relay: PeerId;
private node: LibP2p; private node: LibP2p;
private readonly address: Multiaddr; private readonly address: Multiaddr;
readonly nodePeerId: PeerId; readonly nodePeerId: PeerId;
@ -91,8 +90,9 @@ export class FluenceConnection {
async function (source: AsyncIterable<string>) { async function (source: AsyncIterable<string>) {
for await (const msg of source) { for await (const msg of source) {
try { try {
log.debug(_this.selfPeerIdStr);
let particle = parseParticle(msg); let particle = parseParticle(msg);
log.debug("Particle is received:");
log.debug(JSON.stringify(particle, undefined, 2));
_this.handleCall(particle); _this.handleCall(particle);
} catch(e) { } catch(e) {
log.error("error on handling a new incoming message: " + e); log.error("error on handling a new incoming message: " + e);
@ -123,7 +123,7 @@ export class FluenceConnection {
this.checkConnectedOrThrow(); this.checkConnectedOrThrow();
let particleStr = stringifyParticle(particle); let particleStr = stringifyParticle(particle);
log.debug("send function call: \n" + JSON.stringify(particle, undefined, 2)); log.debug("send particle: \n" + JSON.stringify(particle, undefined, 2));
// create outgoing substream // create outgoing substream
const conn = await this.node.dialProtocol(this.address, PROTOCOL_NAME) as {stream: Stream; protocol: string}; const conn = await this.node.dialProtocol(this.address, PROTOCOL_NAME) as {stream: Stream; protocol: string};

View File

@ -1,7 +1,42 @@
import {Service} from "./call_service"; /*
* 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 {Service} from "./callService";
import {Particle} from "./particle";
// TODO put state with wasm file in each created FluenceClient // TODO put state with wasm file in each created FluenceClient
let services: Map<string, Service> = new Map(); let services: Map<string, Service> = new Map();
let particlesQueue: Particle[] = [];
let currentParticle: string | undefined = undefined;
export function getCurrentParticleId(): string | undefined {
return currentParticle;
}
export function setCurrentParticleId(particle: string | undefined) {
currentParticle = particle;
}
export function addParticle(particle: Particle): void {
particlesQueue.push(particle);
}
export function popParticle(): Particle | undefined {
return particlesQueue.pop();
}
export function registerService(service: Service) { export function registerService(service: Service) {
services.set(service.serviceId, service) services.set(service.serviceId, service)

View File

@ -35,7 +35,7 @@ export async function build(peerId: PeerId, script: string, data: object, ttl?:
let id = genUUID(); let id = genUUID();
let currentTime = (new Date()).getTime(); let currentTime = (new Date()).getTime();
if (ttl) { if (ttl === undefined) {
ttl = DEFAULT_TTL ttl = DEFAULT_TTL
} }

86
src/stepper.ts Normal file
View File

@ -0,0 +1,86 @@
/*
* 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 {wasmBs64} from "../wasmBs64";
import {toByteArray} from "base64-js";
import * as aqua from "../aqua"
import {callService} from "./callService";
import {getInt32Memory0, getStringFromWasm0, passStringToWasm0, WASM_VECTOR_LEN} from "../aqua";
import PeerId from "peer-id";
import log from "loglevel";
export type Stepper = (init_user_id: string, script: string, data: string) => string
export async function instantiateStepper(pid: PeerId): Promise<Stepper> {
// Fetch our Wasm File
const arr = toByteArray(wasmBs64)
let wasm: any = undefined;
const importObject = {
// __wbg_callserviceimpl_c0ca292e3c8c0c97 this is a function generated by bindgen. Could be changed.
// If so, an error with a new name will be occurred after wasm initialization.
"./index_bg.js": { __wbg_callserviceimpl_c0ca292e3c8c0c97: (arg0: any, arg1: any, arg2: any, arg3: any, arg4: any, arg5: any, arg6: any) => {
try {
let serviceId = getStringFromWasm0(wasm, arg1, arg2)
let fnName = getStringFromWasm0(wasm, arg3, arg4)
let args = getStringFromWasm0(wasm, arg5, arg6);
var ret = callService(serviceId, fnName, args);
let retStr = JSON.stringify(ret)
var ptr0 = passStringToWasm0(wasm, retStr, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
getInt32Memory0(wasm)[arg0 / 4 + 1] = len0;
getInt32Memory0(wasm)[arg0 / 4 + 0] = ptr0;
} finally {
wasm.__wbindgen_free(arg1, arg2);
wasm.__wbindgen_free(arg3, arg4);
wasm.__wbindgen_free(arg5, arg6);
}
},
__wbg_getcurrentpeeridimpl_a04b7c07e1108952: (arg0: any) => {
var ret = pid.toB58String();
var ptr0 = passStringToWasm0(wasm, ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
getInt32Memory0(wasm)[arg0 / 4 + 1] = len0;
getInt32Memory0(wasm)[arg0 / 4 + 0] = ptr0;
}
},
"host": { log_utf8_string: (arg0: any, arg1: any) => {
try {
let str = getStringFromWasm0(wasm, arg0, arg1)
log.debug(str)
} finally {
}
}
}
};
let module = await WebAssembly.compile(arr);
let webAssemblyInstantiatedSource = await WebAssembly.instantiate(module, {
...importObject
});
wasm = webAssemblyInstantiatedSource.exports
wasm.main();
let func = (init_user_id: string, script: string, data: string) => {
return aqua.invoke(wasm, init_user_id, script, data)
}
return func
}

View File

@ -1,4 +1,20 @@
interface StepperOutcome { /*
* 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 StepperOutcome {
ret_code: number, ret_code: number,
data: string, data: string,
next_peer_pks: string[] next_peer_pks: string[]

View File

@ -15,9 +15,10 @@
*/ */
import {Particle} from "./particle"; import {Particle} from "./particle";
import log from "loglevel";
export class Subscriptions { export class Subscriptions {
private subscriptions: Map<string, (particle: Particle) => void> = new Map(); private subscriptions: Set<string> = new Set();
constructor() {} constructor() {}
@ -25,35 +26,18 @@ export class Subscriptions {
* Subscriptions will be applied by outside message if id will be the same. * Subscriptions will be applied by outside message if id will be the same.
* *
* @param id message identificator * @param id message identificator
* @param f function to use with outside message
* @param ttl time to live, subscription will be deleted after this time * @param ttl time to live, subscription will be deleted after this time
*/ */
subscribe(id: string, f: (particle: Particle) => void, ttl: number) { subscribe(id: string, ttl: number) {
let _this = this; let _this = this;
setTimeout(() => { setTimeout(() => {
_this.subscriptions.delete(id) _this.subscriptions.delete(id)
console.log(`Particle with id ${id} deleted by timeout`) log.info(`Particle with id ${id} deleted by timeout`)
}, ttl) }, ttl)
this.subscriptions.set(id, f); this.subscriptions.add(id);
} }
/** hasSubscription(particle: Particle): boolean {
* A particle will be applied if id of the particle was subscribed earlier. return this.subscriptions.has(particle.id)
* @param particle
*/
applyToSubscriptions(particle: Particle): boolean {
let callback = this.subscriptions.get(particle.id)
if (callback) {
callback(particle);
return true;
} else {
if (Number(particle.timestamp) + particle.ttl > Date.now()) {
console.log("Old particle received. 'ttl' is ended.");
} else {
console.log("External particle received. 'Stepper' needed on client. Unimplemented.");
}
console.log(particle);
return false;
}
} }
} }

View File

@ -8,7 +8,7 @@ import {certificateFromString, certificateToString, issue} from "../trust/certif
import {TrustGraph} from "../trust/trust_graph"; import {TrustGraph} from "../trust/trust_graph";
import {nodeRootCert} from "../trust/misc"; import {nodeRootCert} from "../trust/misc";
import {peerIdToSeed, seedToPeerId} from "../seed"; import {peerIdToSeed, seedToPeerId} from "../seed";
import {build, Particle} from "../particle"; import {build} from "../particle";
describe("Typescript usage suite", () => { describe("Typescript usage suite", () => {
@ -42,10 +42,15 @@ describe("Typescript usage suite", () => {
let key1 = await Fluence.generatePeerId(); let key1 = await Fluence.generatePeerId();
let key2 = await Fluence.generatePeerId(); let key2 = await Fluence.generatePeerId();
// connect to two different nodes let nodePeerId = "12D3KooWQ8x4SMBmSSUrMzY2m13uzC7UoSyvHaDhTKx7hH8aXxpt"
let cl1 = await Fluence.connect("/dns4/134.209.186.43/tcp/9003/ws/p2p/12D3KooWBUJifCTgaxAUrcM9JysqCcS4CS8tiYH5hExbdWCAoNwb", key1); let addr = '/ip4/127.0.0.1/tcp/9001/ws/p2p/' + nodePeerId
let particle = await build(key1, "123", {a: 777, b: "567"}) // connect to two different nodes
let cl1 = await Fluence.connect(addr, key1);
let script = "((call (%current% (local_service_id local_fn_name) () result_name)) (call (remote_peer_id (service_id fn_name) () g)))"
let particle = await build(key1, script, {})
let result = await cl1.sendParticle(particle) let result = await cl1.sendParticle(particle)
console.log(result) console.log(result)

1
stepper.d.ts vendored
View File

@ -1 +0,0 @@
export function loadWasm(): Promise<typeof import('./pkg')>

View File

@ -1,3 +0,0 @@
export async function loadWasm() {
return import('./pkg')
}

1
wasmBs64.ts Normal file

File diff suppressed because one or more lines are too long

View File

@ -32,7 +32,7 @@ const config = {
new HtmlWebpackPlugin(), new HtmlWebpackPlugin(),
new WasmPackPlugin({ new WasmPackPlugin({
// TODO use another path somehow // TODO use another path somehow
crateDirectory: path.resolve(__dirname, "../../../aquamarine"), crateDirectory: path.resolve(__dirname, "../../../aquamarine/stepper"),
outDir:path.resolve(__dirname, "./pkg") outDir:path.resolve(__dirname, "./pkg")
}), }),
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({