diff --git a/.github/workflows/js_sdk_ci.yml b/.github/workflows/js_sdk_ci.yml index 97f31c87..78f780b7 100644 --- a/.github/workflows/js_sdk_ci.yml +++ b/.github/workflows/js_sdk_ci.yml @@ -1,39 +1,40 @@ -name: Node.js CI +name: Run tests defaults: - run: - working-directory: . + run: + working-directory: . on: - push: + push: jobs: - build: + build: + runs-on: ubuntu-latest - runs-on: ubuntu-latest + strategy: + matrix: + node-version: [14.x, 15.x] + node-env: ['', ':node'] - strategy: - matrix: - node-version: [14.x, 15.x] + steps: + - uses: actions/checkout@v2 - steps: - - uses: actions/checkout@v2 + - uses: actions/cache@v2 + with: + path: ~/.npm + key: ${{ runner.os }}-v1-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-v1-node-${{ matrix.node-version }} - - uses: actions/cache@v2 - with: - path: ~/.npm - key: ${{ runner.os }}-v1-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-v1-node-${{ matrix.node-version }} - - - name: Setup Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - - run: npm install - - run: npm run test:unit - env: - CI: true + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: Run container with Fluence node + run: docker run -d --rm -e RUST_LOG="info" -p 1210:1210 -p 4310:4310 fluencelabs/fluence -t 1210 -w 4310 -k gKdiCSUr1TFGFEgu2t8Ch1XEUsrN5A2UfBLjSZvfci9SPR3NvZpACfcpPGC3eY4zma1pk7UvYv5zb1VjvPHwCjj --local + - run: npm install + - run: npm run test${{ matrix.node-env }}:all + env: + CI: true diff --git a/jest-patched-jsdom.js b/jest-patched-jsdom.js new file mode 100644 index 00000000..a49175ac --- /dev/null +++ b/jest-patched-jsdom.js @@ -0,0 +1,14 @@ +const Environment = require('jest-environment-jsdom'); + +module.exports = class CustomTestEnvironment extends Environment { + async setup() { + await super.setup(); + if (typeof this.global.TextEncoder === 'undefined') { + const { TextEncoder, TextDecoder } = require('util'); + this.global.TextEncoder = TextEncoder; + this.global.TextDecoder = TextDecoder; + this.global.Uint8Array = Uint8Array; + this.global.ArrayBuffer = ArrayBuffer; + } + } +}; diff --git a/jest.config.js b/jest.config.js index 91a2d2c0..2b6b7ead 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,4 +1,4 @@ module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', -}; \ No newline at end of file + preset: 'ts-jest', + testEnvironment: './jest-patched-jsdom.js', +}; diff --git a/package-lock.json b/package-lock.json index 9adb664c..9682dd65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -434,10 +434,20 @@ "minimist": "^1.2.0" } }, - "@fluencelabs/aquamarine-interpreter": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/@fluencelabs/aquamarine-interpreter/-/aquamarine-interpreter-0.8.2.tgz", - "integrity": "sha512-WYn3fO3dy/ZTS2DnjpEv59RAFTuPosO65jejb/2QRAbZId4KiaCxma0yvFC6P0j2qoAxCDTRdbKrNZGXzjddYQ==" + "@fluencelabs/air-interpreter": { + "version": "0.9.7", + "resolved": "https://registry.npmjs.org/@fluencelabs/air-interpreter/-/air-interpreter-0.9.7.tgz", + "integrity": "sha512-9aJwfyfJ18BvSSFhTnq8SJZmIAgbyGXUf3vilsMhU6Iql35ZDd8E+5dF2hRkRrqUj2409w7qOtgLbB8JzdG/fw==", + "requires": { + "base64-js": "1.5.1" + }, + "dependencies": { + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + } + } }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", diff --git a/package.json b/package.json index 91ddc1ef..5de47a80 100644 --- a/package.json +++ b/package.json @@ -5,17 +5,21 @@ "main": "./dist/index.js", "typings": "./dist/index.d.ts", "scripts": { - "test": "jest --env=jsdom --watch", - "test:all": "jest --env=jsdom", - "test:unit": "jest --env=jsdom --testPathPattern=src/__test__/unit", - "test:integration": "jest --env=jsdom --testPathPattern=src/__test__/integration", + "test": "jest --watch", + "test:all": "jest", + "test:unit": "jest --testPathPattern=src/__test__/unit", + "test:integration": "jest --testPathPattern=src/__test__/integration", + "test:node": "jest --env=node --watch", + "test:node:all": "jest --env=node", + "test:node:unit": "jest --env=node --testPathPattern=src/__test__/unit", + "test:node:integration": "jest --env=node --testPathPattern=src/__test__/integration", "build": "tsc" }, "repository": "https://github.com/fluencelabs/fluence-js", "author": "Fluence Labs", "license": "Apache-2.0", "dependencies": { - "@fluencelabs/aquamarine-interpreter": "0.8.2", + "@fluencelabs/air-interpreter": "0.9.7", "async": "3.2.0", "base64-js": "1.3.1", "bs58": "4.0.1", diff --git a/src/__test__/connection.ts b/src/__test__/connection.ts index cb6fbe6a..daea1feb 100644 --- a/src/__test__/connection.ts +++ b/src/__test__/connection.ts @@ -13,7 +13,7 @@ /* * start docker container to run integration tests locally: -docker run --rm -e RUST_LOG="info" -p 1210:1210 -p 4310:4310 fluencelabs/fluence -t 1210 -w 4310 -k gKdiCSUr1TFGFEgu2t8Ch1XEUsrN5A2UfBLjSZvfci9SPR3NvZpACfcpPGC3eY4zma1pk7UvYv5zb1VjvPHwCjj +docker run --rm -e RUST_LOG="info" -p 1210:1210 -p 4310:4310 fluencelabs/fluence -t 1210 -w 4310 -k gKdiCSUr1TFGFEgu2t8Ch1XEUsrN5A2UfBLjSZvfci9SPR3NvZpACfcpPGC3eY4zma1pk7UvYv5zb1VjvPHwCjj --local */ export const nodes = [ diff --git a/src/__test__/integration/legacy.api.spec.ts b/src/__test__/integration/legacy.api.spec.ts index ec3bf3e4..fd3eaa60 100644 --- a/src/__test__/integration/legacy.api.spec.ts +++ b/src/__test__/integration/legacy.api.spec.ts @@ -5,6 +5,12 @@ import { nodes } from '../connection'; let client: FluenceClient; describe('Legacy api suite', () => { + afterEach(async () => { + if (client) { + await client.disconnect(); + } + }); + it('sendParticle', async () => { client = await createClient(nodes[0]); diff --git a/src/__test__/unit/air.spec.ts b/src/__test__/unit/air.spec.ts index 75663fe9..79e25692 100644 --- a/src/__test__/unit/air.spec.ts +++ b/src/__test__/unit/air.spec.ts @@ -67,7 +67,7 @@ describe('== AIR suite', () => { await client.initiateFlow(request); // assert - await expect(error).rejects.toContain("aqua script can't be parsed"); + await expect(error).rejects.toContain("air can't be parsed"); }); it('call script without ttl', async function () { diff --git a/src/__test__/unit/ast.spec.ts b/src/__test__/unit/ast.spec.ts index 53bf87dc..6841abc4 100644 --- a/src/__test__/unit/ast.spec.ts +++ b/src/__test__/unit/ast.spec.ts @@ -1,13 +1,18 @@ -import { AquamarineInterpreter } from '../../internal/aqua/interpreter'; +import { AirInterpreter } from '@fluencelabs/air-interpreter'; +import { genUUID } from '../../internal/particle'; describe('== AST parsing suite', () => { it('parse simple script and return ast', async function () { - const interpreter = await AquamarineInterpreter.create({} as any); + const interpreter = await AirInterpreter.create( + undefined as any, + undefined as any, + undefined as any, + undefined as any, + ); let ast = interpreter.parseAir(` (call "node" ("service" "function") [1 2 3] output) `); - console.log(ast); ast = JSON.parse(ast); expect(ast).toEqual({ diff --git a/src/internal/ClientImpl.ts b/src/internal/ClientImpl.ts index 4154e6c4..b3ea36c1 100644 --- a/src/internal/ClientImpl.ts +++ b/src/internal/ClientImpl.ts @@ -25,7 +25,7 @@ import { AquaCallHandler, errorHandler, fnHandler } from './AquaHandler'; import { loadRelayFn, loadVariablesService } from './RequestFlowBuilder'; import { logParticle, Particle } from './particle'; import log from 'loglevel'; -import { AquamarineInterpreter } from './aqua/interpreter'; +import { AirInterpreter } from '@fluencelabs/air-interpreter'; const makeDefaultClientHandler = (): AquaCallHandler => { const res = new AquaCallHandler(); @@ -54,7 +54,7 @@ export class ClientImpl implements FluenceClient { } private connection: FluenceConnection; - private interpreter: AquamarineInterpreter; + private interpreter: AirInterpreter; constructor(selfPeerIdFull: PeerId) { this.selfPeerIdFull = selfPeerIdFull; @@ -68,13 +68,18 @@ export class ClientImpl implements FluenceClient { await this.connection.disconnect(); } this.clearWathcDog(); + this.requests.forEach((r) => { + r.cancel(); + }); } async initAquamarineRuntime(): Promise { - this.interpreter = await AquamarineInterpreter.create({ - particleHandler: this.interpreterCallback.bind(this), - peerId: this.selfPeerIdFull, - }); + this.interpreter = await AirInterpreter.create( + this.interpreterCallback.bind(this), + this.selfPeerId, + 'trace', + log.log, + ); } async connect(multiaddr: string | Multiaddr, options?: FluenceConnectionOptions): Promise { diff --git a/src/internal/RequestFlow.ts b/src/internal/RequestFlow.ts index 440a5f52..cb3d9ecd 100644 --- a/src/internal/RequestFlow.ts +++ b/src/internal/RequestFlow.ts @@ -1,6 +1,6 @@ import log, { trace } from 'loglevel'; import PeerId from 'peer-id'; -import { AquamarineInterpreter } from './aqua/interpreter'; +import { AirInterpreter } from '@fluencelabs/air-interpreter'; import { AquaCallHandler } from './AquaHandler'; import { InterpreterOutcome, PeerIdB58 } from './commonTypes'; import { FluenceConnection } from './FluenceConnection'; @@ -20,6 +20,7 @@ export class RequestFlow { private prevData: Uint8Array = Buffer.from([]); private onTimeoutHandlers = []; private onErrorHandlers = []; + private timeoutHandle?: NodeJS.Timeout; readonly id: string; readonly isExternal: boolean; @@ -33,7 +34,7 @@ export class RequestFlow { const res = new RequestFlow(true, particle.id, particle.script); res.ttl = particle.ttl; res.state = particle; - setTimeout(res.raiseTimeout.bind(res), particle.ttl); + res.timeoutHandle = setTimeout(res.raiseTimeout.bind(res), particle.ttl); return res; } @@ -57,7 +58,7 @@ export class RequestFlow { this.onErrorHandlers.push(handler); } - async execute(interpreter: AquamarineInterpreter, connection: FluenceConnection, relayPeerId?: PeerIdB58) { + async execute(interpreter: AirInterpreter, connection: FluenceConnection, relayPeerId?: PeerIdB58) { if (this.hasExpired()) { return; } @@ -97,11 +98,18 @@ export class RequestFlow { if (!connection) { this.raiseError('Cannot send particle: non connected'); + return; } this.sendIntoConnection(connection); } + public cancel() { + if (this.timeoutHandle) { + clearTimeout(this.timeoutHandle); + } + } + private throwIncorrectNextPeerPks(nextPeers: PeerIdB58[]) { this.raiseError( `Particle is expected to be sent to only the single peer (relay which client is connected to). @@ -127,7 +135,7 @@ relay peer id: ${this.relayPeerId} }; this.state = particle; - setTimeout(this.raiseTimeout.bind(this), particle.ttl); + this.timeoutHandle = setTimeout(this.raiseTimeout.bind(this), particle.ttl); } receiveUpdate(particle: Particle) { @@ -145,7 +153,7 @@ relay peer id: ${this.relayPeerId} } } - runInterpreter(interpreter: AquamarineInterpreter) { + runInterpreter(interpreter: AirInterpreter) { const interpreterOutcomeStr = interpreter.invoke( this.state.init_peer_id, this.state.script, diff --git a/src/internal/aqua/index.ts b/src/internal/aqua/index.ts deleted file mode 100644 index 683e2fea..00000000 --- a/src/internal/aqua/index.ts +++ /dev/null @@ -1,162 +0,0 @@ -/** - * - * 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; -} - -const lTextEncoder = typeof TextEncoder === 'undefined' ? module.require('util').TextEncoder : TextEncoder; - -let cachedTextEncoder = new lTextEncoder('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; -} - -const lTextDecoder = typeof TextDecoder === 'undefined' ? module.require('util').TextDecoder : TextDecoder; - -let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true }); - -cachedTextDecoder.decode(); - -export function getStringFromWasm0(wasm, ptr, len) { - return cachedTextDecoder.decode(getUint8Memory0(wasm).subarray(ptr, ptr + len)); -} - -function passArray8ToWasm0(wasm, arg, malloc) { - const ptr = malloc(arg.length * 1); - getUint8Memory0(wasm).set(arg, ptr / 1); - WASM_VECTOR_LEN = arg.length; - return ptr; -} - -/** - * @param {any} wasm - * @param {string} init_peer_id - * @param {string} aqua - * @param {string} prev_data - * @param {string} data - * @param {string} log_level - * @returns {string} - */ -export function invoke(wasm, init_peer_id, aqua, prev_data, data, log_level) { - try { - var ptr0 = passStringToWasm0(wasm, init_peer_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 = passArray8ToWasm0(wasm, prev_data, wasm.__wbindgen_malloc); - var len2 = WASM_VECTOR_LEN; - var ptr3 = passArray8ToWasm0(wasm, data, wasm.__wbindgen_malloc); - var len3 = WASM_VECTOR_LEN; - 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); - } finally { - wasm.__wbindgen_free(r0, r1); - } -} - -export function ast(wasm, script) { - try { - var ptr0 = passStringToWasm0(wasm, script, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - var len0 = WASM_VECTOR_LEN; - wasm.ast(8, ptr0, len0); - var r0 = getInt32Memory0(wasm)[8 / 4 + 0]; - var r1 = getInt32Memory0(wasm)[8 / 4 + 1]; - return getStringFromWasm0(wasm, r0, r1); - } finally { - wasm.__wbindgen_free(r0, r1); - } -} - -export function return_current_peer_id(wasm, peerId, arg0) { - var ptr0 = passStringToWasm0(wasm, peerId, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - var len0 = WASM_VECTOR_LEN; - getInt32Memory0(wasm)[arg0 / 4 + 1] = len0; - getInt32Memory0(wasm)[arg0 / 4 + 0] = ptr0; -} - -export function return_call_service_result(wasm, ret, arg0) { - var ptr1 = passStringToWasm0(wasm, ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - var len1 = WASM_VECTOR_LEN; - getInt32Memory0(wasm)[arg0 / 4 + 1] = len1; - getInt32Memory0(wasm)[arg0 / 4 + 0] = ptr1; -} - -export function free(wasm, ptr, len) { - wasm.__wbindgen_free(ptr, len); -} diff --git a/src/internal/aqua/interpreter.ts b/src/internal/aqua/interpreter.ts deleted file mode 100644 index 6cd11e5b..00000000 --- a/src/internal/aqua/interpreter.ts +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright 2020 Fluence Labs Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { toByteArray } from 'base64-js'; -import * as aqua from '.'; -import { return_current_peer_id, return_call_service_result, getStringFromWasm0, free } from '.'; -import { ParticleHandler, CallServiceResult, SecurityTetraplet } from '../commonTypes'; - -import PeerId from 'peer-id'; -import log from 'loglevel'; -import wasmBs64 from '@fluencelabs/aquamarine-interpreter'; - -// prettier-ignore -type InterpreterInvoke = ( - init_peer_id: string, - script: string, - prev_data: Uint8Array, - data: Uint8Array, -) => string; - -type ImportObject = { - './aquamarine_client_bg.js': { - // fn call_service_impl(service_id: String, fn_name: String, args: String, security_tetraplets: String) -> String; - // prettier-ignore - __wbg_callserviceimpl_84d8278762e4c639: (arg0: any, arg1: any, arg2: any, arg3: any, arg4: any, arg5: any, arg6: any, arg7: any, arg8: any, ) => void; - __wbg_getcurrentpeeridimpl_4aca996e28cb8f44: (arg0: any) => void; - __wbindgen_throw: (arg: any) => void; - }; - host: LogImport; -}; - -type LogImport = { - log_utf8_string: (level: any, target: any, offset: any, size: any) => void; -}; - -type Exports = any; -type Instance = any; -type ExportValue = any; - -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(cfg: HostImportsConfig): Promise { - /// Create host imports that use module exports internally - let imports = cfg.newImportObject(); - - /// Instantiate interpreter - let interpreter_module = await WebAssembly.compile(interpreter_wasm); - let instance: Instance = await WebAssembly.instantiate(interpreter_module, imports); - - /// Set exports, so host imports can use them - cfg.setExports(instance.exports); - - /// Trigger interpreter initialization (i.e., call main function) - call_export(instance.exports.main); - - return instance; -} - -/// If export is a function, call it. Otherwise log a warning. -/// NOTE: any here is unavoidable, see Function interface definition -function call_export(f: ExportValue, ...argArray: any[]): any { - if (typeof f === 'function') { - return f(); - } else { - log.warn(`can't call export ${f}: it is not a function, but ${typeof f}`); - } -} - -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); - - switch (level) { - case 1: - log.error(str); - break; - case 2: - log.warn(str); - break; - case 3: - log.info(str); - break; - case 4: - log.debug(str); - break; - case 5: - // we don't want a trace in trace logs - log.debug(str); - break; - } - } finally { - } - }, - }; -} - -const theParticleHandler = ( - callback: ParticleHandler, - service_id: string, - fn_name: string, - args: string, - tetraplets: string, -): CallServiceResult => { - let argsObject; - let tetrapletsObject: SecurityTetraplet[][]; - try { - argsObject = JSON.parse(args); - if (!Array.isArray(argsObject)) { - throw new Error('args is not an array'); - } - - tetrapletsObject = JSON.parse(tetraplets); - } catch (err) { - log.error('Cannot parse arguments: ' + JSON.stringify(err)); - return { - result: JSON.stringify('Cannot parse arguments: ' + JSON.stringify(err)), - ret_code: 1, - }; - } - - return callback(service_id, fn_name, argsObject, tetrapletsObject); -}; - -/// Returns import object that describes host functions called by AIR interpreter -function newImportObject(particleHandler: ParticleHandler, 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': { - // prettier-ignore - __wbg_callserviceimpl_84d8278762e4c639: (arg0: any, arg1: any, arg2: any, arg3: any, arg4: any, arg5: any, arg6: any, arg7: any, arg8: any) => { - let wasm = cfg.exports; - try { - let serviceId = getStringFromWasm0(wasm, arg1, arg2); - let fnName = getStringFromWasm0(wasm, arg3, arg4); - let args = getStringFromWasm0(wasm, arg5, arg6); - let tetraplets = getStringFromWasm0(wasm, arg7, arg8); - /* - TODO:: parse and pack arguments into structure like the following - class Argument { - value: T, - SecurityTetraplet: tetraplet - } - */ - let serviceResult = theParticleHandler(particleHandler, serviceId, fnName, args, tetraplets); - let resultStr = JSON.stringify(serviceResult); - return_call_service_result(wasm, resultStr, arg0); - } finally { - free(wasm, arg1, arg2); - free(wasm, arg3, arg4); - free(wasm, arg5, arg6); - free(wasm, arg7, arg8); - } - }, - __wbg_getcurrentpeeridimpl_4aca996e28cb8f44: (arg0: any) => { - let peerIdStr = peerId.toB58String(); - let wasm = cfg.exports; - return_current_peer_id(wasm, peerIdStr, arg0); - }, - __wbindgen_throw: (arg: any) => { - log.error(`wbindgen throw: ${JSON.stringify(arg)}`); - }, - }, - host: log_import(cfg), - }; -} - -function newLogImport(cfg: HostImportsConfig): ImportObject { - return { - host: log_import(cfg), - './aquamarine_client_bg.js': { - __wbg_callserviceimpl_84d8278762e4c639: (_) => {}, - __wbg_getcurrentpeeridimpl_4aca996e28cb8f44: (_) => {}, - __wbindgen_throw: (_) => {}, - }, - }; -} - -/// Instantiates AIR interpreter, and returns its `invoke` function as closure -async function instantiateInterpreter(particleHandler: ParticleHandler, peerId: PeerId): Promise { - let cfg = new HostImportsConfig((cfg) => { - return newImportObject(particleHandler, cfg, peerId); - }); - let instance = await interpreterInstance(cfg); - - return (init_peer_id: string, script: string, prev_data: Uint8Array, data: Uint8Array) => { - 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_peer_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 -async function parseAstClosure(): Promise<(script: string) => string> { - let cfg = new HostImportsConfig((cfg) => newLogImport(cfg)); - let instance = await interpreterInstance(cfg); - - return (script: string) => { - return aqua.ast(instance.exports, script); - }; -} - -/// Parses script and returns AST in JSON format -/// NOTE & TODO: interpreter is instantiated every time, make it a lazy constant? -async function parseAIR(script: string): Promise { - let closure = await parseAstClosure(); - return closure(script); -} - -export class AquamarineInterpreter { - private wasmWrapper; - - constructor(wasmWrapper) { - this.wasmWrapper = wasmWrapper; - } - - static async create(config: { particleHandler: ParticleHandler; peerId: PeerId }) { - const cfg = new HostImportsConfig((cfg) => { - return newImportObject(config.particleHandler, cfg, config.peerId); - }); - - const instance = await interpreterInstance(cfg); - const res = new AquamarineInterpreter(instance); - return res; - } - - invoke(init_peer_id: string, script: string, prev_data: Uint8Array, data: Uint8Array): string { - 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(this.wasmWrapper.exports, init_peer_id, script, prev_data, data, logLevelStr); - } - - parseAir(script: string): string { - return aqua.ast(this.wasmWrapper.exports, script); - } -}