mirror of
https://github.com/fluencelabs/fluence-js.git
synced 2024-12-04 18:00:18 +00:00
Fix host imports in fluence-js (#997)
This commit is contained in:
parent
84f0b3ba18
commit
8a10957efe
8
package-lock.json
generated
8
package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "fluence",
|
||||
"version": "0.7.96",
|
||||
"version": "0.7.99",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@ -25,9 +25,9 @@
|
||||
}
|
||||
},
|
||||
"@fluencelabs/aquamarine-stepper": {
|
||||
"version": "0.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@fluencelabs/aquamarine-stepper/-/aquamarine-stepper-0.0.15.tgz",
|
||||
"integrity": "sha512-dCzBlqiR0/EiYzCseIlpE8wbi/jRMLvMsFkTIm0tmhvSfWtBskXLniM9CE4JqyUATAVuBdKJ5HKsufACugGf4A=="
|
||||
"version": "0.0.16",
|
||||
"resolved": "https://registry.npmjs.org/@fluencelabs/aquamarine-stepper/-/aquamarine-stepper-0.0.16.tgz",
|
||||
"integrity": "sha512-yrRMH2ysrxkhOGUe599urPopx6bon44qHppyvE6RKdrE1qVgxYKONWU+BNM+ouzIZ3UW5hoT0gQZ7lmI3HS30g=="
|
||||
},
|
||||
"@sinonjs/commons": {
|
||||
"version": "1.7.2",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "fluence",
|
||||
"version": "0.7.97",
|
||||
"version": "0.7.100",
|
||||
"description": "the browser js-libp2p client for the Fluence network",
|
||||
"main": "./dist/fluence.js",
|
||||
"typings": "./dist/fluence.d.ts",
|
||||
@ -10,12 +10,13 @@
|
||||
"package:build": "NODE_ENV=production webpack && npm run package",
|
||||
"package": "tsc && rsync -r src/aqua/*.js dist/aqua",
|
||||
"start": "webpack-dev-server -p",
|
||||
"build": "webpack"
|
||||
"build": "webpack --mode production"
|
||||
},
|
||||
"repository": "https://github.com/fluencelabs/fluence",
|
||||
"author": "Fluence Labs",
|
||||
"license": "Apache 2.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@fluencelabs/aquamarine-stepper": "0.0.15",
|
||||
"@fluencelabs/aquamarine-stepper": "0.0.16",
|
||||
"async": "3.2.0",
|
||||
"base64-js": "1.3.1",
|
||||
"bs58": "4.0.1",
|
||||
|
3
src/aqua/index.d.ts
vendored
3
src/aqua/index.d.ts
vendored
@ -6,9 +6,10 @@
|
||||
* @param {string} aqua
|
||||
* @param {string} prev_data
|
||||
* @param {string} data
|
||||
* @param {string} log_level
|
||||
* @returns {string}
|
||||
*/
|
||||
export function invoke(wasm: any, init_user_id: string, aqua: string, prev_data: string, data: string): string;
|
||||
export function invoke(wasm: any, init_user_id: string, aqua: string, prev_data: string, data: string, log_level: string): string;
|
||||
export function ast(wasm: any, script: string): string;
|
||||
export function getStringFromWasm0(wasm: any, arg1: any, arg2: any): string
|
||||
export function getInt32Memory0(wasm: any): number[]
|
||||
|
@ -96,9 +96,10 @@ export function getStringFromWasm0(wasm, ptr, len) {
|
||||
* @param {string} aqua
|
||||
* @param {string} prev_data
|
||||
* @param {string} data
|
||||
* @param {string} log_level
|
||||
* @returns {string}
|
||||
*/
|
||||
export function invoke(wasm, init_user_id, aqua, prev_data, data) {
|
||||
export function invoke(wasm, init_user_id, aqua, prev_data, data, log_level) {
|
||||
try {
|
||||
var ptr0 = passStringToWasm0(wasm, init_user_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
var len0 = WASM_VECTOR_LEN;
|
||||
@ -108,7 +109,9 @@ export function invoke(wasm, init_user_id, aqua, prev_data, data) {
|
||||
var len2 = WASM_VECTOR_LEN;
|
||||
var ptr3 = passStringToWasm0(wasm, data, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
var len3 = WASM_VECTOR_LEN;
|
||||
wasm.invoke(8, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3);
|
||||
var ptr4 = passStringToWasm0(wasm, log_level, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
var len4 = WASM_VECTOR_LEN;
|
||||
wasm.invoke(8, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4);
|
||||
var r0 = getInt32Memory0(wasm)[8 / 4 + 0];
|
||||
var r1 = getInt32Memory0(wasm)[8 / 4 + 1];
|
||||
return getStringFromWasm0(wasm, r0, r1);
|
||||
|
@ -19,10 +19,7 @@ import Multiaddr from "multiaddr"
|
||||
import {FluenceClient} from "./fluenceClient";
|
||||
import * as log from "loglevel";
|
||||
import {LogLevelDesc} from "loglevel";
|
||||
import {ServiceMultiple} from "./service";
|
||||
import {registerService} from "./globalState";
|
||||
import {build} from "./particle";
|
||||
import {instantiateInterpreter, parseAstClosure} from "./stepper";
|
||||
import {parseAstClosure} from "./stepper";
|
||||
|
||||
log.setLevel('info')
|
||||
|
||||
@ -39,6 +36,22 @@ export default class Fluence {
|
||||
return await PeerId.create({keyType: "Ed25519"});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create FluenceClient without connecting it to a relay
|
||||
*
|
||||
* @param peerId – client's peer id. Must contain a private key. See `generatePeerId()`
|
||||
*/
|
||||
static async local(peerId?: PeerId): Promise<FluenceClient> {
|
||||
if (!peerId) {
|
||||
peerId = await Fluence.generatePeerId()
|
||||
}
|
||||
|
||||
let client = new FluenceClient(peerId);
|
||||
await client.instantiateInterpreter();
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to Fluence node.
|
||||
*
|
||||
@ -46,15 +59,9 @@ export default class Fluence {
|
||||
* @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> {
|
||||
|
||||
if (!peerId) {
|
||||
peerId = await Fluence.generatePeerId()
|
||||
}
|
||||
|
||||
let client = new FluenceClient(peerId);
|
||||
let client = await Fluence.local(peerId);
|
||||
|
||||
await client.connect(multiaddr);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
|
@ -59,7 +59,7 @@ export class FluenceClient {
|
||||
enqueueParticle(particle);
|
||||
} else {
|
||||
if (this.interpreter === undefined) {
|
||||
throw new Error("Undefined. Interpreter is not initialized. User 'Fluence.connect' to create a client.")
|
||||
throw new Error("Undefined. Interpreter is not initialized. Use 'Fluence.connect' to create a client.")
|
||||
}
|
||||
// start particle processing if queue is empty
|
||||
try {
|
||||
@ -87,8 +87,8 @@ export class FluenceClient {
|
||||
log.info("inner interpreter outcome:");
|
||||
log.info(stepperOutcome);
|
||||
|
||||
// do nothing if there is no `next_peer_pks`
|
||||
if (stepperOutcome.next_peer_pks.length > 0) {
|
||||
// do nothing if there is no `next_peer_pks` or if client isn't connected to the network
|
||||
if (stepperOutcome.next_peer_pks.length > 0 && this.connection) {
|
||||
let newParticle: Particle = {...particle};
|
||||
newParticle.data = JSON.parse(stepperOutcome.call_path);
|
||||
|
||||
@ -138,18 +138,27 @@ export class FluenceClient {
|
||||
return this.connection.disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate WebAssembly with AIR interpreter to execute AIR scripts
|
||||
*/
|
||||
async instantiateInterpreter() {
|
||||
this.interpreter = await instantiateInterpreter(this.selfPeerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
|
||||
async connect(multiaddr: string | Multiaddr) {
|
||||
multiaddr = Multiaddr(multiaddr);
|
||||
|
||||
if (!this.interpreter) {
|
||||
throw Error("you must call 'instantiateInterpreter' before 'connect'")
|
||||
}
|
||||
|
||||
let nodePeerId = multiaddr.getPeerId();
|
||||
this.nodePeerIdStr = nodePeerId;
|
||||
|
||||
if (!nodePeerId) {
|
||||
throw Error("'multiaddr' did not contain a valid peer id")
|
||||
}
|
||||
@ -160,12 +169,8 @@ export class FluenceClient {
|
||||
await this.connection.disconnect();
|
||||
}
|
||||
|
||||
let peerId = PeerId.createFromB58String(nodePeerId);
|
||||
|
||||
this.interpreter = await instantiateInterpreter(this.selfPeerId);
|
||||
|
||||
let connection = new FluenceConnection(multiaddr, peerId, this.selfPeerId, this.handleExternalParticle());
|
||||
|
||||
let node = PeerId.createFromB58String(nodePeerId);
|
||||
let connection = new FluenceConnection(multiaddr, node, this.selfPeerId, this.handleExternalParticle());
|
||||
await connection.connect();
|
||||
|
||||
this.connection = connection;
|
||||
@ -176,6 +181,10 @@ export class FluenceClient {
|
||||
return particle.id
|
||||
}
|
||||
|
||||
async executeParticle(particle: Particle) {
|
||||
await this.handleParticle(particle);
|
||||
}
|
||||
|
||||
nodeIdentityCall(): string {
|
||||
return `(call "${this.nodePeerIdStr}" ("op" "identity") [] void[])`
|
||||
}
|
||||
|
@ -40,24 +40,36 @@ type LogImport = {
|
||||
log_utf8_string: (level: any, target: any, offset: any, size: any) => void
|
||||
}
|
||||
|
||||
class HostImportsConfig {
|
||||
exports: Exports | undefined;
|
||||
newImportObject: () => ImportObject;
|
||||
|
||||
constructor(create: (cfg: HostImportsConfig) => ImportObject) {
|
||||
this.exports = undefined;
|
||||
this.newImportObject = () => create(this);
|
||||
}
|
||||
|
||||
setExports(exports: Exports) {
|
||||
this.exports = exports;
|
||||
}
|
||||
}
|
||||
|
||||
const interpreter_wasm = toByteArray(wasmBs64)
|
||||
|
||||
/// Instantiates WebAssembly runtime with AIR interpreter module
|
||||
async function interpreterInstance(hostImports: (wasm: Exports) => Imports): Promise<Instance> {
|
||||
// Uninitialized exports reference that must be initialized after WebAssembly is instantiated
|
||||
let exports: Exports = undefined;
|
||||
async function interpreterInstance(cfg: HostImportsConfig): Promise<Instance> {
|
||||
/// Create host imports that use module exports internally
|
||||
let imports = hostImports(exports);
|
||||
let imports = cfg.newImportObject();
|
||||
|
||||
/// Instantiate interpreter
|
||||
let interpreter_module = await WebAssembly.compile(interpreter_wasm);
|
||||
let instance: Instance = await WebAssembly.instantiate(interpreter_module, imports);
|
||||
|
||||
/// Finally initialize exports, so host imports can use them
|
||||
exports = instance.exports;
|
||||
/// Set exports, so host imports can use them
|
||||
cfg.setExports(instance.exports);
|
||||
|
||||
/// Trigger interpreter initialization (i.e., call main function)
|
||||
call_export(exports.main);
|
||||
call_export(instance.exports.main);
|
||||
|
||||
return instance;
|
||||
}
|
||||
@ -72,9 +84,10 @@ function call_export(f: ExportValue, ...argArray: any[]): any {
|
||||
}
|
||||
}
|
||||
|
||||
function log_import(wasm: Exports): LogImport {
|
||||
function log_import(cfg: HostImportsConfig): LogImport {
|
||||
return {
|
||||
log_utf8_string: (level: any, target: any, offset: any, size: any) => {
|
||||
let wasm = cfg.exports;
|
||||
try {
|
||||
let str = getStringFromWasm0(wasm, offset, size)
|
||||
|
||||
@ -103,12 +116,13 @@ function log_import(wasm: Exports): LogImport {
|
||||
}
|
||||
|
||||
/// Returns import object that describes host functions called by AIR interpreter
|
||||
function newImportObject(wasm: Exports, peerId: PeerId): ImportObject {
|
||||
function newImportObject(cfg: HostImportsConfig, peerId: PeerId): ImportObject {
|
||||
return {
|
||||
// __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.
|
||||
"./aquamarine_client_bg.js": {
|
||||
__wbg_callserviceimpl_7d3cf77a2722659e: (arg0: any, arg1: any, arg2: any, arg3: any, arg4: any, arg5: any, arg6: any) => {
|
||||
let wasm = cfg.exports;
|
||||
try {
|
||||
let serviceId = getStringFromWasm0(wasm, arg1, arg2)
|
||||
let fnName = getStringFromWasm0(wasm, arg3, arg4)
|
||||
@ -126,6 +140,7 @@ function newImportObject(wasm: Exports, peerId: PeerId): ImportObject {
|
||||
}
|
||||
},
|
||||
__wbg_getcurrentpeeridimpl_154ce1848a306ff5: (arg0: any) => {
|
||||
let wasm = cfg.exports;
|
||||
var ret = peerId.toB58String();
|
||||
var ptr0 = passStringToWasm0(wasm, ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
var len0 = WASM_VECTOR_LEN;
|
||||
@ -133,13 +148,13 @@ function newImportObject(wasm: Exports, peerId: PeerId): ImportObject {
|
||||
getInt32Memory0(wasm)[arg0 / 4 + 0] = ptr0;
|
||||
}
|
||||
},
|
||||
host: log_import(wasm)
|
||||
host: log_import(cfg)
|
||||
};
|
||||
}
|
||||
|
||||
function newLogImport(wasm: Exports): ImportObject {
|
||||
function newLogImport(cfg: HostImportsConfig): ImportObject {
|
||||
return {
|
||||
host: log_import(wasm),
|
||||
host: log_import(cfg),
|
||||
"./aquamarine_client_bg.js": {
|
||||
__wbg_callserviceimpl_7d3cf77a2722659e: _ => {},
|
||||
__wbg_getcurrentpeeridimpl_154ce1848a306ff5: _ => {}
|
||||
@ -150,17 +165,36 @@ function newLogImport(wasm: Exports): ImportObject {
|
||||
/// Instantiates AIR interpreter, and returns its `invoke` function as closure
|
||||
/// NOTE: an interpreter is also called a stepper from time to time
|
||||
export async function instantiateInterpreter(peerId: PeerId): Promise<InterpreterInvoke> {
|
||||
let instance = await interpreterInstance(wasm => newImportObject(wasm, peerId));
|
||||
let cfg = new HostImportsConfig((cfg) => newImportObject(cfg, peerId))
|
||||
let instance = await interpreterInstance(cfg);
|
||||
|
||||
return (init_user_id: string, script: string, prev_data: string, data: string) => {
|
||||
return aqua.invoke(instance.exports, init_user_id, script, prev_data, data)
|
||||
|
||||
let logLevel = log.getLevel()
|
||||
let logLevelStr = "info"
|
||||
if (logLevel === 0) {
|
||||
logLevelStr = "trace"
|
||||
} else if (logLevel === 1) {
|
||||
logLevelStr = "debug"
|
||||
} else if (logLevel === 2) {
|
||||
logLevelStr = "info"
|
||||
} else if (logLevel === 3) {
|
||||
logLevelStr = "warn"
|
||||
} else if (logLevel === 4) {
|
||||
logLevelStr = "error"
|
||||
} else if (logLevel === 5) {
|
||||
logLevelStr = "off"
|
||||
}
|
||||
|
||||
return aqua.invoke(instance.exports, init_user_id, script, prev_data, data, logLevelStr)
|
||||
}
|
||||
}
|
||||
|
||||
/// Instantiate AIR interpreter with host imports containing only logger, but not call_service
|
||||
/// peerId isn't actually required for AST parsing, but host imports require it, and I don't see any workaround
|
||||
export async function parseAstClosure(): Promise<(script: string) => string> {
|
||||
let instance = await interpreterInstance(wasm => newLogImport(wasm));
|
||||
let cfg = new HostImportsConfig((cfg) => newLogImport(cfg));
|
||||
let instance = await interpreterInstance(cfg);
|
||||
|
||||
return (script: string) => {
|
||||
return aqua.ast(instance.exports, script)
|
||||
|
27
src/test/air.spec.ts
Normal file
27
src/test/air.spec.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import 'mocha';
|
||||
import Fluence from "../fluence";
|
||||
import {build} from "../particle";
|
||||
import { ServiceMultiple } from "../service";
|
||||
import { registerService } from "../globalState";
|
||||
|
||||
describe("AIR", () => {
|
||||
it("call local function", async function () {
|
||||
let service = new ServiceMultiple("console");
|
||||
registerService(service);
|
||||
service.registerFunction('log', (args: any[]) => {
|
||||
console.log(`log: ${args}`);
|
||||
|
||||
return {}
|
||||
})
|
||||
|
||||
let client = await Fluence.local();
|
||||
|
||||
let script = `(call %init_peer_id% ("console" "log") ["hello"])`
|
||||
|
||||
// Wrap script into particle, so it can be executed by local WASM runtime
|
||||
let particle = await build(client.selfPeerId, script, new Map())
|
||||
|
||||
await client.executeParticle(particle);
|
||||
})
|
||||
})
|
||||
|
@ -3,7 +3,7 @@ import Fluence from "../fluence";
|
||||
|
||||
describe("AIR AST parsing suite", () => {
|
||||
it("parse simple script and return ast", async function () {
|
||||
let ast = Fluence.parseAIR(`
|
||||
let ast = await Fluence.parseAIR(`
|
||||
(call node ("service" "function") [1 2 3 arg] output)
|
||||
`);
|
||||
|
||||
|
@ -6,9 +6,9 @@
|
||||
"./types"
|
||||
],
|
||||
"outDir": "./dist/",
|
||||
"baseUrl": ".",
|
||||
"sourceMap": true,
|
||||
"inlineSources": true,
|
||||
"noImplicitAny": true,
|
||||
"strictFunctionTypes": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
@ -17,11 +17,13 @@
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"declaration": true,
|
||||
"strict": true,
|
||||
"strictNullChecks": false,
|
||||
"esModuleInterop": true,
|
||||
"declarationMap": true,
|
||||
"baseUrl": "."
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"alwaysStrict": true,
|
||||
"noImplicitThis": true,
|
||||
"strictNullChecks": false,
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
|
Loading…
Reference in New Issue
Block a user