From 919c7d6ea1e9c153ff7a367873c85fb36624125d Mon Sep 17 00:00:00 2001 From: Akim <59872966+akim-bow@users.noreply.github.com> Date: Tue, 17 Oct 2023 22:14:08 +0700 Subject: [PATCH] feat(js-client)!: Adding strictes eslint and ts config to all packages [fixes DXJ-464] (#355) * introduce eslint * Fix all eslint errors * Eslint fix and some touches * Fix tests * Fix misc errors * change semver * change semver #2 * Fix path * Fix path #2 * freeze lock file in CI * fix package install * Fix formatting of surrounding files * Add empty prettier config * Fix formatting * Fix build errors * Remove unused deps * remove changelog from formatting * deps cleanup * make resource importers async * Refactor * Fix error message * remove comment * more refactoring * Update packages/core/js-client/src/compilerSupport/registerService.ts Co-authored-by: shamsartem * refactoring * refactoring fix * optimize import * Update packages/@tests/smoke/node/src/index.ts Co-authored-by: shamsartem * Revert package * Fix pnpm lock * Lint-fix * Fix CI * Update tests * Fix build * Fix import * Use forked threads dep * Use fixed version * Update threads * Fix lint * Fix test * Fix test * Add polyfill for assert * Add subpath import * Fix tests * Fix deps --------- Co-authored-by: shamsartem --- .editorconfig | 12 - .eslintrc.json | 133 + .github/workflows/tests.yml | 9 +- .gitignore | 1 + .prettierignore | 9 + .prettierrc | 1 + .prettierrc.cjs | 8 - CONTRIBUTING.md | 2 +- README.md | 154 +- ci.cjs | 217 +- package.json | 26 +- packages/@tests/.eslintrc.json | 6 + packages/@tests/aqua/_aqua/smoke_test.aqua | 3 + packages/@tests/aqua/package.json | 54 +- .../aqua/src/_aqua/finalize_particle.ts | 80 +- packages/@tests/aqua/src/_aqua/smoke_test.ts | 1081 +++--- packages/@tests/aqua/src/index.ts | 149 +- packages/@tests/aqua/src/wasmb64.ts | 19 +- packages/@tests/aqua/tsconfig.json | 13 +- packages/@tests/smoke/node/package.json | 42 +- packages/@tests/smoke/node/src/index.ts | 23 +- packages/@tests/smoke/node/tsconfig.json | 10 +- packages/@tests/smoke/web-cra-ts/package.json | 97 +- packages/@tests/smoke/web-cra-ts/src/App.tsx | 73 +- .../@tests/smoke/web-cra-ts/src/index.css | 6 +- .../@tests/smoke/web-cra-ts/src/index.tsx | 20 +- .../smoke/web-cra-ts/src/reportWebVitals.ts | 4 +- .../@tests/smoke/web-cra-ts/src/setupTests.ts | 2 +- .../@tests/smoke/web-cra-ts/test/index.ts | 68 +- .../@tests/smoke/web-cra-ts/tsconfig.json | 36 +- packages/@tests/smoke/web/package.json | 52 +- packages/@tests/smoke/web/public/index.js | 125 +- packages/@tests/smoke/web/src/index.ts | 91 +- packages/@tests/smoke/web/tsconfig.json | 10 +- packages/@tests/test-utils/package.json | 44 +- packages/@tests/test-utils/src/index.ts | 102 +- packages/@tests/test-utils/tsconfig.json | 11 +- packages/core/aqua-to-js/.eslintrc.json | 3 + packages/core/aqua-to-js/CHANGELOG.md | 9 +- packages/core/aqua-to-js/package.json | 57 +- packages/core/aqua-to-js/src/common.ts | 201 +- packages/core/aqua-to-js/src/constants.ts | 4 +- packages/core/aqua-to-js/src/future.ts | 61 - .../src/generate/__test__/generate.spec.ts | 64 +- .../core/aqua-to-js/src/generate/function.ts | 36 +- .../core/aqua-to-js/src/generate/header.ts | 24 +- .../core/aqua-to-js/src/generate/index.ts | 100 +- .../aqua-to-js/src/generate/interfaces.ts | 213 +- .../core/aqua-to-js/src/generate/service.ts | 91 +- packages/core/aqua-to-js/src/index.ts | 57 +- packages/core/aqua-to-js/src/utils.ts | 115 +- packages/core/aqua-to-js/tsconfig.json | 16 +- packages/core/interfaces/CHANGELOG.md | 41 +- packages/core/interfaces/package.json | 98 +- packages/core/interfaces/src/commonTypes.ts | 82 +- .../compilerSupport/aquaTypeDefinitions.ts | 332 +- .../compilerSupportInterface.ts | 98 +- packages/core/interfaces/src/fluenceClient.ts | 162 +- packages/core/interfaces/src/future.ts | 90 + packages/core/interfaces/src/index.ts | 12 +- packages/core/interfaces/src/utils.ts | 21 + packages/core/interfaces/tsconfig.json | 13 +- packages/core/js-client/CHANGELOG.md | 181 +- packages/core/js-client/package.json | 137 +- packages/core/js-client/src/api.ts | 231 +- .../js-client/src/clientPeer/ClientPeer.ts | 200 +- .../src/clientPeer/__test__/client.spec.ts | 316 +- .../src/clientPeer/__test__/connection.ts | 25 +- .../src/clientPeer/checkConnection.ts | 171 +- .../src/compilerSupport/__test__/v3.spec.ts | 381 +- .../src/compilerSupport/callFunction.ts | 178 +- .../src/compilerSupport/conversions.ts | 314 +- .../src/compilerSupport/registerService.ts | 93 +- .../js-client/src/compilerSupport/services.ts | 296 +- .../src/connection/RelayConnection.ts | 444 +-- .../js-client/src/connection/interfaces.ts | 48 +- .../src/ephemeral/__test__/ephemeral.spec.ts | 105 +- .../core/js-client/src/ephemeral/client.ts | 56 +- .../core/js-client/src/ephemeral/network.ts | 494 +-- .../core/js-client/src/fetchers/browser.ts | 53 +- packages/core/js-client/src/fetchers/index.ts | 25 +- packages/core/js-client/src/fetchers/node.ts | 80 +- packages/core/js-client/src/index.ts | 340 +- .../core/js-client/src/jsPeer/FluencePeer.ts | 1146 +++--- .../js-client/src/jsPeer/__test__/avm.spec.ts | 203 +- .../js-client/src/jsPeer/__test__/par.spec.ts | 122 +- .../src/jsPeer/__test__/parseAst.spec.ts | 59 +- .../src/jsPeer/__test__/peer.spec.ts | 288 +- .../src/jsServiceHost/JsServiceHost.ts | 177 +- .../js-client/src/jsServiceHost/interfaces.ts | 189 +- .../src/jsServiceHost/serviceUtils.ts | 74 +- .../src/keypair/__test__/KeyPair.spec.ts | 52 +- .../src/marine/__test__/marine-js.spec.ts | 76 +- .../src/marine/deps-loader/common.ts | 40 - .../js-client/src/marine/deps-loader/node.ts | 84 +- .../js-client/src/marine/deps-loader/web.ts | 63 - .../core/js-client/src/marine/interfaces.ts | 121 +- .../src/marine/worker-script/workerLoader.ts | 21 +- .../core/js-client/src/marine/worker/index.ts | 169 +- .../core/js-client/src/particle/Particle.ts | 90 +- .../core/js-client/src/particle/interfaces.ts | 61 +- .../core/js-client/src/services/NodeUtils.ts | 96 +- packages/core/js-client/src/services/Sig.ts | 142 +- .../js-client/src/services/SingleModuleSrv.ts | 150 +- .../core/js-client/src/services/Tracing.ts | 19 +- .../services/__test__/builtInHandler.spec.ts | 573 +-- .../src/services/__test__/jsonBuiltin.spec.ts | 108 +- .../src/services/__test__/sigService.spec.ts | 258 +- .../src/services/__test__/srv.spec.ts | 156 +- .../src/services/_aqua/node-utils.ts | 149 +- .../js-client/src/services/_aqua/services.ts | 267 +- .../src/services/_aqua/single-module-srv.ts | 265 +- .../js-client/src/services/_aqua/tracing.ts | 99 +- .../core/js-client/src/services/builtins.ts | 1279 ++++--- .../js-client/src/services/securityGuard.ts | 71 +- packages/core/js-client/src/util/bytes.ts | 22 +- .../core/js-client/src/util/commonTypes.ts | 12 +- .../core/js-client/src/util/libp2pUtils.ts | 30 +- .../core/js-client/src/util/loadClient.ts | 80 - packages/core/js-client/src/util/logger.ts | 54 +- packages/core/js-client/src/util/testUtils.ts | 260 +- packages/core/js-client/src/util/utils.ts | 18 +- packages/core/js-client/tsconfig.json | 19 +- packages/core/js-client/vite.config.ts | 78 +- packages/core/marine-worker/CHANGELOG.md | 14 +- packages/core/marine-worker/package.json | 60 +- packages/core/marine-worker/src/index.ts | 205 +- packages/core/marine-worker/tsconfig.json | 12 +- packages/core/marine-worker/vite.config.ts | 45 +- pnpm-lock.yaml | 3096 +++++++---------- pnpm-workspace.yaml | 2 +- reset.d.ts | 17 + resources/license-header.js | 15 + tsconfig.eslint.json | 4 + tsconfig.json | 25 +- 135 files changed, 10529 insertions(+), 9167 deletions(-) delete mode 100644 .editorconfig create mode 100644 .eslintrc.json create mode 100644 .prettierrc delete mode 100644 .prettierrc.cjs create mode 100644 packages/@tests/.eslintrc.json create mode 100644 packages/core/aqua-to-js/.eslintrc.json delete mode 100644 packages/core/aqua-to-js/src/future.ts create mode 100644 packages/core/interfaces/src/future.ts create mode 100644 packages/core/interfaces/src/utils.ts delete mode 100644 packages/core/js-client/src/marine/deps-loader/common.ts delete mode 100644 packages/core/js-client/src/marine/deps-loader/web.ts delete mode 100644 packages/core/js-client/src/util/loadClient.ts create mode 100644 reset.d.ts create mode 100644 resources/license-header.js create mode 100644 tsconfig.eslint.json diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index c1322dc7..00000000 --- a/.editorconfig +++ /dev/null @@ -1,12 +0,0 @@ -# EditorConfig is awesome: https://EditorConfig.org - -# top-most EditorConfig file -root = true - -[*] -indent_style = space -indent_size = 4 -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = false -insert_final_newline = false \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000..7edefc46 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,133 @@ +{ + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 2022, + "project": ["./tsconfig.eslint.json"], + "sourceType": "module" + }, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/strict-type-checked", + "plugin:import/recommended", + "plugin:import/typescript", + "prettier" + ], + "plugins": [ + "@typescript-eslint", + "import", + "license-header", + "unused-imports" + ], + "ignorePatterns": ["**/node_modules/**/*", "**/dist/**/*"], + "rules": { + "eqeqeq": [ + "error", + "always", + { + "null": "ignore" + } + ], + "no-console": ["error"], + "arrow-body-style": ["error", "always"], + "no-empty": [ + "error", + { + "allowEmptyCatch": true + } + ], + "curly": ["error", "all"], + "no-unused-expressions": ["error"], + "dot-notation": ["off"], + "object-curly-spacing": ["error", "always"], + "padding-line-between-statements": [ + "error", + { + "blankLine": "always", + "prev": "multiline-expression", + "next": "*" + }, + { + "blankLine": "always", + "prev": "*", + "next": "multiline-expression" + }, + { + "blankLine": "always", + "prev": "multiline-block-like", + "next": "*" + }, + { + "blankLine": "always", + "prev": "*", + "next": "multiline-block-like" + }, + { + "blankLine": "always", + "prev": "multiline-const", + "next": "*" + }, + { + "blankLine": "always", + "prev": "*", + "next": "multiline-const" + }, + { + "blankLine": "always", + "prev": "multiline-let", + "next": "*" + }, + { + "blankLine": "always", + "prev": "*", + "next": "multiline-let" + }, + { + "blankLine": "any", + "prev": "case", + "next": "case" + } + ], + "import/extensions": ["error", "ignorePackages"], + "import/no-unresolved": "off", + "import/no-cycle": ["error"], + "import/order": [ + "error", + { + "newlines-between": "always", + "alphabetize": { + "order": "asc", + "caseInsensitive": true + } + } + ], + "node/no-unsupported-features/es-syntax": "off", + "node/no-unpublished-import": "off", + "node/no-missing-import": "off", + "@typescript-eslint/explicit-member-accessibility": [ + "error", + { + "accessibility": "no-public" + } + ], + "@typescript-eslint/strict-boolean-expressions": [ + "error", + { + "allowString": false, + "allowNumber": false, + "allowNullableObject": false, + "allowNullableBoolean": false, + "allowNullableString": false, + "allowNullableNumber": false, + "allowAny": false + } + ], + "@typescript-eslint/consistent-type-assertions": [ + "error", + { + "assertionStyle": "never" + } + ], + "unused-imports/no-unused-imports": "error", + "license-header/header": ["error", "./resources/license-header.js"] + } +} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e66f0541..4aa537f1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -88,6 +88,11 @@ jobs: registry-url: "https://npm.fluence.dev" cache: "pnpm" + - run: pnpm -r i + - run: pnpm -r build + - run: pnpm lint-check + + - name: Override dependencies uses: fluencelabs/github-actions/pnpm-set-dependency@main with: @@ -97,10 +102,8 @@ jobs: "@fluencelabs/marine-js": "${{ inputs.marine-js-version }}" } - - run: pnpm -r --no-frozen-lockfile i - - run: pnpm -r build + - run: pnpm -r i - run: pnpm -r test - - name: Dump rust-peer logs if: always() uses: jwalton/gh-docker-logs@v2 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4ab83125..9ce219d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.log .idea +.eslintcache # Dependency directories node_modules/ diff --git a/.prettierignore b/.prettierignore index 26f5c15f..ce207483 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,10 @@ .github +.eslintcache +pnpm-lock.yaml + +**/node_modules +**/dist +**/build +**/public + +**/CHANGELOG.md \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/.prettierrc @@ -0,0 +1 @@ +{} diff --git a/.prettierrc.cjs b/.prettierrc.cjs deleted file mode 100644 index 36f36b84..00000000 --- a/.prettierrc.cjs +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - semi: true, - trailingComma: 'all', - singleQuote: true, - printWidth: 120, - tabWidth: 4, - useTabs: false, -}; diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b43bbe7a..d68db204 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,4 +10,4 @@ Things you need to know: ### Contributor License Agreement -When you contribute, you have to be aware that your contribution is covered by **[Apache License 2.0](./LICENSE)**, but might relicensed under few other software licenses mentioned in the **Contributor License Agreement**. In particular, you need to agree to the Contributor License Agreement. If you agree to its content, you simply have to click on the link posted by the CLA assistant as a comment to the pull request. Click it to check the CLA, then accept it on the following screen if you agree to it. The CLA assistant will save this decision for upcoming contributions and will notify you if there is any change to the CLA in the meantime. +When you contribute, you have to be aware that your contribution is covered by **[Apache License 2.0](./LICENSE)**, but might relicensed under few other software licenses mentioned in the **Contributor License Agreement**. In particular, you need to agree to the Contributor License Agreement. If you agree to its content, you simply have to click on the link posted by the CLA assistant as a comment to the pull request. Click it to check the CLA, then accept it on the following screen if you agree to it. The CLA assistant will save this decision for upcoming contributions and will notify you if there is any change to the CLA in the meantime. diff --git a/README.md b/README.md index fbbfd87d..b8aa604b 100644 --- a/README.md +++ b/README.md @@ -13,34 +13,34 @@ Adding the Fluence JS client for your web application is very easy. 1. Add a script tag with the JS Client bundle to your `index.html`. The easiest way to do this is using a CDN (like [JSDELIVR](https://www.jsdelivr.com/) or [UNPKG](https://unpkg.com/)). The script is large, thus we highly recommend to use the `async` attribute. - Here is an example using the JSDELIVR CDN: + Here is an example using the JSDELIVR CDN: - ```html - - Cool App - - - ``` + ```html + + Cool App + + + ``` - If you cannot or don't want to use a CDN, feel free to get the script directly from the [npm package](https://www.npmjs.com/package/@fluencelabs/js-client.web.standalone) and host it yourself. You can find the script in the `/dist` directory of the package. (Note: this option means that developers understand what they are doing and know how to serve this file from their own web server.) + If you cannot or don't want to use a CDN, feel free to get the script directly from the [npm package](https://www.npmjs.com/package/@fluencelabs/js-client.web.standalone) and host it yourself. You can find the script in the `/dist` directory of the package. (Note: this option means that developers understand what they are doing and know how to serve this file from their own web server.) 2. Install the following packages: - ``` - npm i @fluencelabs/js-client.api @fluencelabs/fluence-network-environment - ``` + ``` + npm i @fluencelabs/js-client.api @fluencelabs/fluence-network-environment + ``` 3. Add the following lines at the beginning of your code: - ``` - import { Fluence } from "@fluencelabs/js-client.api"; - import { randomKras } from '@fluencelabs/fluence-network-environment'; + ``` + import { Fluence } from "@fluencelabs/js-client.api"; + import { randomKras } from '@fluencelabs/fluence-network-environment'; - Fluence.connect(randomKras()); - ``` + Fluence.connect(randomKras()); + ``` ### Node.js Apps @@ -48,37 +48,37 @@ Adding the Fluence JS client for your web application is very easy. The Fluence JS Client only supports the ESM format. This implies that a few preliminary steps are required if your project is not already using ESM: -- Add `"type": "module"` to your package.json. -- Replace `"main": "index.js"` with `"exports": "./index.js"` in your package.json. -- Remove `'use strict';` from all JavaScript files. -- Replace all `require()`/`module.export` with `import`/`export`. -- Use only full relative file paths for imports: `import x from '.';` → `import x from './index.js';`. +- Add `"type": "module"` to your package.json. +- Replace `"main": "index.js"` with `"exports": "./index.js"` in your package.json. +- Remove `'use strict';` from all JavaScript files. +- Replace all `require()`/`module.export` with `import`/`export`. +- Use only full relative file paths for imports: `import x from '.';` → `import x from './index.js';`. If you are using TypeScript: -- Make sure you are using TypeScript 4.7 or later. -- Add [`"module": "ESNext", "target": "ESNext", "moduleResolution": "nodenext"`](https://www.typescriptlang.org/tsconfig#module) to your tsconfig.json. -- Use only full relative file paths for imports: `import x from '.';` → `import x from './index.js';`. -- Remove `namespace` usage and use `export` instead. -- You must use a `.js` extension in relative imports even though you're importing `.ts` files. +- Make sure you are using TypeScript 4.7 or later. +- Add [`"module": "ESNext", "target": "ESNext", "moduleResolution": "nodenext"`](https://www.typescriptlang.org/tsconfig#module) to your tsconfig.json. +- Use only full relative file paths for imports: `import x from '.';` → `import x from './index.js';`. +- Remove `namespace` usage and use `export` instead. +- You must use a `.js` extension in relative imports even though you're importing `.ts` files. **Installation:** 1. Install the following packages: - ``` - npm i @fluencelabs/js-client.api"@fluencelabs/js-client.node @fluencelabs/fluence-network-environment - ``` + ``` + npm i @fluencelabs/js-client.api"@fluencelabs/js-client.node @fluencelabs/fluence-network-environment + ``` 2. Add the following lines at the beginning of your code: - ``` - import '@fluencelabs/js-client.node'; - import { Fluence } from "@fluencelabs/js-client.api"; - import { randomKras } from '@fluencelabs/fluence-network-environment'; + ``` + import '@fluencelabs/js-client.node'; + import { Fluence } from "@fluencelabs/js-client.api"; + import { randomKras } from '@fluencelabs/fluence-network-environment'; - Fluence.connect(randomKras()); - ``` + Fluence.connect(randomKras()); + ``` ## Usage in an Application @@ -86,9 +86,9 @@ Once you've added the client, you can compile [Aqua](https://github.com/fluencel 1. Install the package: - ``` - npm i -D @fluencelabs/cli - ``` + ``` + npm i -D @fluencelabs/cli + ``` 2. Add a directory in your project for Aqua code, e.g., `_aqua`. @@ -98,46 +98,46 @@ Once you've added the client, you can compile [Aqua](https://github.com/fluencel 5. To compile Aqua code once, run `npx fluence aqua -i ./_aqua -o ./src/_aqua/`. To watch the changes and to recompile on the fly, add the `-w` flag: `npx fluence aqua -w -i ./_aqua -o ./src/_aqua/`. - **Hint**: it might be a good idea to add these scripts to your `package.json` file. - For example, you project structure could look like this: + **Hint**: it might be a good idea to add these scripts to your `package.json` file. + For example, you project structure could look like this: - ``` - ┣ _aqua - ┃ ┗ demo.aqua - ┣ src - ┃ ┣ _aqua - ┃ ┃ ┗ demo.ts - ┃ ┗ index.ts - ┣ package-lock.json - ┣ package.json - ┗ tsconfig.json - ``` + ``` + ┣ _aqua + ┃ ┗ demo.aqua + ┣ src + ┃ ┣ _aqua + ┃ ┃ ┗ demo.ts + ┃ ┗ index.ts + ┣ package-lock.json + ┣ package.json + ┗ tsconfig.json + ``` - Then, your `package.json` file should include the following lines: + Then, your `package.json` file should include the following lines: - ``` - { - ... - "scripts": { - ... - "aqua:compile": "fluence aqua -i ./aqua/ -o ./src/_aqua", - "aqua:watch": "fluence aqua -w -i ./aqua/ -o ./src/_aqua" - }, - ... - } - ``` + ``` + { + ... + "scripts": { + ... + "aqua:compile": "fluence aqua -i ./aqua/ -o ./src/_aqua", + "aqua:watch": "fluence aqua -w -i ./aqua/ -o ./src/_aqua" + }, + ... + } + ``` 6. Now you can import and call Aqua code from your application like this: - ``` - import { getRelayTime } from "./_aqua/demo"; + ``` + import { getRelayTime } from "./_aqua/demo"; - async function buttonClick() { - const time = await getRelayTime(); - alert("relay time: " + time); - } - ``` + async function buttonClick() { + const time = await getRelayTime(); + alert("relay time: " + time); + } + ``` ## Debug @@ -165,10 +165,10 @@ Star (`*`) character can be used as a wildcard to enable logs for multiple compo ### Index of components: -- `particle`: everything related to particle processing queue -- `aqua`: infrastructure of aqua compiler support -- `connection`: connection layer -- `marine`: Marine JS logs +- `particle`: everything related to particle processing queue +- `aqua`: infrastructure of aqua compiler support +- `connection`: connection layer +- `marine`: Marine JS logs ### Enabling logs in Node.js diff --git a/ci.cjs b/ci.cjs index 6fa69e2c..b0b5758a 100644 --- a/ci.cjs +++ b/ci.cjs @@ -4,164 +4,163 @@ const fs = require("fs").promises; const path = require("path"); function printUsage() { - console.log( - `Usage: "ci check-consistency" or "ci bump-version %postfix%" or "ci get-version"` - ); + console.log( + `Usage: "ci check-consistency" or "ci bump-version %postfix%" or "ci get-version"`, + ); } let postfix; const mode = process.argv[2]; function validateArgs() { - switch (mode) { - case "get-version": - return true; + switch (mode) { + case "get-version": + return true; - case "bump-version": - postfix = process.argv[3]; - if (!postfix) { - printUsage(); - process.exit(); - } - return true; + case "bump-version": + postfix = process.argv[3]; + if (!postfix) { + printUsage(); + process.exit(); + } + return true; - case "": - case undefined: - case "check-consistency": - return true; + case "": + case undefined: + case "check-consistency": + return true; - default: - return false; - } + default: + return false; + } } const PATH_TO_PACKAGES = "./packages/"; async function getPackageJsonsRecursive(currentPath) { - return ( - await Promise.all( - (await fs.readdir(currentPath, { withFileTypes: true })) - .filter( - (file) => - file.name !== "node_modules" && file.name !== "@tests" && - (file.isDirectory() || file.name === "package.json") - ) - .map((file) => - file.isDirectory() - ? getPackageJsonsRecursive( - path.join(currentPath, file.name) - ) - : Promise.resolve([ - path.join(process.cwd(), currentPath, file.name), - ]) - ) + return ( + await Promise.all( + (await fs.readdir(currentPath, { withFileTypes: true })) + .filter( + (file) => + file.name !== "node_modules" && + file.name !== "@tests" && + (file.isDirectory() || file.name === "package.json"), ) - ).flat(); + .map((file) => + file.isDirectory() + ? getPackageJsonsRecursive(path.join(currentPath, file.name)) + : Promise.resolve([ + path.join(process.cwd(), currentPath, file.name), + ]), + ), + ) + ).flat(); } async function getVersion(file) { - const content = await fs.readFile(file); - const json = JSON.parse(content); - return [json.name, json.version]; + const content = await fs.readFile(file); + const json = JSON.parse(content); + return [json.name, json.version]; } function processDep(obj, name, fn) { - if (!obj) { - return; - } + if (!obj) { + return; + } - if (!obj[name]) { - return; - } + if (!obj[name]) { + return; + } - fn(obj, obj[name]); + fn(obj, obj[name]); } async function getVersionsMap(allPackageJsons) { - return new Map(await Promise.all(allPackageJsons.map(getVersion))); + return new Map(await Promise.all(allPackageJsons.map(getVersion))); } function getVersionForPackageOrThrow(versionsMap, packageName) { - const version = versionsMap.get(packageName); - if (!version) { - console.log("Failed to get version for package: ", packageName); - process.exit(1); - } - return version; + const version = versionsMap.get(packageName); + if (!version) { + console.log("Failed to get version for package: ", packageName); + process.exit(1); + } + return version; } async function checkConsistency(file, versionsMap) { - console.log("Checking: ", file); - const content = await fs.readFile(file); - const json = JSON.parse(content); + console.log("Checking: ", file); + const content = await fs.readFile(file); + const json = JSON.parse(content); - for (const [name, versionInDep] of versionsMap) { - const check = (x, version) => { - if (version.includes("*")) { - return; - } + for (const [name, versionInDep] of versionsMap) { + const check = (x, version) => { + if (version.includes("*")) { + return; + } - if (versionInDep !== version) { - console.log( - `Error, versions don't match: ${name}:${version} !== ${versionInDep}`, - file - ); - process.exit(1); - } - }; - processDep(json.dependencies, name, check); - processDep(json.devDependencies, name, check); - } + if (versionInDep !== version) { + console.log( + `Error, versions don't match: ${name}:${version} !== ${versionInDep}`, + file, + ); + process.exit(1); + } + }; + processDep(json.dependencies, name, check); + processDep(json.devDependencies, name, check); + } } async function bumpVersions(file, versionsMap) { - console.log("Updating: ", file); - const content = await fs.readFile(file); - const json = JSON.parse(content); + console.log("Updating: ", file); + const content = await fs.readFile(file); + const json = JSON.parse(content); - // bump dependencies - for (const [name, version] of versionsMap) { - const update = (x) => (x[name] = `${version}-${postfix}`); - processDep(json.dependencies, name, update); - processDep(json.devDependencies, name, update); - } + // bump dependencies + for (const [name, version] of versionsMap) { + const update = (x) => (x[name] = `${version}-${postfix}`); + processDep(json.dependencies, name, update); + processDep(json.devDependencies, name, update); + } - // also bump version in package itself - const version = getVersionForPackageOrThrow(versionsMap, json.name); - json.version = `${version}-${postfix}`; + // also bump version in package itself + const version = getVersionForPackageOrThrow(versionsMap, json.name); + json.version = `${version}-${postfix}`; - const newContent = JSON.stringify(json, undefined, 4) + "\n"; - await fs.writeFile(file, newContent); + const newContent = JSON.stringify(json, undefined, 4) + "\n"; + await fs.writeFile(file, newContent); } async function processPackageJsons(allPackageJsons, versionsMap, fn) { - await Promise.all(allPackageJsons.map((x) => fn(x, versionsMap))); + await Promise.all(allPackageJsons.map((x) => fn(x, versionsMap))); } async function run() { - if (!validateArgs()) { - printUsage(); - process.exit(0); - } + if (!validateArgs()) { + printUsage(); + process.exit(0); + } - const packageJsons = await getPackageJsonsRecursive(PATH_TO_PACKAGES); - const versionsMap = await getVersionsMap(packageJsons); + const packageJsons = await getPackageJsonsRecursive(PATH_TO_PACKAGES); + const versionsMap = await getVersionsMap(packageJsons); - if (mode === "get-version") { - const fjs = versionsMap.get("@fluencelabs/fluence"); - console.log(fjs); - return; - } + if (mode === "get-version") { + const fjs = versionsMap.get("@fluencelabs/fluence"); + console.log(fjs); + return; + } - // always check consistency - console.log("Checking versions consistency..."); - await processPackageJsons(packageJsons, versionsMap, checkConsistency); - console.log("Versions are consistent"); + // always check consistency + console.log("Checking versions consistency..."); + await processPackageJsons(packageJsons, versionsMap, checkConsistency); + console.log("Versions are consistent"); - if (mode === "bump-version") { - console.log("Adding postfix: ", postfix); - await processPackageJsons(packageJsons, versionsMap, bumpVersions); - console.log("Done"); - } + if (mode === "bump-version") { + console.log("Adding postfix: ", postfix); + await processPackageJsons(packageJsons, versionsMap, bumpVersions); + console.log("Done"); + } } run(); diff --git a/package.json b/package.json index faae3fd1..258c3dbe 100644 --- a/package.json +++ b/package.json @@ -8,15 +8,29 @@ "node": ">=10", "pnpm": ">=3" }, + "scripts": { + "lint-check": "pnpm run prettier --check && pnpm run eslint", + "lint-fix": "pnpm run prettier --write && pnpm run eslint --fix", + "prettier": "prettier .", + "eslint": "eslint --cache \"**/src/**/*.{js,ts}\"" + }, "author": "Fluence Labs", "license": "Apache-2.0", "devDependencies": { - "http-server": "14.1.1", - "puppeteer": "19.7.2", + "@total-typescript/ts-reset": "0.5.1", + "@tsconfig/strictest": "2.0.2", "@types/node": "18.13.0", + "@typescript-eslint/eslint-plugin": "6.7.3", + "@typescript-eslint/parser": "6.7.3", + "eslint": "8.50.0", + "eslint-config-prettier": "9.0.0", + "eslint-plugin-import": "2.28.1", + "eslint-plugin-license-header": "0.6.0", + "eslint-plugin-unused-imports": "3.0.0", + "http-server": "14.1.1", + "prettier": "3.0.3", + "puppeteer": "19.7.2", "ts-node": "10.9.1", - "typescript": "4.7", - "@fluencelabs/aqua-lib": "0.6.0", - "@fluencelabs/aqua": "0.9.1-374" + "typescript": "5.1.6" } -} \ No newline at end of file +} diff --git a/packages/@tests/.eslintrc.json b/packages/@tests/.eslintrc.json new file mode 100644 index 00000000..9d3374e3 --- /dev/null +++ b/packages/@tests/.eslintrc.json @@ -0,0 +1,6 @@ +{ + "ignorePatterns": ["**/*.css"], + "rules": { + "no-console": "off" + } +} diff --git a/packages/@tests/aqua/_aqua/smoke_test.aqua b/packages/@tests/aqua/_aqua/smoke_test.aqua index adfd9843..e6520538 100644 --- a/packages/@tests/aqua/_aqua/smoke_test.aqua +++ b/packages/@tests/aqua/_aqua/smoke_test.aqua @@ -53,3 +53,6 @@ func marineTest(wasm64: string) -> f64: <- res +func callHappy(a: string, b: f64, c: f64, d: string -> f64) -> f64: + res <- d("abc") + <- res diff --git a/packages/@tests/aqua/package.json b/packages/@tests/aqua/package.json index 2303c808..91bebb3a 100644 --- a/packages/@tests/aqua/package.json +++ b/packages/@tests/aqua/package.json @@ -1,29 +1,29 @@ { - "name": "@test/aqua_for_test", - "version": "0.1.0", - "description": "Shared aqua code for tests", - "main": "./dist/index.js", - "typings": "./dist/index.d.ts", - "engines": { - "node": ">=10", - "pnpm": ">=3" - }, - "type": "module", - "scripts": { - "build": "tsc", - "compile-aqua": "fluence aqua -i ./_aqua -o ./src/_aqua" - }, - "repository": "https://github.com/fluencelabs/fluence-js", - "author": "Fluence Labs", - "license": "Apache-2.0", - "dependencies": { - "@fluencelabs/js-client": "workspace:^", - "base64-js": "1.5.1" - }, - "devDependencies": { - "@fluencelabs/cli": "0.7.2", - "@fluencelabs/registry": "0.8.2", - "@fluencelabs/aqua-lib": "0.6.0", - "@fluencelabs/trust-graph": "3.1.2" - } + "name": "@test/aqua_for_test", + "version": "0.1.0", + "description": "Shared aqua code for tests", + "main": "./dist/index.js", + "typings": "./dist/index.d.ts", + "engines": { + "node": ">=10", + "pnpm": ">=3" + }, + "type": "module", + "scripts": { + "build": "tsc", + "compile-aqua": "fluence aqua -i ./_aqua -o ./src/_aqua" + }, + "repository": "https://github.com/fluencelabs/fluence-js", + "author": "Fluence Labs", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "1.5.1" + }, + "devDependencies": { + "@fluencelabs/aqua-lib": "0.6.0", + "@fluencelabs/cli": "0.7.2", + "@fluencelabs/js-client": "workspace:^", + "@fluencelabs/registry": "0.8.2", + "@fluencelabs/trust-graph": "3.1.2" + } } diff --git a/packages/@tests/aqua/src/_aqua/finalize_particle.ts b/packages/@tests/aqua/src/_aqua/finalize_particle.ts index f4f7c648..99f035f0 100644 --- a/packages/@tests/aqua/src/_aqua/finalize_particle.ts +++ b/packages/@tests/aqua/src/_aqua/finalize_particle.ts @@ -8,13 +8,14 @@ * Aqua version: 0.12.0 * */ -import type { IFluenceClient as IFluenceClient$$, CallParams as CallParams$$ } from '@fluencelabs/js-client'; +import type { + IFluenceClient as IFluenceClient$$, + CallParams as CallParams$$, +} from "@fluencelabs/js-client"; import { - v5_callFunction as callFunction$$, - v5_registerService as registerService$$, -} from '@fluencelabs/js-client'; - - + v5_callFunction as callFunction$$, + v5_registerService as registerService$$, +} from "@fluencelabs/js-client"; // Services @@ -30,49 +31,42 @@ export const test_script = ` (call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 0]) ) ) - ` - + `; + +export function test(config?: { ttl?: number }): Promise; export function test( - config?: {ttl?: number} -): Promise; - -export function test( - peer: IFluenceClient$$, - config?: {ttl?: number} + peer: IFluenceClient$$, + config?: { ttl?: number }, ): Promise; export function test(...args: any) { - - - return callFunction$$( - args, - { - "functionName" : "test", - "arrow" : { - "tag" : "arrow", - "domain" : { - "tag" : "labeledProduct", - "fields" : { - - } + return callFunction$$( + args, + { + functionName: "test", + arrow: { + tag: "arrow", + domain: { + tag: "labeledProduct", + fields: {}, }, - "codomain" : { - "tag" : "nil" - } + codomain: { + tag: "nil", + }, + }, + names: { + relay: "-relay-", + getDataSrv: "getDataSrv", + callbackSrv: "callbackSrv", + responseSrv: "callbackSrv", + responseFnName: "response", + errorHandlingSrv: "errorHandlingSrv", + errorFnName: "error", + }, }, - "names" : { - "relay" : "-relay-", - "getDataSrv" : "getDataSrv", - "callbackSrv" : "callbackSrv", - "responseSrv" : "callbackSrv", - "responseFnName" : "response", - "errorHandlingSrv" : "errorHandlingSrv", - "errorFnName" : "error" - } -}, - test_script - ) + test_script, + ); } -/* eslint-enable */ \ No newline at end of file +/* eslint-enable */ diff --git a/packages/@tests/aqua/src/_aqua/smoke_test.ts b/packages/@tests/aqua/src/_aqua/smoke_test.ts index f4949e55..9ca2479c 100644 --- a/packages/@tests/aqua/src/_aqua/smoke_test.ts +++ b/packages/@tests/aqua/src/_aqua/smoke_test.ts @@ -8,325 +8,360 @@ * Aqua version: 0.12.0 * */ -import type { IFluenceClient as IFluenceClient$$, CallParams as CallParams$$ } from '@fluencelabs/js-client'; +import type { + IFluenceClient as IFluenceClient$$, + CallParams as CallParams$$, +} from "@fluencelabs/js-client"; import { - v5_callFunction as callFunction$$, - v5_registerService as registerService$$, -} from '@fluencelabs/js-client'; - - + v5_callFunction as callFunction$$, + v5_registerService as registerService$$, +} from "@fluencelabs/js-client"; // Services export interface SrvDef { - create: (wasm_b64_content: string, callParams: CallParams$$<'wasm_b64_content'>) => { error: string | null; service_id: string | null; success: boolean; } | Promise<{ error: string | null; service_id: string | null; success: boolean; }>; - list: (callParams: CallParams$$) => string[] | Promise; - remove: (service_id: string, callParams: CallParams$$<'service_id'>) => { error: string | null; success: boolean; } | Promise<{ error: string | null; success: boolean; }>; + create: ( + wasm_b64_content: string, + callParams: CallParams$$<"wasm_b64_content">, + ) => + | { error: string | null; service_id: string | null; success: boolean } + | Promise<{ + error: string | null; + service_id: string | null; + success: boolean; + }>; + list: (callParams: CallParams$$) => string[] | Promise; + remove: ( + service_id: string, + callParams: CallParams$$<"service_id">, + ) => + | { error: string | null; success: boolean } + | Promise<{ error: string | null; success: boolean }>; } export function registerSrv(service: SrvDef): void; export function registerSrv(serviceId: string, service: SrvDef): void; export function registerSrv(peer: IFluenceClient$$, service: SrvDef): void; -export function registerSrv(peer: IFluenceClient$$, serviceId: string, service: SrvDef): void; - +export function registerSrv( + peer: IFluenceClient$$, + serviceId: string, + service: SrvDef, +): void; export function registerSrv(...args: any) { - registerService$$( - args, - { - "defaultServiceId" : "single_module_srv", - "functions" : { - "tag" : "labeledProduct", - "fields" : { - "create" : { - "tag" : "arrow", - "domain" : { - "tag" : "labeledProduct", - "fields" : { - "wasm_b64_content" : { - "tag" : "scalar", - "name" : "string" - } - } - }, - "codomain" : { - "tag" : "unlabeledProduct", - "items" : [ - { - "tag" : "struct", - "name" : "ServiceCreationResult", - "fields" : { - "error" : { - "tag" : "option", - "type" : { - "tag" : "scalar", - "name" : "string" - } - }, - "service_id" : { - "tag" : "option", - "type" : { - "tag" : "scalar", - "name" : "string" - } - }, - "success" : { - "tag" : "scalar", - "name" : "bool" - } - } - } - ] - } + registerService$$(args, { + defaultServiceId: "single_module_srv", + functions: { + tag: "labeledProduct", + fields: { + create: { + tag: "arrow", + domain: { + tag: "labeledProduct", + fields: { + wasm_b64_content: { + tag: "scalar", + name: "string", + }, }, - "list" : { - "tag" : "arrow", - "domain" : { - "tag" : "nil" + }, + codomain: { + tag: "unlabeledProduct", + items: [ + { + tag: "struct", + name: "ServiceCreationResult", + fields: { + error: { + tag: "option", + type: { + tag: "scalar", + name: "string", + }, + }, + service_id: { + tag: "option", + type: { + tag: "scalar", + name: "string", + }, + }, + success: { + tag: "scalar", + name: "bool", + }, }, - "codomain" : { - "tag" : "unlabeledProduct", - "items" : [ - { - "tag" : "array", - "type" : { - "tag" : "scalar", - "name" : "string" - } - } - ] - } + }, + ], + }, + }, + list: { + tag: "arrow", + domain: { + tag: "nil", + }, + codomain: { + tag: "unlabeledProduct", + items: [ + { + tag: "array", + type: { + tag: "scalar", + name: "string", + }, + }, + ], + }, + }, + remove: { + tag: "arrow", + domain: { + tag: "labeledProduct", + fields: { + service_id: { + tag: "scalar", + name: "string", + }, }, - "remove" : { - "tag" : "arrow", - "domain" : { - "tag" : "labeledProduct", - "fields" : { - "service_id" : { - "tag" : "scalar", - "name" : "string" - } - } + }, + codomain: { + tag: "unlabeledProduct", + items: [ + { + tag: "struct", + name: "RemoveResult", + fields: { + error: { + tag: "option", + type: { + tag: "scalar", + name: "string", + }, + }, + success: { + tag: "scalar", + name: "bool", + }, }, - "codomain" : { - "tag" : "unlabeledProduct", - "items" : [ - { - "tag" : "struct", - "name" : "RemoveResult", - "fields" : { - "error" : { - "tag" : "option", - "type" : { - "tag" : "scalar", - "name" : "string" - } - }, - "success" : { - "tag" : "scalar", - "name" : "bool" - } - } - } - ] - } - } - } - } + }, + ], + }, + }, + }, + }, + }); } - ); -} - - export interface CalcServiceDef { - add: (num: number, callParams: CallParams$$<'num'>) => number | Promise; - clear_state: (callParams: CallParams$$) => void | Promise; - divide: (num: number, callParams: CallParams$$<'num'>) => number | Promise; - multiply: (num: number, callParams: CallParams$$<'num'>) => number | Promise; - state: (callParams: CallParams$$) => number | Promise; - subtract: (num: number, callParams: CallParams$$<'num'>) => number | Promise; - test_logs: (callParams: CallParams$$) => void | Promise; + add: ( + num: number, + callParams: CallParams$$<"num">, + ) => number | Promise; + clear_state: (callParams: CallParams$$) => void | Promise; + divide: ( + num: number, + callParams: CallParams$$<"num">, + ) => number | Promise; + multiply: ( + num: number, + callParams: CallParams$$<"num">, + ) => number | Promise; + state: (callParams: CallParams$$) => number | Promise; + subtract: ( + num: number, + callParams: CallParams$$<"num">, + ) => number | Promise; + test_logs: (callParams: CallParams$$) => void | Promise; } -export function registerCalcService(serviceId: string, service: CalcServiceDef): void; -export function registerCalcService(peer: IFluenceClient$$, serviceId: string, service: CalcServiceDef): void; - +export function registerCalcService( + serviceId: string, + service: CalcServiceDef, +): void; +export function registerCalcService( + peer: IFluenceClient$$, + serviceId: string, + service: CalcServiceDef, +): void; export function registerCalcService(...args: any) { - registerService$$( - args, - { - "functions" : { - "tag" : "labeledProduct", - "fields" : { - "add" : { - "tag" : "arrow", - "domain" : { - "tag" : "labeledProduct", - "fields" : { - "num" : { - "tag" : "scalar", - "name" : "f64" - } - } - }, - "codomain" : { - "tag" : "unlabeledProduct", - "items" : [ - { - "tag" : "scalar", - "name" : "f64" - } - ] - } + registerService$$(args, { + functions: { + tag: "labeledProduct", + fields: { + add: { + tag: "arrow", + domain: { + tag: "labeledProduct", + fields: { + num: { + tag: "scalar", + name: "f64", + }, }, - "clear_state" : { - "tag" : "arrow", - "domain" : { - "tag" : "nil" - }, - "codomain" : { - "tag" : "nil" - } + }, + codomain: { + tag: "unlabeledProduct", + items: [ + { + tag: "scalar", + name: "f64", + }, + ], + }, + }, + clear_state: { + tag: "arrow", + domain: { + tag: "nil", + }, + codomain: { + tag: "nil", + }, + }, + divide: { + tag: "arrow", + domain: { + tag: "labeledProduct", + fields: { + num: { + tag: "scalar", + name: "f64", + }, }, - "divide" : { - "tag" : "arrow", - "domain" : { - "tag" : "labeledProduct", - "fields" : { - "num" : { - "tag" : "scalar", - "name" : "f64" - } - } - }, - "codomain" : { - "tag" : "unlabeledProduct", - "items" : [ - { - "tag" : "scalar", - "name" : "f64" - } - ] - } + }, + codomain: { + tag: "unlabeledProduct", + items: [ + { + tag: "scalar", + name: "f64", + }, + ], + }, + }, + multiply: { + tag: "arrow", + domain: { + tag: "labeledProduct", + fields: { + num: { + tag: "scalar", + name: "f64", + }, }, - "multiply" : { - "tag" : "arrow", - "domain" : { - "tag" : "labeledProduct", - "fields" : { - "num" : { - "tag" : "scalar", - "name" : "f64" - } - } - }, - "codomain" : { - "tag" : "unlabeledProduct", - "items" : [ - { - "tag" : "scalar", - "name" : "f64" - } - ] - } + }, + codomain: { + tag: "unlabeledProduct", + items: [ + { + tag: "scalar", + name: "f64", + }, + ], + }, + }, + state: { + tag: "arrow", + domain: { + tag: "nil", + }, + codomain: { + tag: "unlabeledProduct", + items: [ + { + tag: "scalar", + name: "f64", + }, + ], + }, + }, + subtract: { + tag: "arrow", + domain: { + tag: "labeledProduct", + fields: { + num: { + tag: "scalar", + name: "f64", + }, }, - "state" : { - "tag" : "arrow", - "domain" : { - "tag" : "nil" - }, - "codomain" : { - "tag" : "unlabeledProduct", - "items" : [ - { - "tag" : "scalar", - "name" : "f64" - } - ] - } - }, - "subtract" : { - "tag" : "arrow", - "domain" : { - "tag" : "labeledProduct", - "fields" : { - "num" : { - "tag" : "scalar", - "name" : "f64" - } - } - }, - "codomain" : { - "tag" : "unlabeledProduct", - "items" : [ - { - "tag" : "scalar", - "name" : "f64" - } - ] - } - }, - "test_logs" : { - "tag" : "arrow", - "domain" : { - "tag" : "nil" - }, - "codomain" : { - "tag" : "nil" - } - } - } - } + }, + codomain: { + tag: "unlabeledProduct", + items: [ + { + tag: "scalar", + name: "f64", + }, + ], + }, + }, + test_logs: { + tag: "arrow", + domain: { + tag: "nil", + }, + codomain: { + tag: "nil", + }, + }, + }, + }, + }); } - ); -} - - export interface HelloWorldDef { - hello: (str: string, callParams: CallParams$$<'str'>) => string | Promise; + hello: ( + str: string, + callParams: CallParams$$<"str">, + ) => string | Promise; } export function registerHelloWorld(service: HelloWorldDef): void; -export function registerHelloWorld(serviceId: string, service: HelloWorldDef): void; -export function registerHelloWorld(peer: IFluenceClient$$, service: HelloWorldDef): void; -export function registerHelloWorld(peer: IFluenceClient$$, serviceId: string, service: HelloWorldDef): void; - +export function registerHelloWorld( + serviceId: string, + service: HelloWorldDef, +): void; +export function registerHelloWorld( + peer: IFluenceClient$$, + service: HelloWorldDef, +): void; +export function registerHelloWorld( + peer: IFluenceClient$$, + serviceId: string, + service: HelloWorldDef, +): void; export function registerHelloWorld(...args: any) { - registerService$$( - args, - { - "defaultServiceId" : "hello-world", - "functions" : { - "tag" : "labeledProduct", - "fields" : { - "hello" : { - "tag" : "arrow", - "domain" : { - "tag" : "labeledProduct", - "fields" : { - "str" : { - "tag" : "scalar", - "name" : "string" - } - } - }, - "codomain" : { - "tag" : "unlabeledProduct", - "items" : [ - { - "tag" : "scalar", - "name" : "string" - } - ] - } - } - } - } + registerService$$(args, { + defaultServiceId: "hello-world", + functions: { + tag: "labeledProduct", + fields: { + hello: { + tag: "arrow", + domain: { + tag: "labeledProduct", + fields: { + str: { + tag: "scalar", + name: "string", + }, + }, + }, + codomain: { + tag: "unlabeledProduct", + items: [ + { + tag: "scalar", + name: "string", + }, + ], + }, + }, + }, + }, + }); } - ); -} - + // Functions export const resourceTest_script = ` (seq @@ -546,70 +581,68 @@ export const resourceTest_script = ` ) (call %init_peer_id% ("callbackSrv" "response") [-resource_id-flat-0 #error_canon]) ) - ` - -export type ResourceTestResult = [string | null, string[]] + `; + +export type ResourceTestResult = [string | null, string[]]; export function resourceTest( - label: string, - config?: {ttl?: number} + label: string, + config?: { ttl?: number }, ): Promise; export function resourceTest( - peer: IFluenceClient$$, - label: string, - config?: {ttl?: number} + peer: IFluenceClient$$, + label: string, + config?: { ttl?: number }, ): Promise; export function resourceTest(...args: any) { - - - return callFunction$$( - args, - { - "functionName" : "resourceTest", - "arrow" : { - "tag" : "arrow", - "domain" : { - "tag" : "labeledProduct", - "fields" : { - "label" : { - "tag" : "scalar", - "name" : "string" - } - } + return callFunction$$( + args, + { + functionName: "resourceTest", + arrow: { + tag: "arrow", + domain: { + tag: "labeledProduct", + fields: { + label: { + tag: "scalar", + name: "string", + }, + }, }, - "codomain" : { - "tag" : "unlabeledProduct", - "items" : [ - { - "tag" : "option", - "type" : { - "tag" : "scalar", - "name" : "string" - } - }, - { - "tag" : "array", - "type" : { - "tag" : "scalar", - "name" : "string" - } - } - ] - } + codomain: { + tag: "unlabeledProduct", + items: [ + { + tag: "option", + type: { + tag: "scalar", + name: "string", + }, + }, + { + tag: "array", + type: { + tag: "scalar", + name: "string", + }, + }, + ], + }, + }, + names: { + relay: "-relay-", + getDataSrv: "getDataSrv", + callbackSrv: "callbackSrv", + responseSrv: "callbackSrv", + responseFnName: "response", + errorHandlingSrv: "errorHandlingSrv", + errorFnName: "error", + }, }, - "names" : { - "relay" : "-relay-", - "getDataSrv" : "getDataSrv", - "callbackSrv" : "callbackSrv", - "responseSrv" : "callbackSrv", - "responseFnName" : "response", - "errorHandlingSrv" : "errorHandlingSrv", - "errorFnName" : "error" - } -}, - resourceTest_script - ) + resourceTest_script, + ); } export const helloTest_script = ` @@ -623,55 +656,165 @@ export const helloTest_script = ` ) (call %init_peer_id% ("callbackSrv" "response") [hello]) ) - ` - + `; + +export function helloTest(config?: { ttl?: number }): Promise; export function helloTest( - config?: {ttl?: number} -): Promise; - -export function helloTest( - peer: IFluenceClient$$, - config?: {ttl?: number} + peer: IFluenceClient$$, + config?: { ttl?: number }, ): Promise; export function helloTest(...args: any) { - - - return callFunction$$( - args, - { - "functionName" : "helloTest", - "arrow" : { - "tag" : "arrow", - "domain" : { - "tag" : "labeledProduct", - "fields" : { - - } + return callFunction$$( + args, + { + functionName: "helloTest", + arrow: { + tag: "arrow", + domain: { + tag: "labeledProduct", + fields: {}, }, - "codomain" : { - "tag" : "unlabeledProduct", - "items" : [ - { - "tag" : "scalar", - "name" : "string" - } - ] - } + codomain: { + tag: "unlabeledProduct", + items: [ + { + tag: "scalar", + name: "string", + }, + ], + }, + }, + names: { + relay: "-relay-", + getDataSrv: "getDataSrv", + callbackSrv: "callbackSrv", + responseSrv: "callbackSrv", + responseFnName: "response", + errorHandlingSrv: "errorHandlingSrv", + errorFnName: "error", + }, }, - "names" : { - "relay" : "-relay-", - "getDataSrv" : "getDataSrv", - "callbackSrv" : "callbackSrv", - "responseSrv" : "callbackSrv", - "responseFnName" : "response", - "errorHandlingSrv" : "errorHandlingSrv", - "errorFnName" : "error" - } -}, - helloTest_script - ) + helloTest_script, + ); +} + +export const callHappy_script = ` + (seq + (seq + (seq + (seq + (seq + (call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-) + (call %init_peer_id% ("getDataSrv" "a") [] a) + ) + (call %init_peer_id% ("getDataSrv" "b") [] b) + ) + (call %init_peer_id% ("getDataSrv" "c") [] c) + ) + (xor + (xor + (call %init_peer_id% ("callbackSrv" "d") ["abc"] init_call_res0) + (fail %last_error%) + ) + (call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 0]) + ) + ) + (call %init_peer_id% ("callbackSrv" "response") [init_call_res0]) + ) + `; + +export function callHappy( + a: string, + b: number, + c: number, + d: ( + arg0: string, + callParams: CallParams$$<"arg0">, + ) => number | Promise, + config?: { ttl?: number }, +): Promise; + +export function callHappy( + peer: IFluenceClient$$, + a: string, + b: number, + c: number, + d: ( + arg0: string, + callParams: CallParams$$<"arg0">, + ) => number | Promise, + config?: { ttl?: number }, +): Promise; + +export function callHappy(...args: any) { + return callFunction$$( + args, + { + functionName: "callHappy", + arrow: { + tag: "arrow", + domain: { + tag: "labeledProduct", + fields: { + a: { + tag: "scalar", + name: "string", + }, + b: { + tag: "scalar", + name: "f64", + }, + c: { + tag: "scalar", + name: "f64", + }, + d: { + tag: "arrow", + domain: { + tag: "unlabeledProduct", + items: [ + { + tag: "scalar", + name: "string", + }, + ], + }, + codomain: { + tag: "unlabeledProduct", + items: [ + { + tag: "scalar", + name: "f64", + }, + ], + }, + }, + }, + }, + codomain: { + tag: "unlabeledProduct", + items: [ + { + tag: "scalar", + name: "f64", + }, + ], + }, + }, + names: { + relay: "-relay-", + getDataSrv: "getDataSrv", + callbackSrv: "callbackSrv", + responseSrv: "callbackSrv", + responseFnName: "response", + errorHandlingSrv: "errorHandlingSrv", + errorFnName: "error", + }, + }, + callHappy_script, + ); } export const demo_calculation_script = ` @@ -703,60 +846,57 @@ export const demo_calculation_script = ` ) (call %init_peer_id% ("callbackSrv" "response") [res]) ) - ` - + `; export function demo_calculation( - service_id: string, - config?: {ttl?: number} + service_id: string, + config?: { ttl?: number }, ): Promise; export function demo_calculation( - peer: IFluenceClient$$, - service_id: string, - config?: {ttl?: number} + peer: IFluenceClient$$, + service_id: string, + config?: { ttl?: number }, ): Promise; export function demo_calculation(...args: any) { - - - return callFunction$$( - args, - { - "functionName" : "demo_calculation", - "arrow" : { - "tag" : "arrow", - "domain" : { - "tag" : "labeledProduct", - "fields" : { - "service_id" : { - "tag" : "scalar", - "name" : "string" - } - } + return callFunction$$( + args, + { + functionName: "demo_calculation", + arrow: { + tag: "arrow", + domain: { + tag: "labeledProduct", + fields: { + service_id: { + tag: "scalar", + name: "string", + }, + }, }, - "codomain" : { - "tag" : "unlabeledProduct", - "items" : [ - { - "tag" : "scalar", - "name" : "f64" - } - ] - } + codomain: { + tag: "unlabeledProduct", + items: [ + { + tag: "scalar", + name: "f64", + }, + ], + }, + }, + names: { + relay: "-relay-", + getDataSrv: "getDataSrv", + callbackSrv: "callbackSrv", + responseSrv: "callbackSrv", + responseFnName: "response", + errorHandlingSrv: "errorHandlingSrv", + errorFnName: "error", + }, }, - "names" : { - "relay" : "-relay-", - "getDataSrv" : "getDataSrv", - "callbackSrv" : "callbackSrv", - "responseSrv" : "callbackSrv", - "responseFnName" : "response", - "errorHandlingSrv" : "errorHandlingSrv", - "errorFnName" : "error" - } -}, - demo_calculation_script - ) + demo_calculation_script, + ); } export const marineTest_script = ` @@ -791,60 +931,57 @@ export const marineTest_script = ` ) (call %init_peer_id% ("callbackSrv" "response") [res]) ) - ` - + `; export function marineTest( - wasm64: string, - config?: {ttl?: number} + wasm64: string, + config?: { ttl?: number }, ): Promise; export function marineTest( - peer: IFluenceClient$$, - wasm64: string, - config?: {ttl?: number} + peer: IFluenceClient$$, + wasm64: string, + config?: { ttl?: number }, ): Promise; export function marineTest(...args: any) { - - - return callFunction$$( - args, - { - "functionName" : "marineTest", - "arrow" : { - "tag" : "arrow", - "domain" : { - "tag" : "labeledProduct", - "fields" : { - "wasm64" : { - "tag" : "scalar", - "name" : "string" - } - } + return callFunction$$( + args, + { + functionName: "marineTest", + arrow: { + tag: "arrow", + domain: { + tag: "labeledProduct", + fields: { + wasm64: { + tag: "scalar", + name: "string", + }, + }, }, - "codomain" : { - "tag" : "unlabeledProduct", - "items" : [ - { - "tag" : "scalar", - "name" : "f64" - } - ] - } + codomain: { + tag: "unlabeledProduct", + items: [ + { + tag: "scalar", + name: "f64", + }, + ], + }, + }, + names: { + relay: "-relay-", + getDataSrv: "getDataSrv", + callbackSrv: "callbackSrv", + responseSrv: "callbackSrv", + responseFnName: "response", + errorHandlingSrv: "errorHandlingSrv", + errorFnName: "error", + }, }, - "names" : { - "relay" : "-relay-", - "getDataSrv" : "getDataSrv", - "callbackSrv" : "callbackSrv", - "responseSrv" : "callbackSrv", - "responseFnName" : "response", - "errorHandlingSrv" : "errorHandlingSrv", - "errorFnName" : "error" - } -}, - marineTest_script - ) + marineTest_script, + ); } -/* eslint-enable */ \ No newline at end of file +/* eslint-enable */ diff --git a/packages/@tests/aqua/src/index.ts b/packages/@tests/aqua/src/index.ts index 3cdb9ee6..2855a42c 100644 --- a/packages/@tests/aqua/src/index.ts +++ b/packages/@tests/aqua/src/index.ts @@ -1,84 +1,117 @@ -import { fromByteArray } from 'base64-js'; -import { Fluence } from '@fluencelabs/js-client'; -import type { ClientConfig } from '@fluencelabs/js-client'; -import { registerHelloWorld, helloTest, marineTest, resourceTest } from './_aqua/smoke_test.js'; -import { test as particleTest } from './_aqua/finalize_particle.js'; -import { wasm } from './wasmb64.js'; +/** + * Copyright 2023 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 { Fluence } from "@fluencelabs/js-client"; +import type { ClientConfig } from "@fluencelabs/js-client"; +import { fromByteArray } from "base64-js"; + +import { test as particleTest } from "./_aqua/finalize_particle.js"; +import { + registerHelloWorld, + helloTest, + marineTest, +} from "./_aqua/smoke_test.js"; +import { wasm } from "./wasmb64.js"; const relay = { - multiaddr: '/ip4/127.0.0.1/tcp/9991/ws/p2p/12D3KooWBM3SdXWqGaawQDGQ6JprtwswEg3FWGvGhmgmMez1vRbR', - peerId: '12D3KooWBM3SdXWqGaawQDGQ6JprtwswEg3FWGvGhmgmMez1vRbR', + multiaddr: + "/ip4/127.0.0.1/tcp/9991/ws/p2p/12D3KooWBM3SdXWqGaawQDGQ6JprtwswEg3FWGvGhmgmMez1vRbR", + peerId: "12D3KooWBM3SdXWqGaawQDGQ6JprtwswEg3FWGvGhmgmMez1vRbR", }; function generateRandomUint8Array() { - const uint8Array = new Uint8Array(32); - for (let i = 0; i < uint8Array.length; i++) { - uint8Array[i] = Math.floor(Math.random() * 256); - } - return uint8Array; + const uint8Array = new Uint8Array(32); + + for (let i = 0; i < uint8Array.length; i++) { + uint8Array[i] = Math.floor(Math.random() * 256); + } + + return uint8Array; } const optsWithRandomKeyPair = (): ClientConfig => { - return { - keyPair: { - type: 'Ed25519', - source: generateRandomUint8Array(), - }, - } as const; + return { + keyPair: { + type: "Ed25519", + source: generateRandomUint8Array(), + }, + } as const; }; -export type TestResult = { type: 'success'; data: string } | { type: 'failure'; error: string }; +export type TestResult = + | { type: "success"; data: string } + | { type: "failure"; error: string }; export const runTest = async (): Promise => { - try { - Fluence.onConnectionStateChange((state) => console.info('connection state changed: ', state)); + try { + Fluence.onConnectionStateChange((state) => { + console.info("connection state changed: ", state); + }); - console.log('connecting to Fluence Network...'); - console.log('multiaddr: ', relay.multiaddr); - await Fluence.connect(relay, optsWithRandomKeyPair()); + console.log("connecting to Fluence Network..."); + console.log("multiaddr: ", relay.multiaddr); + await Fluence.connect(relay, optsWithRandomKeyPair()); - console.log('connected'); + console.log("connected"); - const relayPeerId = (await Fluence.getClient()).getRelayPeerId(); - console.log('relay:', relayPeerId); + const relayPeerId = Fluence.getClient().getRelayPeerId(); + console.log("relay:", relayPeerId); - await registerHelloWorld({ - hello(str) { - return 'Hello, ' + str + '!'; - }, - }); + registerHelloWorld({ + hello(str) { + return "Hello, " + str + "!"; + }, + }); - const client = await Fluence.getClient(); + const client = Fluence.getClient(); - console.log('my peer id: ', client.getPeerId()); - console.log('my sk id: ', fromByteArray(client.getPeerSecretKey())); + console.log("my peer id: ", client.getPeerId()); + console.log("my sk id: ", fromByteArray(client.getPeerSecretKey())); - console.log('running hello test...'); - const hello = await helloTest(); - console.log('hello test finished, result: ', hello); + console.log("running hello test..."); + const hello = await helloTest(); + console.log("hello test finished, result: ", hello); - console.log('running marine test...'); - const marine = await marineTest(wasm); + console.log("running marine test..."); + const marine = await marineTest(wasm); - console.log('running particle test...'); - await particleTest(); - - console.log('marine test finished, result: ', marine); + console.log("running particle test..."); + await particleTest(); - const returnVal = { - hello, - marine, - }; - return { type: 'success', data: JSON.stringify(returnVal) }; - } finally { - console.log('disconnecting from Fluence Network...'); - await Fluence.disconnect(); - console.log('disconnected'); - } + console.log("marine test finished, result: ", marine); + + const returnVal = { + hello, + marine, + }; + + return { type: "success", data: JSON.stringify(returnVal) }; + } finally { + console.log("disconnecting from Fluence Network..."); + await Fluence.disconnect(); + console.log("disconnected"); + } }; export const runMain = () => { - runTest() - .then(() => console.log('done!')) - .catch((err) => console.error('error: ', err)); + runTest() + .then(() => { + console.log("done!"); + }) + .catch((err) => { + console.error("error: ", err); + }); }; diff --git a/packages/@tests/aqua/src/wasmb64.ts b/packages/@tests/aqua/src/wasmb64.ts index 884eb1ba..e39e805e 100644 --- a/packages/@tests/aqua/src/wasmb64.ts +++ b/packages/@tests/aqua/src/wasmb64.ts @@ -1 +1,18 @@ -export const wasm = "AGFzbQEAAAABmIGAgAAWYAAAYAABf2AAAXxgAX8AYAF/AX9gAX8BfmACf38AYAJ/fwF/YAN/f38AYAN/f38Bf2ADf39/AX5gBH9/f38AYAR/f39/AX9gBX9/f39/AGAFf39/f38Bf2AGf39/f39/AX9gB39/f39/f38AYAd/f39/f39/AX9gCX9/f39/f35+fgBgBH98f38Bf2ADfn9/AX9gAXwBfALRgYCAAAYEaG9zdA9sb2dfdXRmOF9zdHJpbmcACxZ3YXNpX3NuYXBzaG90X3ByZXZpZXcxCGZkX3dyaXRlAAwWd2FzaV9zbmFwc2hvdF9wcmV2aWV3MQpyYW5kb21fZ2V0AAcWd2FzaV9zbmFwc2hvdF9wcmV2aWV3MQtlbnZpcm9uX2dldAAHFndhc2lfc25hcHNob3RfcHJldmlldzERZW52aXJvbl9zaXplc19nZXQABxZ3YXNpX3NuYXBzaG90X3ByZXZpZXcxCXByb2NfZXhpdAADA9iCgIAA1gILBA0HCwgDBgkJBwsHDQcHCRMPCQ0DCwgJEwgIBw0JBwAEBgsKBwcSBwgHCw4QBhAJEQ4GBAcHBwcHBwsUBgQHCQcDBggAAwcHCQMMBgcHCAcHBwgICAYRDgEDDQsVBggIBgYVFRUEAwQDBAYGCwsLBwcHBwcHAAkHBwgEBw0LBAcHBwcDCAYGBggHBwYICAcHBwcHBwcHBwsHAgMLAAcGCQkJCwkLCQQDAwwJDAcDBwgGBgcDAAYABwcHBwcHAQQEAwgNCAsGBwYECAMIBwYGBAMDBwYGBggHBAMHBwcEAAwDAwcABwcHBwYHCwcEBAgHCQcHAwcHBwgJCQcHBwcGBwcHBwcGBgYGBgYGBggICAYGBgYJBxUVFRUDAwEGBgMDAwMAAAACAQEAAwAACAQEBAMABAQEAQEAAAcHBQUFBQQEBAAABQMDAwMDAwYDAwMDAwADAwMEhYCAgAABcAF0dAWDgICAAAEAEQaZgICAAAN/AUGAgMAAC38AQaCBwQALfwBBmIHBAAsH7oGAgAASBm1lbW9yeQIAC19faGVhcF9iYXNlAwEKX19kYXRhX2VuZAMCBl9zdGFydACpAgRtYWluAIMCCXRlc3RfbG9ncwCqAgNhZGQAnAIIc3VidHJhY3QAnQIIbXVsdGlwbHkAngIGZGl2aWRlAJ8CC2Ns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AAAgA0EGaiABQQZqLQAAOgAAIANBB2ogAUEHai0AADoAACADQQhqIQMgAUEIaiEBIARBeGoiBA0ACwsgAAuvCQEHfwJAAkAgAUH/CUsNACABQQV2IQICQAJAAkACQCAAKAIAIgNFDQAgACADQQJ0aiEEIAAgAyACakECdGohBSADQX9qIgNBJ0shBgNAIAYNBCACIANqIgdBKE8NAiAFIAQoAgA2AgAgBUF8aiEFIARBfGohBCADQX9qIgNBf0cNAAsLIAFBIEkNBCAAQQA2AgQgAUHAAE8NAQwECyAHQShB3PHAABCPAQALIABBCGpBADYCACACQQEgAkEBSxsiA0ECRg0CIABBDGpBADYCACADQQNGDQIgAEEQakEANgIAIANBBEYNAiAAQRRqQQA2AgAgA0EFRg0CIABBGGpBADYCACADQQZGDQIgAEEcakEANgIAIANBB0YNAiAAQSBqQQA2AgAgA0EIRg0CIABBJGpBADYCACADQQlGDQIgAEEoakEANgIAIANBCkYNAiAAQSxqQQA2AgAgA0ELRg0CIABBMGpBADYCACADQQxGDQIgAEE0akEANgIAIANBDUYNAiAAQThqQQA2AgAgA0EORg0CIABBPGpBADYCACADQQ9GDQIgAEHAAGpBADYCACADQRBGDQIgAEHEAGpBADYCACADQRFGDQIgAEHIAGpBADYCACADQRJGDQIgAEHMAGpBADYCACADQRNGDQIgAEHQAGpBADYCACADQRRGDQIgAEHUAGpBADYCACADQRVGDQIgAEHYAGpBADYCACADQRZGDQIgAEHcAGpBADYCACADQRdGDQIgAEHgAGpBADYCACADQRhGDQIgAEHkAGpBADYCACADQRlGDQIgAEHoAGpBADYCACADQRpGDQIgAEHsAGpBADYCACADQRtGDQIgAEHwAGpBADYCACADQRxGDQIgAEH0AGpBADYCACADQR1GDQIgAEH4AGpBADYCACADQR5GDQIgAEH8AGpBADYCACADQR9GDQIgAEGAAWpBADYCACADQSBGDQIgAEGEAWpBADYCACADQSFGDQIgAEGIAWpBADYCACADQSJGDQIgAEGMAWpBADYCACADQSNGDQIgAEGQAWpBADYCACADQSRGDQIgAEGUAWpBADYCACADQSVGDQIgAEGYAWpBADYCACADQSZGDQIgAEGc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NgIAIAFBIGpBFGpBAzYCACABQgM3AiQgAUH8g8AANgIgIAFBBDYCPCABIAFBOGo2AjAgASABQRhqNgJIIAEgAUEIajYCQCABIAFBEGo2AjggAUEgakEDQZSEwABBACABEGEgASsDGCECCyABQdAAaiQAIAILvwEBA38jAEEgayICJAACQCABQQFqIgMgAUkNACAAQQRqKAIAIgRBAXQiASADIAEgA0sbIgFBCCABQQhLGyEBAkACQCAEDQBBACEDDAELIAIgBDYCFCACIAAoAgA2AhBBASEDCyACIAM2AhggAiABQQEgAkEQahB1AkAgAigCAEUNACACQQhqKAIAIgBFDQEgAigCBCAAEI4CAAsgAigCBCEDIABBBGogATYCACAAIAM2AgAgAkEgaiQADwsQuwEAC78BAQJ/IwBBIGsiAyQAAkAgASACaiICIAFJDQAgAEEEaigCACIEQQF0IgEgAiABIAJLGyIBQQggAUEISxshAQJAAkAgBA0AQQAhAgwBCyADIAQ2AhQgAyAAKAIANgIQQQEhAgsgAyACNgIYIAMgAUEBIANBEGoQdQJAIAMoAgBFDQAgA0EIaigCACIARQ0BIAMoAgQgABCOAgALIAMoAgQhAiAAQQRqIAE2AgAgACACNgIAIANBIGokAA8LELsBAAu/AQECfyMAQSBrIgMkAAJAIAEgAmoiAiABSQ0AIABBBGooAgAiBEEBdCIBIAIgASACSxsiAUEIIAFBCEsbIQECQAJAIAQNAEEAIQIMAQsgAyAENgIUIAMgACgCADYCEEEBIQILIAMgAjYCGCADIAFBASADQRBqEHMCQCADKAIARQ0AIANBCGooAgAiAEUNASADKAIEIAAQjgIACyADKAIEIQIgAEEEaiABNgIAIAAgAjYCACADQSBqJAAPCxC7AQALvwEBA38jAEEgayICJAACQCABQQFqIgMgAUkNACAAQQRqKAIAIgRBAXQiASADIAEgA0sbIgFBCCABQQhLGyEBAkACQCAEDQBBACEDDAELIAIgBDYCFCACIAAoAgA2AhBBASEDCyACIAM2AhggAiABQQEgAkEQahBzAkAgAigCAEUNACACQQhqKAIAIgBFDQEgAigCBCAAEI4CAAsgAigCBCEDIABBBGogATYCACAAIAM2AgAgAkEgaiQADwsQuwEAC78BAQN/IwBBIGsi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IgBBCGooAgAhAyAAKAIAIQAgAiABEMwBAkAgA0UNAANAIAIgADYCDCACIAJBDGpBnMDAABD/ARogAEEBaiEAIANBf2oiAw0ACwsgAhDPASEAIAJBEGokACAAC3QBAX8jAEEgayIBJAAgAUEQakIANwMAIAFCADcDCCABIAFBCGpBEBDdAQJAIAEvAQBFDQAgASABLwECOwEeQaiiwABBEiABQR5qQdSMwABB3KLAABCDAQALIAAgASkDEDcDCCAAIAEpAwg3AwAgAUEgaiQAC20BAX8jAEEwayIDJAAgAyABNgIEIAMgADYCACADQRxqQQI2AgAgA0EsakEiNgIAIANCAzcCDCADQfjBwAA2AgggA0EiNgIkIAMgA0EgajYCGCADIANBBGo2AiggAyADNgIgIANBCGogAhC8AQALcAEBfyMAQTBrIgIkACACIAE2AgQgAiAANgIAIAJBHGpBAjYCACACQSxqQSI2AgAgAkICNwIMIAJB0N/AADYCCCACQSI2AiQgAiACQSBqNgIYIAIgAkEEajYCKCACIAI2AiAgAkEIakGA4MAAELwBAAtwAQF/IwBBMGsiAiQAIAIgATYCBCACIAA2AgAgAkEcakECNgIAIAJBLGpBIjYCACACQgI3AgwgAkGg4MAANgIIIAJBIjYCJCACIAJBIGo2AhggAiACQQRqNgIoIAIgAjYCICACQQhqQbDgwAAQvAEAC3ABAX8jAEEwayICJAAgAiABNgIEIAIgADYCACACQRxqQQI2AgAgAkEsakEiNgIAIAJCAjcCDCACQeTgwAA2AgggAkEiNgIkIAIgAkEgajYCGCACIAJBBGo2AiggAiACNgIgIAJBCGpB9ODAABC8AQALbQEBfyMAQTBrIgMkACADIAE2AgQgAyAANgIAIANBHGpBAjYCACADQSxqQSI2AgAgA0ICNwIMIANB8NnAADYCCCADQSI2AiQgAyADQSBqNgIYIAMgAzYCKCADIANBBGo2AiAgA0EIaiACELwBAAtkAQJ/IwBBIGsiAiQAIAFBHGooAgAhAyABKAIYIQEgAkEIakEQaiAAQRBqKQIANwMAIAJBCGpBCGogAEEIaikCADcDACACIAApAgA3AwggASADIAJBCGoQJCEAIAJBIGokACAAC2QBAn8jAEEgayICJAAgAEEcaigCACEDIAAoAhghACACQQhqQRBqIAFBEGopAgA3AwAgAkEIakEIaiABQQhqKQIANwMAIAIgASkCADcDCCAAIAMgAkEIahAkIQEgAkEgaiQAIAELbgEBfyMAQSBrIgIkACACQYCSwAA2AgQgAiAANgIAIAJBCGpBEGogAUEQaikCADcDACACQQhqQQhqIAFBCGopAgA3AwAgAiABKQIANwMIQQAgAkGwjcAAIAJBBGpBsI3AACACQQhqQdChwAAQNQALawEBfyMAQSBrIgMkACADQeCWwAA2AgQgAyAANgIAIANBCGpBEGogAUEQaikCADcDACADQQhqQQhqIAFBCGopAgA3AwAgAyABKQIANwMIQQAgA0GgjcAAIANBBGpBoI3AACADQQhqIAIQNQALawEBfyMAQSBrIgMkACADIAE2AgQgAyAANgIAIANBCGpBEGogAkEQaikCADcDACADQQhqQQhqIAJBCGopAgA3AwAgAyACKQIANwMIQQAgA0H82sAAIANBBGpB/NrAACADQQhqQYjDwAAQNQALYwEBfyMAQSBrIgIkACACIAAoAgA2AgQgAkEIakEQaiABQRBqKQIANwMAIAJBCGpBCGogAUEIaikCADcDACACIAEpAgA3AwggAkEEakGEh8AAIAJBCGoQJCEBIAJBIGokACABC2MBAX8jAEEgayICJAAgAiAAKAIANgIEIAJBCGpBEGogAUEQaikCADcDACACQQhqQQhqIAFBCGopAgA3AwAgAiABKQIANwMIIAJBBGpBsIvAACACQQhqECQhASACQSBqJAAgAQtjAQF/IwBBIGsiAiQAIAIgACgCADYCBCACQQhqQRBqIAFBEGopAgA3AwAgAkEIakEIaiABQQhqKQIANwMAIAIgASkCADcDCCACQQRqQeCLwAAgAkEIahAkIQEgAkEgaiQAIAELYwEBfyMAQSBrIgIkACACIAAoAgA2AgQgAkEIakEQaiABQRBqKQIANwMAIAJBCGpBCGogAUEIaikCADcDACACIAEpAgA3AwggAkEEakHIi8AAIAJBCGoQJCEBIAJBIGokACABC2MBAX8jAEEgayICJAAgAiAAKAIANgIEIAJBCGpBEGogAUEQaikCADcDACACQQhqQQhqIAFBCGopAgA3AwAgAiABKQIANwMIIAJBBGpBuN7AACACQQhqECQhASACQSBqJAAgAQtgAQF/IwBBIGsiAiQAIAIgADYCBCACQQhqQRBqIAFBEGopAgA3AwAgAkEIakEIaiABQQhqKQIANwMAIAIgASkCADcDCCACQQRqQYSHwAAgAkEIahAkIQEgAkEgaiQAIAELewEBf0EAQQAoAvz7QCICQQEgAhs2Avz7QAJAAkACQCACDgIAAQILQQAgATYC3PtAQQAgADYC2PtAQQBBAjYC/PtAQQAPCwNAQQAoAvz7QEEBRg0ACwsgACABKAIAEQMAAkAgASgCBCICRQ0AIAAgAiABKAIIEPUBC0EBC2ABAX8jAEEgayICJAAgAiAANgIEIAJBCGpBEGogAUEQaikCADcDACACQQhqQQhqIAFBCGopAgA3AwAgAiABKQIANwMIIAJBBGpBsIvAACACQQhqECQhASACQSBqJAAgAQtgAQF/IwBBIGsiAiQAIAIgADYCBCACQQhqQRBqIAFBEGopAgA3AwAgAkEIakEIaiABQQhqKQIANwMAIAIgASkCADcDCCACQQRqQciLwAAgAkEIahAkIQEgAkEgaiQAIAELZwEBfyMAQRBrIgQkACAEQQIgAiADEKsBAkACQCAELwEADQAgACAEKAIENgIEQQAhAgwBCyAEIAQvAQI7AQ4gACAEQQ5qELUCrUL//wODQiCGNwIEQQEhAgsgACACNgIAIARBEGokAAtgAQF/IwBBIGsiAiQAIAIgADYCBCACQQhqQRBqIAFBEGopAgA3AwAgAkEIakEIaiABQQhqKQIANwMAIAIgASkCADcDCCACQQRqQbjewAAgAkEIahAkIQEgAkEgaiQAIAELZwIBfwF8IwBBIGsiACQAAkBBACgCgPxAQQVJDQAgAEEcakEANgIAIABB2IDAADYCGCAAQgE3AgwgAEGAhcAANgIIIABBCGpBBUGIhcAAQQAgABBhC0EAKwPw+0AhASAAQSBqJAAgAQtaAQF/AkAgACgCECIBRQ0AIAFBADoAACAAQRRqKAIAIgFFDQAgACgCECABQQEQ9QELAkAgAEF/Rg0AIAAgACgCBCIBQX9qNgIEIAFBAUcNACAAQSBBCBD1AQsLVwECfwJAIAFBBGooAgAgAUEIaiIEKAIAIgVrIANPDQAgASAFIAMQZiAEKAIAIQULIAEoAgAgBWogAiADEA4aIAAgAzYCBCAEIAUgA2o2AgAgAEEANgIAC2MBAX8jAEEgayIAJAACQEEAKAKA/EBBBUkNACAAQRxqQQA2AgAgAEHYgMAANgIYIABCATcCDCAAQciEwAA2AgggAEEIakEFQdCEwABBACAAEGELQQBCADcD8PtAIABBIGokAAthAQF/IwBBEGsiAiQAAkACQCAAKAIADQAgAiAANgIIIAFBx47AAEECIAJBCGpB3I7AABBeIQAMAQsgAiAANgIMIAFBxI7AAEEDIAJBDGpBzI7AABBeIQALIAJBEGokACAAC1wBAn8gASgCACECIAFBADYCAAJAAkAgAkUNACABKAIEIQNBCEEEEOwBIgFFDQEgASADNgIEIAEgAjYCACAAQZyLwAA2AgQgACABNgIADwsQsgIAC0EIQQQQjgIAC1MBAn8CQCAAKAIAKAIAIgNBBGooAgAgA0EIaiIEKAIAIgBrIAJPDQAgAyAAIAIQZiAEKAIAIQALIAMoAgAgAGogASACEA4aIAQgACACajYCAEEAC1ABAn8CQCAAKAIAIgNBBGooAgAgA0EIaiIEKAIAIgBrIAJPDQAgAyAAIAIQZSAEKAIAIQALIAMoAgAgAGogASACEA4aIAQgACACajYCAEEAC1ABAn8CQCAAKAIAIgNBBGooAgAgA0EIaiIEKAIAIgBrIAJPDQAgAyAAIAIQZiAEKAIAIQALIAMoAgAgAGogASACEA4aIAQgACACajYCAEEAC1ABAn8CQCABQQRqKAIAIAFBCGoiBCgCACIFayADTw0AIAEgBSADEGYgBCgCACEFCyABKAIAIAVqIAIgAxAOGiAAQQQ6AAAgBCAFIANqNgIAC1ABAn8CQCAAKAIAIgNBBGooAgAgA0EIaiIEKAIAIgBrIAJPDQAgAyAAIAIQZiAEKAIAIQALIAMoAgAgAGogASACEA4aIAQgACACajYCAEEAC1ABAX8jAEEQayIEJAACQAJAIAEgAiADIARBDGoQASIBDQAgAEEEaiAEKAIMNgIAQQAhAQwBCyAAIAE7AQJBASEBCyAAIAE7AQAgBEEQaiQAC0sBAn8CQCAAQQRqKAIAIABBCGoiAygCACIEayACTw0AIAAgBCACEGUgAygCACEECyAAKAIAIARqIAEgAhAOGiADIAQgAmo2AgBBAAtJAAJAIAANAD8AQRB0DwsCQCAAQf//A3ENACAAQX9MDQACQCAAQRB2QAAiAEF/Rw0AQQBBMDYC8IBBQX8PCyAAQRB0DwsQygIAC1YBA38CQCAALQAAQQNHDQAgAEEEaigCACIBKAIAIAEoAgQoAgARAwACQCABKAIEIgIoAgQiA0UNACABKAIAIAMgAigCCBD1AQsgACgCBEEMQQQQ9QELC1YBA38CQCAALQAEQQNHDQAgAEEIaigCACIBKAIAIAEoAgQoAgARAwACQCABKAIEIgIoAgQiA0UNACABKAIAIAMgAigCCBD1AQsgACgCCEEMQQQQ9QELC0cAAkACQCACQQhLDQAgAiADTQ0BCwJAIAIgAxDeASICDQBBAA8LIAIgACADIAEgASADSxsQDiEDIAAQtwIgAw8LIAAgAxAUC0kBA39BACEDAkAgAkUNAAJAA0AgAC0AACIEIAEtAAAiBUcNASABQQFqIQEgAEEBaiEAIAJBf2oiAg0ADAILCyAEIAVrIQMLIAMLVAEBfwJAAkACQCABQYCAxABGDQBBASEEIAAoAhggASAAQRxqKAIAKAIQEQcADQELIAINAUEAIQQLIAQPCyAAKAIYIAIgAyAAQRxqKAIAKAIMEQkAC1MBAX9BACgC7PtAIQICQAJAIAANACACENYBIgANAUEAQTA2AvCAQUEADwsCQCACEGxBAWogAUsNACAAIAIQ+wEPC0EAIQBBAEHEADYC8IBBCyAAC0kBAX8CQCAAQRBqKAIAIgFFDQAgACgCDCABQQEQ9QELAkAgAEF/Rg0AIAAgACgCBCIBQX9qNgIEIAFBAUcNACAAQRhBBBD1AQsLSAEBfyMAQRBrIgIkACACIAA2AgggAiAAQQRqNgIMIAFBkMLAAEEIIAJBCGpBmMLAACACQQxqQajCwAAQXSEAIAJBEGokACAAC0gBAX8jAEEgayIDJAAgA0EUakEANgIAIANBuMLAADYCECADQgE3AgQgAyABNgIcIAMgADYCGCADIANBGGo2AgAgAyACELwBAAtJAQF/IwBBIGsiAiQAIAJBFGpBATYCACACQgE3AgQgAkH02sAANgIAIAJB4QA2AhwgAiAANgIYIAIgAkEYajYCECACIAEQvAEAC0cBAn8gASgCBCECIAEoAgAhAwJAQQhBBBDsASIBDQBBCEEEEI4CAAsgASACNgIEIAEgAzYCACAAQZSdwAA2AgQgACABNgIACz8CAX8BfCABKAIAQQFxIQIgACsDACEDAkAgASgCEEEBRw0AIAEgAyACIAFBFGooAgAQFw8LIAEgAyACQQAQHwtBAQN/IwBBEGsiASQAIAAQugJB5JzAABDjASECIAAQuQIQ5AEhAyABIAI2AgggASAANgIEIAEgAzYCACABEOgBAAtAAQF/IwBBIGsiACQAIABBHGpBADYCACAAQazAwAA2AhggAEIBNwIMIABBhMHAADYCCCAAQQhqQYzBwAAQvAEACz8BAX8jAEEgayICJAAgAkEBOgAYIAIgATYCFCACIAA2AhAgAkHE2sAANgIMIAJBuMLAADYCCCACQQhqELoBAAs5AQF/IwBBMGsiACQAIAAQUAJAIAAQJ0UNAEGAgMAAQSsgAEGsgMAAQciAwAAQgwEACyAAQTBqJAALNAEBfyMAQRBrIgIkACACIAA2AgwgAUHoisAAQQ4gAkEMakH4isAAEF4hASACQRBqJAAgAQsyACAAKAIAIQACQCABEPMBDQACQCABEPQBDQAgACABEP0BDwsgACABEHsPCyAAIAEQegsxAQF/IwBBEGsiAiQAIAJBCGogAUHwj8AAQQsQywEgAkEIahCFASEBIAJBEGokACABCzABAX8jAEEQayICJAAgAkEIaiABQYSXwABBCxDLASACQQhqEHAhASACQRBqJAAgAQsyACAAKAIAIQACQCABEPMBDQACQCABEPQBDQAgACABEP0BDwsgACABEHsPCyAAIAEQegsyACAAKAIAIQACQCABEPMBDQACQCABEPQBDQAgACABEIECDwsgACABEHgPCyAAIAEQdwsvAQJ/IwBBEGsiACQAIABBATYCDCAAQQxqQaSFwABBAEEAEFEhASAAQRBqJAAgAQsvAQF/IwBBEGsiASQAIAAoAgAQsAIgAUEAOgAPIAFBD2oQtAIhACABQRBqJAAgAAsvAQF/IwBBEGsiASQAIAAoAgAQsAIgAUEAOgAPIAFBD2oQtAIhACABQRBqJAAgAAs5AQF/IAAoAgAhAQJAIAAtAAQNAEEAKALI/EBB/////wdxRQ0AEKICDQAgAUEBOgABCyABQQA6AAALLgEBfyMAQRBrIgMkACADIAE2AgwgAyAANgIIIANBCGpBiIvAAEEAIAJBARAaAAsvAQF/IwBBEGsiBSQAIAUgAzYCDCAFIAI2AgggBSABNgIEIAUgADYCACAFEOABAAs0ACAAQQM6ACAgAEKAgICAgAQ3AgAgACABNgIYIABBADYCECAAQQA2AgggAEEcaiACNgIACzAAIAEoAhggAiADIAFBHGooAgAoAgwRCQAhAiAAQQA6AAUgACACOgAEIAAgATYCAAs1AQF/IAEoAhhB6tzAAEEBIAFBHGooAgAoAgwRCQAhAiAAQQA6AAUgACACOgAEIAAgATYCAAsrAAJAAkAgARDzAQ0AIAEQ9AENASAAIAEQggIPCyAAIAEQdg8LIAAgARB5CycBAX8jAEEQayICJAAgAiAAKAIANgIMIAJBDGogARA5IAJBEGokAAsyAQF/QQEhAQJAIAAtAAQNACAAKAIAIgAoAhhB69zAAEEBIAAoAhwoAgwRCQAhAQsgAQsoAQF/IwBBEGsiAyQAIAMgAjYCCCADIAE2AgQgAyAANgIAIAMQ5wEACycBAX8CQCAAKAIEIgFFDQAgAEEIaigCACIARQ0AIAEgAEEBEPUBCwsmAQF/IwBBEGsiAyQAIAMgATYCDCADIAA2AgggA0EIaiACELcBAAsjAAJAAkAgAUEISw0AIAEgAE0NAQsgASAAEN4BDwsgABC2AgsqACAAIAEuAQBBAnQiAUHItsAAaigCADYCBCAAIAFB/LjAAGooAgA2AgALKgAgACABLgEAQQJ0IgFBsLvAAGooAgA2AgQgACABQeS9wABqKAIANgIACyMBAn8CQCAAEGxBAWoiARC2AiICRQ0AIAIgACABEA4aCyACCyABAX8CQCAAQQRqKAIAIgFFDQAgACgCACABQQEQ9QELCyABAX8CQCAAQQRqKAIAIgFFDQAgACgCACABQQEQ9QELCyMAAkAgAC0AAA0AIAFBlN/AAEEFEBYPCyABQZDfwABBBBAWCyEAAkAgASgCAA0AELICAAsgAEGci8AANgIEIAAgATYCAAscACAAIAEpAgA3AgAgAEEIaiABQQhqKAIANgIACx0BAX8gACABQQAoArj8QCICQSMgAhsRBgAQsgIACxkAIAAgASACEAIiATsBAiAAIAFBAEc7AQALFwACQCAAQRBLDQAgARAHDwsgACABEDALJAEBf0GIgcEAIQECQEEAKAKEgUENAEGEgcEAIAAQTiEBCyABCxoAIAAoAgAgACgCBCAAKAIIIAAoAgwQ8QEACxwAIAEoAhhBsNnAAEEOIAFBHGooAgAoAgwRCQALHAAgASgCGEG+8sAAQQUgAUEcaigCACgCDBEJAAsYAAJAIAANAEGYjMAAQSsgARC2AQALIAALGwACQCAADQBBmIzAAEErQfScwAAQtgEACyAACxUBAX8CQBDEASIARQ0AIAAQ+gEACwsUAQF/IAAgASACIAMQsAEhBCAEDwsVACAAKAIAIAAoAgQgACgCCBDIAQALFQAgACgCACAAKAIEIAAoAggQgAEACxUAIAEgACgCACIAKAIAIAAoAgQQFgsTAAJAQQAoAvSAQUF/Rw0AEHwLCxQAIAAoAgAgASAAKAIEKAIMEQcACxABAX8gACABENMBIQIgAg8LEQAgACgCACAAKAIEIAEQmgILDgAgACgCACABED8aQQALEwAgAEGUncAANgIEIAAgATYCAAsQACAAKAIAIAAoAgQgARAZCw0AIAAgASACIAMQEQALEAAgASAAKAIAIAAoAgQQFgsNACAALQAAQRBxQQR2Cw0AIAAtAABBIHFBBXYLDAAgACABIAIQswIPCw8AIAAoAgAoAgAgARC1AQsNACAAKAIAIAEgAhBPCw0AIAAgARADQf//A3ELDQAgACABEARB//8DcQsOABDYAhDYAiAAEKYCAAsLACAAIAEQPhogAAsNACAAKAIAGgN/DAALCw0AIAA1AgBBASABEEILDAAgACABIAIQ0gEACwwAIAAgASACEEogAAsNACAAKAIAIAEgAhAeCw0AIAAxAABBASABEEILDQAgADMBAEEBIAEQQgsPABC/AiAAIAEQwAIQuAILDgAQvwIgACABECsQuAILCgAgACABEI8CDwsNACABQayLwABBAhAWCw0AIAFBrovAAEECEBYLDAAgACgCACABENkBCwsAIAAoAgAgARB/CwsAIAAoAgAgARBHCwoAIAAgARCMAgALCgAgACABEI0CAAsKACAAIAEQhQIACwoAIAAgARCLAgALCgAgACABENwBAAsKACAAIAEQjAEACwoAIAAgARCNAQALCgAgACABEI4BAAsKACAAIAEQlwIACwoAIAAgARCYAgALCgAgACABEJYCAAsKACAAIAEQkgIACwoAIAAgARCQAgALCgAgACABEJECAAsMACAAIAEpAgA3AwALCgAgAiAAIAEQFgsLACAAKAIAIAEQLAsMABC/AiAAEGkQuAILDAAQvwIgABBqELgC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IGdlbmVyYXRlIHVuaXF1ZSB0aHJlYWQgSUQ6IGJpdHNwYWNlIGV4aGF1c3RlZACICBAANwAAAFkIEAAdAAAAEwQAABEAAABZCBAAHQAAABkEAAAqAAAAUlVTVF9CQUNLVFJBQ0UAAPgFEAAAAAAAAGZhaWxlZCB0byB3cml0ZSB0aGUgYnVmZmVyZWQgZGF0YQAAAQkQACEAAAAXAAAAbGlicmFyeS9zdGQvc3JjL2lvL2J1ZmZlcmVkL2J1ZndyaXRlci5yczAJEAAoAAAAjQAAABIAAABmYWlsZWQgdG8gd3JpdGUgd2hvbGUgYnVmZmVyaAkQABwAAAAXAAAAbGlicmFyeS9zdGQvc3JjL2lvL3N0ZGlvLnJzAJAJEAAbAAAAbgIAABMAAABsaWJyYXJ5L3N0ZC9zcmMvaW8vbW9kLnJzAAAAvAkQABkAAAAaBQAAFgAAAGFkdmFuY2luZyBpbyBzbGljZXMgYmV5b25kIHRoZWlyIGxlbmd0aADoCRAAJwAAALwJEAAZAAAAHAUAAA0AAAC8CRAAGQAAAAMGAAAhAAAAZm9ybWF0dGVyIGVycm9yADgKEAAPAAAAKAAAADsAAAAMAAAABAAAADwAAAA9AAAAPgAAADsAAAAMAAAABAAAAD8AAABAAAAAQQAAAGxpYnJhcnkvc3RkL3NyYy9wYW5pYy5yc4QKEAAYAAAA8AAAABIAAAAoAAAABAAAAAQAAABCAAAAQwAAAGxpYnJhcnkvc3RkL3NyYy9zeW5jL29uY2UucnPAChAAHAAAABQBAAAyAAAAYXNzZXJ0aW9uIGZhaWxlZDogc3RhdGVfYW5kX3F1ZXVlLmFkZHIoKSAmIFNUQVRFX01BU0sgPT0gUlVOTklOR09uY2UgaW5zdGFuY2UgaGFzIHByZXZpb3VzbHkgYmVlbiBwb2lzb25lZAAALAsQACoAAAACAAAAwAoQABwAAAD/AQAACQAAAMAKEAAcAAAADAIAADUAAABQb2lzb25FcnJvcnN0YWNrIGJhY2t0cmFjZToKjwsQABEAAABub3RlOiBTb21lIGRldGFpbHMgYXJlIG9taXR0ZWQsIHJ1biB3aXRoIGBSVVNUX0JBQ0tUUkFDRT1mdWxsYCBmb3IgYSB2ZXJib3NlIGJhY2t0cmFjZS4KqAsQAFgAAABsb2NrIGNvdW50IG92ZXJmbG93IGluIHJlZW50cmFudCBtdXRleGxpYnJhcnkvc3RkL3NyYy9zeXNfY29tbW9uL3JlbXV0ZXgucnMALgwQACUAAACnAAAADgAAAGxpYnJhcnkvc3RkL3NyYy9zeXNfY29tbW9uL3RocmVhZF9pbmZvLnJzAAAAZAwQACkAAAAWAAAAMwAAAGQMEAApAAAAKwAAACsAAABhc3NlcnRpb24gZmFpbGVkOiB0aHJlYWRfaW5mby5pc19ub25lKCkAsAwQACcAAABtZW1vcnkgYWxsb2NhdGlvbiBvZiAgYnl0ZXMgZmFpbGVkCgDgDBAAFQAAAPUMEAAOAAAAbGlicmFyeS9zdGQvc3JjL2FsbG9jLnJzFA0QABgAAABSAQAACQAAAGxpYnJhcnkvc3RkL3NyYy9wYW5pY2tpbmcucnM8DRAAHAAAABEBAAAkAAAAQm94PGR5biBBbnk+PHVubmFtZWQ+AAAAKAAAAAAAAAABAAAARAAAAEUAAABGAAAARwAAAEgAAABJAAAASgAAAEsAAAAMAAAABAAAAEwAAABNAAAATgAAAE8AAABQAAAAUQAAAFIAAAB0aHJlYWQgJycgcGFuaWNrZWQgYXQgJycsIAAA0A0QAAgAAADYDRAADwAAAOcNEAADAAAAhQcQAAEAAABub3RlOiBydW4gd2l0aCBgUlVTVF9CQUNLVFJBQ0U9MWAgZW52aXJvbm1lbnQgdmFyaWFibGUgdG8gZGlzcGxheSBhIGJhY2t0cmFjZQoAAAwOEABOAAAAPA0QABwAAABGAgAAHwAAADwNEAAcAAAARwIAAB4AAABLAAAADAAAAAQAAABTAAAAKAAAAAgAAAAEAAAAVAAAAFUAAAAQAAAABAAAAFYAAABXAAAAKAAAAAgAAAAEAAAAWAAAAFkAAAB0aHJlYWQgcGFuaWNrZWQgd2hpbGUgcHJvY2Vzc2luZyBwYW5pYy4gYWJvcnRpbmcuCgAAzA4QADIAAAAKcGFuaWNrZWQgYWZ0ZXIgcGFuaWM6OmFsd2F5c19hYm9ydCgpLCBhYm9ydGluZy4KAAAA+AUQAAAAAAAIDxAAMQAAAHRocmVhZCBwYW5pY2tlZCB3aGlsZSBwYW5pY2tpbmcuIGFib3J0aW5nLgoATA8QACsAAABmYWlsZWQgdG8gaW5pdGlhdGUgcGFuaWMsIGVycm9yIIAPEAAgAAAAYWR2YW5jaW5nIElvU2xpY2UgYmV5b25kIGl0cyBsZW5ndGgAqA8QACMAAABsaWJyYXJ5L3N0ZC9zcmMvc3lzL3dhc2kvaW8ucnMAANQPEAAeAAAAFgAAAA0AAABjb25kdmFyIHdhaXQgbm90IHN1cHBvcnRlZAAABBAQABoAAABsaWJyYXJ5L3N0ZC9zcmMvc3lzL3dhc2kvLi4vdW5zdXBwb3J0ZWQvbG9ja3MvY29uZHZhci5ycygQEAA4AAAAFQAAAAkAAABjYW5ub3QgcmVjdXJzaXZlbHkgYWNxdWlyZSBtdXRleHAQEAAgAAAAbGlicmFyeS9zdGQvc3JjL3N5cy93YXNpLy4uL3Vuc3VwcG9ydGVkL2xvY2tzL211dGV4LnJzAACYEBAANgAAABgAAAAJAAAAcndsb2NrIGxvY2tlZCBmb3Igd3JpdGluZwAAAOAQEAAZAAAACAAOAA8APwACAEAANQANAAQAAwAsABsAHABJABQABgA0ADAAcmFuZG9tX2dldCBmYWlsdXJlbGlicmFyeS9zdGQvc3JjL3N5cy93YXNpL21vZC5ycwAAADoREAAfAAAAXQAAACUAAABsaWJyYXJ5L3N0ZC9zcmMvc3lzX2NvbW1vbi90aHJlYWRfcGFya2VyL2dlbmVyaWMucnMAbBEQADMAAAAnAAAAJgAAAGluY29uc2lzdGVudCBwYXJrIHN0YXRlALAREAAXAAAAbBEQADMAAAA1AAAAFwAAAHBhcmsgc3RhdGUgY2hhbmdlZCB1bmV4cGVjdGVkbHkA4BEQAB8AAABsERAAMwAAADIAAAARAAAAaW5jb25zaXN0ZW50IHN0YXRlIGluIHVucGFyaxgSEAAcAAAAbBEQADMAAABsAAAAEgAAAGwREAAzAAAAegAAAB8AAABjb2RlbmFtZVoAAAAIAAAABAAAAFsAAABtZXNzYWdlTk9UQ0FQQUJMRVhERVZUWFRCU1lUSU1FRE9VVFNUQUxFU1JDSFNQSVBFUk9GU1JBTkdFUFJPVE9UWVBFUFJPVE9OT1NVUFBPUlRQUk9UT1BJUEVQRVJNT1dORVJERUFET1ZFUkZMT1dOWElPTk9UVFlOT1RTVVBOT1RTT0NLTk9UUkVDT1ZFUkFCTEVOT1RFTVBUWU5PVERJUk5PVENPTk5OT1NZU05PU1BDTk9QUk9UT09QVE5PTVNHTk9NRU1OT0xJTktOT0xDS05PRVhFQ05PRU5UTk9ERVZOT0JVRlNORklMRU5FVFVOUkVBQ0hORVRSRVNFVE5FVERPV05OQU1FVE9PTE9OR01VTFRJSE9QTVNHU0laRU1MSU5LTUZJTEVMT09QSVNESVJJU0NPTk5JT0lOVkFMSU5UUklOUFJPR1JFU1NJTFNFUUlEUk1IT1NUVU5SRUFDSEZCSUdGQVVMVEVYSVNURFFVT1RET01ERVNUQUREUlJFUURFQURMS0NPTk5SRVNFVENPTk5SRUZVU0VEQ09OTkFCT1JURURDSElMRENBTkNFTEVEQlVTWUJBRE1TR0JBREZBTFJFQURZQUdBSU5BRk5PU1VQUE9SVEFERFJOT1RBVkFJTEFERFJJTlVTRUFDQ0VTMkJJR1NVQ0NFU1NFeHRlbnNpb246IENhcGFiaWxpdGllcyBpbnN1ZmZpY2llbnQuQ3Jvc3MtZGV2aWNlIGxpbmsuVGV4dCBmaWxlIGJ1c3kuQ29ubmVjdGlvbiB0aW1lZCBvdXQuUmVzZXJ2ZWQuTm8gc3VjaCBwcm9jZXNzLkludmFsaWQgc2Vlay5SZWFkLW9ubHkgZmlsZSBzeXN0ZW0uUmVzdWx0IHRvbyBsYXJnZS5Qcm90b2NvbCB3cm9uZyB0eXBlIGZvciBzb2NrZXQuUHJvdG9jb2wgbm90IHN1cHBvcnRlZC5Qcm90b2NvbCBlcnJvci5Ccm9rZW4gcGlwZS5PcGVyYXRpb24gbm90IHBlcm1pdHRlZC5QcmV2aW91cyBvd25lciBkaWVkLlZhbHVlIHRvbyBsYXJnZSB0byBiZSBzdG9yZWQgaW4gZGF0YSB0eXBlLk5vIHN1Y2ggZGV2aWNlIG9yIGFkZHJlc3MuSW5hcHByb3ByaWF0ZSBJL08gY29udHJvbCBvcGVyYXRpb24uTm90IHN1cHBvcnRlZCwgb3Igb3BlcmF0aW9uIG5vdCBzdXBwb3J0ZWQgb24gc29ja2V0Lk5vdCBhIHNvY2tldC5TdGF0ZSBub3QgcmVjb3ZlcmFibGUuRGlyZWN0b3J5IG5vdCBlbXB0eS5Ob3QgYSBkaXJlY3Rvcnkgb3IgYSBzeW1ib2xpYyBsaW5rIHRvIGEgZGlyZWN0b3J5LlRoZSBzb2NrZXQgaXMgbm90IGNvbm5lY3RlZC5GdW5jdGlvbiBub3Qgc3VwcG9ydGVkLk5vIHNwYWNlIGxlZnQgb24gZGV2aWNlLlByb3RvY29sIG5vdCBhdmFpbGFibGUuTm8gbWVzc2FnZSBvZiB0aGUgZGVzaXJlZCB0eXBlLk5vdCBlbm91Z2ggc3BhY2UuTm8gbG9ja3MgYXZhaWxhYmxlLkV4ZWN1dGFibGUgZmlsZSBmb3JtYXQgZXJyb3IuTm8gc3VjaCBmaWxlIG9yIGRpcmVjdG9yeS5ObyBzdWNoIGRldmljZS5ObyBidWZmZXIgc3BhY2UgYXZhaWxhYmxlLlRvbyBtYW55IGZpbGVzIG9wZW4gaW4gc3lzdGVtLk5ldHdvcmsgdW5yZWFjaGFibGUuQ29ubmVjdGlvbiBhYm9ydGVkIGJ5IG5ldHdvcmsuTmV0d29yayBpcyBkb3duLkZpbGVuYW1lIHRvbyBsb25nLk1lc3NhZ2UgdG9vIGxhcmdlLlRvbyBtYW55IGxpbmtzLkZpbGUgZGVzY3JpcHRvciB2YWx1ZSB0b28gbGFyZ2UuVG9vIG1hbnkgbGV2ZWxzIG9mIHN5bWJvbGljIGxpbmtzLklzIGEgZGlyZWN0b3J5LlNvY2tldCBpcyBjb25uZWN0ZWQuSS9PIGVycm9yLkludmFsaWQgYXJndW1lbnQuSW50ZXJydXB0ZWQgZnVuY3Rpb24uT3BlcmF0aW9uIGluIHByb2dyZXNzLklsbGVnYWwgYnl0ZSBzZXF1ZW5jZS5JZGVudGlmaWVyIHJlbW92ZWQuSG9zdCBpcyB1bnJlYWNoYWJsZS5GaWxlIHRvbyBsYXJnZS5CYWQgYWRkcmVzcy5GaWxlIGV4aXN0cy5NYXRoZW1hdGljcyBhcmd1bWVudCBvdXQgb2YgZG9tYWluIG9mIGZ1bmN0aW9uLkRlc3RpbmF0aW9uIGFkZHJlc3MgcmVxdWlyZWQuUmVzb3VyY2UgZGVhZGxvY2sgd291bGQgb2NjdXIuQ29ubmVjdGlvbiByZXNldC5Db25uZWN0aW9uIHJlZnVzZWQuQ29ubmVjdGlvbiBhYm9ydGVkLk5vIGNoaWxkIHByb2Nlc3Nlcy5PcGVyYXRpb24gY2FuY2VsZWQuRGV2aWNlIG9yIHJlc291cmNlIGJ1c3kuQmFk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AAAAAAAAAAEAAAByAAAAL2hvbWUvcGF2ZWwvLmNhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvbWFyaW5lLXJzLXNkay1tYWluLTAuNi4xNS9zcmMvcmVzdWx0LnJzAFw9EABjAAAAQwAAACMAAABzAAAAAEHY+8AACxiIBBAAlAQQAAEAAAAAAAAAAQAAABggEAAA362BgAAEbmFtZQHUrYGAANwCAEhfWk4xOG1hcmluZV9yc19zZGtfbWFpbjZsb2dnZXIyMGxvZ191dGY4X3N0cmluZ19pbXBsMTdoZGQ5NGY0NzFjMzYyZjE3ZUUBTF9aTjR3YXNpMTNsaWJfZ2VuZXJhdGVkMjJ3YXNpX3NuYXBzaG90X3ByZXZpZXcxOGZkX3dyaXRlMTdoODFlNzQwZmI4NjZkMmMxOUUCT19aTjR3YXNpMTNsaWJfZ2VuZXJhdGVkMjJ3YXNpX3NuYXBzaG90X3ByZXZpZXcxMTByYW5kb21fZ2V0MTdoNjE2NGI5NmNjZGRmYzcxYUUDLV9faW1wb3J0ZWRfd2FzaV9zbmFwc2hvdF9wcmV2aWV3MV9lbnZpcm9uX2dldAQzX19pbXBvcnRlZF93YXNpX3NuYXBzaG90X3ByZXZpZXcxX2Vudmlyb25fc2l6ZXNfZ2V0BStfX2ltcG9ydGVkX3dhc2lfc25hcHNob3RfcHJldmlldzFfcHJvY19leGl0BklfWk40Y29yZTNudW03Zmx0MmRlYzhzdHJhdGVneTZkcmFnb24xNWZvcm1hdF9zaG9ydGVzdDE3aGVkN2VhYzBiMWFiNzg2ODBFBwhkbG1hbGxvYwhGX1pONGNvcmUzbnVtN2ZsdDJkZWM4c3RyYXRlZ3k2ZHJhZ29uMTJmb3JtYXRfZXhhY3QxN2hhNzZkNGM5NjlmN2U5MDkyRQlCX1pONGNvcmUzbnVtN2ZsdDJkZWM4c3RyYXRlZ3k2ZHJhZ29uOW11bF9wb3cxMDE3aDI1NGI5MDc2OWE4M2ZlMGVFCkxfWk40Y29yZTNudW03Zmx0MmRlYzhzdHJhdGVneTVncmlzdTE5Zm9ybWF0X3Nob3J0ZXN0X29wdDE3aGU4ZjFlYjFjNGExYmUyZDNFCy9fWk41YWxsb2M1c2xpY2UxMG1lcmdlX3NvcnQxN2gwMzAzNWY0MDk4M2RhODRhRQwGZGxmcmVlDQ1kaXNwb3NlX2NodW5rDgZtZW1jcHkPB21lbW1vdmUQOV9aTjRjb3JlM251bTZiaWdudW04QmlnMzJ4NDA4bXVsX3BvdzIxN2gzMWFiZGI4ZTA4NTdmMTE2RRE1X1pONGNvcmUzc3RyMTlzbGljZV9lcnJvcl9mYWlsX3J0MTdoNjcwNDcyNjc5ZTFlNDgxMUUSNl9aTjRjb3JlM3N0cjVjb3VudDE0ZG9fY291bnRfY2hhcnMxN2g4MDZiZDU1NTI1NDc0ZDk2RRNJX1pONGNvcmUzbnVtN2ZsdDJkZWM4c3RyYXRlZ3k1Z3Jpc3UxNmZvcm1hdF9leGFjdF9vcHQxN2gyOGRhYzg4MDU0M2FhMTFmRRQHcmVhbGxvYxVhX1pONjNfJExUJGxvZy4uTGV2ZWxGaWx0ZXIkdTIwJGFzJHUyMCRjb3JlLi5zdHIuLnRyYWl0cy4uRnJvbVN0ciRHVCQ4ZnJvbV9zdHIxN2gzYmEwMzMwYmM5NGM4ZWU5RRYuX1pONGNvcmUzZm10OUZvcm1hdHRlcjNwYWQxN2hiOWZkYzQzNGNiYjBjM2E4RRdFX1pONGNvcmUzZm10NWZsb2F0MjlmbG9hdF90b19kZWNpbWFsX2NvbW1vbl9leGFjdDE3aDA4NDZiYzU2YmM0NDU0ZThFGDhfWk40Y29yZTNmbXQ5Rm9ybWF0dGVyMTJwYWRfaW50ZWdyYWwxN2hiYTUzODI4NTNlOTliOGYzRRlFX1pONDBfJExUJHN0ciR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uRGVidWckR1QkM2ZtdDE3aDYzYjYyZWI4NDZiMzg1YzVFGjtfWk4zc3RkOXBhbmlja2luZzIwcnVzdF9wYW5pY193aXRoX2hvb2sxN2hjY2IwNGUwZDg2ODExNmIwRRszX1pOM3N0ZDlwYW5pY2tpbmcxMmRlZmF1bHRfaG9vazE3aGZjNjA2MWQxZDM0NWIzOGVFHDhfWk4zc3RkMmlvNVdyaXRlMTh3cml0ZV9hbGxfdmVjdG9yZWQxN2hjOTA2MDUxYzRiMjgwZDFkRR1RX1pOM3N0ZDlwYW5pY2tpbmcxMmRlZmF1bHRfaG9vazI4XyR1N2IkJHU3YiRjbG9zdXJlJHU3ZCQkdTdkJDE3aDFjOWRhZWViOGU5MDQ0NDVFHmdfWk42OF8kTFQkY29yZS4uZm10Li5idWlsZGVycy4uUGFkQWRhcHRlciR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uV3JpdGUkR1QkOXdyaXRlX3N0cjE3aDViZTYzNjcyNmFkMTEzZmVFH0hfWk40Y29yZTNmbXQ1ZmxvYXQzMmZsb2F0X3RvX2RlY2ltYWxfY29tbW9uX3Nob3J0ZXN0MTdoOGRjNDI5NzIzZDBhNmRlNEUgM19aTjRjb3JlM3N0cjhjb252ZXJ0czlmcm9tX3V0ZjgxN2g5MDZkMmZiMGNiYTdiOTA5RSFmX1pONzFfJExUJGNvcmUuLmhhc2guLnNpcC4uSGFzaGVyJExUJFMkR1QkJHUyMCRhcyR1MjAkY29yZS4uaGFzaC4uSGFzaGVyJEdUJDV3cml0ZTE3aGIxMTk4ZDI3YjkxYjk5OTNFIkFfWk40Y29yZTNmbXQ5Rm9ybWF0dGVyMjF3cml0ZV9mb3JtYXR0ZWRfcGFydHMxN2g2ZjA3NzNkYzljZGQyYWZiRSM2X1pOM3N0ZDRzeW5jNG9uY2U0T25jZTEwY2FsbF9pbm5lcjE3aDE4ZDE4YzAzMTI0NTg5ZTZFJCZfWk40Y29yZTNmbXQ1d3JpdGUxN2g3NWZlNWEyYmI2YjFmYzAyRSU/X1pONGNvcmUzZm10OUZvcm1hdHRlcjE5cGFkX2Zvcm1hdHRlZF9wYXJ0czE3aGM1NDcxZTM3ODI5MDk4MjlFJidfWk4zc3RkNnRocmVhZDRwYXJrMTdoNzk1NzI5MmVlYTk1ZTk1OUUnS19aTjE4bWFyaW5lX3JzX3Nka19tYWluNmxvZ2dlcjE3V2FzbUxvZ2dlckJ1aWxkZXI1YnVpbGQxN2g4ODc3Njg5ZDg5NzE2ZTQzRShgX1pONjdfJExUJG1hcmluZV9yc19zZGtfbWFpbi4ubG9nZ2VyLi5XYXNtTG9nZ2VyJHUyMCRhcyR1MjAkbG9nLi5Mb2ckR1QkM2xvZzE3aDI0NTc5NzllNGRjZWIxMGZFKThfWk4zc3RkMmlvNVdyaXRlMTh3cml0ZV9hbGxfdmVjdG9yZWQxN2g5NDY1OGU4NWY3YjNmZjVhRSo3X1pONGNvcmU0aGFzaDExQnVpbGRIYXNoZXI4aGFzaF9vbmUxN2g2ZGMyMTI5MzA5ZDFkOTVmRSsIYWxsb2NhdGUsXF9aTjRjb3JlM2ZtdDNudW01MF8kTFQkaW1wbCR1MjAkY29yZS4uZm10Li5EZWJ1ZyR1MjAkZm9yJHUyMCR1NjQkR1QkM2ZtdDE3aDRkOTU4MjBmNWUxNzg2YzNFLVlfWk40Y29yZTNudW03Zmx0MmRlYzhzdHJhdGVneTVncmlzdTE2Zm9ybWF0X2V4YWN0X29wdDE0cG9zc2libHlfcm91bmQxN2gwODY5MWYwOTJjYjNjMmYyRS5GX1pONDFfJExUJGNoYXIkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRlYnVnJEdUJDNmbXQxN2g3OGNjMWYxMjdjNmQ1YzU2RS9wX1pONzJfJExUJCRSRiRzdHIkdTIwJGFzJHUyMCRhbGxvYy4uZmZpLi5jX3N0ci4uQ1N0cmluZy4ubmV3Li5TcGVjTmV3SW1wbCRHVCQxM3NwZWNfbmV3X2ltcGwxN2g0MGE1ZjQ3ZDczY2E5MTI2RTARaW50ZXJuYWxfbWVtYWxpZ24xgAFfWk4zc3RkMmlvNWltcGxzNzRfJExUJGltcGwkdTIwJHN0ZC4uaW8uLldyaXRlJHUyMCRmb3IkdTIwJGFsbG9jLi52ZWMuLlZlYyRMVCR1OCRDJEEkR1QkJEdUJDE0d3JpdGVfdmVjdG9yZWQxN2g5ZGY3MmU1NjNiZTNjM2RiRTJGX1pONGNvcmUzbnVtNmJpZ251bThCaWczMng0MDEwbXVsX2RpZ2l0czltdWxfaW5uZXIxN2gzNTk4MWU3NDdmZjIwODFmRTM7X1pONGNvcmUzbnVtN2ZsdDJkZWMxN2RpZ2l0c190b19kZWNfc3RyMTdoODE4MmYzZWM0YTk0NDM2MUU0T19aTjNzdGQyaW84YnVmZmVyZWQ5YnVmd3JpdGVyMThCdWZXcml0ZXIkTFQkVyRHVCQ5Zmx1c2hfYnVmMTdoOTJiZTVhMGVkZDA5MGE0ZEU1O19aTjRjb3JlOXBhbmlja2luZzE5YXNzZXJ0X2ZhaWxlZF9pbm5lcjE3aDI0MmM1ZDY5NGI3ZDZmYjdFNgZtZW1zZXQ3NF9aTjRjb3JlN3VuaWNvZGU5cHJpbnRhYmxlNWNoZWNrMTdoNTk4MDM1ZmYxODliM2E3YUU4PF9aTjRjb3JlM2ZtdDhidWlsZGVyczExRGVidWdTdHJ1Y3Q1ZmllbGQxN2g2ZGQ1MmNlYjYzMWEwODEzRTlSX1pOM3N0ZDRzeW5jNG9uY2U0T25jZTljYWxsX29uY2UyOF8kdTdiJCR1N2IkY2xvc3VyZSR1N2QkJHU3ZCQxN2gzNGE2ODVlMzhjYzg5Y2NhRTpKX1pONGNvcmU3dW5pY29kZTEydW5pY29kZV9kYXRhMTVncmFwaGVtZV9leHRlbmQ2bG9va3VwMTdoZDU5NzU5MTdiNzY5OWYwZUU7V19aTjUwXyRMVCQkUkYkbXV0JHUyMCRXJHUyMCRhcyR1MjAkY29yZS4uZm10Li5Xcml0ZSRHVCQxMHdyaXRlX2NoYXIxN2hmNTc5MDg4ZDJhZWZmODhhRTxmX1pONzNfJExUJGNvcmUuLnBhbmljLi5wYW5pY19pbmZvLi5QYW5pY0luZm8kdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRpc3BsYXkkR1QkM2ZtdDE3aDVhNWY2NWFjZjgzNjczODFFPV9fWk41OF8kTFQkYWxsb2MuLnN0cmluZy4uU3RyaW5nJHUyMCRhcyR1MjAkY29yZS4uZm10Li5Xcml0ZSRHVCQxMHdyaXRlX2NoYXIxN2g1NzU1NjFlNjQyZjg1ODYzRT4IX19zdHBjcHk/X19aTjU4XyRMVCRhbGxvYy4uc3RyaW5nLi5TdHJpbmckdTIwJGFzJHUyMCRjb3JlLi5mbXQuLldyaXRlJEdUJDEwd3JpdGVfY2hhcjE3aDU3NTU2MWU2NDJmODU4NjNFQAtfX3N0cmNocm51bEE+X1pONGNvcmU1c2xpY2U2bWVtY2hyMTltZW1jaHJfZ2VuZXJhbF9jYXNlMTdoY2E1MDhkMGUxZWNmNmQ0Y0VCMF9aTjRjb3JlM2ZtdDNudW0zaW1wN2ZtdF91NjQxN2hiMGNhYmI1NTdjMDZjMWRiRUM7X1pOM3N0ZDVhbGxvYzI0ZGVmYXVsdF9hbGxvY19lcnJvcl9ob29rMTdoYjFmYmM1NTA2ZTkxMzMwZkVEN19aTjNzdGQzc3lzNHdhc2kxN2RlY29kZV9lcnJvcl9raW5kMTdoODUyZGE4ZjU5YWU5NzI3Y0VFXF9aTjRjb3JlM2ZtdDNudW01MF8kTFQkaW1wbCR1MjAkY29yZS4uZm10Li5EZWJ1ZyR1MjAkZm9yJHUyMCR1MzIkR1QkM2ZtdDE3aGEwYzIxYTYxZmY0MmNmMGFFRjtfWk40Y29yZTNmbXQ4YnVpbGRlcnMxMERlYnVnVHVwbGU1ZmllbGQxN2gxZTU2MTBiYWE3NDhjNDA5RUcyX1pONGNvcmUzZm10NVdyaXRlMTB3cml0ZV9jaGFyMTdoMzE2NjBhNDk3ZjE1ZDI3MUVILF9aTjNzdGQzZW52MTFjdXJyZW50X2RpcjE3aGM0MzU4ODc3M2Q2OGViZGNFSURfWk41YWxsb2MzZmZpNWNfc3RyN0NTdHJpbmcxOV9mcm9tX3ZlY191bmNoZWNrZWQxN2g4ZmMzNDk1NjlmZTYyNDQwRUo7X1pONGNvcmUzZm10OGJ1aWxkZXJzMTBEZWJ1Z0lubmVyNWVudHJ5MTdoNjA3MjA4OTI2MGI0ZjUxYUVLCXRlc3RfbG9nc0xMX1pOM3N0ZDEwc3lzX2NvbW1vbjEzdGhyZWFkX3BhcmtlcjdnZW5lcmljNlBhcmtlcjZ1bnBhcmsxN2hjZGE4MWYyNmYwNjFmYzQzRU14X1pOOTFfJExUJHN0ZC4uc3lzX2NvbW1vbi4uYmFja3RyYWNlLi5fcHJpbnQuLkRpc3BsYXlCYWNrdHJhY2UkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRpc3BsYXkkR1QkM2ZtdDE3aGVkMmNkODQ4MWRlOGFjYjFFTlBfWk4zc3RkNnRocmVhZDVsb2NhbDRsYXp5MjFMYXp5S2V5SW5uZXIkTFQkVCRHVCQxMGluaXRpYWxpemUxN2g1MmZmZmE1YWM0MzBlMzNlRU9zX1pOODBfJExUJHN0ZC4uaW8uLldyaXRlLi53cml0ZV9mbXQuLkFkYXB0ZXIkTFQkVCRHVCQkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLldyaXRlJEdUJDl3cml0ZV9zdHIxN2hhMzc3ZjYxNmM2NDRkMTU3RVBJX1pOMThtYXJpbmVfcnNfc2RrX21haW42bG9nZ2VyMTdXYXNtTG9nZ2VyQnVpbGRlcjNuZXcxN2hhNWZmYmYwNWU1MDdiZjY4RVEzX1pOM3N0ZDJydDE5bGFuZ19zdGFydF9pbnRlcm5hbDE3aDcwODg1NmRiYTBkMGUzMjBFUnxfWk45MF8kTFQkc3RkLi5wYW5pY2tpbmcuLmJlZ2luX3BhbmljX2hhbmRsZXIuLlBhbmljUGF5bG9hZCR1MjAkYXMkdTIwJGNvcmUuLnBhbmljLi5Cb3hNZVVwJEdUJDh0YWtlX2JveDE3aDFjNGE5MWU2NjNlN2VkNWNFUy1fWk4zc3RkNnRocmVhZDZUaHJlYWQzbmV3MTdoZWMxNDVkNTM2YjE3NjZmNUVUV19aTjUwXyRMVCQkUkYkbXV0JHUyMCRXJHUyMCRhcyR1MjAkY29yZS4uZm10Li5Xcml0ZSRHVCQxMHdyaXRlX2NoYXIxN2hiOGRlMWU2YzcyMDVmMjNiRVUkX1pOM3N0ZDNlbnY0X3ZhcjE3aDNiM2Y0MDIzYzZhZTMxNGVFVldfWk41MF8kTFQkJFJGJG11dCR1MjAkVyR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uV3JpdGUkR1QkMTB3cml0ZV9jaGFyMTdoOTBjOTc3NzQ0ZjkzMmM0MEVXMl9aTjRjb3JlM2ZtdDVXcml0ZTEwd3JpdGVfY2hhcjE3aDk1NWYzNWY1NGY2ZGNhYzBFWDJfWk40Y29yZTNmbXQ1V3JpdGUxMHdyaXRlX2NoYXIxN2g3NDQ3MTNlN2I5ZDJiNDVkRVknX1pOM3N0ZDNlbnY3X3Zhcl9vczE3aGJjOGZlOTNiMTQwZGM4NGJFWi5fWk4zc3RkMmlvNVdyaXRlOXdyaXRlX2ZtdDE3aGYyNTgwZjg3OWYyMjI0NDhFWy5fWk4zc3RkMmlvNVdyaXRlOXdyaXRlX2ZtdDE3aDc3NjI5YWQ2YmY4NTQ5Y2FFXExfWk41YWxsb2M3cmF3X3ZlYzE5UmF3VmVjJExUJFQkQyRBJEdUJDE2cmVzZXJ2ZV9mb3JfcHVzaDE3aDI5ZjYyNmY4YjY5YWZlYWRFXUVfWk40Y29yZTNmbXQ5Rm9ybWF0dGVyMjVkZWJ1Z190dXBsZV9maWVsZDJfZmluaXNoMTdoMzM1OTVhZDAwNmFlYTZhNUVeRV9aTjRjb3JlM2ZtdDlGb3JtYXR0ZXIyNWRlYnVnX3R1cGxlX2ZpZWxkMV9maW5pc2gxN2hiZTYyNjY4NTNkM2JmNmZmRV82X1pOM3N0ZDVwYW5pYzE5Z2V0X2JhY2t0cmFjZV9zdHlsZTE3aDdlYmE3Y2FlYjZkNDIyZmNFYEBfWk4zc3RkNnRocmVhZDVsb2NhbDE3TG9jYWxLZXkkTFQkVCRHVCQ0d2l0aDE3aDY2ODAyNWNhYmJkMTliYjBFYS5fWk4zbG9nMTdfX3ByaXZhdGVfYXBpX2xvZzE3aGI4ODc0MjgwYTIyNzQ5ZTVFYi5fWk4zc3RkMmlvNVdyaXRlOXdyaXRlX2FsbDE3aGE3NTY5OWEwNGE1ZmU3YTZFYwZkaXZpZGVkTF9aTjVhbGxvYzdyYXdfdmVjMTlSYXdWZWMkTFQkVCRDJEEkR1QkMTZyZXNlcnZlX2Zvcl9wdXNoMTdoNDg2MDE1YWZkYjI2YTFmZkVlWV9aTjVhbGxvYzdyYXdfdmVjMTlSYXdWZWMkTFQkVCRDJEEkR1QkN3Jlc2VydmUyMWRvX3Jlc2VydmVfYW5kX2hhbmRsZTE3aGExM2QxYzE4YjYxOWRiZTJFZllfWk41YWxsb2M3cmF3X3ZlYzE5UmF3VmVjJExUJFQkQyRBJEdUJDdyZXNlcnZlMjFkb19yZXNlcnZlX2FuZF9oYW5kbGUxN2g2ZjI0NmE5NTRkZWU0YTZlRWdMX1pONWFsbG9jN3Jhd192ZWMxOVJhd1ZlYyRMVCRUJEMkQSRHVCQxNnJlc2VydmVfZm9yX3B1c2gxN2hkNmFiMWVkMDJhMTA1YTFkRWhMX1pONWFsbG9jN3Jhd192ZWMxOVJhd1ZlYyRMVCRUJEMkQSRHVCQxNnJlc2VydmVfZm9yX3B1c2gxN2gwZTdmMDE3OGU2MjU5MGJhRWkDYWRkaghzdWJ0cmFjdGsIbXVsdGlwbHlsBnN0cmxlbm04X1pOM3N0ZDEwc3lzX2NvbW1vbjExdGhyZWFkX2luZm8zc2V0MTdoZDVmMDUyMWE4ZDg3NzYyNUVuPF9aTjRjb3JlN3VuaWNvZGU5cHJpbnRhYmxlMTJpc19wcmludGFibGUxN2g4M2M4ZDAxMjkxZDViZmZjRW9dX1pONGNvcmUzcHRyNTlkcm9wX2luX3BsYWNlJExUJG1hcmluZV9yc19zZGtfbWFpbi4ubG9nZ2VyLi5XYXNtTG9nZ2VyJEdUJDE3aDkzMTIyNzY4ZjBlMDkxZTFFcE1fWk40Y29yZTNmbXQ4YnVpbGRlcnMxMURlYnVnU3RydWN0MjFmaW5pc2hfbm9uX2V4aGF1c3RpdmUxN2g4MzJkNjEwYTg0YzU2NzZhRXF3X1pOOTBfJExUJHN0ZC4ucGFuaWNraW5nLi5iZWdpbl9wYW5pY19oYW5kbGVyLi5QYW5pY1BheWxvYWQkdTIwJGFzJHUyMCRjb3JlLi5wYW5pYy4uQm94TWVVcCRHVCQzZ2V0MTdoYzhkNmQ2NGQ0Mzg2YTM3NkVyCnJ1c3RfcGFuaWNzMl9aTjVhbGxvYzdyYXdfdmVjMTFmaW5pc2hfZ3JvdzE3aDc2Y2JiMzFiM2VmYjFhZDlFdDJfWk41YWxsb2M3cmF3X3ZlYzExZmluaXNoX2dyb3cxN2g4MWRjYjY1NWI2YWY2YzYzRXVLX1pONWFsbG9jN3Jhd192ZWMxMWZpbmlzaF9ncm93MTdoMDliNzk0ODExZWZkMGRkNEUubGx2bS43MDQzNDUzNjExMTg5NzYzNzExdl9fWk40Y29yZTNmbXQzbnVtNTNfJExUJGltcGwkdTIwJGNvcmUuLmZtdC4uTG93ZXJIZXgkdTIwJGZvciR1MjAkaTE2JEdUJDNmbXQxN2hmZjVjYzMzMWExYmI0MzgzRXdeX1pONGNvcmUzZm10M251bTUyXyRMVCRpbXBsJHUyMCRjb3JlLi5mbXQuLkxvd2VySGV4JHUyMCRmb3IkdTIwJGk4JEdUJDNmbXQxN2hmZmI4NmQ0NWQxNzEwNDI4RXheX1pONGNvcmUzZm10M251bTUyXyRMVCRpbXBsJHUyMCRjb3JlLi5mbXQuLlVwcGVySGV4JHUyMCRmb3IkdTIwJGk4JEdUJDNmbXQxN2hkOGFjY2IxYmUxMjJkZTVkRXlfX1pONGNvcmUzZm10M251bTUzXyRMVCRpbXBsJHUyMCRjb3JlLi5mbXQuLlVwcGVySGV4JHUyMCRmb3IkdTIwJGkxNiRHVCQzZm10MTdoYjZiODQ4OWQ3NzUxOGM2N0V6X19aTjRjb3JlM2ZtdDNudW01M18kTFQkaW1wbCR1MjAkY29yZS4uZm10Li5Mb3dlckhleCR1MjAkZm9yJHUyMCRpMzIkR1QkM2ZtdDE3aGM5ZTIxNjEzNTIxNjlkOTBFe19fWk40Y29yZTNmbXQzbnVtNTNfJExUJGltcGwkdTIwJGNvcmUuLmZtdC4uVXBwZXJIZXgkdTIwJGZvciR1MjAkaTMyJEdUJDNmbXQxN2g2YjdkOWFiMDdlYWVjZmNhRXwdX193YXNpbGliY19pbml0aWFsaXplX2Vudmlyb259B3N0cm5jbXB+ZF9aTjY3XyRMVCRtYXJpbmVfcnNfc2RrX21haW4uLmxvZ2dlci4uV2FzbUxvZ2dlciR1MjAkYXMkdTIwJGxvZy4uTG9nJEdUJDdlbmFibGVkMTdoN2ExMjgwZTRhNzRkYTBlZkV/Y19aTjcwXyRMVCRjb3JlLi5wYW5pYy4ubG9jYXRpb24uLkxvY2F0aW9uJHUyMCRhcyR1MjAkY29yZS4uZm10Li5EaXNwbGF5JEdUJDNmbXQxN2gxZjM3Mzc4YzAyYzI2OTA3RYABWF9aTjNzdGQ5cGFuaWNraW5nMTliZWdpbl9wYW5pY19oYW5kbGVyMjhfJHU3YiQkdTdiJGNsb3N1cmUkdTdkJCR1N2QkMTdoNTU0MWY3NGEwMzMxYTFkY0WBAQZnZXRlbnaCAVxfWk42M18kTFQkd2FzaS4ubGliX2dlbmVyYXRlZC4uRXJybm8kdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRlYnVnJEdUJDNmbXQxN2g4ZTQwN2U5ZTIwYmNlMDQzRYMBMl9aTjRjb3JlNnJlc3VsdDEzdW53cmFwX2ZhaWxlZDE3aDg2OGEwMDYwMWEzZWI2ODdFhAFfX1pONjRfJExUJHN0ZC4uc3lzLi53YXNpLi5zdGRpby4uU3RkZXJyJHUyMCRhcyR1MjAkc3RkLi5pby4uV3JpdGUkR1QkNXdyaXRlMTdoOGRlNzk5NjUyNGZiM2FkZkWFAT1fWk40Y29yZTNmbXQ4YnVpbGRlcnMxMURlYnVnU3RydWN0NmZpbmlzaDE3aDE1ODY1OWZjZjc5YzU5ZjlFhgFkX1pONzFfJExUJGNvcmUuLm9wcy4ucmFuZ2UuLlJhbmdlJExUJElkeCRHVCQkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRlYnVnJEdUJDNmbXQxN2g2NzQzYWVkNmQyMzZhMzlhRYcBBmNhbGxvY4gBSV9aTjQ0XyRMVCQkUkYkVCR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uRGlzcGxheSRHVCQzZm10MTdoMjMwZDVhOTZjNzhkZDRlN0WJAUdfWk40Ml8kTFQkJFJGJFQkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRlYnVnJEdUJDNmbXQxN2gxZmM4NGJmMDZlOTFkZWUwRYoBOV9aTjNzdGQzc3lzNHdhc2kxOWhhc2htYXBfcmFuZG9tX2tleXMxN2gxYzBjMGM4ZGQ1ZDQ3YmE4RYsBSV9aTjVhbGxvYzN2ZWMxNlZlYyRMVCRUJEMkQSRHVCQ2cmVtb3ZlMTNhc3NlcnRfZmFpbGVkMTdoYWYyNmM2ODIzNjUyOGZkNkWMAUdfWk40Y29yZTVzbGljZTVpbmRleDI5c2xpY2Vfc3RhcnRfaW5kZXhfbGVuX2ZhaWxfcnQxN2hhNGE1MTA5MTg5YmU2MDBiRY0BRV9aTjRjb3JlNXNsaWNlNWluZGV4MjdzbGljZV9lbmRfaW5kZXhfbGVuX2ZhaWxfcnQxN2gxYjk4OWE3YjkyYmY2Y2Y1RY4BQ19aTjRjb3JlNXNsaWNlNWluZGV4MjVzbGljZV9pbmRleF9vcmRlcl9mYWlsX3J0MTdoNTlmZTk3Mzc0MzdiZTljMUWPATpfWk40Y29yZTlwYW5pY2tpbmcxOHBhbmljX2JvdW5kc19jaGVjazE3aDk4NDRlNDZjMzM5YjliZmZFkAFYX1pONTlfJExUJGNvcmUuLmZtdC4uQXJndW1lbnRzJHUyMCRhcyR1MjAkY29yZS4uZm10Li5EaXNwbGF5JEdUJDNmbXQxN2hhNjM1MjMyMTQxNzYyYTVlRZEBNF9aTjRjb3JlM2ZtdDlGb3JtYXR0ZXI5d3JpdGVfZm10MTdoNTVhZGViODYxNTYzM2VkMUWSATVfWk40Y29yZTlwYW5pY2tpbmcxM2Fzc2VydF9mYWlsZWQxN2g4OTZkODFlMmU3ZDI3MjAwRZMBNV9aTjRjb3JlOXBhbmlja2luZzEzYXNzZXJ0X2ZhaWxlZDE3aDRhNzEyZTM2ZmQwN2U5ZDdFlAE1X1pONGNvcmU5cGFuaWNraW5nMTNhc3NlcnRfZmFpbGVkMTdoM2NmYmY5MWI2NGRiMzljMEWVAVVfWk41MF8kTFQkJFJGJG11dCR1MjAkVyR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uV3JpdGUkR1QkOXdyaXRlX2ZtdDE3aDBmMzMwMTcwODg4OGU2ZTNFlgFVX1pONTBfJExUJCRSRiRtdXQkdTIwJFckdTIwJGFzJHUyMCRjb3JlLi5mbXQuLldyaXRlJEdUJDl3cml0ZV9mbXQxN2gzYzY0ODYzMzk5NmZlMDc3RZcBVV9aTjUwXyRMVCQkUkYkbXV0JHUyMCRXJHUyMCRhcyR1MjAkY29yZS4uZm10Li5Xcml0ZSRHVCQ5d3JpdGVfZm10MTdoNjA5Zjc0NDg3MTYwNWU4YUWYAVVfWk41MF8kTFQkJFJGJG11dCR1MjAkVyR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uV3JpdGUkR1QkOXdyaXRlX2ZtdDE3aGU5ZThmNDI3ZGJhMWU1N2ZFmQFVX1pONTBfJExUJCRSRiRtdXQkdTIwJFckdTIwJGFzJHUyMCRjb3JlLi5mbXQuLldyaXRlJEdUJDl3cml0ZV9mbXQxN2gwZTQ3NWRiNzM1NDYxZTQ5RZoBMF9aTjRjb3JlM2ZtdDVXcml0ZTl3cml0ZV9mbXQxN2hkMmIzMGJjOGFhNTczYzQ3RZsBLV9aTjNsb2cxNnNldF9ib3hlZF9sb2dnZXIxN2g1YzA4OTg2YmQxZDc2MDNkRZwBMF9aTjRjb3JlM2ZtdDVXcml0ZTl3cml0ZV9mbXQxN2g0OGE0MmFkNzU4OGY0ZWU5RZ0BMF9aTjRjb3JlM2ZtdDVXcml0ZTl3cml0ZV9mbXQxN2g2ZWE0NzQ0ZmU2M2IyMTk1RZ4BaV9aTjY0XyRMVCRzdGQuLnN5cy4ud2FzaS4uc3RkaW8uLlN0ZGVyciR1MjAkYXMkdTIwJHN0ZC4uaW8uLldyaXRlJEdUJDE0d3JpdGVfdmVjdG9yZWQxN2g2OWRkZjM2YTk3OTdhOWFiRZ8BMF9aTjRjb3JlM2ZtdDVXcml0ZTl3cml0ZV9mbXQxN2hjZWU1ZjNmZTQ1M2ZiZmQxRaABBXN0YXRloQE6X1pONWFsbG9jNHN5bmMxMkFyYyRMVCRUJEdUJDlkcm9wX3Nsb3cxN2gxOTI4YzYyNTVmYTMzMDdkRaIBdl9aTjNzdGQyaW81aW1wbHM3NF8kTFQkaW1wbCR1MjAkc3RkLi5pby4uV3JpdGUkdTIwJGZvciR1MjAkYWxsb2MuLnZlYy4uVmVjJExUJHU4JEMkQSRHVCQkR1QkNXdyaXRlMTdoMzk5MTU1OWFhYzY0NzBmMkWjAQtjbGVhcl9zdGF0ZaQBY19aTjcwXyRMVCRjb3JlLi5yZXN1bHQuLlJlc3VsdCRMVCRUJEMkRSRHVCQkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRlYnVnJEdUJDNmbXQxN2hiZjI5ODBhYmFjYmMxZGU4RaUBfV9aTjkxXyRMVCRzdGQuLnBhbmlja2luZy4uYmVnaW5fcGFuaWMuLlBhbmljUGF5bG9hZCRMVCRBJEdUJCR1MjAkYXMkdTIwJGNvcmUuLnBhbmljLi5Cb3hNZVVwJEdUJDh0YWtlX2JveDE3aGUyYTNiMTY3ZGZhYjliNjBFpgFVX1pONTBfJExUJCRSRiRtdXQkdTIwJFckdTIwJGFzJHUyMCRjb3JlLi5mbXQuLldyaXRlJEdUJDl3cml0ZV9zdHIxN2g4YjdhNTE4MzA2ZWM2M2RiRacBVV9aTjUwXyRMVCQkUkYkbXV0JHUyMCRXJHUyMCRhcyR1MjAkY29yZS4uZm10Li5Xcml0ZSRHVCQ5d3JpdGVfc3RyMTdoMWI2MWEwMjJhZGMyNWRkYkWoAVVfWk41MF8kTFQkJFJGJG11dCR1MjAkVyR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uV3JpdGUkR1QkOXdyaXRlX3N0cjE3aDQ0ODczYjQ2ODNjZjk0M2FFqQF6X1pOM3N0ZDJpbzVpbXBsczc0XyRMVCRpbXBsJHUyMCRzdGQuLmlvLi5Xcml0ZSR1MjAkZm9yJHUyMCRhbGxvYy4udmVjLi5WZWMkTFQkdTgkQyRBJEdUJCRHVCQ5d3JpdGVfYWxsMTdoZThjYzI2MjMwNDQwNmUwYkWqAXNfWk44MF8kTFQkc3RkLi5pby4uV3JpdGUuLndyaXRlX2ZtdC4uQWRhcHRlciRMVCRUJEdUJCR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uV3JpdGUkR1QkOXdyaXRlX3N0cjE3aDkzNmQ4ZTkxYTE1NjhmNThFqwE0X1pONHdhc2kxM2xpYl9nZW5lcmF0ZWQ4ZmRfd3JpdGUxN2g1MDZlNmUxNGE4MGEwYmJiRawBXV9aTjU4XyRMVCRhbGxvYy4uc3RyaW5nLi5TdHJpbmckdTIwJGFzJHUyMCRjb3JlLi5mbXQuLldyaXRlJEdUJDl3cml0ZV9zdHIxN2g1MTY4MGQyM2NjMWU2ZWIxRa0BBHNicmuuAXNfWk40Y29yZTNwdHI4MWRyb3BfaW5fcGxhY2UkTFQkY29yZS4ucmVzdWx0Li5SZXN1bHQkTFQkJExQJCRSUCQkQyRzdGQuLmlvLi5lcnJvci4uRXJyb3IkR1QkJEdUJDE3aGQ4NGFkYWIyYThlNWVjYmFFrwF5X1pONGNvcmUzcHRyODdkcm9wX2luX3BsYWNlJExUJHN0ZC4uaW8uLldyaXRlLi53cml0ZV9mbXQuLkFkYXB0ZXIkTFQkJFJGJG11dCR1MjAkJHU1YiR1OCR1NWQkJEdUJCRHVCQxN2g0ZjQzY2EyMGEwY2JhNmM0RbABDV9fcmRsX3JlYWxsb2OxAQZtZW1jbXCyAUZfWk40Y29yZTNmbXQ5Rm9ybWF0dGVyMTJwYWRfaW50ZWdyYWwxMndyaXRlX3ByZWZpeDE3aDhmMmU0ZmMzZGFhMjYzNzVFswEGZ2V0Y3dktAE6X1pONWFsbG9jNHN5bmMxMkFyYyRMVCRUJEdUJDlkcm9wX3Nsb3cxN2g2YzFmZGI4NjZlMmFlZmQ3RbUBXV9aTjY0XyRMVCRhbGxvYy4uZmZpLi5jX3N0ci4uTnVsRXJyb3IkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRlYnVnJEdUJDNmbXQxN2hlOGFiOTlhZDhmZDZiOTA4RbYBLF9aTjRjb3JlOXBhbmlja2luZzVwYW5pYzE3aGVkZjQzNzhlMjA0NjFiMjVFtwE1X1pONGNvcmU5cGFuaWNraW5nMTNwYW5pY19kaXNwbGF5MTdoNjE0YmNmYTcyMzZmOTFhYUW4AX9fWk45M18kTFQkc3RkLi5wYW5pY2tpbmcuLmJlZ2luX3BhbmljX2hhbmRsZXIuLlN0clBhbmljUGF5bG9hZCR1MjAkYXMkdTIwJGNvcmUuLnBhbmljLi5Cb3hNZVVwJEdUJDh0YWtlX2JveDE3aGNmODc3ZmRiMGZjY2Q1NjFFuQFgX1pONGNvcmUzZm10NWZsb2F0NTJfJExUJGltcGwkdTIwJGNvcmUuLmZtdC4uRGlzcGxheSR1MjAkZm9yJHUyMCRmNjQkR1QkM2ZtdDE3aDY1NDMyN2NkNmY5ZWM1YTJFugERcnVzdF9iZWdpbl91bndpbmS7AThfWk41YWxsb2M3cmF3X3ZlYzE3Y2FwYWNpdHlfb3ZlcmZsb3cxN2hjYTIzZTA2MDBhOTUzNGY2RbwBMF9aTjRjb3JlOXBhbmlja2luZzlwYW5pY19mbXQxN2g3YTY1MWM3MGE5NDkyMjhjRb0BKl9aTjEyY2FsY19zZXJ2aWNlNG1haW4xN2gwY2E3NWE5OTZkODFiYTczRb4BVV9aTjU2XyRMVCRsb2cuLlNldExvZ2dlckVycm9yJHUyMCRhcyR1MjAkY29yZS4uZm10Li5EZWJ1ZyRHVCQzZm10MTdoNjUyYzYxNzYzODg0NTJhMkW/AUdfWk40Ml8kTFQkJFJGJFQkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRlYnVnJEdUJDNmbXQxN2gzZWJjMzNmYzA3ZjQwZmRmRcABYV9aTjY4XyRMVCRzdGQuLnRocmVhZC4ubG9jYWwuLkFjY2Vzc0Vycm9yJHUyMCRhcyR1MjAkY29yZS4uZm10Li5EZWJ1ZyRHVCQzZm10MTdoNTE3ZjAxOTlmNzk2OWU0ZEXBAWlfWk43Nl8kTFQkc3RkLi5zeW5jLi5wb2lzb24uLlBvaXNvbkVycm9yJExUJFQkR1QkJHUyMCRhcyR1MjAkY29yZS4uZm10Li5EZWJ1ZyRHVCQzZm10MTdoMTdhNDJkMjNlOGVjMWJjZEXCAUdfWk40Ml8kTFQkJFJGJFQkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRlYnVnJEdUJDNmbXQxN2g1Y2M3Njk4N2QxYWY4YzljRcMBR19aTjQyXyRMVCQkUkYkVCR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uRGVidWckR1QkM2ZtdDE3aGMxOGRkNjA0OTVjZWZlMDBFxAEPX19vcmlnaW5hbF9tYWluxQFhX1pOM3N0ZDJydDEwbGFuZ19zdGFydDI4XyR1N2IkJHU3YiRjbG9zdXJlJHU3ZCQkdTdkJDE3aDY5ZmNlMWZmZGI2ZjVhOThFLmxsdm0uNzEzOTI0MTQxMTY3NDgxMjc1M8YBc19aTjRjb3JlM29wczhmdW5jdGlvbjZGbk9uY2U0MGNhbGxfb25jZSR1N2IkJHU3YiR2dGFibGUuc2hpbSR1N2QkJHU3ZCQxN2gyMjFjZWNjMmEwOGQ2ZTM4RS5sbHZtLjcxMzkyNDE0MTE2NzQ4MTI3NTPHAYoBX1pONGNvcmUzcHRyMTAzZHJvcF9pbl9wbGFjZSRMVCRzdGQuLnN5bmMuLnBvaXNvbi4uUG9pc29uRXJyb3IkTFQkc3RkLi5zeW5jLi5tdXRleC4uTXV0ZXhHdWFyZCRMVCQkTFAkJFJQJCRHVCQkR1QkJEdUJDE3aGEzYjY1YzIyMDJlZDhjOWNFyAFQX1pOM3N0ZDlwYW5pY2tpbmcxMWJlZ2luX3BhbmljMjhfJHU3YiQkdTdiJGNsb3N1cmUkdTdkJCR1N2QkMTdoM2ZhOWYwYzVkM2JkNzBmZEXJATJfWk40Y29yZTNzdHIxNnNsaWNlX2Vycm9yX2ZhaWwxN2hkNGY4MTcyYTA1YjExYTk5RcoBLl9aTjRjb3JlM2ZtdDlGb3JtYXR0ZXIzbmV3MTdoNDkxOGE3OWU1NTQyN2Y4OEXLAThfWk40Y29yZTNmbXQ5Rm9ybWF0dGVyMTJkZWJ1Z19zdHJ1Y3QxN2g5N2UxYzNkYjQ0YzU5YmE3RcwBNl9aTjRjb3JlM2ZtdDlGb3JtYXR0ZXIxMGRlYnVnX2xpc3QxN2hjZmE0NmI2NDgxODczMjEyRc0BXF9aTjRjb3JlM2ZtdDNudW01MF8kTFQkaW1wbCR1MjAkY29yZS4uZm10Li5EZWJ1ZyR1MjAkZm9yJHUyMCR1MTYkR1QkM2ZtdDE3aGRjNTNlOTE1M2E4MGM4ZWFFzgFaX1pONGNvcmUzb3BzOGZ1bmN0aW9uNkZuT25jZTQwY2FsbF9vbmNlJHU3YiQkdTdiJHZ0YWJsZS5zaGltJHU3ZCQkdTdkJDE3aGIwYzFkNzhjYWUyM2M5ODFFzwE6X1pONGNvcmUzZm10OGJ1aWxkZXJzOURlYnVnTGlzdDZmaW5pc2gxN2hjNmJlNTZkZDZiZDQxYWVkRdABMl9aTjNzdGQ5cGFuaWNraW5nMTFiZWdpbl9wYW5pYzE3aGQ2N2I5NTY5NmM3ZjRmZjVF0QFoX1pONGNvcmUzcHRyNzBkcm9wX2luX3BsYWNlJExUJHN0ZC4ucGFuaWNraW5nLi5iZWdpbl9wYW5pY19oYW5kbGVyLi5QYW5pY1BheWxvYWQkR1QkMTdoNGI1ODNhYTM0NDIzYzUwMkXSATBfWk40Y29yZTlwYW5pY2tpbmc5cGFuaWNfc3RyMTdoZTE2NDQzMDcwMTk4OGU1YUXTAQtfX3JkbF9hbGxvY9QBNl9aTjR3YXNpMTNsaWJfZ2VuZXJhdGVkNUVycm5vNG5hbWUxN2hmZDJlOWQ1YjM5NGFhNzljRdUBOV9aTjR3YXNpMTNsaWJfZ2VuZXJhdGVkNUVycm5vN21lc3NhZ2UxN2hlNjQ0MWI5OTQ1ZjI4YjM0RdYBBnN0cmR1cNcBTF9aTjRjb3JlM3B0cjQyZHJvcF9pbl9wbGFjZSRMVCRhbGxvYy4uc3RyaW5nLi5TdHJpbmckR1QkMTdoYzI0MWFmNWJhYTA2M2RkM0XYAYUCX1pONGNvcmUzcHRyMjI2ZHJvcF9pbl9wbGFjZSRMVCRzdGQuLmVycm9yLi4kTFQkaW1wbCR1MjAkY29yZS4uY29udmVydC4uRnJvbSRMVCRhbGxvYy4uc3RyaW5nLi5TdHJpbmckR1QkJHUyMCRmb3IkdTIwJGFsbG9jLi5ib3hlZC4uQm94JExUJGR5biR1MjAkc3RkLi5lcnJvci4uRXJyb3IkdTJiJGNvcmUuLm1hcmtlci4uU3luYyR1MmIkY29yZS4ubWFya2VyLi5TZW5kJEdUJCRHVCQuLmZyb20uLlN0cmluZ0Vycm9yJEdUJDE3aGU0NzcyOTQ5NTNmOTJhZWNF2QFIX1pONDNfJExUJGJvb2wkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRpc3BsYXkkR1QkM2ZtdDE3aGExY2M5ZWEzZDEyZGRjNDJF2gF4X1pOOTFfJExUJHN0ZC4ucGFuaWNraW5nLi5iZWdpbl9wYW5pYy4uUGFuaWNQYXlsb2FkJExUJEEkR1QkJHUyMCRhcyR1MjAkY29yZS4ucGFuaWMuLkJveE1lVXAkR1QkM2dldDE3aDliMDdiZjIyZThiZTZkYjdF2wE/X1pONWFsbG9jNnN0cmluZzEzRnJvbVV0ZjhFcnJvcjEwaW50b19ieXRlczE3aGExN2RmMTk0OGI3Njc4NGVF3AEIcnVzdF9vb23dATdfWk40d2FzaTEzbGliX2dlbmVyYXRlZDEwcmFuZG9tX2dldDE3aDkxN2JjYWNhOWU5YWY4MGNF3gENYWxpZ25lZF9hbGxvY98BTl9aTjE4bWFyaW5lX3JzX3Nka19tYWluNnJlc3VsdDE4T0JKRUNUU19UT19SRUxFQVNFN19fZ2V0aXQxN2hjYjA2MGU1MmVlNWY5NTU4ReABO19aTjRjb3JlMTBpbnRyaW5zaWNzMTdjb25zdF9ldmFsX3NlbGVjdDE3aGQ1YWZmYTBjOWNlZTZlY2VF4QFcX1pONjNfJExUJGNvcmUuLmNlbGwuLkJvcnJvd011dEVycm9yJHUyMCRhcyR1MjAkY29yZS4uZm10Li5EZWJ1ZyRHVCQzZm10MTdoMTI0MmQ5MTEyNjQ3MTI4MkXiAVJfWk41M18kTFQkY29yZS4uZm10Li5FcnJvciR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uRGVidWckR1QkM2ZtdDE3aGE2MGRhOGViOWMxMzQ5MThF4wE7X1pONGNvcmU2b3B0aW9uMTVPcHRpb24kTFQkVCRHVCQ2dW53cmFwMTdoNDRhNDBjZDU2YTcyNGUxY0XkATtfWk40Y29yZTZvcHRpb24xNU9wdGlvbiRMVCRUJEdUJDZ1bndyYXAxN2hiYzljMDJjZGNkZGNlMDE0ReUBBl9zdGFydOYBDl9fcnVzdF9yZWFsbG9j5wFNX1pOM3N0ZDEwc3lzX2NvbW1vbjliYWNrdHJhY2UyNl9fcnVzdF9lbmRfc2hvcnRfYmFja3RyYWNlMTdoODU1MzJmMzkwZGFlYWM0ZUXoAU1fWk4zc3RkMTBzeXNfY29tbW9uOWJhY2t0cmFjZTI2X19ydXN0X2VuZF9zaG9ydF9iYWNrdHJhY2UxN2g3M2E0M2YzOGI1ZTZlZTA5RekBSV9aTjQ0XyRMVCQkUkYkVCR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uRGlzcGxheSRHVCQzZm10MTdoMDY3YzhlZGUyYTNmYmQ3YkXqARlfX3dhc2lsaWJjX2Vuc3VyZV9lbnZpcm9u6wFHX1pONDJfJExUJCRSRiRUJHUyMCRhcyR1MjAkY29yZS4uZm10Li5EZWJ1ZyRHVCQzZm10MTdoMjAwNWIzMWM4OTYxOTIxOUXsAQxfX3J1c3RfYWxsb2PtAUlfWk40NF8kTFQkJFJGJFQkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRpc3BsYXkkR1QkM2ZtdDE3aGEwOGZhZjdmN2M3MTgyNzNF7gFXX1pONTBfJExUJCRSRiRtdXQkdTIwJFckdTIwJGFzJHUyMCRjb3JlLi5mbXQuLldyaXRlJEdUJDEwd3JpdGVfY2hhcjE3aDQyNTAzNDBiM2FjZTQyYzJF7wF6X1pOOTNfJExUJHN0ZC4ucGFuaWNraW5nLi5iZWdpbl9wYW5pY19oYW5kbGVyLi5TdHJQYW5pY1BheWxvYWQkdTIwJGFzJHUyMCRjb3JlLi5wYW5pYy4uQm94TWVVcCRHVCQzZ2V0MTdoN2VkNjU2ZThmZjhjNmIzZkXwAUdfWk40Ml8kTFQkJFJGJFQkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRlYnVnJEdUJDNmbXQxN2g0MWQzYTA2OTc1OTMzNTc1RfEBOl9aTjRjb3JlM29wczhmdW5jdGlvbjZGbk9uY2U5Y2FsbF9vbmNlMTdoYjdiZDgyYzY0MzM2OTI0Y0XyAUlfWk40NF8kTFQkJFJGJFQkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRpc3BsYXkkR1QkM2ZtdDE3aGFjMGZkZjAzZWE5ODFmMmVF8wE7X1pONGNvcmUzZm10OUZvcm1hdHRlcjE1ZGVidWdfbG93ZXJfaGV4MTdoMzVkZWQxZDc5NGVhNWQ3MkX0ATtfWk40Y29yZTNmbXQ5Rm9ybWF0dGVyMTVkZWJ1Z191cHBlcl9oZXgxN2hmOWY5MmFjYTFlNTBkZDExRfUBDl9fcnVzdF9kZWFsbG9j9gFHX1pONDJfJExUJCRSRiRUJHUyMCRhcyR1MjAkY29yZS4uZm10Li5EZWJ1ZyRHVCQzZm10MTdoNGNmYzczNWI4NDgyYmNjYUX3AVVfWk41MF8kTFQkJFJGJG11dCR1MjAkVyR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uV3JpdGUkR1QkOXdyaXRlX3N0cjE3aDRjMmI3MDQ4N2EzZmQxMzVF+AESX193YXNpX2Vudmlyb25fZ2V0+QEYX193YXNpX2Vudmlyb25fc2l6ZXNfZ2V0+gEEZXhpdPsBBnN0cmNwefwBOl9aTjRjb3JlM29wczhmdW5jdGlvbjZGbk9uY2U5Y2FsbF9vbmNlMTdoYzVhODdmYmI0MDFhN2VlZUX9AWJfWk40Y29yZTNmbXQzbnVtM2ltcDUyXyRMVCRpbXBsJHUyMCRjb3JlLi5mbXQuLkRpc3BsYXkkdTIwJGZvciR1MjAkdTMyJEdUJDNmbXQxN2hkNGI2ZmZjOGEwYzdlMzUyRf4BMl9aTjRjb3JlNm9wdGlvbjEzZXhwZWN0X2ZhaWxlZDE3aGI3ZWRkYzVhMTkzN2U2YmJF/wE4X1pONGNvcmUzZm10OGJ1aWxkZXJzOERlYnVnU2V0NWVudHJ5MTdoMDE0ZjlkNmYyZGMyZTQ2Y0WAAlVfWk41MF8kTFQkJFJGJG11dCR1MjAkVyR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uV3JpdGUkR1QkOXdyaXRlX3N0cjE3aDIxODAyY2EzZjM4MTRlZGZFgQJhX1pONGNvcmUzZm10M251bTNpbXA1MV8kTFQkaW1wbCR1MjAkY29yZS4uZm10Li5EaXNwbGF5JHUyMCRmb3IkdTIwJHU4JEdUJDNmbXQxN2g0MDBmMWYyNmZmMzI2Y2I2RYICYl9aTjRjb3JlM2ZtdDNudW0zaW1wNTJfJExUJGltcGwkdTIwJGNvcmUuLmZtdC4uRGlzcGxheSR1MjAkZm9yJHUyMCR1MTYkR1QkM2ZtdDE3aDI1NDk4MTU3MWU3OTU5NThFgwITbWFpbi5jb21tYW5kX2V4cG9ydIQCF2FsbG9jYXRlLmNvbW1hbmRfZXhwb3J0hQIaX19ydXN0X2FsbG9jX2Vycm9yX2hhbmRsZXKGAkdfWk40Ml8kTFQkJFJGJFQkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRlYnVnJEdUJDNmbXQxN2gzYTIzMzM0ODlkZDVlNjM4RYcCR19aTjQyXyRMVCQkUkYkVCR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uRGVidWckR1QkM2ZtdDE3aDBlOGZhNTFlM2UyYzJkZDBFiAJHX1pONDJfJExUJCRSRiRUJHUyMCRhcyR1MjAkY29yZS4uZm10Li5EZWJ1ZyRHVCQzZm10MTdoZmNmMWQ4NDAxZWIxNzQwZkWJAklfWk40NF8kTFQkJFJGJFQkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRpc3BsYXkkR1QkM2ZtdDE3aGI5ODMxNTk5NGYxODYwMmVFigJXX1pONTBfJExUJCRSRiRtdXQkdTIwJFckdTIwJGFzJHUyMCRjb3JlLi5mbXQuLldyaXRlJEdUJDEwd3JpdGVfY2hhcjE3aDZkOTY1Mjc3MmE3ZmE1YjhFiwI7X1pONGNvcmUxMGludHJpbnNpY3MxN2NvbnN0X2V2YWxfc2VsZWN0MTdoNzRlNzg5ZDUzNmFlMDYyYkWMAjpfWk40Y29yZTNvcHM4ZnVuY3Rpb242Rm5PbmNlOWNhbGxfb25jZTE3aGViYTk2ODMwZjRlYzc1YzdFjQJAX1pONWFsbG9jNWFsbG9jMThoYW5kbGVfYWxsb2NfZXJyb3I4cnRfZXJyb3IxN2hmMjhjYTFkNDc1YWU3OTQ4RY4CN19aTjVhbGxvYzVhbGxvYzE4aGFuZGxlX2FsbG9jX2Vycm9yMTdoMWVlMWNjZmRhZmU2YTQwYUWPAghfX3JnX29vbZACOl9aTjRjb3JlM29wczhmdW5jdGlvbjZGbk9uY2U5Y2FsbF9vbmNlMTdoMjQ4ZGJjYjIwNGNjMTRlNUWRAjpfWk40Y29yZTNvcHM4ZnVuY3Rpb242Rm5PbmNlOWNhbGxfb25jZTE3aDM4ZjFhYjY0YjJjZWM1MTRFkgI6X1pONGNvcmUzb3BzOGZ1bmN0aW9uNkZuT25jZTljYWxsX29uY2UxN2hkNWU1NThhODE1YzU0YWU3RZMCRF9aTjRjb3JlNXNsaWNlNWluZGV4MjZzbGljZV9zdGFydF9pbmRleF9sZW5fZmFpbDE3aDIzMWE0NDJlYWUyMDBkNmJFlAJCX1pONGNvcmU1c2xpY2U1aW5kZXgyNHNsaWNlX2VuZF9pbmRleF9sZW5fZmFpbDE3aGM5Mjg4ZWViMTkyN2M4ZDdFlQJAX1pONGNvcmU1c2xpY2U1aW5kZXgyMnNsaWNlX2luZGV4X29yZGVyX2ZhaWwxN2hmMTc0ZTA1NWFlNTMzNzNmRZYCO19aTjRjb3JlMTBpbnRyaW5zaWNzMTdjb25zdF9ldmFsX3NlbGVjdDE3aDA0MzllMDIyMmZhMjQ3MGFFlwI7X1pONGNvcmUxMGludHJpbnNpY3MxN2NvbnN0X2V2YWxfc2VsZWN0MTdoNDExYTg0ZjM2ODM4NWFmYUWYAjtfWk40Y29yZTEwaW50cmluc2ljczE3Y29uc3RfZXZhbF9zZWxlY3QxN2hlMDQxN2M3NjBiNGFhOTA3RZkCQF9aTjRjb3JlNXBhbmljMTBwYW5pY19pbmZvOVBhbmljSW5mbzdwYXlsb2FkMTdoNjk5YTNkYmZlZDIxMGQyNkWaAkdfWk40Ml8kTFQkc3RyJHUyMCRhcyR1MjAkY29yZS4uZm10Li5EaXNwbGF5JEdUJDNmbXQxN2hiYTZmZTJhY2Q1NTE0YzJkRZsCR19aTjQyXyRMVCQkUkYkVCR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uRGVidWckR1QkM2ZtdDE3aDdiMWQzZDMzMGE3YTgxMzFFnAISYWRkLmNvbW1hbmRfZXhwb3J0nQIXc3VidHJhY3QuY29tbWFuZF9leHBvcnSeAhdtdWx0aXBseS5jb21tYW5kX2V4cG9ydJ8CFWRpdmlkZS5jb21tYW5kX2V4cG9ydKACHXNldF9yZXN1bHRfcHRyLmNvbW1hbmRfZXhwb3J0oQIec2V0X3Jlc3VsdF9zaXplLmNvbW1hbmRfZXhwb3J0ogJFX1pOM3N0ZDlwYW5pY2tpbmcxMXBhbmljX2NvdW50MTdpc196ZXJvX3Nsb3dfcGF0aDE3aGZmNzI3ZTc1ODE2MThkNTVFowJ2X1pOM3N0ZDJpbzVpbXBsczc0XyRMVCRpbXBsJHUyMCRzdGQuLmlvLi5Xcml0ZSR1MjAkZm9yJHUyMCRhbGxvYy4udmVjLi5WZWMkTFQkdTgkQyRBJEdUJCRHVCQ1Zmx1c2gxN2hiNDY5NDA3NmQwNWYyYjhiRaQCX19aTjY0XyRMVCRzdGQuLnN5cy4ud2FzaS4uc3RkaW8uLlN0ZGVyciR1MjAkYXMkdTIwJHN0ZC4uaW8uLldyaXRlJEdUJDVmbHVzaDE3aDgzMzY1ZTNiMTQ1ZDA4NzZFpQIQX193YXNpX3Byb2NfZXhpdKYCBV9FeGl0pwIOc2V0X3Jlc3VsdF9wdHKoAg9zZXRfcmVzdWx0X3NpemWpAhVfc3RhcnQuY29tbWFuZF9leHBvcnSqAhh0ZXN0X2xvZ3MuY29tbWFuZF9leHBvcnSrAhpjbGVhcl9zdGF0ZS5jb21tYW5kX2V4cG9ydKwCFHN0YXRlLmNvbW1hbmRfZXhwb3J0rQIdZ2V0X3Jlc3VsdF9wdHIuY29tbWFuZF9leHBvcnSuAh5nZXRfcmVzdWx0X3NpemUuY29tbWFuZF9leHBvcnSvAh5yZWxlYXNlX29iamVjdHMuY29tbWFuZF9leHBvcnSwAk9fWk4zc3RkMTBzeXNfY29tbW9uOWJhY2t0cmFjZTI4X19ydXN0X2JlZ2luX3Nob3J0X2JhY2t0cmFjZTE3aDNkMTVlZmZkMDg2MzYzOTJFsQI0X1pOM3N0ZDNzeXM0d2FzaTE0YWJvcnRfaW50ZXJuYWwxN2hlMjU0NWYwYjAyYmQzNjJkRbICKV9aTjNzdGQ3cHJvY2VzczVhYm9ydDE3aDRjYzRjY2QwZjViZGM4OTFFswINX19yZGxfZGVhbGxvY7QCPF9aTjNzdGQzc3lzNHdhc2k3cHJvY2VzczhFeGl0Q29kZTZhc19pMzIxN2g2OWY4NTg2MjEzYzJlNmQ3RbUCNV9aTjR3YXNpMTNsaWJfZ2VuZXJhdGVkNUVycm5vM3JhdzE3aDg4MzgzNDkzOTY4NGE4YTlFtgIGbWFsbG9jtwIEZnJlZbgCEV9fd2FzbV9jYWxsX2R0b3JzuQJAX1pONGNvcmU1cGFuaWMxMHBhbmljX2luZm85UGFuaWNJbmZvN21lc3NhZ2UxN2hhMDI2YTM3Zjc1MTM1NGFjRboCQV9aTjRjb3JlNXBhbmljMTBwYW5pY19pbmZvOVBhbmljSW5mbzhsb2NhdGlvbjE3aDU5NzNiODU1YTE4N2E5ZDZFuwJEX1pONGNvcmU1cGFuaWMxMHBhbmljX2luZm85UGFuaWNJbmZvMTBjYW5fdW53aW5kMTdoMjFiZGQ3NTAyZDZlMzVlZkW8Ag5nZXRfcmVzdWx0X3B0cr0CD2dldF9yZXN1bHRfc2l6Zb4CD3JlbGVhc2Vfb2JqZWN0c78CEV9fd2FzbV9jYWxsX2N0b3JzwAIEbWFpbsECTF9aTjQzXyRMVCRsb2cuLk5vcExvZ2dlciR1MjAkYXMkdTIwJGxvZy4uTG9nJEdUJDdlbmFibGVkMTdoNzAwNDJmODk4NmE2NWU5N0XCAkVfWk4zNl8kTFQkVCR1MjAkYXMkdTIwJGNvcmUuLmFueS4uQW55JEdUJDd0eXBlX2lkMTdoZDg0ZjVlZGUwYzY3ZTAxZUXDAkVfWk4zNl8kTFQkVCR1MjAkYXMkdTIwJGNvcmUuLmFueS4uQW55JEdUJDd0eXBlX2lkMTdoMzAyYzRiYTdiZTViNzY4NUXEAkVfWk4zNl8kTFQkVCR1MjAkYXMkdTIwJGNvcmUuLmFueS4uQW55JEdUJDd0eXBlX2lkMTdoM2RmZjNmOTA0ZTRlMmJiMkXFAkVfWk4zNl8kTFQkVCR1MjAkYXMkdTIwJGNvcmUuLmFueS4uQW55JEdUJDd0eXBlX2lkMTdoNjc4M2NkNzgyNGFlZDY4NEXGAoMBX1pOM3N0ZDJpbzVpbXBsczc0XyRMVCRpbXBsJHUyMCRzdGQuLmlvLi5Xcml0ZSR1MjAkZm9yJHUyMCRhbGxvYy4udmVjLi5WZWMkTFQkdTgkQyRBJEdUJCRHVCQxN2lzX3dyaXRlX3ZlY3RvcmVkMTdoNzIzMmUyNTQwZWU5MjVjOEXHAmxfWk42NF8kTFQkc3RkLi5zeXMuLndhc2kuLnN0ZGlvLi5TdGRlcnIkdTIwJGFzJHUyMCRzdGQuLmlvLi5Xcml0ZSRHVCQxN2lzX3dyaXRlX3ZlY3RvcmVkMTdoMDY5YjFmODgzMGU4MzZkZUXIAhJfX3J1c3Rfc3RhcnRfcGFuaWPJAiVfX3dhc2lsaWJjX2luaXRpYWxpemVfZW52aXJvbl9lYWdlcmx5ygIFYWJvcnTLAkVfWk4zNl8kTFQkVCR1MjAkYXMkdTIwJGNvcmUuLmFueS4uQW55JEdUJDd0eXBlX2lkMTdoYTc4MjMzN2Y2YzZlYWNmOUXMAkpfWk40Y29yZTNwdHI0MGRyb3BfaW5fcGxhY2UkTFQkbG9nLi5TZXRMb2dnZXJFcnJvciRHVCQxN2hhOWVlYTFjZGFhNmZkNGVmRc0CkAFfWk40Y29yZTNwdHI4NWRyb3BfaW5fcGxhY2UkTFQkc3RkLi5ydC4ubGFuZ19zdGFydCRMVCQkTFAkJFJQJCRHVCQuLiR1N2IkJHU3YiRjbG9zdXJlJHU3ZCQkdTdkJCRHVCQxN2gxNWUxZWJkMGFkYzg5ZWIyRS5sbHZtLjcxMzkyNDE0MTE2NzQ4MTI3NTPOAlhfWk40Y29yZTNwdHI1NGRyb3BfaW5fcGxhY2UkTFQkJFJGJG11dCR1MjAkYWxsb2MuLnN0cmluZy4uU3RyaW5nJEdUJDE3aDI2OWU0NDFiNTA0NmVjMjlFzwJHX1pONGNvcmUzcHRyMzdkcm9wX2luX3BsYWNlJExUJGNvcmUuLmZtdC4uRXJyb3IkR1QkMTdoZGM2NzQxY2U1YjMyZGVmM0XQAmJfWk42N18kTFQkbWFyaW5lX3JzX3Nka19tYWluLi5sb2dnZXIuLldhc21Mb2dnZXIkdTIwJGFzJHUyMCRsb2cuLkxvZyRHVCQ1Zmx1c2gxN2hjNTU5OWU5YWZhMDQ3YTg3RdECQl9aTjRjb3JlM3B0cjMyZHJvcF9pbl9wbGFjZSRMVCQkUkYkJFJGJHN0ciRHVCQxN2g5YzYyM2JkNTFiNzk1YTYxRdICSF9aTjQzXyRMVCRsb2cuLk5vcExvZ2dlciR1MjAkYXMkdTIwJGxvZy4uTG9nJEdUJDNsb2cxN2g4NzI1YjgwOTY1NWJjMjEyRdMCSl9aTjQzXyRMVCRsb2cuLk5vcExvZ2dlciR1MjAkYXMkdTIwJGxvZy4uTG9nJEdUJDVmbHVzaDE3aDQ4MmNhZTIzZWMzYWZiNTRF1AJvX1pONGNvcmUzcHRyNzdkcm9wX2luX3BsYWNlJExUJHN0ZC4ucGFuaWNraW5nLi5iZWdpbl9wYW5pYy4uUGFuaWNQYXlsb2FkJExUJCRSRiRzdHIkR1QkJEdUJDE3aGIyMTFiMWMzNmZlYzM2ZThF1QI+X1pONGNvcmUzcHRyMjhkcm9wX2luX3BsYWNlJExUJCRSRiRzdHIkR1QkMTdoNjJiZTY3ZWM0ODNiMGVhZUXWAocBX1pONGNvcmUzcHRyMTAwZHJvcF9pbl9wbGFjZSRMVCQkUkYkbXV0JHUyMCRzdGQuLmlvLi5Xcml0ZS4ud3JpdGVfZm10Li5BZGFwdGVyJExUJGFsbG9jLi52ZWMuLlZlYyRMVCR1OCRHVCQkR1QkJEdUJDE3aDU4Y2JlYzFiOGUyNGE4MDZF1wI5X1pONGNvcmUzcHRyMjNkcm9wX2luX3BsYWNlJExUJHU4JEdUJDE3aDExMjIyOTU3MGNhOWEwMTZF2AIFZHVtbXnZAj1fWk40Y29yZTNwdHIyN2Ryb3BfaW5fcGxhY2UkTFQkJFJGJHU4JEdUJDE3aGE0YmY1OGZjMWU2YmI5YzhF2gKJAV9aTjRjb3JlM3B0cjEwMmRyb3BfaW5fcGxhY2UkTFQkJFJGJGNvcmUuLml0ZXIuLmFkYXB0ZXJzLi5jb3BpZWQuLkNvcGllZCRMVCRjb3JlLi5zbGljZS4uaXRlci4uSXRlciRMVCR1OCRHVCQkR1QkJEdUJDE3aGRkZDAxNjVkMjVmNWVjNTRF2wJrX1pONGNvcmUzcHRyNDdkcm9wX2luX3BsYWNlJExUJGNvcmUuLmNlbGwuLkJvcnJvd011dEVycm9yJEdUJDE3aDdjNWJmNDRiNmE2ZDBiOTZFLmxsdm0uMTU3Nzc2NDg0NDA1MzUyNzMyMTQA8ICAgAAJcHJvZHVjZXJzAghsYW5ndWFnZQEEUnVzdAAMcHJvY2Vzc2VkLWJ5AwVjbGFuZwYxNC4wLjAFcnVzdGMlMS42NC4wLW5pZ2h0bHkgKDgzMDg4MDY0MCAyMDIyLTA2LTI4KQZ3YWxydXMGMC4xOS4wAKqBgIAAHV9fbV9nZW5lcmF0ZWRfc2VjdGlvbl9fZGl2aWRleyJhc3RfdHlwZSI6IkZ1bmN0aW9uIiwic2lnbmF0dXJlIjp7Im5hbWUiOiJkaXZpZGUiLCJhcmd1bWVudHMiOlt7Im5hbWUiOiJudW0iLCJ0eSI6eyJGNjQiOiJCeVZhbHVlIn19XSwib3V0cHV0X3R5cGVzIjpbeyJGNjQiOiJCeVZhbHVlIn1dfX0AroGAgAAfX19tX2dlbmVyYXRlZF9zZWN0aW9uX19tdWx0aXBseXsiYXN0X3R5cGUiOiJGdW5jdGlvbiIsInNpZ25hdHVyZSI6eyJuYW1lIjoibXVsdGlwbHkiLCJhcmd1bWVudHMiOlt7Im5hbWUiOiJudW0iLCJ0eSI6eyJGNjQiOiJCeVZhbHVlIn19XSwib3V0cHV0X3R5cGVzIjpbeyJGNjQiOiJCeVZhbHVlIn1dfX0Ag4GAgAAcX19tX2dlbmVyYXRlZF9zZWN0aW9uX19zdGF0ZXsiYXN0X3R5cGUiOiJGdW5jdGlvbiIsInNpZ25hdHVyZSI6eyJuYW1lIjoic3RhdGUiLCJhcmd1bWVudHMiOltdLCJvdXRwdXRfdHlwZXMiOlt7IkY2NCI6IkJ5VmFsdWUifV19fQD+gICAACJfX21fZ2VuZXJhdGVkX3NlY3Rpb25fX2NsZWFyX3N0YXRleyJhc3RfdHlwZSI6IkZ1bmN0aW9uIiwic2lnbmF0dXJlIjp7Im5hbWUiOiJjbGVhcl9zdGF0ZSIsImFyZ3VtZW50cyI6W10sIm91dHB1dF90eXBlcyI6W119fQCugYCAAB9fX21fZ2VuZXJhdGVkX3NlY3Rpb25fX3N1YnRyYWN0eyJhc3RfdHlwZSI6IkZ1bmN0aW9uIiwic2lnbmF0dXJlIjp7Im5hbWUiOiJzdWJ0cmFjdCIsImFyZ3VtZW50cyI6W3sibmFtZSI6Im51bSIsInR5Ijp7IkY2NCI6IkJ5VmFsdWUifX1dLCJvdXRwdXRfdHlwZXMiOlt7IkY2NCI6IkJ5VmFsdWUifV19fQCkgYCAABpfX21fZ2VuZXJhdGVkX3NlY3Rpb25fX2FkZHsiYXN0X3R5cGUiOiJGdW5jdGlvbiIsInNpZ25hdHVyZSI6eyJuYW1lIjoiYWRkIiwiYXJndW1lbnRzIjpbeyJuYW1lIjoibnVtIiwidHkiOnsiRjY0IjoiQnlWYWx1ZSJ9fV0sIm91dHB1dF90eXBlcyI6W3siRjY0IjoiQnlWYWx1ZSJ9XX19APqAgIAAIF9fbV9nZW5lcmF0ZWRfc2VjdGlvbl9fdGVzdF9sb2dzeyJhc3RfdHlwZSI6IkZ1bmN0aW9uIiwic2lnbmF0dXJlIjp7Im5hbWUiOiJ0ZXN0X2xvZ3MiLCJhcmd1bWVudHMiOltdLCJvdXRwdXRfdHlwZXMiOltdfX0A/4KAgAAPaW50ZXJmYWNlLXR5cGVzBQYwLjI0LjEAFAABBHNpemUMAQwAAAAAAAEMAAABDAABC3Jlc3VsdF9zaXplDAAAAQpyZXN1bHRfcHRyDAAAAQNudW0JAQkAAQNudW0JAQkAAQNudW0JAQkAAQNudW0JAQkAAAEJAAABCQAAAAAAAAABA251bQkBCQABA251bQkBCQABA251bQkBCQABA251bQkBCQAAAAAAAAIHBgIAAAEGCAIAAAEHCgEBCAwBAQkOAgAAAQoQAgAAAQsSAQEMAw0IYWxsb2NhdGUAD3JlbGVhc2Vfb2JqZWN0cwEPZ2V0X3Jlc3VsdF9zaXplAg5nZXRfcmVzdWx0X3B0cgMPc2V0X3Jlc3VsdF9zaXplBA5zZXRfcmVzdWx0X3B0cgUGZGl2aWRlBwhtdWx0aXBseQkFc3RhdGULC2NsZWFyX3N0YXRlDQhzdWJ0cmFjdA8DYWRkEQl0ZXN0X2xvZ3MTBAcHBgkICwoNDA8OERATEgCcgICAABVfX2ZsdWVuY2Vfc2RrX3ZlcnNpb24wLjYuMTU="; \ No newline at end of file +/** + * Copyright 2023 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 const wasm = + "AGFzbQEAAAABmIGAgAAWYAAAYAABf2AAAXxgAX8AYAF/AX9gAX8BfmACf38AYAJ/fwF/YAN/f38AYAN/f38Bf2ADf39/AX5gBH9/f38AYAR/f39/AX9gBX9/f39/AGAFf39/f38Bf2AGf39/f39/AX9gB39/f39/f38AYAd/f39/f39/AX9gCX9/f39/f35+fgBgBH98f38Bf2ADfn9/AX9gAXwBfALRgYCAAAYEaG9zdA9sb2dfdXRmOF9zdHJpbmcACxZ3YXNpX3NuYXBzaG90X3ByZXZpZXcxCGZkX3dyaXRlAAwWd2FzaV9zbmFwc2hvdF9wcmV2aWV3MQpyYW5kb21fZ2V0AAcWd2FzaV9zbmFwc2hvdF9wcmV2aWV3MQtlbnZpcm9uX2dldAAHFndhc2lfc25hcHNob3RfcHJldmlldzERZW52aXJvbl9zaXplc19nZXQABxZ3YXNpX3NuYXBzaG90X3ByZXZpZXcxCXByb2NfZXhpdAADA9iCgIAA1gILBA0HCwgDBgkJBwsHDQcHCRMPCQ0DCwgJEwgIBw0JBwAEBgsKBwcSBwgHCw4QBhAJEQ4GBAcHBwcHBwsUBgQHCQcDBggAAwcHCQMMBgcHCAcHBwgICAYRDgEDDQsVBggIBgYVFRUEAwQDBAYGCwsLBwcHBwcHAAkHBwgEBw0LBAcHBwcDCAYGBggHBwYICAcHBwcHBwcHBwsHAgMLAAcGCQkJCwkLCQQDAwwJDAcDBwgGBgcDAAYABwcHBwcHAQQEAwgNCAsGBwYECAMIBwYGBAMDBwYGBggHBAMHBwcEAAwDAwcABwcHBwYHCwcEBAgHCQcHAwcHBwgJCQcHBwcGBwcHBwcGBgYGBgYGBggICAYGBgYJBxUVFRUDAwEGBgMDAwMAAAACAQEAAwAACAQEBAMABAQEAQEAAAcHBQUFBQQEBAAABQMDAwMDAwYDAwMDAwADAwMEhYCAgAABcAF0dAWDgICAAAEAEQaZgICAAAN/AUGAgMAAC38AQaCBwQALfwBBmIHBAAsH7oGAgAASBm1lbW9yeQIAC19faGVhcF9iYXNlAwEKX19kYXRhX2VuZAMCBl9zdGFydACpAgRtYWluAIMCCXRlc3RfbG9ncwCqAgNhZGQAnAIIc3VidHJhY3QAnQIIbXVsdGlwbHkAngIGZGl2aWRlAJ8CC2Ns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AAAgA0EGaiABQQZqLQAAOgAAIANBB2ogAUEHai0AADoAACADQQhqIQMgAUEIaiEBIARBeGoiBA0ACwsgAAuvCQEHfwJAAkAgAUH/CUsNACABQQV2IQICQAJAAkACQCAAKAIAIgNFDQAgACADQQJ0aiEEIAAgAyACakECdGohBSADQX9qIgNBJ0shBgNAIAYNBCACIANqIgdBKE8NAiAFIAQoAgA2AgAgBUF8aiEFIARBfGohBCADQX9qIgNBf0cNAAsLIAFBIEkNBCAAQQA2AgQgAUHAAE8NAQwECyAHQShB3PHAABCPAQALIABBCGpBADYCACACQQEgAkEBSxsiA0ECRg0CIABBDGpBADYCACADQQNGDQIgAEEQakEANgIAIANBBEYNAiAAQRRqQQA2AgAgA0EFRg0CIABBGGpBADYCACADQQZGDQIgAEEcakEANgIAIANBB0YNAiAAQSBqQQA2AgAgA0EIRg0CIABBJGpBADYCACADQQlGDQIgAEEoakEANgIAIANBCkYNAiAAQSxqQQA2AgAgA0ELRg0CIABBMGpBADYCACADQQxGDQIgAEE0akEANgIAIANBDUYNAiAAQThqQQA2AgAgA0EORg0CIABBPGpBADYCACADQQ9GDQIgAEHAAGpBADYCACADQRBGDQIgAEHEAGpBADYCACADQRFGDQIgAEHIAGpBADYCACADQRJGDQIgAEHMAGpBADYCACADQRNGDQIgAEHQAGpBADYCACADQRRGDQIgAEHUAGpBADYCACADQRVGDQIgAEHYAGpBADYCACADQRZGDQIgAEHcAGpBADYCACADQRdGDQIgAEHgAGpBADYCACADQRhGDQIgAEHkAGpBADYCACADQRlGDQIgAEHoAGpBADYCACADQRpGDQIgAEHsAGpBADYCACADQRtGDQIgAEHwAGpBADYCACADQRxGDQIgAEH0AGpBADYCACADQR1GDQIgAEH4AGpBADYCACADQR5GDQIgAEH8AGpBADYCACADQR9GDQIgAEGAAWpBADYCACADQSBGDQIgAEGEAWpBADYCACADQSFGDQIgAEGIAWpBADYCACADQSJGDQIgAEGMAWpBADYCACADQSNGDQIgAEGQAWpBADYCACADQSRGDQIgAEGUAWpBADYCACADQSVGDQIgAEGYAWpBADYCACADQSZGDQIgAEGc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NgIAIAFBIGpBFGpBAzYCACABQgM3AiQgAUH8g8AANgIgIAFBBDYCPCABIAFBOGo2AjAgASABQRhqNgJIIAEgAUEIajYCQCABIAFBEGo2AjggAUEgakEDQZSEwABBACABEGEgASsDGCECCyABQdAAaiQAIAILvwEBA38jAEEgayICJAACQCABQQFqIgMgAUkNACAAQQRqKAIAIgRBAXQiASADIAEgA0sbIgFBCCABQQhLGyEBAkACQCAEDQBBACEDDAELIAIgBDYCFCACIAAoAgA2AhBBASEDCyACIAM2AhggAiABQQEgAkEQahB1AkAgAigCAEUNACACQQhqKAIAIgBFDQEgAigCBCAAEI4CAAsgAigCBCEDIABBBGogATYCACAAIAM2AgAgAkEgaiQADwsQuwEAC78BAQJ/IwBBIGsiAyQAAkAgASACaiICIAFJDQAgAEEEaigCACIEQQF0IgEgAiABIAJLGyIBQQggAUEISxshAQJAAkAgBA0AQQAhAgwBCyADIAQ2AhQgAyAAKAIANgIQQQEhAgsgAyACNgIYIAMgAUEBIANBEGoQdQJAIAMoAgBFDQAgA0EIaigCACIARQ0BIAMoAgQgABCOAgALIAMoAgQhAiAAQQRqIAE2AgAgACACNgIAIANBIGokAA8LELsBAAu/AQECfyMAQSBrIgMkAAJAIAEgAmoiAiABSQ0AIABBBGooAgAiBEEBdCIBIAIgASACSxsiAUEIIAFBCEsbIQECQAJAIAQNAEEAIQIMAQsgAyAENgIUIAMgACgCADYCEEEBIQILIAMgAjYCGCADIAFBASADQRBqEHMCQCADKAIARQ0AIANBCGooAgAiAEUNASADKAIEIAAQjgIACyADKAIEIQIgAEEEaiABNgIAIAAgAjYCACADQSBqJAAPCxC7AQALvwEBA38jAEEgayICJAACQCABQQFqIgMgAUkNACAAQQRqKAIAIgRBAXQiASADIAEgA0sbIgFBCCABQQhLGyEBAkACQCAEDQBBACEDDAELIAIgBDYCFCACIAAoAgA2AhBBASEDCyACIAM2AhggAiABQQEgAkEQahBzAkAgAigCAEUNACACQQhqKAIAIgBFDQEgAigCBCAAEI4CAAsgAigCBCEDIABBBGogATYCACAAIAM2AgAgAkEgaiQADwsQuwEAC78BAQN/IwBBIGsi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IgBBCGooAgAhAyAAKAIAIQAgAiABEMwBAkAgA0UNAANAIAIgADYCDCACIAJBDGpBnMDAABD/ARogAEEBaiEAIANBf2oiAw0ACwsgAhDPASEAIAJBEGokACAAC3QBAX8jAEEgayIBJAAgAUEQakIANwMAIAFCADcDCCABIAFBCGpBEBDdAQJAIAEvAQBFDQAgASABLwECOwEeQaiiwABBEiABQR5qQdSMwABB3KLAABCDAQALIAAgASkDEDcDCCAAIAEpAwg3AwAgAUEgaiQAC20BAX8jAEEwayIDJAAgAyABNgIEIAMgADYCACADQRxqQQI2AgAgA0EsakEiNgIAIANCAzcCDCADQfjBwAA2AgggA0EiNgIkIAMgA0EgajYCGCADIANBBGo2AiggAyADNgIgIANBCGogAhC8AQALcAEBfyMAQTBrIgIkACACIAE2AgQgAiAANgIAIAJBHGpBAjYCACACQSxqQSI2AgAgAkICNwIMIAJB0N/AADYCCCACQSI2AiQgAiACQSBqNgIYIAIgAkEEajYCKCACIAI2AiAgAkEIakGA4MAAELwBAAtwAQF/IwBBMGsiAiQAIAIgATYCBCACIAA2AgAgAkEcakECNgIAIAJBLGpBIjYCACACQgI3AgwgAkGg4MAANgIIIAJBIjYCJCACIAJBIGo2AhggAiACQQRqNgIoIAIgAjYCICACQQhqQbDgwAAQvAEAC3ABAX8jAEEwayICJAAgAiABNgIEIAIgADYCACACQRxqQQI2AgAgAkEsakEiNgIAIAJCAjcCDCACQeTgwAA2AgggAkEiNgIkIAIgAkEgajYCGCACIAJBBGo2AiggAiACNgIgIAJBCGpB9ODAABC8AQALbQEBfyMAQTBrIgMkACADIAE2AgQgAyAANgIAIANBHGpBAjYCACADQSxqQSI2AgAgA0ICNwIMIANB8NnAADYCCCADQSI2AiQgAyADQSBqNgIYIAMgAzYCKCADIANBBGo2AiAgA0EIaiACELwBAAtkAQJ/IwBBIGsiAiQAIAFBHGooAgAhAyABKAIYIQEgAkEIakEQaiAAQRBqKQIANwMAIAJBCGpBCGogAEEIaikCADcDACACIAApAgA3AwggASADIAJBCGoQJCEAIAJBIGokACAAC2QBAn8jAEEgayICJAAgAEEcaigCACEDIAAoAhghACACQQhqQRBqIAFBEGopAgA3AwAgAkEIakEIaiABQQhqKQIANwMAIAIgASkCADcDCCAAIAMgAkEIahAkIQEgAkEgaiQAIAELbgEBfyMAQSBrIgIkACACQYCSwAA2AgQgAiAANgIAIAJBCGpBEGogAUEQaikCADcDACACQQhqQQhqIAFBCGopAgA3AwAgAiABKQIANwMIQQAgAkGwjcAAIAJBBGpBsI3AACACQQhqQdChwAAQNQALawEBfyMAQSBrIgMkACADQeCWwAA2AgQgAyAANgIAIANBCGpBEGogAUEQaikCADcDACADQQhqQQhqIAFBCGopAgA3AwAgAyABKQIANwMIQQAgA0GgjcAAIANBBGpBoI3AACADQQhqIAIQNQALawEBfyMAQSBrIgMkACADIAE2AgQgAyAANgIAIANBCGpBEGogAkEQaikCADcDACADQQhqQQhqIAJBCGopAgA3AwAgAyACKQIANwMIQQAgA0H82sAAIANBBGpB/NrAACADQQhqQYjDwAAQNQALYwEBfyMAQSBrIgIkACACIAAoAgA2AgQgAkEIakEQaiABQRBqKQIANwMAIAJBCGpBCGogAUEIaikCADcDACACIAEpAgA3AwggAkEEakGEh8AAIAJBCGoQJCEBIAJBIGokACABC2MBAX8jAEEgayICJAAgAiAAKAIANgIEIAJBCGpBEGogAUEQaikCADcDACACQQhqQQhqIAFBCGopAgA3AwAgAiABKQIANwMIIAJBBGpBsIvAACACQQhqECQhASACQSBqJAAgAQtjAQF/IwBBIGsiAiQAIAIgACgCADYCBCACQQhqQRBqIAFBEGopAgA3AwAgAkEIakEIaiABQQhqKQIANwMAIAIgASkCADcDCCACQQRqQeCLwAAgAkEIahAkIQEgAkEgaiQAIAELYwEBfyMAQSBrIgIkACACIAAoAgA2AgQgAkEIakEQaiABQRBqKQIANwMAIAJBCGpBCGogAUEIaikCADcDACACIAEpAgA3AwggAkEEakHIi8AAIAJBCGoQJCEBIAJBIGokACABC2MBAX8jAEEgayICJAAgAiAAKAIANgIEIAJBCGpBEGogAUEQaikCADcDACACQQhqQQhqIAFBCGopAgA3AwAgAiABKQIANwMIIAJBBGpBuN7AACACQQhqECQhASACQSBqJAAgAQtgAQF/IwBBIGsiAiQAIAIgADYCBCACQQhqQRBqIAFBEGopAgA3AwAgAkEIakEIaiABQQhqKQIANwMAIAIgASkCADcDCCACQQRqQYSHwAAgAkEIahAkIQEgAkEgaiQAIAELewEBf0EAQQAoAvz7QCICQQEgAhs2Avz7QAJAAkACQCACDgIAAQILQQAgATYC3PtAQQAgADYC2PtAQQBBAjYC/PtAQQAPCwNAQQAoAvz7QEEBRg0ACwsgACABKAIAEQMAAkAgASgCBCICRQ0AIAAgAiABKAIIEPUBC0EBC2ABAX8jAEEgayICJAAgAiAANgIEIAJBCGpBEGogAUEQaikCADcDACACQQhqQQhqIAFBCGopAgA3AwAgAiABKQIANwMIIAJBBGpBsIvAACACQQhqECQhASACQSBqJAAgAQtgAQF/IwBBIGsiAiQAIAIgADYCBCACQQhqQRBqIAFBEGopAgA3AwAgAkEIakEIaiABQQhqKQIANwMAIAIgASkCADcDCCACQQRqQciLwAAgAkEIahAkIQEgAkEgaiQAIAELZwEBfyMAQRBrIgQkACAEQQIgAiADEKsBAkACQCAELwEADQAgACAEKAIENgIEQQAhAgwBCyAEIAQvAQI7AQ4gACAEQQ5qELUCrUL//wODQiCGNwIEQQEhAgsgACACNgIAIARBEGokAAtgAQF/IwBBIGsiAiQAIAIgADYCBCACQQhqQRBqIAFBEGopAgA3AwAgAkEIakEIaiABQQhqKQIANwMAIAIgASkCADcDCCACQQRqQbjewAAgAkEIahAkIQEgAkEgaiQAIAELZwIBfwF8IwBBIGsiACQAAkBBACgCgPxAQQVJDQAgAEEcakEANgIAIABB2IDAADYCGCAAQgE3AgwgAEGAhcAANgIIIABBCGpBBUGIhcAAQQAgABBhC0EAKwPw+0AhASAAQSBqJAAgAQtaAQF/AkAgACgCECIBRQ0AIAFBADoAACAAQRRqKAIAIgFFDQAgACgCECABQQEQ9QELAkAgAEF/Rg0AIAAgACgCBCIBQX9qNgIEIAFBAUcNACAAQSBBCBD1AQsLVwECfwJAIAFBBGooAgAgAUEIaiIEKAIAIgVrIANPDQAgASAFIAMQZiAEKAIAIQULIAEoAgAgBWogAiADEA4aIAAgAzYCBCAEIAUgA2o2AgAgAEEANgIAC2MBAX8jAEEgayIAJAACQEEAKAKA/EBBBUkNACAAQRxqQQA2AgAgAEHYgMAANgIYIABCATcCDCAAQciEwAA2AgggAEEIakEFQdCEwABBACAAEGELQQBCADcD8PtAIABBIGokAAthAQF/IwBBEGsiAiQAAkACQCAAKAIADQAgAiAANgIIIAFBx47AAEECIAJBCGpB3I7AABBeIQAMAQsgAiAANgIMIAFBxI7AAEEDIAJBDGpBzI7AABBeIQALIAJBEGokACAAC1wBAn8gASgCACECIAFBADYCAAJAAkAgAkUNACABKAIEIQNBCEEEEOwBIgFFDQEgASADNgIEIAEgAjYCACAAQZyLwAA2AgQgACABNgIADwsQsgIAC0EIQQQQjgIAC1MBAn8CQCAAKAIAKAIAIgNBBGooAgAgA0EIaiIEKAIAIgBrIAJPDQAgAyAAIAIQZiAEKAIAIQALIAMoAgAgAGogASACEA4aIAQgACACajYCAEEAC1ABAn8CQCAAKAIAIgNBBGooAgAgA0EIaiIEKAIAIgBrIAJPDQAgAyAAIAIQZSAEKAIAIQALIAMoAgAgAGogASACEA4aIAQgACACajYCAEEAC1ABAn8CQCAAKAIAIgNBBGooAgAgA0EIaiIEKAIAIgBrIAJPDQAgAyAAIAIQZiAEKAIAIQALIAMoAgAgAGogASACEA4aIAQgACACajYCAEEAC1ABAn8CQCABQQRqKAIAIAFBCGoiBCgCACIFayADTw0AIAEgBSADEGYgBCgCACEFCyABKAIAIAVqIAIgAxAOGiAAQQQ6AAAgBCAFIANqNgIAC1ABAn8CQCAAKAIAIgNBBGooAgAgA0EIaiIEKAIAIgBrIAJPDQAgAyAAIAIQZiAEKAIAIQALIAMoAgAgAGogASACEA4aIAQgACACajYCAEEAC1ABAX8jAEEQayIEJAACQAJAIAEgAiADIARBDGoQASIBDQAgAEEEaiAEKAIMNgIAQQAhAQwBCyAAIAE7AQJBASEBCyAAIAE7AQAgBEEQaiQAC0sBAn8CQCAAQQRqKAIAIABBCGoiAygCACIEayACTw0AIAAgBCACEGUgAygCACEECyAAKAIAIARqIAEgAhAOGiADIAQgAmo2AgBBAAtJAAJAIAANAD8AQRB0DwsCQCAAQf//A3ENACAAQX9MDQACQCAAQRB2QAAiAEF/Rw0AQQBBMDYC8IBBQX8PCyAAQRB0DwsQygIAC1YBA38CQCAALQAAQQNHDQAgAEEEaigCACIBKAIAIAEoAgQoAgARAwACQCABKAIEIgIoAgQiA0UNACABKAIAIAMgAigCCBD1AQsgACgCBEEMQQQQ9QELC1YBA38CQCAALQAEQQNHDQAgAEEIaigCACIBKAIAIAEoAgQoAgARAwACQCABKAIEIgIoAgQiA0UNACABKAIAIAMgAigCCBD1AQsgACgCCEEMQQQQ9QELC0cAAkACQCACQQhLDQAgAiADTQ0BCwJAIAIgAxDeASICDQBBAA8LIAIgACADIAEgASADSxsQDiEDIAAQtwIgAw8LIAAgAxAUC0kBA39BACEDAkAgAkUNAAJAA0AgAC0AACIEIAEtAAAiBUcNASABQQFqIQEgAEEBaiEAIAJBf2oiAg0ADAILCyAEIAVrIQMLIAMLVAEBfwJAAkACQCABQYCAxABGDQBBASEEIAAoAhggASAAQRxqKAIAKAIQEQcADQELIAINAUEAIQQLIAQPCyAAKAIYIAIgAyAAQRxqKAIAKAIMEQkAC1MBAX9BACgC7PtAIQICQAJAIAANACACENYBIgANAUEAQTA2AvCAQUEADwsCQCACEGxBAWogAUsNACAAIAIQ+wEPC0EAIQBBAEHEADYC8IBBCyAAC0kBAX8CQCAAQRBqKAIAIgFFDQAgACgCDCABQQEQ9QELAkAgAEF/Rg0AIAAgACgCBCIBQX9qNgIEIAFBAUcNACAAQRhBBBD1AQsLSAEBfyMAQRBrIgIkACACIAA2AgggAiAAQQRqNgIMIAFBkMLAAEEIIAJBCGpBmMLAACACQQxqQajCwAAQXSEAIAJBEGokACAAC0gBAX8jAEEgayIDJAAgA0EUakEANgIAIANBuMLAADYCECADQgE3AgQgAyABNgIcIAMgADYCGCADIANBGGo2AgAgAyACELwBAAtJAQF/IwBBIGsiAiQAIAJBFGpBATYCACACQgE3AgQgAkH02sAANgIAIAJB4QA2AhwgAiAANgIYIAIgAkEYajYCECACIAEQvAEAC0cBAn8gASgCBCECIAEoAgAhAwJAQQhBBBDsASIBDQBBCEEEEI4CAAsgASACNgIEIAEgAzYCACAAQZSdwAA2AgQgACABNgIACz8CAX8BfCABKAIAQQFxIQIgACsDACEDAkAgASgCEEEBRw0AIAEgAyACIAFBFGooAgAQFw8LIAEgAyACQQAQHwtBAQN/IwBBEGsiASQAIAAQugJB5JzAABDjASECIAAQuQIQ5AEhAyABIAI2AgggASAANgIEIAEgAzYCACABEOgBAAtAAQF/IwBBIGsiACQAIABBHGpBADYCACAAQazAwAA2AhggAEIBNwIMIABBhMHAADYCCCAAQQhqQYzBwAAQvAEACz8BAX8jAEEgayICJAAgAkEBOgAYIAIgATYCFCACIAA2AhAgAkHE2sAANgIMIAJBuMLAADYCCCACQQhqELoBAAs5AQF/IwBBMGsiACQAIAAQUAJAIAAQJ0UNAEGAgMAAQSsgAEGsgMAAQciAwAAQgwEACyAAQTBqJAALNAEBfyMAQRBrIgIkACACIAA2AgwgAUHoisAAQQ4gAkEMakH4isAAEF4hASACQRBqJAAgAQsyACAAKAIAIQACQCABEPMBDQACQCABEPQBDQAgACABEP0BDwsgACABEHsPCyAAIAEQegsxAQF/IwBBEGsiAiQAIAJBCGogAUHwj8AAQQsQywEgAkEIahCFASEBIAJBEGokACABCzABAX8jAEEQayICJAAgAkEIaiABQYSXwABBCxDLASACQQhqEHAhASACQRBqJAAgAQsyACAAKAIAIQACQCABEPMBDQACQCABEPQBDQAgACABEP0BDwsgACABEHsPCyAAIAEQegsyACAAKAIAIQACQCABEPMBDQACQCABEPQBDQAgACABEIECDwsgACABEHgPCyAAIAEQdwsvAQJ/IwBBEGsiACQAIABBATYCDCAAQQxqQaSFwABBAEEAEFEhASAAQRBqJAAgAQsvAQF/IwBBEGsiASQAIAAoAgAQsAIgAUEAOgAPIAFBD2oQtAIhACABQRBqJAAgAAsvAQF/IwBBEGsiASQAIAAoAgAQsAIgAUEAOgAPIAFBD2oQtAIhACABQRBqJAAgAAs5AQF/IAAoAgAhAQJAIAAtAAQNAEEAKALI/EBB/////wdxRQ0AEKICDQAgAUEBOgABCyABQQA6AAALLgEBfyMAQRBrIgMkACADIAE2AgwgAyAANgIIIANBCGpBiIvAAEEAIAJBARAaAAsvAQF/IwBBEGsiBSQAIAUgAzYCDCAFIAI2AgggBSABNgIEIAUgADYCACAFEOABAAs0ACAAQQM6ACAgAEKAgICAgAQ3AgAgACABNgIYIABBADYCECAAQQA2AgggAEEcaiACNgIACzAAIAEoAhggAiADIAFBHGooAgAoAgwRCQAhAiAAQQA6AAUgACACOgAEIAAgATYCAAs1AQF/IAEoAhhB6tzAAEEBIAFBHGooAgAoAgwRCQAhAiAAQQA6AAUgACACOgAEIAAgATYCAAsrAAJAAkAgARDzAQ0AIAEQ9AENASAAIAEQggIPCyAAIAEQdg8LIAAgARB5CycBAX8jAEEQayICJAAgAiAAKAIANgIMIAJBDGogARA5IAJBEGokAAsyAQF/QQEhAQJAIAAtAAQNACAAKAIAIgAoAhhB69zAAEEBIAAoAhwoAgwRCQAhAQsgAQsoAQF/IwBBEGsiAyQAIAMgAjYCCCADIAE2AgQgAyAANgIAIAMQ5wEACycBAX8CQCAAKAIEIgFFDQAgAEEIaigCACIARQ0AIAEgAEEBEPUBCwsmAQF/IwBBEGsiAyQAIAMgATYCDCADIAA2AgggA0EIaiACELcBAAsjAAJAAkAgAUEISw0AIAEgAE0NAQsgASAAEN4BDwsgABC2AgsqACAAIAEuAQBBAnQiAUHItsAAaigCADYCBCAAIAFB/LjAAGooAgA2AgALKgAgACABLgEAQQJ0IgFBsLvAAGooAgA2AgQgACABQeS9wABqKAIANgIACyMBAn8CQCAAEGxBAWoiARC2AiICRQ0AIAIgACABEA4aCyACCyABAX8CQCAAQQRqKAIAIgFFDQAgACgCACABQQEQ9QELCyABAX8CQCAAQQRqKAIAIgFFDQAgACgCACABQQEQ9QELCyMAAkAgAC0AAA0AIAFBlN/AAEEFEBYPCyABQZDfwABBBBAWCyEAAkAgASgCAA0AELICAAsgAEGci8AANgIEIAAgATYCAAscACAAIAEpAgA3AgAgAEEIaiABQQhqKAIANgIACx0BAX8gACABQQAoArj8QCICQSMgAhsRBgAQsgIACxkAIAAgASACEAIiATsBAiAAIAFBAEc7AQALFwACQCAAQRBLDQAgARAHDwsgACABEDALJAEBf0GIgcEAIQECQEEAKAKEgUENAEGEgcEAIAAQTiEBCyABCxoAIAAoAgAgACgCBCAAKAIIIAAoAgwQ8QEACxwAIAEoAhhBsNnAAEEOIAFBHGooAgAoAgwRCQALHAAgASgCGEG+8sAAQQUgAUEcaigCACgCDBEJAAsYAAJAIAANAEGYjMAAQSsgARC2AQALIAALGwACQCAADQBBmIzAAEErQfScwAAQtgEACyAACxUBAX8CQBDEASIARQ0AIAAQ+gEACwsUAQF/IAAgASACIAMQsAEhBCAEDwsVACAAKAIAIAAoAgQgACgCCBDIAQALFQAgACgCACAAKAIEIAAoAggQgAEACxUAIAEgACgCACIAKAIAIAAoAgQQFgsTAAJAQQAoAvSAQUF/Rw0AEHwLCxQAIAAoAgAgASAAKAIEKAIMEQcACxABAX8gACABENMBIQIgAg8LEQAgACgCACAAKAIEIAEQmgILDgAgACgCACABED8aQQALEwAgAEGUncAANgIEIAAgATYCAAsQACAAKAIAIAAoAgQgARAZCw0AIAAgASACIAMQEQALEAAgASAAKAIAIAAoAgQQFgsNACAALQAAQRBxQQR2Cw0AIAAtAABBIHFBBXYLDAAgACABIAIQswIPCw8AIAAoAgAoAgAgARC1AQsNACAAKAIAIAEgAhBPCw0AIAAgARADQf//A3ELDQAgACABEARB//8DcQsOABDYAhDYAiAAEKYCAAsLACAAIAEQPhogAAsNACAAKAIAGgN/DAALCw0AIAA1AgBBASABEEILDAAgACABIAIQ0gEACwwAIAAgASACEEogAAsNACAAKAIAIAEgAhAeCw0AIAAxAABBASABEEILDQAgADMBAEEBIAEQQgsPABC/AiAAIAEQwAIQuAILDgAQvwIgACABECsQuAILCgAgACABEI8CDwsNACABQayLwABBAhAWCw0AIAFBrovAAEECEBYLDAAgACgCACABENkBCwsAIAAoAgAgARB/CwsAIAAoAgAgARBHCwoAIAAgARCMAgALCgAgACABEI0CAAsKACAAIAEQhQIACwoAIAAgARCLAgALCgAgACABENwBAAsKACAAIAEQjAEACwoAIAAgARCNAQALCgAgACABEI4BAAsKACAAIAEQlwIACwoAIAAgARCYAgALCgAgACABEJYCAAsKACAAIAEQkgIACwoAIAAgARCQAgALCgAgACABEJECAAsMACAAIAEpAgA3AwALCgAgAiAAIAEQFgsLACAAKAIAIAEQLAsMABC/AiAAEGkQuAILDAAQvwIgABBqELgC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IGdlbmVyYXRlIHVuaXF1ZSB0aHJlYWQgSUQ6IGJpdHNwYWNlIGV4aGF1c3RlZACICBAANwAAAFkIEAAdAAAAEwQAABEAAABZCBAAHQAAABkEAAAqAAAAUlVTVF9CQUNLVFJBQ0UAAPgFEAAAAAAAAGZhaWxlZCB0byB3cml0ZSB0aGUgYnVmZmVyZWQgZGF0YQAAAQkQACEAAAAXAAAAbGlicmFyeS9zdGQvc3JjL2lvL2J1ZmZlcmVkL2J1ZndyaXRlci5yczAJEAAoAAAAjQAAABIAAABmYWlsZWQgdG8gd3JpdGUgd2hvbGUgYnVmZmVyaAkQABwAAAAXAAAAbGlicmFyeS9zdGQvc3JjL2lvL3N0ZGlvLnJzAJAJEAAbAAAAbgIAABMAAABsaWJyYXJ5L3N0ZC9zcmMvaW8vbW9kLnJzAAAAvAkQABkAAAAaBQAAFgAAAGFkdmFuY2luZyBpbyBzbGljZXMgYmV5b25kIHRoZWlyIGxlbmd0aADoCRAAJwAAALwJEAAZAAAAHAUAAA0AAAC8CRAAGQAAAAMGAAAhAAAAZm9ybWF0dGVyIGVycm9yADgKEAAPAAAAKAAAADsAAAAMAAAABAAAADwAAAA9AAAAPgAAADsAAAAMAAAABAAAAD8AAABAAAAAQQAAAGxpYnJhcnkvc3RkL3NyYy9wYW5pYy5yc4QKEAAYAAAA8AAAABIAAAAoAAAABAAAAAQAAABCAAAAQwAAAGxpYnJhcnkvc3RkL3NyYy9zeW5jL29uY2UucnPAChAAHAAAABQBAAAyAAAAYXNzZXJ0aW9uIGZhaWxlZDogc3RhdGVfYW5kX3F1ZXVlLmFkZHIoKSAmIFNUQVRFX01BU0sgPT0gUlVOTklOR09uY2UgaW5zdGFuY2UgaGFzIHByZXZpb3VzbHkgYmVlbiBwb2lzb25lZAAALAsQACoAAAACAAAAwAoQABwAAAD/AQAACQAAAMAKEAAcAAAADAIAADUAAABQb2lzb25FcnJvcnN0YWNrIGJhY2t0cmFjZToKjwsQABEAAABub3RlOiBTb21lIGRldGFpbHMgYXJlIG9taXR0ZWQsIHJ1biB3aXRoIGBSVVNUX0JBQ0tUUkFDRT1mdWxsYCBmb3IgYSB2ZXJib3NlIGJhY2t0cmFjZS4KqAsQAFgAAABsb2NrIGNvdW50IG92ZXJmbG93IGluIHJlZW50cmFudCBtdXRleGxpYnJhcnkvc3RkL3NyYy9zeXNfY29tbW9uL3JlbXV0ZXgucnMALgwQACUAAACnAAAADgAAAGxpYnJhcnkvc3RkL3NyYy9zeXNfY29tbW9uL3RocmVhZF9pbmZvLnJzAAAAZAwQACkAAAAWAAAAMwAAAGQMEAApAAAAKwAAACsAAABhc3NlcnRpb24gZmFpbGVkOiB0aHJlYWRfaW5mby5pc19ub25lKCkAsAwQACcAAABtZW1vcnkgYWxsb2NhdGlvbiBvZiAgYnl0ZXMgZmFpbGVkCgDgDBAAFQAAAPUMEAAOAAAAbGlicmFyeS9zdGQvc3JjL2FsbG9jLnJzFA0QABgAAABSAQAACQAAAGxpYnJhcnkvc3RkL3NyYy9wYW5pY2tpbmcucnM8DRAAHAAAABEBAAAkAAAAQm94PGR5biBBbnk+PHVubmFtZWQ+AAAAKAAAAAAAAAABAAAARAAAAEUAAABGAAAARwAAAEgAAABJAAAASgAAAEsAAAAMAAAABAAAAEwAAABNAAAATgAAAE8AAABQAAAAUQAAAFIAAAB0aHJlYWQgJycgcGFuaWNrZWQgYXQgJycsIAAA0A0QAAgAAADYDRAADwAAAOcNEAADAAAAhQcQAAEAAABub3RlOiBydW4gd2l0aCBgUlVTVF9CQUNLVFJBQ0U9MWAgZW52aXJvbm1lbnQgdmFyaWFibGUgdG8gZGlzcGxheSBhIGJhY2t0cmFjZQoAAAwOEABOAAAAPA0QABwAAABGAgAAHwAAADwNEAAcAAAARwIAAB4AAABLAAAADAAAAAQAAABTAAAAKAAAAAgAAAAEAAAAVAAAAFUAAAAQAAAABAAAAFYAAABXAAAAKAAAAAgAAAAEAAAAWAAAAFkAAAB0aHJlYWQgcGFuaWNrZWQgd2hpbGUgcHJvY2Vzc2luZyBwYW5pYy4gYWJvcnRpbmcuCgAAzA4QADIAAAAKcGFuaWNrZWQgYWZ0ZXIgcGFuaWM6OmFsd2F5c19hYm9ydCgpLCBhYm9ydGluZy4KAAAA+AUQAAAAAAAIDxAAMQAAAHRocmVhZCBwYW5pY2tlZCB3aGlsZSBwYW5pY2tpbmcuIGFib3J0aW5nLgoATA8QACsAAABmYWlsZWQgdG8gaW5pdGlhdGUgcGFuaWMsIGVycm9yIIAPEAAgAAAAYWR2YW5jaW5nIElvU2xpY2UgYmV5b25kIGl0cyBsZW5ndGgAqA8QACMAAABsaWJyYXJ5L3N0ZC9zcmMvc3lzL3dhc2kvaW8ucnMAANQPEAAeAAAAFgAAAA0AAABjb25kdmFyIHdhaXQgbm90IHN1cHBvcnRlZAAABBAQABoAAABsaWJyYXJ5L3N0ZC9zcmMvc3lzL3dhc2kvLi4vdW5zdXBwb3J0ZWQvbG9ja3MvY29uZHZhci5ycygQEAA4AAAAFQAAAAkAAABjYW5ub3QgcmVjdXJzaXZlbHkgYWNxdWlyZSBtdXRleHAQEAAgAAAAbGlicmFyeS9zdGQvc3JjL3N5cy93YXNpLy4uL3Vuc3VwcG9ydGVkL2xvY2tzL211dGV4LnJzAACYEBAANgAAABgAAAAJAAAAcndsb2NrIGxvY2tlZCBmb3Igd3JpdGluZwAAAOAQEAAZAAAACAAOAA8APwACAEAANQANAAQAAwAsABsAHABJABQABgA0ADAAcmFuZG9tX2dldCBmYWlsdXJlbGlicmFyeS9zdGQvc3JjL3N5cy93YXNpL21vZC5ycwAAADoREAAfAAAAXQAAACUAAABsaWJyYXJ5L3N0ZC9zcmMvc3lzX2NvbW1vbi90aHJlYWRfcGFya2VyL2dlbmVyaWMucnMAbBEQADMAAAAnAAAAJgAAAGluY29uc2lzdGVudCBwYXJrIHN0YXRlALAREAAXAAAAbBEQADMAAAA1AAAAFwAAAHBhcmsgc3RhdGUgY2hhbmdlZCB1bmV4cGVjdGVkbHkA4BEQAB8AAABsERAAMwAAADIAAAARAAAAaW5jb25zaXN0ZW50IHN0YXRlIGluIHVucGFyaxgSEAAcAAAAbBEQADMAAABsAAAAEgAAAGwREAAzAAAAegAAAB8AAABjb2RlbmFtZVoAAAAIAAAABAAAAFsAAABtZXNzYWdlTk9UQ0FQQUJMRVhERVZUWFRCU1lUSU1FRE9VVFNUQUxFU1JDSFNQSVBFUk9GU1JBTkdFUFJPVE9UWVBFUFJPVE9OT1NVUFBPUlRQUk9UT1BJUEVQRVJNT1dORVJERUFET1ZFUkZMT1dOWElPTk9UVFlOT1RTVVBOT1RTT0NLTk9UUkVDT1ZFUkFCTEVOT1RFTVBUWU5PVERJUk5PVENPTk5OT1NZU05PU1BDTk9QUk9UT09QVE5PTVNHTk9NRU1OT0xJTktOT0xDS05PRVhFQ05PRU5UTk9ERVZOT0JVRlNORklMRU5FVFVOUkVBQ0hORVRSRVNFVE5FVERPV05OQU1FVE9PTE9OR01VTFRJSE9QTVNHU0laRU1MSU5LTUZJTEVMT09QSVNESVJJU0NPTk5JT0lOVkFMSU5UUklOUFJPR1JFU1NJTFNFUUlEUk1IT1NUVU5SRUFDSEZCSUdGQVVMVEVYSVNURFFVT1RET01ERVNUQUREUlJFUURFQURMS0NPTk5SRVNFVENPTk5SRUZVU0VEQ09OTkFCT1JURURDSElMRENBTkNFTEVEQlVTWUJBRE1TR0JBREZBTFJFQURZQUdBSU5BRk5PU1VQUE9SVEFERFJOT1RBVkFJTEFERFJJTlVTRUFDQ0VTMkJJR1NVQ0NFU1NFeHRlbnNpb246IENhcGFiaWxpdGllcyBpbnN1ZmZpY2llbnQuQ3Jvc3MtZGV2aWNlIGxpbmsuVGV4dCBmaWxlIGJ1c3kuQ29ubmVjdGlvbiB0aW1lZCBvdXQuUmVzZXJ2ZWQuTm8gc3VjaCBwcm9jZXNzLkludmFsaWQgc2Vlay5SZWFkLW9ubHkgZmlsZSBzeXN0ZW0uUmVzdWx0IHRvbyBsYXJnZS5Qcm90b2NvbCB3cm9uZyB0eXBlIGZvciBzb2NrZXQuUHJvdG9jb2wgbm90IHN1cHBvcnRlZC5Qcm90b2NvbCBlcnJvci5Ccm9rZW4gcGlwZS5PcGVyYXRpb24gbm90IHBlcm1pdHRlZC5QcmV2aW91cyBvd25lciBkaWVkLlZhbHVlIHRvbyBsYXJnZSB0byBiZSBzdG9yZWQgaW4gZGF0YSB0eXBlLk5vIHN1Y2ggZGV2aWNlIG9yIGFkZHJlc3MuSW5hcHByb3ByaWF0ZSBJL08gY29udHJvbCBvcGVyYXRpb24uTm90IHN1cHBvcnRlZCwgb3Igb3BlcmF0aW9uIG5vdCBzdXBwb3J0ZWQgb24gc29ja2V0Lk5vdCBhIHNvY2tldC5TdGF0ZSBub3QgcmVjb3ZlcmFibGUuRGlyZWN0b3J5IG5vdCBlbXB0eS5Ob3QgYSBkaXJlY3Rvcnkgb3IgYSBzeW1ib2xpYyBsaW5rIHRvIGEgZGlyZWN0b3J5LlRoZSBzb2NrZXQgaXMgbm90IGNvbm5lY3RlZC5GdW5jdGlvbiBub3Qgc3VwcG9ydGVkLk5vIHNwYWNlIGxlZnQgb24gZGV2aWNlLlByb3RvY29sIG5vdCBhdmFpbGFibGUuTm8gbWVzc2FnZSBvZiB0aGUgZGVzaXJlZCB0eXBlLk5vdCBlbm91Z2ggc3BhY2UuTm8gbG9ja3MgYXZhaWxhYmxlLkV4ZWN1dGFibGUgZmlsZSBmb3JtYXQgZXJyb3IuTm8gc3VjaCBmaWxlIG9yIGRpcmVjdG9yeS5ObyBzdWNoIGRldmljZS5ObyBidWZmZXIgc3BhY2UgYXZhaWxhYmxlLlRvbyBtYW55IGZpbGVzIG9wZW4gaW4gc3lzdGVtLk5ldHdvcmsgdW5yZWFjaGFibGUuQ29ubmVjdGlvbiBhYm9ydGVkIGJ5IG5ldHdvcmsuTmV0d29yayBpcyBkb3duLkZpbGVuYW1lIHRvbyBsb25nLk1lc3NhZ2UgdG9vIGxhcmdlLlRvbyBtYW55IGxpbmtzLkZpbGUgZGVzY3JpcHRvciB2YWx1ZSB0b28gbGFyZ2UuVG9vIG1hbnkgbGV2ZWxzIG9mIHN5bWJvbGljIGxpbmtzLklzIGEgZGlyZWN0b3J5LlNvY2tldCBpcyBjb25uZWN0ZWQuSS9PIGVycm9yLkludmFsaWQgYXJndW1lbnQuSW50ZXJydXB0ZWQgZnVuY3Rpb24uT3BlcmF0aW9uIGluIHByb2dyZXNzLklsbGVnYWwgYnl0ZSBzZXF1ZW5jZS5JZGVudGlmaWVyIHJlbW92ZWQuSG9zdCBpcyB1bnJlYWNoYWJsZS5GaWxlIHRvbyBsYXJnZS5CYWQgYWRkcmVzcy5GaWxlIGV4aXN0cy5NYXRoZW1hdGljcyBhcmd1bWVudCBvdXQgb2YgZG9tYWluIG9mIGZ1bmN0aW9uLkRlc3RpbmF0aW9uIGFkZHJlc3MgcmVxdWlyZWQuUmVzb3VyY2UgZGVhZGxvY2sgd291bGQgb2NjdXIuQ29ubmVjdGlvbiByZXNldC5Db25uZWN0aW9uIHJlZnVzZWQuQ29ubmVjdGlvbiBhYm9ydGVkLk5vIGNoaWxkIHByb2Nlc3Nlcy5PcGVyYXRpb24gY2FuY2VsZWQuRGV2aWNlIG9yIHJlc291cmNlIGJ1c3kuQmFk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AAAAAAAAAAEAAAByAAAAL2hvbWUvcGF2ZWwvLmNhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvbWFyaW5lLXJzLXNkay1tYWluLTAuNi4xNS9zcmMvcmVzdWx0LnJzAFw9EABjAAAAQwAAACMAAABzAAAAAEHY+8AACxiIBBAAlAQQAAEAAAAAAAAAAQAAABggEAAA362BgAAEbmFtZQHUrYGAANwCAEhfWk4xOG1hcmluZV9yc19zZGtfbWFpbjZsb2dnZXIyMGxvZ191dGY4X3N0cmluZ19pbXBsMTdoZGQ5NGY0NzFjMzYyZjE3ZUUBTF9aTjR3YXNpMTNsaWJfZ2VuZXJhdGVkMjJ3YXNpX3NuYXBzaG90X3ByZXZpZXcxOGZkX3dyaXRlMTdoODFlNzQwZmI4NjZkMmMxOUUCT19aTjR3YXNpMTNsaWJfZ2VuZXJhdGVkMjJ3YXNpX3NuYXBzaG90X3ByZXZpZXcxMTByYW5kb21fZ2V0MTdoNjE2NGI5NmNjZGRmYzcxYUUDLV9faW1wb3J0ZWRfd2FzaV9zbmFwc2hvdF9wcmV2aWV3MV9lbnZpcm9uX2dldAQzX19pbXBvcnRlZF93YXNpX3NuYXBzaG90X3ByZXZpZXcxX2Vudmlyb25fc2l6ZXNfZ2V0BStfX2ltcG9ydGVkX3dhc2lfc25hcHNob3RfcHJldmlldzFfcHJvY19leGl0BklfWk40Y29yZTNudW03Zmx0MmRlYzhzdHJhdGVneTZkcmFnb24xNWZvcm1hdF9zaG9ydGVzdDE3aGVkN2VhYzBiMWFiNzg2ODBFBwhkbG1hbGxvYwhGX1pONGNvcmUzbnVtN2ZsdDJkZWM4c3RyYXRlZ3k2ZHJhZ29uMTJmb3JtYXRfZXhhY3QxN2hhNzZkNGM5NjlmN2U5MDkyRQlCX1pONGNvcmUzbnVtN2ZsdDJkZWM4c3RyYXRlZ3k2ZHJhZ29uOW11bF9wb3cxMDE3aDI1NGI5MDc2OWE4M2ZlMGVFCkxfWk40Y29yZTNudW03Zmx0MmRlYzhzdHJhdGVneTVncmlzdTE5Zm9ybWF0X3Nob3J0ZXN0X29wdDE3aGU4ZjFlYjFjNGExYmUyZDNFCy9fWk41YWxsb2M1c2xpY2UxMG1lcmdlX3NvcnQxN2gwMzAzNWY0MDk4M2RhODRhRQwGZGxmcmVlDQ1kaXNwb3NlX2NodW5rDgZtZW1jcHkPB21lbW1vdmUQOV9aTjRjb3JlM251bTZiaWdudW04QmlnMzJ4NDA4bXVsX3BvdzIxN2gzMWFiZGI4ZTA4NTdmMTE2RRE1X1pONGNvcmUzc3RyMTlzbGljZV9lcnJvcl9mYWlsX3J0MTdoNjcwNDcyNjc5ZTFlNDgxMUUSNl9aTjRjb3JlM3N0cjVjb3VudDE0ZG9fY291bnRfY2hhcnMxN2g4MDZiZDU1NTI1NDc0ZDk2RRNJX1pONGNvcmUzbnVtN2ZsdDJkZWM4c3RyYXRlZ3k1Z3Jpc3UxNmZvcm1hdF9leGFjdF9vcHQxN2gyOGRhYzg4MDU0M2FhMTFmRRQHcmVhbGxvYxVhX1pONjNfJExUJGxvZy4uTGV2ZWxGaWx0ZXIkdTIwJGFzJHUyMCRjb3JlLi5zdHIuLnRyYWl0cy4uRnJvbVN0ciRHVCQ4ZnJvbV9zdHIxN2gzYmEwMzMwYmM5NGM4ZWU5RRYuX1pONGNvcmUzZm10OUZvcm1hdHRlcjNwYWQxN2hiOWZkYzQzNGNiYjBjM2E4RRdFX1pONGNvcmUzZm10NWZsb2F0MjlmbG9hdF90b19kZWNpbWFsX2NvbW1vbl9leGFjdDE3aDA4NDZiYzU2YmM0NDU0ZThFGDhfWk40Y29yZTNmbXQ5Rm9ybWF0dGVyMTJwYWRfaW50ZWdyYWwxN2hiYTUzODI4NTNlOTliOGYzRRlFX1pONDBfJExUJHN0ciR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uRGVidWckR1QkM2ZtdDE3aDYzYjYyZWI4NDZiMzg1YzVFGjtfWk4zc3RkOXBhbmlja2luZzIwcnVzdF9wYW5pY193aXRoX2hvb2sxN2hjY2IwNGUwZDg2ODExNmIwRRszX1pOM3N0ZDlwYW5pY2tpbmcxMmRlZmF1bHRfaG9vazE3aGZjNjA2MWQxZDM0NWIzOGVFHDhfWk4zc3RkMmlvNVdyaXRlMTh3cml0ZV9hbGxfdmVjdG9yZWQxN2hjOTA2MDUxYzRiMjgwZDFkRR1RX1pOM3N0ZDlwYW5pY2tpbmcxMmRlZmF1bHRfaG9vazI4XyR1N2IkJHU3YiRjbG9zdXJlJHU3ZCQkdTdkJDE3aDFjOWRhZWViOGU5MDQ0NDVFHmdfWk42OF8kTFQkY29yZS4uZm10Li5idWlsZGVycy4uUGFkQWRhcHRlciR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uV3JpdGUkR1QkOXdyaXRlX3N0cjE3aDViZTYzNjcyNmFkMTEzZmVFH0hfWk40Y29yZTNmbXQ1ZmxvYXQzMmZsb2F0X3RvX2RlY2ltYWxfY29tbW9uX3Nob3J0ZXN0MTdoOGRjNDI5NzIzZDBhNmRlNEUgM19aTjRjb3JlM3N0cjhjb252ZXJ0czlmcm9tX3V0ZjgxN2g5MDZkMmZiMGNiYTdiOTA5RSFmX1pONzFfJExUJGNvcmUuLmhhc2guLnNpcC4uSGFzaGVyJExUJFMkR1QkJHUyMCRhcyR1MjAkY29yZS4uaGFzaC4uSGFzaGVyJEdUJDV3cml0ZTE3aGIxMTk4ZDI3YjkxYjk5OTNFIkFfWk40Y29yZTNmbXQ5Rm9ybWF0dGVyMjF3cml0ZV9mb3JtYXR0ZWRfcGFydHMxN2g2ZjA3NzNkYzljZGQyYWZiRSM2X1pOM3N0ZDRzeW5jNG9uY2U0T25jZTEwY2FsbF9pbm5lcjE3aDE4ZDE4YzAzMTI0NTg5ZTZFJCZfWk40Y29yZTNmbXQ1d3JpdGUxN2g3NWZlNWEyYmI2YjFmYzAyRSU/X1pONGNvcmUzZm10OUZvcm1hdHRlcjE5cGFkX2Zvcm1hdHRlZF9wYXJ0czE3aGM1NDcxZTM3ODI5MDk4MjlFJidfWk4zc3RkNnRocmVhZDRwYXJrMTdoNzk1NzI5MmVlYTk1ZTk1OUUnS19aTjE4bWFyaW5lX3JzX3Nka19tYWluNmxvZ2dlcjE3V2FzbUxvZ2dlckJ1aWxkZXI1YnVpbGQxN2g4ODc3Njg5ZDg5NzE2ZTQzRShgX1pONjdfJExUJG1hcmluZV9yc19zZGtfbWFpbi4ubG9nZ2VyLi5XYXNtTG9nZ2VyJHUyMCRhcyR1MjAkbG9nLi5Mb2ckR1QkM2xvZzE3aDI0NTc5NzllNGRjZWIxMGZFKThfWk4zc3RkMmlvNVdyaXRlMTh3cml0ZV9hbGxfdmVjdG9yZWQxN2g5NDY1OGU4NWY3YjNmZjVhRSo3X1pONGNvcmU0aGFzaDExQnVpbGRIYXNoZXI4aGFzaF9vbmUxN2g2ZGMyMTI5MzA5ZDFkOTVmRSsIYWxsb2NhdGUsXF9aTjRjb3JlM2ZtdDNudW01MF8kTFQkaW1wbCR1MjAkY29yZS4uZm10Li5EZWJ1ZyR1MjAkZm9yJHUyMCR1NjQkR1QkM2ZtdDE3aDRkOTU4MjBmNWUxNzg2YzNFLVlfWk40Y29yZTNudW03Zmx0MmRlYzhzdHJhdGVneTVncmlzdTE2Zm9ybWF0X2V4YWN0X29wdDE0cG9zc2libHlfcm91bmQxN2gwODY5MWYwOTJjYjNjMmYyRS5GX1pONDFfJExUJGNoYXIkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRlYnVnJEdUJDNmbXQxN2g3OGNjMWYxMjdjNmQ1YzU2RS9wX1pONzJfJExUJCRSRiRzdHIkdTIwJGFzJHUyMCRhbGxvYy4uZmZpLi5jX3N0ci4uQ1N0cmluZy4ubmV3Li5TcGVjTmV3SW1wbCRHVCQxM3NwZWNfbmV3X2ltcGwxN2g0MGE1ZjQ3ZDczY2E5MTI2RTARaW50ZXJuYWxfbWVtYWxpZ24xgAFfWk4zc3RkMmlvNWltcGxzNzRfJExUJGltcGwkdTIwJHN0ZC4uaW8uLldyaXRlJHUyMCRmb3IkdTIwJGFsbG9jLi52ZWMuLlZlYyRMVCR1OCRDJEEkR1QkJEdUJDE0d3JpdGVfdmVjdG9yZWQxN2g5ZGY3MmU1NjNiZTNjM2RiRTJGX1pONGNvcmUzbnVtNmJpZ251bThCaWczMng0MDEwbXVsX2RpZ2l0czltdWxfaW5uZXIxN2gzNTk4MWU3NDdmZjIwODFmRTM7X1pONGNvcmUzbnVtN2ZsdDJkZWMxN2RpZ2l0c190b19kZWNfc3RyMTdoODE4MmYzZWM0YTk0NDM2MUU0T19aTjNzdGQyaW84YnVmZmVyZWQ5YnVmd3JpdGVyMThCdWZXcml0ZXIkTFQkVyRHVCQ5Zmx1c2hfYnVmMTdoOTJiZTVhMGVkZDA5MGE0ZEU1O19aTjRjb3JlOXBhbmlja2luZzE5YXNzZXJ0X2ZhaWxlZF9pbm5lcjE3aDI0MmM1ZDY5NGI3ZDZmYjdFNgZtZW1zZXQ3NF9aTjRjb3JlN3VuaWNvZGU5cHJpbnRhYmxlNWNoZWNrMTdoNTk4MDM1ZmYxODliM2E3YUU4PF9aTjRjb3JlM2ZtdDhidWlsZGVyczExRGVidWdTdHJ1Y3Q1ZmllbGQxN2g2ZGQ1MmNlYjYzMWEwODEzRTlSX1pOM3N0ZDRzeW5jNG9uY2U0T25jZTljYWxsX29uY2UyOF8kdTdiJCR1N2IkY2xvc3VyZSR1N2QkJHU3ZCQxN2gzNGE2ODVlMzhjYzg5Y2NhRTpKX1pONGNvcmU3dW5pY29kZTEydW5pY29kZV9kYXRhMTVncmFwaGVtZV9leHRlbmQ2bG9va3VwMTdoZDU5NzU5MTdiNzY5OWYwZUU7V19aTjUwXyRMVCQkUkYkbXV0JHUyMCRXJHUyMCRhcyR1MjAkY29yZS4uZm10Li5Xcml0ZSRHVCQxMHdyaXRlX2NoYXIxN2hmNTc5MDg4ZDJhZWZmODhhRTxmX1pONzNfJExUJGNvcmUuLnBhbmljLi5wYW5pY19pbmZvLi5QYW5pY0luZm8kdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRpc3BsYXkkR1QkM2ZtdDE3aDVhNWY2NWFjZjgzNjczODFFPV9fWk41OF8kTFQkYWxsb2MuLnN0cmluZy4uU3RyaW5nJHUyMCRhcyR1MjAkY29yZS4uZm10Li5Xcml0ZSRHVCQxMHdyaXRlX2NoYXIxN2g1NzU1NjFlNjQyZjg1ODYzRT4IX19zdHBjcHk/X19aTjU4XyRMVCRhbGxvYy4uc3RyaW5nLi5TdHJpbmckdTIwJGFzJHUyMCRjb3JlLi5mbXQuLldyaXRlJEdUJDEwd3JpdGVfY2hhcjE3aDU3NTU2MWU2NDJmODU4NjNFQAtfX3N0cmNocm51bEE+X1pONGNvcmU1c2xpY2U2bWVtY2hyMTltZW1jaHJfZ2VuZXJhbF9jYXNlMTdoY2E1MDhkMGUxZWNmNmQ0Y0VCMF9aTjRjb3JlM2ZtdDNudW0zaW1wN2ZtdF91NjQxN2hiMGNhYmI1NTdjMDZjMWRiRUM7X1pOM3N0ZDVhbGxvYzI0ZGVmYXVsdF9hbGxvY19lcnJvcl9ob29rMTdoYjFmYmM1NTA2ZTkxMzMwZkVEN19aTjNzdGQzc3lzNHdhc2kxN2RlY29kZV9lcnJvcl9raW5kMTdoODUyZGE4ZjU5YWU5NzI3Y0VFXF9aTjRjb3JlM2ZtdDNudW01MF8kTFQkaW1wbCR1MjAkY29yZS4uZm10Li5EZWJ1ZyR1MjAkZm9yJHUyMCR1MzIkR1QkM2ZtdDE3aGEwYzIxYTYxZmY0MmNmMGFFRjtfWk40Y29yZTNmbXQ4YnVpbGRlcnMxMERlYnVnVHVwbGU1ZmllbGQxN2gxZTU2MTBiYWE3NDhjNDA5RUcyX1pONGNvcmUzZm10NVdyaXRlMTB3cml0ZV9jaGFyMTdoMzE2NjBhNDk3ZjE1ZDI3MUVILF9aTjNzdGQzZW52MTFjdXJyZW50X2RpcjE3aGM0MzU4ODc3M2Q2OGViZGNFSURfWk41YWxsb2MzZmZpNWNfc3RyN0NTdHJpbmcxOV9mcm9tX3ZlY191bmNoZWNrZWQxN2g4ZmMzNDk1NjlmZTYyNDQwRUo7X1pONGNvcmUzZm10OGJ1aWxkZXJzMTBEZWJ1Z0lubmVyNWVudHJ5MTdoNjA3MjA4OTI2MGI0ZjUxYUVLCXRlc3RfbG9nc0xMX1pOM3N0ZDEwc3lzX2NvbW1vbjEzdGhyZWFkX3BhcmtlcjdnZW5lcmljNlBhcmtlcjZ1bnBhcmsxN2hjZGE4MWYyNmYwNjFmYzQzRU14X1pOOTFfJExUJHN0ZC4uc3lzX2NvbW1vbi4uYmFja3RyYWNlLi5fcHJpbnQuLkRpc3BsYXlCYWNrdHJhY2UkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRpc3BsYXkkR1QkM2ZtdDE3aGVkMmNkODQ4MWRlOGFjYjFFTlBfWk4zc3RkNnRocmVhZDVsb2NhbDRsYXp5MjFMYXp5S2V5SW5uZXIkTFQkVCRHVCQxMGluaXRpYWxpemUxN2g1MmZmZmE1YWM0MzBlMzNlRU9zX1pOODBfJExUJHN0ZC4uaW8uLldyaXRlLi53cml0ZV9mbXQuLkFkYXB0ZXIkTFQkVCRHVCQkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLldyaXRlJEdUJDl3cml0ZV9zdHIxN2hhMzc3ZjYxNmM2NDRkMTU3RVBJX1pOMThtYXJpbmVfcnNfc2RrX21haW42bG9nZ2VyMTdXYXNtTG9nZ2VyQnVpbGRlcjNuZXcxN2hhNWZmYmYwNWU1MDdiZjY4RVEzX1pOM3N0ZDJydDE5bGFuZ19zdGFydF9pbnRlcm5hbDE3aDcwODg1NmRiYTBkMGUzMjBFUnxfWk45MF8kTFQkc3RkLi5wYW5pY2tpbmcuLmJlZ2luX3BhbmljX2hhbmRsZXIuLlBhbmljUGF5bG9hZCR1MjAkYXMkdTIwJGNvcmUuLnBhbmljLi5Cb3hNZVVwJEdUJDh0YWtlX2JveDE3aDFjNGE5MWU2NjNlN2VkNWNFUy1fWk4zc3RkNnRocmVhZDZUaHJlYWQzbmV3MTdoZWMxNDVkNTM2YjE3NjZmNUVUV19aTjUwXyRMVCQkUkYkbXV0JHUyMCRXJHUyMCRhcyR1MjAkY29yZS4uZm10Li5Xcml0ZSRHVCQxMHdyaXRlX2NoYXIxN2hiOGRlMWU2YzcyMDVmMjNiRVUkX1pOM3N0ZDNlbnY0X3ZhcjE3aDNiM2Y0MDIzYzZhZTMxNGVFVldfWk41MF8kTFQkJFJGJG11dCR1MjAkVyR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uV3JpdGUkR1QkMTB3cml0ZV9jaGFyMTdoOTBjOTc3NzQ0ZjkzMmM0MEVXMl9aTjRjb3JlM2ZtdDVXcml0ZTEwd3JpdGVfY2hhcjE3aDk1NWYzNWY1NGY2ZGNhYzBFWDJfWk40Y29yZTNmbXQ1V3JpdGUxMHdyaXRlX2NoYXIxN2g3NDQ3MTNlN2I5ZDJiNDVkRVknX1pOM3N0ZDNlbnY3X3Zhcl9vczE3aGJjOGZlOTNiMTQwZGM4NGJFWi5fWk4zc3RkMmlvNVdyaXRlOXdyaXRlX2ZtdDE3aGYyNTgwZjg3OWYyMjI0NDhFWy5fWk4zc3RkMmlvNVdyaXRlOXdyaXRlX2ZtdDE3aDc3NjI5YWQ2YmY4NTQ5Y2FFXExfWk41YWxsb2M3cmF3X3ZlYzE5UmF3VmVjJExUJFQkQyRBJEdUJDE2cmVzZXJ2ZV9mb3JfcHVzaDE3aDI5ZjYyNmY4YjY5YWZlYWRFXUVfWk40Y29yZTNmbXQ5Rm9ybWF0dGVyMjVkZWJ1Z190dXBsZV9maWVsZDJfZmluaXNoMTdoMzM1OTVhZDAwNmFlYTZhNUVeRV9aTjRjb3JlM2ZtdDlGb3JtYXR0ZXIyNWRlYnVnX3R1cGxlX2ZpZWxkMV9maW5pc2gxN2hiZTYyNjY4NTNkM2JmNmZmRV82X1pOM3N0ZDVwYW5pYzE5Z2V0X2JhY2t0cmFjZV9zdHlsZTE3aDdlYmE3Y2FlYjZkNDIyZmNFYEBfWk4zc3RkNnRocmVhZDVsb2NhbDE3TG9jYWxLZXkkTFQkVCRHVCQ0d2l0aDE3aDY2ODAyNWNhYmJkMTliYjBFYS5fWk4zbG9nMTdfX3ByaXZhdGVfYXBpX2xvZzE3aGI4ODc0MjgwYTIyNzQ5ZTVFYi5fWk4zc3RkMmlvNVdyaXRlOXdyaXRlX2FsbDE3aGE3NTY5OWEwNGE1ZmU3YTZFYwZkaXZpZGVkTF9aTjVhbGxvYzdyYXdfdmVjMTlSYXdWZWMkTFQkVCRDJEEkR1QkMTZyZXNlcnZlX2Zvcl9wdXNoMTdoNDg2MDE1YWZkYjI2YTFmZkVlWV9aTjVhbGxvYzdyYXdfdmVjMTlSYXdWZWMkTFQkVCRDJEEkR1QkN3Jlc2VydmUyMWRvX3Jlc2VydmVfYW5kX2hhbmRsZTE3aGExM2QxYzE4YjYxOWRiZTJFZllfWk41YWxsb2M3cmF3X3ZlYzE5UmF3VmVjJExUJFQkQyRBJEdUJDdyZXNlcnZlMjFkb19yZXNlcnZlX2FuZF9oYW5kbGUxN2g2ZjI0NmE5NTRkZWU0YTZlRWdMX1pONWFsbG9jN3Jhd192ZWMxOVJhd1ZlYyRMVCRUJEMkQSRHVCQxNnJlc2VydmVfZm9yX3B1c2gxN2hkNmFiMWVkMDJhMTA1YTFkRWhMX1pONWFsbG9jN3Jhd192ZWMxOVJhd1ZlYyRMVCRUJEMkQSRHVCQxNnJlc2VydmVfZm9yX3B1c2gxN2gwZTdmMDE3OGU2MjU5MGJhRWkDYWRkaghzdWJ0cmFjdGsIbXVsdGlwbHlsBnN0cmxlbm04X1pOM3N0ZDEwc3lzX2NvbW1vbjExdGhyZWFkX2luZm8zc2V0MTdoZDVmMDUyMWE4ZDg3NzYyNUVuPF9aTjRjb3JlN3VuaWNvZGU5cHJpbnRhYmxlMTJpc19wcmludGFibGUxN2g4M2M4ZDAxMjkxZDViZmZjRW9dX1pONGNvcmUzcHRyNTlkcm9wX2luX3BsYWNlJExUJG1hcmluZV9yc19zZGtfbWFpbi4ubG9nZ2VyLi5XYXNtTG9nZ2VyJEdUJDE3aDkzMTIyNzY4ZjBlMDkxZTFFcE1fWk40Y29yZTNmbXQ4YnVpbGRlcnMxMURlYnVnU3RydWN0MjFmaW5pc2hfbm9uX2V4aGF1c3RpdmUxN2g4MzJkNjEwYTg0YzU2NzZhRXF3X1pOOTBfJExUJHN0ZC4ucGFuaWNraW5nLi5iZWdpbl9wYW5pY19oYW5kbGVyLi5QYW5pY1BheWxvYWQkdTIwJGFzJHUyMCRjb3JlLi5wYW5pYy4uQm94TWVVcCRHVCQzZ2V0MTdoYzhkNmQ2NGQ0Mzg2YTM3NkVyCnJ1c3RfcGFuaWNzMl9aTjVhbGxvYzdyYXdfdmVjMTFmaW5pc2hfZ3JvdzE3aDc2Y2JiMzFiM2VmYjFhZDlFdDJfWk41YWxsb2M3cmF3X3ZlYzExZmluaXNoX2dyb3cxN2g4MWRjYjY1NWI2YWY2YzYzRXVLX1pONWFsbG9jN3Jhd192ZWMxMWZpbmlzaF9ncm93MTdoMDliNzk0ODExZWZkMGRkNEUubGx2bS43MDQzNDUzNjExMTg5NzYzNzExdl9fWk40Y29yZTNmbXQzbnVtNTNfJExUJGltcGwkdTIwJGNvcmUuLmZtdC4uTG93ZXJIZXgkdTIwJGZvciR1MjAkaTE2JEdUJDNmbXQxN2hmZjVjYzMzMWExYmI0MzgzRXdeX1pONGNvcmUzZm10M251bTUyXyRMVCRpbXBsJHUyMCRjb3JlLi5mbXQuLkxvd2VySGV4JHUyMCRmb3IkdTIwJGk4JEdUJDNmbXQxN2hmZmI4NmQ0NWQxNzEwNDI4RXheX1pONGNvcmUzZm10M251bTUyXyRMVCRpbXBsJHUyMCRjb3JlLi5mbXQuLlVwcGVySGV4JHUyMCRmb3IkdTIwJGk4JEdUJDNmbXQxN2hkOGFjY2IxYmUxMjJkZTVkRXlfX1pONGNvcmUzZm10M251bTUzXyRMVCRpbXBsJHUyMCRjb3JlLi5mbXQuLlVwcGVySGV4JHUyMCRmb3IkdTIwJGkxNiRHVCQzZm10MTdoYjZiODQ4OWQ3NzUxOGM2N0V6X19aTjRjb3JlM2ZtdDNudW01M18kTFQkaW1wbCR1MjAkY29yZS4uZm10Li5Mb3dlckhleCR1MjAkZm9yJHUyMCRpMzIkR1QkM2ZtdDE3aGM5ZTIxNjEzNTIxNjlkOTBFe19fWk40Y29yZTNmbXQzbnVtNTNfJExUJGltcGwkdTIwJGNvcmUuLmZtdC4uVXBwZXJIZXgkdTIwJGZvciR1MjAkaTMyJEdUJDNmbXQxN2g2YjdkOWFiMDdlYWVjZmNhRXwdX193YXNpbGliY19pbml0aWFsaXplX2Vudmlyb259B3N0cm5jbXB+ZF9aTjY3XyRMVCRtYXJpbmVfcnNfc2RrX21haW4uLmxvZ2dlci4uV2FzbUxvZ2dlciR1MjAkYXMkdTIwJGxvZy4uTG9nJEdUJDdlbmFibGVkMTdoN2ExMjgwZTRhNzRkYTBlZkV/Y19aTjcwXyRMVCRjb3JlLi5wYW5pYy4ubG9jYXRpb24uLkxvY2F0aW9uJHUyMCRhcyR1MjAkY29yZS4uZm10Li5EaXNwbGF5JEdUJDNmbXQxN2gxZjM3Mzc4YzAyYzI2OTA3RYABWF9aTjNzdGQ5cGFuaWNraW5nMTliZWdpbl9wYW5pY19oYW5kbGVyMjhfJHU3YiQkdTdiJGNsb3N1cmUkdTdkJCR1N2QkMTdoNTU0MWY3NGEwMzMxYTFkY0WBAQZnZXRlbnaCAVxfWk42M18kTFQkd2FzaS4ubGliX2dlbmVyYXRlZC4uRXJybm8kdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRlYnVnJEdUJDNmbXQxN2g4ZTQwN2U5ZTIwYmNlMDQzRYMBMl9aTjRjb3JlNnJlc3VsdDEzdW53cmFwX2ZhaWxlZDE3aDg2OGEwMDYwMWEzZWI2ODdFhAFfX1pONjRfJExUJHN0ZC4uc3lzLi53YXNpLi5zdGRpby4uU3RkZXJyJHUyMCRhcyR1MjAkc3RkLi5pby4uV3JpdGUkR1QkNXdyaXRlMTdoOGRlNzk5NjUyNGZiM2FkZkWFAT1fWk40Y29yZTNmbXQ4YnVpbGRlcnMxMURlYnVnU3RydWN0NmZpbmlzaDE3aDE1ODY1OWZjZjc5YzU5ZjlFhgFkX1pONzFfJExUJGNvcmUuLm9wcy4ucmFuZ2UuLlJhbmdlJExUJElkeCRHVCQkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRlYnVnJEdUJDNmbXQxN2g2NzQzYWVkNmQyMzZhMzlhRYcBBmNhbGxvY4gBSV9aTjQ0XyRMVCQkUkYkVCR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uRGlzcGxheSRHVCQzZm10MTdoMjMwZDVhOTZjNzhkZDRlN0WJAUdfWk40Ml8kTFQkJFJGJFQkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRlYnVnJEdUJDNmbXQxN2gxZmM4NGJmMDZlOTFkZWUwRYoBOV9aTjNzdGQzc3lzNHdhc2kxOWhhc2htYXBfcmFuZG9tX2tleXMxN2gxYzBjMGM4ZGQ1ZDQ3YmE4RYsBSV9aTjVhbGxvYzN2ZWMxNlZlYyRMVCRUJEMkQSRHVCQ2cmVtb3ZlMTNhc3NlcnRfZmFpbGVkMTdoYWYyNmM2ODIzNjUyOGZkNkWMAUdfWk40Y29yZTVzbGljZTVpbmRleDI5c2xpY2Vfc3RhcnRfaW5kZXhfbGVuX2ZhaWxfcnQxN2hhNGE1MTA5MTg5YmU2MDBiRY0BRV9aTjRjb3JlNXNsaWNlNWluZGV4MjdzbGljZV9lbmRfaW5kZXhfbGVuX2ZhaWxfcnQxN2gxYjk4OWE3YjkyYmY2Y2Y1RY4BQ19aTjRjb3JlNXNsaWNlNWluZGV4MjVzbGljZV9pbmRleF9vcmRlcl9mYWlsX3J0MTdoNTlmZTk3Mzc0MzdiZTljMUWPATpfWk40Y29yZTlwYW5pY2tpbmcxOHBhbmljX2JvdW5kc19jaGVjazE3aDk4NDRlNDZjMzM5YjliZmZFkAFYX1pONTlfJExUJGNvcmUuLmZtdC4uQXJndW1lbnRzJHUyMCRhcyR1MjAkY29yZS4uZm10Li5EaXNwbGF5JEdUJDNmbXQxN2hhNjM1MjMyMTQxNzYyYTVlRZEBNF9aTjRjb3JlM2ZtdDlGb3JtYXR0ZXI5d3JpdGVfZm10MTdoNTVhZGViODYxNTYzM2VkMUWSATVfWk40Y29yZTlwYW5pY2tpbmcxM2Fzc2VydF9mYWlsZWQxN2g4OTZkODFlMmU3ZDI3MjAwRZMBNV9aTjRjb3JlOXBhbmlja2luZzEzYXNzZXJ0X2ZhaWxlZDE3aDRhNzEyZTM2ZmQwN2U5ZDdFlAE1X1pONGNvcmU5cGFuaWNraW5nMTNhc3NlcnRfZmFpbGVkMTdoM2NmYmY5MWI2NGRiMzljMEWVAVVfWk41MF8kTFQkJFJGJG11dCR1MjAkVyR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uV3JpdGUkR1QkOXdyaXRlX2ZtdDE3aDBmMzMwMTcwODg4OGU2ZTNFlgFVX1pONTBfJExUJCRSRiRtdXQkdTIwJFckdTIwJGFzJHUyMCRjb3JlLi5mbXQuLldyaXRlJEdUJDl3cml0ZV9mbXQxN2gzYzY0ODYzMzk5NmZlMDc3RZcBVV9aTjUwXyRMVCQkUkYkbXV0JHUyMCRXJHUyMCRhcyR1MjAkY29yZS4uZm10Li5Xcml0ZSRHVCQ5d3JpdGVfZm10MTdoNjA5Zjc0NDg3MTYwNWU4YUWYAVVfWk41MF8kTFQkJFJGJG11dCR1MjAkVyR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uV3JpdGUkR1QkOXdyaXRlX2ZtdDE3aGU5ZThmNDI3ZGJhMWU1N2ZFmQFVX1pONTBfJExUJCRSRiRtdXQkdTIwJFckdTIwJGFzJHUyMCRjb3JlLi5mbXQuLldyaXRlJEdUJDl3cml0ZV9mbXQxN2gwZTQ3NWRiNzM1NDYxZTQ5RZoBMF9aTjRjb3JlM2ZtdDVXcml0ZTl3cml0ZV9mbXQxN2hkMmIzMGJjOGFhNTczYzQ3RZsBLV9aTjNsb2cxNnNldF9ib3hlZF9sb2dnZXIxN2g1YzA4OTg2YmQxZDc2MDNkRZwBMF9aTjRjb3JlM2ZtdDVXcml0ZTl3cml0ZV9mbXQxN2g0OGE0MmFkNzU4OGY0ZWU5RZ0BMF9aTjRjb3JlM2ZtdDVXcml0ZTl3cml0ZV9mbXQxN2g2ZWE0NzQ0ZmU2M2IyMTk1RZ4BaV9aTjY0XyRMVCRzdGQuLnN5cy4ud2FzaS4uc3RkaW8uLlN0ZGVyciR1MjAkYXMkdTIwJHN0ZC4uaW8uLldyaXRlJEdUJDE0d3JpdGVfdmVjdG9yZWQxN2g2OWRkZjM2YTk3OTdhOWFiRZ8BMF9aTjRjb3JlM2ZtdDVXcml0ZTl3cml0ZV9mbXQxN2hjZWU1ZjNmZTQ1M2ZiZmQxRaABBXN0YXRloQE6X1pONWFsbG9jNHN5bmMxMkFyYyRMVCRUJEdUJDlkcm9wX3Nsb3cxN2gxOTI4YzYyNTVmYTMzMDdkRaIBdl9aTjNzdGQyaW81aW1wbHM3NF8kTFQkaW1wbCR1MjAkc3RkLi5pby4uV3JpdGUkdTIwJGZvciR1MjAkYWxsb2MuLnZlYy4uVmVjJExUJHU4JEMkQSRHVCQkR1QkNXdyaXRlMTdoMzk5MTU1OWFhYzY0NzBmMkWjAQtjbGVhcl9zdGF0ZaQBY19aTjcwXyRMVCRjb3JlLi5yZXN1bHQuLlJlc3VsdCRMVCRUJEMkRSRHVCQkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRlYnVnJEdUJDNmbXQxN2hiZjI5ODBhYmFjYmMxZGU4RaUBfV9aTjkxXyRMVCRzdGQuLnBhbmlja2luZy4uYmVnaW5fcGFuaWMuLlBhbmljUGF5bG9hZCRMVCRBJEdUJCR1MjAkYXMkdTIwJGNvcmUuLnBhbmljLi5Cb3hNZVVwJEdUJDh0YWtlX2JveDE3aGUyYTNiMTY3ZGZhYjliNjBFpgFVX1pONTBfJExUJCRSRiRtdXQkdTIwJFckdTIwJGFzJHUyMCRjb3JlLi5mbXQuLldyaXRlJEdUJDl3cml0ZV9zdHIxN2g4YjdhNTE4MzA2ZWM2M2RiRacBVV9aTjUwXyRMVCQkUkYkbXV0JHUyMCRXJHUyMCRhcyR1MjAkY29yZS4uZm10Li5Xcml0ZSRHVCQ5d3JpdGVfc3RyMTdoMWI2MWEwMjJhZGMyNWRkYkWoAVVfWk41MF8kTFQkJFJGJG11dCR1MjAkVyR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uV3JpdGUkR1QkOXdyaXRlX3N0cjE3aDQ0ODczYjQ2ODNjZjk0M2FFqQF6X1pOM3N0ZDJpbzVpbXBsczc0XyRMVCRpbXBsJHUyMCRzdGQuLmlvLi5Xcml0ZSR1MjAkZm9yJHUyMCRhbGxvYy4udmVjLi5WZWMkTFQkdTgkQyRBJEdUJCRHVCQ5d3JpdGVfYWxsMTdoZThjYzI2MjMwNDQwNmUwYkWqAXNfWk44MF8kTFQkc3RkLi5pby4uV3JpdGUuLndyaXRlX2ZtdC4uQWRhcHRlciRMVCRUJEdUJCR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uV3JpdGUkR1QkOXdyaXRlX3N0cjE3aDkzNmQ4ZTkxYTE1NjhmNThFqwE0X1pONHdhc2kxM2xpYl9nZW5lcmF0ZWQ4ZmRfd3JpdGUxN2g1MDZlNmUxNGE4MGEwYmJiRawBXV9aTjU4XyRMVCRhbGxvYy4uc3RyaW5nLi5TdHJpbmckdTIwJGFzJHUyMCRjb3JlLi5mbXQuLldyaXRlJEdUJDl3cml0ZV9zdHIxN2g1MTY4MGQyM2NjMWU2ZWIxRa0BBHNicmuuAXNfWk40Y29yZTNwdHI4MWRyb3BfaW5fcGxhY2UkTFQkY29yZS4ucmVzdWx0Li5SZXN1bHQkTFQkJExQJCRSUCQkQyRzdGQuLmlvLi5lcnJvci4uRXJyb3IkR1QkJEdUJDE3aGQ4NGFkYWIyYThlNWVjYmFFrwF5X1pONGNvcmUzcHRyODdkcm9wX2luX3BsYWNlJExUJHN0ZC4uaW8uLldyaXRlLi53cml0ZV9mbXQuLkFkYXB0ZXIkTFQkJFJGJG11dCR1MjAkJHU1YiR1OCR1NWQkJEdUJCRHVCQxN2g0ZjQzY2EyMGEwY2JhNmM0RbABDV9fcmRsX3JlYWxsb2OxAQZtZW1jbXCyAUZfWk40Y29yZTNmbXQ5Rm9ybWF0dGVyMTJwYWRfaW50ZWdyYWwxMndyaXRlX3ByZWZpeDE3aDhmMmU0ZmMzZGFhMjYzNzVFswEGZ2V0Y3dktAE6X1pONWFsbG9jNHN5bmMxMkFyYyRMVCRUJEdUJDlkcm9wX3Nsb3cxN2g2YzFmZGI4NjZlMmFlZmQ3RbUBXV9aTjY0XyRMVCRhbGxvYy4uZmZpLi5jX3N0ci4uTnVsRXJyb3IkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRlYnVnJEdUJDNmbXQxN2hlOGFiOTlhZDhmZDZiOTA4RbYBLF9aTjRjb3JlOXBhbmlja2luZzVwYW5pYzE3aGVkZjQzNzhlMjA0NjFiMjVFtwE1X1pONGNvcmU5cGFuaWNraW5nMTNwYW5pY19kaXNwbGF5MTdoNjE0YmNmYTcyMzZmOTFhYUW4AX9fWk45M18kTFQkc3RkLi5wYW5pY2tpbmcuLmJlZ2luX3BhbmljX2hhbmRsZXIuLlN0clBhbmljUGF5bG9hZCR1MjAkYXMkdTIwJGNvcmUuLnBhbmljLi5Cb3hNZVVwJEdUJDh0YWtlX2JveDE3aGNmODc3ZmRiMGZjY2Q1NjFFuQFgX1pONGNvcmUzZm10NWZsb2F0NTJfJExUJGltcGwkdTIwJGNvcmUuLmZtdC4uRGlzcGxheSR1MjAkZm9yJHUyMCRmNjQkR1QkM2ZtdDE3aDY1NDMyN2NkNmY5ZWM1YTJFugERcnVzdF9iZWdpbl91bndpbmS7AThfWk41YWxsb2M3cmF3X3ZlYzE3Y2FwYWNpdHlfb3ZlcmZsb3cxN2hjYTIzZTA2MDBhOTUzNGY2RbwBMF9aTjRjb3JlOXBhbmlja2luZzlwYW5pY19mbXQxN2g3YTY1MWM3MGE5NDkyMjhjRb0BKl9aTjEyY2FsY19zZXJ2aWNlNG1haW4xN2gwY2E3NWE5OTZkODFiYTczRb4BVV9aTjU2XyRMVCRsb2cuLlNldExvZ2dlckVycm9yJHUyMCRhcyR1MjAkY29yZS4uZm10Li5EZWJ1ZyRHVCQzZm10MTdoNjUyYzYxNzYzODg0NTJhMkW/AUdfWk40Ml8kTFQkJFJGJFQkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRlYnVnJEdUJDNmbXQxN2gzZWJjMzNmYzA3ZjQwZmRmRcABYV9aTjY4XyRMVCRzdGQuLnRocmVhZC4ubG9jYWwuLkFjY2Vzc0Vycm9yJHUyMCRhcyR1MjAkY29yZS4uZm10Li5EZWJ1ZyRHVCQzZm10MTdoNTE3ZjAxOTlmNzk2OWU0ZEXBAWlfWk43Nl8kTFQkc3RkLi5zeW5jLi5wb2lzb24uLlBvaXNvbkVycm9yJExUJFQkR1QkJHUyMCRhcyR1MjAkY29yZS4uZm10Li5EZWJ1ZyRHVCQzZm10MTdoMTdhNDJkMjNlOGVjMWJjZEXCAUdfWk40Ml8kTFQkJFJGJFQkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRlYnVnJEdUJDNmbXQxN2g1Y2M3Njk4N2QxYWY4YzljRcMBR19aTjQyXyRMVCQkUkYkVCR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uRGVidWckR1QkM2ZtdDE3aGMxOGRkNjA0OTVjZWZlMDBFxAEPX19vcmlnaW5hbF9tYWluxQFhX1pOM3N0ZDJydDEwbGFuZ19zdGFydDI4XyR1N2IkJHU3YiRjbG9zdXJlJHU3ZCQkdTdkJDE3aDY5ZmNlMWZmZGI2ZjVhOThFLmxsdm0uNzEzOTI0MTQxMTY3NDgxMjc1M8YBc19aTjRjb3JlM29wczhmdW5jdGlvbjZGbk9uY2U0MGNhbGxfb25jZSR1N2IkJHU3YiR2dGFibGUuc2hpbSR1N2QkJHU3ZCQxN2gyMjFjZWNjMmEwOGQ2ZTM4RS5sbHZtLjcxMzkyNDE0MTE2NzQ4MTI3NTPHAYoBX1pONGNvcmUzcHRyMTAzZHJvcF9pbl9wbGFjZSRMVCRzdGQuLnN5bmMuLnBvaXNvbi4uUG9pc29uRXJyb3IkTFQkc3RkLi5zeW5jLi5tdXRleC4uTXV0ZXhHdWFyZCRMVCQkTFAkJFJQJCRHVCQkR1QkJEdUJDE3aGEzYjY1YzIyMDJlZDhjOWNFyAFQX1pOM3N0ZDlwYW5pY2tpbmcxMWJlZ2luX3BhbmljMjhfJHU3YiQkdTdiJGNsb3N1cmUkdTdkJCR1N2QkMTdoM2ZhOWYwYzVkM2JkNzBmZEXJATJfWk40Y29yZTNzdHIxNnNsaWNlX2Vycm9yX2ZhaWwxN2hkNGY4MTcyYTA1YjExYTk5RcoBLl9aTjRjb3JlM2ZtdDlGb3JtYXR0ZXIzbmV3MTdoNDkxOGE3OWU1NTQyN2Y4OEXLAThfWk40Y29yZTNmbXQ5Rm9ybWF0dGVyMTJkZWJ1Z19zdHJ1Y3QxN2g5N2UxYzNkYjQ0YzU5YmE3RcwBNl9aTjRjb3JlM2ZtdDlGb3JtYXR0ZXIxMGRlYnVnX2xpc3QxN2hjZmE0NmI2NDgxODczMjEyRc0BXF9aTjRjb3JlM2ZtdDNudW01MF8kTFQkaW1wbCR1MjAkY29yZS4uZm10Li5EZWJ1ZyR1MjAkZm9yJHUyMCR1MTYkR1QkM2ZtdDE3aGRjNTNlOTE1M2E4MGM4ZWFFzgFaX1pONGNvcmUzb3BzOGZ1bmN0aW9uNkZuT25jZTQwY2FsbF9vbmNlJHU3YiQkdTdiJHZ0YWJsZS5zaGltJHU3ZCQkdTdkJDE3aGIwYzFkNzhjYWUyM2M5ODFFzwE6X1pONGNvcmUzZm10OGJ1aWxkZXJzOURlYnVnTGlzdDZmaW5pc2gxN2hjNmJlNTZkZDZiZDQxYWVkRdABMl9aTjNzdGQ5cGFuaWNraW5nMTFiZWdpbl9wYW5pYzE3aGQ2N2I5NTY5NmM3ZjRmZjVF0QFoX1pONGNvcmUzcHRyNzBkcm9wX2luX3BsYWNlJExUJHN0ZC4ucGFuaWNraW5nLi5iZWdpbl9wYW5pY19oYW5kbGVyLi5QYW5pY1BheWxvYWQkR1QkMTdoNGI1ODNhYTM0NDIzYzUwMkXSATBfWk40Y29yZTlwYW5pY2tpbmc5cGFuaWNfc3RyMTdoZTE2NDQzMDcwMTk4OGU1YUXTAQtfX3JkbF9hbGxvY9QBNl9aTjR3YXNpMTNsaWJfZ2VuZXJhdGVkNUVycm5vNG5hbWUxN2hmZDJlOWQ1YjM5NGFhNzljRdUBOV9aTjR3YXNpMTNsaWJfZ2VuZXJhdGVkNUVycm5vN21lc3NhZ2UxN2hlNjQ0MWI5OTQ1ZjI4YjM0RdYBBnN0cmR1cNcBTF9aTjRjb3JlM3B0cjQyZHJvcF9pbl9wbGFjZSRMVCRhbGxvYy4uc3RyaW5nLi5TdHJpbmckR1QkMTdoYzI0MWFmNWJhYTA2M2RkM0XYAYUCX1pONGNvcmUzcHRyMjI2ZHJvcF9pbl9wbGFjZSRMVCRzdGQuLmVycm9yLi4kTFQkaW1wbCR1MjAkY29yZS4uY29udmVydC4uRnJvbSRMVCRhbGxvYy4uc3RyaW5nLi5TdHJpbmckR1QkJHUyMCRmb3IkdTIwJGFsbG9jLi5ib3hlZC4uQm94JExUJGR5biR1MjAkc3RkLi5lcnJvci4uRXJyb3IkdTJiJGNvcmUuLm1hcmtlci4uU3luYyR1MmIkY29yZS4ubWFya2VyLi5TZW5kJEdUJCRHVCQuLmZyb20uLlN0cmluZ0Vycm9yJEdUJDE3aGU0NzcyOTQ5NTNmOTJhZWNF2QFIX1pONDNfJExUJGJvb2wkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRpc3BsYXkkR1QkM2ZtdDE3aGExY2M5ZWEzZDEyZGRjNDJF2gF4X1pOOTFfJExUJHN0ZC4ucGFuaWNraW5nLi5iZWdpbl9wYW5pYy4uUGFuaWNQYXlsb2FkJExUJEEkR1QkJHUyMCRhcyR1MjAkY29yZS4ucGFuaWMuLkJveE1lVXAkR1QkM2dldDE3aDliMDdiZjIyZThiZTZkYjdF2wE/X1pONWFsbG9jNnN0cmluZzEzRnJvbVV0ZjhFcnJvcjEwaW50b19ieXRlczE3aGExN2RmMTk0OGI3Njc4NGVF3AEIcnVzdF9vb23dATdfWk40d2FzaTEzbGliX2dlbmVyYXRlZDEwcmFuZG9tX2dldDE3aDkxN2JjYWNhOWU5YWY4MGNF3gENYWxpZ25lZF9hbGxvY98BTl9aTjE4bWFyaW5lX3JzX3Nka19tYWluNnJlc3VsdDE4T0JKRUNUU19UT19SRUxFQVNFN19fZ2V0aXQxN2hjYjA2MGU1MmVlNWY5NTU4ReABO19aTjRjb3JlMTBpbnRyaW5zaWNzMTdjb25zdF9ldmFsX3NlbGVjdDE3aGQ1YWZmYTBjOWNlZTZlY2VF4QFcX1pONjNfJExUJGNvcmUuLmNlbGwuLkJvcnJvd011dEVycm9yJHUyMCRhcyR1MjAkY29yZS4uZm10Li5EZWJ1ZyRHVCQzZm10MTdoMTI0MmQ5MTEyNjQ3MTI4MkXiAVJfWk41M18kTFQkY29yZS4uZm10Li5FcnJvciR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uRGVidWckR1QkM2ZtdDE3aGE2MGRhOGViOWMxMzQ5MThF4wE7X1pONGNvcmU2b3B0aW9uMTVPcHRpb24kTFQkVCRHVCQ2dW53cmFwMTdoNDRhNDBjZDU2YTcyNGUxY0XkATtfWk40Y29yZTZvcHRpb24xNU9wdGlvbiRMVCRUJEdUJDZ1bndyYXAxN2hiYzljMDJjZGNkZGNlMDE0ReUBBl9zdGFydOYBDl9fcnVzdF9yZWFsbG9j5wFNX1pOM3N0ZDEwc3lzX2NvbW1vbjliYWNrdHJhY2UyNl9fcnVzdF9lbmRfc2hvcnRfYmFja3RyYWNlMTdoODU1MzJmMzkwZGFlYWM0ZUXoAU1fWk4zc3RkMTBzeXNfY29tbW9uOWJhY2t0cmFjZTI2X19ydXN0X2VuZF9zaG9ydF9iYWNrdHJhY2UxN2g3M2E0M2YzOGI1ZTZlZTA5RekBSV9aTjQ0XyRMVCQkUkYkVCR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uRGlzcGxheSRHVCQzZm10MTdoMDY3YzhlZGUyYTNmYmQ3YkXqARlfX3dhc2lsaWJjX2Vuc3VyZV9lbnZpcm9u6wFHX1pONDJfJExUJCRSRiRUJHUyMCRhcyR1MjAkY29yZS4uZm10Li5EZWJ1ZyRHVCQzZm10MTdoMjAwNWIzMWM4OTYxOTIxOUXsAQxfX3J1c3RfYWxsb2PtAUlfWk40NF8kTFQkJFJGJFQkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRpc3BsYXkkR1QkM2ZtdDE3aGEwOGZhZjdmN2M3MTgyNzNF7gFXX1pONTBfJExUJCRSRiRtdXQkdTIwJFckdTIwJGFzJHUyMCRjb3JlLi5mbXQuLldyaXRlJEdUJDEwd3JpdGVfY2hhcjE3aDQyNTAzNDBiM2FjZTQyYzJF7wF6X1pOOTNfJExUJHN0ZC4ucGFuaWNraW5nLi5iZWdpbl9wYW5pY19oYW5kbGVyLi5TdHJQYW5pY1BheWxvYWQkdTIwJGFzJHUyMCRjb3JlLi5wYW5pYy4uQm94TWVVcCRHVCQzZ2V0MTdoN2VkNjU2ZThmZjhjNmIzZkXwAUdfWk40Ml8kTFQkJFJGJFQkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRlYnVnJEdUJDNmbXQxN2g0MWQzYTA2OTc1OTMzNTc1RfEBOl9aTjRjb3JlM29wczhmdW5jdGlvbjZGbk9uY2U5Y2FsbF9vbmNlMTdoYjdiZDgyYzY0MzM2OTI0Y0XyAUlfWk40NF8kTFQkJFJGJFQkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRpc3BsYXkkR1QkM2ZtdDE3aGFjMGZkZjAzZWE5ODFmMmVF8wE7X1pONGNvcmUzZm10OUZvcm1hdHRlcjE1ZGVidWdfbG93ZXJfaGV4MTdoMzVkZWQxZDc5NGVhNWQ3MkX0ATtfWk40Y29yZTNmbXQ5Rm9ybWF0dGVyMTVkZWJ1Z191cHBlcl9oZXgxN2hmOWY5MmFjYTFlNTBkZDExRfUBDl9fcnVzdF9kZWFsbG9j9gFHX1pONDJfJExUJCRSRiRUJHUyMCRhcyR1MjAkY29yZS4uZm10Li5EZWJ1ZyRHVCQzZm10MTdoNGNmYzczNWI4NDgyYmNjYUX3AVVfWk41MF8kTFQkJFJGJG11dCR1MjAkVyR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uV3JpdGUkR1QkOXdyaXRlX3N0cjE3aDRjMmI3MDQ4N2EzZmQxMzVF+AESX193YXNpX2Vudmlyb25fZ2V0+QEYX193YXNpX2Vudmlyb25fc2l6ZXNfZ2V0+gEEZXhpdPsBBnN0cmNwefwBOl9aTjRjb3JlM29wczhmdW5jdGlvbjZGbk9uY2U5Y2FsbF9vbmNlMTdoYzVhODdmYmI0MDFhN2VlZUX9AWJfWk40Y29yZTNmbXQzbnVtM2ltcDUyXyRMVCRpbXBsJHUyMCRjb3JlLi5mbXQuLkRpc3BsYXkkdTIwJGZvciR1MjAkdTMyJEdUJDNmbXQxN2hkNGI2ZmZjOGEwYzdlMzUyRf4BMl9aTjRjb3JlNm9wdGlvbjEzZXhwZWN0X2ZhaWxlZDE3aGI3ZWRkYzVhMTkzN2U2YmJF/wE4X1pONGNvcmUzZm10OGJ1aWxkZXJzOERlYnVnU2V0NWVudHJ5MTdoMDE0ZjlkNmYyZGMyZTQ2Y0WAAlVfWk41MF8kTFQkJFJGJG11dCR1MjAkVyR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uV3JpdGUkR1QkOXdyaXRlX3N0cjE3aDIxODAyY2EzZjM4MTRlZGZFgQJhX1pONGNvcmUzZm10M251bTNpbXA1MV8kTFQkaW1wbCR1MjAkY29yZS4uZm10Li5EaXNwbGF5JHUyMCRmb3IkdTIwJHU4JEdUJDNmbXQxN2g0MDBmMWYyNmZmMzI2Y2I2RYICYl9aTjRjb3JlM2ZtdDNudW0zaW1wNTJfJExUJGltcGwkdTIwJGNvcmUuLmZtdC4uRGlzcGxheSR1MjAkZm9yJHUyMCR1MTYkR1QkM2ZtdDE3aDI1NDk4MTU3MWU3OTU5NThFgwITbWFpbi5jb21tYW5kX2V4cG9ydIQCF2FsbG9jYXRlLmNvbW1hbmRfZXhwb3J0hQIaX19ydXN0X2FsbG9jX2Vycm9yX2hhbmRsZXKGAkdfWk40Ml8kTFQkJFJGJFQkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRlYnVnJEdUJDNmbXQxN2gzYTIzMzM0ODlkZDVlNjM4RYcCR19aTjQyXyRMVCQkUkYkVCR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uRGVidWckR1QkM2ZtdDE3aDBlOGZhNTFlM2UyYzJkZDBFiAJHX1pONDJfJExUJCRSRiRUJHUyMCRhcyR1MjAkY29yZS4uZm10Li5EZWJ1ZyRHVCQzZm10MTdoZmNmMWQ4NDAxZWIxNzQwZkWJAklfWk40NF8kTFQkJFJGJFQkdTIwJGFzJHUyMCRjb3JlLi5mbXQuLkRpc3BsYXkkR1QkM2ZtdDE3aGI5ODMxNTk5NGYxODYwMmVFigJXX1pONTBfJExUJCRSRiRtdXQkdTIwJFckdTIwJGFzJHUyMCRjb3JlLi5mbXQuLldyaXRlJEdUJDEwd3JpdGVfY2hhcjE3aDZkOTY1Mjc3MmE3ZmE1YjhFiwI7X1pONGNvcmUxMGludHJpbnNpY3MxN2NvbnN0X2V2YWxfc2VsZWN0MTdoNzRlNzg5ZDUzNmFlMDYyYkWMAjpfWk40Y29yZTNvcHM4ZnVuY3Rpb242Rm5PbmNlOWNhbGxfb25jZTE3aGViYTk2ODMwZjRlYzc1YzdFjQJAX1pONWFsbG9jNWFsbG9jMThoYW5kbGVfYWxsb2NfZXJyb3I4cnRfZXJyb3IxN2hmMjhjYTFkNDc1YWU3OTQ4RY4CN19aTjVhbGxvYzVhbGxvYzE4aGFuZGxlX2FsbG9jX2Vycm9yMTdoMWVlMWNjZmRhZmU2YTQwYUWPAghfX3JnX29vbZACOl9aTjRjb3JlM29wczhmdW5jdGlvbjZGbk9uY2U5Y2FsbF9vbmNlMTdoMjQ4ZGJjYjIwNGNjMTRlNUWRAjpfWk40Y29yZTNvcHM4ZnVuY3Rpb242Rm5PbmNlOWNhbGxfb25jZTE3aDM4ZjFhYjY0YjJjZWM1MTRFkgI6X1pONGNvcmUzb3BzOGZ1bmN0aW9uNkZuT25jZTljYWxsX29uY2UxN2hkNWU1NThhODE1YzU0YWU3RZMCRF9aTjRjb3JlNXNsaWNlNWluZGV4MjZzbGljZV9zdGFydF9pbmRleF9sZW5fZmFpbDE3aDIzMWE0NDJlYWUyMDBkNmJFlAJCX1pONGNvcmU1c2xpY2U1aW5kZXgyNHNsaWNlX2VuZF9pbmRleF9sZW5fZmFpbDE3aGM5Mjg4ZWViMTkyN2M4ZDdFlQJAX1pONGNvcmU1c2xpY2U1aW5kZXgyMnNsaWNlX2luZGV4X29yZGVyX2ZhaWwxN2hmMTc0ZTA1NWFlNTMzNzNmRZYCO19aTjRjb3JlMTBpbnRyaW5zaWNzMTdjb25zdF9ldmFsX3NlbGVjdDE3aDA0MzllMDIyMmZhMjQ3MGFFlwI7X1pONGNvcmUxMGludHJpbnNpY3MxN2NvbnN0X2V2YWxfc2VsZWN0MTdoNDExYTg0ZjM2ODM4NWFmYUWYAjtfWk40Y29yZTEwaW50cmluc2ljczE3Y29uc3RfZXZhbF9zZWxlY3QxN2hlMDQxN2M3NjBiNGFhOTA3RZkCQF9aTjRjb3JlNXBhbmljMTBwYW5pY19pbmZvOVBhbmljSW5mbzdwYXlsb2FkMTdoNjk5YTNkYmZlZDIxMGQyNkWaAkdfWk40Ml8kTFQkc3RyJHUyMCRhcyR1MjAkY29yZS4uZm10Li5EaXNwbGF5JEdUJDNmbXQxN2hiYTZmZTJhY2Q1NTE0YzJkRZsCR19aTjQyXyRMVCQkUkYkVCR1MjAkYXMkdTIwJGNvcmUuLmZtdC4uRGVidWckR1QkM2ZtdDE3aDdiMWQzZDMzMGE3YTgxMzFFnAISYWRkLmNvbW1hbmRfZXhwb3J0nQIXc3VidHJhY3QuY29tbWFuZF9leHBvcnSeAhdtdWx0aXBseS5jb21tYW5kX2V4cG9ydJ8CFWRpdmlkZS5jb21tYW5kX2V4cG9ydKACHXNldF9yZXN1bHRfcHRyLmNvbW1hbmRfZXhwb3J0oQIec2V0X3Jlc3VsdF9zaXplLmNvbW1hbmRfZXhwb3J0ogJFX1pOM3N0ZDlwYW5pY2tpbmcxMXBhbmljX2NvdW50MTdpc196ZXJvX3Nsb3dfcGF0aDE3aGZmNzI3ZTc1ODE2MThkNTVFowJ2X1pOM3N0ZDJpbzVpbXBsczc0XyRMVCRpbXBsJHUyMCRzdGQuLmlvLi5Xcml0ZSR1MjAkZm9yJHUyMCRhbGxvYy4udmVjLi5WZWMkTFQkdTgkQyRBJEdUJCRHVCQ1Zmx1c2gxN2hiNDY5NDA3NmQwNWYyYjhiRaQCX19aTjY0XyRMVCRzdGQuLnN5cy4ud2FzaS4uc3RkaW8uLlN0ZGVyciR1MjAkYXMkdTIwJHN0ZC4uaW8uLldyaXRlJEdUJDVmbHVzaDE3aDgzMzY1ZTNiMTQ1ZDA4NzZFpQIQX193YXNpX3Byb2NfZXhpdKYCBV9FeGl0pwIOc2V0X3Jlc3VsdF9wdHKoAg9zZXRfcmVzdWx0X3NpemWpAhVfc3RhcnQuY29tbWFuZF9leHBvcnSqAhh0ZXN0X2xvZ3MuY29tbWFuZF9leHBvcnSrAhpjbGVhcl9zdGF0ZS5jb21tYW5kX2V4cG9ydKwCFHN0YXRlLmNvbW1hbmRfZXhwb3J0rQIdZ2V0X3Jlc3VsdF9wdHIuY29tbWFuZF9leHBvcnSuAh5nZXRfcmVzdWx0X3NpemUuY29tbWFuZF9leHBvcnSvAh5yZWxlYXNlX29iamVjdHMuY29tbWFuZF9leHBvcnSwAk9fWk4zc3RkMTBzeXNfY29tbW9uOWJhY2t0cmFjZTI4X19ydXN0X2JlZ2luX3Nob3J0X2JhY2t0cmFjZTE3aDNkMTVlZmZkMDg2MzYzOTJFsQI0X1pOM3N0ZDNzeXM0d2FzaTE0YWJvcnRfaW50ZXJuYWwxN2hlMjU0NWYwYjAyYmQzNjJkRbICKV9aTjNzdGQ3cHJvY2VzczVhYm9ydDE3aDRjYzRjY2QwZjViZGM4OTFFswINX19yZGxfZGVhbGxvY7QCPF9aTjNzdGQzc3lzNHdhc2k3cHJvY2VzczhFeGl0Q29kZTZhc19pMzIxN2g2OWY4NTg2MjEzYzJlNmQ3RbUCNV9aTjR3YXNpMTNsaWJfZ2VuZXJhdGVkNUVycm5vM3JhdzE3aDg4MzgzNDkzOTY4NGE4YTlFtgIGbWFsbG9jtwIEZnJlZbgCEV9fd2FzbV9jYWxsX2R0b3JzuQJAX1pONGNvcmU1cGFuaWMxMHBhbmljX2luZm85UGFuaWNJbmZvN21lc3NhZ2UxN2hhMDI2YTM3Zjc1MTM1NGFjRboCQV9aTjRjb3JlNXBhbmljMTBwYW5pY19pbmZvOVBhbmljSW5mbzhsb2NhdGlvbjE3aDU5NzNiODU1YTE4N2E5ZDZFuwJEX1pONGNvcmU1cGFuaWMxMHBhbmljX2luZm85UGFuaWNJbmZvMTBjYW5fdW53aW5kMTdoMjFiZGQ3NTAyZDZlMzVlZkW8Ag5nZXRfcmVzdWx0X3B0cr0CD2dldF9yZXN1bHRfc2l6Zb4CD3JlbGVhc2Vfb2JqZWN0c78CEV9fd2FzbV9jYWxsX2N0b3JzwAIEbWFpbsECTF9aTjQzXyRMVCRsb2cuLk5vcExvZ2dlciR1MjAkYXMkdTIwJGxvZy4uTG9nJEdUJDdlbmFibGVkMTdoNzAwNDJmODk4NmE2NWU5N0XCAkVfWk4zNl8kTFQkVCR1MjAkYXMkdTIwJGNvcmUuLmFueS4uQW55JEdUJDd0eXBlX2lkMTdoZDg0ZjVlZGUwYzY3ZTAxZUXDAkVfWk4zNl8kTFQkVCR1MjAkYXMkdTIwJGNvcmUuLmFueS4uQW55JEdUJDd0eXBlX2lkMTdoMzAyYzRiYTdiZTViNzY4NUXEAkVfWk4zNl8kTFQkVCR1MjAkYXMkdTIwJGNvcmUuLmFueS4uQW55JEdUJDd0eXBlX2lkMTdoM2RmZjNmOTA0ZTRlMmJiMkXFAkVfWk4zNl8kTFQkVCR1MjAkYXMkdTIwJGNvcmUuLmFueS4uQW55JEdUJDd0eXBlX2lkMTdoNjc4M2NkNzgyNGFlZDY4NEXGAoMBX1pOM3N0ZDJpbzVpbXBsczc0XyRMVCRpbXBsJHUyMCRzdGQuLmlvLi5Xcml0ZSR1MjAkZm9yJHUyMCRhbGxvYy4udmVjLi5WZWMkTFQkdTgkQyRBJEdUJCRHVCQxN2lzX3dyaXRlX3ZlY3RvcmVkMTdoNzIzMmUyNTQwZWU5MjVjOEXHAmxfWk42NF8kTFQkc3RkLi5zeXMuLndhc2kuLnN0ZGlvLi5TdGRlcnIkdTIwJGFzJHUyMCRzdGQuLmlvLi5Xcml0ZSRHVCQxN2lzX3dyaXRlX3ZlY3RvcmVkMTdoMDY5YjFmODgzMGU4MzZkZUXIAhJfX3J1c3Rfc3RhcnRfcGFuaWPJAiVfX3dhc2lsaWJjX2luaXRpYWxpemVfZW52aXJvbl9lYWdlcmx5ygIFYWJvcnTLAkVfWk4zNl8kTFQkVCR1MjAkYXMkdTIwJGNvcmUuLmFueS4uQW55JEdUJDd0eXBlX2lkMTdoYTc4MjMzN2Y2YzZlYWNmOUXMAkpfWk40Y29yZTNwdHI0MGRyb3BfaW5fcGxhY2UkTFQkbG9nLi5TZXRMb2dnZXJFcnJvciRHVCQxN2hhOWVlYTFjZGFhNmZkNGVmRc0CkAFfWk40Y29yZTNwdHI4NWRyb3BfaW5fcGxhY2UkTFQkc3RkLi5ydC4ubGFuZ19zdGFydCRMVCQkTFAkJFJQJCRHVCQuLiR1N2IkJHU3YiRjbG9zdXJlJHU3ZCQkdTdkJCRHVCQxN2gxNWUxZWJkMGFkYzg5ZWIyRS5sbHZtLjcxMzkyNDE0MTE2NzQ4MTI3NTPOAlhfWk40Y29yZTNwdHI1NGRyb3BfaW5fcGxhY2UkTFQkJFJGJG11dCR1MjAkYWxsb2MuLnN0cmluZy4uU3RyaW5nJEdUJDE3aDI2OWU0NDFiNTA0NmVjMjlFzwJHX1pONGNvcmUzcHRyMzdkcm9wX2luX3BsYWNlJExUJGNvcmUuLmZtdC4uRXJyb3IkR1QkMTdoZGM2NzQxY2U1YjMyZGVmM0XQAmJfWk42N18kTFQkbWFyaW5lX3JzX3Nka19tYWluLi5sb2dnZXIuLldhc21Mb2dnZXIkdTIwJGFzJHUyMCRsb2cuLkxvZyRHVCQ1Zmx1c2gxN2hjNTU5OWU5YWZhMDQ3YTg3RdECQl9aTjRjb3JlM3B0cjMyZHJvcF9pbl9wbGFjZSRMVCQkUkYkJFJGJHN0ciRHVCQxN2g5YzYyM2JkNTFiNzk1YTYxRdICSF9aTjQzXyRMVCRsb2cuLk5vcExvZ2dlciR1MjAkYXMkdTIwJGxvZy4uTG9nJEdUJDNsb2cxN2g4NzI1YjgwOTY1NWJjMjEyRdMCSl9aTjQzXyRMVCRsb2cuLk5vcExvZ2dlciR1MjAkYXMkdTIwJGxvZy4uTG9nJEdUJDVmbHVzaDE3aDQ4MmNhZTIzZWMzYWZiNTRF1AJvX1pONGNvcmUzcHRyNzdkcm9wX2luX3BsYWNlJExUJHN0ZC4ucGFuaWNraW5nLi5iZWdpbl9wYW5pYy4uUGFuaWNQYXlsb2FkJExUJCRSRiRzdHIkR1QkJEdUJDE3aGIyMTFiMWMzNmZlYzM2ZThF1QI+X1pONGNvcmUzcHRyMjhkcm9wX2luX3BsYWNlJExUJCRSRiRzdHIkR1QkMTdoNjJiZTY3ZWM0ODNiMGVhZUXWAocBX1pONGNvcmUzcHRyMTAwZHJvcF9pbl9wbGFjZSRMVCQkUkYkbXV0JHUyMCRzdGQuLmlvLi5Xcml0ZS4ud3JpdGVfZm10Li5BZGFwdGVyJExUJGFsbG9jLi52ZWMuLlZlYyRMVCR1OCRHVCQkR1QkJEdUJDE3aDU4Y2JlYzFiOGUyNGE4MDZF1wI5X1pONGNvcmUzcHRyMjNkcm9wX2luX3BsYWNlJExUJHU4JEdUJDE3aDExMjIyOTU3MGNhOWEwMTZF2AIFZHVtbXnZAj1fWk40Y29yZTNwdHIyN2Ryb3BfaW5fcGxhY2UkTFQkJFJGJHU4JEdUJDE3aGE0YmY1OGZjMWU2YmI5YzhF2gKJAV9aTjRjb3JlM3B0cjEwMmRyb3BfaW5fcGxhY2UkTFQkJFJGJGNvcmUuLml0ZXIuLmFkYXB0ZXJzLi5jb3BpZWQuLkNvcGllZCRMVCRjb3JlLi5zbGljZS4uaXRlci4uSXRlciRMVCR1OCRHVCQkR1QkJEdUJDE3aGRkZDAxNjVkMjVmNWVjNTRF2wJrX1pONGNvcmUzcHRyNDdkcm9wX2luX3BsYWNlJExUJGNvcmUuLmNlbGwuLkJvcnJvd011dEVycm9yJEdUJDE3aDdjNWJmNDRiNmE2ZDBiOTZFLmxsdm0uMTU3Nzc2NDg0NDA1MzUyNzMyMTQA8ICAgAAJcHJvZHVjZXJzAghsYW5ndWFnZQEEUnVzdAAMcHJvY2Vzc2VkLWJ5AwVjbGFuZwYxNC4wLjAFcnVzdGMlMS42NC4wLW5pZ2h0bHkgKDgzMDg4MDY0MCAyMDIyLTA2LTI4KQZ3YWxydXMGMC4xOS4wAKqBgIAAHV9fbV9nZW5lcmF0ZWRfc2VjdGlvbl9fZGl2aWRleyJhc3RfdHlwZSI6IkZ1bmN0aW9uIiwic2lnbmF0dXJlIjp7Im5hbWUiOiJkaXZpZGUiLCJhcmd1bWVudHMiOlt7Im5hbWUiOiJudW0iLCJ0eSI6eyJGNjQiOiJCeVZhbHVlIn19XSwib3V0cHV0X3R5cGVzIjpbeyJGNjQiOiJCeVZhbHVlIn1dfX0AroGAgAAfX19tX2dlbmVyYXRlZF9zZWN0aW9uX19tdWx0aXBseXsiYXN0X3R5cGUiOiJGdW5jdGlvbiIsInNpZ25hdHVyZSI6eyJuYW1lIjoibXVsdGlwbHkiLCJhcmd1bWVudHMiOlt7Im5hbWUiOiJudW0iLCJ0eSI6eyJGNjQiOiJCeVZhbHVlIn19XSwib3V0cHV0X3R5cGVzIjpbeyJGNjQiOiJCeVZhbHVlIn1dfX0Ag4GAgAAcX19tX2dlbmVyYXRlZF9zZWN0aW9uX19zdGF0ZXsiYXN0X3R5cGUiOiJGdW5jdGlvbiIsInNpZ25hdHVyZSI6eyJuYW1lIjoic3RhdGUiLCJhcmd1bWVudHMiOltdLCJvdXRwdXRfdHlwZXMiOlt7IkY2NCI6IkJ5VmFsdWUifV19fQD+gICAACJfX21fZ2VuZXJhdGVkX3NlY3Rpb25fX2NsZWFyX3N0YXRleyJhc3RfdHlwZSI6IkZ1bmN0aW9uIiwic2lnbmF0dXJlIjp7Im5hbWUiOiJjbGVhcl9zdGF0ZSIsImFyZ3VtZW50cyI6W10sIm91dHB1dF90eXBlcyI6W119fQCugYCAAB9fX21fZ2VuZXJhdGVkX3NlY3Rpb25fX3N1YnRyYWN0eyJhc3RfdHlwZSI6IkZ1bmN0aW9uIiwic2lnbmF0dXJlIjp7Im5hbWUiOiJzdWJ0cmFjdCIsImFyZ3VtZW50cyI6W3sibmFtZSI6Im51bSIsInR5Ijp7IkY2NCI6IkJ5VmFsdWUifX1dLCJvdXRwdXRfdHlwZXMiOlt7IkY2NCI6IkJ5VmFsdWUifV19fQCkgYCAABpfX21fZ2VuZXJhdGVkX3NlY3Rpb25fX2FkZHsiYXN0X3R5cGUiOiJGdW5jdGlvbiIsInNpZ25hdHVyZSI6eyJuYW1lIjoiYWRkIiwiYXJndW1lbnRzIjpbeyJuYW1lIjoibnVtIiwidHkiOnsiRjY0IjoiQnlWYWx1ZSJ9fV0sIm91dHB1dF90eXBlcyI6W3siRjY0IjoiQnlWYWx1ZSJ9XX19APqAgIAAIF9fbV9nZW5lcmF0ZWRfc2VjdGlvbl9fdGVzdF9sb2dzeyJhc3RfdHlwZSI6IkZ1bmN0aW9uIiwic2lnbmF0dXJlIjp7Im5hbWUiOiJ0ZXN0X2xvZ3MiLCJhcmd1bWVudHMiOltdLCJvdXRwdXRfdHlwZXMiOltdfX0A/4KAgAAPaW50ZXJmYWNlLXR5cGVzBQYwLjI0LjEAFAABBHNpemUMAQwAAAAAAAEMAAABDAABC3Jlc3VsdF9zaXplDAAAAQpyZXN1bHRfcHRyDAAAAQNudW0JAQkAAQNudW0JAQkAAQNudW0JAQkAAQNudW0JAQkAAAEJAAABCQAAAAAAAAABA251bQkBCQABA251bQkBCQABA251bQkBCQABA251bQkBCQAAAAAAAAIHBgIAAAEGCAIAAAEHCgEBCAwBAQkOAgAAAQoQAgAAAQsSAQEMAw0IYWxsb2NhdGUAD3JlbGVhc2Vfb2JqZWN0cwEPZ2V0X3Jlc3VsdF9zaXplAg5nZXRfcmVzdWx0X3B0cgMPc2V0X3Jlc3VsdF9zaXplBA5zZXRfcmVzdWx0X3B0cgUGZGl2aWRlBwhtdWx0aXBseQkFc3RhdGULC2NsZWFyX3N0YXRlDQhzdWJ0cmFjdA8DYWRkEQl0ZXN0X2xvZ3MTBAcHBgkICwoNDA8OERATEgCcgICAABVfX2ZsdWVuY2Vfc2RrX3ZlcnNpb24wLjYuMTU="; diff --git a/packages/@tests/aqua/tsconfig.json b/packages/@tests/aqua/tsconfig.json index eccdb786..44db9b10 100644 --- a/packages/@tests/aqua/tsconfig.json +++ b/packages/@tests/aqua/tsconfig.json @@ -1,8 +1,9 @@ { - "extends": "../../../tsconfig.json", - "compilerOptions": { - "outDir": "./dist", - "module": "NodeNext" - }, - "exclude": ["node_modules", "dist"] + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "module": "NodeNext" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] } diff --git a/packages/@tests/smoke/node/package.json b/packages/@tests/smoke/node/package.json index 24a790bd..7c6be3a6 100644 --- a/packages/@tests/smoke/node/package.json +++ b/packages/@tests/smoke/node/package.json @@ -1,23 +1,23 @@ { - "name": "@test/smoke", - "version": "0.1.0", - "description": "Smoke test", - "main": "./dist/index.js", - "typings": "./dist/index.d.ts", - "engines": { - "node": ">=10", - "pnpm": ">=3" - }, - "type": "module", - "scripts": { - "build": "tsc", - "test": "node --loader ts-node/esm ./src/index.ts" - }, - "repository": "https://github.com/fluencelabs/fluence-js", - "author": "Fluence Labs", - "license": "Apache-2.0", - "dependencies": { - "@fluencelabs/js-client": "workspace:*", - "@test/aqua_for_test": "workspace:*" - } + "name": "@test/smoke", + "version": "0.1.0", + "description": "Smoke test", + "main": "./dist/index.js", + "typings": "./dist/index.d.ts", + "engines": { + "node": ">=10", + "pnpm": ">=3" + }, + "type": "module", + "scripts": { + "build": "tsc", + "test": "node --loader ts-node/esm ./src/index.ts" + }, + "repository": "https://github.com/fluencelabs/fluence-js", + "author": "Fluence Labs", + "license": "Apache-2.0", + "dependencies": { + "@fluencelabs/js-client": "workspace:*", + "@test/aqua_for_test": "workspace:*" + } } diff --git a/packages/@tests/smoke/node/src/index.ts b/packages/@tests/smoke/node/src/index.ts index 3040537d..39025990 100644 --- a/packages/@tests/smoke/node/src/index.ts +++ b/packages/@tests/smoke/node/src/index.ts @@ -1,4 +1,21 @@ -import '@fluencelabs/js-client'; -import { runTest } from '@test/aqua_for_test'; +/** + * Copyright 2023 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. + */ -runTest().then(() => console.log('Smoke tests succeed!')); +import "@fluencelabs/js-client"; +import { runTest } from "@test/aqua_for_test"; + +await runTest(); +console.log("Smoke tests succeed!"); diff --git a/packages/@tests/smoke/node/tsconfig.json b/packages/@tests/smoke/node/tsconfig.json index 32d340ac..4398ffda 100644 --- a/packages/@tests/smoke/node/tsconfig.json +++ b/packages/@tests/smoke/node/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../../../tsconfig.json", - "compilerOptions": { - "outDir": "./dist" - }, - "exclude": ["node_modules", "dist"] + "extends": "../../../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist" + }, + "exclude": ["node_modules", "dist"] } diff --git a/packages/@tests/smoke/web-cra-ts/package.json b/packages/@tests/smoke/web-cra-ts/package.json index 2cd242d0..bfeea5c8 100644 --- a/packages/@tests/smoke/web-cra-ts/package.json +++ b/packages/@tests/smoke/web-cra-ts/package.json @@ -1,51 +1,50 @@ { - "name": "cra-ts", - "version": "0.1.0", - "private": true, - "type": "module", - "dependencies": { - "@test/aqua_for_test": "workspace:^", - "@testing-library/jest-dom": "5.16.5", - "@testing-library/react": "13.4.0", - "@testing-library/user-event": "13.5.0", - "@types/jest": "27.5.2", - "@types/node": "16.18.12", - "@types/react": "18.0.27", - "@types/react-dom": "18.0.10", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-scripts": "5.0.1", - "typescript": "4.9.5", - "web-vitals": "2.1.4" - }, - "devDependencies": { - "@test/test-utils": "workspace:^", - "puppeteer": "19.7.2" - }, - "scripts": { - "test": "node --loader ts-node/esm ./test/index.ts", - "simulate-cdn": "http-server -p 8766 ../../../client/js-client.web.standalone/dist", - "start": "react-scripts start", - "build": "react-scripts build", - "_test": "react-scripts test", - "eject": "react-scripts eject" - }, - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ] - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - } + "name": "cra-ts", + "version": "0.1.0", + "private": true, + "type": "module", + "dependencies": { + "@test/aqua_for_test": "workspace:*", + "@testing-library/jest-dom": "5.16.5", + "@testing-library/react": "13.4.0", + "@testing-library/user-event": "13.5.0", + "@types/jest": "27.5.2", + "@types/node": "16.18.12", + "@types/react": "18.0.27", + "@types/react-dom": "18.0.10", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-scripts": "5.0.1", + "web-vitals": "2.1.4" + }, + "devDependencies": { + "@test/test-utils": "workspace:*", + "puppeteer": "19.7.2" + }, + "scripts": { + "test": "node --loader ts-node/esm ./test/index.ts", + "simulate-cdn": "http-server -p 8766 ../../../client/js-client.web.standalone/dist", + "start": "react-scripts start", + "build": "react-scripts build", + "_test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } } diff --git a/packages/@tests/smoke/web-cra-ts/src/App.tsx b/packages/@tests/smoke/web-cra-ts/src/App.tsx index ba9d1e00..d5ca86f3 100644 --- a/packages/@tests/smoke/web-cra-ts/src/App.tsx +++ b/packages/@tests/smoke/web-cra-ts/src/App.tsx @@ -1,40 +1,49 @@ -import { runTest, TestResult } from '@test/aqua_for_test'; -import React from 'react'; -import logo from './logo.svg'; -import './App.css'; +import { runTest, TestResult } from "@test/aqua_for_test"; +import React from "react"; +import logo from "./logo.svg"; +import "./App.css"; function App() { - const [result, setResult] = React.useState(null); + const [result, setResult] = React.useState(null); - const onButtonClick = () => { - runTest() - .then((res) => { - setResult(res); - }) - .catch((err) => { - setResult({ type: 'failure', error: err.toString() }); - }); - }; + const onButtonClick = () => { + runTest() + .then((res) => { + setResult(res); + }) + .catch((err) => { + setResult({ type: "failure", error: err.toString() }); + }); + }; - return ( -
-
- logo -

- Edit src/App.tsx and save to reload. -

- + return ( +
+
+ logo +

+ Edit src/App.tsx and save to reload. +

+ - {result && result.type === 'success' &&
{result.data}
} - {result && result.type === 'failure' &&
{result.error}
} - - Learn React - -
-
- ); + {result && result.type === "success" && ( +
{result.data}
+ )} + {result && result.type === "failure" && ( +
{result.error}
+ )} + + Learn React + +
+
+ ); } export default App; diff --git a/packages/@tests/smoke/web-cra-ts/src/index.css b/packages/@tests/smoke/web-cra-ts/src/index.css index ec2585e8..4a1df4db 100644 --- a/packages/@tests/smoke/web-cra-ts/src/index.css +++ b/packages/@tests/smoke/web-cra-ts/src/index.css @@ -1,13 +1,13 @@ body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } diff --git a/packages/@tests/smoke/web-cra-ts/src/index.tsx b/packages/@tests/smoke/web-cra-ts/src/index.tsx index bff925e5..4fa2a47f 100644 --- a/packages/@tests/smoke/web-cra-ts/src/index.tsx +++ b/packages/@tests/smoke/web-cra-ts/src/index.tsx @@ -1,14 +1,16 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import App from './App'; -import reportWebVitals from './reportWebVitals'; +import React from "react"; +import ReactDOM from "react-dom/client"; +import "./index.css"; +import App from "./App"; +import reportWebVitals from "./reportWebVitals"; -const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); +const root = ReactDOM.createRoot( + document.getElementById("root") as HTMLElement, +); root.render( - - - , + + + , ); // If you want to start measuring performance in your app, pass a function diff --git a/packages/@tests/smoke/web-cra-ts/src/reportWebVitals.ts b/packages/@tests/smoke/web-cra-ts/src/reportWebVitals.ts index 49a2a16e..5fa3583b 100644 --- a/packages/@tests/smoke/web-cra-ts/src/reportWebVitals.ts +++ b/packages/@tests/smoke/web-cra-ts/src/reportWebVitals.ts @@ -1,8 +1,8 @@ -import { ReportHandler } from 'web-vitals'; +import { ReportHandler } from "web-vitals"; const reportWebVitals = (onPerfEntry?: ReportHandler) => { if (onPerfEntry && onPerfEntry instanceof Function) { - import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { getCLS(onPerfEntry); getFID(onPerfEntry); getFCP(onPerfEntry); diff --git a/packages/@tests/smoke/web-cra-ts/src/setupTests.ts b/packages/@tests/smoke/web-cra-ts/src/setupTests.ts index 8f2609b7..1dd407a6 100644 --- a/packages/@tests/smoke/web-cra-ts/src/setupTests.ts +++ b/packages/@tests/smoke/web-cra-ts/src/setupTests.ts @@ -2,4 +2,4 @@ // allows you to do things like: // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom'; +import "@testing-library/jest-dom"; diff --git a/packages/@tests/smoke/web-cra-ts/test/index.ts b/packages/@tests/smoke/web-cra-ts/test/index.ts index 4d2d5731..456f9980 100644 --- a/packages/@tests/smoke/web-cra-ts/test/index.ts +++ b/packages/@tests/smoke/web-cra-ts/test/index.ts @@ -1,49 +1,53 @@ -import puppeteer from 'puppeteer'; -import { dirname, join } from 'path'; -import { fileURLToPath } from 'url'; +import puppeteer from "puppeteer"; +import { dirname, join } from "path"; +import { fileURLToPath } from "url"; -import { CDN_PUBLIC_PATH, startContentServer, stopServer } from '@test/test-utils'; -import { access, symlink } from 'fs/promises'; +import { + CDN_PUBLIC_PATH, + startContentServer, + stopServer, +} from "@test/test-utils"; +import { access, symlink } from "fs/promises"; const port = 3001; const uri = `http://localhost:${port}/`; const __dirname = dirname(fileURLToPath(import.meta.url)); -const publicPath = join(__dirname, '../build/'); +const publicPath = join(__dirname, "../build/"); const test = async () => { - const localServer = await startContentServer(port, publicPath); - try { - await access(join(publicPath, 'source')) - } catch { - await symlink(CDN_PUBLIC_PATH, join(publicPath, 'source')); - } + const localServer = await startContentServer(port, publicPath); + try { + await access(join(publicPath, "source")); + } catch { + await symlink(CDN_PUBLIC_PATH, join(publicPath, "source")); + } - console.log('starting puppeteer...'); - const browser = await puppeteer.launch(); - const page = (await browser.pages())[0]; + console.log("starting puppeteer..."); + const browser = await puppeteer.launch(); + const page = (await browser.pages())[0]; - // uncomment to debug what's happening inside the browser - // page.on('console', (msg) => console.log('// from console: ', msg.text())); + // uncomment to debug what's happening inside the browser + // page.on('console', (msg) => console.log('// from console: ', msg.text())); - console.log('going to the page in browser...'); - await page.goto(uri); + console.log("going to the page in browser..."); + await page.goto(uri); - console.log('clicking button...'); - await page.click('#btn'); + console.log("clicking button..."); + await page.click("#btn"); - console.log('waiting for result to appear...'); - const elem = await page.waitForSelector('#res'); + console.log("waiting for result to appear..."); + const elem = await page.waitForSelector("#res"); - console.log('getting the content of result div...'); - const content = await elem?.evaluate((x) => x.textContent); - console.log('raw result: ', content); + console.log("getting the content of result div..."); + const content = await elem?.evaluate((x) => x.textContent); + console.log("raw result: ", content); - await browser.close(); - await stopServer(localServer); + await browser.close(); + await stopServer(localServer); - if (!content) { - throw new Error('smoke test failed!'); - } + if (!content) { + throw new Error("smoke test failed!"); + } }; -test().then(() => console.log('smoke tests succeed!')); +test().then(() => console.log("smoke tests succeed!")); diff --git a/packages/@tests/smoke/web-cra-ts/tsconfig.json b/packages/@tests/smoke/web-cra-ts/tsconfig.json index 65063b6f..7d0ccc55 100644 --- a/packages/@tests/smoke/web-cra-ts/tsconfig.json +++ b/packages/@tests/smoke/web-cra-ts/tsconfig.json @@ -1,20 +1,20 @@ { - "compilerOptions": { - "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx" - }, - "include": ["src", "test"] + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src", "test"] } diff --git a/packages/@tests/smoke/web/package.json b/packages/@tests/smoke/web/package.json index 320c0b7f..3bacec7a 100644 --- a/packages/@tests/smoke/web/package.json +++ b/packages/@tests/smoke/web/package.json @@ -1,28 +1,28 @@ { - "name": "@tests/smoke_web", - "version": "0.1.0", - "description": "Smoke test web", - "main": "./dist/index.js", - "typings": "./dist/index.d.ts", - "engines": { - "node": ">=10", - "pnpm": ">=3" - }, - "type": "module", - "scripts": { - "build": "tsc", - "simulate-cdn": "http-server -p 8765 ../../../client/js-client.web.standalone/dist", - "test": "node --loader ts-node/esm ./src/index.ts", - "serve": "http-server public" - }, - "repository": "https://github.com/fluencelabs/fluence-js", - "author": "Fluence Labs", - "license": "Apache-2.0", - "dependencies": { - "@fluencelabs/js-client": "workspace:^", - "@test/test-utils": "workspace:../../test-utils" - }, - "devDependencies": { - "puppeteer": "19.7.2" - } + "name": "@tests/smoke_web", + "version": "0.1.0", + "description": "Smoke test web", + "main": "./dist/index.js", + "typings": "./dist/index.d.ts", + "engines": { + "node": ">=10", + "pnpm": ">=3" + }, + "type": "module", + "scripts": { + "build": "tsc", + "simulate-cdn": "http-server -p 8765 ../../../client/js-client.web.standalone/dist", + "test": "node --loader ts-node/esm ./src/index.ts", + "serve": "http-server public" + }, + "repository": "https://github.com/fluencelabs/fluence-js", + "author": "Fluence Labs", + "license": "Apache-2.0", + "dependencies": { + "@fluencelabs/js-client": "workspace:*", + "@test/test-utils": "workspace:*" + }, + "devDependencies": { + "puppeteer": "19.7.2" + } } diff --git a/packages/@tests/smoke/web/public/index.js b/packages/@tests/smoke/web/public/index.js index 108155f4..31228be6 100644 --- a/packages/@tests/smoke/web/public/index.js +++ b/packages/@tests/smoke/web/public/index.js @@ -1,12 +1,13 @@ const fluence = globalThis.fluence; const relay = { - multiaddr: '/ip4/127.0.0.1/tcp/9991/ws/p2p/12D3KooWBM3SdXWqGaawQDGQ6JprtwswEg3FWGvGhmgmMez1vRbR', - peerId: '12D3KooWBM3SdXWqGaawQDGQ6JprtwswEg3FWGvGhmgmMez1vRbR', + multiaddr: + "/ip4/127.0.0.1/tcp/9991/ws/p2p/12D3KooWBM3SdXWqGaawQDGQ6JprtwswEg3FWGvGhmgmMez1vRbR", + peerId: "12D3KooWBM3SdXWqGaawQDGQ6JprtwswEg3FWGvGhmgmMez1vRbR", }; const getRelayTime = () => { - const script = ` + const script = ` (xor (seq (seq @@ -36,75 +37,75 @@ const getRelayTime = () => { (call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 3]) )`; - const def = { - functionName: 'getRelayTime', - arrow: { - tag: 'arrow', - domain: { - tag: 'labeledProduct', - fields: { - relayPeerId: { - tag: 'scalar', - name: 'string', - }, - }, - }, - codomain: { - tag: 'unlabeledProduct', - items: [ - { - tag: 'scalar', - name: 'u64', - }, - ], - }, + const def = { + functionName: "getRelayTime", + arrow: { + tag: "arrow", + domain: { + tag: "labeledProduct", + fields: { + relayPeerId: { + tag: "scalar", + name: "string", + }, }, - names: { - relay: '-relay-', - getDataSrv: 'getDataSrv', - callbackSrv: 'callbackSrv', - responseSrv: 'callbackSrv', - responseFnName: 'response', - errorHandlingSrv: 'errorHandlingSrv', - errorFnName: 'error', - }, - }; + }, + codomain: { + tag: "unlabeledProduct", + items: [ + { + tag: "scalar", + name: "u64", + }, + ], + }, + }, + names: { + relay: "-relay-", + getDataSrv: "getDataSrv", + callbackSrv: "callbackSrv", + responseSrv: "callbackSrv", + responseFnName: "response", + errorHandlingSrv: "errorHandlingSrv", + errorFnName: "error", + }, + }; - const config = {}; + const config = {}; - const args = { relayPeerId: relay.peerId }; - return fluence.callAquaFunction({ - args, - def, - script, - config, - peer: fluence.defaultClient, - }); + const args = { relayPeerId: relay.peerId }; + return fluence.callAquaFunction({ + args, + def, + script, + config, + peer: fluence.defaultClient, + }); }; const main = async () => { - console.log('starting fluence...'); - fluence.defaultClient = await fluence.clientFactory(relay); - console.log('started fluence'); + console.log("starting fluence..."); + fluence.defaultClient = await fluence.clientFactory(relay, {}); + console.log("started fluence"); - console.log('getting relay time...'); - const relayTime = await getRelayTime(); - console.log('got relay time, ', relayTime); + console.log("getting relay time..."); + const relayTime = await getRelayTime(); + console.log("got relay time, ", relayTime); - console.log('stopping fluence...'); - await fluence.defaultClient.stop(); - console.log('stopped fluence...'); + console.log("stopping fluence..."); + await fluence.defaultClient.stop(); + console.log("stopped fluence..."); - return relayTime; + return relayTime; }; -const btn = document.getElementById('btn'); +const btn = document.getElementById("btn"); -btn.addEventListener('click', () => { - main().then((res) => { - const inner = document.createElement('div'); - inner.id = 'res'; - inner.innerText = res; - document.getElementById('res-placeholder').appendChild(inner); - }); +btn.addEventListener("click", () => { + main().then((res) => { + const inner = document.createElement("div"); + inner.id = "res"; + inner.innerText = res; + document.getElementById("res-placeholder").appendChild(inner); + }); }); diff --git a/packages/@tests/smoke/web/src/index.ts b/packages/@tests/smoke/web/src/index.ts index bec89991..8e718a09 100644 --- a/packages/@tests/smoke/web/src/index.ts +++ b/packages/@tests/smoke/web/src/index.ts @@ -1,49 +1,76 @@ -import puppeteer from 'puppeteer'; -import { dirname, join } from 'path'; -import { fileURLToPath } from 'url'; +/** + * Copyright 2023 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 { CDN_PUBLIC_PATH, startCdn, startContentServer, stopServer } from '@test/test-utils'; -import { symlink, access } from 'fs/promises'; +import { symlink, access } from "fs/promises"; +import { dirname, join } from "path"; +import { fileURLToPath } from "url"; + +import { + CDN_PUBLIC_PATH, + startContentServer, + stopServer, +} from "@test/test-utils"; +import puppeteer from "puppeteer"; const port = 3000; const uri = `http://localhost:${port}/`; const __dirname = dirname(fileURLToPath(import.meta.url)); -const publicPath = join(__dirname, '../public/'); +const publicPath = join(__dirname, "../public/"); const test = async () => { - const localServer = await startContentServer(port, publicPath); - try { - await access(join(publicPath, 'source')) - } catch { - await symlink(CDN_PUBLIC_PATH, join(publicPath, 'source')); - } + const localServer = await startContentServer(port, publicPath); - console.log('starting puppeteer...'); - const browser = await puppeteer.launch(); - const page = (await browser.pages())[0]; + try { + await access(join(publicPath, "source")); + } catch { + await symlink(CDN_PUBLIC_PATH, join(publicPath, "source")); + } - // uncomment to debug what's happening inside the browser - // page.on('console', (msg) => console.log('// from console: ', msg.text())); + console.log("starting puppeteer..."); + const browser = await puppeteer.launch(); + const page = (await browser.pages())[0]; - console.log('going to the page in browser...'); - await page.goto(uri); + // uncomment to debug what's happening inside the browser + // page.on('console', (msg) => console.log('// from console: ', msg.text())); - console.log('clicking button...'); - await page.click('#btn'); + console.log("going to the page in browser..."); + await page.goto(uri); - console.log('waiting for result to appear...'); - const elem = await page.waitForSelector('#res'); + console.log("clicking button..."); + await page.click("#btn"); - console.log('getting the content of result div...'); - const content = await elem?.evaluate((x) => x.textContent); - console.log('raw result: ', content); + console.log("waiting for result to appear..."); + const elem = await page.waitForSelector("#res"); - await browser.close(); - await stopServer(localServer); + console.log("getting the content of result div..."); - if (!content) { - throw new Error('smoke test failed!'); - } + const content = await elem?.evaluate((x) => { + return x.textContent; + }); + + console.log("raw result: ", content); + + await browser.close(); + await stopServer(localServer); + + if (content == null) { + throw new Error("smoke test failed!"); + } }; -test().then(() => console.log('smoke tests succeed!')); +void test().then(() => { + console.log("smoke tests succeed!"); +}); diff --git a/packages/@tests/smoke/web/tsconfig.json b/packages/@tests/smoke/web/tsconfig.json index 32d340ac..aec34796 100644 --- a/packages/@tests/smoke/web/tsconfig.json +++ b/packages/@tests/smoke/web/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../../../tsconfig.json", - "compilerOptions": { - "outDir": "./dist" - }, - "exclude": ["node_modules", "dist"] + "extends": "../../../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist" + }, + "exclude": ["node_modules", "dist", "public"] } diff --git a/packages/@tests/test-utils/package.json b/packages/@tests/test-utils/package.json index 0c6ca2bc..10b157df 100644 --- a/packages/@tests/test-utils/package.json +++ b/packages/@tests/test-utils/package.json @@ -1,24 +1,24 @@ { - "name": "@test/test-utils", - "version": "0.1.0", - "description": "Test utils", - "main": "./dist/index.js", - "typings": "./dist/index.d.ts", - "engines": { - "node": ">=10", - "pnpm": ">=3" - }, - "type": "module", - "scripts": { - "build": "tsc" - }, - "repository": "https://github.com/fluencelabs/fluence-js", - "author": "Fluence Labs", - "license": "Apache-2.0", - "dependencies": { - "serve-handler": "6.1.5" - }, - "devDependencies": { - "@types/serve-handler": "6.1.1" - } + "name": "@test/test-utils", + "version": "0.1.0", + "description": "Test utils", + "main": "./dist/index.js", + "typings": "./dist/index.d.ts", + "engines": { + "node": ">=10", + "pnpm": ">=3" + }, + "type": "module", + "scripts": { + "build": "tsc" + }, + "repository": "https://github.com/fluencelabs/fluence-js", + "author": "Fluence Labs", + "license": "Apache-2.0", + "dependencies": { + "serve-handler": "6.1.5" + }, + "devDependencies": { + "@types/serve-handler": "6.1.1" + } } diff --git a/packages/@tests/test-utils/src/index.ts b/packages/@tests/test-utils/src/index.ts index 5f8232c0..9dcd1097 100644 --- a/packages/@tests/test-utils/src/index.ts +++ b/packages/@tests/test-utils/src/index.ts @@ -1,48 +1,82 @@ -import handler from 'serve-handler'; -import { createServer } from 'http'; -import type { Server } from 'http'; +/** + * Copyright 2023 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 { dirname, join } from 'path'; -import { fileURLToPath } from 'url'; +import { createServer } from "http"; +import type { Server } from "http"; +import { dirname, join } from "path"; +import { fileURLToPath } from "url"; + +import handler from "serve-handler"; const __dirname = dirname(fileURLToPath(import.meta.url)); -export const CDN_PUBLIC_PATH = join(__dirname, '../../../core/js-client/dist/browser'); +export const CDN_PUBLIC_PATH = join( + __dirname, + "../../../core/js-client/dist/browser", +); -export const startCdn = (port: number) => startContentServer(port, CDN_PUBLIC_PATH); +export const startCdn = (port: number) => { + return startContentServer(port, CDN_PUBLIC_PATH); +}; -export const startContentServer = (port: number, publicDir: string): Promise => { - const server = createServer((request, response) => { - return handler(request, response, { - public: publicDir, - rewrites: [{ - source: '/js-client.min.js', - destination: '/source/index.umd.cjs' - }], - headers: [{ - source: '**/*', - headers: [ - { key: 'Cross-Origin-Opener-Policy', value: 'same-origin' }, - { key: 'Cross-Origin-Embedder-Policy', value: 'require-corp' } - ] - }] - }); +export const startContentServer = ( + port: number, + publicDir: string, +): Promise => { + const server = createServer((request, response) => { + void handler(request, response, { + public: publicDir, + rewrites: [ + { + source: "/js-client.min.js", + destination: "/source/index.umd.cjs", + }, + ], + headers: [ + { + source: "**/*", + headers: [ + { + key: "Cross-Origin-Opener-Policy", + value: "same-origin", + }, + { + key: "Cross-Origin-Embedder-Policy", + value: "require-corp", + }, + ], + }, + ], }); + }); - return new Promise((resolve) => { - const result = server.listen(port, () => { - console.log(`server started on port ${port}`); - console.log(`public dir ${publicDir}`); - resolve(result); - }); + return new Promise((resolve) => { + const result = server.listen(port, () => { + console.log(`server started on port ${port}`); + console.log(`public dir ${publicDir}`); + resolve(result); }); + }); }; export const stopServer = (app: Server): Promise => { - return new Promise((resolve) => { - app.close(() => { - console.log('server stopped'); - resolve(); - }); + return new Promise((resolve) => { + app.close(() => { + console.log("server stopped"); + resolve(); }); + }); }; diff --git a/packages/@tests/test-utils/tsconfig.json b/packages/@tests/test-utils/tsconfig.json index 8c892f21..78eeba95 100644 --- a/packages/@tests/test-utils/tsconfig.json +++ b/packages/@tests/test-utils/tsconfig.json @@ -1,7 +1,8 @@ { - "extends": "../../../tsconfig.json", - "compilerOptions": { - "outDir": "./dist" - }, - "exclude": ["node_modules", "dist"] + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist" + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] } diff --git a/packages/core/aqua-to-js/.eslintrc.json b/packages/core/aqua-to-js/.eslintrc.json new file mode 100644 index 00000000..0e1b20d4 --- /dev/null +++ b/packages/core/aqua-to-js/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "ignorePatterns": ["src/**/__snapshots__/**/*"] +} diff --git a/packages/core/aqua-to-js/CHANGELOG.md b/packages/core/aqua-to-js/CHANGELOG.md index 6d47fd72..458863af 100644 --- a/packages/core/aqua-to-js/CHANGELOG.md +++ b/packages/core/aqua-to-js/CHANGELOG.md @@ -2,9 +2,9 @@ ### Dependencies -* The following workspace dependencies were updated - * devDependencies - * @fluencelabs/js-client bumped to 0.1.7 +- The following workspace dependencies were updated + - devDependencies + - @fluencelabs/js-client bumped to 0.1.7 ### Dependencies @@ -20,7 +20,6 @@ ## 0.0.1 (2023-09-22) - ### Features -* **aqua-compiler:** JS-client aqua wrapper [fixes DXJ-461] ([#347](https://github.com/fluencelabs/js-client/issues/347)) ([7fff3b1](https://github.com/fluencelabs/js-client/commit/7fff3b1c0374eef76ab4e665b13cf97b5c50ff70)) +- **aqua-compiler:** JS-client aqua wrapper [fixes DXJ-461] ([#347](https://github.com/fluencelabs/js-client/issues/347)) ([7fff3b1](https://github.com/fluencelabs/js-client/commit/7fff3b1c0374eef76ab4e665b13cf97b5c50ff70)) diff --git a/packages/core/aqua-to-js/package.json b/packages/core/aqua-to-js/package.json index efda7956..211d474c 100644 --- a/packages/core/aqua-to-js/package.json +++ b/packages/core/aqua-to-js/package.json @@ -1,31 +1,30 @@ { - "name": "@fluencelabs/aqua-to-js", - "type": "module", - "version": "0.0.4", - "description": "Tool for generating aqua wrapper", - "main": "dist/index.js", - "files": [ - "dist" - ], - "scripts": { - "test": "vitest run", - "build": "tsc" - }, - "keywords": [], - "author": "Fluence Labs", - "license": "Apache-2.0", - "dependencies": { - "ts-pattern": "5.0.5" - }, - "devDependencies": { - "@fluencelabs/aqua-api": "0.12.0", - "@fluencelabs/aqua-lib": "0.7.3", - "@fluencelabs/interfaces": "workspace:*", - "@fluencelabs/js-client": "workspace:*", - "@fluencelabs/registry": "0.8.7", - "@fluencelabs/spell": "0.5.20", - "@fluencelabs/trust-graph": "0.4.7", - "typescript": "5.1.6", - "vitest": "0.29.7" - } + "name": "@fluencelabs/aqua-to-js", + "type": "module", + "version": "0.0.4", + "description": "Tool for generating aqua wrapper", + "main": "dist/index.js", + "files": [ + "dist" + ], + "scripts": { + "test": "vitest run", + "build": "tsc" + }, + "keywords": [], + "author": "Fluence Labs", + "license": "Apache-2.0", + "dependencies": { + "ts-pattern": "5.0.5" + }, + "devDependencies": { + "@fluencelabs/aqua-api": "0.12.0", + "@fluencelabs/aqua-lib": "0.7.3", + "@fluencelabs/interfaces": "workspace:*", + "@fluencelabs/js-client": "workspace:*", + "@fluencelabs/registry": "0.8.7", + "@fluencelabs/spell": "0.5.20", + "@fluencelabs/trust-graph": "0.4.7", + "vitest": "0.34.6" + } } diff --git a/packages/core/aqua-to-js/src/common.ts b/packages/core/aqua-to-js/src/common.ts index c4d8aac0..27d98974 100644 --- a/packages/core/aqua-to-js/src/common.ts +++ b/packages/core/aqua-to-js/src/common.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,85 +14,136 @@ * limitations under the License. */ -import { ArrowType, ArrowWithoutCallbacks, NonArrowType, ProductType } from '@fluencelabs/interfaces'; -import { match, P } from 'ts-pattern'; -import { getFuncArgs } from './utils.js'; +import { ArrowWithoutCallbacks, NonArrowType } from "@fluencelabs/interfaces"; +import { match, P } from "ts-pattern"; -export function genTypeName(t: NonArrowType | ProductType | ArrowWithoutCallbacks, name: string): readonly [string | undefined, string] { - const genType = typeToTs(t); - return match(t) - .with( - { tag: 'nil' }, - () => [undefined, 'void'] as const - ).with( - { tag: 'struct' }, - () => [`export type ${name} = ${genType}`, name] as const - ).with( - { tag: P.union('labeledProduct', 'unlabeledProduct') }, - (item) => { - const args = item.tag === 'labeledProduct' - ? Object.values(item.fields) - : item.items; - - if (args.length === 1) { - return genTypeName(args[0], name); - } +import { getFuncArgs } from "./utils.js"; - return [`export type ${name} = ${genType}`, name] as const; - }, - ).otherwise(() => [undefined, genType] as const); +export function genTypeName( + t: NonArrowType | ArrowWithoutCallbacks, + name: string, +): readonly [string | undefined, string] { + const genType = typeToTs(t); + return match(t) + .with({ tag: "nil" }, () => { + return [undefined, "void"] as const; + }) + .with({ tag: "struct" }, () => { + return [`export type ${name} = ${genType}`, name] as const; + }) + .with({ tag: P.union("labeledProduct", "unlabeledProduct") }, (item) => { + const args = + item.tag === "labeledProduct" ? Object.values(item.fields) : item.items; + + if (args.length === 1) { + return genTypeName(args[0], name); + } + + return [`export type ${name} = ${genType}`, name] as const; + }) + .otherwise(() => { + return [undefined, genType] as const; + }); } -export function typeToTs(t: NonArrowType | ArrowWithoutCallbacks | ProductType): string { - return match(t) +export function typeToTs(t: NonArrowType | ArrowWithoutCallbacks): string { + return match(t) + .with({ tag: "nil" }, () => { + return "null"; + }) + .with({ tag: "option" }, ({ type }) => { + return typeToTs(type) + " | null"; + }) + .with({ tag: "scalar" }, ({ name }) => { + return match(name) .with( - { tag: 'nil' }, - () => 'null' - ).with( - { tag: 'option' }, - ({ type }) => typeToTs(type) + ' | null' - ).with( - { tag: 'scalar' }, - ({ name }) => match(name) - .with(P.union('u8', 'u16', 'u32', 'u64', 'i8', 'i16', 'i32', 'i64', 'f32', 'f64'), () => 'number') - .with('bool', () => 'boolean') - .with('string', () => 'string') - .with(P._, () => 'any').exhaustive() - ).with( - { tag: 'array' }, - ({ type }) => typeToTs(type) + '[]' - ).with( - { tag: 'struct' }, - ({ fields }) => `{ ${Object.entries(fields).map(([field, type]) => `${field}: ${typeToTs(type)};`).join(' ')} }` - ).with( - { tag: 'labeledProduct' }, - ({ fields }) => `{ ${Object.entries(fields).map(([field, type]) => `${field}: ${typeToTs(type)};`).join(' ')} }` - ).with( - { tag: 'unlabeledProduct' }, - ({ items }) => `[${items.map(item => typeToTs(item)).join(', ')}]` - ).with( - { tag: 'arrow' }, - ({ tag, domain, codomain }) => { - const retType = codomain.tag === 'nil' - ? 'void' - : codomain.items.length === 1 - ? typeToTs(codomain.items[0]) - : typeToTs(codomain); - - const args = getFuncArgs(domain).map(([name, type]) => ([name, typeToTs(type)])); + P.union( + "u8", + "u16", + "u32", + "u64", + "i8", + "i16", + "i32", + "i64", + "f32", + "f64", + ), + () => { + return "number"; + }, + ) + .with("bool", () => { + return "boolean"; + }) + .with("string", () => { + return "string"; + }) + .with(P._, () => { + return "any"; + }) + .exhaustive(); + }) + .with({ tag: "array" }, ({ type }) => { + return typeToTs(type) + "[]"; + }) + .with({ tag: "struct" }, ({ fields }) => { + return `{ ${Object.entries(fields) + .map(([field, type]) => { + return `${field}: ${typeToTs(type)};`; + }) + .join(" ")} }`; + }) + .with({ tag: "labeledProduct" }, ({ fields }) => { + return `{ ${Object.entries(fields) + .map(([field, type]) => { + return `${field}: ${typeToTs(type)};`; + }) + .join(" ")} }`; + }) + .with({ tag: "unlabeledProduct" }, ({ items }) => { + return `[${items + .map((item) => { + return typeToTs(item); + }) + .join(", ")}]`; + }) + .with({ tag: "arrow" }, ({ domain, codomain }) => { + const retType = + codomain.tag === "nil" + ? "void" + : codomain.items.length === 1 + ? typeToTs(codomain.items[0]) + : typeToTs(codomain); - const generic = args.length === 0 ? 'null' : args.map(([name]) => `'${name}'`).join(' | '); - args.push(['callParams', `CallParams$$<${generic}>`]); + const args = getFuncArgs(domain).map(([name, type]) => { + return [name, typeToTs(type)]; + }); - const funcArgs = args.map(([name, type]) => `${name}: ${type}`).join(', '); + const generic = + args.length === 0 + ? "null" + : args + .map(([name]) => { + return `'${name}'`; + }) + .join(" | "); - return `(${funcArgs}) => ${retType} | Promise<${retType}>`; - } - ).with( - { tag: 'topType' }, - () => 'unknown' - ).with( - { tag: 'bottomType' }, - () => 'never' - ).exhaustive(); -} \ No newline at end of file + args.push(["callParams", `CallParams$$<${generic}>`]); + + const funcArgs = args + .map(([name, type]) => { + return `${name}: ${type}`; + }) + .join(", "); + + return `(${funcArgs}) => ${retType} | Promise<${retType}>`; + }) + .with({ tag: "topType" }, () => { + return "unknown"; + }) + .with({ tag: "bottomType" }, () => { + return "never"; + }) + .exhaustive(); +} diff --git a/packages/core/aqua-to-js/src/constants.ts b/packages/core/aqua-to-js/src/constants.ts index f6126517..3a50511a 100644 --- a/packages/core/aqua-to-js/src/constants.ts +++ b/packages/core/aqua-to-js/src/constants.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,4 +14,4 @@ * limitations under the License. */ -export const CLIENT = 'IFluenceClient$$'; \ No newline at end of file +export const CLIENT = "IFluenceClient$$"; diff --git a/packages/core/aqua-to-js/src/future.ts b/packages/core/aqua-to-js/src/future.ts deleted file mode 100644 index c894eda0..00000000 --- a/packages/core/aqua-to-js/src/future.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2023 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 { - ArrayType, - BottomType, LabeledProductType, - NilType, - NonArrowType, - OptionType, - ScalarType, - StructType, - TopType, UnlabeledProductType -} from '@fluencelabs/interfaces'; - -// Type definitions for inferring ts types from air json definition -// In the future we may remove string type declaration and move to type inference. - -type GetTsTypeFromScalar = T['name'] extends 'u8' | 'u16' | 'u32' | 'u64' | 'i8' | 'i16' | 'i32' | 'i64' | 'f32' | 'f64' - ? number - : T['name'] extends 'bool' - ? boolean - : T['name'] extends 'string' - ? string - : never; - -type MapTuple = { [K in keyof T]: T[K] extends NonArrowType ? GetTsType : never } - -type GetTsType = T extends NilType - ? null - : T extends ArrayType - ? GetTsType[] - : T extends StructType - ? { [K in keyof T]: GetTsType } - : T extends OptionType - ? GetTsType | null - : T extends ScalarType - ? GetTsTypeFromScalar - : T extends TopType - ? unknown - : T extends BottomType - ? never - : T extends Exclude, NilType> - ? MapTuple - : T extends Exclude, NilType> - ? H extends NonArrowType - ? { [K in keyof T['fields']]: GetTsType } - : never - : never; \ No newline at end of file diff --git a/packages/core/aqua-to-js/src/generate/__test__/generate.spec.ts b/packages/core/aqua-to-js/src/generate/__test__/generate.spec.ts index e2ba65f8..34effb2b 100644 --- a/packages/core/aqua-to-js/src/generate/__test__/generate.spec.ts +++ b/packages/core/aqua-to-js/src/generate/__test__/generate.spec.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,36 +14,40 @@ * limitations under the License. */ -import { describe, expect, it } from 'vitest'; -import { generateTypes, generateSources } from '../index.js'; -import { compileFromPath } from '@fluencelabs/aqua-api'; -import url from 'url'; -import { getPackageJsonContent, PackageJson } from '../../utils.js'; +import url from "url"; -describe('Aqua to js/ts compiler', () => { - it('compiles smoke tests successfully', async () => { - const res = await compileFromPath({ - filePath: url.fileURLToPath(new URL('./sources/smoke_test.aqua', import.meta.url)), - imports: ['./node_modules'], - targetType: 'air' - }); - - const pkg: PackageJson = { - ...(await getPackageJsonContent()), - version: '0.0.0', - devDependencies: { - '@fluencelabs/aqua-api': '0.0.0' - }, - }; - - const jsResult = await generateSources(res, 'js', pkg); - const jsTypes = await generateTypes(res, pkg); - - expect(jsResult).toMatchSnapshot(); - expect(jsTypes).toMatchSnapshot(); +import { compileFromPath } from "@fluencelabs/aqua-api"; +import { describe, expect, it } from "vitest"; - const tsResult = await generateSources(res, 'ts', pkg); +import { getPackageJsonContent, PackageJson } from "../../utils.js"; +import { generateTypes, generateSources } from "../index.js"; - expect(tsResult).toMatchSnapshot(); +describe("Aqua to js/ts compiler", () => { + it("compiles smoke tests successfully", async () => { + const res = await compileFromPath({ + filePath: url.fileURLToPath( + new URL("./sources/smoke_test.aqua", import.meta.url), + ), + imports: ["./node_modules"], + targetType: "air", }); -}); \ No newline at end of file + + const pkg: PackageJson = { + ...(await getPackageJsonContent()), + version: "0.0.0", + devDependencies: { + "@fluencelabs/aqua-api": "0.0.0", + }, + }; + + const jsResult = generateSources(res, "js", pkg); + const jsTypes = generateTypes(res, pkg); + + expect(jsResult).toMatchSnapshot(); + expect(jsTypes).toMatchSnapshot(); + + const tsResult = generateSources(res, "ts", pkg); + + expect(tsResult).toMatchSnapshot(); + }); +}); diff --git a/packages/core/aqua-to-js/src/generate/function.ts b/packages/core/aqua-to-js/src/generate/function.ts index d76ad2c4..0eca8ab6 100644 --- a/packages/core/aqua-to-js/src/generate/function.ts +++ b/packages/core/aqua-to-js/src/generate/function.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,24 +14,38 @@ * limitations under the License. */ -import { recursiveRenameLaquaProps } from '../utils.js'; -import { AquaFunction, TypeGenerator } from './interfaces.js'; +import { recursiveRenameLaquaProps } from "../utils.js"; -export function generateFunctions(typeGenerator: TypeGenerator, functions: Record) { - return Object.values(functions).map(func => generateFunction(typeGenerator, func)).join('\n\n'); +import { AquaFunction, TypeGenerator } from "./interfaces.js"; + +export function generateFunctions( + typeGenerator: TypeGenerator, + functions: Record, +) { + return Object.values(functions) + .map((func) => { + return generateFunction(typeGenerator, func); + }) + .join("\n\n"); } +type DeepToType = { [K in keyof T]: DeepToType }; + function generateFunction(typeGenerator: TypeGenerator, func: AquaFunction) { - const scriptConstName = func.funcDef.functionName + '_script'; - return `export const ${scriptConstName} = \` + const funcDef: DeepToType = func.funcDef; + const scriptConstName = func.funcDef.functionName + "_script"; + return `export const ${scriptConstName} = \` ${func.script}\`; ${typeGenerator.funcType(func)} -export function ${func.funcDef.functionName}(${typeGenerator.type('...args', 'any[]')}) { +export function ${func.funcDef.functionName}(${typeGenerator.type( + "...args", + "any[]", + )}) { return callFunction$$( args, - ${JSON.stringify(recursiveRenameLaquaProps(func.funcDef), null, 4)}, + ${JSON.stringify(recursiveRenameLaquaProps(funcDef), null, 4)}, ${scriptConstName} ); -}` -} \ No newline at end of file +}`; +} diff --git a/packages/core/aqua-to-js/src/generate/header.ts b/packages/core/aqua-to-js/src/generate/header.ts index 335ea0cc..8a6d0794 100644 --- a/packages/core/aqua-to-js/src/generate/header.ts +++ b/packages/core/aqua-to-js/src/generate/header.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,25 +14,33 @@ * limitations under the License. */ -import { OutputType } from './interfaces.js'; -import { PackageJson } from '../utils.js'; +import { PackageJson } from "../utils.js"; -export default function generateHeader({ version, devDependencies }: PackageJson, outputType: OutputType) { - return `/* eslint-disable */ +import { OutputType } from "./interfaces.js"; + +export default function generateHeader( + { version, devDependencies }: PackageJson, + outputType: OutputType, +) { + return `/* eslint-disable */ // @ts-nocheck /** * * This file is generated using: - * @fluencelabs/aqua-api version: ${devDependencies['@fluencelabs/aqua-api']} + * @fluencelabs/aqua-api version: ${devDependencies["@fluencelabs/aqua-api"]} * @fluencelabs/aqua-to-js version: ${version} * If you find any bugs in generated AIR, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues * If you find any bugs in generated JS/TS, please write an issue on GitHub: https://github.com/fluencelabs/js-client/issues * */ -${outputType === 'ts' ? 'import type { IFluenceClient as IFluenceClient$$, CallParams as CallParams$$ } from \'@fluencelabs/js-client\';' : ''} +${ + outputType === "ts" + ? "import type { IFluenceClient as IFluenceClient$$, CallParams as CallParams$$ } from '@fluencelabs/js-client';" + : "" +} import { v5_callFunction as callFunction$$, v5_registerService as registerService$$, } from '@fluencelabs/js-client';`; -} \ No newline at end of file +} diff --git a/packages/core/aqua-to-js/src/generate/index.ts b/packages/core/aqua-to-js/src/generate/index.ts index cb1aa4ac..c7077c17 100644 --- a/packages/core/aqua-to-js/src/generate/index.ts +++ b/packages/core/aqua-to-js/src/generate/index.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,46 +14,80 @@ * limitations under the License. */ -import { CompilationResult, JSTypeGenerator, OutputType, TSTypeGenerator, TypeGenerator } from './interfaces.js'; -import { PackageJson } from '../utils.js'; -import { generateServices } from './service.js'; -import { generateFunctions } from './function.js'; -import header from './header.js'; +import { PackageJson } from "../utils.js"; -const typeGenerators: Record = { - 'js': new JSTypeGenerator(), - 'ts': new TSTypeGenerator() +import { generateFunctions } from "./function.js"; +import header from "./header.js"; +import { + CompilationResult, + JSTypeGenerator, + OutputType, + TSTypeGenerator, + TypeGenerator, +} from "./interfaces.js"; +import { generateServices } from "./service.js"; + +const typeGenerators: Record = { + js: new JSTypeGenerator(), + ts: new TSTypeGenerator(), }; -export async function generateSources({ services, functions }: CompilationResult, outputType: OutputType, packageJson: PackageJson) { - const typeGenerator = typeGenerators[outputType]; - return `${header(packageJson, outputType)} +export function generateSources( + { services, functions }: CompilationResult, + outputType: OutputType, + packageJson: PackageJson, +) { + const typeGenerator = typeGenerators[outputType]; + return `${header(packageJson, outputType)} -${Object.entries(services).length > 0 ? `// Services +${ + Object.entries(services).length > 0 + ? `// Services ${generateServices(typeGenerator, services)} -` : ''} -${Object.entries(functions).length > 0 ? `// Functions +` + : "" +} +${ + Object.entries(functions).length > 0 + ? `// Functions ${generateFunctions(typeGenerator, functions)} -`: ''}` +` + : "" +}`; } -export async function generateTypes({ services, functions }: CompilationResult, packageJson: PackageJson) { - const typeGenerator = typeGenerators['ts']; - - const generatedServices = Object.entries(services) - .map(([srvName, srvDef]) => typeGenerator.serviceType(srvName, srvDef)) - .join('\n'); - - const generatedFunctions = Object.entries(functions) - .map(([funcName, funcDef]) => typeGenerator.funcType(funcDef)) - .join('\n'); - - return `${header(packageJson, 'ts')} +export function generateTypes( + { services, functions }: CompilationResult, + packageJson: PackageJson, +) { + const typeGenerator = typeGenerators["ts"]; -${Object.entries(services).length > 0 ? `// Services + const generatedServices = Object.entries(services) + .map(([srvName, srvDef]) => { + return typeGenerator.serviceType(srvName, srvDef); + }) + .join("\n"); + + const generatedFunctions = Object.entries(functions) + .map(([, funcDef]) => { + return typeGenerator.funcType(funcDef); + }) + .join("\n"); + + return `${header(packageJson, "ts")} + +${ + Object.entries(services).length > 0 + ? `// Services ${generatedServices} -` : ''} -${Object.entries(functions).length > 0 ? `// Functions +` + : "" +} +${ + Object.entries(functions).length > 0 + ? `// Functions ${generatedFunctions} -`: ''}` -} \ No newline at end of file +` + : "" +}`; +} diff --git a/packages/core/aqua-to-js/src/generate/interfaces.ts b/packages/core/aqua-to-js/src/generate/interfaces.ts index 0769a2a8..97693fad 100644 --- a/packages/core/aqua-to-js/src/generate/interfaces.ts +++ b/packages/core/aqua-to-js/src/generate/interfaces.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,123 +14,160 @@ * limitations under the License. */ -import { CLIENT } from '../constants.js'; -import { FunctionCallDef, ServiceDef } from '@fluencelabs/interfaces'; -import { genTypeName, typeToTs } from '../common.js'; -import { capitalize, getFuncArgs } from '../utils.js'; +import { FunctionCallDef, ServiceDef } from "@fluencelabs/interfaces"; + +import { genTypeName, typeToTs } from "../common.js"; +import { CLIENT } from "../constants.js"; +import { capitalize, getFuncArgs } from "../utils.js"; export interface TypeGenerator { - type(field: string, type: string): string; - generic(field: string, type: string): string; - bang(field: string): string; - funcType(funcDef: AquaFunction): string; - serviceType(srvName: string, srvDef: ServiceDef): string; + type(field: string, type: string): string; + generic(field: string, type: string): string; + bang(field: string): string; + funcType(funcDef: AquaFunction): string; + serviceType(srvName: string, srvDef: ServiceDef): string; } export class TSTypeGenerator implements TypeGenerator { - bang(field: string): string { - return `${field}!`; - } + bang(field: string): string { + return `${field}!`; + } - generic(field: string, type: string): string { - return `${field}<${type}>`; - } + generic(field: string, type: string): string { + return `${field}<${type}>`; + } - type(field: string, type: string): string { - return `${field}: ${type}`; - } + type(field: string, type: string): string { + return `${field}: ${type}`; + } - funcType({ funcDef }: AquaFunction): string { - const args = getFuncArgs(funcDef.arrow.domain).map(([name, type]) => { - const [typeDesc, t] = genTypeName(type, capitalize(funcDef.functionName) + 'Arg' + capitalize(name)); - return [typeDesc, `${name}: ${t}`] as const; - }); - args.push([undefined, `config?: {ttl?: number}`]); + funcType({ funcDef }: AquaFunction): string { + const args = getFuncArgs(funcDef.arrow.domain).map(([name, type]) => { + const [typeDesc, t] = genTypeName( + type, + capitalize(funcDef.functionName) + "Arg" + capitalize(name), + ); - const argsDefs = args.map(([, def]) => " " + def); - const argsDesc = args.filter(([desc]) => desc !== undefined).map(([desc]) => desc); + return [typeDesc, `${name}: ${t}`] as const; + }); - const functionOverloads = [ - argsDefs.join(',\n'), - [` peer: ${CLIENT}`, ...argsDefs].join(',\n') - ]; - - const [resTypeDesc, resType] = genTypeName(funcDef.arrow.codomain, capitalize(funcDef.functionName) + "Result"); + args.push([undefined, `config?: {ttl?: number}`]); - return [ - argsDesc.join('\n'), - resTypeDesc || "", - functionOverloads.flatMap(fo => [ - `export function ${funcDef.functionName}(`, - fo, - `): Promise<${resType}>;`, - '' - ]).join('\n') - ].filter(s => s !== '').join('\n\n'); - } + const argsDefs = args.map(([, def]) => { + return " " + def; + }); - serviceType(srvName: string, srvDef: ServiceDef): string { - const members = srvDef.functions.tag === 'nil' ? [] : Object.entries(srvDef.functions.fields); + const argsDesc = args + .filter(([desc]) => { + return desc !== undefined; + }) + .map(([desc]) => { + return desc; + }); - const interfaceDefs = members - .map(([name, arrow]) => { - return ` ${name}: ${typeToTs(arrow)};`; - }) - .join('\n'); + const functionOverloads = [ + argsDefs.join(",\n"), + [` peer: ${CLIENT}`, ...argsDefs].join(",\n"), + ]; - const interfaces = [`export interface ${srvName}Def {`, interfaceDefs, '}'].join('\n'); - - const peerDecl = `peer: ${CLIENT}`; - const serviceDecl = `service: ${srvName}Def`; - const serviceIdDecl = `serviceId: string`; - const registerServiceArgs = [ - [serviceDecl], - [serviceIdDecl, serviceDecl], - [peerDecl, serviceDecl], - [peerDecl, serviceIdDecl, serviceDecl] - ]; + const [resTypeDesc, resType] = genTypeName( + funcDef.arrow.codomain, + capitalize(funcDef.functionName) + "Result", + ); - return [interfaces, ...registerServiceArgs.map(registerServiceArg => { - const args = registerServiceArg.join(', '); - return `export function register${srvName}(${args}): void;` - })].join('\n'); - } + return [ + argsDesc.join("\n"), + resTypeDesc ?? "", + functionOverloads + .flatMap((fo) => { + return [ + `export function ${funcDef.functionName}(`, + fo, + `): Promise<${resType}>;`, + "", + ]; + }) + .join("\n"), + ] + .filter((s) => { + return s !== ""; + }) + .join("\n\n"); + } + + serviceType(srvName: string, srvDef: ServiceDef): string { + const members = + srvDef.functions.tag === "nil" + ? [] + : Object.entries(srvDef.functions.fields); + + const interfaceDefs = members + .map(([name, arrow]) => { + return ` ${name}: ${typeToTs(arrow)};`; + }) + .join("\n"); + + const interfaces = [ + `export interface ${srvName}Def {`, + interfaceDefs, + "}", + ].join("\n"); + + const peerDecl = `peer: ${CLIENT}`; + const serviceDecl = `service: ${srvName}Def`; + const serviceIdDecl = `serviceId: string`; + + const registerServiceArgs = [ + [serviceDecl], + [serviceIdDecl, serviceDecl], + [peerDecl, serviceDecl], + [peerDecl, serviceIdDecl, serviceDecl], + ]; + + return [ + interfaces, + ...registerServiceArgs.map((registerServiceArg) => { + const args = registerServiceArg.join(", "); + return `export function register${srvName}(${args}): void;`; + }), + ].join("\n"); + } } export class JSTypeGenerator implements TypeGenerator { - bang(field: string): string { - return field; - } + bang(field: string): string { + return field; + } - generic(field: string, type: string): string { - return field; - } + generic(field: string): string { + return field; + } - type(field: string, type: string): string { - return field; - } + type(field: string): string { + return field; + } - funcType(): string { - return ''; - } + funcType(): string { + return ""; + } - serviceType(): string { - return ''; - } + serviceType(): string { + return ""; + } } export interface AquaFunction { - funcDef: FunctionCallDef; - script: string; + funcDef: FunctionCallDef; + script: string; } export interface CompilationResult { - services: Record; - functions: Record; + services: Record; + functions: Record; } export interface EntityGenerator { - generate(compilationResult: CompilationResult): string; + generate(compilationResult: CompilationResult): string; } -export type OutputType = 'js' | 'ts'; \ No newline at end of file +export type OutputType = "js" | "ts"; diff --git a/packages/core/aqua-to-js/src/generate/service.ts b/packages/core/aqua-to-js/src/generate/service.ts index dfea63f2..4c643bd6 100644 --- a/packages/core/aqua-to-js/src/generate/service.ts +++ b/packages/core/aqua-to-js/src/generate/service.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,45 +14,74 @@ * limitations under the License. */ -import { ServiceDef } from '@fluencelabs/interfaces'; -import { recursiveRenameLaquaProps } from '../utils.js'; -import { TypeGenerator } from './interfaces.js'; +import { ServiceDef } from "@fluencelabs/interfaces"; + +import { recursiveRenameLaquaProps } from "../utils.js"; + +import { TypeGenerator } from "./interfaces.js"; interface DefaultServiceId { - s_Some__f_value?: string + s_Some__f_value?: string; } -export function generateServices(typeGenerator: TypeGenerator, services: Record) { - const generated = Object.entries(services).map(([srvName, srvDef]) => generateService(typeGenerator, srvName, srvDef)).join('\n\n'); +export function generateServices( + typeGenerator: TypeGenerator, + services: Record, +) { + const generated = Object.entries(services) + .map(([srvName, srvDef]) => { + return generateService(typeGenerator, srvName, srvDef); + }) + .join("\n\n"); - return generated + '\n'; + return generated + "\n"; } -function generateService(typeGenerator: TypeGenerator, srvName: string, srvDef: ServiceDef) { - return [ - typeGenerator.serviceType(srvName, srvDef), - generateRegisterServiceOverload(typeGenerator, srvName, srvDef) - ].join('\n'); +function generateService( + typeGenerator: TypeGenerator, + srvName: string, + srvDef: ServiceDef, +) { + return [ + typeGenerator.serviceType(srvName, srvDef), + generateRegisterServiceOverload(typeGenerator, srvName, srvDef), + ].join("\n"); } -function generateRegisterServiceOverload(typeGenerator: TypeGenerator, srvName: string, srvDef: ServiceDef) { - return [ - `export function register${srvName}(${typeGenerator.type('...args', 'any[]')}) {`, - ' registerService$$(', - ' args,', - ` ${serviceToJson(srvDef)}`, - ' );', - '}' - ].join('\n'); +function generateRegisterServiceOverload( + typeGenerator: TypeGenerator, + srvName: string, + srvDef: ServiceDef, +) { + return [ + `export function register${srvName}(${typeGenerator.type( + "...args", + "any[]", + )}) {`, + " registerService$$(", + " args,", + ` ${serviceToJson(srvDef)}`, + " );", + "}", + ].join("\n"); } function serviceToJson(service: ServiceDef): string { - return JSON.stringify({ - ...( - (service.defaultServiceId as DefaultServiceId)?.s_Some__f_value - ? { defaultServiceId: (service.defaultServiceId as DefaultServiceId).s_Some__f_value } - : {} - ), - functions: recursiveRenameLaquaProps(service.functions) - }, null, 4); -} \ No newline at end of file + return JSON.stringify( + { + // This assertion is required because aqua-api gives bad types + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + ...((service.defaultServiceId as DefaultServiceId).s_Some__f_value != null + ? { + defaultServiceId: + // This assertion is required because aqua-api gives bad types + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + (service.defaultServiceId as DefaultServiceId).s_Some__f_value, + } + : {}), + functions: recursiveRenameLaquaProps(service.functions), + }, + null, + 4, + ); +} diff --git a/packages/core/aqua-to-js/src/index.ts b/packages/core/aqua-to-js/src/index.ts index 081713b7..44c21160 100644 --- a/packages/core/aqua-to-js/src/index.ts +++ b/packages/core/aqua-to-js/src/index.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,34 +14,47 @@ * limitations under the License. */ -import { - generateSources, - generateTypes, -} from './generate/index.js'; -import { CompilationResult, OutputType } from './generate/interfaces.js'; -import { getPackageJsonContent } from './utils.js'; +import { generateSources, generateTypes } from "./generate/index.js"; +import { CompilationResult, OutputType } from "./generate/interfaces.js"; +import { getPackageJsonContent } from "./utils.js"; interface JsOutput { - sources: string; - types: string; + sources: string; + types: string; } interface TsOutput { - sources: string; + sources: string; } type LanguageOutput = { - "js": JsOutput, - "ts": TsOutput + js: JsOutput; + ts: TsOutput; }; -export default async function aquaToJs(res: CompilationResult, outputType: T): Promise { - const packageJson = await getPackageJsonContent(); - - return outputType === 'js' ? { - sources: await generateSources(res, 'js', packageJson), - types: await generateTypes(res, packageJson) - } : { - sources: await generateSources(res, 'ts', packageJson), - } as LanguageOutput[T]; -}; \ No newline at end of file +type NothingToGenerate = null; + +export default async function aquaToJs( + res: CompilationResult, + outputType: T, +): Promise { + if ( + Object.keys(res.services).length === 0 && + Object.keys(res.functions).length === 0 + ) { + return null; + } + + const packageJson = await getPackageJsonContent(); + + return outputType === "js" + ? { + sources: generateSources(res, "js", packageJson), + types: generateTypes(res, packageJson), + } + : // TODO: probably there is a way to remove this type assert + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + ({ + sources: generateSources(res, "ts", packageJson), + } as LanguageOutput[T]); +} diff --git a/packages/core/aqua-to-js/src/utils.ts b/packages/core/aqua-to-js/src/utils.ts index a18657c3..cd2a5d1d 100644 --- a/packages/core/aqua-to-js/src/utils.ts +++ b/packages/core/aqua-to-js/src/utils.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,57 +14,94 @@ * limitations under the License. */ -import { ArrowWithoutCallbacks, NonArrowType, ProductType } from '@fluencelabs/interfaces'; -import { readFile } from 'fs/promises'; -import path from 'path'; +import assert from "assert"; +import { readFile } from "fs/promises"; +import path from "path"; + +import { + ArrowType, + ArrowWithoutCallbacks, + JSONValue, + LabeledProductType, + NilType, + SimpleTypes, + UnlabeledProductType, +} from "@fluencelabs/interfaces"; export interface PackageJson { - name: string; - version: string; - devDependencies: { - ['@fluencelabs/aqua-api']: string - } + name: string; + version: string; + devDependencies: { + ["@fluencelabs/aqua-api"]: string; + }; } export async function getPackageJsonContent(): Promise { - const content = await readFile(new URL(path.join('..', 'package.json'), import.meta.url), 'utf-8'); - return JSON.parse(content); + const content = await readFile( + new URL(path.join("..", "package.json"), import.meta.url), + "utf-8", + ); + + // TODO: Add validation here + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return JSON.parse(content) as PackageJson; } -export function getFuncArgs(domain: ProductType): [string, NonArrowType | ArrowWithoutCallbacks][] { - if (domain.tag === 'labeledProduct') { - return Object.entries(domain.fields).map(([label, type]) => [label, type]); - } else if (domain.tag === 'unlabeledProduct') { - return domain.items.map((type, index) => ['arg' + index, type]); - } else { - return []; - } +export function getFuncArgs( + domain: + | LabeledProductType> + | UnlabeledProductType + | NilType, +): [string, SimpleTypes | ArrowWithoutCallbacks][] { + if (domain.tag === "labeledProduct") { + return Object.entries(domain.fields).map(([label, type]) => { + return [label, type]; + }); + } else if (domain.tag === "unlabeledProduct") { + return domain.items.map((type, index) => { + return ["arg" + index, type]; + }); + } else { + return []; + } } -export function recursiveRenameLaquaProps(obj: unknown): unknown { - if (typeof obj !== 'object' || obj === null) return obj; +export function recursiveRenameLaquaProps(obj: JSONValue): unknown { + if (typeof obj !== "object" || obj === null) { + return obj; + } - if (Array.isArray(obj)) { - return obj.map(item => recursiveRenameLaquaProps(item)); + if (Array.isArray(obj)) { + return obj.map((item) => { + return recursiveRenameLaquaProps(item); + }); + } + + return Object.getOwnPropertyNames(obj).reduce((acc, prop) => { + let accessProp = prop; + + if (prop.includes("Laqua_js")) { + // Last part of the property separated by "_" is a correct name + const refinedProperty = prop.split("_").pop(); + + if (refinedProperty == null) { + throw new Error(`Bad property name: ${prop}.`); + } + + if (refinedProperty in obj) { + accessProp = refinedProperty; + } } - return Object.getOwnPropertyNames(obj).reduce((acc, prop) => { - let accessProp = prop; - if (prop.includes('Laqua_js')) { - // Last part of the property separated by "_" is a correct name - const refinedProperty = prop.split('_').pop()!; - if (refinedProperty in obj) { - accessProp = refinedProperty; - } - } + assert(accessProp in obj); - return { - ...acc, - [accessProp]: recursiveRenameLaquaProps(obj[accessProp as keyof typeof obj]) - }; - }, {}); + return { + ...acc, + [accessProp]: recursiveRenameLaquaProps(obj[accessProp]), + }; + }, {}); } export function capitalize(str: string) { - return str.slice(0, 1).toUpperCase() + str.slice(1); -} \ No newline at end of file + return str.slice(0, 1).toUpperCase() + str.slice(1); +} diff --git a/packages/core/aqua-to-js/tsconfig.json b/packages/core/aqua-to-js/tsconfig.json index 1dbe1a1d..db12131a 100644 --- a/packages/core/aqua-to-js/tsconfig.json +++ b/packages/core/aqua-to-js/tsconfig.json @@ -1,10 +1,10 @@ { - "extends": "../../../tsconfig.json", - "compilerOptions": { - "esModuleInterop": true, - "resolveJsonModule": true, - "outDir": "./dist" - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "src/**/__test__"], + "extends": "../../../tsconfig.json", + "compilerOptions": { + "esModuleInterop": true, + "resolveJsonModule": true, + "outDir": "./dist" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "src/**/__test__"] } diff --git a/packages/core/interfaces/CHANGELOG.md b/packages/core/interfaces/CHANGELOG.md index 9db7edbe..65eaaefa 100644 --- a/packages/core/interfaces/CHANGELOG.md +++ b/packages/core/interfaces/CHANGELOG.md @@ -2,85 +2,74 @@ ## [0.8.2](https://github.com/fluencelabs/js-client/compare/interfaces-v0.8.1...interfaces-v0.8.2) (2023-08-24) - ### Features -* use marine-js 0.7.2 ([#321](https://github.com/fluencelabs/js-client/issues/321)) ([c99a509](https://github.com/fluencelabs/js-client/commit/c99a509c8743471856b0beb25696ffe7357d5399)) +- use marine-js 0.7.2 ([#321](https://github.com/fluencelabs/js-client/issues/321)) ([c99a509](https://github.com/fluencelabs/js-client/commit/c99a509c8743471856b0beb25696ffe7357d5399)) ## [0.8.1](https://github.com/fluencelabs/js-client/compare/interfaces-v0.8.0...interfaces-v0.8.1) (2023-08-08) - ### Bug Fixes -* **deps:** update dependency @fluencelabs/avm to v0.43.1 ([#322](https://github.com/fluencelabs/js-client/issues/322)) ([c1d1fa6](https://github.com/fluencelabs/js-client/commit/c1d1fa6659b6dc2c6707786748b3410fab7f1bcd)) +- **deps:** update dependency @fluencelabs/avm to v0.43.1 ([#322](https://github.com/fluencelabs/js-client/issues/322)) ([c1d1fa6](https://github.com/fluencelabs/js-client/commit/c1d1fa6659b6dc2c6707786748b3410fab7f1bcd)) ## [0.8.0](https://github.com/fluencelabs/js-client/compare/interfaces-v0.7.6...interfaces-v0.8.0) (2023-06-29) - ### ⚠ BREAKING CHANGES -* **avm:** avm 0.40.0 (https://github.com/fluencelabs/js-client/pull/315) +- **avm:** avm 0.40.0 (https://github.com/fluencelabs/js-client/pull/315) ### Features -* **avm:** avm 0.40.0 (https://github.com/fluencelabs/js-client/pull/315) ([8bae6e2](https://github.com/fluencelabs/js-client/commit/8bae6e24e62153b567f320ccecc7bce76bc826d1)) +- **avm:** avm 0.40.0 (https://github.com/fluencelabs/js-client/pull/315) ([8bae6e2](https://github.com/fluencelabs/js-client/commit/8bae6e24e62153b567f320ccecc7bce76bc826d1)) ## [0.7.6](https://github.com/fluencelabs/js-client/compare/interfaces-v0.7.5...interfaces-v0.7.6) (2023-06-20) - ### Features -* support signatures [fixes DXJ-389] ([#310](https://github.com/fluencelabs/js-client/issues/310)) ([a60dfe0](https://github.com/fluencelabs/js-client/commit/a60dfe0d680b4d9ac5092dec64e2ebf478bf80eb)) +- support signatures [fixes DXJ-389] ([#310](https://github.com/fluencelabs/js-client/issues/310)) ([a60dfe0](https://github.com/fluencelabs/js-client/commit/a60dfe0d680b4d9ac5092dec64e2ebf478bf80eb)) ## [0.7.5](https://github.com/fluencelabs/js-client/compare/interfaces-v0.7.4...interfaces-v0.7.5) (2023-04-04) - ### Features -* Cleaning up technical debts ([#295](https://github.com/fluencelabs/js-client/issues/295)) ([0b2f12d](https://github.com/fluencelabs/js-client/commit/0b2f12d8ac223db341d6c30ff403166b3eae2e56)) +- Cleaning up technical debts ([#295](https://github.com/fluencelabs/js-client/issues/295)) ([0b2f12d](https://github.com/fluencelabs/js-client/commit/0b2f12d8ac223db341d6c30ff403166b3eae2e56)) ## [0.7.4](https://github.com/fluencelabs/js-client/compare/interfaces-v0.7.3...interfaces-v0.7.4) (2023-03-31) - ### Features -* **logs:** Use `debug.js` library for logging [DXJ-327] ([#285](https://github.com/fluencelabs/js-client/issues/285)) ([e95c34a](https://github.com/fluencelabs/js-client/commit/e95c34a79220bd8ecdcee806802ac3d69a2af0cb)) +- **logs:** Use `debug.js` library for logging [DXJ-327] ([#285](https://github.com/fluencelabs/js-client/issues/285)) ([e95c34a](https://github.com/fluencelabs/js-client/commit/e95c34a79220bd8ecdcee806802ac3d69a2af0cb)) ## [0.7.3](https://github.com/fluencelabs/js-client/compare/interfaces-v0.7.2...interfaces-v0.7.3) (2023-02-16) - ### Bug Fixes -* Trigger release to publish packages that were built ([#262](https://github.com/fluencelabs/js-client/issues/262)) ([47abf38](https://github.com/fluencelabs/js-client/commit/47abf3882956ffbdc52df372db26ba6252e8306b)) +- Trigger release to publish packages that were built ([#262](https://github.com/fluencelabs/js-client/issues/262)) ([47abf38](https://github.com/fluencelabs/js-client/commit/47abf3882956ffbdc52df372db26ba6252e8306b)) ## [0.7.2](https://github.com/fluencelabs/js-client/compare/interfaces-v0.7.1...interfaces-v0.7.2) (2023-02-16) - ### Features -* Add `getRelayPeerId` method for `IFluenceClient` ([#260](https://github.com/fluencelabs/js-client/issues/260)) ([a10278a](https://github.com/fluencelabs/js-client/commit/a10278afaa782a307feb10c4eac060094c101230)) +- Add `getRelayPeerId` method for `IFluenceClient` ([#260](https://github.com/fluencelabs/js-client/issues/260)) ([a10278a](https://github.com/fluencelabs/js-client/commit/a10278afaa782a307feb10c4eac060094c101230)) ## [0.7.1](https://github.com/fluencelabs/js-client/compare/interfaces-v0.7.0...interfaces-v0.7.1) (2023-02-16) - ### Features -* Simplify JS Client public API ([#257](https://github.com/fluencelabs/js-client/issues/257)) ([9daaf41](https://github.com/fluencelabs/js-client/commit/9daaf410964d43228192c829c7ff785db6e88081)) +- Simplify JS Client public API ([#257](https://github.com/fluencelabs/js-client/issues/257)) ([9daaf41](https://github.com/fluencelabs/js-client/commit/9daaf410964d43228192c829c7ff785db6e88081)) ## [0.7.0](https://github.com/fluencelabs/fluence-js/compare/interfaces-v0.6.0...interfaces-v0.7.0) (2023-02-15) - ### ⚠ BREAKING CHANGES -* Expose updated JS Client API via `js-client.api` package ([#246](https://github.com/fluencelabs/fluence-js/issues/246)) -* Standalone web JS Client ([#243](https://github.com/fluencelabs/fluence-js/issues/243)) +- Expose updated JS Client API via `js-client.api` package ([#246](https://github.com/fluencelabs/fluence-js/issues/246)) +- Standalone web JS Client ([#243](https://github.com/fluencelabs/fluence-js/issues/243)) ### Features -* Expose updated JS Client API via `js-client.api` package ([#246](https://github.com/fluencelabs/fluence-js/issues/246)) ([d4bb8fb](https://github.com/fluencelabs/fluence-js/commit/d4bb8fb42964b3ba25154232980b9ae82c21e627)) -* Standalone web JS Client ([#243](https://github.com/fluencelabs/fluence-js/issues/243)) ([9667c4f](https://github.com/fluencelabs/fluence-js/commit/9667c4fec6868f984bba13249f3c47d293396406)) - +- Expose updated JS Client API via `js-client.api` package ([#246](https://github.com/fluencelabs/fluence-js/issues/246)) ([d4bb8fb](https://github.com/fluencelabs/fluence-js/commit/d4bb8fb42964b3ba25154232980b9ae82c21e627)) +- Standalone web JS Client ([#243](https://github.com/fluencelabs/fluence-js/issues/243)) ([9667c4f](https://github.com/fluencelabs/fluence-js/commit/9667c4fec6868f984bba13249f3c47d293396406)) ### Bug Fixes -* NodeJS package building ([#248](https://github.com/fluencelabs/fluence-js/issues/248)) ([0d05e51](https://github.com/fluencelabs/fluence-js/commit/0d05e517d89529af513fcb96cfa6c722ccc357a7)) +- NodeJS package building ([#248](https://github.com/fluencelabs/fluence-js/issues/248)) ([0d05e51](https://github.com/fluencelabs/fluence-js/commit/0d05e517d89529af513fcb96cfa6c722ccc357a7)) diff --git a/packages/core/interfaces/package.json b/packages/core/interfaces/package.json index 2c116ea4..7fc887a0 100644 --- a/packages/core/interfaces/package.json +++ b/packages/core/interfaces/package.json @@ -1,56 +1,56 @@ { - "name": "@fluencelabs/interfaces", - "type": "module", - "version": "0.8.2", - "description": "Interfaces", - "main": "./dist/index.js", - "typings": "./dist/index.d.ts", - "exports": { - ".": { - "import": "./dist/index.js", - "types": "./dist/index.d.ts" - }, - "./fluenceClient": { - "import": "./dist/fluenceClient.js", - "types": "./dist/fluenceClient.d.ts" - }, - "./compilerSupport": { - "import": "./dist/compilerSupport.js", - "types": "./dist/compilerSupport.d.ts" - }, - "./dist/fluenceClient": { - "import": "./dist/fluenceClient.js", - "types": "./dist/fluenceClient.d.ts" - }, - "./dist/compilerSupport": { - "import": "./dist/compilerSupport.js", - "types": "./dist/compilerSupport.d.ts" - } + "name": "@fluencelabs/interfaces", + "type": "module", + "version": "0.8.2", + "description": "Interfaces", + "main": "./dist/index.js", + "typings": "./dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" }, - "typesVersions": { - "*": { - "fluenceClient.d.ts": [ - "./dist/fluenceClient.d.ts" - ], - "compilerSupport.d.ts": [ - "./dist/compilerSupport.d.ts" - ] - } + "./fluenceClient": { + "import": "./dist/fluenceClient.js", + "types": "./dist/fluenceClient.d.ts" }, - "engines": { - "node": ">=10", - "pnpm": ">=3" + "./compilerSupport": { + "import": "./dist/compilerSupport.js", + "types": "./dist/compilerSupport.d.ts" }, - "scripts": { - "build": "tsc" + "./dist/fluenceClient": { + "import": "./dist/fluenceClient.js", + "types": "./dist/fluenceClient.d.ts" }, - "repository": "https://github.com/fluencelabs/fluence-js", - "author": "Fluence Labs", - "license": "Apache-2.0", - "dependencies": {}, - "devDependencies": { - "@multiformats/multiaddr": "11.3.0", - "@fluencelabs/avm": "0.48.0", - "@fluencelabs/marine-js": "0.7.2" + "./dist/compilerSupport": { + "import": "./dist/compilerSupport.js", + "types": "./dist/compilerSupport.d.ts" } + }, + "typesVersions": { + "*": { + "fluenceClient.d.ts": [ + "./dist/fluenceClient.d.ts" + ], + "compilerSupport.d.ts": [ + "./dist/compilerSupport.d.ts" + ] + } + }, + "engines": { + "node": ">=10", + "pnpm": ">=3" + }, + "scripts": { + "build": "tsc" + }, + "repository": "https://github.com/fluencelabs/fluence-js", + "author": "Fluence Labs", + "license": "Apache-2.0", + "dependencies": {}, + "devDependencies": { + "@multiformats/multiaddr": "11.3.0", + "@fluencelabs/avm": "0.48.0", + "hotscript": "1.0.13" + } } diff --git a/packages/core/interfaces/src/commonTypes.ts b/packages/core/interfaces/src/commonTypes.ts index d7e64f5a..6cff6f5f 100644 --- a/packages/core/interfaces/src/commonTypes.ts +++ b/packages/core/interfaces/src/commonTypes.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,7 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import type { SecurityTetraplet } from '@fluencelabs/avm'; + +import type { SecurityTetraplet } from "@fluencelabs/avm"; + +import { InterfaceToType, MaybePromise } from "./utils.js"; /** * Peer ID's id as a base58 string (multihash/CIDv0). @@ -24,42 +27,61 @@ export type PeerIdB58 = string; * Node of the Fluence network specified as a pair of node's multiaddr and it's peer id */ export type Node = { - peerId: PeerIdB58; - multiaddr: string; + peerId: PeerIdB58; + multiaddr: string; }; /** * Additional information about a service call * @typeparam ArgName */ -export interface CallParams { - /** - * The identifier of particle which triggered the call - */ - particleId: string; +export type CallParams = { + /** + * The identifier of particle which triggered the call + */ + particleId: string; - /** - * The peer id which created the particle - */ - initPeerId: PeerIdB58; + /** + * The peer id which created the particle + */ + initPeerId: PeerIdB58; - /** - * Particle's timestamp when it was created - */ - timestamp: number; + /** + * Particle's timestamp when it was created + */ + timestamp: number; - /** - * Time to live in milliseconds. The time after the particle should be expired - */ - ttl: number; + /** + * Time to live in milliseconds. The time after the particle should be expired + */ + ttl: number; - /** - * Particle's signature - */ - signature?: string; + /** + * Particle's signature + */ + signature?: string; - /** - * Security tetraplets - */ - tetraplets: ArgName extends string ? Record : Record; -} + /** + * Security tetraplets + */ + tetraplets: ArgName extends string + ? Record[]> + : Record; +}; + +export type ServiceImpl = Record< + string, + ( + ...args: [...JSONArray, CallParams] + ) => MaybePromise +>; + +export type JSONValue = + | string + | number + | boolean + | null + | { [x: string]: JSONValue } + | Array; +export type JSONArray = Array; +export type JSONObject = { [x: string]: JSONValue }; diff --git a/packages/core/interfaces/src/compilerSupport/aquaTypeDefinitions.ts b/packages/core/interfaces/src/compilerSupport/aquaTypeDefinitions.ts index e18d3c78..4d34cc82 100644 --- a/packages/core/interfaces/src/compilerSupport/aquaTypeDefinitions.ts +++ b/packages/core/interfaces/src/compilerSupport/aquaTypeDefinitions.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,257 +13,279 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -type SimpleTypes = ScalarType | OptionType | ArrayType | StructType | TopType | BottomType | NilType; -export type NonArrowType = SimpleTypes | ProductType; +export type SimpleTypes = + | ScalarType + | OptionType + | ArrayType + | StructType + | TopType + | BottomType + | NilType; + +export type NonArrowType = SimpleTypes | ProductType; export type TopType = { - /** - * Type descriptor. Used for pattern-matching - */ - tag: 'topType'; + /** + * Type descriptor. Used for pattern-matching + */ + tag: "topType"; }; export type BottomType = { - /** - * Type descriptor. Used for pattern-matching - */ - tag: 'bottomType'; + /** + * Type descriptor. Used for pattern-matching + */ + tag: "bottomType"; }; export type OptionType = { - /** - * Type descriptor. Used for pattern-matching - */ - tag: 'option'; + /** + * Type descriptor. Used for pattern-matching + */ + tag: "option"; - /** - * Underlying type of the option - */ - type: NonArrowType; + /** + * Underlying type of the option + */ + type: SimpleTypes; }; export type NilType = { - /** - * Type descriptor. Used for pattern-matching - */ - tag: 'nil'; + /** + * Type descriptor. Used for pattern-matching + */ + tag: "nil"; }; export type ArrayType = { - /** - * Type descriptor. Used for pattern-matching - */ - tag: 'array'; + /** + * Type descriptor. Used for pattern-matching + */ + tag: "array"; - /** - * Type of array elements - */ - type: NonArrowType; + /** + * Type of array elements + */ + type: SimpleTypes; }; /** * All possible scalar type names */ export type ScalarNames = - | 'u8' - | 'u16' - | 'u32' - | 'u64' - | 'i8' - | 'i16' - | 'i32' - | 'i64' - | 'f32' - | 'f64' - | 'bool' - | 'string'; + | "u8" + | "u16" + | "u32" + | "u64" + | "i8" + | "i16" + | "i32" + | "i64" + | "f32" + | "f64" + | "bool" + | "string"; export type ScalarType = { - /** - * Type descriptor. Used for pattern-matching - */ - tag: 'scalar'; + /** + * Type descriptor. Used for pattern-matching + */ + tag: "scalar"; - /** - * Name of the scalar type - */ - name: ScalarNames; + /** + * Name of the scalar type + */ + name: ScalarNames; }; export type StructType = { - /** - * Type descriptor. Used for pattern-matching - */ - tag: 'struct'; + /** + * Type descriptor. Used for pattern-matching + */ + tag: "struct"; - /** - * Struct name - */ - name: string; + /** + * Struct name + */ + name: string; - /** - * Struct fields - */ - fields: { [key: string]: NonArrowType }; + /** + * Struct fields + */ + fields: { [key: string]: SimpleTypes }; }; -export type LabeledProductType = { - /** - * Type descriptor. Used for pattern-matching - */ - tag: 'labeledProduct'; +export type LabeledProductType< + T extends + | SimpleTypes + | ArrowType | UnlabeledProductType> = + | SimpleTypes + | ArrowType | UnlabeledProductType>, + K extends { [key: string]: T } = { [key: string]: T }, +> = { + /** + * Type descriptor. Used for pattern-matching + */ + tag: "labeledProduct"; - /** - * Labelled product fields - */ - fields: { [key: string]: T }; + /** + * Labelled product fields + */ + fields: K; }; -export type UnlabeledProductType = { +export type UnlabeledProductType = SimpleTypes[]> = + { /** * Type descriptor. Used for pattern-matching */ - tag: 'unlabeledProduct'; + tag: "unlabeledProduct"; /** * Items in unlabelled product */ - items: Array; -}; + items: T; + }; -export type ProductType = UnlabeledProductType | LabeledProductType | NilType; +export type ProductType = UnlabeledProductType | LabeledProductType; /** * ArrowType is a profunctor pointing its domain to codomain. * Profunctor means variance: Arrow is contravariant on domain, and variant on codomain. */ -export type ArrowType = { - /** - * Type descriptor. Used for pattern-matching - */ - tag: 'arrow'; +export type ArrowType = { + /** + * Type descriptor. Used for pattern-matching + */ + tag: "arrow"; - /** - * Where this Arrow is defined - */ - domain: ProductType; + /** + * Where this Arrow is defined + */ + domain: T | NilType; - /** - * Where this Arrow points to - */ - codomain: UnlabeledProductType | NilType; + /** + * Where this Arrow points to + */ + codomain: UnlabeledProductType | NilType; }; /** * Arrow which domain contains only non-arrow types */ -export type ArrowWithoutCallbacks = ArrowType; +export type ArrowWithoutCallbacks = ArrowType< + UnlabeledProductType | LabeledProductType +>; /** * Arrow which domain does can contain both non-arrow types and arrows (which themselves cannot contain arrows) */ -export type ArrowWithCallbacks = ArrowType; +export type ArrowWithCallbacks = ArrowType; export interface FunctionCallConstants { - /** - * The name of the relay variable - */ - relay: string; + /** + * The name of the relay variable + */ + relay: string; - /** - * The name of the serviceId used load variables at the beginning of the script - */ - getDataSrv: string; + /** + * The name of the serviceId used load variables at the beginning of the script + */ + getDataSrv: string; - /** - * The name of serviceId is used to execute callbacks for the current particle - */ - callbackSrv: string; + /** + * The name of serviceId is used to execute callbacks for the current particle + */ + callbackSrv: string; - /** - * The name of the serviceId which is called to propagate return value to the generated function caller - */ - responseSrv: string; + /** + * The name of the serviceId which is called to propagate return value to the generated function caller + */ + responseSrv: string; - /** - * The name of the functionName which is called to propagate return value to the generated function caller - */ - responseFnName: string; + /** + * The name of the functionName which is called to propagate return value to the generated function caller + */ + responseFnName: string; - /** - * The name of the serviceId which is called to report errors to the generated function caller - */ - errorHandlingSrv: string; + /** + * The name of the serviceId which is called to report errors to the generated function caller + */ + errorHandlingSrv: string; - /** - * The name of the functionName which is called to report errors to the generated function caller - */ - errorFnName: string; + /** + * The name of the functionName which is called to report errors to the generated function caller + */ + errorFnName: string; } /** * Definition of function (`func` instruction) generated by the Aqua compiler */ export interface FunctionCallDef { - /** - * The name of the function in Aqua language - */ - functionName: string; + /** + * The name of the function in Aqua language + */ + functionName: string; - /** - * Underlying arrow which represents function in aqua - */ - arrow: ArrowWithCallbacks; + /** + * Underlying arrow which represents function in aqua + */ + arrow: ArrowType< + LabeledProductType> + >; - /** - * Names of the different entities used in generated air script - */ - names: FunctionCallConstants; + /** + * Names of the different entities used in generated air script + */ + names: FunctionCallConstants; } /** * Definition of service registration function (`service` instruction) generated by the Aqua compiler */ export interface ServiceDef { - /** - * Default service id. If the service has no default id the value should be undefined - */ - defaultServiceId?: string; + /** + * Default service id. If the service has no default id the value should be undefined + */ + defaultServiceId?: string; - /** - * List of functions which the service consists of - */ - functions: LabeledProductType | NilType; + /** + * List of functions which the service consists of + */ + functions: + | LabeledProductType>> + | NilType; } /** * Options to configure Aqua function execution */ export interface FnConfig { - /** - * Sets the TTL (time to live) for particle responsible for the function execution - * If the option is not set the default TTL from FluencePeer config is used - */ - ttl?: number; + /** + * Sets the TTL (time to live) for particle responsible for the function execution + * If the option is not set the default TTL from FluencePeer config is used + */ + ttl?: number; } export const getArgumentTypes = ( - def: FunctionCallDef, + def: FunctionCallDef, ): { - [key: string]: NonArrowType | ArrowWithoutCallbacks; + [key: string]: NonArrowType | ArrowWithoutCallbacks; } => { - if (def.arrow.domain.tag !== 'labeledProduct') { - throw new Error('Should be impossible'); - } + if (def.arrow.domain.tag !== "labeledProduct") { + throw new Error("Should be impossible"); + } - return def.arrow.domain.fields; + return def.arrow.domain.fields; }; export const isReturnTypeVoid = (def: FunctionCallDef): boolean => { - if (def.arrow.codomain.tag === 'nil') { - return true; - } + if (def.arrow.codomain.tag === "nil") { + return true; + } - return def.arrow.codomain.items.length == 0; + return def.arrow.codomain.items.length === 0; }; diff --git a/packages/core/interfaces/src/compilerSupport/compilerSupportInterface.ts b/packages/core/interfaces/src/compilerSupport/compilerSupportInterface.ts index 515ab90a..1d21ffd6 100644 --- a/packages/core/interfaces/src/compilerSupport/compilerSupportInterface.ts +++ b/packages/core/interfaces/src/compilerSupport/compilerSupportInterface.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,72 +13,88 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { IFluenceInternalApi } from '../fluenceClient.js'; -import { FnConfig, FunctionCallDef, ServiceDef } from './aquaTypeDefinitions.js'; + +import { JSONValue } from "../commonTypes.js"; +import { IFluenceInternalApi } from "../fluenceClient.js"; + +import { + FnConfig, + FunctionCallDef, + ServiceDef, +} from "./aquaTypeDefinitions.js"; + +/** + * Type for callback passed as aqua function argument + */ +export type ArgCallbackFunction = ( + ...args: JSONValue[] +) => JSONValue | Promise; /** * Arguments passed to Aqua function */ -export type PassedArgs = { [key: string]: any }; +export type PassedArgs = { [key: string]: JSONValue | ArgCallbackFunction }; /** * Arguments for callAquaFunction function */ export interface CallAquaFunctionArgs { - /** - * Peer to call the function on - */ - peer: IFluenceInternalApi; + /** + * Peer to call the function on + */ + peer: IFluenceInternalApi; - /** - * Function definition - */ - def: FunctionCallDef; + /** + * Function definition + */ + def: FunctionCallDef; - /** - * Air script used by the aqua function - */ - script: string; + /** + * Air script used by the aqua function + */ + script: string; - /** - * Function configuration - */ - config: FnConfig; + /** + * Function configuration + */ + config: FnConfig; - /** - * Arguments to pass to the function - */ - args: PassedArgs; + /** + * Arguments to pass to the function + */ + args: PassedArgs; } /** * Call a function from Aqua script */ -export type CallAquaFunctionType = (args: CallAquaFunctionArgs) => Promise; +export type CallAquaFunctionType = ( + args: CallAquaFunctionArgs, +) => Promise; /** * Arguments for registerService function */ export interface RegisterServiceArgs { - /** - * Peer to register the service on - */ - peer: IFluenceInternalApi; + /** + * Peer to register the service on + */ + peer: IFluenceInternalApi; - /** - * Service definition - */ - def: ServiceDef; + /** + * Service definition + */ + def: ServiceDef; - /** - * Service id - */ - serviceId: string | undefined; + /** + * Service id + */ + serviceId: string | undefined; - /** - * Service implementation - */ - service: any; + /** + * Service implementation + */ + service: unknown; } /** diff --git a/packages/core/interfaces/src/fluenceClient.ts b/packages/core/interfaces/src/fluenceClient.ts index 77b8e2a4..743b987c 100644 --- a/packages/core/interfaces/src/fluenceClient.ts +++ b/packages/core/interfaces/src/fluenceClient.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import type { Node } from './commonTypes.js'; + +import type { Node } from "./commonTypes.js"; /** * A node in Fluence network a client can connect to. @@ -26,78 +27,83 @@ export type RelayOptions = string | Node; /** * Fluence Peer's key pair types */ -export type KeyTypes = 'RSA' | 'Ed25519' | 'secp256k1'; +export type KeyTypes = "RSA" | "Ed25519" | "secp256k1"; /** * Options to specify key pair used in Fluence Peer */ export type KeyPairOptions = { - type: 'Ed25519'; - source: 'random' | Uint8Array; + type: "Ed25519"; + source: "random" | Uint8Array; }; /** * Configuration used when initiating Fluence Client */ export interface ClientConfig { + /** + * Specify the KeyPair to be used to identify the Fluence Peer. + * Will be generated randomly if not specified + */ + keyPair?: KeyPairOptions; + + /** + * Options to configure the connection to the Fluence network + */ + connectionOptions?: { /** - * Specify the KeyPair to be used to identify the Fluence Peer. - * Will be generated randomly if not specified + * When the peer established the connection to the network it sends a ping-like message to check if it works correctly. + * The options allows to specify the timeout for that message in milliseconds. + * If not specified the default timeout will be used */ - keyPair?: KeyPairOptions; + skipCheckConnection?: boolean; /** - * Options to configure the connection to the Fluence network + * The dialing timeout in milliseconds */ - connectionOptions?: { - /** - * When the peer established the connection to the network it sends a ping-like message to check if it works correctly. - * The options allows to specify the timeout for that message in milliseconds. - * If not specified the default timeout will be used - */ - skipCheckConnection?: boolean; - - /** - * The dialing timeout in milliseconds - */ - dialTimeoutMs?: number; - - /** - * The maximum number of inbound streams for the libp2p node. - * Default: 1024 - */ - maxInboundStreams?: number; - - /** - * The maximum number of outbound streams for the libp2p node. - * Default: 1024 - */ - maxOutboundStreams?: number; - }; + dialTimeoutMs?: number; /** - * Sets the default TTL for all particles originating from the peer with no TTL specified. - * If the originating particle's TTL is defined then that value will be used - * If the option is not set default TTL will be 7000 + * The maximum number of inbound streams for the libp2p node. + * Default: 1024 */ - defaultTtlMs?: number; + maxInboundStreams?: number; /** - * Enables\disabled various debugging features + * The maximum number of outbound streams for the libp2p node. + * Default: 1024 */ - debug?: { - /** - * If set to true, newly initiated particle ids will be printed to console. - * Useful to see what particle id is responsible for aqua function - */ - printParticleId?: boolean; - }; + maxOutboundStreams?: number; + }; + + /** + * Sets the default TTL for all particles originating from the peer with no TTL specified. + * If the originating particle's TTL is defined then that value will be used + * If the option is not set default TTL will be 7000 + */ + defaultTtlMs?: number; + + /** + * Enables\disabled various debugging features + */ + debug?: { + /** + * If set to true, newly initiated particle ids will be printed to console. + * Useful to see what particle id is responsible for aqua function + */ + printParticleId?: boolean; + }; } /** * Fluence JS Client connection states as string literals */ -export const ConnectionStates = ['disconnected', 'connecting', 'connected', 'disconnecting'] as const; +export const ConnectionStates = [ + "disconnected", + "connecting", + "connected", + "disconnecting", +] as const; /** * Fluence JS Client connection states @@ -105,47 +111,45 @@ export const ConnectionStates = ['disconnected', 'connecting', 'connected', 'dis export type ConnectionState = (typeof ConnectionStates)[number]; export interface IFluenceInternalApi { - /** - * Internal API - */ - internals: any; + /** + * Internal API + */ + internals: unknown; } /** * Public API of Fluence JS Client */ export interface IFluenceClient extends IFluenceInternalApi { - /** - * Connect to the Fluence network - */ - connect: () => Promise; + /** + * Connect to the Fluence network + */ + connect: () => Promise; - /** - * Disconnect from the Fluence network - */ - disconnect(): Promise; + /** + * Disconnect from the Fluence network + */ + disconnect(): Promise; - /** - * Handle connection state changes. Immediately returns current connection state - */ - onConnectionStateChange(handler: (state: ConnectionState) => void): ConnectionState; + /** + * Handle connection state changes. Immediately returns current connection state + */ + onConnectionStateChange( + handler: (state: ConnectionState) => void, + ): ConnectionState; - /** - * Return peer's secret key as byte array. - */ - getPeerSecretKey(): Uint8Array; + /** + * Return peer's secret key as byte array. + */ + getPeerSecretKey(): Uint8Array; - /** - * Return peer's public key as a base58 string (multihash/CIDv0). - */ - getPeerId(): string; + /** + * Return peer's public key as a base58 string (multihash/CIDv0). + */ + getPeerId(): string; - /** - * Return relay's public key as a base58 string (multihash/CIDv0). - */ - getRelayPeerId(): string; + /** + * Return relay's public key as a base58 string (multihash/CIDv0). + */ + getRelayPeerId(): string; } - - - - diff --git a/packages/core/interfaces/src/future.ts b/packages/core/interfaces/src/future.ts new file mode 100644 index 00000000..0cb12aab --- /dev/null +++ b/packages/core/interfaces/src/future.ts @@ -0,0 +1,90 @@ +/** + * Copyright 2023 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 { + ArrayType, + ArrowType, + LabeledProductType, + NilType, + OptionType, + ScalarType, + SimpleTypes, + StructType, + TopType, + UnlabeledProductType, +} from "@fluencelabs/interfaces"; +import { Call, Pipe, Objects, Tuples, Unions, Fn } from "hotscript"; + +// Type definitions for inferring ts types from air json definition +// In the future we may remove string type declaration and move to type inference. + +type GetTsTypeFromScalar = [T["name"]] extends [ + "u8" | "u16" | "u32" | "u64" | "i8" | "i16" | "i32" | "i64" | "f32" | "f64", +] + ? number + : [T["name"]] extends ["bool"] + ? boolean + : [T["name"]] extends ["string"] + ? string + : never; + +type MapTuple = { + [K in keyof T]: [T[K]] extends [SimpleTypes] ? GetSimpleType : never; +}; + +type UnpackIfSingle = [T] extends [[infer R]] ? R : T; + +type GetSimpleType = [T] extends [NilType] + ? null + : [T] extends [ArrayType] + ? GetSimpleType[] + : [T] extends [StructType] + ? { [K in keyof T["fields"]]: GetSimpleType } + : [T] extends [OptionType] + ? GetSimpleType | null + : [T] extends [ScalarType] + ? GetTsTypeFromScalar + : [T] extends [TopType] + ? unknown + : never; + +interface Access extends Fn { + return: __GetTsType, T>>; +} + +type __GetTsType = [T] extends [SimpleTypes] + ? GetSimpleType + : [T] extends [UnlabeledProductType] + ? MapTuple + : [T] extends [LabeledProductType] + ? { [K in keyof T["fields"]]: __GetTsType } + : [T] extends [ArrowType] + ? ( + ...t: [H] extends [UnlabeledProductType] + ? MapTuple + : [H] extends [LabeledProductType] + ? Pipe>]> + : [] + ) => [T["codomain"]] extends [UnlabeledProductType] + ? UnpackIfSingle> + : undefined + : never; + +type DeepMutable = { + -readonly [K in keyof T]: DeepMutable; +}; + +export type GetTsType = __GetTsType>; diff --git a/packages/core/interfaces/src/index.ts b/packages/core/interfaces/src/index.ts index f175fab7..c984197c 100644 --- a/packages/core/interfaces/src/index.ts +++ b/packages/core/interfaces/src/index.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,7 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './compilerSupport/aquaTypeDefinitions.js'; -export * from './compilerSupport/compilerSupportInterface.js'; -export * from './commonTypes.js'; -export * from './fluenceClient.js'; + +export * from "./compilerSupport/aquaTypeDefinitions.js"; +export * from "./compilerSupport/compilerSupportInterface.js"; +export * from "./commonTypes.js"; +export * from "./fluenceClient.js"; +export * from "./future.js"; diff --git a/packages/core/interfaces/src/utils.ts b/packages/core/interfaces/src/utils.ts new file mode 100644 index 00000000..06015c7f --- /dev/null +++ b/packages/core/interfaces/src/utils.ts @@ -0,0 +1,21 @@ +/** + * Copyright 2023 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 type InterfaceToType = { + [K in keyof T]: T[K]; +}; + +export type MaybePromise = T | Promise; diff --git a/packages/core/interfaces/tsconfig.json b/packages/core/interfaces/tsconfig.json index aa5bb830..258e367d 100644 --- a/packages/core/interfaces/tsconfig.json +++ b/packages/core/interfaces/tsconfig.json @@ -1,8 +1,9 @@ { - "extends": "../../../tsconfig.json", - "compilerOptions": { - "outDir": "./dist" - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] } diff --git a/packages/core/js-client/CHANGELOG.md b/packages/core/js-client/CHANGELOG.md index 340b0ce9..c59af776 100644 --- a/packages/core/js-client/CHANGELOG.md +++ b/packages/core/js-client/CHANGELOG.md @@ -25,271 +25,234 @@ ## [0.1.7](https://github.com/fluencelabs/js-client/compare/js-client-v0.1.6...js-client-v0.1.7) (2023-09-22) - ### Bug Fixes -* **deps:** update dependency @fluencelabs/avm to v0.48.0 ([#350](https://github.com/fluencelabs/js-client/issues/350)) ([945908a](https://github.com/fluencelabs/js-client/commit/945908a992976f2ad953bcaa3918741f890ffeeb)) - +- **deps:** update dependency @fluencelabs/avm to v0.48.0 ([#350](https://github.com/fluencelabs/js-client/issues/350)) ([945908a](https://github.com/fluencelabs/js-client/commit/945908a992976f2ad953bcaa3918741f890ffeeb)) ### Dependencies -* The following workspace dependencies were updated - * devDependencies - * @fluencelabs/marine-worker bumped to 0.3.3 +- The following workspace dependencies were updated + - devDependencies + - @fluencelabs/marine-worker bumped to 0.3.3 ## [0.1.6](https://github.com/fluencelabs/js-client/compare/js-client-v0.1.5...js-client-v0.1.6) (2023-09-15) - ### Bug Fixes -* **deps:** update dependency @fluencelabs/avm to v0.47.0 ([#341](https://github.com/fluencelabs/js-client/issues/341)) ([f186f20](https://github.com/fluencelabs/js-client/commit/f186f209366c29f12e6677e03564ee2fa14b51ae)) - +- **deps:** update dependency @fluencelabs/avm to v0.47.0 ([#341](https://github.com/fluencelabs/js-client/issues/341)) ([f186f20](https://github.com/fluencelabs/js-client/commit/f186f209366c29f12e6677e03564ee2fa14b51ae)) ### Dependencies -* The following workspace dependencies were updated - * devDependencies - * @fluencelabs/marine-worker bumped to 0.3.2 +- The following workspace dependencies were updated + - devDependencies + - @fluencelabs/marine-worker bumped to 0.3.2 ## [0.1.5](https://github.com/fluencelabs/js-client/compare/js-client-v0.1.4...js-client-v0.1.5) (2023-09-14) - ### Bug Fixes -* **libp2p:** Add fluence protocol to local peer store protocols [fixes DXJ-471] ([#343](https://github.com/fluencelabs/js-client/issues/343)) ([88fcf02](https://github.com/fluencelabs/js-client/commit/88fcf02d5fd3d28db619427c31b38154646f7ad2)) +- **libp2p:** Add fluence protocol to local peer store protocols [fixes DXJ-471] ([#343](https://github.com/fluencelabs/js-client/issues/343)) ([88fcf02](https://github.com/fluencelabs/js-client/commit/88fcf02d5fd3d28db619427c31b38154646f7ad2)) ## [0.1.4](https://github.com/fluencelabs/js-client/compare/js-client-v0.1.3...js-client-v0.1.4) (2023-09-14) - ### Bug Fixes -* Fire and forget [fixes DXJ-446] ([#336](https://github.com/fluencelabs/js-client/issues/336)) ([e0a970d](https://github.com/fluencelabs/js-client/commit/e0a970d86a13f1617778a461c1c4d558d7dbafcb)) +- Fire and forget [fixes DXJ-446] ([#336](https://github.com/fluencelabs/js-client/issues/336)) ([e0a970d](https://github.com/fluencelabs/js-client/commit/e0a970d86a13f1617778a461c1c4d558d7dbafcb)) ## [0.1.3](https://github.com/fluencelabs/js-client/compare/js-client-v0.1.2...js-client-v0.1.3) (2023-09-07) - ### Bug Fixes -* **deps:** update dependency @fluencelabs/avm to v0.46.0 ([#338](https://github.com/fluencelabs/js-client/issues/338)) ([8e6918c](https://github.com/fluencelabs/js-client/commit/8e6918c4da5bc4cdfe1c840312f477d782d9ca20)) - +- **deps:** update dependency @fluencelabs/avm to v0.46.0 ([#338](https://github.com/fluencelabs/js-client/issues/338)) ([8e6918c](https://github.com/fluencelabs/js-client/commit/8e6918c4da5bc4cdfe1c840312f477d782d9ca20)) ### Dependencies -* The following workspace dependencies were updated - * devDependencies - * @fluencelabs/marine-worker bumped to 0.3.1 +- The following workspace dependencies were updated + - devDependencies + - @fluencelabs/marine-worker bumped to 0.3.1 ## [0.1.2](https://github.com/fluencelabs/js-client/compare/js-client-v0.1.1...js-client-v0.1.2) (2023-09-05) - ### Features -* remove obsolete packages [fixes DXJ-462] ([#337](https://github.com/fluencelabs/js-client/issues/337)) ([e7e6176](https://github.com/fluencelabs/js-client/commit/e7e617661f39e1df36a703d5dad93ba52a338919)) - +- remove obsolete packages [fixes DXJ-462] ([#337](https://github.com/fluencelabs/js-client/issues/337)) ([e7e6176](https://github.com/fluencelabs/js-client/commit/e7e617661f39e1df36a703d5dad93ba52a338919)) ### Bug Fixes -* **logger:** Change formatter that collides with new libp2p version [fixes DXJ-459] ([#334](https://github.com/fluencelabs/js-client/issues/334)) ([18a972b](https://github.com/fluencelabs/js-client/commit/18a972b573559d0717ec93a95b8c63dd1cbcd93b)) +- **logger:** Change formatter that collides with new libp2p version [fixes DXJ-459] ([#334](https://github.com/fluencelabs/js-client/issues/334)) ([18a972b](https://github.com/fluencelabs/js-client/commit/18a972b573559d0717ec93a95b8c63dd1cbcd93b)) ## [0.1.1](https://github.com/fluencelabs/js-client/compare/js-client-v0.1.0...js-client-v0.1.1) (2023-08-25) - ### Bug Fixes -* Use info log level instead trace [Fixes DXJ-457] ([#328](https://github.com/fluencelabs/js-client/issues/328)) ([477c6f0](https://github.com/fluencelabs/js-client/commit/477c6f0c151ef6759aaa2802c5e9907065d58e17)) +- Use info log level instead trace [Fixes DXJ-457] ([#328](https://github.com/fluencelabs/js-client/issues/328)) ([477c6f0](https://github.com/fluencelabs/js-client/commit/477c6f0c151ef6759aaa2802c5e9907065d58e17)) ## [0.1.0](https://github.com/fluencelabs/js-client/compare/js-client-v0.0.10...js-client-v0.1.0) (2023-08-24) - ### ⚠ BREAKING CHANGES -* Unify all packages ([#327](https://github.com/fluencelabs/js-client/issues/327)) +- Unify all packages ([#327](https://github.com/fluencelabs/js-client/issues/327)) ### Features -* Unify all packages ([#327](https://github.com/fluencelabs/js-client/issues/327)) ([97c2491](https://github.com/fluencelabs/js-client/commit/97c24918d84b34e7ac58337838dc8343cbd44b19)) - +- Unify all packages ([#327](https://github.com/fluencelabs/js-client/issues/327)) ([97c2491](https://github.com/fluencelabs/js-client/commit/97c24918d84b34e7ac58337838dc8343cbd44b19)) ### Dependencies -* The following workspace dependencies were updated - * dependencies - * @fluencelabs/interfaces bumped from 0.8.1 to 0.8.2 - * devDependencies - * @fluencelabs/marine-worker bumped to 0.3.0 +- The following workspace dependencies were updated + - dependencies + - @fluencelabs/interfaces bumped from 0.8.1 to 0.8.2 + - devDependencies + - @fluencelabs/marine-worker bumped to 0.3.0 ## [0.9.1](https://github.com/fluencelabs/js-client/compare/js-peer-v0.9.0...js-peer-v0.9.1) (2023-08-08) - ### Bug Fixes -* **deps:** update dependency @fluencelabs/avm to v0.43.1 ([#322](https://github.com/fluencelabs/js-client/issues/322)) ([c1d1fa6](https://github.com/fluencelabs/js-client/commit/c1d1fa6659b6dc2c6707786748b3410fab7f1bcd)) - +- **deps:** update dependency @fluencelabs/avm to v0.43.1 ([#322](https://github.com/fluencelabs/js-client/issues/322)) ([c1d1fa6](https://github.com/fluencelabs/js-client/commit/c1d1fa6659b6dc2c6707786748b3410fab7f1bcd)) ### Dependencies -* The following workspace dependencies were updated - * dependencies - * @fluencelabs/interfaces bumped from 0.8.0 to 0.8.1 +- The following workspace dependencies were updated + - dependencies + - @fluencelabs/interfaces bumped from 0.8.0 to 0.8.1 ## [0.9.0](https://github.com/fluencelabs/js-client/compare/js-peer-v0.8.10...js-peer-v0.9.0) (2023-06-29) - ### ⚠ BREAKING CHANGES -* **avm:** avm 0.40.0 (https://github.com/fluencelabs/js-client/pull/315) +- **avm:** avm 0.40.0 (https://github.com/fluencelabs/js-client/pull/315) ### Features -* **avm:** avm 0.40.0 (https://github.com/fluencelabs/js-client/pull/315) ([8bae6e2](https://github.com/fluencelabs/js-client/commit/8bae6e24e62153b567f320ccecc7bce76bc826d1)) - +- **avm:** avm 0.40.0 (https://github.com/fluencelabs/js-client/pull/315) ([8bae6e2](https://github.com/fluencelabs/js-client/commit/8bae6e24e62153b567f320ccecc7bce76bc826d1)) ### Dependencies -* The following workspace dependencies were updated - * dependencies - * @fluencelabs/interfaces bumped from 0.7.6 to 0.8.0 +- The following workspace dependencies were updated + - dependencies + - @fluencelabs/interfaces bumped from 0.7.6 to 0.8.0 ## [0.8.10](https://github.com/fluencelabs/js-client/compare/js-peer-v0.8.9...js-peer-v0.8.10) (2023-06-20) - ### Features -* support signatures [fixes DXJ-389] ([#310](https://github.com/fluencelabs/js-client/issues/310)) ([a60dfe0](https://github.com/fluencelabs/js-client/commit/a60dfe0d680b4d9ac5092dec64e2ebf478bf80eb)) - +- support signatures [fixes DXJ-389] ([#310](https://github.com/fluencelabs/js-client/issues/310)) ([a60dfe0](https://github.com/fluencelabs/js-client/commit/a60dfe0d680b4d9ac5092dec64e2ebf478bf80eb)) ### Dependencies -* The following workspace dependencies were updated - * dependencies - * @fluencelabs/interfaces bumped from 0.7.5 to 0.7.6 +- The following workspace dependencies were updated + - dependencies + - @fluencelabs/interfaces bumped from 0.7.5 to 0.7.6 ## [0.8.9](https://github.com/fluencelabs/js-client/compare/js-peer-v0.8.8...js-peer-v0.8.9) (2023-06-14) - ### Features -* Add tracing service [fixes DXJ-388] ([#307](https://github.com/fluencelabs/js-client/issues/307)) ([771086f](https://github.com/fluencelabs/js-client/commit/771086fddf52b7a5a1280894c7238e409cdf6a64)) -* improve ttl error message ([#300](https://github.com/fluencelabs/js-client/issues/300)) ([9821183](https://github.com/fluencelabs/js-client/commit/9821183d53870240cb5700be67cb8d57533b954b)) +- Add tracing service [fixes DXJ-388] ([#307](https://github.com/fluencelabs/js-client/issues/307)) ([771086f](https://github.com/fluencelabs/js-client/commit/771086fddf52b7a5a1280894c7238e409cdf6a64)) +- improve ttl error message ([#300](https://github.com/fluencelabs/js-client/issues/300)) ([9821183](https://github.com/fluencelabs/js-client/commit/9821183d53870240cb5700be67cb8d57533b954b)) ## [0.8.8](https://github.com/fluencelabs/js-client/compare/js-peer-v0.8.7...js-peer-v0.8.8) (2023-05-30) - ### Features -* add run-console ([#305](https://github.com/fluencelabs/js-client/issues/305)) ([cf1f029](https://github.com/fluencelabs/js-client/commit/cf1f02963c1d7e1a17866f5798901a0f61b8bc31)) +- add run-console ([#305](https://github.com/fluencelabs/js-client/issues/305)) ([cf1f029](https://github.com/fluencelabs/js-client/commit/cf1f02963c1d7e1a17866f5798901a0f61b8bc31)) ## [0.8.7](https://github.com/fluencelabs/js-client/compare/js-peer-v0.8.6...js-peer-v0.8.7) (2023-04-04) - ### Features -* Cleaning up technical debts ([#295](https://github.com/fluencelabs/js-client/issues/295)) ([0b2f12d](https://github.com/fluencelabs/js-client/commit/0b2f12d8ac223db341d6c30ff403166b3eae2e56)) - +- Cleaning up technical debts ([#295](https://github.com/fluencelabs/js-client/issues/295)) ([0b2f12d](https://github.com/fluencelabs/js-client/commit/0b2f12d8ac223db341d6c30ff403166b3eae2e56)) ### Dependencies -* The following workspace dependencies were updated - * dependencies - * @fluencelabs/interfaces bumped from 0.7.4 to 0.7.5 +- The following workspace dependencies were updated + - dependencies + - @fluencelabs/interfaces bumped from 0.7.4 to 0.7.5 ## [0.8.6](https://github.com/fluencelabs/js-client/compare/js-peer-v0.8.5...js-peer-v0.8.6) (2023-03-31) - ### Features -* **logs:** Use `debug.js` library for logging [DXJ-327] ([#285](https://github.com/fluencelabs/js-client/issues/285)) ([e95c34a](https://github.com/fluencelabs/js-client/commit/e95c34a79220bd8ecdcee806802ac3d69a2af0cb)) -* **test:** Automate smoke tests for JS Client [DXJ-293] ([#282](https://github.com/fluencelabs/js-client/issues/282)) ([10d7eae](https://github.com/fluencelabs/js-client/commit/10d7eaed809dde721b582d4b3228a48bbec50884)) - +- **logs:** Use `debug.js` library for logging [DXJ-327] ([#285](https://github.com/fluencelabs/js-client/issues/285)) ([e95c34a](https://github.com/fluencelabs/js-client/commit/e95c34a79220bd8ecdcee806802ac3d69a2af0cb)) +- **test:** Automate smoke tests for JS Client [DXJ-293] ([#282](https://github.com/fluencelabs/js-client/issues/282)) ([10d7eae](https://github.com/fluencelabs/js-client/commit/10d7eaed809dde721b582d4b3228a48bbec50884)) ### Bug Fixes -* **test:** All tests are working with vitest [DXJ-306] ([#291](https://github.com/fluencelabs/js-client/issues/291)) ([58ad3ca](https://github.com/fluencelabs/js-client/commit/58ad3ca6f666e8580997bb47609947645903436d)) - +- **test:** All tests are working with vitest [DXJ-306] ([#291](https://github.com/fluencelabs/js-client/issues/291)) ([58ad3ca](https://github.com/fluencelabs/js-client/commit/58ad3ca6f666e8580997bb47609947645903436d)) ### Dependencies -* The following workspace dependencies were updated - * dependencies - * @fluencelabs/interfaces bumped from 0.7.3 to 0.7.4 +- The following workspace dependencies were updated + - dependencies + - @fluencelabs/interfaces bumped from 0.7.3 to 0.7.4 ## [0.8.5](https://github.com/fluencelabs/js-client/compare/js-peer-v0.8.4...js-peer-v0.8.5) (2023-03-03) - ### Bug Fixes -* Increase number of inbound and outbound streams to 1024 ([#280](https://github.com/fluencelabs/js-client/issues/280)) ([1ccc483](https://github.com/fluencelabs/js-client/commit/1ccc4835328426b546f31e1646d3a49ed042fdf9)) +- Increase number of inbound and outbound streams to 1024 ([#280](https://github.com/fluencelabs/js-client/issues/280)) ([1ccc483](https://github.com/fluencelabs/js-client/commit/1ccc4835328426b546f31e1646d3a49ed042fdf9)) ## [0.8.4](https://github.com/fluencelabs/js-client/compare/js-peer-v0.8.3...js-peer-v0.8.4) (2023-02-22) - ### Bug Fixes -* `nodenext` moduleResolution for js peer ([#271](https://github.com/fluencelabs/js-client/issues/271)) ([78d98f1](https://github.com/fluencelabs/js-client/commit/78d98f15c12431dee9fdd7b9869d57760503f8c7)) +- `nodenext` moduleResolution for js peer ([#271](https://github.com/fluencelabs/js-client/issues/271)) ([78d98f1](https://github.com/fluencelabs/js-client/commit/78d98f15c12431dee9fdd7b9869d57760503f8c7)) ## [0.8.3](https://github.com/fluencelabs/js-client/compare/js-peer-v0.8.2...js-peer-v0.8.3) (2023-02-16) - ### Bug Fixes -* Trigger release to publish packages that were built ([#262](https://github.com/fluencelabs/js-client/issues/262)) ([47abf38](https://github.com/fluencelabs/js-client/commit/47abf3882956ffbdc52df372db26ba6252e8306b)) - +- Trigger release to publish packages that were built ([#262](https://github.com/fluencelabs/js-client/issues/262)) ([47abf38](https://github.com/fluencelabs/js-client/commit/47abf3882956ffbdc52df372db26ba6252e8306b)) ### Dependencies -* The following workspace dependencies were updated - * dependencies - * @fluencelabs/interfaces bumped from 0.7.2 to 0.7.3 +- The following workspace dependencies were updated + - dependencies + - @fluencelabs/interfaces bumped from 0.7.2 to 0.7.3 ## [0.8.2](https://github.com/fluencelabs/js-client/compare/js-peer-v0.8.1...js-peer-v0.8.2) (2023-02-16) - ### Features -* Add `getRelayPeerId` method for `IFluenceClient` ([#260](https://github.com/fluencelabs/js-client/issues/260)) ([a10278a](https://github.com/fluencelabs/js-client/commit/a10278afaa782a307feb10c4eac060094c101230)) - +- Add `getRelayPeerId` method for `IFluenceClient` ([#260](https://github.com/fluencelabs/js-client/issues/260)) ([a10278a](https://github.com/fluencelabs/js-client/commit/a10278afaa782a307feb10c4eac060094c101230)) ### Dependencies -* The following workspace dependencies were updated - * dependencies - * @fluencelabs/interfaces bumped from 0.7.1 to 0.7.2 +- The following workspace dependencies were updated + - dependencies + - @fluencelabs/interfaces bumped from 0.7.1 to 0.7.2 ## [0.8.1](https://github.com/fluencelabs/js-client/compare/js-peer-v0.8.0...js-peer-v0.8.1) (2023-02-16) - ### Features -* Simplify JS Client public API ([#257](https://github.com/fluencelabs/js-client/issues/257)) ([9daaf41](https://github.com/fluencelabs/js-client/commit/9daaf410964d43228192c829c7ff785db6e88081)) - +- Simplify JS Client public API ([#257](https://github.com/fluencelabs/js-client/issues/257)) ([9daaf41](https://github.com/fluencelabs/js-client/commit/9daaf410964d43228192c829c7ff785db6e88081)) ### Dependencies -* The following workspace dependencies were updated - * dependencies - * @fluencelabs/interfaces bumped from 0.7.0 to 0.7.1 +- The following workspace dependencies were updated + - dependencies + - @fluencelabs/interfaces bumped from 0.7.0 to 0.7.1 ## [0.8.0](https://github.com/fluencelabs/fluence-js/compare/js-peer-v0.7.0...js-peer-v0.8.0) (2023-02-15) - ### ⚠ BREAKING CHANGES -* Expose updated JS Client API via `js-client.api` package ([#246](https://github.com/fluencelabs/fluence-js/issues/246)) -* Standalone web JS Client ([#243](https://github.com/fluencelabs/fluence-js/issues/243)) +- Expose updated JS Client API via `js-client.api` package ([#246](https://github.com/fluencelabs/fluence-js/issues/246)) +- Standalone web JS Client ([#243](https://github.com/fluencelabs/fluence-js/issues/243)) ### Features -* Expose updated JS Client API via `js-client.api` package ([#246](https://github.com/fluencelabs/fluence-js/issues/246)) ([d4bb8fb](https://github.com/fluencelabs/fluence-js/commit/d4bb8fb42964b3ba25154232980b9ae82c21e627)) -* Standalone web JS Client ([#243](https://github.com/fluencelabs/fluence-js/issues/243)) ([9667c4f](https://github.com/fluencelabs/fluence-js/commit/9667c4fec6868f984bba13249f3c47d293396406)) - +- Expose updated JS Client API via `js-client.api` package ([#246](https://github.com/fluencelabs/fluence-js/issues/246)) ([d4bb8fb](https://github.com/fluencelabs/fluence-js/commit/d4bb8fb42964b3ba25154232980b9ae82c21e627)) +- Standalone web JS Client ([#243](https://github.com/fluencelabs/fluence-js/issues/243)) ([9667c4f](https://github.com/fluencelabs/fluence-js/commit/9667c4fec6868f984bba13249f3c47d293396406)) ### Bug Fixes -* NodeJS package building ([#248](https://github.com/fluencelabs/fluence-js/issues/248)) ([0d05e51](https://github.com/fluencelabs/fluence-js/commit/0d05e517d89529af513fcb96cfa6c722ccc357a7)) - +- NodeJS package building ([#248](https://github.com/fluencelabs/fluence-js/issues/248)) ([0d05e51](https://github.com/fluencelabs/fluence-js/commit/0d05e517d89529af513fcb96cfa6c722ccc357a7)) ### Dependencies -* The following workspace dependencies were updated - * dependencies - * @fluencelabs/interfaces bumped from 0.6.0 to 0.7.0 +- The following workspace dependencies were updated + - dependencies + - @fluencelabs/interfaces bumped from 0.6.0 to 0.7.0 diff --git a/packages/core/js-client/package.json b/packages/core/js-client/package.json index 5563d0f8..b6ef1850 100644 --- a/packages/core/js-client/package.json +++ b/packages/core/js-client/package.json @@ -1,68 +1,77 @@ { - "name": "@fluencelabs/js-client", - "version": "0.2.1", - "description": "Client for interacting with Fluence network", - "engines": { - "node": ">=10", - "pnpm": ">=8" - }, - "files": [ - "dist" - ], - "main": "./dist/index.js", - "unpkg": "./dist/browser/index.umd.js", + "name": "@fluencelabs/js-client", + "version": "0.2.1", + "description": "Client for interacting with Fluence network", + "engines": { + "node": ">=10", + "pnpm": ">=8" + }, + "files": [ + "dist" + ], + "main": "./dist/index.js", + "unpkg": "./dist/browser/index.umd.js", + "types": "./dist/index.d.ts", + "exports": { "types": "./dist/index.d.ts", - "exports": { - "types": "./dist/index.d.ts", - "node": "./dist/index.js", - "default": "./dist/browser/index.js" - }, - "type": "module", - "scripts": { - "build": "tsc && vite build", - "test": "vitest --threads false run" - }, - "repository": "https://github.com/fluencelabs/fluence-js", - "author": "Fluence Labs", - "license": "Apache-2.0", - "dependencies": { - "@chainsafe/libp2p-noise": "13.0.0", - "@chainsafe/libp2p-yamux": "5.0.0", - "@fluencelabs/interfaces": "workspace:*", - "@fluencelabs/marine-worker": "0.3.3", - "@libp2p/crypto": "2.0.3", - "@libp2p/interface": "0.1.2", - "@libp2p/peer-id": "3.0.2", - "@libp2p/peer-id-factory": "3.0.3", - "@libp2p/websockets": "7.0.4", - "@multiformats/multiaddr": "11.3.0", - "async": "3.2.4", - "bs58": "5.0.0", - "buffer": "6.0.3", - "debug": "4.3.4", - "it-length-prefixed": "8.0.4", - "it-map": "2.0.0", - "it-pipe": "2.0.5", - "js-base64": "3.7.5", - "libp2p": "0.46.6", - "multiformats": "11.0.1", - "rxjs": "7.5.5", - "threads": "1.7.0", - "ts-pattern": "3.3.3", - "uint8arrays": "4.0.3", - "uuid": "8.3.2" - }, - "devDependencies": { - "@fluencelabs/aqua-api": "0.9.3", - "@fluencelabs/avm": "0.48.0", - "@fluencelabs/marine-js": "0.7.2", - "@rollup/plugin-inject": "5.0.3", - "@types/bs58": "4.0.1", - "@types/debug": "4.1.7", - "@types/node": "20.7.0", - "@types/uuid": "8.3.2", - "vite": "4.0.4", - "vite-tsconfig-paths": "4.0.3", - "vitest": "0.29.7" + "node": "./dist/index.js", + "default": "./dist/browser/index.js" + }, + "imports": { + "#fetcher": { + "node": "./dist/fetchers/node.js", + "default": "./dist/fetchers/browser.js" } + }, + "type": "module", + "scripts": { + "build": "tsc && vite build", + "test": "vitest --threads false run" + }, + "repository": "https://github.com/fluencelabs/fluence-js", + "author": "Fluence Labs", + "license": "Apache-2.0", + "dependencies": { + "@chainsafe/libp2p-noise": "13.0.0", + "@chainsafe/libp2p-yamux": "5.0.0", + "@fluencelabs/avm": "0.48.0", + "@fluencelabs/interfaces": "workspace:*", + "@fluencelabs/marine-worker": "0.3.3", + "@libp2p/crypto": "2.0.3", + "@libp2p/interface": "0.1.2", + "@libp2p/peer-id": "3.0.2", + "@libp2p/peer-id-factory": "3.0.3", + "@libp2p/websockets": "7.0.4", + "@multiformats/multiaddr": "11.3.0", + "assert": "2.1.0", + "async": "3.2.4", + "bs58": "5.0.0", + "buffer": "6.0.3", + "debug": "4.3.4", + "it-length-prefixed": "8.0.4", + "it-map": "2.0.0", + "it-pipe": "2.0.5", + "js-base64": "3.7.5", + "libp2p": "0.46.6", + "multiformats": "11.0.1", + "rxjs": "7.5.5", + "threads": "fluencelabs/threads.js#b00a5342380b0278d3ae56dcfb170effb3cad7cd", + "ts-pattern": "3.3.3", + "uint8arrays": "4.0.3", + "uuid": "8.3.2", + "zod": "3.22.4" + }, + "devDependencies": { + "@fluencelabs/aqua-api": "0.9.3", + "@fluencelabs/marine-js": "0.7.2", + "@rollup/plugin-inject": "5.0.3", + "@types/bs58": "4.0.1", + "@types/debug": "4.1.7", + "@types/node": "20.7.0", + "@types/uuid": "8.3.2", + "hotscript": "1.0.13", + "vite": "4.4.11", + "vite-tsconfig-paths": "4.0.3", + "vitest": "0.34.6" + } } diff --git a/packages/core/js-client/src/api.ts b/packages/core/js-client/src/api.ts index 38085de8..84213ed3 100644 --- a/packages/core/js-client/src/api.ts +++ b/packages/core/js-client/src/api.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,14 +14,23 @@ * limitations under the License. */ -import type { FnConfig, FunctionCallDef, ServiceDef } from '@fluencelabs/interfaces'; -import type { IFluenceClient } from '@fluencelabs/interfaces'; -import { getArgumentTypes } from '@fluencelabs/interfaces'; -import { callAquaFunction, Fluence, registerService } from './index.js'; -import { FluencePeer } from './jsPeer/FluencePeer.js'; +import type { + FnConfig, + FunctionCallDef, + ServiceDef, + PassedArgs, + ServiceImpl, +} from "@fluencelabs/interfaces"; +import { getArgumentTypes } from "@fluencelabs/interfaces"; -export const isFluencePeer = (fluencePeerCandidate: unknown): fluencePeerCandidate is IFluenceClient => { - return fluencePeerCandidate instanceof FluencePeer; +import { FluencePeer } from "./jsPeer/FluencePeer.js"; + +import { callAquaFunction, Fluence, registerService } from "./index.js"; + +export const isFluencePeer = ( + fluencePeerCandidate: unknown, +): fluencePeerCandidate is FluencePeer => { + return fluencePeerCandidate instanceof FluencePeer; }; /** @@ -33,38 +42,47 @@ export const isFluencePeer = (fluencePeerCandidate: unknown): fluencePeerCandida * @param script - air script with function execution logic generated by the Aqua compiler */ export const v5_callFunction = async ( - rawFnArgs: Array, - def: FunctionCallDef, - script: string, + rawFnArgs: unknown[], + def: FunctionCallDef, + script: string, ): Promise => { - const { args, client: peer, config } = await extractFunctionArgs(rawFnArgs, def); - - return callAquaFunction({ - args, - def, - script, - config: config || {}, - peer: peer, - }); + const { args, client: peer, config } = extractFunctionArgs(rawFnArgs, def); + + return callAquaFunction({ + args, + def, + script, + config, + peer, + }); }; /** * Convenience function to support Aqua `service` generation backend * The compiler only need to generate a call the function and provide the corresponding definitions and the air script * @param args - raw arguments passed by user to the generated function + * TODO: dont forget to add jsdoc for new arg * @param def - service definition generated by the Aqua compiler */ -export const v5_registerService = async (args: any[], def: ServiceDef): Promise => { - const { peer, service, serviceId } = await extractServiceArgs(args, def.defaultServiceId); - - return registerService({ - def, - service, - serviceId, - peer, - }); +export const v5_registerService = (args: unknown[], def: ServiceDef): void => { + // TODO: Support this in aqua-to-js package + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const service: ServiceImpl = args.pop() as ServiceImpl; + + const { peer, serviceId } = extractServiceArgs(args, def.defaultServiceId); + + registerService({ + def, + service, + serviceId, + peer, + }); }; +function isConfig(arg: unknown): arg is FnConfig { + return typeof arg === "object" && arg !== null; +} + /** * Arguments could be passed in one these configurations: * [...actualArgs] @@ -75,48 +93,60 @@ export const v5_registerService = async (args: any[], def: ServiceDef): Promise< * This function select the appropriate configuration and returns * arguments in a structured way of: { peer, config, args } */ -const extractFunctionArgs = async ( - args: any[], - def: FunctionCallDef, -): Promise<{ - client: IFluenceClient; - config?: FnConfig; - args: { [key: string]: any }; -}> => { - const argumentTypes = getArgumentTypes(def); - const argumentNames = Object.keys(argumentTypes); - const numberOfExpectedArgs = argumentNames.length; +function extractFunctionArgs( + args: unknown[], + def: FunctionCallDef, +): { + client: FluencePeer; + config: FnConfig; + args: PassedArgs; +} { + const argumentTypes = getArgumentTypes(def); + const argumentNames = Object.keys(argumentTypes); + const numberOfExpectedArgs = argumentNames.length; - let peer: IFluenceClient; - let structuredArgs: any[]; - let config: FnConfig; - if (isFluencePeer(args[0])) { - peer = args[0]; - structuredArgs = args.slice(1, numberOfExpectedArgs + 1); - config = args[numberOfExpectedArgs + 1]; - } else { - if (!Fluence.defaultClient) { - throw new Error( - 'Could not register Aqua service because the client is not initialized. Did you forget to call Fluence.connect()?', - ); - } - peer = Fluence.defaultClient; - structuredArgs = args.slice(0, numberOfExpectedArgs); - config = args[numberOfExpectedArgs]; + let peer: FluencePeer; + let config: FnConfig; + + if (isFluencePeer(args[0])) { + peer = args[0]; + args = args.slice(1); + } else { + if (Fluence.defaultClient == null) { + throw new Error( + "Could not register Aqua service because the client is not initialized. Did you forget to call Fluence.connect()?", + ); } - if (structuredArgs.length !== numberOfExpectedArgs) { - throw new Error(`Incorrect number of arguments. Expecting ${numberOfExpectedArgs}`); - } + peer = Fluence.defaultClient; + } - const argsRes = argumentNames.reduce((acc, name, index) => ({ ...acc, [name]: structuredArgs[index] }), {}); + const maybeConfig = args[numberOfExpectedArgs]; - return { - client: peer, - config: config, - args: argsRes, - }; -}; + if (isConfig(maybeConfig)) { + config = maybeConfig; + } else { + config = {}; + } + + const structuredArgs = args.slice(0, numberOfExpectedArgs); + + if (structuredArgs.length !== numberOfExpectedArgs) { + throw new Error( + `Incorrect number of arguments. Expecting ${numberOfExpectedArgs}`, + ); + } + + const argsRes = argumentNames.reduce((acc, name, index) => { + return { ...acc, [name]: structuredArgs[index] }; + }, {}); + + return { + client: peer, + args: argsRes, + config: config, + }; +} /** * Arguments could be passed in one these configurations: @@ -130,48 +160,37 @@ const extractFunctionArgs = async ( * This function select the appropriate configuration and returns * arguments in a structured way of: { peer, serviceId, service } */ -const extractServiceArgs = async ( - args: any[], - defaultServiceId?: string, -): Promise<{ peer: IFluenceClient; serviceId: string; service: any }> => { - let peer: IFluenceClient; - let serviceId: any; - let service: any; - if (isFluencePeer(args[0])) { - peer = args[0]; - } else { - if (!Fluence.defaultClient) { - throw new Error( - 'Could not register Aqua service because the client is not initialized. Did you forget to call Fluence.connect()?', - ); - } - peer = Fluence.defaultClient; +const extractServiceArgs = ( + args: unknown[], + defaultServiceId?: string, +): { + peer: FluencePeer; + serviceId: string | undefined; +} => { + let peer: FluencePeer; + let serviceId: string | undefined; + + if (isFluencePeer(args[0])) { + peer = args[0]; + args = args.slice(1); + } else { + if (Fluence.defaultClient == null) { + throw new Error( + "Could not register Aqua service because the client is not initialized. Did you forget to call Fluence.connect()?", + ); } - if (typeof args[0] === 'string') { - serviceId = args[0]; - } else if (typeof args[1] === 'string') { - serviceId = args[1]; - } else { - serviceId = defaultServiceId; - } + peer = Fluence.defaultClient; + } - // Figuring out which overload is the service. - // If the first argument is not Fluence Peer and it is an object, then it can only be the service def - // If the first argument is peer, we are checking further. The second argument might either be - // an object, that it must be the service object - // or a string, which is the service id. In that case the service is the third argument - if (!isFluencePeer(args[0]) && typeof args[0] === 'object') { - service = args[0]; - } else if (typeof args[1] === 'object') { - service = args[1]; - } else { - service = args[2]; - } + if (typeof args[0] === "string") { + serviceId = args[0]; + } else { + serviceId = defaultServiceId; + } - return { - peer: peer, - serviceId: serviceId, - service: service, - }; + return { + peer, + serviceId, + }; }; diff --git a/packages/core/js-client/src/clientPeer/ClientPeer.ts b/packages/core/js-client/src/clientPeer/ClientPeer.ts index b93da9bf..215748db 100644 --- a/packages/core/js-client/src/clientPeer/ClientPeer.ts +++ b/packages/core/js-client/src/clientPeer/ClientPeer.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,111 +13,139 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ClientConfig, ConnectionState, IFluenceClient, PeerIdB58, RelayOptions } from '@fluencelabs/interfaces'; -import { RelayConnection, RelayConnectionConfig } from '../connection/RelayConnection.js'; -import { fromOpts, KeyPair } from '../keypair/index.js'; -import { FluencePeer, PeerConfig } from '../jsPeer/FluencePeer.js'; -import { relayOptionToMultiaddr } from '../util/libp2pUtils.js'; -import { IAvmRunner, IMarineHost } from '../marine/interfaces.js'; -import { JsServiceHost } from '../jsServiceHost/JsServiceHost.js'; -import { logger } from '../util/logger.js'; -const log = logger('client'); +import { + ClientConfig, + ConnectionState, + IFluenceClient, + RelayOptions, +} from "@fluencelabs/interfaces"; + +import { + RelayConnection, + RelayConnectionConfig, +} from "../connection/RelayConnection.js"; +import { FluencePeer, PeerConfig } from "../jsPeer/FluencePeer.js"; +import { JsServiceHost } from "../jsServiceHost/JsServiceHost.js"; +import { fromOpts, KeyPair } from "../keypair/index.js"; +import { IMarineHost } from "../marine/interfaces.js"; +import { relayOptionToMultiaddr } from "../util/libp2pUtils.js"; +import { logger } from "../util/logger.js"; + +const log = logger("client"); const DEFAULT_TTL_MS = 7000; const MAX_OUTBOUND_STREAMS = 1024; const MAX_INBOUND_STREAMS = 1024; export const makeClientPeerConfig = async ( - relay: RelayOptions, - config: ClientConfig, -): Promise<{ peerConfig: PeerConfig; relayConfig: RelayConnectionConfig; keyPair: KeyPair }> => { - const opts = config?.keyPair || { type: 'Ed25519', source: 'random' }; - const keyPair = await fromOpts(opts); - const relayAddress = relayOptionToMultiaddr(relay); + relay: RelayOptions, + config: ClientConfig, +): Promise<{ + peerConfig: PeerConfig; + relayConfig: RelayConnectionConfig; + keyPair: KeyPair; +}> => { + const opts = config.keyPair ?? { type: "Ed25519", source: "random" }; + const keyPair = await fromOpts(opts); + const relayAddress = relayOptionToMultiaddr(relay); - return { - peerConfig: { - debug: { - printParticleId: config?.debug?.printParticleId || false, - }, - defaultTtlMs: config?.defaultTtlMs || DEFAULT_TTL_MS, - }, - relayConfig: { - peerId: keyPair.getLibp2pPeerId(), - relayAddress: relayAddress, - dialTimeoutMs: config?.connectionOptions?.dialTimeoutMs, - maxInboundStreams: config?.connectionOptions?.maxInboundStreams || MAX_OUTBOUND_STREAMS, - maxOutboundStreams: config?.connectionOptions?.maxOutboundStreams || MAX_INBOUND_STREAMS, - }, - keyPair: keyPair, - }; + return { + peerConfig: { + debug: { + printParticleId: config.debug?.printParticleId ?? false, + }, + defaultTtlMs: config.defaultTtlMs ?? DEFAULT_TTL_MS, + }, + relayConfig: { + peerId: keyPair.getLibp2pPeerId(), + relayAddress: relayAddress, + ...(config.connectionOptions?.dialTimeoutMs != null + ? { + dialTimeout: config.connectionOptions.dialTimeoutMs, + } + : {}), + maxInboundStreams: + config.connectionOptions?.maxInboundStreams ?? MAX_OUTBOUND_STREAMS, + maxOutboundStreams: + config.connectionOptions?.maxOutboundStreams ?? MAX_INBOUND_STREAMS, + }, + keyPair: keyPair, + }; }; export class ClientPeer extends FluencePeer implements IFluenceClient { - constructor( - peerConfig: PeerConfig, - relayConfig: RelayConnectionConfig, - keyPair: KeyPair, - marine: IMarineHost, - ) { - super(peerConfig, keyPair, marine, new JsServiceHost(), new RelayConnection(relayConfig)); - } + constructor( + peerConfig: PeerConfig, + relayConfig: RelayConnectionConfig, + keyPair: KeyPair, + marine: IMarineHost, + ) { + super( + peerConfig, + keyPair, + marine, + new JsServiceHost(), + new RelayConnection(relayConfig), + ); + } - getPeerId(): string { - return this.keyPair.getPeerId(); - } + getPeerId(): string { + return this.keyPair.getPeerId(); + } - getPeerSecretKey(): Uint8Array { - return this.keyPair.toEd25519PrivateKey(); - } + getPeerSecretKey(): Uint8Array { + return this.keyPair.toEd25519PrivateKey(); + } - connectionState: ConnectionState = 'disconnected'; - connectionStateChangeHandler: (state: ConnectionState) => void = () => {}; + connectionState: ConnectionState = "disconnected"; + connectionStateChangeHandler: (state: ConnectionState) => void = () => {}; - getRelayPeerId(): string { - return this.internals.getRelayPeerId(); - } + getRelayPeerId(): string { + return this.internals.getRelayPeerId(); + } - onConnectionStateChange(handler: (state: ConnectionState) => void): ConnectionState { - this.connectionStateChangeHandler = handler; + onConnectionStateChange( + handler: (state: ConnectionState) => void, + ): ConnectionState { + this.connectionStateChangeHandler = handler; - return this.connectionState; - } + return this.connectionState; + } - private changeConnectionState(state: ConnectionState) { - this.connectionState = state; - this.connectionStateChangeHandler(state); - } + private changeConnectionState(state: ConnectionState) { + this.connectionState = state; + this.connectionStateChangeHandler(state); + } - /** - * Connect to the Fluence network - */ - async connect(): Promise { - return this.start(); - } + /** + * Connect to the Fluence network + */ + async connect(): Promise { + return this.start(); + } - // /** - // * Disconnect from the Fluence network - // */ - async disconnect(): Promise { - return this.stop(); - } + // /** + // * Disconnect from the Fluence network + // */ + async disconnect(): Promise { + return this.stop(); + } - async start(): Promise { - log.trace('connecting to Fluence network'); - this.changeConnectionState('connecting'); - await super.start(); - // TODO: check connection (`checkConnection` function) here - this.changeConnectionState('connected'); - log.trace('connected'); - } + override async start(): Promise { + log.trace("connecting to Fluence network"); + this.changeConnectionState("connecting"); + await super.start(); + // TODO: check connection (`checkConnection` function) here + this.changeConnectionState("connected"); + log.trace("connected"); + } - async stop(): Promise { - log.trace('disconnecting from Fluence network'); - this.changeConnectionState('disconnecting'); - await super.stop(); - this.changeConnectionState('disconnected'); - log.trace('disconnected'); - } + override async stop(): Promise { + log.trace("disconnecting from Fluence network"); + this.changeConnectionState("disconnecting"); + await super.stop(); + this.changeConnectionState("disconnected"); + log.trace("disconnected"); + } } diff --git a/packages/core/js-client/src/clientPeer/__test__/client.spec.ts b/packages/core/js-client/src/clientPeer/__test__/client.spec.ts index 06c52194..41806e9a 100644 --- a/packages/core/js-client/src/clientPeer/__test__/client.spec.ts +++ b/packages/core/js-client/src/clientPeer/__test__/client.spec.ts @@ -1,17 +1,36 @@ -import { it, describe, expect } from 'vitest'; -import { handleTimeout } from '../../particle/Particle.js'; -import { doNothing } from '../../jsServiceHost/serviceUtils.js'; -import { registerHandlersHelper, withClient } from '../../util/testUtils.js'; -import { checkConnection } from '../checkConnection.js'; -import { nodes, RELAY } from './connection.js'; -import { CallServiceData } from '../../jsServiceHost/interfaces.js'; +/** + * Copyright 2023 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. + */ -describe('FluenceClient usage test suite', () => { - it('should make a call through network', async () => { - await withClient(RELAY, {}, async (peer) => { - // arrange +import { JSONValue } from "@fluencelabs/interfaces"; +import { it, describe, expect } from "vitest"; - const script = ` +import { CallServiceData } from "../../jsServiceHost/interfaces.js"; +import { doNothing } from "../../jsServiceHost/serviceUtils.js"; +import { handleTimeout } from "../../particle/Particle.js"; +import { registerHandlersHelper, withClient } from "../../util/testUtils.js"; +import { checkConnection } from "../checkConnection.js"; + +import { nodes, RELAY } from "./connection.js"; + +describe("FluenceClient usage test suite", () => { + it("should make a call through network", async () => { + await withClient(RELAY, {}, async (peer) => { + // arrange + + const script = ` (xor (seq (call %init_peer_id% ("load" "relay") [] init_relay) @@ -26,162 +45,179 @@ describe('FluenceClient usage test suite', () => { ) )`; - const particle = await peer.internals.createNewParticle(script); - - const result = await new Promise((resolve, reject) => { - if (particle instanceof Error) { - return reject(particle.message); - } + const particle = await peer.internals.createNewParticle(script); - registerHandlersHelper(peer, particle, { - load: { - relay: () => { - return peer.getRelayPeerId(); - }, - }, - callback: { - callback: (args: any) => { - const [val] = args; - resolve(val); - }, - error: (args: any) => { - const [error] = args; - reject(error); - }, - }, - }); + const result = await new Promise((resolve, reject) => { + if (particle instanceof Error) { + reject(particle.message); + return; + } - peer.internals.initiateParticle(particle, handleTimeout(reject)); - }); - - expect(result).toBe('hello world!'); + registerHandlersHelper(peer, particle, { + load: { + relay: () => { + return peer.getRelayPeerId(); + }, + }, + callback: { + callback: (args): undefined => { + const [val] = args; + resolve(val); + }, + error: (args): undefined => { + const [error] = args; + reject(error); + }, + }, }); + + peer.internals.initiateParticle(particle, handleTimeout(reject)); + }); + + expect(result).toBe("hello world!"); }); + }); - it('check connection should work', async function () { - await withClient(RELAY, {}, async (peer) => { - const isConnected = await checkConnection(peer); + it("check connection should work", async function () { + await withClient(RELAY, {}, async (peer) => { + const isConnected = await checkConnection(peer); - expect(isConnected).toEqual(true); + expect(isConnected).toEqual(true); + }); + }); + + it("check connection should work with ttl", async function () { + await withClient(RELAY, {}, async (peer) => { + const isConnected = await checkConnection(peer, 10000); + + expect(isConnected).toEqual(true); + }); + }); + + it("two clients should work inside the same time javascript process", async () => { + await withClient(RELAY, {}, async (peer1) => { + await withClient(RELAY, {}, async (peer2) => { + const res = new Promise((resolve) => { + peer2.internals.regHandler.common( + "test", + "test", + (req: CallServiceData) => { + resolve(req.args[0]); + return { + result: {}, + retCode: 0, + }; + }, + ); }); - }); - it('check connection should work with ttl', async function () { - await withClient(RELAY, {}, async (peer) => { - const isConnected = await checkConnection(peer, 10000); - - expect(isConnected).toEqual(true); - }); - }); - - it('two clients should work inside the same time javascript process', async () => { - await withClient(RELAY, {}, async (peer1) => { - await withClient(RELAY, {}, async (peer2) => { - const res = new Promise((resolve) => { - peer2.internals.regHandler.common('test', 'test', (req: CallServiceData) => { - resolve(req.args[0]); - return { - result: {}, - retCode: 0, - }; - }); - }); - - const script = ` + const script = ` (seq (call "${peer1.getRelayPeerId()}" ("op" "identity") []) (call "${peer2.getPeerId()}" ("test" "test") ["test"]) ) `; - const particle = await peer1.internals.createNewParticle(script); - if (particle instanceof Error) { - throw particle; - } + const particle = await peer1.internals.createNewParticle(script); - peer1.internals.initiateParticle(particle, doNothing); + if (particle instanceof Error) { + throw particle; + } - expect(await res).toEqual('test'); - }); - }); + peer1.internals.initiateParticle(particle, doNothing); + + expect(await res).toEqual("test"); + }); + }); + }); + + describe("should make connection to network", () => { + it("address as string", async () => { + await withClient(nodes[0].multiaddr, {}, async (peer) => { + const isConnected = await checkConnection(peer); + + expect(isConnected).toBeTruthy(); + }); }); - describe('should make connection to network', () => { - it('address as string', async () => { - await withClient(nodes[0].multiaddr, {}, async (peer) => { - const isConnected = await checkConnection(peer); + it("address as node", async () => { + await withClient(nodes[0], {}, async (peer) => { + const isConnected = await checkConnection(peer); - expect(isConnected).toBeTruthy(); - }); - }); - - it('address as node', async () => { - await withClient(nodes[0], {}, async (peer) => { - const isConnected = await checkConnection(peer); - - expect(isConnected).toBeTruthy(); - }); - }); - - it('With connection options: dialTimeout', async () => { - await withClient(RELAY, { connectionOptions: { dialTimeoutMs: 100000 } }, async (peer) => { - const isConnected = await checkConnection(peer); - - expect(isConnected).toBeTruthy(); - }); - }); - - it('With connection options: skipCheckConnection', async () => { - await withClient(RELAY, { connectionOptions: { skipCheckConnection: true } }, async (peer) => { - const isConnected = await checkConnection(peer); - - expect(isConnected).toBeTruthy(); - }); - }); - - it('With connection options: defaultTTL', async () => { - await withClient(RELAY, { defaultTtlMs: 1 }, async (peer) => { - const isConnected = await checkConnection(peer); - - expect(isConnected).toBeFalsy(); - }); - }); + expect(isConnected).toBeTruthy(); + }); }); - it.skip('Should throw correct error when the client tries to send a particle not to the relay', async () => { - await withClient(RELAY, {}, async (peer) => { - const script = ` + it("With connection options: dialTimeout", async () => { + await withClient( + RELAY, + { connectionOptions: { dialTimeoutMs: 100000 } }, + async (peer) => { + const isConnected = await checkConnection(peer); + + expect(isConnected).toBeTruthy(); + }, + ); + }); + + it("With connection options: skipCheckConnection", async () => { + await withClient( + RELAY, + { connectionOptions: { skipCheckConnection: true } }, + async (peer) => { + const isConnected = await checkConnection(peer); + + expect(isConnected).toBeTruthy(); + }, + ); + }); + + it("With connection options: defaultTTL", async () => { + await withClient(RELAY, { defaultTtlMs: 1 }, async (peer) => { + const isConnected = await checkConnection(peer); + + expect(isConnected).toBeFalsy(); + }); + }); + }); + + it.skip("Should throw correct error when the client tries to send a particle not to the relay", async () => { + await withClient(RELAY, {}, async (peer) => { + const script = ` (xor (call "incorrect_peer_id" ("any" "service") []) (call %init_peer_id% ("callback" "error") [%last_error%]) )`; - const particle = await peer.internals.createNewParticle(script); - const promise = new Promise((resolve, reject) => { - if (particle instanceof Error) { - return reject(particle.message); - } - registerHandlersHelper(peer, particle, { - callback: { - error: (args: any) => { - const [error] = args; - reject(error); - }, - }, - }); + const particle = await peer.internals.createNewParticle(script); - peer.internals.initiateParticle(particle, (stage) => { - if (stage.stage === 'sendingError') { - reject(stage.errorMessage); - } - }); - }); + const promise = new Promise((_resolve, reject) => { + if (particle instanceof Error) { + reject(particle.message); + return; + } - await promise; - - await expect(promise).rejects.toMatch( - 'Particle is expected to be sent to only the single peer (relay which client is connected to)', - ); + registerHandlersHelper(peer, particle, { + callback: { + error: (args): undefined => { + const [error] = args; + reject(error); + }, + }, }); + + peer.internals.initiateParticle(particle, (stage) => { + if (stage.stage === "sendingError") { + reject(stage.errorMessage); + } + }); + }); + + await promise; + + await expect(promise).rejects.toMatch( + "Particle is expected to be sent to only the single peer (relay which client is connected to)", + ); }); + }); }); diff --git a/packages/core/js-client/src/clientPeer/__test__/connection.ts b/packages/core/js-client/src/clientPeer/__test__/connection.ts index 99d621b3..8554564b 100644 --- a/packages/core/js-client/src/clientPeer/__test__/connection.ts +++ b/packages/core/js-client/src/clientPeer/__test__/connection.ts @@ -1,8 +1,25 @@ +/** + * Copyright 2023 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 const nodes = [ - { - multiaddr: '/ip4/127.0.0.1/tcp/9991/ws/p2p/12D3KooWBM3SdXWqGaawQDGQ6JprtwswEg3FWGvGhmgmMez1vRbR', - peerId: '12D3KooWBM3SdXWqGaawQDGQ6JprtwswEg3FWGvGhmgmMez1vRbR', - }, + { + multiaddr: + "/ip4/127.0.0.1/tcp/9991/ws/p2p/12D3KooWBM3SdXWqGaawQDGQ6JprtwswEg3FWGvGhmgmMez1vRbR", + peerId: "12D3KooWBM3SdXWqGaawQDGQ6JprtwswEg3FWGvGhmgmMez1vRbR", + }, ]; export const RELAY = nodes[0].multiaddr; diff --git a/packages/core/js-client/src/clientPeer/checkConnection.ts b/packages/core/js-client/src/clientPeer/checkConnection.ts index c969be16..e2f39824 100644 --- a/packages/core/js-client/src/clientPeer/checkConnection.ts +++ b/packages/core/js-client/src/clientPeer/checkConnection.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,22 +13,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ClientPeer } from './ClientPeer.js'; -import { logger } from '../util/logger.js'; -import { WrapFnIntoServiceCall } from '../jsServiceHost/serviceUtils.js'; -import { handleTimeout } from '../particle/Particle.js'; +import { JSONValue } from "@fluencelabs/interfaces"; -const log = logger('connection'); +import { WrapFnIntoServiceCall } from "../jsServiceHost/serviceUtils.js"; +import { handleTimeout } from "../particle/Particle.js"; +import { logger } from "../util/logger.js"; + +import { ClientPeer } from "./ClientPeer.js"; + +const log = logger("connection"); /** * Checks the network connection by sending a ping-like request to relay node * @param { ClientPeer } peer - The Fluence Client instance. */ -export const checkConnection = async (peer: ClientPeer, ttl?: number): Promise => { - const msg = Math.random().toString(36).substring(7); +export const checkConnection = async ( + peer: ClientPeer, + ttl?: number, +): Promise => { + const msg = Math.random().toString(36).substring(7); - const script = ` + const script = ` (xor (seq (call %init_peer_id% ("load" "relay") [] init_relay) @@ -45,73 +51,88 @@ export const checkConnection = async (peer: ClientPeer, ttl?: number): Promise((resolve, reject) => { - if (particle instanceof Error) { - return reject(particle.message); - } + const particle = await peer.internals.createNewParticle(script, ttl); - peer.internals.regHandler.forParticle( - particle.id, - 'load', - 'relay', - WrapFnIntoServiceCall(() => { - return peer.getRelayPeerId(); - }), - ); - - peer.internals.regHandler.forParticle( - particle.id, - 'load', - 'msg', - WrapFnIntoServiceCall(() => { - return msg; - }), - ); - - peer.internals.regHandler.forParticle( - particle.id, - 'callback', - 'callback', - WrapFnIntoServiceCall((args) => { - const [val] = args; - setTimeout(() => { - resolve(val); - }, 0); - return {}; - }), - ); - - peer.internals.regHandler.forParticle( - particle.id, - 'callback', - 'error', - WrapFnIntoServiceCall((args) => { - const [error] = args; - setTimeout(() => { - reject(error); - }, 0); - return {}; - }), - ); - - peer.internals.initiateParticle( - particle, - handleTimeout(() => { - reject('particle timed out'); - }), - ); - }); - - try { - const result = await promise; - if (result != msg) { - log.error("unexpected behavior. 'identity' must return the passed arguments."); - } - return true; - } catch (e) { - log.error('error on establishing connection. Relay: %s error: %j', peer.getRelayPeerId(), e); - return false; + const promise = new Promise((resolve, reject) => { + if (particle instanceof Error) { + reject(particle.message); + return; } + + peer.internals.regHandler.forParticle( + particle.id, + "load", + "relay", + WrapFnIntoServiceCall(() => { + return peer.getRelayPeerId(); + }), + ); + + peer.internals.regHandler.forParticle( + particle.id, + "load", + "msg", + WrapFnIntoServiceCall(() => { + return msg; + }), + ); + + peer.internals.regHandler.forParticle( + particle.id, + "callback", + "callback", + WrapFnIntoServiceCall((args) => { + const [val] = args; + + setTimeout(() => { + resolve(val); + }, 0); + + return {}; + }), + ); + + peer.internals.regHandler.forParticle( + particle.id, + "callback", + "error", + WrapFnIntoServiceCall((args) => { + const [error] = args; + + setTimeout(() => { + reject(error); + }, 0); + + return {}; + }), + ); + + peer.internals.initiateParticle( + particle, + handleTimeout(() => { + reject("particle timed out"); + }), + ); + }); + + try { + const result = await promise; + + if (result !== msg) { + log.error( + "unexpected behavior. 'identity' must return the passed arguments.", + ); + } + + return true; + } catch (e) { + log.error( + "error on establishing connection. Relay: %s error: %j", + peer.getRelayPeerId(), + e, + ); + + return false; + } }; diff --git a/packages/core/js-client/src/compilerSupport/__test__/v3.spec.ts b/packages/core/js-client/src/compilerSupport/__test__/v3.spec.ts index 651f5eb1..89207e8e 100644 --- a/packages/core/js-client/src/compilerSupport/__test__/v3.spec.ts +++ b/packages/core/js-client/src/compilerSupport/__test__/v3.spec.ts @@ -1,223 +1,248 @@ -import { it, describe, expect, test } from 'vitest'; -import { aqua2ts, ts2aqua } from '../conversions.js'; +/** + * Copyright 2023 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. + */ -const i32 = { tag: 'scalar', name: 'i32' } as const; +import { JSONValue, NonArrowType } from "@fluencelabs/interfaces"; +import { it, describe, expect, test } from "vitest"; + +import { aqua2ts, ts2aqua } from "../conversions.js"; + +const i32 = { tag: "scalar", name: "i32" } as const; const opt_i32 = { - tag: 'option', - type: i32, + tag: "option", + type: i32, } as const; -const array_i32 = { tag: 'array', type: i32 }; +const array_i32 = { tag: "array", type: i32 }; -const array_opt_i32 = { tag: 'array', type: opt_i32 }; +const array_opt_i32 = { tag: "array", type: opt_i32 }; const labeledProduct = { - tag: 'labeledProduct', - fields: { - a: i32, - b: opt_i32, - c: array_opt_i32, - }, + tag: "labeledProduct", + fields: { + a: i32, + b: opt_i32, + c: array_opt_i32, + }, }; const struct = { - tag: 'struct', - name: 'someStruct', - fields: { - a: i32, - b: opt_i32, - c: array_opt_i32, - }, + tag: "struct", + name: "someStruct", + fields: { + a: i32, + b: opt_i32, + c: array_opt_i32, + }, }; const structs = [ - { - aqua: { - a: 1, - b: [2], - c: [[1], [2]], - }, - - ts: { - a: 1, - b: 2, - c: [1, 2], - }, + { + aqua: { + a: 1, + b: [2], + c: [[1], [2]], }, - { - aqua: { - a: 1, - b: [], - c: [[], [2]], - }, - ts: { - a: 1, - b: null, - c: [null, 2], - }, + ts: { + a: 1, + b: 2, + c: [1, 2], }, + }, + { + aqua: { + a: 1, + b: [], + c: [[], [2]], + }, + + ts: { + a: 1, + b: null, + c: [null, 2], + }, + }, ]; const labeledProduct2 = { - tag: 'labeledProduct', - fields: { - x: i32, - y: i32, - }, + tag: "labeledProduct", + fields: { + x: i32, + y: i32, + }, }; const nestedLabeledProductType = { - tag: 'labeledProduct', - fields: { - a: labeledProduct2, - b: { - tag: 'option', - type: labeledProduct2, - }, - c: { - tag: 'array', - type: labeledProduct2, - }, + tag: "labeledProduct", + fields: { + a: labeledProduct2, + b: { + tag: "option", + type: labeledProduct2, }, + c: { + tag: "array", + type: labeledProduct2, + }, + }, }; const nestedStructs = [ - { - aqua: { - a: { - x: 1, - y: 2, - }, - b: [ - { - x: 1, - y: 2, - }, - ], - c: [ - { - x: 1, - y: 2, - }, - { - x: 3, - y: 4, - }, - ], + { + aqua: { + a: { + x: 1, + y: 2, + }, + b: [ + { + x: 1, + y: 2, }, - - ts: { - a: { - x: 1, - y: 2, - }, - b: { - x: 1, - y: 2, - }, - - c: [ - { - x: 1, - y: 2, - }, - { - x: 3, - y: 4, - }, - ], + ], + c: [ + { + x: 1, + y: 2, }, + { + x: 3, + y: 4, + }, + ], }, - { - aqua: { - a: { - x: 1, - y: 2, - }, - b: [], - c: [], - }, - ts: { - a: { - x: 1, - y: 2, - }, - b: null, - c: [], + ts: { + a: { + x: 1, + y: 2, + }, + b: { + x: 1, + y: 2, + }, + + c: [ + { + x: 1, + y: 2, }, + { + x: 3, + y: 4, + }, + ], }, + }, + { + aqua: { + a: { + x: 1, + y: 2, + }, + b: [], + c: [], + }, + + ts: { + a: { + x: 1, + y: 2, + }, + b: null, + c: [], + }, + }, ]; -describe('Conversion from aqua to typescript', () => { - test.each` - aqua | ts | type - ${1} | ${1} | ${i32} - ${[]} | ${null} | ${opt_i32} - ${[1]} | ${1} | ${opt_i32} - ${[1, 2, 3]} | ${[1, 2, 3]} | ${array_i32} - ${[]} | ${[]} | ${array_i32} - ${[[1]]} | ${[1]} | ${array_opt_i32} - ${[[]]} | ${[null]} | ${array_opt_i32} - ${[[1], [2]]} | ${[1, 2]} | ${array_opt_i32} - ${[[], [2]]} | ${[null, 2]} | ${array_opt_i32} - ${structs[0].aqua} | ${structs[0].ts} | ${labeledProduct} - ${structs[1].aqua} | ${structs[1].ts} | ${labeledProduct} - ${structs[0].aqua} | ${structs[0].ts} | ${struct} - ${structs[1].aqua} | ${structs[1].ts} | ${struct} - ${nestedStructs[0].aqua} | ${nestedStructs[0].ts} | ${nestedLabeledProductType} - ${nestedStructs[1].aqua} | ${nestedStructs[1].ts} | ${nestedLabeledProductType} - `( - // - 'aqua: $aqua. ts: $ts. type: $type', - async ({ aqua, ts, type }) => { - // arrange +interface ConversionTestArgs { + aqua: JSONValue; + ts: JSONValue; + type: NonArrowType; +} - // act - const tsFromAqua = aqua2ts(aqua, type); - const aquaFromTs = ts2aqua(ts, type); +describe("Conversion from aqua to typescript", () => { + test.each` + aqua | ts | type + ${1} | ${1} | ${i32} + ${[]} | ${null} | ${opt_i32} + ${[1]} | ${1} | ${opt_i32} + ${[1, 2, 3]} | ${[1, 2, 3]} | ${array_i32} + ${[]} | ${[]} | ${array_i32} + ${[[1]]} | ${[1]} | ${array_opt_i32} + ${[[]]} | ${[null]} | ${array_opt_i32} + ${[[1], [2]]} | ${[1, 2]} | ${array_opt_i32} + ${[[], [2]]} | ${[null, 2]} | ${array_opt_i32} + ${structs[0].aqua} | ${structs[0].ts} | ${labeledProduct} + ${structs[1].aqua} | ${structs[1].ts} | ${labeledProduct} + ${structs[0].aqua} | ${structs[0].ts} | ${struct} + ${structs[1].aqua} | ${structs[1].ts} | ${struct} + ${nestedStructs[0].aqua} | ${nestedStructs[0].ts} | ${nestedLabeledProductType} + ${nestedStructs[1].aqua} | ${nestedStructs[1].ts} | ${nestedLabeledProductType} + `( + // + "aqua: $aqua. ts: $ts. type: $type", + ({ aqua, ts, type }: ConversionTestArgs) => { + // arrange - // assert - expect(tsFromAqua).toStrictEqual(ts); - expect(aquaFromTs).toStrictEqual(aqua); - }, - ); + // act + const tsFromAqua = aqua2ts(aqua, type); + const aquaFromTs = ts2aqua(ts, type); + + // assert + expect(tsFromAqua).toStrictEqual(ts); + expect(aquaFromTs).toStrictEqual(aqua); + }, + ); }); -describe('Conversion corner cases', () => { - it('Should accept undefined in object entry', () => { - // arrange - const type = { - tag: 'labeledProduct', - fields: { - x: opt_i32, - y: opt_i32, - }, - } as const; +describe("Conversion corner cases", () => { + it("Should accept undefined in object entry", () => { + // arrange + const type = { + tag: "labeledProduct", + fields: { + x: opt_i32, + y: opt_i32, + }, + } as const; - const valueInTs = { - x: 1, - }; - const valueInAqua = { - x: [1], - y: [], - }; + const valueInTs = { + x: 1, + }; - // act - const aqua = ts2aqua(valueInTs, type); - const ts = aqua2ts(valueInAqua, type); + const valueInAqua = { + x: [1], + y: [], + }; - // assert - expect(aqua).toStrictEqual({ - x: [1], - y: [], - }); + // act + const aqua = ts2aqua(valueInTs, type); + const ts = aqua2ts(valueInAqua, type); - expect(ts).toStrictEqual({ - x: 1, - y: null, - }); + // assert + expect(aqua).toStrictEqual({ + x: [1], + y: [], }); + + expect(ts).toStrictEqual({ + x: 1, + y: null, + }); + }); }); diff --git a/packages/core/js-client/src/compilerSupport/callFunction.ts b/packages/core/js-client/src/compilerSupport/callFunction.ts index 9f562aa6..7786cee8 100644 --- a/packages/core/js-client/src/compilerSupport/callFunction.ts +++ b/packages/core/js-client/src/compilerSupport/callFunction.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,22 +13,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { CallAquaFunctionType, getArgumentTypes, isReturnTypeVoid } from '@fluencelabs/interfaces'; + +import assert from "assert"; import { - errorHandlingService, - injectRelayService, - injectValueService, - registerParticleScopeService, - responseService, - ServiceDescription, - userHandlerService, -} from './services.js'; + FnConfig, + FunctionCallDef, + getArgumentTypes, + isReturnTypeVoid, + PassedArgs, +} from "@fluencelabs/interfaces"; -import { logger } from '../util/logger.js'; -import { IParticle } from '../particle/interfaces.js'; +import { FluencePeer } from "../jsPeer/FluencePeer.js"; +import { logger } from "../util/logger.js"; -const log = logger('aqua'); +import { + errorHandlingService, + injectRelayService, + injectValueService, + registerParticleScopeService, + responseService, + ServiceDescription, + userHandlerService, +} from "./services.js"; + +const log = logger("aqua"); /** * Convenience function which does all the internal work of creating particles @@ -41,57 +50,96 @@ const log = logger('aqua'); * @param args - args in the form of JSON where each key corresponds to the name of the argument * @returns */ -export const callAquaFunction: CallAquaFunctionType = async ({ def, script, config, peer, args }) => { - log.trace('calling aqua function %j', { def, script, config, args }); - const argumentTypes = getArgumentTypes(def); - const particle = await peer.internals.createNewParticle(script, config?.ttl); - - return new Promise((resolve, reject) => { - if (particle instanceof Error) { - return reject(particle.message); - } - - for (let [name, argVal] of Object.entries(args)) { - const type = argumentTypes[name]; - let service: ServiceDescription; - if (type.tag === 'arrow') { - service = userHandlerService(def.names.callbackSrv, [name, type], argVal); - } else { - service = injectValueService(def.names.getDataSrv, name, type, argVal); - } - registerParticleScopeService(peer, particle, service); - } - - registerParticleScopeService(peer, particle, responseService(def, resolve)); - - registerParticleScopeService(peer, particle, injectRelayService(def, peer)); - - registerParticleScopeService(peer, particle, errorHandlingService(def, reject)); - - peer.internals.initiateParticle(particle, (stage: any) => { - // If function is void, then it's completed when one of the two conditions is met: - // 1. The particle is sent to the network (state 'sent') - // 2. All CallRequests are executed, e.g., all variable loading and local function calls are completed (state 'localWorkDone') - if (isReturnTypeVoid(def) && (stage.stage === 'sent' || stage.stage === 'localWorkDone')) { - resolve(undefined); - } - - if (stage.stage === 'sendingError') { - reject(`Could not send particle for ${def.functionName}: not connected (particle id: ${particle.id})`); - } - - if (stage.stage === 'expired') { - reject( - `Particle expired after ttl of ${particle.ttl}ms for function ${def.functionName} (particle id: ${particle.id})`, - ); - } - - if (stage.stage === 'interpreterError') { - reject( - `Script interpretation failed for ${def.functionName}: ${stage.errorMessage} (particle id: ${particle.id})`, - ); - } - }); - }) +type CallAquaFunctionArgs = { + def: FunctionCallDef; + script: string; + config: FnConfig; + peer: FluencePeer; + args: PassedArgs; +}; + +export const callAquaFunction = async ({ + def, + script, + config, + peer, + args, +}: CallAquaFunctionArgs) => { + // TODO: this function should be rewritten. We can remove asserts if we wont check definition there + log.trace("calling aqua function %j", { def, script, config, args }); + const argumentTypes = getArgumentTypes(def); + + const particle = await peer.internals.createNewParticle(script, config.ttl); + + return new Promise((resolve, reject) => { + for (const [name, argVal] of Object.entries(args)) { + const type = argumentTypes[name]; + let service: ServiceDescription; + + if (type.tag === "arrow") { + // TODO: Add validation here + assert( + typeof argVal === "function", + "Should not be possible, bad types", + ); + + service = userHandlerService( + def.names.callbackSrv, + [name, type], + argVal, + ); + } else { + // TODO: Add validation here + assert( + typeof argVal !== "function", + "Should not be possible, bad types", + ); + + service = injectValueService(def.names.getDataSrv, name, type, argVal); + } + + registerParticleScopeService(peer, particle, service); + } + + registerParticleScopeService(peer, particle, responseService(def, resolve)); + + registerParticleScopeService(peer, particle, injectRelayService(def, peer)); + + registerParticleScopeService( + peer, + particle, + errorHandlingService(def, reject), + ); + + peer.internals.initiateParticle(particle, (stage) => { + // If function is void, then it's completed when one of the two conditions is met: + // 1. The particle is sent to the network (state 'sent') + // 2. All CallRequests are executed, e.g., all variable loading and local function calls are completed (state 'localWorkDone') + if ( + isReturnTypeVoid(def) && + (stage.stage === "sent" || stage.stage === "localWorkDone") + ) { + resolve(undefined); + } + + if (stage.stage === "sendingError") { + reject( + `Could not send particle for ${def.functionName}: not connected (particle id: ${particle.id})`, + ); + } + + if (stage.stage === "expired") { + reject( + `Particle expired after ttl of ${particle.ttl}ms for function ${def.functionName} (particle id: ${particle.id})`, + ); + } + + if (stage.stage === "interpreterError") { + reject( + `Script interpretation failed for ${def.functionName}: ${stage.errorMessage} (particle id: ${particle.id})`, + ); + } + }); + }); }; diff --git a/packages/core/js-client/src/compilerSupport/conversions.ts b/packages/core/js-client/src/compilerSupport/conversions.ts index 5840313f..9e93a252 100644 --- a/packages/core/js-client/src/compilerSupport/conversions.ts +++ b/packages/core/js-client/src/compilerSupport/conversions.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,10 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { jsonify } from '../util/utils.js'; -import { match } from 'ts-pattern'; -import type { ArrowType, ArrowWithoutCallbacks, NonArrowType } from '@fluencelabs/interfaces'; -import { CallServiceData } from '../jsServiceHost/interfaces.js'; + +// TODO: This file is a mess. Need to refactor it later +/* eslint-disable */ +// @ts-nocheck + +import assert from "assert"; + +import type { + ArrowType, + ArrowWithoutCallbacks, + JSONArray, + JSONValue, + NonArrowType, +} from "@fluencelabs/interfaces"; +import { match } from "ts-pattern"; + +import { CallServiceData } from "../jsServiceHost/interfaces.js"; +import { jsonify } from "../util/utils.js"; /** * Convert value from its representation in aqua language to representation in typescript @@ -24,48 +38,53 @@ import { CallServiceData } from '../jsServiceHost/interfaces.js'; * @param type - definition of the aqua type * @returns value represented in typescript */ -export const aqua2ts = (value: any, type: NonArrowType): any => { - const res = match(type) - .with({ tag: 'nil' }, () => { - return null; - }) - .with({ tag: 'option' }, (opt) => { - if (value.length === 0) { - return null; - } else { - return aqua2ts(value[0], opt.type); - } - }) - // @ts-ignore - .with({ tag: 'scalar' }, { tag: 'bottomType' }, { tag: 'topType' }, () => { - return value; - }) - .with({ tag: 'array' }, (arr) => { - return value.map((y: any) => aqua2ts(y, arr.type)); - }) - .with({ tag: 'struct' }, (x) => { - return Object.entries(x.fields).reduce((agg, [key, type]) => { - const val = aqua2ts(value[key], type); - return { ...agg, [key]: val }; - }, {}); - }) - .with({ tag: 'labeledProduct' }, (x) => { - return Object.entries(x.fields).reduce((agg, [key, type]) => { - const val = aqua2ts(value[key], type); - return { ...agg, [key]: val }; - }, {}); - }) - .with({ tag: 'unlabeledProduct' }, (x) => { - return x.items.map((type, index) => { - return aqua2ts(value[index], type); - }); - }) - // uncomment to check that every pattern in matched - // .exhaustive(); - .otherwise(() => { - throw new Error('Unexpected tag: ' + jsonify(type)); - }); - return res; +export const aqua2ts = (value: JSONValue, type: NonArrowType): JSONValue => { + const res = match(type) + .with({ tag: "nil" }, () => { + return null; + }) + .with({ tag: "option" }, (opt) => { + assert(Array.isArray(value), "Should not be possible, bad types"); + + if (value.length === 0) { + return null; + } else { + return aqua2ts(value[0], opt.type); + } + }) + .with({ tag: "scalar" }, { tag: "bottomType" }, { tag: "topType" }, () => { + return value; + }) + .with({ tag: "array" }, (arr) => { + assert(Array.isArray(value), "Should not be possible, bad types"); + return value.map((y) => { + return aqua2ts(y, arr.type); + }); + }) + .with({ tag: "struct" }, (x) => { + return Object.entries(x.fields).reduce((agg, [key, type]) => { + const val = aqua2ts(value[key], type); + return { ...agg, [key]: val }; + }, {}); + }) + .with({ tag: "labeledProduct" }, (x) => { + return Object.entries(x.fields).reduce((agg, [key, type]) => { + const val = aqua2ts(value[key], type); + return { ...agg, [key]: val }; + }, {}); + }) + .with({ tag: "unlabeledProduct" }, (x) => { + return x.items.map((type, index) => { + return aqua2ts(value[index], type); + }); + }) + // uncomment to check that every pattern in matched + // .exhaustive(); + .otherwise(() => { + throw new Error("Unexpected tag: " + jsonify(type)); + }); + + return res; }; /** @@ -74,30 +93,35 @@ export const aqua2ts = (value: any, type: NonArrowType): any => { * @param arrow - aqua type definition * @returns arguments in typescript representation */ -export const aquaArgs2Ts = (req: CallServiceData, arrow: ArrowWithoutCallbacks) => { - const argTypes = match(arrow.domain) - .with({ tag: 'labeledProduct' }, (x) => { - return Object.values(x.fields); - }) - .with({ tag: 'unlabeledProduct' }, (x) => { - return x.items; - }) - .with({ tag: 'nil' }, (x) => { - return []; - }) - // uncomment to check that every pattern in matched - // .exhaustive() - .otherwise(() => { - throw new Error('Unexpected tag: ' + jsonify(arrow.domain)); - }); - - if (req.args.length !== argTypes.length) { - throw new Error(`incorrect number of arguments, expected: ${argTypes.length}, got: ${req.args.length}`); - } - - return req.args.map((arg, index) => { - return aqua2ts(arg, argTypes[index]); +export const aquaArgs2Ts = ( + req: CallServiceData, + arrow: ArrowWithoutCallbacks, +): JSONArray => { + const argTypes = match(arrow.domain) + .with({ tag: "labeledProduct" }, (x) => { + return Object.values(x.fields); + }) + .with({ tag: "unlabeledProduct" }, (x) => { + return x.items; + }) + .with({ tag: "nil" }, (x) => { + return []; + }) + // uncomment to check that every pattern in matched + // .exhaustive() + .otherwise(() => { + throw new Error("Unexpected tag: " + jsonify(arrow.domain)); }); + + if (req.args.length !== argTypes.length) { + throw new Error( + `incorrect number of arguments, expected: ${argTypes.length}, got: ${req.args.length}`, + ); + } + + return req.args.map((arg, index) => { + return aqua2ts(arg, argTypes[index]); + }); }; /** @@ -106,49 +130,51 @@ export const aquaArgs2Ts = (req: CallServiceData, arrow: ArrowWithoutCallbacks) * @param type - definition of the aqua type * @returns value represented in aqua */ -export const ts2aqua = (value: any, type: NonArrowType): any => { - const res = match(type) - .with({ tag: 'nil' }, () => { - return null; - }) - .with({ tag: 'option' }, (opt) => { - if (value === null || value === undefined) { - return []; - } else { - return [ts2aqua(value, opt.type)]; - } - }) - // @ts-ignore - .with({ tag: 'scalar' }, { tag: 'bottomType' }, { tag: 'topType' }, () => { - return value; - }) - .with({ tag: 'array' }, (arr) => { - return value.map((y: any) => ts2aqua(y, arr.type)); - }) - .with({ tag: 'struct' }, (x) => { - return Object.entries(x.fields).reduce((agg, [key, type]) => { - const val = ts2aqua(value[key], type); - return { ...agg, [key]: val }; - }, {}); - }) - .with({ tag: 'labeledProduct' }, (x) => { - return Object.entries(x.fields).reduce((agg, [key, type]) => { - const val = ts2aqua(value[key], type); - return { ...agg, [key]: val }; - }, {}); - }) - .with({ tag: 'unlabeledProduct' }, (x) => { - return x.items.map((type, index) => { - return ts2aqua(value[index], type); - }); - }) - // uncomment to check that every pattern in matched - // .exhaustive() - .otherwise(() => { - throw new Error('Unexpected tag: ' + jsonify(type)); - }); +export const ts2aqua = (value: JSONValue, type: NonArrowType): JSONValue => { + const res = match(type) + .with({ tag: "nil" }, () => { + return null; + }) + .with({ tag: "option" }, (opt) => { + if (value === null || value === undefined) { + return []; + } else { + return [ts2aqua(value, opt.type)]; + } + }) + .with({ tag: "scalar" }, { tag: "bottomType" }, { tag: "topType" }, () => { + return value; + }) + .with({ tag: "array" }, (arr) => { + assert(Array.isArray(value), "Should not be possible, bad types"); + return value.map((y) => { + return ts2aqua(y, arr.type); + }); + }) + .with({ tag: "struct" }, (x) => { + return Object.entries(x.fields).reduce((agg, [key, type]) => { + const val = ts2aqua(value[key], type); + return { ...agg, [key]: val }; + }, {}); + }) + .with({ tag: "labeledProduct" }, (x) => { + return Object.entries(x.fields).reduce((agg, [key, type]) => { + const val = ts2aqua(value[key], type); + return { ...agg, [key]: val }; + }, {}); + }) + .with({ tag: "unlabeledProduct" }, (x) => { + return x.items.map((type, index) => { + return ts2aqua(value[index], type); + }); + }) + // uncomment to check that every pattern in matched + // .exhaustive() + .otherwise(() => { + throw new Error("Unexpected tag: " + jsonify(type)); + }); - return res; + return res; }; /** @@ -157,22 +183,25 @@ export const ts2aqua = (value: any, type: NonArrowType): any => { * @param arrowType - the arrow type which describes the service * @returns - value represented in aqua */ -export const returnType2Aqua = (returnValue: any, arrowType: ArrowType) => { - if (arrowType.codomain.tag === 'nil') { - return {}; - } +export const returnType2Aqua = ( + returnValue: any, + arrowType: ArrowType, +) => { + if (arrowType.codomain.tag === "nil") { + return {}; + } - if (arrowType.codomain.items.length === 0) { - return {}; - } + if (arrowType.codomain.items.length === 0) { + return {}; + } - if (arrowType.codomain.items.length === 1) { - return ts2aqua(returnValue, arrowType.codomain.items[0]); - } + if (arrowType.codomain.items.length === 1) { + return ts2aqua(returnValue, arrowType.codomain.items[0]); + } - return arrowType.codomain.items.map((type, index) => { - return ts2aqua(returnValue[index], type); - }); + return arrowType.codomain.items.map((type, index) => { + return ts2aqua(returnValue[index], type); + }); }; /** @@ -181,21 +210,26 @@ export const returnType2Aqua = (returnValue: any, arrowType: ArrowType) => { - return match(arrow.codomain) - .with({ tag: 'nil' }, () => { - return undefined; - }) - .with({ tag: 'unlabeledProduct' }, (x) => { - if (x.items.length === 0) { - return undefined; - } +export const responseServiceValue2ts = ( + req: CallServiceData, + arrow: ArrowType, +) => { + return match(arrow.codomain) + .with({ tag: "nil" }, () => { + return null; + }) + .with({ tag: "unlabeledProduct" }, (x) => { + if (x.items.length === 0) { + return null; + } - if (x.items.length === 1) { - return aqua2ts(req.args[0], x.items[0]); - } + if (x.items.length === 1) { + return aqua2ts(req.args[0], x.items[0]); + } - return req.args.map((y, index) => aqua2ts(y, x.items[index])); - }) - .exhaustive(); + return req.args.map((y, index) => { + return aqua2ts(y, x.items[index]); + }); + }) + .exhaustive(); }; diff --git a/packages/core/js-client/src/compilerSupport/registerService.ts b/packages/core/js-client/src/compilerSupport/registerService.ts index 3c687b8c..8f651e66 100644 --- a/packages/core/js-client/src/compilerSupport/registerService.ts +++ b/packages/core/js-client/src/compilerSupport/registerService.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,43 +13,72 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import type { RegisterServiceType } from '@fluencelabs/interfaces'; -import { registerGlobalService, userHandlerService } from './services.js'; -import { logger } from '../util/logger.js'; +import type { ServiceDef, ServiceImpl } from "@fluencelabs/interfaces"; -const log = logger('aqua'); +import { FluencePeer } from "../jsPeer/FluencePeer.js"; +import { logger } from "../util/logger.js"; -export const registerService: RegisterServiceType = ({ peer, def, serviceId, service }) => { - log.trace('registering aqua service %o', { def, serviceId, service }); +import { registerGlobalService, userHandlerService } from "./services.js"; - // Checking for missing keys - const requiredKeys = def.functions.tag === 'nil' ? [] : Object.keys(def.functions.fields); - const incorrectServiceDefinitions = requiredKeys.filter((f) => !(f in service)); - if (!!incorrectServiceDefinitions.length) { - throw new Error( - `Error registering service ${serviceId}: missing functions: ` + - incorrectServiceDefinitions.map((d) => "'" + d + "'").join(', '), - ); - } +const log = logger("aqua"); - if (!serviceId) { - serviceId = def.defaultServiceId; - } +interface RegisterServiceArgs { + peer: FluencePeer; + def: ServiceDef; + serviceId: string | undefined; + service: ServiceImpl; +} - if (!serviceId) { - throw new Error('Service ID must be specified'); - } +export const registerService = ({ + peer, + def, + serviceId = def.defaultServiceId, + service, +}: RegisterServiceArgs) => { + // TODO: Need to refactor this. We can compute function types from service implementation, making func more type safe + log.trace("registering aqua service %o", { def, serviceId, service }); - const singleFunctions = def.functions.tag === 'nil' ? [] : Object.entries(def.functions.fields); - for (let singleFunction of singleFunctions) { - let [name, type] = singleFunction; - // The function has type of (arg1, arg2, arg3, ... , callParams) => CallServiceResultType | void - // Account for the fact that user service might be defined as a class - .bind(...) - const userDefinedHandler = service[name].bind(service); + // Checking for missing keys + const requiredKeys = + def.functions.tag === "nil" ? [] : Object.keys(def.functions.fields); - const serviceDescription = userHandlerService(serviceId, singleFunction, userDefinedHandler); - registerGlobalService(peer, serviceDescription); - } - log.trace('aqua service registered %s', serviceId); + const incorrectServiceDefinitions = requiredKeys.filter((f) => { + return !(f in service); + }); + + if (serviceId == null) { + throw new Error("Service ID must be specified"); + } + + if (incorrectServiceDefinitions.length > 0) { + throw new Error( + `Error registering service ${serviceId}: missing functions: ` + + incorrectServiceDefinitions + .map((d) => { + return "'" + d + "'"; + }) + .join(", "), + ); + } + + const singleFunctions = + def.functions.tag === "nil" ? [] : Object.entries(def.functions.fields); + + for (const singleFunction of singleFunctions) { + const [name] = singleFunction; + // The function has type of (arg1, arg2, arg3, ... , callParams) => CallServiceResultType | void + // Account for the fact that user service might be defined as a class - .bind(...) + const userDefinedHandler = service[name].bind(service); + + const serviceDescription = userHandlerService( + serviceId, + singleFunction, + userDefinedHandler, + ); + + registerGlobalService(peer, serviceDescription); + } + + log.trace("aqua service registered %s", serviceId); }; diff --git a/packages/core/js-client/src/compilerSupport/services.ts b/packages/core/js-client/src/compilerSupport/services.ts index 7da4c51e..dd1b16e4 100644 --- a/packages/core/js-client/src/compilerSupport/services.ts +++ b/packages/core/js-client/src/compilerSupport/services.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,186 +13,216 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { SecurityTetraplet } from '@fluencelabs/avm'; -import { match } from 'ts-pattern'; -import { Particle } from '../particle/Particle.js'; - -import { aquaArgs2Ts, responseServiceValue2ts, returnType2Aqua, ts2aqua } from './conversions.js'; +import { SecurityTetraplet } from "@fluencelabs/avm"; import { - CallParams, - ArrowWithoutCallbacks, - FunctionCallConstants, - FunctionCallDef, - NonArrowType, - IFluenceInternalApi, -} from '@fluencelabs/interfaces'; -import { CallServiceData, GenericCallServiceHandler, ResultCodes } from '../jsServiceHost/interfaces.js'; -import { fromUint8Array } from 'js-base64'; + CallParams, + ArrowWithoutCallbacks, + FunctionCallDef, + NonArrowType, + ServiceImpl, + JSONValue, +} from "@fluencelabs/interfaces"; +import { fromUint8Array } from "js-base64"; +import { match } from "ts-pattern"; + +import { FluencePeer } from "../jsPeer/FluencePeer.js"; +import { + CallServiceData, + GenericCallServiceHandler, + ResultCodes, +} from "../jsServiceHost/interfaces.js"; +import { Particle } from "../particle/Particle.js"; + +import { + aquaArgs2Ts, + responseServiceValue2ts, + returnType2Aqua, + ts2aqua, +} from "./conversions.js"; export interface ServiceDescription { - serviceId: string; - fnName: string; - handler: GenericCallServiceHandler; + serviceId: string; + fnName: string; + handler: GenericCallServiceHandler; } /** * Creates a service which injects relay's peer id into aqua space */ -export const injectRelayService = (def: FunctionCallDef, peer: IFluenceInternalApi) => { - return { - serviceId: def.names.getDataSrv, - fnName: def.names.relay, - handler: () => { - return { - retCode: ResultCodes.success, - result: peer.internals.getRelayPeerId(), - }; - }, - }; +export const injectRelayService = (def: FunctionCallDef, peer: FluencePeer) => { + return { + serviceId: def.names.getDataSrv, + fnName: def.names.relay, + handler: () => { + return { + retCode: ResultCodes.success, + result: peer.internals.getRelayPeerId(), + }; + }, + }; }; /** * Creates a service which injects plain value into aqua space */ -export const injectValueService = (serviceId: string, fnName: string, valueType: NonArrowType, value: any) => { - return { - serviceId: serviceId, - fnName: fnName, - handler: () => { - return { - retCode: ResultCodes.success, - result: ts2aqua(value, valueType), - }; - }, - }; +export const injectValueService = ( + serviceId: string, + fnName: string, + valueType: NonArrowType, + value: JSONValue, +) => { + return { + serviceId: serviceId, + fnName: fnName, + handler: () => { + return { + retCode: ResultCodes.success, + result: ts2aqua(value, valueType), + }; + }, + }; }; /** * Creates a service which is used to return value from aqua function into typescript space */ -export const responseService = (def: FunctionCallDef, resolveCallback: Function) => { - return { - serviceId: def.names.responseSrv, - fnName: def.names.responseFnName, - handler: (req: CallServiceData) => { - const userFunctionReturn = responseServiceValue2ts(req, def.arrow); +export const responseService = ( + def: FunctionCallDef, + resolveCallback: (val: JSONValue) => void, +) => { + return { + serviceId: def.names.responseSrv, + fnName: def.names.responseFnName, + handler: (req: CallServiceData) => { + const userFunctionReturn = responseServiceValue2ts(req, def.arrow); - setTimeout(() => { - resolveCallback(userFunctionReturn); - }, 0); + setTimeout(() => { + resolveCallback(userFunctionReturn); + }, 0); - return { - retCode: ResultCodes.success, - result: {}, - }; - }, - }; + return { + retCode: ResultCodes.success, + result: {}, + }; + }, + }; }; /** * Creates a service which is used to return errors from aqua function into typescript space */ -export const errorHandlingService = (def: FunctionCallDef, rejectCallback: Function) => { - return { - serviceId: def.names.errorHandlingSrv, - fnName: def.names.errorFnName, - handler: (req: CallServiceData) => { - const [err, _] = req.args; - setTimeout(() => { - rejectCallback(err); - }, 0); - return { - retCode: ResultCodes.success, - result: {}, - }; - }, - }; +export const errorHandlingService = ( + def: FunctionCallDef, + rejectCallback: (err: JSONValue) => void, +) => { + return { + serviceId: def.names.errorHandlingSrv, + fnName: def.names.errorFnName, + handler: (req: CallServiceData) => { + const [err] = req.args; + + setTimeout(() => { + rejectCallback(err); + }, 0); + + return { + retCode: ResultCodes.success, + result: {}, + }; + }, + }; }; /** * Creates a service for user-defined service function handler */ export const userHandlerService = ( - serviceId: string, - arrowType: [string, ArrowWithoutCallbacks], - userHandler: (...args: Array) => Promise, + serviceId: string, + arrowType: [string, ArrowWithoutCallbacks], + userHandler: ServiceImpl[string], ) => { - const [fnName, type] = arrowType; - return { - serviceId, - fnName, - handler: async (req: CallServiceData) => { - const args = [...aquaArgs2Ts(req, type), extractCallParams(req, type)]; - const rawResult = await userHandler.apply(null, args); - const result = returnType2Aqua(rawResult, type); + const [fnName, type] = arrowType; + return { + serviceId, + fnName, + handler: async (req: CallServiceData) => { + const args: [...JSONValue[], CallParams] = [ + ...aquaArgs2Ts(req, type), + extractCallParams(req, type), + ]; - return { - retCode: ResultCodes.success, - result: result, - }; - }, - }; -}; + const rawResult = await userHandler.bind(null)(...args); + const result = returnType2Aqua(rawResult, type); -/** - * Converts argument of aqua function to a corresponding service. - * For arguments of non-arrow types the resulting service injects the argument into aqua space. - * For arguments of arrow types the resulting service calls the corresponding function. - */ -export const argToServiceDef = ( - arg: any, - argName: string, - argType: NonArrowType | ArrowWithoutCallbacks, - names: FunctionCallConstants, -): ServiceDescription => { - if (argType.tag === 'arrow') { - return userHandlerService(names.callbackSrv, [argName, argType], arg); - } else { - return injectValueService(names.getDataSrv, argName, arg, argType); - } + return { + retCode: ResultCodes.success, + result: result, + }; + }, + }; }; /** * Extracts call params from from call service data according to aqua type definition */ -const extractCallParams = (req: CallServiceData, arrow: ArrowWithoutCallbacks): CallParams => { - const names = match(arrow.domain) - .with({ tag: 'nil' }, () => { - return [] as string[]; - }) - .with({ tag: 'labeledProduct' }, (x) => { - return Object.keys(x.fields); - }) - .with({ tag: 'unlabeledProduct' }, (x) => { - return x.items.map((_, index) => 'arg' + index); - }) - .exhaustive(); +const extractCallParams = ( + req: CallServiceData, + arrow: ArrowWithoutCallbacks, +): CallParams => { + const names: (string | undefined)[] = match(arrow.domain) + .with({ tag: "nil" }, () => { + return []; + }) + .with({ tag: "unlabeledProduct" }, (x) => { + return x.items.map((_, index) => { + return "arg" + index; + }); + }) + .with({ tag: "labeledProduct" }, (x) => { + return Object.keys(x.fields); + }) + .exhaustive(); - const tetraplets: Record = {}; - for (let i = 0; i < req.args.length; i++) { - if (names[i]) { - tetraplets[names[i]] = req.tetraplets[i]; - } + const tetraplets: Record = {}; + + for (let i = 0; i < req.args.length; i++) { + const name = names[i]; + + if (name != null) { + tetraplets[name] = req.tetraplets[i]; } + } - const callParams = { - ...req.particleContext, - signature: fromUint8Array(req.particleContext.signature), - tetraplets, - }; + const callParams = { + ...req.particleContext, + signature: fromUint8Array(req.particleContext.signature), + tetraplets, + }; - return callParams; + return callParams; }; export const registerParticleScopeService = ( - peer: IFluenceInternalApi, - particle: Particle, - service: ServiceDescription, + peer: FluencePeer, + particle: Particle, + service: ServiceDescription, ) => { - peer.internals.regHandler.forParticle(particle.id, service.serviceId, service.fnName, service.handler); + peer.internals.regHandler.forParticle( + particle.id, + service.serviceId, + service.fnName, + service.handler, + ); }; -export const registerGlobalService = (peer: IFluenceInternalApi, service: ServiceDescription) => { - peer.internals.regHandler.common(service.serviceId, service.fnName, service.handler); +export const registerGlobalService = ( + peer: FluencePeer, + service: ServiceDescription, +) => { + peer.internals.regHandler.common( + service.serviceId, + service.fnName, + service.handler, + ); }; diff --git a/packages/core/js-client/src/connection/RelayConnection.ts b/packages/core/js-client/src/connection/RelayConnection.ts index 42ce62c7..aca20a10 100644 --- a/packages/core/js-client/src/connection/RelayConnection.ts +++ b/packages/core/js-client/src/connection/RelayConnection.ts @@ -1,5 +1,5 @@ -/* - * Copyright 2020 Fluence Labs Limited +/** + * Copyright 2023 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. @@ -13,235 +13,281 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { PeerIdB58 } from '@fluencelabs/interfaces'; -import { pipe } from 'it-pipe'; -import { decode, encode } from 'it-length-prefixed'; -import type { PeerId } from '@libp2p/interface/peer-id'; -import { createLibp2p, Libp2p } from 'libp2p'; -import { noise } from '@chainsafe/libp2p-noise'; -import { yamux } from '@chainsafe/libp2p-yamux'; -import { webSockets } from '@libp2p/websockets'; -import { all } from '@libp2p/websockets/filters'; -import { multiaddr, type Multiaddr } from '@multiformats/multiaddr'; +import { noise } from "@chainsafe/libp2p-noise"; +import { yamux } from "@chainsafe/libp2p-yamux"; +import { PeerIdB58 } from "@fluencelabs/interfaces"; +import { Stream } from "@libp2p/interface/connection"; +import type { PeerId } from "@libp2p/interface/peer-id"; +import { peerIdFromString } from "@libp2p/peer-id"; +import { webSockets } from "@libp2p/websockets"; +import { all } from "@libp2p/websockets/filters"; +import { multiaddr, type Multiaddr } from "@multiformats/multiaddr"; +import { decode, encode } from "it-length-prefixed"; +import map from "it-map"; +import { pipe } from "it-pipe"; +import { createLibp2p, Libp2p } from "libp2p"; +import { identifyService } from "libp2p/identify"; +import { pingService } from "libp2p/ping"; +import { Subject } from "rxjs"; +import { fromString } from "uint8arrays/from-string"; +import { toString } from "uint8arrays/to-string"; -import map from 'it-map'; -import { fromString } from 'uint8arrays/from-string'; -import { toString } from 'uint8arrays/to-string'; +import { KeyPair } from "../keypair/index.js"; +import { IParticle } from "../particle/interfaces.js"; +import { + buildParticleMessage, + Particle, + serializeToString, +} from "../particle/Particle.js"; +import { throwHasNoPeerId } from "../util/libp2pUtils.js"; +import { logger } from "../util/logger.js"; -import { logger } from '../util/logger.js'; -import { Subject } from 'rxjs'; -import { throwIfHasNoPeerId } from '../util/libp2pUtils.js'; -import { IConnection } from './interfaces.js'; -import { IParticle } from '../particle/interfaces.js'; -import { buildParticleMessage, Particle, serializeToString, verifySignature } from '../particle/Particle.js'; -import { identifyService } from 'libp2p/identify'; -import { pingService } from 'libp2p/ping'; -import { unmarshalPublicKey } from '@libp2p/crypto/keys'; -import { peerIdFromString } from '@libp2p/peer-id'; -import { Stream } from '@libp2p/interface/connection'; -import { KeyPair } from '../keypair/index.js'; +import { IConnection } from "./interfaces.js"; -const log = logger('connection'); +const log = logger("connection"); -export const PROTOCOL_NAME = '/fluence/particle/2.0.0'; +export const PROTOCOL_NAME = "/fluence/particle/2.0.0"; /** * Options to configure fluence relay connection */ export interface RelayConnectionConfig { - /** - * Peer id of the Fluence Peer - */ - peerId: PeerId; + /** + * Peer id of the Fluence Peer + */ + peerId: PeerId; - /** - * Multiaddress of the relay to make connection to - */ - relayAddress: Multiaddr; + /** + * Multiaddress of the relay to make connection to + */ + relayAddress: Multiaddr; - /** - * The dialing timeout in milliseconds - */ - dialTimeoutMs?: number; + /** + * The dialing timeout in milliseconds + */ + dialTimeoutMs?: number; - /** - * The maximum number of inbound streams for the libp2p node. - * Default: 1024 - */ - maxInboundStreams: number; + /** + * The maximum number of inbound streams for the libp2p node. + * Default: 1024 + */ + maxInboundStreams: number; - /** - * The maximum number of outbound streams for the libp2p node. - * Default: 1024 - */ - maxOutboundStreams: number; + /** + * The maximum number of outbound streams for the libp2p node. + * Default: 1024 + */ + maxOutboundStreams: number; } /** * Implementation for JS peers which connects to Fluence through relay node */ export class RelayConnection implements IConnection { - private relayAddress: Multiaddr; - private lib2p2Peer: Libp2p | null = null; + private relayAddress: Multiaddr; + private lib2p2Peer: Libp2p | null = null; + private relayPeerId: string; - constructor(private config: RelayConnectionConfig) { - this.relayAddress = multiaddr(this.config.relayAddress); - throwIfHasNoPeerId(this.relayAddress); + constructor(private config: RelayConnectionConfig) { + this.relayAddress = multiaddr(this.config.relayAddress); + const peerId = this.relayAddress.getPeerId(); + + if (peerId == null) { + throwHasNoPeerId(this.relayAddress); } - getRelayPeerId(): string { - // since we check for peer id in constructor, we can safely use ! here - return this.relayAddress.getPeerId()!; + this.relayPeerId = peerId; + } + + getRelayPeerId(): string { + return this.relayPeerId; + } + + supportsRelay(): boolean { + return true; + } + + particleSource = new Subject(); + + async start(): Promise { + // check if already started + if (this.lib2p2Peer !== null) { + return; } - supportsRelay(): boolean { - return true; - } - - particleSource = new Subject(); - - async start(): Promise { - // check if already started - if (this.lib2p2Peer !== null) { - return; - } - - this.lib2p2Peer = await createLibp2p({ - peerId: this.config.peerId, - transports: [ - webSockets({ - filter: all, - }), - ], - streamMuxers: [yamux()], - connectionEncryption: [noise()], - connectionManager: { - dialTimeout: this.config.dialTimeoutMs, - }, - connectionGater: { - // By default, this function forbids connections to private peers. For example multiaddr with ip 127.0.0.1 isn't allowed - denyDialMultiaddr: () => Promise.resolve(false), - }, - services: { - identify: identifyService(), - ping: pingService(), - }, - }); - - const supportedProtocols = (await this.lib2p2Peer.peerStore.get(this.lib2p2Peer.peerId)).protocols; - await this.lib2p2Peer.peerStore.patch(this.lib2p2Peer.peerId, { - protocols: [...supportedProtocols, PROTOCOL_NAME], - }); - - await this.connect(); - } - - async stop(): Promise { - // check if already stopped - if (this.lib2p2Peer === null) { - return; - } - - await this.lib2p2Peer.unhandle(PROTOCOL_NAME); - await this.lib2p2Peer.stop(); - } - - async sendParticle(nextPeerIds: PeerIdB58[], particle: IParticle): Promise { - if (this.lib2p2Peer === null) { - throw new Error('Relay connection is not started'); - } - - if (nextPeerIds.length !== 1 && nextPeerIds[0] !== this.getRelayPeerId()) { - throw new Error( - `Relay connection only accepts peer id of the connected relay. Got: ${JSON.stringify( - nextPeerIds, - )} instead.`, - ); - } - - log.trace('sending particle...'); - // Reusing active connection here - const stream = await this.lib2p2Peer.dialProtocol(this.relayAddress, PROTOCOL_NAME); - log.trace('created stream with id ', stream.id); - const sink = stream.sink; - - await pipe([fromString(serializeToString(particle))], encode(), sink); - log.trace('data written to sink'); - } - - private async processIncomingMessage(msg: string, stream: Stream) { - let particle: Particle | undefined; - try { - particle = Particle.fromString(msg); - log.trace('got particle from stream with id %s and particle id %s', stream.id, particle.id); - const initPeerId = peerIdFromString(particle.initPeerId); - - if (initPeerId.publicKey === undefined) { - log.error( - 'cannot retrieve public key from init_peer_id. particle id: %s. init_peer_id: %s', - particle.id, - particle.initPeerId, - ); - return; + this.lib2p2Peer = await createLibp2p({ + peerId: this.config.peerId, + transports: [ + webSockets({ + filter: all, + }), + ], + streamMuxers: [yamux()], + connectionEncryption: [noise()], + connectionManager: { + ...(this.config.dialTimeoutMs != null + ? { + dialTimeout: this.config.dialTimeoutMs, } + : {}), + }, + connectionGater: { + // By default, this function forbids connections to private peers. For example multiaddr with ip 127.0.0.1 isn't allowed + denyDialMultiaddr: () => { + return Promise.resolve(false); + }, + }, + services: { + identify: identifyService(), + ping: pingService(), + }, + }); - const isVerified = await KeyPair.verifyWithPublicKey( - initPeerId.publicKey, - buildParticleMessage(particle), - particle.signature, - ); - if (isVerified) { - this.particleSource.next(particle); - } else { - log.trace('particle signature is incorrect. rejecting particle with id: %s', particle.id); - } - } catch (e) { - const particleId = particle?.id; - const particleIdMessage = typeof particleId === 'string' ? `. particle id: ${particleId}` : ''; - log.error(`error on handling an incoming message: %O%s`, e, particleIdMessage); - } + const supportedProtocols = ( + await this.lib2p2Peer.peerStore.get(this.lib2p2Peer.peerId) + ).protocols; + + await this.lib2p2Peer.peerStore.patch(this.lib2p2Peer.peerId, { + protocols: [...supportedProtocols, PROTOCOL_NAME], + }); + + await this.connect(); + } + + async stop(): Promise { + // check if already stopped + if (this.lib2p2Peer === null) { + return; } - private async connect() { - if (this.lib2p2Peer === null) { - throw new Error('Relay connection is not started'); - } + await this.lib2p2Peer.unhandle(PROTOCOL_NAME); + await this.lib2p2Peer.stop(); + } - await this.lib2p2Peer.handle( - [PROTOCOL_NAME], - async ({ connection, stream }) => - pipe( - stream.source, - decode(), - (source) => map(source, (buf) => toString(buf.subarray())), - async (source) => { - try { - for await (const msg of source) { - await this.processIncomingMessage(msg, stream); - } - } catch (e) { - log.error('connection closed: %j', e); - } - }, - ), - { - maxInboundStreams: this.config.maxInboundStreams, - maxOutboundStreams: this.config.maxOutboundStreams, - }, + async sendParticle( + nextPeerIds: PeerIdB58[], + particle: IParticle, + ): Promise { + if (this.lib2p2Peer === null) { + throw new Error("Relay connection is not started"); + } + + if (nextPeerIds.length !== 1 && nextPeerIds[0] !== this.getRelayPeerId()) { + throw new Error( + `Relay connection only accepts peer id of the connected relay. Got: ${JSON.stringify( + nextPeerIds, + )} instead.`, + ); + } + + log.trace("sending particle..."); + + // Reusing active connection here + const stream = await this.lib2p2Peer.dialProtocol( + this.relayAddress, + PROTOCOL_NAME, + ); + + log.trace("created stream with id ", stream.id); + const sink = stream.sink; + + await pipe([fromString(serializeToString(particle))], encode(), sink); + log.trace("data written to sink"); + } + + // Await will appear after uncommenting lines in func body + // eslint-disable-next-line @typescript-eslint/require-await + private async processIncomingMessage(msg: string, stream: Stream) { + let particle: Particle | undefined; + + try { + particle = Particle.fromString(msg); + + log.trace( + "got particle from stream with id %s and particle id %s", + stream.id, + particle.id, + ); + + const initPeerId = peerIdFromString(particle.initPeerId); + + if (initPeerId.publicKey === undefined) { + log.error( + "cannot retrieve public key from init_peer_id. particle id: %s. init_peer_id: %s", + particle.id, + particle.initPeerId, ); - log.debug("dialing to the node with client's address: %s", this.lib2p2Peer.peerId.toString()); + return; + } - try { - await this.lib2p2Peer.dial(this.relayAddress); - } catch (e: any) { - if (e.name === 'AggregateError' && e._errors?.length === 1) { - const error = e._errors[0]; - throw new Error(`Error dialing node ${this.relayAddress}:\n${error.code}\n${error.message}`); - } else { - throw e; - } - } + const isVerified = await KeyPair.verifyWithPublicKey( + initPeerId.publicKey, + buildParticleMessage(particle), + particle.signature, + ); + + if (isVerified) { + this.particleSource.next(particle); + } else { + log.trace( + "particle signature is incorrect. rejecting particle with id: %s", + particle.id, + ); + } + } catch (e) { + const particleId = particle?.id; + + const particleIdMessage = + typeof particleId === "string" ? `. particle id: ${particleId}` : ""; + + log.error( + `error on handling an incoming message: %O%s`, + e, + particleIdMessage, + ); } + } + + private async connect() { + if (this.lib2p2Peer === null) { + throw new Error("Relay connection is not started"); + } + + await this.lib2p2Peer.handle( + [PROTOCOL_NAME], + ({ stream }) => { + void pipe( + stream.source, + decode(), + (source) => { + return map(source, (buf) => { + return toString(buf.subarray()); + }); + }, + async (source) => { + try { + for await (const msg of source) { + await this.processIncomingMessage(msg, stream); + } + } catch (e) { + log.error("connection closed: %j", e); + } + }, + ); + }, + { + maxInboundStreams: this.config.maxInboundStreams, + maxOutboundStreams: this.config.maxOutboundStreams, + }, + ); + + log.debug( + "dialing to the node with client's address: %s", + this.lib2p2Peer.peerId.toString(), + ); + + await this.lib2p2Peer.dial(this.relayAddress); + } } diff --git a/packages/core/js-client/src/connection/interfaces.ts b/packages/core/js-client/src/connection/interfaces.ts index b285cdfb..bb0345c4 100644 --- a/packages/core/js-client/src/connection/interfaces.ts +++ b/packages/core/js-client/src/connection/interfaces.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,34 +13,36 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import type { PeerIdB58 } from '@fluencelabs/interfaces'; -import type { Subscribable } from 'rxjs'; -import { IParticle } from '../particle/interfaces.js'; -import { IStartable } from '../util/commonTypes.js'; + +import type { PeerIdB58 } from "@fluencelabs/interfaces"; +import type { Subscribable } from "rxjs"; + +import { IParticle } from "../particle/interfaces.js"; +import { IStartable } from "../util/commonTypes.js"; /** * Interface for connection used in Fluence Peer. */ export interface IConnection extends IStartable { - /** - * Observable that emits particles received from the connection. - */ - particleSource: Subscribable; + /** + * Observable that emits particles received from the connection. + */ + particleSource: Subscribable; - /** - * Send particle to the network using the connection. - * @param nextPeerIds - list of peer ids to send the particle to - * @param particle - particle to send - */ - sendParticle(nextPeerIds: PeerIdB58[], particle: IParticle): Promise; + /** + * Send particle to the network using the connection. + * @param nextPeerIds - list of peer ids to send the particle to + * @param particle - particle to send + */ + sendParticle(nextPeerIds: PeerIdB58[], particle: IParticle): Promise; - /** - * Get peer id of the relay peer. Throws an error if the connection doesn't support relay. - */ - getRelayPeerId(): PeerIdB58; + /** + * Get peer id of the relay peer. Throws an error if the connection doesn't support relay. + */ + getRelayPeerId(): PeerIdB58; - /** - * Check if the connection supports relay. - */ - supportsRelay(): boolean; + /** + * Check if the connection supports relay. + */ + supportsRelay(): boolean; } diff --git a/packages/core/js-client/src/ephemeral/__test__/ephemeral.spec.ts b/packages/core/js-client/src/ephemeral/__test__/ephemeral.spec.ts index c9aad47e..267861e0 100644 --- a/packages/core/js-client/src/ephemeral/__test__/ephemeral.spec.ts +++ b/packages/core/js-client/src/ephemeral/__test__/ephemeral.spec.ts @@ -1,40 +1,54 @@ -import { it, describe, expect, beforeEach, afterEach } from 'vitest'; -import { DEFAULT_CONFIG, FluencePeer } from '../../jsPeer/FluencePeer.js'; -import { CallServiceData, ResultCodes } from '../../jsServiceHost/interfaces.js'; -import { KeyPair } from '../../keypair/index.js'; -import { EphemeralNetworkClient } from '../client.js'; +/** + * Copyright 2023 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 { EphemeralNetwork, defaultConfig } from '../network.js'; +import { it, describe, expect, beforeEach, afterEach } from "vitest"; + +import { DEFAULT_CONFIG, FluencePeer } from "../../jsPeer/FluencePeer.js"; +import { ResultCodes } from "../../jsServiceHost/interfaces.js"; +import { KeyPair } from "../../keypair/index.js"; +import { EphemeralNetworkClient } from "../client.js"; +import { EphemeralNetwork, defaultConfig } from "../network.js"; let en: EphemeralNetwork; let client: FluencePeer; const relay = defaultConfig.peers[0].peerId; // TODO: race condition here. Needs to be fixed -describe.skip('Ephemeral networks tests', () => { - beforeEach(async () => { - en = new EphemeralNetwork(defaultConfig); - await en.up(); +describe.skip("Ephemeral networks tests", () => { + beforeEach(async () => { + en = new EphemeralNetwork(defaultConfig); + await en.up(); - const kp = await KeyPair.randomEd25519(); - client = new EphemeralNetworkClient(DEFAULT_CONFIG, kp, en, relay); - await client.start(); + const kp = await KeyPair.randomEd25519(); + client = new EphemeralNetworkClient(DEFAULT_CONFIG, kp, en, relay); + await client.start(); + }); + + afterEach(async () => { + await client.stop(); + await en.down(); + }); + + it("smoke test", async function () { + // arrange + const peers = defaultConfig.peers.map((x) => { + return x.peerId; }); - afterEach(async () => { - if (client) { - await client.stop(); - } - if (en) { - await en.down(); - } - }); - - it('smoke test', async function () { - // arrange - const peers = defaultConfig.peers.map((x) => x.peerId); - - const script = ` + const script = ` (seq (call "${relay}" ("op" "noop") []) (seq @@ -59,22 +73,27 @@ describe.skip('Ephemeral networks tests', () => { ) `; - const particle = await client.internals.createNewParticle(script); + const particle = await client.internals.createNewParticle(script); - const promise = new Promise((resolve) => { - client.internals.regHandler.forParticle(particle.id, 'test', 'test', (req: CallServiceData) => { - resolve('success'); - return { - result: 'test', - retCode: ResultCodes.success, - }; - }); - }); - - // act - client.internals.initiateParticle(particle, () => {}); - - // assert - await expect(promise).resolves.toBe('success'); + const promise = new Promise((resolve) => { + client.internals.regHandler.forParticle( + particle.id, + "test", + "test", + () => { + resolve("success"); + return { + result: "test", + retCode: ResultCodes.success, + }; + }, + ); }); + + // act + client.internals.initiateParticle(particle, () => {}); + + // assert + await expect(promise).resolves.toBe("success"); + }); }); diff --git a/packages/core/js-client/src/ephemeral/client.ts b/packages/core/js-client/src/ephemeral/client.ts index 78cb9809..c4d45773 100644 --- a/packages/core/js-client/src/ephemeral/client.ts +++ b/packages/core/js-client/src/ephemeral/client.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,25 +13,47 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { PeerIdB58 } from '@fluencelabs/interfaces'; -import { FluencePeer, PeerConfig } from '../jsPeer/FluencePeer.js'; -import { KeyPair } from '../keypair/index.js'; -import { WasmLoaderFromNpm } from '../marine/deps-loader/node.js'; -import { WorkerLoader } from '../marine/worker-script/workerLoader.js'; -import { MarineBackgroundRunner } from '../marine/worker/index.js'; -import { EphemeralNetwork } from './network.js'; -import { JsServiceHost } from '../jsServiceHost/JsServiceHost.js'; + +import { PeerIdB58 } from "@fluencelabs/interfaces"; + +import { FluencePeer, PeerConfig } from "../jsPeer/FluencePeer.js"; +import { JsServiceHost } from "../jsServiceHost/JsServiceHost.js"; +import { KeyPair } from "../keypair/index.js"; +import { WasmLoaderFromNpm } from "../marine/deps-loader/node.js"; +import { MarineBackgroundRunner } from "../marine/worker/index.js"; +import { WorkerLoader } from "../marine/worker-script/workerLoader.js"; + +import { EphemeralNetwork } from "./network.js"; /** * Ephemeral network client is a FluencePeer that connects to a relay peer in an ephemeral network. */ export class EphemeralNetworkClient extends FluencePeer { - constructor(config: PeerConfig, keyPair: KeyPair, network: EphemeralNetwork, relay: PeerIdB58) { - const workerLoader = new WorkerLoader(); - const controlModuleLoader = new WasmLoaderFromNpm('@fluencelabs/marine-js', 'marine-js.wasm'); - const avmModuleLoader = new WasmLoaderFromNpm('@fluencelabs/avm', 'avm.wasm'); - const marine = new MarineBackgroundRunner(workerLoader, controlModuleLoader, avmModuleLoader); - const conn = network.getRelayConnection(keyPair.getPeerId(), relay); - super(config, keyPair, marine, new JsServiceHost(), conn); - } + constructor( + config: PeerConfig, + keyPair: KeyPair, + network: EphemeralNetwork, + relay: PeerIdB58, + ) { + const workerLoader = new WorkerLoader(); + + const controlModuleLoader = new WasmLoaderFromNpm( + "@fluencelabs/marine-js", + "marine-js.wasm", + ); + + const avmModuleLoader = new WasmLoaderFromNpm( + "@fluencelabs/avm", + "avm.wasm", + ); + + const marine = new MarineBackgroundRunner( + workerLoader, + controlModuleLoader, + avmModuleLoader, + ); + + const conn = network.getRelayConnection(keyPair.getPeerId(), relay); + super(config, keyPair, marine, new JsServiceHost(), conn); + } } diff --git a/packages/core/js-client/src/ephemeral/network.ts b/packages/core/js-client/src/ephemeral/network.ts index b953adcb..ba8cf710 100644 --- a/packages/core/js-client/src/ephemeral/network.ts +++ b/packages/core/js-client/src/ephemeral/network.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,200 +13,208 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { PeerIdB58 } from '@fluencelabs/interfaces'; -import { fromBase64Sk, KeyPair } from '../keypair/index.js'; -import { MarineBackgroundRunner } from '../marine/worker/index.js'; -import { WorkerLoaderFromFs } from '../marine/deps-loader/node.js'; +import { PeerIdB58 } from "@fluencelabs/interfaces"; +import { Subject } from "rxjs"; -import { logger } from '../util/logger.js'; -import { Subject } from 'rxjs'; -import { Particle } from '../particle/Particle.js'; +import { IConnection } from "../connection/interfaces.js"; +import { DEFAULT_CONFIG, FluencePeer } from "../jsPeer/FluencePeer.js"; +import { JsServiceHost } from "../jsServiceHost/JsServiceHost.js"; +import { fromBase64Sk, KeyPair } from "../keypair/index.js"; +import { + WorkerLoaderFromFs, + WasmLoaderFromNpm, +} from "../marine/deps-loader/node.js"; +import { IMarineHost } from "../marine/interfaces.js"; +import { MarineBackgroundRunner } from "../marine/worker/index.js"; +import { Particle } from "../particle/Particle.js"; +import { logger } from "../util/logger.js"; -import { WasmLoaderFromNpm } from '../marine/deps-loader/node.js'; -import { DEFAULT_CONFIG, FluencePeer } from '../jsPeer/FluencePeer.js'; -import { IConnection } from '../connection/interfaces.js'; -import { IAvmRunner, IMarineHost } from '../marine/interfaces.js'; -import { JsServiceHost } from '../jsServiceHost/JsServiceHost.js'; - -const log = logger('ephemeral'); +const log = logger("ephemeral"); interface EphemeralConfig { - peers: Array<{ - peerId: PeerIdB58; - sk: string; - }>; + peers: Array<{ + peerId: PeerIdB58; + sk: string; + }>; } export const defaultConfig = { - peers: [ - { - peerId: '12D3KooWJankP2PcEDYCZDdJ26JsU8BMRfdGWyGqbtFiWyoKVtmx', - sk: 'dWNAHhDVuFj9bEieILMu6TcCFRxBJdOPIvAWmf4sZQI=', - }, - { - peerId: '12D3KooWSBTB5sYxdwayUyTnqopBwABsnGFY3p4dTx5hABYDtJjV', - sk: 'dOmaxAeu4Th+MJ22vRDLMFTNbiDgKNXar9fW9ofAMgQ=', - }, - { - peerId: '12D3KooWQjwf781DJ41moW5RrZXypLdnTbo6aMsoA8QLctGGX8RB', - sk: 'TgzaLlxXuOMDNuuuTKEHUKsW0jM4AmX0gahFvkB1KgE=', - }, - { - peerId: '12D3KooWCXWTLFyY1mqKnNAhLQTsjW1zqDzCMbUs8M4a8zdz28HK', - sk: 'hiO2Ta8g2ibMQ7iu5yj9CfN+qQCwE8oRShjr7ortKww=', - }, - { - peerId: '12D3KooWPmZpf4ng6GMS39HLagxsXbjiTPLH5CFJpFAHyN6amw6V', - sk: 'LzJtOHTqxfrlHDW40BKiLfjai8JU4yW6/s2zrXLCcQE=', - }, - { - peerId: '12D3KooWKrx8PZxM1R9A8tp2jmrFf6c6q1ZQiWfD4QkNgh7fWSoF', - sk: 'XMhlk/xr1FPcp7sKQhS18doXlq1x16EMhBC2NGW2LQ4=', - }, - { - peerId: '12D3KooWCbJHvnzSZEXjR1UJmtSUozuJK13iRiCYHLN1gjvm4TZZ', - sk: 'KXPAIqxrSHr7v0ngv3qagcqivFvnQ0xd3s1/rKmi8QU=', - }, - { - peerId: '12D3KooWEvKe7WQHp42W4xhHRgTAWQjtDWyH38uJbLHAsMuTtYvD', - sk: 'GCYMAshGnsrNtrHhuT7ayzh5uCzX99J03PmAXoOcCgw=', - }, - { - peerId: '12D3KooWSznSHN3BGrSykBXkLkFsqo9SYB73wVauVdqeuRt562cC', - sk: 'UP+SEuznS0h259VbFquzyOJAQ4W5iIwhP+hd1PmUQQ0=', - }, - { - peerId: '12D3KooWF57jwbShfnT3c4dNfRDdGjr6SQ3B71m87UVpEpSWHFwi', - sk: '8dl+Crm5RSh0eh+LqLKwX8/Eo4QLpvIjfD8L0wzX4A4=', - }, - { - peerId: '12D3KooWBWrzpSg9nwMLBCa2cJubUjTv63Mfy6PYg9rHGbetaV5C', - sk: 'qolc1FcpJ+vHDon0HeXdUYnstjV1wiVx2p0mjblrfAg=', - }, - { - peerId: '12D3KooWNkLVU6juM8oyN2SVq5nBd2kp7Rf4uzJH1hET6vj6G5j6', - sk: 'vN6QzWILTM7hSHp+iGkKxiXcqs8bzlnH3FPaRaDGSQY=', - }, - { - peerId: '12D3KooWKo1YwGL5vivPiKJMJS7wjtB6B2nJNdSXPkSABT4NKBUU', - sk: 'YbDQ++bsor2kei7rYAsu2SbyoiOYPRzFRZWnNRUpBgQ=', - }, - { - peerId: '12D3KooWLUyBKmmNCyxaPkXoWcUFPcy5qrZsUo2E1tyM6CJmGJvC', - sk: 'ptB9eSFMKudAtHaFgDrRK/1oIMrhBujxbMw2Pzwx/wA=', - }, - { - peerId: '12D3KooWAEZXME4KMu9FvLezsJWDbYFe2zyujyMnDT1AgcAxgcCk', - sk: 'xtwTOKgAbDIgkuPf7RKiR7gYyZ1HY4mOgFMv3sOUcAQ=', - }, - { - peerId: '12D3KooWEhXetsFVAD9h2dRz9XgFpfidho1TCZVhFrczX8h8qgzY', - sk: '1I2MGuiKG1F4FDMiRihVOcOP2mxzOLWJ99MeexK27A4=', - }, - { - peerId: '12D3KooWDBfVNdMyV3hPEF4WLBmx9DwD2t2SYuqZ2mztYmDzZWM1', - sk: 'eqJ4Bp7iN4aBXgPH0ezwSg+nVsatkYtfrXv9obI0YQ0=', - }, - { - peerId: '12D3KooWSyY7wiSiR4vbXa1WtZawi3ackMTqcQhEPrvqtagoWPny', - sk: 'UVM3SBJhPYIY/gafpnd9/q/Fn9V4BE9zkgrvF1T7Pgc=', - }, - { - peerId: '12D3KooWFZmBMGG9PxTs9s6ASzkLGKJWMyPheA5ruaYc2FDkDTmv', - sk: '8RbZfEVpQhPVuhv64uqxENDuSoyJrslQoSQJznxsTQ0=', - }, - { - peerId: '12D3KooWBbhUaqqur6KHPunnKxXjY1daCtqJdy4wRji89LmAkVB4', - sk: 'RbgKmG6soWW9uOi7yRedm+0Qck3f3rw6MSnDP7AcBQs=', - }, - ], + peers: [ + { + peerId: "12D3KooWJankP2PcEDYCZDdJ26JsU8BMRfdGWyGqbtFiWyoKVtmx", + sk: "dWNAHhDVuFj9bEieILMu6TcCFRxBJdOPIvAWmf4sZQI=", + }, + { + peerId: "12D3KooWSBTB5sYxdwayUyTnqopBwABsnGFY3p4dTx5hABYDtJjV", + sk: "dOmaxAeu4Th+MJ22vRDLMFTNbiDgKNXar9fW9ofAMgQ=", + }, + { + peerId: "12D3KooWQjwf781DJ41moW5RrZXypLdnTbo6aMsoA8QLctGGX8RB", + sk: "TgzaLlxXuOMDNuuuTKEHUKsW0jM4AmX0gahFvkB1KgE=", + }, + { + peerId: "12D3KooWCXWTLFyY1mqKnNAhLQTsjW1zqDzCMbUs8M4a8zdz28HK", + sk: "hiO2Ta8g2ibMQ7iu5yj9CfN+qQCwE8oRShjr7ortKww=", + }, + { + peerId: "12D3KooWPmZpf4ng6GMS39HLagxsXbjiTPLH5CFJpFAHyN6amw6V", + sk: "LzJtOHTqxfrlHDW40BKiLfjai8JU4yW6/s2zrXLCcQE=", + }, + { + peerId: "12D3KooWKrx8PZxM1R9A8tp2jmrFf6c6q1ZQiWfD4QkNgh7fWSoF", + sk: "XMhlk/xr1FPcp7sKQhS18doXlq1x16EMhBC2NGW2LQ4=", + }, + { + peerId: "12D3KooWCbJHvnzSZEXjR1UJmtSUozuJK13iRiCYHLN1gjvm4TZZ", + sk: "KXPAIqxrSHr7v0ngv3qagcqivFvnQ0xd3s1/rKmi8QU=", + }, + { + peerId: "12D3KooWEvKe7WQHp42W4xhHRgTAWQjtDWyH38uJbLHAsMuTtYvD", + sk: "GCYMAshGnsrNtrHhuT7ayzh5uCzX99J03PmAXoOcCgw=", + }, + { + peerId: "12D3KooWSznSHN3BGrSykBXkLkFsqo9SYB73wVauVdqeuRt562cC", + sk: "UP+SEuznS0h259VbFquzyOJAQ4W5iIwhP+hd1PmUQQ0=", + }, + { + peerId: "12D3KooWF57jwbShfnT3c4dNfRDdGjr6SQ3B71m87UVpEpSWHFwi", + sk: "8dl+Crm5RSh0eh+LqLKwX8/Eo4QLpvIjfD8L0wzX4A4=", + }, + { + peerId: "12D3KooWBWrzpSg9nwMLBCa2cJubUjTv63Mfy6PYg9rHGbetaV5C", + sk: "qolc1FcpJ+vHDon0HeXdUYnstjV1wiVx2p0mjblrfAg=", + }, + { + peerId: "12D3KooWNkLVU6juM8oyN2SVq5nBd2kp7Rf4uzJH1hET6vj6G5j6", + sk: "vN6QzWILTM7hSHp+iGkKxiXcqs8bzlnH3FPaRaDGSQY=", + }, + { + peerId: "12D3KooWKo1YwGL5vivPiKJMJS7wjtB6B2nJNdSXPkSABT4NKBUU", + sk: "YbDQ++bsor2kei7rYAsu2SbyoiOYPRzFRZWnNRUpBgQ=", + }, + { + peerId: "12D3KooWLUyBKmmNCyxaPkXoWcUFPcy5qrZsUo2E1tyM6CJmGJvC", + sk: "ptB9eSFMKudAtHaFgDrRK/1oIMrhBujxbMw2Pzwx/wA=", + }, + { + peerId: "12D3KooWAEZXME4KMu9FvLezsJWDbYFe2zyujyMnDT1AgcAxgcCk", + sk: "xtwTOKgAbDIgkuPf7RKiR7gYyZ1HY4mOgFMv3sOUcAQ=", + }, + { + peerId: "12D3KooWEhXetsFVAD9h2dRz9XgFpfidho1TCZVhFrczX8h8qgzY", + sk: "1I2MGuiKG1F4FDMiRihVOcOP2mxzOLWJ99MeexK27A4=", + }, + { + peerId: "12D3KooWDBfVNdMyV3hPEF4WLBmx9DwD2t2SYuqZ2mztYmDzZWM1", + sk: "eqJ4Bp7iN4aBXgPH0ezwSg+nVsatkYtfrXv9obI0YQ0=", + }, + { + peerId: "12D3KooWSyY7wiSiR4vbXa1WtZawi3ackMTqcQhEPrvqtagoWPny", + sk: "UVM3SBJhPYIY/gafpnd9/q/Fn9V4BE9zkgrvF1T7Pgc=", + }, + { + peerId: "12D3KooWFZmBMGG9PxTs9s6ASzkLGKJWMyPheA5ruaYc2FDkDTmv", + sk: "8RbZfEVpQhPVuhv64uqxENDuSoyJrslQoSQJznxsTQ0=", + }, + { + peerId: "12D3KooWBbhUaqqur6KHPunnKxXjY1daCtqJdy4wRji89LmAkVB4", + sk: "RbgKmG6soWW9uOi7yRedm+0Qck3f3rw6MSnDP7AcBQs=", + }, + ], }; export interface IEphemeralConnection extends IConnection { - readonly selfPeerId: PeerIdB58; - readonly connections: Map; - receiveParticle(particle: Particle): void; + readonly selfPeerId: PeerIdB58; + readonly connections: Map; + receiveParticle(particle: Particle): void; } export class EphemeralConnection implements IEphemeralConnection { - readonly selfPeerId: PeerIdB58; - readonly connections: Map = new Map(); + readonly selfPeerId: PeerIdB58; + readonly connections: Map = new Map(); - constructor(selfPeerId: PeerIdB58) { - this.selfPeerId = selfPeerId; - } - - start(): Promise { - return Promise.resolve(); + constructor(selfPeerId: PeerIdB58) { + this.selfPeerId = selfPeerId; + } + + start(): Promise { + return Promise.resolve(); + } + + stop(): Promise { + return Promise.resolve(); + } + + connectToOther(other: IEphemeralConnection) { + if (other.selfPeerId === this.selfPeerId) { + return; } - stop(): Promise { - return Promise.resolve(); + this.connections.set(other.selfPeerId, other); + other.connections.set(this.selfPeerId, this); + } + + disconnectFromOther(other: IEphemeralConnection) { + this.connections.delete(other.selfPeerId); + other.connections.delete(this.selfPeerId); + } + + disconnectFromAll() { + for (const other of this.connections.values()) { + this.disconnectFromOther(other); + } + } + + particleSource = new Subject(); + + receiveParticle(particle: Particle): void { + this.particleSource.next(particle); + } + + sendParticle(nextPeerIds: string[], particle: Particle): Promise { + const from = this.selfPeerId; + + for (const to of nextPeerIds) { + const destConnection = this.connections.get(to); + + if (destConnection === undefined) { + log.error("peer %s has no connection with %s", from, to); + continue; + } + + // log.trace(`Sending particle from %s, to %j, particleId %s`, from, to, particle.id); + destConnection.receiveParticle(particle); } - connectToOther(other: IEphemeralConnection) { - if (other.selfPeerId === this.selfPeerId) { - return; - } + return Promise.resolve(); + } - this.connections.set(other.selfPeerId, other); - other.connections.set(this.selfPeerId, this); + getRelayPeerId(): string { + const firstMapKey = this.connections.keys().next(); + + // Empty map + if (firstMapKey.done === true) { + throw new Error("relay is not supported in this Ephemeral network peer"); } - disconnectFromOther(other: IEphemeralConnection) { - this.connections.delete(other.selfPeerId); - other.connections.delete(this.selfPeerId); - } + return firstMapKey.value; + } - disconnectFromAll() { - for (let other of this.connections.values()) { - this.disconnectFromOther(other); - } - } - - particleSource = new Subject(); - - receiveParticle(particle: Particle): void { - this.particleSource.next(Particle.fromString(particle.toString())); - } - - async sendParticle(nextPeerIds: string[], particle: Particle): Promise { - const from = this.selfPeerId; - for (let to of nextPeerIds) { - const destConnection = this.connections.get(to); - if (destConnection === undefined) { - log.error('peer %s has no connection with %s', from, to); - continue; - } - - // log.trace(`Sending particle from %s, to %j, particleId %s`, from, to, particle.id); - destConnection.receiveParticle(particle); - } - } - - getRelayPeerId(): string { - if (this.connections.size === 1) { - return this.connections.keys().next().value; - } - - throw new Error('relay is not supported in this Ephemeral network peer'); - } - - supportsRelay(): boolean { - return this.connections.size === 1; - } + supportsRelay(): boolean { + return this.connections.size === 1; + } } class EphemeralPeer extends FluencePeer { - ephemeralConnection: EphemeralConnection; + ephemeralConnection: EphemeralConnection; - constructor(keyPair: KeyPair, marine: IMarineHost) { - const conn = new EphemeralConnection(keyPair.getPeerId()); - super(DEFAULT_CONFIG, keyPair, marine, new JsServiceHost(), conn); + constructor(keyPair: KeyPair, marine: IMarineHost) { + const conn = new EphemeralConnection(keyPair.getPeerId()); + super(DEFAULT_CONFIG, keyPair, marine, new JsServiceHost(), conn); - this.ephemeralConnection = conn; - } + this.ephemeralConnection = conn; + } } /** @@ -214,83 +222,107 @@ class EphemeralPeer extends FluencePeer { * Ephemeral network is a virtual network which runs locally and focuses on p2p interaction by removing connectivity layer out of the equation. */ export class EphemeralNetwork { - private peers: Map = new Map(); + private peers: Map = new Map(); - workerLoader: WorkerLoaderFromFs; - controlModuleLoader: WasmLoaderFromNpm; - avmModuleLoader: WasmLoaderFromNpm; + workerLoader: WorkerLoaderFromFs; + controlModuleLoader: WasmLoaderFromNpm; + avmModuleLoader: WasmLoaderFromNpm; - constructor(public readonly config: EphemeralConfig) { - // shared worker for all the peers - this.workerLoader = new WorkerLoaderFromFs('../../marine/worker-script'); - this.controlModuleLoader = new WasmLoaderFromNpm('@fluencelabs/marine-js', 'marine-js.wasm'); - this.avmModuleLoader = new WasmLoaderFromNpm('@fluencelabs/avm', 'avm.wasm'); - } + constructor(readonly config: EphemeralConfig) { + // shared worker for all the peers + this.workerLoader = new WorkerLoaderFromFs("../../marine/worker-script"); - /** - * Starts the Ephemeral network up - */ - async up(): Promise { - log.trace('starting ephemeral network up...'); + this.controlModuleLoader = new WasmLoaderFromNpm( + "@fluencelabs/marine-js", + "marine-js.wasm", + ); - const promises = this.config.peers.map(async (x) => { - const kp = await fromBase64Sk(x.sk); - const marine = new MarineBackgroundRunner(this.workerLoader, this.controlModuleLoader, this.avmModuleLoader); - const peerId = kp.getPeerId(); - if (peerId !== x.peerId) { - throw new Error(`Invalid config: peer id ${x.peerId} does not match the secret key ${x.sk}`); - } + this.avmModuleLoader = new WasmLoaderFromNpm( + "@fluencelabs/avm", + "avm.wasm", + ); + } - return new EphemeralPeer(kp, marine); - }); + /** + * Starts the Ephemeral network up + */ + async up(): Promise { + log.trace("starting ephemeral network up..."); - const peers = await Promise.all(promises); + const promises = this.config.peers.map(async (x) => { + const kp = await fromBase64Sk(x.sk); - for (let i = 0; i < peers.length; i++) { - for (let j = 0; j < i; j++) { - if (i === j) { - continue; - } + const marine = new MarineBackgroundRunner( + this.workerLoader, + this.controlModuleLoader, + this.avmModuleLoader, + ); - peers[i].ephemeralConnection.connectToOther(peers[j].ephemeralConnection); - } + const peerId = kp.getPeerId(); + + if (peerId !== x.peerId) { + throw new Error( + `Invalid config: peer id ${x.peerId} does not match the secret key ${x.sk}`, + ); + } + + return new EphemeralPeer(kp, marine); + }); + + const peers = await Promise.all(promises); + + for (let i = 0; i < peers.length; i++) { + for (let j = 0; j < i; j++) { + if (i === j) { + continue; } - const startPromises = peers.map((x) => x.start()); - await Promise.all(startPromises); - - for (let p of peers) { - this.peers.set(p.keyPair.getPeerId(), p); - } + peers[i].ephemeralConnection.connectToOther( + peers[j].ephemeralConnection, + ); + } } - /** - * Shuts the ephemeral network down. Will disconnect all connected peers. - */ - async down(): Promise { - log.trace('shutting down ephemeral network...'); - const peers = Array.from(this.peers.entries()); - const promises = peers.map(async ([k, p]) => { - await p.ephemeralConnection.disconnectFromAll(); - await p.stop(); - }); + const startPromises = peers.map((x) => { + return x.start(); + }); - await Promise.all(promises); - this.peers.clear(); - log.trace('ephemeral network shut down'); + await Promise.all(startPromises); + + for (const p of peers) { + this.peers.set(p.keyPair.getPeerId(), p); + } + } + + /** + * Shuts the ephemeral network down. Will disconnect all connected peers. + */ + async down(): Promise { + log.trace("shutting down ephemeral network..."); + const peers = Array.from(this.peers.entries()); + + const promises = peers.map(async ([, p]) => { + p.ephemeralConnection.disconnectFromAll(); + await p.stop(); + }); + + await Promise.all(promises); + this.peers.clear(); + log.trace("ephemeral network shut down"); + } + + /** + * Gets a relay connection to the specified peer. + */ + getRelayConnection(peerId: PeerIdB58, relayPeerId: PeerIdB58): IConnection { + const relay = this.peers.get(relayPeerId); + + if (relay === undefined) { + throw new Error(`Peer ${relayPeerId} is not found`); } - /** - * Gets a relay connection to the specified peer. - */ - getRelayConnection(peerId: PeerIdB58, relayPeerId: PeerIdB58): IConnection { - const relay = this.peers.get(relayPeerId); - if (relay === undefined) { - throw new Error(`Peer ${relayPeerId} is not found`); - } - - const res = new EphemeralConnection(peerId); - res.connectToOther(relay.ephemeralConnection); - return res; - } + const res = new EphemeralConnection(peerId); + res.connectToOther(relay.ephemeralConnection); + return res; + } } diff --git a/packages/core/js-client/src/fetchers/browser.ts b/packages/core/js-client/src/fetchers/browser.ts index ae710ca9..b01d5958 100644 --- a/packages/core/js-client/src/fetchers/browser.ts +++ b/packages/core/js-client/src/fetchers/browser.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,26 +15,47 @@ */ interface PackageJsonContent { - dependencies: Record; - devDependencies: Record; + dependencies: Record; + devDependencies: Record; } // This will be substituted in build phase const packageJsonContentString = `__PACKAGE_JSON_CONTENT__`; -let parsedPackageJsonContent: PackageJsonContent; +let parsedPackageJsonContent: PackageJsonContent | undefined; -const PRIMARY_CDN = "https://unpkg.com/"; +const PRIMARY_CDN = "https://unpkg.com/"; export async function fetchResource(pkg: string, assetPath: string) { - const packageJsonContent = parsedPackageJsonContent || (parsedPackageJsonContent = JSON.parse(packageJsonContentString)); - const version = packageJsonContent.dependencies[pkg] || packageJsonContent.devDependencies[pkg]; - - if (version === undefined) { - const availableDeps = [...Object.keys(packageJsonContent.dependencies), ...Object.keys(packageJsonContent.devDependencies)]; - throw new Error(`Cannot find version of ${pkg} in package.json. Available versions: ${availableDeps.join(',')}`); - } - - const refinedAssetPath = assetPath.startsWith('/') ? assetPath.slice(1) : assetPath; - - return fetch(new globalThis.URL(`${pkg}@${version}/` + refinedAssetPath, PRIMARY_CDN)); + const packageJsonContent = + parsedPackageJsonContent ?? + // TODO: Should be validated + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + (parsedPackageJsonContent = JSON.parse( + packageJsonContentString, + ) as PackageJsonContent); + + const version = + packageJsonContent.dependencies[pkg] ?? + packageJsonContent.devDependencies[pkg]; + + if (version === undefined) { + const availableDeps = [ + ...Object.keys(packageJsonContent.dependencies), + ...Object.keys(packageJsonContent.devDependencies), + ]; + + throw new Error( + `Cannot find version of ${pkg} in package.json. Available versions: ${availableDeps.join( + ",", + )}`, + ); + } + + const refinedAssetPath = assetPath.startsWith("/") + ? assetPath.slice(1) + : assetPath; + + return fetch( + new globalThis.URL(`${pkg}@${version}/` + refinedAssetPath, PRIMARY_CDN), + ); } diff --git a/packages/core/js-client/src/fetchers/index.ts b/packages/core/js-client/src/fetchers/index.ts index 191ab924..152bc792 100644 --- a/packages/core/js-client/src/fetchers/index.ts +++ b/packages/core/js-client/src/fetchers/index.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,17 +14,20 @@ * limitations under the License. */ -import { fetchResource as fetchResourceBrowser } from './browser.js'; -import { fetchResource as fetchResourceNode } from './node.js'; -import process from 'process'; +import process from "process"; -const isNode = typeof process !== 'undefined' && process?.release?.name === 'node'; +import { fetchResource as fetchResourceIsomorphic } from "#fetcher"; + +const isNode = + // process.release is undefined in browser env + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + typeof process !== "undefined" && process.release?.name === "node"; export async function fetchResource(pkg: string, path: string) { - switch (true) { - case isNode: - return fetchResourceNode(pkg, path); - default: - return fetchResourceBrowser(pkg, path); - } + switch (true) { + case isNode: + return fetchResourceIsomorphic(pkg, path); + default: + return fetchResourceIsomorphic(pkg, path); + } } diff --git a/packages/core/js-client/src/fetchers/node.ts b/packages/core/js-client/src/fetchers/node.ts index 759078e4..a2b3dbc8 100644 --- a/packages/core/js-client/src/fetchers/node.ts +++ b/packages/core/js-client/src/fetchers/node.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,46 +14,46 @@ * limitations under the License. */ -import fs from 'fs'; -import path from 'path'; -import module from 'module'; +import fs from "fs"; +import module from "module"; +import path from "path"; export async function fetchResource(pkg: string, assetPath: string) { - const require = module.createRequire(import.meta.url); - const packagePathIndex = require.resolve(pkg); - - // Ensure that windows path is converted to posix path. So we can find a package - const posixPath = packagePathIndex.split(path.sep).join(path.posix.sep); - - const matches = new RegExp(`(.+${pkg})`).exec(posixPath); - - const packagePath = matches?.[0]; - - if (!packagePath) { - throw new Error(`Cannot find dependency ${pkg} in path ${posixPath}`); - } - - const pathToResource = path.join(packagePath, assetPath); - - const file = await new Promise((resolve, reject) => { - // Cannot use 'fs/promises' with current vite config. This module is not polyfilled by default. - fs.readFile(pathToResource, (err, data) => { - if (err) { - reject(err); - return; - } - resolve(data); - }); - }); - - return new Response(file, { - headers: { - 'Content-type': - assetPath.endsWith('.wasm') - ? 'application/wasm' - : assetPath.endsWith('.js') - ? 'application/javascript' - : 'application/text' - } + const require = module.createRequire(import.meta.url); + const packagePathIndex = require.resolve(pkg); + + // Ensure that windows path is converted to posix path. So we can find a package + const posixPath = packagePathIndex.split(path.sep).join(path.posix.sep); + + const matches = new RegExp(`(.+${pkg})`).exec(posixPath); + + const packagePath = matches?.[0]; + + if (packagePath == null) { + throw new Error(`Cannot find dependency ${pkg} in path ${posixPath}`); + } + + const pathToResource = path.join(packagePath, assetPath); + + const file = await new Promise((resolve, reject) => { + // Cannot use 'fs/promises' with current vite config. This module is not polyfilled by default. + fs.readFile(pathToResource, (err, data) => { + if (err != null) { + reject(err); + return; + } + + resolve(data); }); + }); + + return new Response(file, { + headers: { + "Content-type": assetPath.endsWith(".wasm") + ? "application/wasm" + : assetPath.endsWith(".js") + ? "application/javascript" + : "application/text", + }, + }); } diff --git a/packages/core/js-client/src/index.ts b/packages/core/js-client/src/index.ts index 0003a021..a3470e09 100644 --- a/packages/core/js-client/src/index.ts +++ b/packages/core/js-client/src/index.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,157 +13,235 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import type { ClientConfig, IFluenceClient, RelayOptions, ConnectionState, CallAquaFunctionType, RegisterServiceType } from '@fluencelabs/interfaces'; -import { ClientPeer, makeClientPeerConfig } from './clientPeer/ClientPeer.js'; -import { callAquaFunction } from './compilerSupport/callFunction.js'; -import { registerService } from './compilerSupport/registerService.js'; -import { MarineBackgroundRunner } from './marine/worker/index.js'; -// @ts-ignore -import { BlobWorker, Worker } from 'threads'; -import { doRegisterNodeUtils } from './services/NodeUtils.js'; -import { fetchResource } from './fetchers/index.js'; -import process from 'process'; -import path from 'path'; -import url from 'url'; -import module from 'module'; -const isNode = typeof process !== 'undefined' && process?.release?.name === 'node'; +import module from "module"; +import path from "path"; +import process from "process"; +import url from "url"; -const fetchWorkerCode = () => fetchResource('@fluencelabs/marine-worker', '/dist/browser/marine-worker.umd.cjs').then(res => res.text()); -const fetchMarineJsWasm = () => fetchResource('@fluencelabs/marine-js', '/dist/marine-js.wasm').then(res => res.arrayBuffer()); -const fetchAvmWasm = () => fetchResource('@fluencelabs/avm', '/dist/avm.wasm').then(res => res.arrayBuffer()); +import type { + ClientConfig, + ConnectionState, + RelayOptions, +} from "@fluencelabs/interfaces"; +import { BlobWorker, Worker } from "threads/master"; -const createClient = async (relay: RelayOptions, config: ClientConfig): Promise => { - const marineJsWasm = await fetchMarineJsWasm(); - const avmWasm = await fetchAvmWasm(); - - const marine = new MarineBackgroundRunner({ - async getValue() { - if (isNode) { - const require = module.createRequire(import.meta.url); - const pathToThisFile = path.dirname(url.fileURLToPath(import.meta.url)); - const pathToWorker = require.resolve('@fluencelabs/marine-worker'); - const relativePathToWorker = path.relative(pathToThisFile, pathToWorker); - return new Worker(relativePathToWorker); - } else { - const workerCode = await fetchWorkerCode(); - return BlobWorker.fromText(workerCode) - } - }, - start() { - return Promise.resolve(undefined); - }, - stop() { - return Promise.resolve(undefined); - }, - }, { - getValue() { - return marineJsWasm; - }, start(): Promise { - return Promise.resolve(undefined); - }, stop(): Promise { - return Promise.resolve(undefined); +import { ClientPeer, makeClientPeerConfig } from "./clientPeer/ClientPeer.js"; +import { callAquaFunction } from "./compilerSupport/callFunction.js"; +import { registerService } from "./compilerSupport/registerService.js"; +import { fetchResource } from "./fetchers/index.js"; +import { MarineBackgroundRunner } from "./marine/worker/index.js"; +import { doRegisterNodeUtils } from "./services/NodeUtils.js"; + +const isNode = + // process.release is undefined in browser env + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + typeof process !== "undefined" && process.release?.name === "node"; + +const fetchWorkerCode = async () => { + const resource = await fetchResource( + "@fluencelabs/marine-worker", + "/dist/browser/marine-worker.umd.cjs", + ); + + return resource.text(); +}; + +const fetchMarineJsWasm = async () => { + const resource = await fetchResource( + "@fluencelabs/marine-js", + "/dist/marine-js.wasm", + ); + + return resource.arrayBuffer(); +}; + +const fetchAvmWasm = async () => { + const resource = await fetchResource("@fluencelabs/avm", "/dist/avm.wasm"); + return resource.arrayBuffer(); +}; + +const createClient = async ( + relay: RelayOptions, + config: ClientConfig, +): Promise => { + const marineJsWasm = await fetchMarineJsWasm(); + const avmWasm = await fetchAvmWasm(); + + const marine = new MarineBackgroundRunner( + { + async getValue() { + if (isNode) { + const require = module.createRequire(import.meta.url); + + const pathToThisFile = path.dirname( + url.fileURLToPath(import.meta.url), + ); + + const pathToWorker = require.resolve("@fluencelabs/marine-worker"); + + const relativePathToWorker = path.relative( + pathToThisFile, + pathToWorker, + ); + + return new Worker(relativePathToWorker); + } else { + const workerCode = await fetchWorkerCode(); + return BlobWorker.fromText(workerCode); } - }, { - getValue() { - return avmWasm; - }, start(): Promise { - return Promise.resolve(undefined); - }, stop(): Promise { - return Promise.resolve(undefined); - } - }); - const { keyPair, peerConfig, relayConfig } = await makeClientPeerConfig(relay, config); - const client: IFluenceClient = new ClientPeer(peerConfig, relayConfig, keyPair, marine); - if (isNode) { - doRegisterNodeUtils(client); - } - await client.connect(); - return client; + }, + start() { + return Promise.resolve(undefined); + }, + stop() { + return Promise.resolve(undefined); + }, + }, + { + getValue() { + return marineJsWasm; + }, + start(): Promise { + return Promise.resolve(undefined); + }, + stop(): Promise { + return Promise.resolve(undefined); + }, + }, + { + getValue() { + return avmWasm; + }, + start(): Promise { + return Promise.resolve(undefined); + }, + stop(): Promise { + return Promise.resolve(undefined); + }, + }, + ); + + const { keyPair, peerConfig, relayConfig } = await makeClientPeerConfig( + relay, + config, + ); + + const client = new ClientPeer(peerConfig, relayConfig, keyPair, marine); + + if (isNode) { + doRegisterNodeUtils(client); + } + + await client.connect(); + return client; }; /** * Public interface to Fluence Network */ -export const Fluence = { - defaultClient: undefined as (IFluenceClient | undefined), - /** - * Connect to the Fluence network - * @param relay - relay node to connect to - * @param config - client configuration - */ - connect: async function(relay: RelayOptions, config: ClientConfig): Promise { - const client = await createClient(relay, config); - this.defaultClient = client; - }, +interface FluencePublicApi { + defaultClient: ClientPeer | undefined; + connect: (relay: RelayOptions, config: ClientConfig) => Promise; + disconnect: () => Promise; + onConnectionStateChange: ( + handler: (state: ConnectionState) => void, + ) => ConnectionState; + getClient: () => ClientPeer; +} - /** - * Disconnect from the Fluence network - */ - disconnect: async function(): Promise { - await this.defaultClient?.disconnect(); - this.defaultClient = undefined; - }, +export const Fluence: FluencePublicApi = { + defaultClient: undefined, + /** + * Connect to the Fluence network + * @param relay - relay node to connect to + * @param config - client configuration + */ + connect: async function (relay, config) { + this.defaultClient = await createClient(relay, config); + }, - /** - * Handle connection state changes. Immediately returns the current connection state - */ - onConnectionStateChange(handler: (state: ConnectionState) => void): ConnectionState { - return this.defaultClient?.onConnectionStateChange(handler) || 'disconnected'; - }, + /** + * Disconnect from the Fluence network + */ + disconnect: async function (): Promise { + await this.defaultClient?.disconnect(); + this.defaultClient = undefined; + }, - /** - * Low level API. Get the underlying client instance which holds the connection to the network - * @returns IFluenceClient instance - */ - getClient: async function(): Promise { - if (!this.defaultClient) { - throw new Error('Fluence client is not initialized. Call Fluence.connect() first'); - } - return this.defaultClient; - }, + /** + * Handle connection state changes. Immediately returns the current connection state + */ + onConnectionStateChange(handler) { + return ( + this.defaultClient?.onConnectionStateChange(handler) ?? "disconnected" + ); + }, + + /** + * Low level API. Get the underlying client instance which holds the connection to the network + * @returns IFluenceClient instance + */ + getClient: function () { + if (this.defaultClient == null) { + throw new Error( + "Fluence client is not initialized. Call Fluence.connect() first", + ); + } + + return this.defaultClient; + }, }; -export type { IFluenceClient, ClientConfig, CallParams } from '@fluencelabs/interfaces'; +export type { + IFluenceClient, + ClientConfig, + CallParams, +} from "@fluencelabs/interfaces"; export type { - ArrayType, - ArrowType, - ArrowWithCallbacks, - ArrowWithoutCallbacks, - BottomType, - FunctionCallConstants, - FunctionCallDef, - LabeledProductType, - NilType, - NonArrowType, - OptionType, - ProductType, - ScalarNames, - ScalarType, - ServiceDef, - StructType, - TopType, - UnlabeledProductType, - CallAquaFunctionType, - CallAquaFunctionArgs, - PassedArgs, - FnConfig, - RegisterServiceType, - RegisterServiceArgs, -} from '@fluencelabs/interfaces'; + ArrayType, + ArrowType, + ArrowWithCallbacks, + ArrowWithoutCallbacks, + BottomType, + FunctionCallConstants, + FunctionCallDef, + LabeledProductType, + NilType, + NonArrowType, + OptionType, + ProductType, + ScalarNames, + ScalarType, + ServiceDef, + StructType, + TopType, + UnlabeledProductType, + CallAquaFunctionType, + CallAquaFunctionArgs, + PassedArgs, + FnConfig, + RegisterServiceType, + RegisterServiceArgs, +} from "@fluencelabs/interfaces"; -export { v5_callFunction, v5_registerService } from './api.js'; +export { v5_callFunction, v5_registerService } from "./api.js"; -// @ts-ignore +// @ts-expect-error Writing to global object like this prohibited by ts globalThis.new_fluence = Fluence; -// @ts-ignore +// @ts-expect-error Writing to global object like this prohibited by ts globalThis.fluence = { - clientFactory: createClient, - callAquaFunction, - registerService, + clientFactory: createClient, + callAquaFunction, + registerService, }; export { createClient, callAquaFunction, registerService }; -export { getFluenceInterface, getFluenceInterfaceFromGlobalThis } from './util/loadClient.js'; +export { + KeyPair, + fromBase64Sk, + fromBase58Sk, + fromOpts, +} from "./keypair/index.js"; diff --git a/packages/core/js-client/src/jsPeer/FluencePeer.ts b/packages/core/js-client/src/jsPeer/FluencePeer.ts index 661bafe8..487bc875 100644 --- a/packages/core/js-client/src/jsPeer/FluencePeer.ts +++ b/packages/core/js-client/src/jsPeer/FluencePeer.ts @@ -1,5 +1,5 @@ -/* - * Copyright 2021 Fluence Labs Limited +/** + * Copyright 2023 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. @@ -13,87 +13,89 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { KeyPair } from '../keypair/index.js'; -import type { PeerIdB58 } from '@fluencelabs/interfaces'; -import { deserializeAvmResult, InterpreterResult, KeyPairFormat, serializeAvmArgs } from '@fluencelabs/avm'; +import { Buffer } from "buffer"; + import { - cloneWithNewData, - getActualTTL, - hasExpired, - Particle, - ParticleExecutionStage, - ParticleQueueItem, -} from '../particle/Particle.js'; -import { defaultCallParameters } from '@fluencelabs/marine-js/dist/types' -import { jsonify, isString } from '../util/utils.js'; + deserializeAvmResult, + InterpreterResult, + KeyPairFormat, + serializeAvmArgs, +} from "@fluencelabs/avm"; +import { defaultCallParameters } from "@fluencelabs/marine-js/dist/types"; +import { fromUint8Array } from "js-base64"; import { - concatAll, - concatMap, - filter, - from, - groupBy, - lastValueFrom, - mergeAll, - mergeMap, - Observable, - pipe, - Subject, - tap, - Unsubscribable -} from 'rxjs'; -import { defaultSigGuard, Sig } from '../services/Sig.js'; -import { registerSig } from '../services/_aqua/services.js'; -import { registerSrv } from '../services/_aqua/single-module-srv.js'; -import { registerTracing } from '../services/_aqua/tracing.js'; -import { Buffer } from 'buffer'; + concatMap, + filter, + groupBy, + lastValueFrom, + mergeMap, + pipe, + Subject, + tap, + Unsubscribable, +} from "rxjs"; -import { Srv } from '../services/SingleModuleSrv.js'; -import { Tracing } from '../services/Tracing.js'; - -import { logger } from '../util/logger.js'; -import { getParticleContext, registerDefaultServices, ServiceError } from '../jsServiceHost/serviceUtils.js'; -import { IParticle } from '../particle/interfaces.js'; -import { IConnection } from '../connection/interfaces.js'; -import { IAvmRunner, IMarineHost } from '../marine/interfaces.js'; +import { IConnection } from "../connection/interfaces.js"; import { - CallServiceData, - CallServiceResult, - GenericCallServiceHandler, - IJsServiceHost, - ResultCodes, -} from '../jsServiceHost/interfaces.js'; -import { JSONValue } from '../util/commonTypes.js'; -import { fromUint8Array } from 'js-base64'; + CallServiceData, + CallServiceResult, + IJsServiceHost, + ResultCodes, +} from "../jsServiceHost/interfaces.js"; +import { + getParticleContext, + registerDefaultServices, + ServiceError, +} from "../jsServiceHost/serviceUtils.js"; +import { KeyPair } from "../keypair/index.js"; +import { IMarineHost } from "../marine/interfaces.js"; +import { IParticle } from "../particle/interfaces.js"; +import { + cloneWithNewData, + getActualTTL, + hasExpired, + Particle, + ParticleExecutionStage, + ParticleQueueItem, +} from "../particle/Particle.js"; +import { registerSig } from "../services/_aqua/services.js"; +import { registerSrv } from "../services/_aqua/single-module-srv.js"; +import { registerTracing } from "../services/_aqua/tracing.js"; +import { defaultSigGuard, Sig } from "../services/Sig.js"; +import { Srv } from "../services/SingleModuleSrv.js"; +import { Tracing } from "../services/Tracing.js"; +import { logger } from "../util/logger.js"; +import { jsonify, isString, getErrorMessage } from "../util/utils.js"; -const log_particle = logger('particle'); -const log_peer = logger('peer'); +const log_particle = logger("particle"); +const log_peer = logger("peer"); export type PeerConfig = { - /** - * Sets the default TTL for all particles originating from the peer with no TTL specified. - * If the originating particle's TTL is defined then that value will be used - * If the option is not set default TTL will be 7000 - */ - defaultTtlMs: number; + /** + * Sets the default TTL for all particles originating from the peer with no TTL specified. + * If the originating particle's TTL is defined then that value will be used + * If the option is not set default TTL will be 7000 + */ + defaultTtlMs: number; + /** + * Enables\disabled various debugging features + */ + debug: { /** - * Enables\disabled various debugging features + * If set to true, newly initiated particle ids will be printed to console. + * Useful to see what particle id is responsible for aqua function */ - debug: { - /** - * If set to true, newly initiated particle ids will be printed to console. - * Useful to see what particle id is responsible for aqua function - */ - printParticleId: boolean; - }; + printParticleId: boolean; + }; }; export const DEFAULT_CONFIG: PeerConfig = { - debug: { - printParticleId: false, - }, - defaultTtlMs: 7000, + debug: { + printParticleId: false, + }, + defaultTtlMs: 7000, }; /** @@ -101,468 +103,606 @@ export const DEFAULT_CONFIG: PeerConfig = { * It provides all the necessary features to communicate with Fluence network */ export abstract class FluencePeer { - constructor( - protected readonly config: PeerConfig, - public readonly keyPair: KeyPair, - protected readonly marineHost: IMarineHost, - protected readonly jsServiceHost: IJsServiceHost, - protected readonly connection: IConnection, - ) { - this._initServices(); + constructor( + protected readonly config: PeerConfig, + readonly keyPair: KeyPair, + protected readonly marineHost: IMarineHost, + protected readonly jsServiceHost: IJsServiceHost, + protected readonly connection: IConnection, + ) { + this._initServices(); + } + + async start(): Promise { + log_peer.trace("starting Fluence peer"); + + if (this.config.debug.printParticleId) { + this.printParticleId = true; } - async start(): Promise { - log_peer.trace('starting Fluence peer'); - if (this.config?.debug?.printParticleId) { - this.printParticleId = true; - } - await this.marineHost.start(); + await this.marineHost.start(); - this._startParticleProcessing(); - this.isInitialized = true; - await this.connection.start(); - log_peer.trace('started Fluence peer'); + this._startParticleProcessing(); + this.isInitialized = true; + await this.connection.start(); + log_peer.trace("started Fluence peer"); + } + + /** + * Un-initializes the peer: stops all the underlying workflows, stops the Aqua VM + * and disconnects from the Fluence network + */ + async stop() { + log_peer.trace("stopping Fluence peer"); + + this._particleSourceSubscription?.unsubscribe(); + + log_peer.trace("Waiting for all particles to finish execution"); + this._incomingParticles.complete(); + await this._incomingParticlePromise; + log_peer.trace("All particles finished execution"); + + this._stopParticleProcessing(); + await this.marineHost.stop(); + await this.connection.stop(); + this.isInitialized = false; + log_peer.trace("stopped Fluence peer"); + } + + /** + * Registers marine service within the Fluence peer from wasm file. + * Following helper functions can be used to load wasm files: + * * loadWasmFromFileSystem + * * loadWasmFromNpmPackage + * * loadWasmFromServer + * @param wasm - buffer with the wasm file for service + * @param serviceId - the service id by which the service can be accessed in aqua + */ + async registerMarineService( + wasm: SharedArrayBuffer | Buffer, + serviceId: string, + ): Promise { + if (this.jsServiceHost.hasService(serviceId)) { + throw new Error(`Service with '${serviceId}' id already exists`); } - /** - * Un-initializes the peer: stops all the underlying workflows, stops the Aqua VM - * and disconnects from the Fluence network - */ - async stop() { - log_peer.trace('stopping Fluence peer'); - - this._particleSourceSubscription?.unsubscribe(); + await this.marineHost.createService(wasm, serviceId); + } - log_peer.trace('Waiting for all particles to finish execution'); - this._incomingParticles.complete(); - await this._incomingParticlePromise; - log_peer.trace('All particles finished execution'); - - this._stopParticleProcessing(); - await this.marineHost.stop(); - await this.connection.stop(); - this.isInitialized = false; - log_peer.trace('stopped Fluence peer'); - } + /** + * Removes the specified marine service from the Fluence peer + * @param serviceId - the service id to remove + */ + async removeMarineService(serviceId: string): Promise { + await this.marineHost.removeService(serviceId); + } - /** - * Registers marine service within the Fluence peer from wasm file. - * Following helper functions can be used to load wasm files: - * * loadWasmFromFileSystem - * * loadWasmFromNpmPackage - * * loadWasmFromServer - * @param wasm - buffer with the wasm file for service - * @param serviceId - the service id by which the service can be accessed in aqua - */ - async registerMarineService(wasm: SharedArrayBuffer | Buffer, serviceId: string): Promise { - if (!this.marineHost) { - throw new Error("Can't register marine service: peer is not initialized"); + /** + * @private Is not intended to be used manually. Subject to change + */ + get internals() { + return { + getServices: () => { + return this._classServices; + }, + + getRelayPeerId: () => { + if (this.connection.supportsRelay()) { + return this.connection.getRelayPeerId(); } - if (this.jsServiceHost.hasService(serviceId)) { - throw new Error(`Service with '${serviceId}' id already exists`); + throw new Error("Relay is not supported by the current connection"); + }, + + parseAst: async ( + air: string, + ): Promise<{ success: boolean; data: unknown }> => { + if (!this.isInitialized) { + new Error("Can't use avm: peer is not initialized"); } - await this.marineHost.createService(wasm, serviceId); - } + const res = await this.marineHost.callService( + "avm", + "ast", + [air], + defaultCallParameters, + ); - /** - * Removes the specified marine service from the Fluence peer - * @param serviceId - the service id to remove - */ - async removeMarineService(serviceId: string): Promise { - await this.marineHost.removeService(serviceId); - } + if (!isString(res)) { + throw new Error( + `Call to avm:ast expected to return string. Actual return: ${JSON.stringify( + res, + )}`, + ); + } - // internal api + try { + if (res.startsWith("error")) { + return { + success: false, + data: res, + }; + } else { + return { + success: true, + data: JSON.parse(res), + }; + } + } catch (err) { + throw new Error( + "Failed to call avm. Result: " + + res + + ". Error: " + + getErrorMessage(err), + ); + } + }, - /** - * @private Is not intended to be used manually. Subject to change - */ - get internals() { - return { - getServices: () => this._classServices, + createNewParticle: ( + script: string, + ttl: number = this.config.defaultTtlMs, + ): Promise => { + return Particle.createNew( + script, + this.keyPair.getPeerId(), + ttl, + this.keyPair, + ); + }, - getRelayPeerId: () => { - if (this.connection.supportsRelay()) { - return this.connection.getRelayPeerId(); - } + /** + * Initiates a new particle execution starting from local peer + * @param particle - particle to start execution of + * @param onStageChange - callback for reacting on particle state changes + */ + initiateParticle: ( + particle: IParticle, + onStageChange: (stage: ParticleExecutionStage) => void, + ): void => { + if (!this.isInitialized) { + throw new Error( + "Cannot initiate new particle: peer is not initialized", + ); + } - throw new Error('Relay is not supported by the current connection'); - }, + if (this.printParticleId) { + // This is intended console-log + // eslint-disable-next-line no-console + console.log("Particle id: ", particle.id); + } - parseAst: async (air: string): Promise<{ success: boolean; data: any }> => { - if (!this.isInitialized) { - new Error("Can't use avm: peer is not initialized"); - } + this._incomingParticles.next({ + particle: particle, + callResults: [], + onStageChange: onStageChange, + }); + }, - const res = await this.marineHost.callService('avm', 'ast', [air], defaultCallParameters); - if (!isString(res)) { - throw new Error(`Call to avm:ast expected to return string. Actual return: ${res}`); - } + /** + * Register Call Service handler functions + */ + regHandler: { + /** + * Register handler for all particles + */ + common: this.jsServiceHost.registerGlobalHandler.bind( + this.jsServiceHost, + ), + /** + * Register handler which will be called only for particle with the specific id + */ + forParticle: this.jsServiceHost.registerParticleScopeHandler.bind( + this.jsServiceHost, + ), + }, + }; + } - try { - if (res.startsWith('error')) { - return { - success: false, - data: res, - }; - } else { - return { - success: true, - data: JSON.parse(res), - }; - } - } catch (err) { - throw new Error('Failed to call avm. Result: ' + res + '. Error: ' + err); - } - }, + // Queues for incoming and outgoing particles - createNewParticle: (script: string, ttl: number = this.config.defaultTtlMs): Promise => { - return Particle.createNew(script, this.keyPair.getPeerId(), ttl, this.keyPair); - }, + private _incomingParticles = new Subject(); + private _timeouts: Array = []; + private _particleSourceSubscription?: Unsubscribable; + private _incomingParticlePromise?: Promise; - /** - * Initiates a new particle execution starting from local peer - * @param particle - particle to start execution of - * @param onStageChange - callback for reacting on particle state changes - */ - initiateParticle: (particle: IParticle, onStageChange: (stage: ParticleExecutionStage) => void): void => { - if (!this.isInitialized) { - throw new Error('Cannot initiate new particle: peer is not initialized'); - } + // Internal peer state - if (this.printParticleId) { - console.log('Particle id: ', particle.id); - } + // @ts-expect-error - initialized in constructor through `_initServices` call + private _classServices: { + sig: Sig; + srv: Srv; + tracing: Tracing; + }; - this._incomingParticles.next({ - particle: particle, - callResults: [], - onStageChange: onStageChange, - }); - }, + private isInitialized = false; + private printParticleId = false; - /** - * Register Call Service handler functions - */ - regHandler: { - /** - * Register handler for all particles - */ - common: this.jsServiceHost.registerGlobalHandler.bind(this.jsServiceHost), - /** - * Register handler which will be called only for particle with the specific id - */ - forParticle: this.jsServiceHost.registerParticleScopeHandler.bind(this.jsServiceHost), - }, - }; - } - - // Queues for incoming and outgoing particles - - private _incomingParticles = new Subject(); - private _timeouts: Array = []; - private _particleSourceSubscription?: Unsubscribable; - private _incomingParticlePromise?: Promise; - - // Internal peer state - - // @ts-expect-error - initialized in constructor through `_initServices` call - private _classServices: { - sig: Sig; - srv: Srv; - tracing: Tracing; + private _initServices() { + this._classServices = { + sig: new Sig(this.keyPair), + srv: new Srv(this), + tracing: new Tracing(), }; - private isInitialized = false; - private printParticleId = false; + const peerId = this.keyPair.getPeerId(); - private _initServices() { - this._classServices = { - sig: new Sig(this.keyPair), - srv: new Srv(this), - tracing: new Tracing(), - }; + registerDefaultServices(this); - const peerId = this.keyPair.getPeerId(); + this._classServices.sig.securityGuard = defaultSigGuard(peerId); + registerSig(this, "sig", this._classServices.sig); + registerSig(this, peerId, this._classServices.sig); + registerSrv(this, "single_module_srv", this._classServices.srv); + registerTracing(this, "tracingSrv", this._classServices.tracing); + } - registerDefaultServices(this); - - this._classServices.sig.securityGuard = defaultSigGuard(peerId); - registerSig(this, 'sig', this._classServices.sig); - registerSig(this, peerId, this._classServices.sig); - registerSrv(this, 'single_module_srv', this._classServices.srv); - registerTracing(this, 'tracingSrv', this._classServices.tracing); - } - - private _startParticleProcessing() { - this._particleSourceSubscription = this.connection.particleSource.subscribe({ - next: (p) => { - this._incomingParticles.next({ particle: p, callResults: [], onStageChange: () => {} }); - }, - }); - - this._incomingParticlePromise = lastValueFrom(this._incomingParticles - .pipe( - tap((item) => { - log_particle.debug('id %s. received:', item.particle.id); - log_particle.trace('id %s. data: %j', item.particle.id, { - initPeerId: item.particle.initPeerId, - timestamp: item.particle.timestamp, - ttl: item.particle.ttl, - signature: item.particle.signature, - }); - - log_particle.trace('id %s. script: %s', item.particle.id, item.particle.script); - log_particle.trace('id %s. call results: %j', item.particle.id, item.callResults); - }), - filterExpiredParticles(this._expireParticle.bind(this)), - groupBy(item => fromUint8Array(item.particle.signature)), - mergeMap(group$ => { - let prevData: Uint8Array = Buffer.from([]); - let firstRun = true; - - return group$.pipe( - concatMap(async (item) => { - if (firstRun) { - const timeout = setTimeout(() => { - this._expireParticle(item); - }, getActualTTL(item.particle)); - - this._timeouts.push(timeout); - firstRun = false; - } - - if (!this.isInitialized || this.marineHost === undefined) { - // If `.stop()` was called return null to stop particle processing immediately - return null; - } - - // IMPORTANT! - // AVM runner execution and prevData <-> newData swapping - // MUST happen sequentially (in a critical section). - // Otherwise the race might occur corrupting the prevData - - log_particle.debug('id %s. sending particle to interpreter', item.particle.id); - log_particle.trace('id %s. prevData: %s', item.particle.id, this.decodeAvmData(prevData).slice(0, 50)); - - const args = serializeAvmArgs( - { - initPeerId: item.particle.initPeerId, - currentPeerId: this.keyPair.getPeerId(), - timestamp: item.particle.timestamp, - ttl: item.particle.ttl, - keyFormat: KeyPairFormat.Ed25519, - particleId: item.particle.id, - secretKeyBytes: this.keyPair.toEd25519PrivateKey(), - }, - item.particle.script, - prevData, - item.particle.data, - item.callResults, - ); - - let avmCallResult: InterpreterResult | Error; - try { - const res = await this.marineHost.callService('avm', 'invoke', args, defaultCallParameters); - avmCallResult = deserializeAvmResult(res); - } catch (e) { - avmCallResult = e instanceof Error ? e : new Error(String(e)); - } - - if (!(avmCallResult instanceof Error) && avmCallResult.retCode === 0) { - const newData = Buffer.from(avmCallResult.data); - prevData = newData; - } - - return { - ...item, - result: avmCallResult, - }; - }), - filter((item): item is NonNullable => item !== null), - filterExpiredParticles(this._expireParticle.bind(this)), - mergeMap(async (item) => { - // If peer was stopped, do not proceed further - if (!this.isInitialized) { - return; - } - - // Do not continue if there was an error in particle interpretation - if (item.result instanceof Error) { - log_particle.error('id %s. interpreter failed: %s', item.particle.id, item.result.message); - item.onStageChange({ stage: 'interpreterError', errorMessage: item.result.message }); - return; - } - - if (item.result.retCode !== 0) { - log_particle.error( - 'id %s. interpreter failed: retCode: %d, message: %s', - item.particle.id, - item.result.retCode, - item.result.errorMessage, - ); - log_particle.trace('id %s. avm data: %s', item.particle.id, this.decodeAvmData(item.result.data)); - item.onStageChange({ stage: 'interpreterError', errorMessage: item.result.errorMessage }); - return; - } - - log_particle.trace( - 'id %s. interpreter result: retCode: %d, avm data: %s', - item.particle.id, - item.result.retCode, - this.decodeAvmData(item.result.data) - ); - - setTimeout(() => { - item.onStageChange({ stage: 'interpreted' }); - }, 0); - - let connectionPromise: Promise = Promise.resolve(); - - // send particle further if requested - if (item.result.nextPeerPks.length > 0) { - const newParticle = cloneWithNewData(item.particle, Buffer.from(item.result.data)); - - // Do not send particle after the peer has been stopped - if (!this.isInitialized) { - return; - } - - log_particle.debug( - 'id %s. sending particle into network. Next peer ids: %s', - newParticle.id, - item.result.nextPeerPks.toString(), - ); - - connectionPromise = this.connection - ?.sendParticle(item.result.nextPeerPks, newParticle) - .then(() => { - log_particle.trace('id %s. send successful', newParticle.id); - item.onStageChange({ stage: 'sent' }); - }) - .catch((e: any) => { - log_particle.error('id %s. send failed %j', newParticle.id, e); - item.onStageChange({ stage: 'sendingError', errorMessage: e.toString() }); - }); - } - - // execute call requests if needed - // and put particle with the results back to queue - if (item.result.callRequests.length > 0) { - for (const [key, cr] of item.result.callRequests) { - const req = { - fnName: cr.functionName, - args: cr.arguments, - serviceId: cr.serviceId, - tetraplets: cr.tetraplets, - particleContext: getParticleContext(item.particle), - }; - - this._execSingleCallRequest(req) - .catch((err): CallServiceResult => { - if (err instanceof ServiceError) { - return { - retCode: ResultCodes.error, - result: err.message, - }; - } - - return { - retCode: ResultCodes.error, - result: `Service call failed. fnName="${req.fnName}" serviceId="${ - req.serviceId - }" error: ${err.toString()}`, - }; - }) - .then((res) => { - const serviceResult = { - result: jsonify(res.result), - retCode: res.retCode, - }; - - const newParticle = cloneWithNewData(item.particle, Buffer.from([])); - this._incomingParticles.next({ - ...item, - particle: newParticle, - callResults: [[key, serviceResult]], - }); - }); - } - } else { - item.onStageChange({ stage: 'localWorkDone' }); - } - - return connectionPromise; - }), - - ) - }) - ), { defaultValue: undefined }); - } - - private _expireParticle(item: ParticleQueueItem) { - const particleId = item.particle.id; - log_particle.debug( - 'id %s. particle has expired after %d. Deleting particle-related queues and handlers', - item.particle.id, - item.particle.ttl, - ); - - this.jsServiceHost.removeParticleScopeHandlers(particleId); - - item.onStageChange({ stage: 'expired' }); - } - - private decodeAvmData(data: Uint8Array) { - return new TextDecoder().decode(data.buffer); - } - - private async _execSingleCallRequest(req: CallServiceData): Promise { - const particleId = req.particleContext.particleId; - log_particle.trace('id %s. executing call service handler %j', particleId, req); - - if (this.marineHost && await this.marineHost.hasService(req.serviceId)) { - // TODO build correct CallParameters instead of default ones - const result = await this.marineHost.callService(req.serviceId, req.fnName, req.args, defaultCallParameters); - - return { - retCode: ResultCodes.success, - result: result as JSONValue, - }; - } - - let res = await this.jsServiceHost.callService(req); - - if (res === null) { - res = { - retCode: ResultCodes.error, - result: `No service found for service call: serviceId='${req.serviceId}', fnName='${ - req.fnName - }' args='${jsonify(req.args)}'`, - }; - } - - log_particle.trace('id %s. executed call service handler, req: %j, res: %j ', particleId, req, res); - return res; - } - - private _stopParticleProcessing() { - // do not hang if the peer has been stopped while some of the timeouts are still being executed - this._timeouts.forEach((timeout) => { - clearTimeout(timeout); - }); - } -} - -function filterExpiredParticles(onParticleExpiration: (item: T) => void) { - return pipe( - tap((item: T) => { - if (hasExpired(item.particle)) { - onParticleExpiration(item); - } - }), - filter((x) => !hasExpired(x.particle)), + private _startParticleProcessing() { + this._particleSourceSubscription = this.connection.particleSource.subscribe( + { + next: (p) => { + this._incomingParticles.next({ + particle: p, + callResults: [], + onStageChange: () => {}, + }); + }, + }, ); + + this._incomingParticlePromise = lastValueFrom( + this._incomingParticles.pipe( + tap((item) => { + log_particle.debug("id %s. received:", item.particle.id); + + log_particle.trace("id %s. data: %j", item.particle.id, { + initPeerId: item.particle.initPeerId, + timestamp: item.particle.timestamp, + ttl: item.particle.ttl, + signature: item.particle.signature, + }); + + log_particle.trace( + "id %s. script: %s", + item.particle.id, + item.particle.script, + ); + + log_particle.trace( + "id %s. call results: %j", + item.particle.id, + item.callResults, + ); + }), + filterExpiredParticles(this._expireParticle.bind(this)), + groupBy((item) => { + return fromUint8Array(item.particle.signature); + }), + mergeMap((group$) => { + let prevData: Uint8Array = Buffer.from([]); + let firstRun = true; + + return group$.pipe( + concatMap(async (item) => { + if (firstRun) { + const timeout = setTimeout(() => { + this._expireParticle(item); + }, getActualTTL(item.particle)); + + this._timeouts.push(timeout); + firstRun = false; + } + + if (!this.isInitialized) { + // If `.stop()` was called return null to stop particle processing immediately + return null; + } + + // IMPORTANT! + // AVM runner execution and prevData <-> newData swapping + // MUST happen sequentially (in a critical section). + // Otherwise the race might occur corrupting the prevData + + log_particle.debug( + "id %s. sending particle to interpreter", + item.particle.id, + ); + + log_particle.trace( + "id %s. prevData: %s", + item.particle.id, + this.decodeAvmData(prevData).slice(0, 50), + ); + + const args = serializeAvmArgs( + { + initPeerId: item.particle.initPeerId, + currentPeerId: this.keyPair.getPeerId(), + timestamp: item.particle.timestamp, + ttl: item.particle.ttl, + keyFormat: KeyPairFormat.Ed25519, + particleId: item.particle.id, + secretKeyBytes: this.keyPair.toEd25519PrivateKey(), + }, + item.particle.script, + prevData, + item.particle.data, + item.callResults, + ); + + let avmCallResult: InterpreterResult | Error; + + try { + const res = await this.marineHost.callService( + "avm", + "invoke", + args, + defaultCallParameters, + ); + + avmCallResult = deserializeAvmResult(res); + } catch (e) { + avmCallResult = e instanceof Error ? e : new Error(String(e)); + } + + if ( + !(avmCallResult instanceof Error) && + avmCallResult.retCode === 0 + ) { + const newData = Buffer.from(avmCallResult.data); + prevData = newData; + } + + return { + ...item, + result: avmCallResult, + }; + }), + filter((item): item is NonNullable => { + return item !== null; + }), + filterExpiredParticles< + ParticleQueueItem & { + result: Error | InterpreterResult; + } + >(this._expireParticle.bind(this)), + mergeMap(async (item) => { + // If peer was stopped, do not proceed further + if (!this.isInitialized) { + return; + } + + // Do not continue if there was an error in particle interpretation + if (item.result instanceof Error) { + log_particle.error( + "id %s. interpreter failed: %s", + item.particle.id, + item.result.message, + ); + + item.onStageChange({ + stage: "interpreterError", + errorMessage: item.result.message, + }); + + return; + } + + if (item.result.retCode !== 0) { + log_particle.error( + "id %s. interpreter failed: retCode: %d, message: %s", + item.particle.id, + item.result.retCode, + item.result.errorMessage, + ); + + log_particle.trace( + "id %s. avm data: %s", + item.particle.id, + this.decodeAvmData(item.result.data), + ); + + item.onStageChange({ + stage: "interpreterError", + errorMessage: item.result.errorMessage, + }); + + return; + } + + log_particle.trace( + "id %s. interpreter result: retCode: %d, avm data: %s", + item.particle.id, + item.result.retCode, + this.decodeAvmData(item.result.data), + ); + + setTimeout(() => { + item.onStageChange({ stage: "interpreted" }); + }, 0); + + let connectionPromise: Promise = Promise.resolve(); + + // send particle further if requested + if (item.result.nextPeerPks.length > 0) { + const newParticle = cloneWithNewData( + item.particle, + Buffer.from(item.result.data), + ); + + log_particle.debug( + "id %s. sending particle into network. Next peer ids: %s", + newParticle.id, + item.result.nextPeerPks.toString(), + ); + + connectionPromise = this.connection + .sendParticle(item.result.nextPeerPks, newParticle) + .then(() => { + log_particle.trace( + "id %s. send successful", + newParticle.id, + ); + + item.onStageChange({ stage: "sent" }); + }) + .catch((e: unknown) => { + log_particle.error( + "id %s. send failed %j", + newParticle.id, + e, + ); + + item.onStageChange({ + stage: "sendingError", + errorMessage: getErrorMessage(e), + }); + }); + } + + // execute call requests if needed + // and put particle with the results back to queue + if (item.result.callRequests.length > 0) { + for (const [key, cr] of item.result.callRequests) { + const req = { + fnName: cr.functionName, + args: cr.arguments, + serviceId: cr.serviceId, + tetraplets: cr.tetraplets, + particleContext: getParticleContext(item.particle), + }; + + void this._execSingleCallRequest(req) + .catch((err): CallServiceResult => { + if (err instanceof ServiceError) { + return { + retCode: ResultCodes.error, + result: err.message, + }; + } + + return { + retCode: ResultCodes.error, + result: `Service call failed. fnName="${ + req.fnName + }" serviceId="${ + req.serviceId + }" error: ${getErrorMessage(err)}`, + }; + }) + .then((res) => { + const serviceResult = { + result: jsonify(res.result), + retCode: res.retCode, + }; + + const newParticle = cloneWithNewData( + item.particle, + Buffer.from([]), + ); + + this._incomingParticles.next({ + ...item, + particle: newParticle, + callResults: [[key, serviceResult]], + }); + }); + } + } else { + item.onStageChange({ stage: "localWorkDone" }); + } + + return connectionPromise; + }), + ); + }), + ), + { defaultValue: undefined }, + ); + } + + private _expireParticle(item: ParticleQueueItem) { + const particleId = item.particle.id; + + log_particle.debug( + "id %s. particle has expired after %d. Deleting particle-related queues and handlers", + item.particle.id, + item.particle.ttl, + ); + + this.jsServiceHost.removeParticleScopeHandlers(particleId); + + item.onStageChange({ stage: "expired" }); + } + + private decodeAvmData(data: Uint8Array) { + return new TextDecoder().decode(data.buffer); + } + + private async _execSingleCallRequest( + req: CallServiceData, + ): Promise { + const particleId = req.particleContext.particleId; + + log_particle.trace( + "id %s. executing call service handler %j", + particleId, + req, + ); + + if (await this.marineHost.hasService(req.serviceId)) { + // TODO build correct CallParameters instead of default ones + const result = await this.marineHost.callService( + req.serviceId, + req.fnName, + req.args, + defaultCallParameters, + ); + + return { + retCode: ResultCodes.success, + result: result, + }; + } + + let res = await this.jsServiceHost.callService(req); + + if (res === null) { + res = { + retCode: ResultCodes.error, + result: `No service found for service call: serviceId='${ + req.serviceId + }', fnName='${req.fnName}' args='${jsonify(req.args)}'`, + }; + } + + log_particle.trace( + "id %s. executed call service handler, req: %j, res: %j ", + particleId, + req, + res, + ); + + return res; + } + + private _stopParticleProcessing() { + // do not hang if the peer has been stopped while some of the timeouts are still being executed + this._timeouts.forEach((timeout) => { + clearTimeout(timeout); + }); + } +} + +function filterExpiredParticles( + onParticleExpiration: (item: T) => void, +) { + return pipe( + tap((item: T) => { + if (hasExpired(item.particle)) { + onParticleExpiration(item); + } + }), + filter((x) => { + return !hasExpired(x.particle); + }), + ); } diff --git a/packages/core/js-client/src/jsPeer/__test__/avm.spec.ts b/packages/core/js-client/src/jsPeer/__test__/avm.spec.ts index 548fe4c4..565749d8 100644 --- a/packages/core/js-client/src/jsPeer/__test__/avm.spec.ts +++ b/packages/core/js-client/src/jsPeer/__test__/avm.spec.ts @@ -1,39 +1,59 @@ -import { it, describe, expect } from 'vitest'; -import { registerHandlersHelper, withPeer } from '../../util/testUtils.js'; -import { handleTimeout } from '../../particle/Particle.js'; +/** + * Copyright 2023 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. + */ -describe('Basic AVM functionality in Fluence Peer tests', () => { - it('Simple call', async () => { - await withPeer(async (peer) => { - const script = ` +import { JSONValue } from "@fluencelabs/interfaces"; +import { it, describe, expect } from "vitest"; + +import { handleTimeout } from "../../particle/Particle.js"; +import { registerHandlersHelper, withPeer } from "../../util/testUtils.js"; + +describe("Basic AVM functionality in Fluence Peer tests", () => { + it("Simple call", async () => { + await withPeer(async (peer) => { + const script = ` (call %init_peer_id% ("print" "print") ["1"]) `; - const particle = await peer.internals.createNewParticle(script); - - const res = await new Promise((resolve, reject) => { - if (particle instanceof Error) { - return reject(particle.message); - } - registerHandlersHelper(peer, particle, { - print: { - print: (args: Array) => { - const [res] = args; - resolve(res); - }, - }, - }); + const particle = await peer.internals.createNewParticle(script); - peer.internals.initiateParticle(particle, handleTimeout(reject)); - }); + const res = await new Promise((resolve, reject) => { + if (particle instanceof Error) { + reject(particle.message); + return; + } - expect(res).toBe('1'); + registerHandlersHelper(peer, particle, { + print: { + print: (args): undefined => { + const [res] = args; + resolve(res); + }, + }, }); - }); - it('Par call', async () => { - await withPeer(async (peer) => { - const script = ` + peer.internals.initiateParticle(particle, handleTimeout(reject)); + }); + + expect(res).toBe("1"); + }); + }); + + it("Par call", async () => { + await withPeer(async (peer) => { + const script = ` (seq (par (call %init_peer_id% ("print" "print") ["1"]) @@ -42,36 +62,39 @@ describe('Basic AVM functionality in Fluence Peer tests', () => { (call %init_peer_id% ("print" "print") ["2"]) ) `; - const particle = await peer.internals.createNewParticle(script); - - const res = await new Promise((resolve, reject) => { - const res: any[] = []; - if (particle instanceof Error) { - return reject(particle.message); - } + const particle = await peer.internals.createNewParticle(script); - registerHandlersHelper(peer, particle, { - print: { - print: (args: any) => { - res.push(args[0]); - if (res.length == 2) { - resolve(res); - } - }, - }, - }); + const res = await new Promise((resolve, reject) => { + const res: JSONValue[] = []; - peer.internals.initiateParticle(particle, handleTimeout(reject)); - }); + if (particle instanceof Error) { + reject(particle.message); + return; + } - expect(res).toStrictEqual(['1', '2']); + registerHandlersHelper(peer, particle, { + print: { + print: (args): undefined => { + res.push(args[0]); + + if (res.length === 2) { + resolve(res); + } + }, + }, }); - }); - it('Timeout in par call: race', async () => { - await withPeer(async (peer) => { - const script = ` + peer.internals.initiateParticle(particle, handleTimeout(reject)); + }); + + expect(res).toStrictEqual(["1", "2"]); + }); + }); + + it("Timeout in par call: race", async () => { + await withPeer(async (peer) => { + const script = ` (seq (call %init_peer_id% ("op" "identity") ["slow_result"] arg) (seq @@ -86,31 +109,33 @@ describe('Basic AVM functionality in Fluence Peer tests', () => { ) ) `; - const particle = await peer.internals.createNewParticle(script); - - const res = await new Promise((resolve, reject) => { - if (particle instanceof Error) { - return reject(particle.message); - } - registerHandlersHelper(peer, particle, { - return: { - return: (args: any) => { - resolve(args[0]); - }, - }, - }); + const particle = await peer.internals.createNewParticle(script); - peer.internals.initiateParticle(particle, handleTimeout(reject)); - }); + const res = await new Promise((resolve, reject) => { + if (particle instanceof Error) { + reject(particle.message); + return; + } - expect(res).toBe('fast_result'); + registerHandlersHelper(peer, particle, { + return: { + return: (args): undefined => { + resolve(args[0]); + }, + }, }); - }); - it('Timeout in par call: wait', async () => { - await withPeer(async (peer) => { - const script = ` + peer.internals.initiateParticle(particle, handleTimeout(reject)); + }); + + expect(res).toBe("fast_result"); + }); + }); + + it("Timeout in par call: wait", async () => { + await withPeer(async (peer) => { + const script = ` (seq (call %init_peer_id% ("op" "identity") ["timeout_msg"] arg) (seq @@ -136,25 +161,27 @@ describe('Basic AVM functionality in Fluence Peer tests', () => { ) ) `; - const particle = await peer.internals.createNewParticle(script); - - const res = await new Promise((resolve, reject) => { - if (particle instanceof Error) { - return reject(particle.message); - } - registerHandlersHelper(peer, particle, { - return: { - return: (args: any) => { - resolve(args[0]); - }, - }, - }); + const particle = await peer.internals.createNewParticle(script); - peer.internals.initiateParticle(particle, handleTimeout(reject)); - }); + const res = await new Promise((resolve, reject) => { + if (particle instanceof Error) { + reject(particle.message); + return; + } - expect(res).toBe('failed_with_timeout'); + registerHandlersHelper(peer, particle, { + return: { + return: (args): undefined => { + resolve(args[0]); + }, + }, }); + + peer.internals.initiateParticle(particle, handleTimeout(reject)); + }); + + expect(res).toBe("failed_with_timeout"); }); + }); }); diff --git a/packages/core/js-client/src/jsPeer/__test__/par.spec.ts b/packages/core/js-client/src/jsPeer/__test__/par.spec.ts index 88182c5a..0a4da727 100644 --- a/packages/core/js-client/src/jsPeer/__test__/par.spec.ts +++ b/packages/core/js-client/src/jsPeer/__test__/par.spec.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,15 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { describe, expect, it } from 'vitest'; -import { registerHandlersHelper, withPeer } from '../../util/testUtils.js'; -import { handleTimeout } from '../../particle/Particle.js'; -import { CallServiceData, ResultCodes } from '../../jsServiceHost/interfaces.js'; -describe('FluencePeer flow tests', () => { - it('should execute par instruction in parallel', async function () { - await withPeer(async (peer) => { - const script = ` +import assert from "assert"; + +import { JSONValue } from "@fluencelabs/interfaces"; +import { describe, expect, it } from "vitest"; + +import { + CallServiceData, + ResultCodes, +} from "../../jsServiceHost/interfaces.js"; +import { handleTimeout } from "../../particle/Particle.js"; +import { registerHandlersHelper, withPeer } from "../../util/testUtils.js"; + +describe("FluencePeer flow tests", () => { + it("should execute par instruction in parallel", async function () { + await withPeer(async (peer) => { + const script = ` (par (seq (call %init_peer_id% ("flow" "timeout") [1000 "test1"] res1) @@ -34,52 +42,62 @@ describe('FluencePeer flow tests', () => { ) `; - const particle = await peer.internals.createNewParticle(script); - - const res = await new Promise((resolve, reject) => { - peer.internals.regHandler.forParticle(particle.id, 'flow', 'timeout', (req: CallServiceData) => { - const [timeout, message] = req.args; - - return new Promise((resolve) => { - setTimeout(() => { - const res = { - result: message, - retCode: ResultCodes.success, - }; - resolve(res); - }, timeout); - }); - }); + const particle = await peer.internals.createNewParticle(script); - if (particle instanceof Error) { - return reject(particle.message); - } - - const values: any[] = []; + const res = await new Promise((resolve, reject) => { + peer.internals.regHandler.forParticle( + particle.id, + "flow", + "timeout", + (req: CallServiceData) => { + const [timeout, message] = req.args; + assert(typeof timeout === "number"); - registerHandlersHelper(peer, particle, { - callback: { - callback1: (args: any) => { - const [val] = args; - values.push(val); - if (values.length === 2) { - resolve(values); - } - }, - callback2: (args: any) => { - const [val] = args; - values.push(val); - if (values.length === 2) { - resolve(values); - } - }, - }, - }); + return new Promise((resolve) => { + setTimeout(() => { + const res = { + result: message, + retCode: ResultCodes.success, + }; - peer.internals.initiateParticle(particle, handleTimeout(reject)); + resolve(res); + }, timeout); }); + }, + ); - await expect(res).toEqual(expect.arrayContaining(["test1", "test1"])); + if (particle instanceof Error) { + reject(particle.message); + return; + } + + const values: JSONValue[] = []; + + registerHandlersHelper(peer, particle, { + callback: { + callback1: (args): undefined => { + const [val] = args; + values.push(val); + + if (values.length === 2) { + resolve(values); + } + }, + callback2: (args): undefined => { + const [val] = args; + values.push(val); + + if (values.length === 2) { + resolve(values); + } + }, + }, }); - }, 1500); -}); \ No newline at end of file + + peer.internals.initiateParticle(particle, handleTimeout(reject)); + }); + + expect(res).toEqual(expect.arrayContaining(["test1", "test1"])); + }); + }, 1500); +}); diff --git a/packages/core/js-client/src/jsPeer/__test__/parseAst.spec.ts b/packages/core/js-client/src/jsPeer/__test__/parseAst.spec.ts index 7c66043e..2249bb11 100644 --- a/packages/core/js-client/src/jsPeer/__test__/parseAst.spec.ts +++ b/packages/core/js-client/src/jsPeer/__test__/parseAst.spec.ts @@ -1,29 +1,46 @@ -import { it, describe, expect } from 'vitest'; +/** + * Copyright 2023 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 { withPeer } from '../../util/testUtils.js'; +import { it, describe, expect } from "vitest"; -describe('Parse ast tests', () => { - it('Correct ast should be parsed correctly', async () => { - withPeer(async (peer) => { - const air = `(null)`; - const res = await peer.internals.parseAst(air); +import { withPeer } from "../../util/testUtils.js"; - expect(res).toStrictEqual({ - success: true, - data: { Null: null }, - }); - }); +describe("Parse ast tests", () => { + it("Correct ast should be parsed correctly", async () => { + await withPeer(async (peer) => { + const air = `(null)`; + const res = await peer.internals.parseAst(air); + + expect(res).toStrictEqual({ + success: true, + data: { Null: null }, + }); }); + }); - it('Incorrect ast should result in corresponding error', async () => { - withPeer(async (peer) => { - const air = `(null`; - const res = await peer.internals.parseAst(air); + it("Incorrect ast should result in corresponding error", async () => { + await withPeer(async (peer) => { + const air = `(null`; + const res = await peer.internals.parseAst(air); - expect(res).toStrictEqual({ - success: false, - data: expect.stringContaining('error'), - }); - }); + expect(res).toStrictEqual({ + success: false, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + data: expect.stringContaining("error"), + }); }); + }); }); diff --git a/packages/core/js-client/src/jsPeer/__test__/peer.spec.ts b/packages/core/js-client/src/jsPeer/__test__/peer.spec.ts index 09b88067..f71db1be 100644 --- a/packages/core/js-client/src/jsPeer/__test__/peer.spec.ts +++ b/packages/core/js-client/src/jsPeer/__test__/peer.spec.ts @@ -1,78 +1,101 @@ -import { it, describe, expect } from 'vitest'; +/** + * Copyright 2023 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 { isFluencePeer } from '../../api.js'; -import { mkTestPeer, registerHandlersHelper, withPeer } from '../../util/testUtils.js'; -import { handleTimeout } from '../../particle/Particle.js'; -import { FluencePeer } from '../FluencePeer.js'; +import { it, describe, expect } from "vitest"; -describe('FluencePeer usage test suite', () => { - it('should perform test for FluencePeer class correctly', async () => { - // arrange - const peer = await mkTestPeer(); - const number = 1; - const object = { str: 'Hello!' }; - const undefinedVal = undefined; +import { isFluencePeer } from "../../api.js"; +import { handleTimeout } from "../../particle/Particle.js"; +import { + mkTestPeer, + registerHandlersHelper, + withPeer, +} from "../../util/testUtils.js"; +import { FluencePeer } from "../FluencePeer.js"; - // act - const isPeerPeer = isFluencePeer(peer); - const isNumberPeer = isFluencePeer(number); - const isObjectPeer = isFluencePeer(object); - const isUndefinedPeer = isFluencePeer(undefinedVal); +describe("FluencePeer usage test suite", () => { + it("should perform test for FluencePeer class correctly", async () => { + // arrange + const peer = await mkTestPeer(); + const number = 1; + const object = { str: "Hello!" }; + const undefinedVal = undefined; - // act - expect(isPeerPeer).toBe(true); - expect(isNumberPeer).toBe(false); - expect(isObjectPeer).toBe(false); - expect(isUndefinedPeer).toBe(false); - }); + // act + const isPeerPeer = isFluencePeer(peer); + const isNumberPeer = isFluencePeer(number); + const isObjectPeer = isFluencePeer(object); + const isUndefinedPeer = isFluencePeer(undefinedVal); - it('Should successfully call identity on local peer', async function () { - await withPeer(async (peer) => { - const script = ` + // act + expect(isPeerPeer).toBe(true); + expect(isNumberPeer).toBe(false); + expect(isObjectPeer).toBe(false); + expect(isUndefinedPeer).toBe(false); + }); + + it("Should successfully call identity on local peer", async function () { + await withPeer(async (peer) => { + const script = ` (seq (call %init_peer_id% ("op" "identity") ["test"] res) (call %init_peer_id% ("callback" "callback") [res]) ) `; - const particle = await peer.internals.createNewParticle(script); - - const res = await new Promise((resolve, reject) => { - if (particle instanceof Error) { - return reject(particle.message); - } - registerHandlersHelper(peer, particle, { - callback: { - callback: async (args: any) => { - const [res] = args; - resolve(res); - }, - }, - }); + const particle = await peer.internals.createNewParticle(script); - peer.internals.initiateParticle(particle, handleTimeout(reject)); - }); + const res = await new Promise((resolve, reject) => { + if (particle instanceof Error) { + reject(particle.message); + return; + } - expect(res).toBe('test'); + registerHandlersHelper(peer, particle, { + callback: { + callback: (args): undefined => { + const [res] = args; + resolve(res); + }, + }, }); + + peer.internals.initiateParticle(particle, handleTimeout(reject)); + }); + + expect(res).toBe("test"); }); + }); - it('Should throw correct message when calling non existing local service', async function () { - await withPeer(async (peer) => { - const res = callIncorrectService(peer); + it("Should throw correct message when calling non existing local service", async function () { + await withPeer(async (peer) => { + const res = callIncorrectService(peer); - await expect(res).rejects.toMatchObject({ - message: expect.stringContaining( - `"No service found for service call: serviceId='incorrect', fnName='incorrect' args='[]'"`, - ), - instruction: 'call %init_peer_id% ("incorrect" "incorrect") [] res', - }); - }); + await expect(res).rejects.toMatchObject({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + message: expect.stringContaining( + `"No service found for service call: serviceId='incorrect', fnName='incorrect' args='[]'"`, + ), + instruction: 'call %init_peer_id% ("incorrect" "incorrect") [] res', + }); }); + }); - it('Should not crash if undefined is passed as a variable', async () => { - await withPeer(async (peer) => { - const script = ` + it("Should not crash if undefined is passed as a variable", async () => { + await withPeer(async (peer) => { + const script = ` (seq (call %init_peer_id% ("load" "arg") [] arg) (seq @@ -80,99 +103,108 @@ describe('FluencePeer usage test suite', () => { (call %init_peer_id% ("callback" "callback") [res]) ) )`; - const particle = await peer.internals.createNewParticle(script); - - const res = await new Promise((resolve, reject) => { - if (particle instanceof Error) { - return reject(particle.message); - } - registerHandlersHelper(peer, particle, { - load: { - arg: () => undefined, - }, - callback: { - callback: (args: any) => { - const [val] = args; - resolve(val); - }, - error: (args: any) => { - const [error] = args; - reject(error); - }, - }, - }); + const particle = await peer.internals.createNewParticle(script); - peer.internals.initiateParticle(particle, handleTimeout(reject)); - }); + const res = await new Promise((resolve, reject) => { + if (particle instanceof Error) { + reject(particle.message); + return; + } - expect(res).toBe(null); + registerHandlersHelper(peer, particle, { + load: { + arg: () => { + return undefined; + }, + }, + callback: { + callback: (args): undefined => { + const [val] = args; + resolve(val); + }, + error: (args): undefined => { + const [error] = args; + reject(error); + }, + }, }); - }); - it('Should not crash if an error ocurred in user-defined handler', async () => { - await withPeer(async (peer) => { - const script = ` + peer.internals.initiateParticle(particle, handleTimeout(reject)); + }); + + expect(res).toBe(null); + }); + }); + + it("Should not crash if an error ocurred in user-defined handler", async () => { + await withPeer(async (peer) => { + const script = ` (xor (call %init_peer_id% ("load" "arg") [] arg) (call %init_peer_id% ("callback" "error") [%last_error%]) )`; - const particle = await peer.internals.createNewParticle(script); - - const promise = new Promise((_resolve, reject) => { - if (particle instanceof Error) { - return reject(particle.message); - } - registerHandlersHelper(peer, particle, { - load: { - arg: () => { - throw new Error('my super custom error message'); - }, - }, - callback: { - error: (args: any) => { - const [error] = args; - reject(error); - }, - }, - }); + const particle = await peer.internals.createNewParticle(script); - peer.internals.initiateParticle(particle, handleTimeout(reject)); - }); + const promise = new Promise((_resolve, reject) => { + if (particle instanceof Error) { + reject(particle.message); + return; + } - await expect(promise).rejects.toMatchObject({ - message: expect.stringContaining('my super custom error message'), - }); + registerHandlersHelper(peer, particle, { + load: { + arg: () => { + throw new Error("my super custom error message"); + }, + }, + callback: { + error: (args): undefined => { + const [error] = args; + reject(error); + }, + }, }); + + peer.internals.initiateParticle(particle, handleTimeout(reject)); + }); + + await expect(promise).rejects.toMatchObject({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + message: expect.stringContaining("my super custom error message"), + }); }); + }); }); -async function callIncorrectService(peer: FluencePeer): Promise { - const script = ` +async function callIncorrectService(peer: FluencePeer) { + const script = ` (xor (call %init_peer_id% ("incorrect" "incorrect") [] res) (call %init_peer_id% ("callback" "error") [%last_error%]) )`; - const particle = await peer.internals.createNewParticle(script); - - return new Promise((resolve, reject) => { - if (particle instanceof Error) { - return reject(particle.message); - } - registerHandlersHelper(peer, particle, { - callback: { - callback: (args: any) => { - resolve(args); - }, - error: (args: any) => { - const [error] = args; - reject(error); - }, - }, - }); + const particle = await peer.internals.createNewParticle(script); - peer.internals.initiateParticle(particle, handleTimeout(reject)); + return new Promise((resolve, reject) => { + if (particle instanceof Error) { + reject(particle.message); + return; + } + + registerHandlersHelper(peer, particle, { + callback: { + callback: (args): undefined => { + resolve(args); + }, + error: (args): undefined => { + const [error] = args; + reject(error); + }, + }, }); + + peer.internals.initiateParticle(particle, handleTimeout(reject)); + }); } diff --git a/packages/core/js-client/src/jsServiceHost/JsServiceHost.ts b/packages/core/js-client/src/jsServiceHost/JsServiceHost.ts index fd41de18..96127865 100644 --- a/packages/core/js-client/src/jsServiceHost/JsServiceHost.ts +++ b/packages/core/js-client/src/jsServiceHost/JsServiceHost.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,99 +13,114 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { CallServiceData, CallServiceResult, GenericCallServiceHandler, IJsServiceHost } from './interfaces.js'; + +import { + CallServiceData, + CallServiceResult, + GenericCallServiceHandler, + IJsServiceHost, +} from "./interfaces.js"; export class JsServiceHost implements IJsServiceHost { - private particleScopeHandlers = new Map>(); - private commonHandlers = new Map(); + private particleScopeHandlers = new Map< + string, + Map + >(); + private commonHandlers = new Map(); - /** - * Returns true if any handler for the specified serviceId is registered - */ - hasService(serviceId: string): boolean { - return this.commonHandlers.has(serviceId) || this.particleScopeHandlers.has(serviceId); + /** + * Returns true if any handler for the specified serviceId is registered + */ + hasService(serviceId: string): boolean { + return ( + this.commonHandlers.has(serviceId) || + this.particleScopeHandlers.has(serviceId) + ); + } + + /** + * Removes all handlers associated with the specified particle scope + * @param particleId Particle ID to remove handlers for + */ + removeParticleScopeHandlers(particleId: string): void { + this.particleScopeHandlers.delete(particleId); + } + + /** + * Find call service handler for specified particle + * @param serviceId Service ID as specified in `call` air instruction + * @param fnName Function name as specified in `call` air instruction + * @param particleId Particle ID + */ + getHandler( + serviceId: string, + fnName: string, + particleId: string, + ): GenericCallServiceHandler | null { + const key = serviceFnKey(serviceId, fnName); + return ( + this.particleScopeHandlers.get(particleId)?.get(key) ?? + this.commonHandlers.get(key) ?? + null + ); + } + + /** + * Execute service call for specified call service data. Return null if no handler was found + */ + async callService(req: CallServiceData): Promise { + const handler = this.getHandler( + req.serviceId, + req.fnName, + req.particleContext.particleId, + ); + + if (handler === null) { + return null; } - /** - * Removes all handlers associated with the specified particle scope - * @param particleId Particle ID to remove handlers for - */ - removeParticleScopeHandlers(particleId: string): void { - this.particleScopeHandlers.delete(particleId); + const result = await handler(req); + + // Otherwise AVM might break + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (result.result === undefined) { + result.result = null; } - /** - * Find call service handler for specified particle - * @param serviceId Service ID as specified in `call` air instruction - * @param fnName Function name as specified in `call` air instruction - * @param particleId Particle ID - */ - getHandler(serviceId: string, fnName: string, particleId: string): GenericCallServiceHandler | null { - const key = serviceFnKey(serviceId, fnName); - const psh = this.particleScopeHandlers.get(particleId); - let handler: GenericCallServiceHandler | undefined = undefined; + return result; + } - // we should prioritize handler for this particle if there is one - // if particle-scoped handler exist for this particle try getting handler there - if (psh !== undefined) { - handler = psh.get(key); - } + /** + * Register handler for all particles + */ + registerGlobalHandler( + serviceId: string, + fnName: string, + handler: GenericCallServiceHandler, + ): void { + this.commonHandlers.set(serviceFnKey(serviceId, fnName), handler); + } - // then try to find a common handler for all particles with this service-fn key - // if there is no particle-specific handler, get one from common map - if (handler === undefined) { - handler = this.commonHandlers.get(key); - } + /** + * Register handler which will be called only for particle with the specific id + */ + registerParticleScopeHandler( + particleId: string, + serviceId: string, + fnName: string, + handler: GenericCallServiceHandler, + ): void { + let psh = this.particleScopeHandlers.get(particleId); - return handler || null; + if (psh === undefined) { + psh = new Map(); + this.particleScopeHandlers.set(particleId, psh); } - /** - * Execute service call for specified call service data. Return null if no handler was found - */ - async callService(req: CallServiceData): Promise { - const handler = this.getHandler(req.serviceId, req.fnName, req.particleContext.particleId); - - if (handler === null) { - return null; - } - - const result = await handler(req); - - // Otherwise AVM might break - if (result.result === undefined) { - result.result = null; - } - - return result; - } - - /** - * Register handler for all particles - */ - registerGlobalHandler(serviceId: string, fnName: string, handler: GenericCallServiceHandler): void { - this.commonHandlers.set(serviceFnKey(serviceId, fnName), handler); - } - - /** - * Register handler which will be called only for particle with the specific id - */ - registerParticleScopeHandler( - particleId: string, - serviceId: string, - fnName: string, - handler: GenericCallServiceHandler, - ): void { - let psh = this.particleScopeHandlers.get(particleId); - if (psh === undefined) { - psh = new Map(); - this.particleScopeHandlers.set(particleId, psh); - } - - psh.set(serviceFnKey(serviceId, fnName), handler); - } + psh.set(serviceFnKey(serviceId, fnName), handler); + } } function serviceFnKey(serviceId: string, fnName: string) { - return `${serviceId}/${fnName}`; + return `${serviceId}/${fnName}`; } diff --git a/packages/core/js-client/src/jsServiceHost/interfaces.ts b/packages/core/js-client/src/jsServiceHost/interfaces.ts index 09a879e6..be6c3818 100644 --- a/packages/core/js-client/src/jsServiceHost/interfaces.ts +++ b/packages/core/js-client/src/jsServiceHost/interfaces.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,118 +13,127 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import type { PeerIdB58 } from '@fluencelabs/interfaces'; -import type { SecurityTetraplet } from '@fluencelabs/avm'; -import { JSONValue } from '../util/commonTypes.js'; + +import type { SecurityTetraplet } from "@fluencelabs/avm"; +import type { PeerIdB58 } from "@fluencelabs/interfaces"; +import { JSONArray, JSONValue } from "@fluencelabs/interfaces"; /** - * JS Service host a low level interface for managing pure javascript services. + * JS Service host a low level interface for managing pure javascript services. * It operates on a notion of Call Service Handlers - functions which are called when a `call` air instruction is executed on the local peer. */ export interface IJsServiceHost { - /** - * Returns true if any handler for the specified serviceId is registered - */ - hasService(serviceId: string): boolean; + /** + * Returns true if any handler for the specified serviceId is registered + */ + hasService(serviceId: string): boolean; - /** - * Find call service handler for specified particle - * @param serviceId Service ID as specified in `call` air instruction - * @param fnName Function name as specified in `call` air instruction - * @param particleId Particle ID - */ - getHandler(serviceId: string, fnName: string, particleId: string): GenericCallServiceHandler | null; + /** + * Find call service handler for specified particle + * @param serviceId Service ID as specified in `call` air instruction + * @param fnName Function name as specified in `call` air instruction + * @param particleId Particle ID + */ + getHandler( + serviceId: string, + fnName: string, + particleId: string, + ): GenericCallServiceHandler | null; - /** - * Execute service call for specified call service data - */ - callService(req: CallServiceData): Promise; + /** + * Execute service call for specified call service data + */ + callService(req: CallServiceData): Promise; - /** - * Register handler for all particles - */ - registerGlobalHandler(serviceId: string, fnName: string, handler: GenericCallServiceHandler): void; + /** + * Register handler for all particles + */ + registerGlobalHandler( + serviceId: string, + fnName: string, + handler: GenericCallServiceHandler, + ): void; - /** - * Register handler which will be called only for particle with the specific id - */ - registerParticleScopeHandler( - particleId: string, - serviceId: string, - fnName: string, - handler: GenericCallServiceHandler, - ): void; + /** + * Register handler which will be called only for particle with the specific id + */ + registerParticleScopeHandler( + particleId: string, + serviceId: string, + fnName: string, + handler: GenericCallServiceHandler, + ): void; - /** - * Removes all handlers associated with the specified particle scope - * @param particleId Particle ID to remove handlers for - */ - removeParticleScopeHandlers(particleId: string): void; + /** + * Removes all handlers associated with the specified particle scope + * @param particleId Particle ID to remove handlers for + */ + removeParticleScopeHandlers(particleId: string): void; } export enum ResultCodes { - success = 0, - error = 1, + success = 0, + error = 1, } /** * Particle context. Contains additional information about particle which triggered `call` air instruction from AVM */ export interface ParticleContext { - /** - * The identifier of particle which triggered the call - */ - particleId: string; + /** + * The identifier of particle which triggered the call + */ + particleId: string; - /** - * The peer id which created the particle - */ - initPeerId: PeerIdB58; + /** + * The peer id which created the particle + */ + initPeerId: PeerIdB58; - /** - * Particle's timestamp when it was created - */ - timestamp: number; + /** + * Particle's timestamp when it was created + */ + timestamp: number; - /** - * Time to live in milliseconds. The time after the particle should be expired - */ - ttl: number; + /** + * Time to live in milliseconds. The time after the particle should be expired + */ + ttl: number; - /** - * Particle's signature - */ - signature: Uint8Array; + /** + * Particle's signature + */ + signature: Uint8Array; } /** * Represents the information passed from AVM when a `call` air instruction is executed on the local peer */ export interface CallServiceData { - /** - * Service ID as specified in `call` air instruction - */ - serviceId: string; + /** + * Service ID as specified in `call` air instruction + */ + serviceId: string; - /** - * Function name as specified in `call` air instruction - */ - fnName: string; + /** + * Function name as specified in `call` air instruction + */ + fnName: string; - /** - * Arguments as specified in `call` air instruction - */ - args: any[]; + /** + * Arguments as specified in `call` air instruction + */ + args: JSONArray; - /** - * Security Tetraplets received from AVM - */ - tetraplets: SecurityTetraplet[][]; + /** + * Security Tetraplets received from AVM + */ + tetraplets: SecurityTetraplet[][]; - /** - * Particle context, @see {@link ParticleContext} - */ - particleContext: ParticleContext; + /** + * Particle context, @see {@link ParticleContext} + */ + particleContext: ParticleContext; } /** @@ -135,19 +144,21 @@ export type CallServiceResultType = JSONValue; /** * Generic call service handler */ -export type GenericCallServiceHandler = (req: CallServiceData) => CallServiceResult | Promise; +export type GenericCallServiceHandler = ( + req: CallServiceData, +) => CallServiceResult | Promise; /** * Represents the result of the `call` air instruction to be returned into AVM */ export interface CallServiceResult { - /** - * Return code to be returned to AVM - */ - retCode: ResultCodes; + /** + * Return code to be returned to AVM + */ + retCode: ResultCodes; - /** - * Result object to be returned to AVM - */ - result: CallServiceResultType; + /** + * Result object to be returned to AVM + */ + result: CallServiceResultType; } diff --git a/packages/core/js-client/src/jsServiceHost/serviceUtils.ts b/packages/core/js-client/src/jsServiceHost/serviceUtils.ts index 234fbf09..ac9090c3 100644 --- a/packages/core/js-client/src/jsServiceHost/serviceUtils.ts +++ b/packages/core/js-client/src/jsServiceHost/serviceUtils.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,48 +13,58 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { FluencePeer } from '../jsPeer/FluencePeer.js'; -import { IParticle } from '../particle/interfaces.js'; -import { builtInServices } from '../services/builtins.js'; + +import { JSONArray } from "@fluencelabs/interfaces"; + +import { FluencePeer } from "../jsPeer/FluencePeer.js"; +import { IParticle } from "../particle/interfaces.js"; +import { builtInServices } from "../services/builtins.js"; + import { - CallServiceData, - CallServiceResult, - CallServiceResultType, - ParticleContext, - ResultCodes, -} from './interfaces.js'; + CallServiceData, + CallServiceResult, + CallServiceResultType, + ParticleContext, + ResultCodes, +} from "./interfaces.js"; -export const doNothing = (..._args: Array) => undefined; +export const doNothing = () => { + return undefined; +}; -export const WrapFnIntoServiceCall = - (fn: (args: any[]) => CallServiceResultType) => - (req: CallServiceData): CallServiceResult => ({ - retCode: ResultCodes.success, - result: fn(req.args), - }); +export const WrapFnIntoServiceCall = ( + fn: (args: JSONArray) => CallServiceResultType | undefined, +) => { + return (req: CallServiceData): CallServiceResult => { + return { + retCode: ResultCodes.success, + result: fn(req.args) ?? null, + }; + }; +}; export class ServiceError extends Error { - constructor(message: string) { - super(message); + constructor(message: string) { + super(message); - Object.setPrototypeOf(this, ServiceError.prototype); - } + Object.setPrototypeOf(this, ServiceError.prototype); + } } export const getParticleContext = (particle: IParticle): ParticleContext => { - return { - particleId: particle.id, - initPeerId: particle.initPeerId, - timestamp: particle.timestamp, - ttl: particle.ttl, - signature: particle.signature, - }; + return { + particleId: particle.id, + initPeerId: particle.initPeerId, + timestamp: particle.timestamp, + ttl: particle.ttl, + signature: particle.signature, + }; }; export function registerDefaultServices(peer: FluencePeer) { - Object.entries(builtInServices).forEach(([serviceId, service]) => { - Object.entries(service).forEach(([fnName, fn]) => { - peer.internals.regHandler.common(serviceId, fnName, fn); - }); + Object.entries(builtInServices).forEach(([serviceId, service]) => { + Object.entries(service).forEach(([fnName, fn]) => { + peer.internals.regHandler.common(serviceId, fnName, fn); }); + }); } diff --git a/packages/core/js-client/src/keypair/__test__/KeyPair.spec.ts b/packages/core/js-client/src/keypair/__test__/KeyPair.spec.ts index bda57e39..d560fd23 100644 --- a/packages/core/js-client/src/keypair/__test__/KeyPair.spec.ts +++ b/packages/core/js-client/src/keypair/__test__/KeyPair.spec.ts @@ -15,11 +15,11 @@ */ import bs58 from "bs58"; -import { fromUint8Array, toUint8Array } from 'js-base64'; +import { fromUint8Array, toUint8Array } from "js-base64"; import { it, describe, expect } from "vitest"; -import { fromBase64Sk, KeyPair } from '../index.js'; -import { Particle, serializeToString, buildParticleMessage } from '../../particle/Particle.js'; +import { Particle, buildParticleMessage } from "../../particle/Particle.js"; +import { fromBase64Sk, KeyPair } from "../index.js"; const key = "+cmeYlZKj+MfSa9dpHV+BmLPm6wq4inGlsPlQ1GvtPk="; const keyBytes = toUint8Array(key); @@ -27,9 +27,10 @@ const keyBytes = toUint8Array(key); const testData = Uint8Array.from([1, 2, 3, 4, 5, 6, 7, 9, 10]); const testDataSig = Uint8Array.from([ - 224, 104, 245, 206, 140, 248, 27, 72, 68, 133, 111, 10, 164, 197, 242, 132, 107, 77, 224, 67, 99, 106, 76, 29, 144, - 121, 122, 169, 36, 173, 58, 80, 170, 102, 137, 253, 157, 247, 168, 87, 162, 223, 188, 214, 203, 220, 52, 246, 29, - 86, 77, 71, 224, 248, 16, 213, 254, 75, 78, 239, 243, 222, 241, 15, + 224, 104, 245, 206, 140, 248, 27, 72, 68, 133, 111, 10, 164, 197, 242, 132, + 107, 77, 224, 67, 99, 106, 76, 29, 144, 121, 122, 169, 36, 173, 58, 80, 170, + 102, 137, 253, 157, 247, 168, 87, 162, 223, 188, 214, 203, 220, 52, 246, 29, + 86, 77, 71, 224, 248, 16, 213, 254, 75, 78, 239, 243, 222, 241, 15, ]); // signature produced by KeyPair created from some random KeyPair @@ -112,24 +113,49 @@ describe("KeyPair tests", () => { }); it("validates particle signature checks", async function () { - const keyPair = await fromBase64Sk("7h48PQ/f1rS9TxacmgODxbD42Il9B3KC117jvOPppPE="); - expect(bs58.encode(keyPair.getLibp2pPeerId().toBytes())).toBe("12D3KooWANqfCDrV79MZdMnMqTvDdqSAPSxdgFY1L6DCq2DVGB4D"); + const keyPair = await fromBase64Sk( + "7h48PQ/f1rS9TxacmgODxbD42Il9B3KC117jvOPppPE=", + ); + + expect(bs58.encode(keyPair.getLibp2pPeerId().toBytes())).toBe( + "12D3KooWANqfCDrV79MZdMnMqTvDdqSAPSxdgFY1L6DCq2DVGB4D", + ); + const message = toUint8Array(btoa("message")); const signature = await keyPair.signBytes(message); const verified = await keyPair.verify(message, signature); expect(verified).toBe(true); - expect(fromUint8Array(signature)).toBe("sBW7H6/1fwAwF86ldwVm9BDu0YH3w30oFQjTWX0Tiu9yTVZHmxkV2OX4GL5jn0Iz0CrasGcOfozzkZwtJBPMBg=="); - const particle = await Particle.createNew("abc", keyPair.getPeerId(), 7000, keyPair, "2883f959-e9e7-4843-8c37-205d393ca372", 1696934545662); + expect(fromUint8Array(signature)).toBe( + "sBW7H6/1fwAwF86ldwVm9BDu0YH3w30oFQjTWX0Tiu9yTVZHmxkV2OX4GL5jn0Iz0CrasGcOfozzkZwtJBPMBg==", + ); + + const particle = await Particle.createNew( + "abc", + keyPair.getPeerId(), + 7000, + keyPair, + "2883f959-e9e7-4843-8c37-205d393ca372", + 1696934545662, + ); const particle_bytes = buildParticleMessage(particle); - expect(fromUint8Array(particle_bytes)).toBe("Mjg4M2Y5NTktZTllNy00ODQzLThjMzctMjA1ZDM5M2NhMzcy/kguGYsBAABYGwAAYWJj"); - const isParticleVerified = await KeyPair.verifyWithPublicKey(keyPair.getPublicKey(), particle_bytes, particle.signature); + expect(fromUint8Array(particle_bytes)).toBe( + "Mjg4M2Y5NTktZTllNy00ODQzLThjMzctMjA1ZDM5M2NhMzcy/kguGYsBAABYGwAAYWJj", + ); + + const isParticleVerified = await KeyPair.verifyWithPublicKey( + keyPair.getPublicKey(), + particle_bytes, + particle.signature, + ); expect(isParticleVerified).toBe(true); - expect(fromUint8Array(particle.signature)).toBe("KceXDnOfqe0dOnAxiDsyWBIvUq6WHoT0ge+VMHXOZsjZvCNH7/10oufdlYfcPomfv28On6E87ZhDcHGBZcb7Bw=="); + expect(fromUint8Array(particle.signature)).toBe( + "KceXDnOfqe0dOnAxiDsyWBIvUq6WHoT0ge+VMHXOZsjZvCNH7/10oufdlYfcPomfv28On6E87ZhDcHGBZcb7Bw==", + ); }); }); diff --git a/packages/core/js-client/src/marine/__test__/marine-js.spec.ts b/packages/core/js-client/src/marine/__test__/marine-js.spec.ts index 22ecc6e0..f5e3c3c8 100644 --- a/packages/core/js-client/src/marine/__test__/marine-js.spec.ts +++ b/packages/core/js-client/src/marine/__test__/marine-js.spec.ts @@ -1,31 +1,55 @@ -import { it, describe, expect, beforeAll } from 'vitest'; +/** + * Copyright 2023 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 * as fs from 'fs'; -import * as url from 'url'; -import * as path from 'path'; -import { compileAqua, withPeer } from '../../util/testUtils.js'; +import * as fs from "fs"; +import * as path from "path"; +import * as url from "url"; -let aqua: any; -const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); +import { it, describe, expect, beforeAll } from "vitest"; -describe('Marine js tests', () => { - beforeAll(async () => { - const pathToAquaFiles = path.join(__dirname, '../../../aqua_test/marine-js.aqua'); - const { services, functions } = await compileAqua(pathToAquaFiles); - aqua = functions; - }); - - it('should call marine service correctly', async () => { - await withPeer(async (peer) => { - // arrange - const wasm = await fs.promises.readFile(path.join(__dirname, '../../../data_for_test/greeting.wasm')); - await peer.registerMarineService(wasm, 'greeting'); - - // act - const res = await aqua.call(peer, { arg: 'test' }); - - // assert - expect(res).toBe('Hi, Hi, Hi, test'); - }); +import { compileAqua, CompiledFnCall, withPeer } from "../../util/testUtils.js"; + +let aqua: Record; +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); + +describe("Marine js tests", () => { + beforeAll(async () => { + const pathToAquaFiles = path.join( + __dirname, + "../../../aqua_test/marine-js.aqua", + ); + + const { functions } = await compileAqua(pathToAquaFiles); + aqua = functions; + }); + + it("should call marine service correctly", async () => { + await withPeer(async (peer) => { + // arrange + const wasm = await fs.promises.readFile( + path.join(__dirname, "../../../data_for_test/greeting.wasm"), + ); + + await peer.registerMarineService(wasm, "greeting"); + + // act + const res = await aqua["call"](peer, { arg: "test" }); + + // assert + expect(res).toBe("Hi, Hi, Hi, test"); }); + }); }); diff --git a/packages/core/js-client/src/marine/deps-loader/common.ts b/packages/core/js-client/src/marine/deps-loader/common.ts deleted file mode 100644 index df7ba986..00000000 --- a/packages/core/js-client/src/marine/deps-loader/common.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2023 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. - */ -// @ts-ignore -import { BlobWorker } from 'threads'; -import { fromBase64, toUint8Array } from 'js-base64'; -// @ts-ignore -import type { WorkerImplementation } from 'threads/dist/types/master'; -import { Buffer } from 'buffer'; -import { LazyLoader } from '../interfaces.js'; - -export class InlinedWorkerLoader extends LazyLoader { - constructor(b64script: string) { - super(() => { - const script = fromBase64(b64script); - return BlobWorker.fromText(script); - }); - } -} - -export class InlinedWasmLoader extends LazyLoader { - constructor(b64wasm: string) { - super(() => { - const wasm = toUint8Array(b64wasm); - return Buffer.from(wasm); - }); - } -} diff --git a/packages/core/js-client/src/marine/deps-loader/node.ts b/packages/core/js-client/src/marine/deps-loader/node.ts index 79f0a647..93b0877d 100644 --- a/packages/core/js-client/src/marine/deps-loader/node.ts +++ b/packages/core/js-client/src/marine/deps-loader/node.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,24 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createRequire } from 'module'; -// @ts-ignore -import type { WorkerImplementation } from 'threads/dist/types/master'; -// @ts-ignore -import { Worker } from 'threads'; -import { Buffer } from 'buffer'; -import * as fs from 'fs'; -import * as path from 'path'; -import { LazyLoader } from '../interfaces.js'; +import { Buffer } from "buffer"; +import fs from "fs"; +import { createRequire } from "module"; +import path from "path"; + +import { Worker, type Worker as WorkerImplementation } from "threads/master"; + +import { LazyLoader } from "../interfaces.js"; const require = createRequire(import.meta.url); const bufferToSharedArrayBuffer = (buffer: Buffer): SharedArrayBuffer => { - const sab = new SharedArrayBuffer(buffer.length); - const tmp = new Uint8Array(sab); - tmp.set(buffer, 0); - return sab; + const sab = new SharedArrayBuffer(buffer.length); + const tmp = new Uint8Array(sab); + tmp.set(buffer, 0); + return sab; }; /** @@ -39,10 +38,13 @@ const bufferToSharedArrayBuffer = (buffer: Buffer): SharedArrayBuffer => { * @param source - object specifying the source of the file. Consist two fields: package name and file path. * @returns SharedArrayBuffer with the wasm file */ -export const loadWasmFromNpmPackage = async (source: { package: string; file: string }): Promise => { - const packagePath = require.resolve(source.package); - const filePath = path.join(path.dirname(packagePath), source.file); - return loadWasmFromFileSystem(filePath); +export const loadWasmFromNpmPackage = async (source: { + package: string; + file: string; +}): Promise => { + const packagePath = require.resolve(source.package); + const filePath = path.join(path.dirname(packagePath), source.file); + return loadWasmFromFileSystem(filePath); }; /** @@ -51,35 +53,43 @@ export const loadWasmFromNpmPackage = async (source: { package: string; file: st * @param filePath - path to the wasm file * @returns SharedArrayBuffer with the wasm fileWorker */ -export const loadWasmFromFileSystem = async (filePath: string): Promise => { - const buffer = await fs.promises.readFile(filePath); - return bufferToSharedArrayBuffer(buffer); +export const loadWasmFromFileSystem = async ( + filePath: string, +): Promise => { + const buffer = await fs.promises.readFile(filePath); + return bufferToSharedArrayBuffer(buffer); }; export class WasmLoaderFromFs extends LazyLoader { - constructor(filePath: string) { - super(() => loadWasmFromFileSystem(filePath)); - } + constructor(filePath: string) { + super(() => { + return loadWasmFromFileSystem(filePath); + }); + } } export class WasmLoaderFromNpm extends LazyLoader { - constructor(pkg: string, file: string) { - super(() => loadWasmFromNpmPackage({ package: pkg, file: file })); - } + constructor(pkg: string, file: string) { + super(() => { + return loadWasmFromNpmPackage({ package: pkg, file: file }); + }); + } } export class WorkerLoaderFromFs extends LazyLoader { - constructor(scriptPath: string) { - super(() => new Worker(scriptPath)); - } + constructor(scriptPath: string) { + super(() => { + return new Worker(scriptPath); + }); + } } export class WorkerLoaderFromNpm extends LazyLoader { - constructor(pkg: string, file: string) { - super(() => { - const packagePath = require.resolve(pkg); - const scriptPath = path.join(path.dirname(packagePath), file); - return new Worker(scriptPath); - }); - } + constructor(pkg: string, file: string) { + super(() => { + const packagePath = require.resolve(pkg); + const scriptPath = path.join(path.dirname(packagePath), file); + return new Worker(scriptPath); + }); + } } diff --git a/packages/core/js-client/src/marine/deps-loader/web.ts b/packages/core/js-client/src/marine/deps-loader/web.ts deleted file mode 100644 index 3e4d91fa..00000000 --- a/packages/core/js-client/src/marine/deps-loader/web.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2023 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 { Buffer } from 'buffer'; -import { LazyLoader } from '../interfaces.js'; -// @ts-ignore -import type { WorkerImplementation } from 'threads/dist/types/master'; - -const bufferToSharedArrayBuffer = (buffer: Buffer): SharedArrayBuffer => { - const sab = new SharedArrayBuffer(buffer.length); - const tmp = new Uint8Array(sab); - tmp.set(buffer, 0); - return sab; -}; - -/** - * Load wasm file from the server. Only works in browsers. - * The function will try load file into SharedArrayBuffer if the site is cross-origin isolated. - * Otherwise the return value fallbacks to Buffer which is less performant but is still compatible with FluenceAppService methods. - * We strongly recommend to set-up cross-origin headers. For more details see: See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer#security_requirements - * Filename is relative to current origin. - * @param filePath - path to the wasm file relative to current origin - * @returns Either SharedArrayBuffer or Buffer with the wasm file - */ -export const loadWasmFromUrl = async (filePath: string): Promise => { - const fullUrl = window.location.origin + '/' + filePath; - const res = await fetch(fullUrl); - const ab = await res.arrayBuffer(); - new Uint8Array(ab); - const buffer = Buffer.from(ab); - - // only convert to shared buffers if necessary CORS headers have been set: - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer#security_requirements - if (crossOriginIsolated) { - return bufferToSharedArrayBuffer(buffer); - } - - return buffer; -}; - -export class WasmLoaderFromUrl extends LazyLoader { - constructor(filePath: string) { - super(() => loadWasmFromUrl(filePath)); - } -} - -export class WorkerLoaderFromUrl extends LazyLoader { - constructor(scriptPath: string) { - super(() => new Worker(scriptPath)); - } -} diff --git a/packages/core/js-client/src/marine/interfaces.ts b/packages/core/js-client/src/marine/interfaces.ts index 10858ae8..a1a2f046 100644 --- a/packages/core/js-client/src/marine/interfaces.ts +++ b/packages/core/js-client/src/marine/interfaces.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,97 +13,112 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { CallResultsArray, InterpreterResult, RunParameters } from '@fluencelabs/avm'; -import { IStartable, JSONArray, JSONObject, CallParameters } from '../util/commonTypes.js'; -// @ts-ignore -import type { WorkerImplementation } from 'threads/dist/types/master'; + +import { + CallResultsArray, + InterpreterResult, + RunParameters, +} from "@fluencelabs/avm"; +import { JSONObject, JSONValue, JSONArray } from "@fluencelabs/interfaces"; +import type { Worker as WorkerImplementation } from "threads/master"; + +import { IStartable, CallParameters } from "../util/commonTypes.js"; /** * Contract for marine host implementations. Marine host is responsible for creating calling and removing marine services */ export interface IMarineHost extends IStartable { - /** - * Creates marine service from the given module and service id - */ - createService(serviceModule: ArrayBuffer | SharedArrayBuffer, serviceId: string): Promise; + /** + * Creates marine service from the given module and service id + */ + createService( + serviceModule: ArrayBuffer | SharedArrayBuffer, + serviceId: string, + ): Promise; - /** - * Removes marine service with the given service id - */ - removeService(serviceId: string): Promise; + /** + * Removes marine service with the given service id + */ + removeService(serviceId: string): Promise; - /** - * Returns true if any service with the specified service id is registered - */ - hasService(serviceId: string): Promise; + /** + * Returns true if any service with the specified service id is registered + */ + hasService(serviceId: string): Promise; - /** - * Calls the specified function of the specified service with the given arguments - */ - callService( - serviceId: string, - functionName: string, - args: JSONArray | JSONObject, - callParams: CallParameters, - ): Promise; + /** + * Calls the specified function of the specified service with the given arguments + */ + callService( + serviceId: string, + functionName: string, + args: JSONArray | JSONObject, + callParams: CallParameters, + ): Promise; } /** * Interface for different implementations of AVM runner */ export interface IAvmRunner extends IStartable { - /** - * Run AVM interpreter with the specified parameters - */ - run( - runParams: RunParameters, - air: string, - prevData: Uint8Array, - data: Uint8Array, - callResults: CallResultsArray, - ): Promise; + /** + * Run AVM interpreter with the specified parameters + */ + run( + runParams: RunParameters, + air: string, + prevData: Uint8Array, + data: Uint8Array, + callResults: CallResultsArray, + ): Promise; } /** * Interface for something which can hold a value */ export interface IValueLoader { - getValue(): T; + getValue(): T; } /** * Interface for something which can load wasm files */ -export interface IWasmLoader extends IValueLoader, IStartable {} +export interface IWasmLoader + extends IValueLoader, + IStartable {} /** * Interface for something which can thread.js based worker */ -export interface IWorkerLoader extends IValueLoader, IStartable {} +export interface IWorkerLoader + extends IValueLoader>, + IStartable {} /** * Lazy loader for some value. Value is loaded only when `start` method is called */ export class LazyLoader implements IStartable, IValueLoader { - private value: T | null = null; + private value: T | null = null; - constructor(private loadValue: () => Promise | T) {} + constructor(private loadValue: () => Promise | T) {} - getValue(): T { - if (this.value == null) { - throw new Error('Value has not been loaded. Call `start` method to load the value.'); - } - - return this.value; + getValue(): T { + if (this.value == null) { + throw new Error( + "Value has not been loaded. Call `start` method to load the value.", + ); } - async start() { - if (this.value !== null) { - return; - } + return this.value; + } - this.value = await this.loadValue(); + async start() { + if (this.value !== null) { + return; } - async stop() {} + this.value = await this.loadValue(); + } + + async stop() {} } diff --git a/packages/core/js-client/src/marine/worker-script/workerLoader.ts b/packages/core/js-client/src/marine/worker-script/workerLoader.ts index 813f9939..66b656a7 100644 --- a/packages/core/js-client/src/marine/worker-script/workerLoader.ts +++ b/packages/core/js-client/src/marine/worker-script/workerLoader.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,14 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -// @ts-ignore -import type { WorkerImplementation } from 'threads/dist/types/master'; -// @ts-ignore -import { Worker } from 'threads'; -import { LazyLoader } from '../interfaces.js'; + +import { Worker, type Worker as WorkerImplementation } from "threads/master"; + +import { LazyLoader } from "../interfaces.js"; export class WorkerLoader extends LazyLoader { - constructor() { - super(() => new Worker('../../../node_modules/@fluencelabs/marine-worker/dist/index.js')); - } + constructor() { + super(() => { + return new Worker( + "../../../node_modules/@fluencelabs/marine-worker/dist/index.js", + ); + }); + } } diff --git a/packages/core/js-client/src/marine/worker/index.ts b/packages/core/js-client/src/marine/worker/index.ts index df627259..21086b8b 100644 --- a/packages/core/js-client/src/marine/worker/index.ts +++ b/packages/core/js-client/src/marine/worker/index.ts @@ -1,5 +1,5 @@ -/* - * Copyright 2022 Fluence Labs Limited +/** + * Copyright 2023 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. @@ -14,96 +14,117 @@ * limitations under the License. */ -import type { JSONArray, JSONObject, CallParameters } from '@fluencelabs/marine-js/dist/types'; -import { LogFunction, logLevelToEnv } from '@fluencelabs/marine-js/dist/types'; -import type { MarineBackgroundInterface } from '@fluencelabs/marine-worker'; -// @ts-ignore -import { ModuleThread, spawn, Thread } from 'threads'; +import { JSONValue } from "@fluencelabs/interfaces"; +import type { + JSONArray, + JSONObject, + CallParameters, +} from "@fluencelabs/marine-js/dist/types"; +import { LogFunction, logLevelToEnv } from "@fluencelabs/marine-js/dist/types"; +import type { MarineBackgroundInterface } from "@fluencelabs/marine-worker"; +import { ModuleThread, Thread, spawn } from "threads/master"; -import { MarineLogger, marineLogger } from '../../util/logger.js'; -import { IMarineHost, IWasmLoader, IWorkerLoader } from '../interfaces.js'; +import { MarineLogger, marineLogger } from "../../util/logger.js"; +import { IMarineHost, IWasmLoader, IWorkerLoader } from "../interfaces.js"; export class MarineBackgroundRunner implements IMarineHost { - private workerThread?: MarineBackgroundInterface; + private workerThread?: ModuleThread; - private loggers = new Map(); + private loggers = new Map(); - constructor(private workerLoader: IWorkerLoader, private controlModuleLoader: IWasmLoader, private avmWasmLoader: IWasmLoader) {} + constructor( + private workerLoader: IWorkerLoader, + private controlModuleLoader: IWasmLoader, + private avmWasmLoader: IWasmLoader, + ) {} - async hasService(serviceId: string) { - if (!this.workerThread) { - throw new Error('Worker is not initialized'); - } - - return this.workerThread.hasService(serviceId); + async hasService(serviceId: string) { + if (this.workerThread == null) { + throw new Error("Worker is not initialized"); } - async removeService(serviceId: string) { - if (!this.workerThread) { - throw new Error('Worker is not initialized'); - } - - await this.workerThread.removeService(serviceId); + return this.workerThread.hasService(serviceId); + } + + async removeService(serviceId: string) { + if (this.workerThread == null) { + throw new Error("Worker is not initialized"); } - async start(): Promise { - if (this.workerThread) { - throw new Error('Worker thread already initialized'); - } - - await this.controlModuleLoader.start(); - const wasm = this.controlModuleLoader.getValue(); - - await this.avmWasmLoader.start(); + await this.workerThread.removeService(serviceId); + } - await this.workerLoader.start(); - const worker = await this.workerLoader.getValue(); - - const workerThread = await spawn(worker); - const logfn: LogFunction = (message) => { - const serviceLogger = this.loggers.get(message.service); - if (!serviceLogger) { - return; - } - serviceLogger[message.level](message.message); - }; - workerThread.onLogMessage().subscribe(logfn); - await workerThread.init(wasm); - this.workerThread = workerThread; - await this.createService(this.avmWasmLoader.getValue(), 'avm'); + async start(): Promise { + if (this.workerThread != null) { + throw new Error("Worker thread already initialized"); } - async createService(serviceModule: ArrayBuffer | SharedArrayBuffer, serviceId: string): Promise { - if (!this.workerThread) { - throw new Error('Worker is not initialized'); - } + await this.controlModuleLoader.start(); + const wasm = this.controlModuleLoader.getValue(); - // The logging level is controlled by the environment variable passed to enable debug logs. - // We enable all possible log levels passing the control for exact printouts to the logger - const env = logLevelToEnv('info'); - this.loggers.set(serviceId, marineLogger(serviceId)); - await this.workerThread.createService(serviceModule, serviceId, env); + await this.avmWasmLoader.start(); + + await this.workerLoader.start(); + const worker = await this.workerLoader.getValue(); + + const workerThread: ModuleThread = + await spawn(worker); + + const logfn: LogFunction = (message) => { + const serviceLogger = this.loggers.get(message.service); + + if (serviceLogger == null) { + return; + } + + serviceLogger[message.level](message.message); + }; + + workerThread.onLogMessage().subscribe(logfn); + await workerThread.init(wasm); + this.workerThread = workerThread; + await this.createService(this.avmWasmLoader.getValue(), "avm"); + } + + async createService( + serviceModule: ArrayBuffer | SharedArrayBuffer, + serviceId: string, + ): Promise { + if (this.workerThread == null) { + throw new Error("Worker is not initialized"); } - async callService( - serviceId: string, - functionName: string, - args: JSONArray | JSONObject, - callParams: CallParameters, - ): Promise { - if (!this.workerThread) { - throw 'Worker is not initialized'; - } + // The logging level is controlled by the environment variable passed to enable debug logs. + // We enable all possible log levels passing the control for exact printouts to the logger + const env = logLevelToEnv("info"); + this.loggers.set(serviceId, marineLogger(serviceId)); + await this.workerThread.createService(serviceModule, serviceId, env); + } - return this.workerThread.callService(serviceId, functionName, args, callParams); + async callService( + serviceId: string, + functionName: string, + args: JSONArray | JSONObject, + callParams: CallParameters, + ): Promise { + if (this.workerThread == null) { + throw new Error("Worker is not initialized"); } - async stop(): Promise { - if (!this.workerThread) { - return; - } - - await this.workerThread.terminate(); - await Thread.terminate(this.workerThread); + return this.workerThread.callService( + serviceId, + functionName, + args, + callParams, + ); + } + + async stop(): Promise { + if (this.workerThread == null) { + return; } + + await this.workerThread.terminate(); + await Thread.terminate(this.workerThread); + } } diff --git a/packages/core/js-client/src/particle/Particle.ts b/packages/core/js-client/src/particle/Particle.ts index 7c5277d5..15a5127b 100644 --- a/packages/core/js-client/src/particle/Particle.ts +++ b/packages/core/js-client/src/particle/Particle.ts @@ -1,5 +1,5 @@ -/* - * Copyright 2020 Fluence Labs Limited +/** + * Copyright 2023 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. @@ -18,23 +18,34 @@ import { CallResultsArray } from "@fluencelabs/avm"; import { fromUint8Array, toUint8Array } from "js-base64"; import { concat } from "uint8arrays/concat"; import { v4 as uuidv4 } from "uuid"; +import { z } from "zod"; import { KeyPair } from "../keypair/index.js"; import { numberToLittleEndianBytes } from "../util/bytes.js"; import { IParticle } from "./interfaces.js"; +const particleSchema = z.object({ + id: z.string(), + timestamp: z.number().positive(), + script: z.string(), + data: z.string(), + ttl: z.number().positive(), + init_peer_id: z.string(), + signature: z.array(z.number()), +}); + export class Particle implements IParticle { - constructor( - public readonly id: string, - public readonly timestamp: number, - public readonly script: string, - public readonly data: Uint8Array, - public readonly ttl: number, - public readonly initPeerId: string, - public readonly signature: Uint8Array - ) {} - + constructor( + readonly id: string, + readonly timestamp: number, + readonly script: string, + readonly data: Uint8Array, + readonly ttl: number, + readonly initPeerId: string, + readonly signature: Uint8Array, + ) {} + static async createNew( script: string, initPeerId: string, @@ -60,20 +71,31 @@ export class Particle implements IParticle { ); } - static fromString(str: string): Particle { - const json = JSON.parse(str); - const res = new Particle( - json.id, - json.timestamp, - json.script, - toUint8Array(json.data), - json.ttl, - json.init_peer_id, - new Uint8Array(json.signature) - ); + static fromString(str: string): Particle { + const json = JSON.parse(str); - return res; + const res = particleSchema.safeParse(json); + + if (!res.success) { + throw new Error( + `Particle format invalid. Errors: ${JSON.stringify( + res.error.flatten(), + )}`, + ); } + + const data = res.data; + + return new Particle( + data.id, + data.timestamp, + data.script, + toUint8Array(data.data), + data.ttl, + data.init_peer_id, + new Uint8Array(data.signature), + ); + } } const en = new TextEncoder(); @@ -109,16 +131,6 @@ export const hasExpired = (particle: IParticle): boolean => { return getActualTTL(particle) <= 0; }; -/** - * Validates that particle signature is correct - */ -export const verifySignature = async (particle: IParticle, publicKey: Uint8Array): Promise => { - // TODO: Uncomment this when nox roll out particle signatures - return true; - // const message = buildParticleMessage(particle); - // return unmarshalPublicKey(publicKey).verify(message, particle.signature); -} - /** * Creates a particle clone with new data */ @@ -179,8 +191,8 @@ export interface ParticleQueueItem { */ export const handleTimeout = (fn: () => void) => { return (stage: ParticleExecutionStage) => { - if (stage.stage === "expired") { - fn(); - } - } -} + if (stage.stage === "expired") { + fn(); + } + }; +}; diff --git a/packages/core/js-client/src/particle/interfaces.ts b/packages/core/js-client/src/particle/interfaces.ts index 3f7a131a..cbe5c8cb 100644 --- a/packages/core/js-client/src/particle/interfaces.ts +++ b/packages/core/js-client/src/particle/interfaces.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,49 +13,50 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { PeerIdB58 } from '@fluencelabs/interfaces'; + +import { PeerIdB58 } from "@fluencelabs/interfaces"; /** * Immutable part of the particle. */ export interface IImmutableParticlePart { - /** - * Particle id - */ - readonly id: string; + /** + * Particle id + */ + readonly id: string; - /** - * Particle timestamp. Specifies when the particle was created. - */ - readonly timestamp: number; + /** + * Particle timestamp. Specifies when the particle was created. + */ + readonly timestamp: number; - /** - * Particle's air script - */ - readonly script: string; + /** + * Particle's air script + */ + readonly script: string; - /** - * Particle's ttl. Specifies how long the particle is valid in milliseconds. - */ - readonly ttl: number; + /** + * Particle's ttl. Specifies how long the particle is valid in milliseconds. + */ + readonly ttl: number; - /** - * Peer id where the particle was initiated. - */ - readonly initPeerId: PeerIdB58; + /** + * Peer id where the particle was initiated. + */ + readonly initPeerId: PeerIdB58; - /** - * Particle's signature of concatenation of bytes of all immutable particle fields. - */ - readonly signature: Uint8Array; + /** + * Particle's signature of concatenation of bytes of all immutable particle fields. + */ + readonly signature: Uint8Array; } /** * Particle is a data structure that is used to transfer data between peers in Fluence network. */ export interface IParticle extends IImmutableParticlePart { - /** - * Mutable particle data - */ - data: Uint8Array; + /** + * Mutable particle data + */ + data: Uint8Array; } diff --git a/packages/core/js-client/src/services/NodeUtils.ts b/packages/core/js-client/src/services/NodeUtils.ts index d6b89be8..4ebba7b4 100644 --- a/packages/core/js-client/src/services/NodeUtils.ts +++ b/packages/core/js-client/src/services/NodeUtils.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,57 +14,65 @@ * limitations under the License. */ -import { CallParams, IFluenceInternalApi } from '@fluencelabs/interfaces'; -import { defaultGuard } from './SingleModuleSrv.js'; -import { NodeUtilsDef, registerNodeUtils } from './_aqua/node-utils.js'; -import { SecurityGuard } from './securityGuard.js'; -import * as fs from 'fs'; -import { FluencePeer } from '../jsPeer/FluencePeer.js'; -import { Buffer } from 'buffer'; +import { Buffer } from "buffer"; +import * as fs from "fs"; + +import { CallParams } from "@fluencelabs/interfaces"; + +import { FluencePeer } from "../jsPeer/FluencePeer.js"; +import { getErrorMessage } from "../util/utils.js"; + +import { NodeUtilsDef, registerNodeUtils } from "./_aqua/node-utils.js"; +import { SecurityGuard } from "./securityGuard.js"; +import { defaultGuard } from "./SingleModuleSrv.js"; export class NodeUtils implements NodeUtilsDef { - constructor(private peer: FluencePeer) { - this.securityGuard_readFile = defaultGuard(this.peer); + constructor(private peer: FluencePeer) { + this.securityGuard_readFile = defaultGuard(this.peer); + } + + securityGuard_readFile: SecurityGuard<"path">; + + async read_file(path: string, callParams: CallParams<"path">) { + if (!this.securityGuard_readFile(callParams)) { + return { + success: false, + error: "Security guard validation failed", + content: null, + }; } - securityGuard_readFile: SecurityGuard<'path'>; + try { + // Strange enough, but Buffer type works here, while reading with encoding 'utf-8' doesn't + const data = await new Promise((resolve, reject) => { + fs.readFile(path, (err, data) => { + if (err != null) { + reject(err); + return; + } - async read_file(path: string, callParams: CallParams<'path'>) { - if (!this.securityGuard_readFile(callParams)) { - return { - success: false, - error: 'Security guard validation failed', - content: null, - }; - } + resolve(data); + }); + }); - try { - // Strange enough, but Buffer type works here, while reading with encoding 'utf-8' doesn't - const data = await new Promise((resolve, reject) => { - fs.readFile(path, (err, data) => { - if (err) { - reject(err); - return; - } - resolve(data); - }) - }); - return { - success: true, - content: data as unknown as string, - error: null, - }; - } catch (err: any) { - return { - success: false, - error: err.message, - content: null, - }; - } + return { + success: true, + // TODO: this is strange bug. + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + content: data as unknown as string, + error: null, + }; + } catch (err: unknown) { + return { + success: false, + error: getErrorMessage(err), + content: null, + }; } + } } // HACK:: security guard functions must be ported to user API -export const doRegisterNodeUtils = (peer: any) => { - registerNodeUtils(peer, 'node_utils', new NodeUtils(peer)); +export const doRegisterNodeUtils = (peer: FluencePeer) => { + registerNodeUtils(peer, "node_utils", new NodeUtils(peer)); }; diff --git a/packages/core/js-client/src/services/Sig.ts b/packages/core/js-client/src/services/Sig.ts index 64703bfd..5663e572 100644 --- a/packages/core/js-client/src/services/Sig.ts +++ b/packages/core/js-client/src/services/Sig.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,75 +14,93 @@ * limitations under the License. */ -import { CallParams, PeerIdB58 } from '@fluencelabs/interfaces'; -import { KeyPair } from '../keypair/index.js'; -import { FluencePeer } from '../jsPeer/FluencePeer.js'; -import { SigDef } from './_aqua/services.js'; -import { allowOnlyParticleOriginatedAt, allowServiceFn, and, or, SecurityGuard } from './securityGuard.js'; +import { CallParams, PeerIdB58 } from "@fluencelabs/interfaces"; + +import { KeyPair } from "../keypair/index.js"; + +import { SigDef } from "./_aqua/services.js"; +import { + allowOnlyParticleOriginatedAt, + allowServiceFn, + and, + or, + SecurityGuard, +} from "./securityGuard.js"; export const defaultSigGuard = (peerId: PeerIdB58) => { - return and<'data'>( - allowOnlyParticleOriginatedAt(peerId), - or( - allowServiceFn('trust-graph', 'get_trust_bytes'), - allowServiceFn('trust-graph', 'get_revocation_bytes'), - allowServiceFn('registry', 'get_key_bytes'), - allowServiceFn('registry', 'get_record_bytes'), - allowServiceFn('registry', 'get_record_metadata_bytes'), - allowServiceFn('registry', 'get_tombstone_bytes'), - ), - ); + return and<"data">( + allowOnlyParticleOriginatedAt(peerId), + or( + allowServiceFn("trust-graph", "get_trust_bytes"), + allowServiceFn("trust-graph", "get_revocation_bytes"), + allowServiceFn("registry", "get_key_bytes"), + allowServiceFn("registry", "get_record_bytes"), + allowServiceFn("registry", "get_record_metadata_bytes"), + allowServiceFn("registry", "get_tombstone_bytes"), + ), + ); }; -export class Sig implements SigDef { - constructor(private keyPair: KeyPair) {} - - /** - * Configurable security guard for sign method - */ - securityGuard: SecurityGuard<'data'> = (params) => { - return true; +type SignReturnType = + | { + error: null; + signature: number[]; + success: true; + } + | { + error: string; + signature: null; + success: false; }; - /** - * Gets the public key of KeyPair. Required by aqua - */ - get_peer_id() { - return this.keyPair.getPeerId(); +export class Sig implements SigDef { + constructor(private keyPair: KeyPair) {} + + /** + * Configurable security guard for sign method + */ + securityGuard: SecurityGuard<"data"> = () => { + return true; + }; + + /** + * Gets the public key of KeyPair. Required by aqua + */ + get_peer_id() { + return this.keyPair.getPeerId(); + } + + /** + * Signs the data using key pair's private key. Required by aqua + */ + async sign( + data: number[], + callParams: CallParams<"data">, + ): Promise { + if (!this.securityGuard(callParams)) { + return { + success: false, + error: "Security guard validation failed", + signature: null, + }; } - /** - * Signs the data using key pair's private key. Required by aqua - */ - async sign( - data: number[], - callParams: CallParams<'data'>, - ): Promise<{ error: string | null; signature: number[] | null; success: boolean }> { - if (!this.securityGuard(callParams)) { - return { - success: false, - error: 'Security guard validation failed', - signature: null, - }; - } + const signedData = await this.keyPair.signBytes(Uint8Array.from(data)); - const signedData = await this.keyPair.signBytes(Uint8Array.from(data)); + return { + success: true, + error: null, + signature: Array.from(signedData), + }; + } - return { - success: true, - error: null, - signature: Array.from(signedData), - }; - } - - /** - * Verifies the signature. Required by aqua - */ - verify(signature: number[], data: number[]): Promise { - return this.keyPair.verify(Uint8Array.from(data), Uint8Array.from(signature)) - } + /** + * Verifies the signature. Required by aqua + */ + verify(signature: number[], data: number[]): Promise { + return this.keyPair.verify( + Uint8Array.from(data), + Uint8Array.from(signature), + ); + } } - -export const getDefaultSig = (peer: FluencePeer) => { - peer.registerMarineService; -}; diff --git a/packages/core/js-client/src/services/SingleModuleSrv.ts b/packages/core/js-client/src/services/SingleModuleSrv.ts index 36fedf49..4c29e875 100644 --- a/packages/core/js-client/src/services/SingleModuleSrv.ts +++ b/packages/core/js-client/src/services/SingleModuleSrv.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,88 +14,98 @@ * limitations under the License. */ -import { v4 as uuidv4 } from 'uuid'; -import { SrvDef } from './_aqua/single-module-srv.js'; -import { FluencePeer } from '../jsPeer/FluencePeer.js'; -import { CallParams } from '@fluencelabs/interfaces'; -import { Buffer } from 'buffer'; -import { allowOnlyParticleOriginatedAt, SecurityGuard } from './securityGuard.js'; +import { Buffer } from "buffer"; + +import { CallParams } from "@fluencelabs/interfaces"; +import { v4 as uuidv4 } from "uuid"; + +import { FluencePeer } from "../jsPeer/FluencePeer.js"; +import { getErrorMessage } from "../util/utils.js"; + +import { SrvDef } from "./_aqua/single-module-srv.js"; +import { + allowOnlyParticleOriginatedAt, + SecurityGuard, +} from "./securityGuard.js"; export const defaultGuard = (peer: FluencePeer) => { - return allowOnlyParticleOriginatedAt(peer.keyPair.getPeerId()); + return allowOnlyParticleOriginatedAt(peer.keyPair.getPeerId()); }; export class Srv implements SrvDef { - private services: Set = new Set(); + private services: Set = new Set(); - constructor(private peer: FluencePeer) { - this.securityGuard_create = defaultGuard(this.peer); - this.securityGuard_remove = defaultGuard(this.peer); + constructor(private peer: FluencePeer) { + this.securityGuard_create = defaultGuard(this.peer); + this.securityGuard_remove = defaultGuard(this.peer); + } + + securityGuard_create: SecurityGuard<"wasm_b64_content">; + + async create( + wasm_b64_content: string, + callParams: CallParams<"wasm_b64_content">, + ) { + if (!this.securityGuard_create(callParams)) { + return { + success: false, + error: "Security guard validation failed", + service_id: null, + }; } - securityGuard_create: SecurityGuard<'wasm_b64_content'>; + try { + const newServiceId = uuidv4(); + const buffer = Buffer.from(wasm_b64_content, "base64"); + // TODO:: figure out why SharedArrayBuffer is not working here + // const sab = new SharedArrayBuffer(buffer.length); + // const tmp = new Uint8Array(sab); + // tmp.set(buffer, 0); + await this.peer.registerMarineService(buffer, newServiceId); + this.services.add(newServiceId); - async create(wasm_b64_content: string, callParams: CallParams<'wasm_b64_content'>) { - if (!this.securityGuard_create(callParams)) { - return { - success: false, - error: 'Security guard validation failed', - service_id: null, - }; - } + return { + success: true, + service_id: newServiceId, + error: null, + }; + } catch (err: unknown) { + return { + success: true, + service_id: null, + error: getErrorMessage(err), + }; + } + } - try { - const newServiceId = uuidv4(); - const buffer = Buffer.from(wasm_b64_content, 'base64'); - // TODO:: figure out why SharedArrayBuffer is not working here - // const sab = new SharedArrayBuffer(buffer.length); - // const tmp = new Uint8Array(sab); - // tmp.set(buffer, 0); - await this.peer.registerMarineService(buffer, newServiceId); - this.services.add(newServiceId); + securityGuard_remove: SecurityGuard<"service_id">; - return { - success: true, - service_id: newServiceId, - error: null, - }; - } catch (err: any) { - return { - success: true, - service_id: null, - error: err.message, - }; - } + async remove(service_id: string, callParams: CallParams<"service_id">) { + if (!this.securityGuard_remove(callParams)) { + return { + success: false, + error: "Security guard validation failed", + service_id: null, + }; } - securityGuard_remove: SecurityGuard<'service_id'>; - - async remove(service_id: string, callParams: CallParams<'service_id'>) { - if (!this.securityGuard_remove(callParams)) { - return { - success: false, - error: 'Security guard validation failed', - service_id: null, - }; - } - - if (!this.services.has(service_id)) { - return { - success: false, - error: `Service with id ${service_id} not found`, - }; - } - - await this.peer.removeMarineService(service_id); - this.services.delete(service_id); - - return { - success: true, - error: null, - }; + if (!this.services.has(service_id)) { + return { + success: false, + error: `Service with id ${service_id} not found`, + }; } - list() { - return Array.from(this.services.values()); - } + await this.peer.removeMarineService(service_id); + this.services.delete(service_id); + + return { + success: true, + error: null, + }; + } + + list() { + return Array.from(this.services.values()); + } } diff --git a/packages/core/js-client/src/services/Tracing.ts b/packages/core/js-client/src/services/Tracing.ts index 93bb61b5..3764aa8b 100644 --- a/packages/core/js-client/src/services/Tracing.ts +++ b/packages/core/js-client/src/services/Tracing.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,11 +14,18 @@ * limitations under the License. */ -import { CallParams } from '@fluencelabs/interfaces'; -import { TracingDef } from './_aqua/tracing.js'; +import { CallParams } from "@fluencelabs/interfaces"; + +import { TracingDef } from "./_aqua/tracing.js"; export class Tracing implements TracingDef { - tracingEvent(arrowName: string, event: string, callParams: CallParams<'arrowName' | 'event'>): void { - console.log('[%s] (%s) %s', callParams.particleId, arrowName, event); - } + tracingEvent( + arrowName: string, + event: string, + callParams: CallParams<"arrowName" | "event">, + ): void { + // This console log is intentional + // eslint-disable-next-line no-console + console.log("[%s] (%s) %s", callParams.particleId, arrowName, event); + } } diff --git a/packages/core/js-client/src/services/__test__/builtInHandler.spec.ts b/packages/core/js-client/src/services/__test__/builtInHandler.spec.ts index 8601f2d2..3d538096 100644 --- a/packages/core/js-client/src/services/__test__/builtInHandler.spec.ts +++ b/packages/core/js-client/src/services/__test__/builtInHandler.spec.ts @@ -1,12 +1,30 @@ -import { it, describe, expect, test } from 'vitest'; +/** + * Copyright 2023 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 { CallParams } from '@fluencelabs/interfaces'; -import { toUint8Array } from 'js-base64'; -import { KeyPair } from '../../keypair/index.js'; -import { Sig, defaultSigGuard } from '../Sig.js'; -import { allowServiceFn } from '../securityGuard.js'; -import { builtInServices } from '../builtins.js'; -import { CallServiceData } from '../../jsServiceHost/interfaces.js'; +import assert from "assert"; + +import { CallParams, JSONArray } from "@fluencelabs/interfaces"; +import { toUint8Array } from "js-base64"; +import { it, describe, expect, test } from "vitest"; + +import { CallServiceData } from "../../jsServiceHost/interfaces.js"; +import { KeyPair } from "../../keypair/index.js"; +import { builtInServices } from "../builtins.js"; +import { allowServiceFn } from "../securityGuard.js"; +import { Sig, defaultSigGuard } from "../Sig.js"; const a10b20 = `{ "a": 10, @@ -20,294 +38,353 @@ const oneTwoThreeFour = `[ 4 ]`; -describe('Tests for default handler', () => { - test.each` - serviceId | fnName | args | retCode | result - ${'op'} | ${'identity'} | ${[]} | ${0} | ${{}} - ${'op'} | ${'identity'} | ${[1]} | ${0} | ${1} - ${'op'} | ${'identity'} | ${[1, 2]} | ${1} | ${'identity accepts up to 1 arguments, received 2 arguments'} - ${'op'} | ${'noop'} | ${[1, 2]} | ${0} | ${{}} - ${'op'} | ${'array'} | ${[1, 2, 3]} | ${0} | ${[1, 2, 3]} - ${'op'} | ${'array_length'} | ${[[1, 2, 3]]} | ${0} | ${3} - ${'op'} | ${'array_length'} | ${[]} | ${1} | ${'array_length accepts exactly one argument, found: 0'} - ${'op'} | ${'concat'} | ${[[1, 2], [3, 4], [5, 6]]} | ${0} | ${[1, 2, 3, 4, 5, 6]} - ${'op'} | ${'concat'} | ${[[1, 2]]} | ${0} | ${[1, 2]} - ${'op'} | ${'concat'} | ${[]} | ${0} | ${[]} - ${'op'} | ${'concat'} | ${[1, [1, 2], 1]} | ${1} | ${"All arguments of 'concat' must be arrays: arguments 0, 2 are not"} - ${'op'} | ${'string_to_b58'} | ${['test']} | ${0} | ${'3yZe7d'} - ${'op'} | ${'string_to_b58'} | ${['test', 1]} | ${1} | ${'string_to_b58 accepts only one string argument'} - ${'op'} | ${'string_from_b58'} | ${['3yZe7d']} | ${0} | ${'test'} - ${'op'} | ${'string_from_b58'} | ${['3yZe7d', 1]} | ${1} | ${'string_from_b58 accepts only one string argument'} - ${'op'} | ${'bytes_to_b58'} | ${[[116, 101, 115, 116]]} | ${0} | ${'3yZe7d'} - ${'op'} | ${'bytes_to_b58'} | ${[[116, 101, 115, 116], 1]} | ${1} | ${'bytes_to_b58 accepts only single argument: array of numbers'} - ${'op'} | ${'bytes_from_b58'} | ${['3yZe7d']} | ${0} | ${[116, 101, 115, 116]} - ${'op'} | ${'bytes_from_b58'} | ${['3yZe7d', 1]} | ${1} | ${'bytes_from_b58 accepts only one string argument'} - ${'op'} | ${'sha256_string'} | ${['hello, world!']} | ${0} | ${'QmVQ8pg6L1tpoWYeq6dpoWqnzZoSLCh7E96fCFXKvfKD3u'} - ${'op'} | ${'sha256_string'} | ${['hello, world!', true]} | ${0} | ${'84V7ZxLW7qKsx1Qvbd63BdGaHxUc3TfT2MBPqAXM7Wyu'} - ${'op'} | ${'sha256_string'} | ${[]} | ${1} | ${'sha256_string accepts 1-3 arguments, found: 0'} - ${'op'} | ${'concat_strings'} | ${[]} | ${0} | ${''} - ${'op'} | ${'concat_strings'} | ${['a', 'b', 'c']} | ${0} | ${'abc'} - ${'peer'} | ${'timeout'} | ${[200, []]} | ${0} | ${[]} - ${'peer'} | ${'timeout'} | ${[200, ['test']]} | ${0} | ${['test']} - ${'peer'} | ${'timeout'} | ${[]} | ${1} | ${'timeout accepts exactly two arguments: timeout duration in ms and a message string'} - ${'peer'} | ${'timeout'} | ${[200, 'test', 1]} | ${1} | ${'timeout accepts exactly two arguments: timeout duration in ms and a message string'} - ${'debug'} | ${'stringify'} | ${[]} | ${0} | ${'""'} - ${'debug'} | ${'stringify'} | ${[{ a: 10, b: 20 }]} | ${0} | ${a10b20} - ${'debug'} | ${'stringify'} | ${[1, 2, 3, 4]} | ${0} | ${oneTwoThreeFour} - ${'math'} | ${'add'} | ${[2, 2]} | ${0} | ${4} - ${'math'} | ${'add'} | ${[2]} | ${1} | ${'Expected 2 argument(s). Got 1'} - ${'math'} | ${'sub'} | ${[2, 2]} | ${0} | ${0} - ${'math'} | ${'sub'} | ${[2, 3]} | ${0} | ${-1} - ${'math'} | ${'mul'} | ${[2, 2]} | ${0} | ${4} - ${'math'} | ${'mul'} | ${[2, 0]} | ${0} | ${0} - ${'math'} | ${'mul'} | ${[2, -1]} | ${0} | ${-2} - ${'math'} | ${'fmul'} | ${[10, 0.66]} | ${0} | ${6} - ${'math'} | ${'fmul'} | ${[0.5, 0.5]} | ${0} | ${0} - ${'math'} | ${'fmul'} | ${[100.5, 0.5]} | ${0} | ${50} - ${'math'} | ${'div'} | ${[2, 2]} | ${0} | ${1} - ${'math'} | ${'div'} | ${[2, 3]} | ${0} | ${0} - ${'math'} | ${'div'} | ${[10, 5]} | ${0} | ${2} - ${'math'} | ${'rem'} | ${[10, 3]} | ${0} | ${1} - ${'math'} | ${'pow'} | ${[2, 2]} | ${0} | ${4} - ${'math'} | ${'pow'} | ${[2, 0]} | ${0} | ${1} - ${'math'} | ${'log'} | ${[2, 2]} | ${0} | ${1} - ${'math'} | ${'log'} | ${[2, 4]} | ${0} | ${2} - ${'cmp'} | ${'gt'} | ${[2, 4]} | ${0} | ${false} - ${'cmp'} | ${'gte'} | ${[2, 4]} | ${0} | ${false} - ${'cmp'} | ${'gte'} | ${[4, 2]} | ${0} | ${true} - ${'cmp'} | ${'gte'} | ${[2, 2]} | ${0} | ${true} - ${'cmp'} | ${'lt'} | ${[2, 4]} | ${0} | ${true} - ${'cmp'} | ${'lte'} | ${[2, 4]} | ${0} | ${true} - ${'cmp'} | ${'lte'} | ${[4, 2]} | ${0} | ${false} - ${'cmp'} | ${'lte'} | ${[2, 2]} | ${0} | ${true} - ${'cmp'} | ${'cmp'} | ${[2, 4]} | ${0} | ${-1} - ${'cmp'} | ${'cmp'} | ${[2, -4]} | ${0} | ${1} - ${'cmp'} | ${'cmp'} | ${[2, 2]} | ${0} | ${0} - ${'array'} | ${'sum'} | ${[[1, 2, 3]]} | ${0} | ${6} - ${'array'} | ${'dedup'} | ${[['a', 'a', 'b', 'c', 'a', 'b', 'c']]} | ${0} | ${['a', 'b', 'c']} - ${'array'} | ${'intersect'} | ${[['a', 'b', 'c'], ['c', 'b', 'd']]} | ${0} | ${['b', 'c']} - ${'array'} | ${'diff'} | ${[['a', 'b', 'c'], ['c', 'b', 'd']]} | ${0} | ${['a']} - ${'array'} | ${'sdiff'} | ${[['a', 'b', 'c'], ['c', 'b', 'd']]} | ${0} | ${['a', 'd']} - ${'json'} | ${'obj'} | ${['a', 10, 'b', 'string', 'c', null]} | ${0} | ${{ a: 10, b: 'string', c: null }} - ${'json'} | ${'obj'} | ${['a', 10, 'b', 'string', 'c']} | ${1} | ${'Expected even number of argument(s). Got 5'} - ${'json'} | ${'obj'} | ${[]} | ${0} | ${{}} - ${'json'} | ${'put'} | ${[{}, 'a', 10]} | ${0} | ${{ a: 10 }} - ${'json'} | ${'put'} | ${[{ b: 11 }, 'a', 10]} | ${0} | ${{ a: 10, b: 11 }} - ${'json'} | ${'put'} | ${['a', 'a', 11]} | ${1} | ${'Argument 0 expected to be of type object, Got string'} - ${'json'} | ${'put'} | ${[{}, 'a', 10, 'b', 20]} | ${1} | ${'Expected 3 argument(s). Got 5'} - ${'json'} | ${'put'} | ${[{}]} | ${1} | ${'Expected 3 argument(s). Got 1'} - ${'json'} | ${'puts'} | ${[{}, 'a', 10]} | ${0} | ${{ a: 10 }} - ${'json'} | ${'puts'} | ${[{ b: 11 }, 'a', 10]} | ${0} | ${{ a: 10, b: 11 }} - ${'json'} | ${'puts'} | ${[{}, 'a', 10, 'b', 'string', 'c', null]} | ${0} | ${{ a: 10, b: 'string', c: null }} - ${'json'} | ${'puts'} | ${[{ x: 'text' }, 'a', 10, 'b', 'string']} | ${0} | ${{ a: 10, b: 'string', x: 'text' }} - ${'json'} | ${'puts'} | ${[{}]} | ${1} | ${'Expected more than 3 argument(s). Got 1'} - ${'json'} | ${'puts'} | ${['a', 'a', 11]} | ${1} | ${'Argument 0 expected to be of type object, Got string'} - ${'json'} | ${'stringify'} | ${[{ a: 10, b: 'string', c: null }]} | ${0} | ${'{"a":10,"b":"string","c":null}'} - ${'json'} | ${'stringify'} | ${[1]} | ${1} | ${'Argument 0 expected to be of type object, Got number'} - ${'json'} | ${'parse'} | ${['{"a":10,"b":"string","c":null}']} | ${0} | ${{ a: 10, b: 'string', c: null }} - ${'json'} | ${'parse'} | ${['incorrect']} | ${1} | ${'Unexpected token i in JSON at position 0'} - ${'json'} | ${'parse'} | ${[10]} | ${1} | ${'Argument 0 expected to be of type string, Got number'} - `( - // - '$fnName with $args expected retcode: $retCode and result: $result', - async ({ serviceId, fnName, args, retCode, result }) => { - // arrange - const req: CallServiceData = { - serviceId: serviceId, - fnName: fnName, - args: args, - tetraplets: [], - particleContext: { - particleId: 'some', - initPeerId: 'init peer id', - timestamp: 595951200, - ttl: 595961200, - signature: new Uint8Array([]), - }, - }; +interface ServiceCallType { + serviceId: string; + fnName: string; + args: JSONArray; + retCode: 0 | 1; + result: unknown; +} - // act - const fn = builtInServices[req.serviceId][req.fnName]; - const res = await fn(req); - - // Our test cases above depend on node error message. In node 20 error message for JSON.parse has changed. - // Simple and fast solution for this specific case is to unify both variations into node 18 version error format. - if (res.result === 'Unexpected token \'i\', "incorrect" is not valid JSON') { - res.result = 'Unexpected token i in JSON at position 0'; - } - - // assert - expect(res).toMatchObject({ - retCode: retCode, - result: result, - }); +describe("Tests for default handler", () => { + test.each` + serviceId | fnName | args | retCode | result + ${"op"} | ${"identity"} | ${[]} | ${0} | ${{}} + ${"op"} | ${"identity"} | ${[1]} | ${0} | ${1} + ${"op"} | ${"identity"} | ${[1, 2]} | ${1} | ${"identity accepts up to 1 arguments, received 2 arguments"} + ${"op"} | ${"noop"} | ${[1, 2]} | ${0} | ${{}} + ${"op"} | ${"array"} | ${[1, 2, 3]} | ${0} | ${[1, 2, 3]} + ${"op"} | ${"array_length"} | ${[[1, 2, 3]]} | ${0} | ${3} + ${"op"} | ${"array_length"} | ${[]} | ${1} | ${"array_length accepts exactly one argument, found: 0"} + ${"op"} | ${"concat"} | ${[[1, 2], [3, 4], [5, 6]]} | ${0} | ${[1, 2, 3, 4, 5, 6]} + ${"op"} | ${"concat"} | ${[[1, 2]]} | ${0} | ${[1, 2]} + ${"op"} | ${"concat"} | ${[]} | ${0} | ${[]} + ${"op"} | ${"concat"} | ${[1, [1, 2], 1]} | ${1} | ${"All arguments of 'concat' must be arrays: arguments 0, 2 are not"} + ${"op"} | ${"string_to_b58"} | ${["test"]} | ${0} | ${"3yZe7d"} + ${"op"} | ${"string_to_b58"} | ${["test", 1]} | ${1} | ${"string_to_b58 accepts only one string argument"} + ${"op"} | ${"string_from_b58"} | ${["3yZe7d"]} | ${0} | ${"test"} + ${"op"} | ${"string_from_b58"} | ${["3yZe7d", 1]} | ${1} | ${"string_from_b58 accepts only one string argument"} + ${"op"} | ${"bytes_to_b58"} | ${[[116, 101, 115, 116]]} | ${0} | ${"3yZe7d"} + ${"op"} | ${"bytes_to_b58"} | ${[[116, 101, 115, 116], 1]} | ${1} | ${"bytes_to_b58 accepts only single argument: array of numbers"} + ${"op"} | ${"bytes_from_b58"} | ${["3yZe7d"]} | ${0} | ${[116, 101, 115, 116]} + ${"op"} | ${"bytes_from_b58"} | ${["3yZe7d", 1]} | ${1} | ${"bytes_from_b58 accepts only one string argument"} + ${"op"} | ${"sha256_string"} | ${["hello, world!"]} | ${0} | ${"QmVQ8pg6L1tpoWYeq6dpoWqnzZoSLCh7E96fCFXKvfKD3u"} + ${"op"} | ${"sha256_string"} | ${["hello, world!", true]} | ${1} | ${"sha256_string accepts 1 argument, found: 2"} + ${"op"} | ${"sha256_string"} | ${[]} | ${1} | ${"sha256_string accepts 1 argument, found: 0"} + ${"op"} | ${"concat_strings"} | ${[]} | ${0} | ${""} + ${"op"} | ${"concat_strings"} | ${["a", "b", "c"]} | ${0} | ${"abc"} + ${"peer"} | ${"timeout"} | ${[200, []]} | ${1} | ${"timeout accepts exactly two arguments: timeout duration in ms and a message string"} + ${"peer"} | ${"timeout"} | ${[200, "test"]} | ${0} | ${"test"} + ${"peer"} | ${"timeout"} | ${[]} | ${1} | ${"timeout accepts exactly two arguments: timeout duration in ms and a message string"} + ${"peer"} | ${"timeout"} | ${[200, "test", 1]} | ${1} | ${"timeout accepts exactly two arguments: timeout duration in ms and a message string"} + ${"debug"} | ${"stringify"} | ${[]} | ${0} | ${'""'} + ${"debug"} | ${"stringify"} | ${[{ a: 10, b: 20 }]} | ${0} | ${a10b20} + ${"debug"} | ${"stringify"} | ${[1, 2, 3, 4]} | ${0} | ${oneTwoThreeFour} + ${"math"} | ${"add"} | ${[2, 2]} | ${0} | ${4} + ${"math"} | ${"add"} | ${[2]} | ${1} | ${"Expected 2 argument(s). Got 1"} + ${"math"} | ${"sub"} | ${[2, 2]} | ${0} | ${0} + ${"math"} | ${"sub"} | ${[2, 3]} | ${0} | ${-1} + ${"math"} | ${"mul"} | ${[2, 2]} | ${0} | ${4} + ${"math"} | ${"mul"} | ${[2, 0]} | ${0} | ${0} + ${"math"} | ${"mul"} | ${[2, -1]} | ${0} | ${-2} + ${"math"} | ${"fmul"} | ${[10, 0.66]} | ${0} | ${6} + ${"math"} | ${"fmul"} | ${[0.5, 0.5]} | ${0} | ${0} + ${"math"} | ${"fmul"} | ${[100.5, 0.5]} | ${0} | ${50} + ${"math"} | ${"div"} | ${[2, 2]} | ${0} | ${1} + ${"math"} | ${"div"} | ${[2, 3]} | ${0} | ${0} + ${"math"} | ${"div"} | ${[10, 5]} | ${0} | ${2} + ${"math"} | ${"rem"} | ${[10, 3]} | ${0} | ${1} + ${"math"} | ${"pow"} | ${[2, 2]} | ${0} | ${4} + ${"math"} | ${"pow"} | ${[2, 0]} | ${0} | ${1} + ${"math"} | ${"log"} | ${[2, 2]} | ${0} | ${1} + ${"math"} | ${"log"} | ${[2, 4]} | ${0} | ${2} + ${"cmp"} | ${"gt"} | ${[2, 4]} | ${0} | ${false} + ${"cmp"} | ${"gte"} | ${[2, 4]} | ${0} | ${false} + ${"cmp"} | ${"gte"} | ${[4, 2]} | ${0} | ${true} + ${"cmp"} | ${"gte"} | ${[2, 2]} | ${0} | ${true} + ${"cmp"} | ${"lt"} | ${[2, 4]} | ${0} | ${true} + ${"cmp"} | ${"lte"} | ${[2, 4]} | ${0} | ${true} + ${"cmp"} | ${"lte"} | ${[4, 2]} | ${0} | ${false} + ${"cmp"} | ${"lte"} | ${[2, 2]} | ${0} | ${true} + ${"cmp"} | ${"cmp"} | ${[2, 4]} | ${0} | ${-1} + ${"cmp"} | ${"cmp"} | ${[2, -4]} | ${0} | ${1} + ${"cmp"} | ${"cmp"} | ${[2, 2]} | ${0} | ${0} + ${"array"} | ${"sum"} | ${[[1, 2, 3]]} | ${0} | ${6} + ${"array"} | ${"dedup"} | ${[["a", "a", "b", "c", "a", "b", "c"]]} | ${0} | ${["a", "b", "c"]} + ${"array"} | ${"intersect"} | ${[["a", "b", "c"], ["c", "b", "d"]]} | ${0} | ${["b", "c"]} + ${"array"} | ${"diff"} | ${[["a", "b", "c"], ["c", "b", "d"]]} | ${0} | ${["a"]} + ${"array"} | ${"sdiff"} | ${[["a", "b", "c"], ["c", "b", "d"]]} | ${0} | ${["a", "d"]} + ${"json"} | ${"obj"} | ${["a", 10, "b", "string", "c", null]} | ${0} | ${{ a: 10, b: "string", c: null }} + ${"json"} | ${"obj"} | ${["a", 10, "b", "string", "c"]} | ${1} | ${"Expected even number of argument(s). Got 5"} + ${"json"} | ${"obj"} | ${[]} | ${0} | ${{}} + ${"json"} | ${"put"} | ${[{}, "a", 10]} | ${0} | ${{ a: 10 }} + ${"json"} | ${"put"} | ${[{ b: 11 }, "a", 10]} | ${0} | ${{ a: 10, b: 11 }} + ${"json"} | ${"put"} | ${["a", "a", 11]} | ${1} | ${"Argument 0 expected to be of type object, Got string"} + ${"json"} | ${"put"} | ${[{}, "a", 10, "b", 20]} | ${1} | ${"Expected 3 argument(s). Got 5"} + ${"json"} | ${"put"} | ${[{}]} | ${1} | ${"Expected 3 argument(s). Got 1"} + ${"json"} | ${"puts"} | ${[{}, "a", 10]} | ${0} | ${{ a: 10 }} + ${"json"} | ${"puts"} | ${[{ b: 11 }, "a", 10]} | ${0} | ${{ a: 10, b: 11 }} + ${"json"} | ${"puts"} | ${[{}, "a", 10, "b", "string", "c", null]} | ${0} | ${{ a: 10, b: "string", c: null }} + ${"json"} | ${"puts"} | ${[{ x: "text" }, "a", 10, "b", "string"]} | ${0} | ${{ a: 10, b: "string", x: "text" }} + ${"json"} | ${"puts"} | ${[{}]} | ${1} | ${"Expected more than 3 argument(s). Got 1"} + ${"json"} | ${"puts"} | ${["a", "a", 11]} | ${1} | ${"Argument 0 expected to be of type object, Got string"} + ${"json"} | ${"stringify"} | ${[{ a: 10, b: "string", c: null }]} | ${0} | ${'{"a":10,"b":"string","c":null}'} + ${"json"} | ${"stringify"} | ${[1]} | ${1} | ${"Argument 0 expected to be of type object, Got number"} + ${"json"} | ${"parse"} | ${['{"a":10,"b":"string","c":null}']} | ${0} | ${{ a: 10, b: "string", c: null }} + ${"json"} | ${"parse"} | ${["incorrect"]} | ${1} | ${"Unexpected token i in JSON at position 0"} + ${"json"} | ${"parse"} | ${[10]} | ${1} | ${"Argument 0 expected to be of type string, Got number"} + `( + // + "$fnName with $args expected retcode: $retCode and result: $result", + async ({ serviceId, fnName, args, retCode, result }: ServiceCallType) => { + // arrange + const req: CallServiceData = { + serviceId: serviceId, + fnName: fnName, + args: args, + tetraplets: [], + particleContext: { + particleId: "some", + initPeerId: "init peer id", + timestamp: 595951200, + ttl: 595961200, + signature: new Uint8Array([]), }, - ); + }; - it('should return correct error message for identiy service', async () => { - // arrange - const req: CallServiceData = { - serviceId: 'peer', - fnName: 'identify', - args: [], - tetraplets: [], - particleContext: { - particleId: 'some', - initPeerId: 'init peer id', - timestamp: 595951200, - ttl: 595961200, - signature: new Uint8Array([]), - }, - }; + // act + const fn = builtInServices[req.serviceId][req.fnName]; + const res = await fn(req); - // act - const fn = builtInServices[req.serviceId][req.fnName]; - const res = await fn(req); + // Our test cases above depend on node error message. In node 20 error message for JSON.parse has changed. + // Simple and fast solution for this specific case is to unify both variations into node 18 version error format. + if ( + res.result === "Unexpected token 'i', \"incorrect\" is not valid JSON" + ) { + res.result = "Unexpected token i in JSON at position 0"; + } - // assert - expect(res).toMatchObject({ - retCode: 0, - result: { - external_addresses: [], - node_version: expect.stringContaining('js'), - air_version: expect.stringContaining('js'), - }, - }); + // assert + expect(res).toMatchObject({ + retCode: retCode, + result: result, + }); + }, + ); + + it("should return correct error message for identiy service", async () => { + // arrange + const req: CallServiceData = { + serviceId: "peer", + fnName: "identify", + args: [], + tetraplets: [], + particleContext: { + particleId: "some", + initPeerId: "init peer id", + timestamp: 595951200, + ttl: 595961200, + signature: new Uint8Array([]), + }, + }; + + // act + const fn = builtInServices[req.serviceId][req.fnName]; + const res = await fn(req); + + // assert + expect(res).toMatchObject({ + retCode: 0, + result: { + external_addresses: [], + // stringContaining method returns any for some reason + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + node_version: expect.stringContaining("js"), + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + air_version: expect.stringContaining("js"), + }, }); + }); }); -const key = '+cmeYlZKj+MfSa9dpHV+BmLPm6wq4inGlsPlQ1GvtPk='; +const key = "+cmeYlZKj+MfSa9dpHV+BmLPm6wq4inGlsPlQ1GvtPk="; const context = (async () => { - const keyBytes = toUint8Array(key); - const kp = await KeyPair.fromEd25519SK(keyBytes); - const res = { - peerKeyPair: kp, - peerId: kp.getPeerId(), - }; - return res; + const keyBytes = toUint8Array(key); + const kp = await KeyPair.fromEd25519SK(keyBytes); + + const res = { + peerKeyPair: kp, + peerId: kp.getPeerId(), + }; + + return res; })(); const testData = [1, 2, 3, 4, 5, 6, 7, 9, 10]; // signature produced by KeyPair created from key above (`key` variable) const testDataSig = [ - 224, 104, 245, 206, 140, 248, 27, 72, 68, 133, 111, 10, 164, 197, 242, 132, 107, 77, 224, 67, 99, 106, 76, 29, 144, - 121, 122, 169, 36, 173, 58, 80, 170, 102, 137, 253, 157, 247, 168, 87, 162, 223, 188, 214, 203, 220, 52, 246, 29, - 86, 77, 71, 224, 248, 16, 213, 254, 75, 78, 239, 243, 222, 241, 15, + 224, 104, 245, 206, 140, 248, 27, 72, 68, 133, 111, 10, 164, 197, 242, 132, + 107, 77, 224, 67, 99, 106, 76, 29, 144, 121, 122, 169, 36, 173, 58, 80, 170, + 102, 137, 253, 157, 247, 168, 87, 162, 223, 188, 214, 203, 220, 52, 246, 29, + 86, 77, 71, 224, 248, 16, 213, 254, 75, 78, 239, 243, 222, 241, 15, ]; // signature produced by KeyPair created from some random KeyPair const testDataWrongSig = [ - 116, 247, 189, 118, 236, 53, 147, 123, 219, 75, 176, 105, 101, 108, 233, 137, 97, 14, 146, 132, 252, 70, 51, 153, - 237, 167, 156, 150, 36, 90, 229, 108, 166, 231, 255, 137, 8, 246, 125, 0, 213, 150, 83, 196, 237, 221, 131, 159, - 157, 159, 25, 109, 95, 160, 181, 65, 254, 238, 47, 156, 240, 151, 58, 14, + 116, 247, 189, 118, 236, 53, 147, 123, 219, 75, 176, 105, 101, 108, 233, 137, + 97, 14, 146, 132, 252, 70, 51, 153, 237, 167, 156, 150, 36, 90, 229, 108, 166, + 231, 255, 137, 8, 246, 125, 0, 213, 150, 83, 196, 237, 221, 131, 159, 157, + 159, 25, 109, 95, 160, 181, 65, 254, 238, 47, 156, 240, 151, 58, 14, ]; -const makeTetraplet = (initPeerId: string, serviceId?: string, fnName?: string): CallParams<'data'> => { - return { - initPeerId: initPeerId, - tetraplets: { - data: [ - { - function_name: fnName, - service_id: serviceId, - }, - ], +const makeTestTetraplet = ( + initPeerId: string, + serviceId: string, + fnName: string, +): CallParams<"data"> => { + return { + particleId: "", + timestamp: 0, + ttl: 0, + initPeerId: initPeerId, + tetraplets: { + data: [ + { + peer_pk: initPeerId, + function_name: fnName, + service_id: serviceId, + json_path: "", }, - } as any; + ], + }, + }; }; -describe('Sig service tests', () => { - it('sig.sign should create the correct signature', async () => { - const ctx = await context; - const sig = new Sig(ctx.peerKeyPair); +describe("Sig service tests", () => { + it("sig.sign should create the correct signature", async () => { + const ctx = await context; + const sig = new Sig(ctx.peerKeyPair); - const res = await sig.sign(testData, makeTetraplet(ctx.peerId)); + const res = await sig.sign( + testData, + makeTestTetraplet(ctx.peerId, "any_service", "any_func"), + ); - expect(res.success).toBe(true); - expect(res.signature).toStrictEqual(testDataSig); - }); + expect(res.success).toBe(true); + expect(res.signature).toStrictEqual(testDataSig); + }); - it('sig.verify should return true for the correct signature', async () => { - const ctx = await context; - const sig = new Sig(ctx.peerKeyPair); + it("sig.verify should return true for the correct signature", async () => { + const ctx = await context; + const sig = new Sig(ctx.peerKeyPair); - const res = await sig.verify(testDataSig, testData); + const res = await sig.verify(testDataSig, testData); - expect(res).toBe(true); - }); + expect(res).toBe(true); + }); - it('sig.verify should return false for the incorrect signature', async () => { - const ctx = await context; - const sig = new Sig(ctx.peerKeyPair); + it("sig.verify should return false for the incorrect signature", async () => { + const ctx = await context; + const sig = new Sig(ctx.peerKeyPair); - const res = await sig.verify(testDataWrongSig, testData); + const res = await sig.verify(testDataWrongSig, testData); - expect(res).toBe(false); - }); + expect(res).toBe(false); + }); - it('sign-verify call chain should work', async () => { - const ctx = await context; - const sig = new Sig(ctx.peerKeyPair); + it("sign-verify call chain should work", async () => { + const ctx = await context; + const sig = new Sig(ctx.peerKeyPair); - const signature = await sig.sign(testData, makeTetraplet(ctx.peerId)); - const res = await sig.verify(signature.signature as number[], testData); + const signature = await sig.sign( + testData, + makeTestTetraplet(ctx.peerId, "any_service", "any_func"), + ); - expect(res).toBe(true); - }); + expect(signature.success).toBe(true); + assert(signature.success); + const res = await sig.verify(signature.signature, testData); - it('sig.sign with defaultSigGuard should work for correct callParams', async () => { - const ctx = await context; - const sig = new Sig(ctx.peerKeyPair); - sig.securityGuard = defaultSigGuard(ctx.peerId); + expect(res).toBe(true); + }); - const signature = await sig.sign(testData, makeTetraplet(ctx.peerId, 'registry', 'get_route_bytes')); + it("sig.sign with defaultSigGuard should work for correct callParams", async () => { + const ctx = await context; + const sig = new Sig(ctx.peerKeyPair); + sig.securityGuard = defaultSigGuard(ctx.peerId); - await expect(signature).toBeDefined(); - }); + const signature = await sig.sign( + testData, + makeTestTetraplet(ctx.peerId, "registry", "get_route_bytes"), + ); - it('sig.sign with defaultSigGuard should not allow particles initiated from incorrect service', async () => { - const ctx = await context; - const sig = new Sig(ctx.peerKeyPair); - sig.securityGuard = defaultSigGuard(ctx.peerId); + expect(signature).toBeDefined(); + }); - const res = await sig.sign(testData, makeTetraplet(ctx.peerId, 'other_service', 'other_fn')); + it("sig.sign with defaultSigGuard should not allow particles initiated from incorrect service", async () => { + const ctx = await context; + const sig = new Sig(ctx.peerKeyPair); + sig.securityGuard = defaultSigGuard(ctx.peerId); - await expect(res.success).toBe(false); - await expect(res.error).toBe('Security guard validation failed'); - }); + const res = await sig.sign( + testData, + makeTestTetraplet(ctx.peerId, "other_service", "other_fn"), + ); - it('sig.sign with defaultSigGuard should not allow particles initiated from other peers', async () => { - const ctx = await context; - const sig = new Sig(ctx.peerKeyPair); - sig.securityGuard = defaultSigGuard(ctx.peerId); + expect(res.success).toBe(false); + expect(res.error).toBe("Security guard validation failed"); + }); - const res = await sig.sign( - testData, - makeTetraplet((await KeyPair.randomEd25519()).getPeerId(), 'registry', 'get_key_bytes'), - ); + it("sig.sign with defaultSigGuard should not allow particles initiated from other peers", async () => { + const ctx = await context; + const sig = new Sig(ctx.peerKeyPair); + sig.securityGuard = defaultSigGuard(ctx.peerId); - await expect(res.success).toBe(false); - await expect(res.error).toBe('Security guard validation failed'); - }); + const res = await sig.sign( + testData, + makeTestTetraplet( + (await KeyPair.randomEd25519()).getPeerId(), + "registry", + "get_key_bytes", + ), + ); - it('changing securityGuard should work', async () => { - const ctx = await context; - const sig = new Sig(ctx.peerKeyPair); - sig.securityGuard = allowServiceFn('test', 'test'); + expect(res.success).toBe(false); + expect(res.error).toBe("Security guard validation failed"); + }); - const successful1 = await sig.sign(testData, makeTetraplet(ctx.peerId, 'test', 'test')); - const unSuccessful1 = await sig.sign(testData, makeTetraplet(ctx.peerId, 'wrong', 'wrong')); + it("changing securityGuard should work", async () => { + const ctx = await context; + const sig = new Sig(ctx.peerKeyPair); + sig.securityGuard = allowServiceFn("test", "test"); - sig.securityGuard = allowServiceFn('wrong', 'wrong'); + const successful1 = await sig.sign( + testData, + makeTestTetraplet(ctx.peerId, "test", "test"), + ); - const successful2 = await sig.sign(testData, makeTetraplet(ctx.peerId, 'wrong', 'wrong')); - const unSuccessful2 = await sig.sign(testData, makeTetraplet(ctx.peerId, 'test', 'test')); + const unSuccessful1 = await sig.sign( + testData, + makeTestTetraplet(ctx.peerId, "wrong", "wrong"), + ); - expect(successful1.success).toBe(true); - expect(successful2.success).toBe(true); - expect(unSuccessful1.success).toBe(false); - expect(unSuccessful2.success).toBe(false); - }); + sig.securityGuard = allowServiceFn("wrong", "wrong"); + + const successful2 = await sig.sign( + testData, + makeTestTetraplet(ctx.peerId, "wrong", "wrong"), + ); + + const unSuccessful2 = await sig.sign( + testData, + makeTestTetraplet(ctx.peerId, "test", "test"), + ); + + expect(successful1.success).toBe(true); + expect(successful2.success).toBe(true); + expect(unSuccessful1.success).toBe(false); + expect(unSuccessful2.success).toBe(false); + }); }); diff --git a/packages/core/js-client/src/services/__test__/jsonBuiltin.spec.ts b/packages/core/js-client/src/services/__test__/jsonBuiltin.spec.ts index 02cf4667..430ee609 100644 --- a/packages/core/js-client/src/services/__test__/jsonBuiltin.spec.ts +++ b/packages/core/js-client/src/services/__test__/jsonBuiltin.spec.ts @@ -1,26 +1,39 @@ -import { it, describe, expect, beforeEach, afterEach } from 'vitest'; +/** + * Copyright 2023 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 { Particle } from '../../particle/Particle.js'; -import { FluencePeer } from '../../jsPeer/FluencePeer.js'; -import { mkTestPeer } from '../../util/testUtils.js'; -import { doNothing } from '../../jsServiceHost/serviceUtils.js'; +import { it, describe, expect, beforeEach, afterEach } from "vitest"; + +import { FluencePeer } from "../../jsPeer/FluencePeer.js"; +import { doNothing } from "../../jsServiceHost/serviceUtils.js"; +import { mkTestPeer } from "../../util/testUtils.js"; let peer: FluencePeer; -describe('Sig service test suite', () => { - afterEach(async () => { - if (peer) { - await peer.stop(); - } - }); +describe("Sig service test suite", () => { + afterEach(async () => { + await peer.stop(); + }); - beforeEach(async () => { - peer = await mkTestPeer(); - await peer.start(); - }); + beforeEach(async () => { + peer = await mkTestPeer(); + await peer.start(); + }); - it('JSON builtin spec', async () => { - const script = ` + it("JSON builtin spec", async () => { + const script = ` (seq (seq (seq @@ -47,32 +60,41 @@ describe('Sig service test suite', () => { (call %init_peer_id% ("res" "res") [nested_first nested_second outer_first outer_second outer_first_string outer_first_parsed]) ) `; - const promise = new Promise((resolve) => { - peer.internals.regHandler.common('res', 'res', (req) => { - resolve(req.args); - return { - result: {}, - retCode: 0, - }; - }); - }); - const p = await peer.internals.createNewParticle(script); - await peer.internals.initiateParticle(p, doNothing); - const [nestedFirst, nestedSecond, outerFirst, outerSecond, outerFirstString, outerFirstParsed] = await promise; - - const nfExpected = { name: 'nested_first', num: 1 }; - const nsExpected = { name: 'nested_second', num: 2 }; - - const ofExpected = { name: 'outer_first', nested: nfExpected, num: 0 }; - const ofString = JSON.stringify(ofExpected); - const osExpected = { name: 'outer_second', num: 3, nested: nsExpected }; - - expect(nestedFirst).toMatchObject(nfExpected); - expect(nestedSecond).toMatchObject(nsExpected); - expect(outerFirst).toMatchObject(ofExpected); - expect(outerSecond).toMatchObject(osExpected); - expect(outerFirstParsed).toMatchObject(ofExpected); - expect(outerFirstString).toBe(ofString); + const promise = new Promise((resolve) => { + peer.internals.regHandler.common("res", "res", (req) => { + resolve(req.args); + return { + result: {}, + retCode: 0, + }; + }); }); + + const p = await peer.internals.createNewParticle(script); + peer.internals.initiateParticle(p, doNothing); + + const [ + nestedFirst, + nestedSecond, + outerFirst, + outerSecond, + outerFirstString, + outerFirstParsed, + ] = await promise; + + const nfExpected = { name: "nested_first", num: 1 }; + const nsExpected = { name: "nested_second", num: 2 }; + + const ofExpected = { name: "outer_first", nested: nfExpected, num: 0 }; + const ofString = JSON.stringify(ofExpected); + const osExpected = { name: "outer_second", num: 3, nested: nsExpected }; + + expect(nestedFirst).toMatchObject(nfExpected); + expect(nestedSecond).toMatchObject(nsExpected); + expect(outerFirst).toMatchObject(ofExpected); + expect(outerSecond).toMatchObject(osExpected); + expect(outerFirstParsed).toMatchObject(ofExpected); + expect(outerFirstString).toBe(ofString); + }); }); diff --git a/packages/core/js-client/src/services/__test__/sigService.spec.ts b/packages/core/js-client/src/services/__test__/sigService.spec.ts index c3a40b0f..c46ffffa 100644 --- a/packages/core/js-client/src/services/__test__/sigService.spec.ts +++ b/packages/core/js-client/src/services/__test__/sigService.spec.ts @@ -1,119 +1,177 @@ -import { it, describe, expect, beforeAll } from 'vitest'; +/** + * Copyright 2023 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 * as path from 'path'; -import * as url from 'url'; -import { KeyPair } from '../../keypair/index.js'; -import { allowServiceFn } from '../securityGuard.js'; -import { Sig } from '../Sig.js'; -import { registerService } from '../../compilerSupport/registerService.js'; -import { compileAqua, withPeer } from '../../util/testUtils.js'; +import * as path from "path"; +import * as url from "url"; -const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); +import { ServiceDef, ServiceImpl } from "@fluencelabs/interfaces"; +import { it, describe, expect, beforeAll } from "vitest"; -let aqua: any; -let sigDef: any; -let dataProviderDef: any; +import { registerService } from "../../compilerSupport/registerService.js"; +import { KeyPair } from "../../keypair/index.js"; +import { compileAqua, CompiledFnCall, withPeer } from "../../util/testUtils.js"; +import { allowServiceFn } from "../securityGuard.js"; +import { Sig } from "../Sig.js"; -describe('Sig service test suite', () => { - beforeAll(async () => { - const pathToAquaFiles = path.join(__dirname, '../../../aqua_test/sigService.aqua'); - const { services, functions } = await compileAqua(pathToAquaFiles); +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); - aqua = functions; - sigDef = services.Sig; - dataProviderDef = services.DataProvider; +let aqua: Record; +let sigDef: ServiceDef; +let dataProviderDef: ServiceDef; + +describe("Sig service test suite", () => { + beforeAll(async () => { + const pathToAquaFiles = path.join( + __dirname, + "../../../aqua_test/sigService.aqua", + ); + + const { services, functions } = await compileAqua(pathToAquaFiles); + + aqua = functions; + sigDef = services["Sig"]; + dataProviderDef = services["DataProvider"]; + }); + + it("Use custom sig service, success path", async () => { + await withPeer(async (peer) => { + const customKeyPair = await KeyPair.randomEd25519(); + const customSig = new Sig(customKeyPair); + const data = [1, 2, 3, 4, 5]; + + registerService({ + peer, + def: sigDef, + serviceId: "CustomSig", + // TODO: fix this after changing registerService signature + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + service: customSig as unknown as ServiceImpl, + }); + + registerService({ + peer, + def: dataProviderDef, + serviceId: "data", + service: { + provide_data: () => { + return data; + }, + }, + }); + + customSig.securityGuard = allowServiceFn("data", "provide_data"); + + const result = await aqua["callSig"](peer, { sigId: "CustomSig" }); + + expect(result).toHaveProperty("success", true); + + const isSigCorrect = await customSig.verify( + // TODO: Use compiled ts wrappers + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + (result as { signature: number[] }).signature, + data, + ); + + expect(isSigCorrect).toBe(true); }); + }); - it('Use custom sig service, success path', async () => { - await withPeer(async (peer) => { - const customKeyPair = await KeyPair.randomEd25519(); - const customSig = new Sig(customKeyPair); - const data = [1, 2, 3, 4, 5]; + it("Use custom sig service, fail path", async () => { + await withPeer(async (peer) => { + const customKeyPair = await KeyPair.randomEd25519(); + const customSig = new Sig(customKeyPair); + const data = [1, 2, 3, 4, 5]; - registerService({ peer, def: sigDef, serviceId: 'CustomSig', service: customSig }); + registerService({ + peer, + def: sigDef, + serviceId: "CustomSig", + // TODO: fix this after changing registerService signature + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + service: customSig as unknown as ServiceImpl, + }); - registerService({ - peer, - def: dataProviderDef, - serviceId: 'data', - service: { - provide_data: () => { - return data; - }, - }, - }); + registerService({ + peer, + def: dataProviderDef, + serviceId: "data", + service: { + provide_data: () => { + return data; + }, + }, + }); - customSig.securityGuard = allowServiceFn('data', 'provide_data'); + customSig.securityGuard = allowServiceFn("wrong", "wrong"); - const result = await aqua.callSig(peer, { sigId: 'CustomSig' }); - - expect(result.success).toBe(true); - const isSigCorrect = await customSig.verify(result.signature as number[], data); - expect(isSigCorrect).toBe(true); - }); + const result = await aqua["callSig"](peer, { sigId: "CustomSig" }); + expect(result).toHaveProperty("success", false); }); + }); - it('Use custom sig service, fail path', async () => { - await withPeer(async (peer) => { - const customKeyPair = await KeyPair.randomEd25519(); - const customSig = new Sig(customKeyPair); - const data = [1, 2, 3, 4, 5]; + it("Default sig service should be resolvable by peer id", async () => { + await withPeer(async (peer) => { + const sig = peer.internals.getServices().sig; - registerService({ peer, def: sigDef, serviceId: 'CustomSig', service: customSig }); + const data = [1, 2, 3, 4, 5]; - registerService({ - peer, - def: dataProviderDef, - serviceId: 'data', - service: { - provide_data: () => { - return data; - }, - }, - }); + registerService({ + peer: peer, + def: dataProviderDef, + serviceId: "data", + service: { + provide_data: () => { + return data; + }, + }, + }); - customSig.securityGuard = allowServiceFn('wrong', 'wrong'); + const callAsSigRes = await aqua["callSig"](peer, { sigId: "sig" }); - const result = await aqua.callSig(peer, { sigId: 'CustomSig' }); - expect(result.success).toBe(false); - }); - }); - - it('Default sig service should be resolvable by peer id', async () => { - await withPeer(async (peer) => { - const sig = peer.internals.getServices().sig; - - const data = [1, 2, 3, 4, 5]; - registerService({ - peer: peer, - def: dataProviderDef, - serviceId: 'data', - service: { - provide_data: () => { - return data; - }, - }, - }); - - const callAsSigRes = await aqua.callSig(peer, { sigId: 'sig' }); - const callAsPeerIdRes = await aqua.callSig(peer, { sigId: peer.keyPair.getPeerId() }); - - expect(callAsSigRes.success).toBe(false); - expect(callAsPeerIdRes.success).toBe(false); - - sig.securityGuard = () => true; - - const callAsSigResAfterGuardChange = await aqua.callSig(peer, { sigId: 'sig' }); - const callAsPeerIdResAfterGuardChange = await aqua.callSig(peer, { - sigId: peer.keyPair.getPeerId(), - }); - - expect(callAsSigResAfterGuardChange.success).toBe(true); - expect(callAsPeerIdResAfterGuardChange.success).toBe(true); - - const isValid = await sig.verify(callAsSigResAfterGuardChange.signature as number[], data); - - expect(isValid).toBe(true); - }); + const callAsPeerIdRes = await aqua["callSig"](peer, { + sigId: peer.keyPair.getPeerId(), + }); + + expect(callAsSigRes).toHaveProperty("success", false); + expect(callAsPeerIdRes).toHaveProperty("success", false); + + sig.securityGuard = () => { + return true; + }; + + const callAsSigResAfterGuardChange = await aqua["callSig"](peer, { + sigId: "sig", + }); + + const callAsPeerIdResAfterGuardChange = await aqua["callSig"](peer, { + sigId: peer.keyPair.getPeerId(), + }); + + expect(callAsSigResAfterGuardChange).toHaveProperty("success", true); + + expect(callAsPeerIdResAfterGuardChange).toHaveProperty("success", true); + + const isValid = await sig.verify( + // TODO: Use compiled ts wrappers + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + (callAsSigResAfterGuardChange as { signature: number[] }).signature, + data, + ); + + expect(isValid).toBe(true); }); + }); }); diff --git a/packages/core/js-client/src/services/__test__/srv.spec.ts b/packages/core/js-client/src/services/__test__/srv.spec.ts index 48c649c1..caaa3054 100644 --- a/packages/core/js-client/src/services/__test__/srv.spec.ts +++ b/packages/core/js-client/src/services/__test__/srv.spec.ts @@ -1,85 +1,111 @@ -import { it, describe, expect, beforeAll } from 'vitest'; -import * as path from 'path'; -import * as url from 'url'; -import { compileAqua, withPeer } from '../../util/testUtils.js'; -import { registerNodeUtils } from '../_aqua/node-utils.js'; -import { NodeUtils } from '../NodeUtils.js'; +/** + * Copyright 2023 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. + */ -const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); -let aqua: any; +import * as path from "path"; +import * as url from "url"; -describe('Srv service test suite', () => { - beforeAll(async () => { - const pathToAquaFiles = path.join(__dirname, '../../../aqua_test/srv.aqua'); - const { services, functions } = await compileAqua(pathToAquaFiles); - aqua = functions; +import { it, describe, expect, beforeAll } from "vitest"; + +import { compileAqua, CompiledFnCall, withPeer } from "../../util/testUtils.js"; +import { registerNodeUtils } from "../_aqua/node-utils.js"; +import { NodeUtils } from "../NodeUtils.js"; + +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); +let aqua: Record; + +describe("Srv service test suite", () => { + beforeAll(async () => { + const pathToAquaFiles = path.join(__dirname, "../../../aqua_test/srv.aqua"); + + const { functions } = await compileAqua(pathToAquaFiles); + aqua = functions; + }); + + it("Use custom srv service, success path", async () => { + await withPeer(async (peer) => { + // arrange + registerNodeUtils(peer, "node_utils", new NodeUtils(peer)); + + const wasm = path.join(__dirname, "../../../data_for_test/greeting.wasm"); + + // act + const res = await aqua["happy_path"](peer, { file_path: wasm }); + + // assert + expect(res).toBe("Hi, test"); }); + }); - it('Use custom srv service, success path', async () => { - await withPeer(async (peer) => { - // arrange - registerNodeUtils(peer, 'node_utils', new NodeUtils(peer)); - const wasm = path.join(__dirname, '../../../data_for_test/greeting.wasm'); + it("List deployed services", async () => { + await withPeer(async (peer) => { + // arrange + registerNodeUtils(peer, "node_utils", new NodeUtils(peer)); - // act - const res = await aqua.happy_path(peer, { file_path: wasm }); + const wasm = path.join(__dirname, "../../../data_for_test/greeting.wasm"); - // assert - expect(res).toBe('Hi, test'); - }); + // act + const res = await aqua["list_services"](peer, { file_path: wasm }); + + // assert + expect(res).toHaveLength(3); }); + }); - it('List deployed services', async () => { - await withPeer(async (peer) => { - // arrange - registerNodeUtils(peer, 'node_utils', new NodeUtils(peer)); - const wasm = path.join(__dirname, '../../../data_for_test/greeting.wasm'); + it("Correct error for removed services", async () => { + await withPeer(async (peer) => { + // arrange + registerNodeUtils(peer, "node_utils", new NodeUtils(peer)); - // act - const res = await aqua.list_services(peer, { file_path: wasm }); + const wasm = path.join(__dirname, "../../../data_for_test/greeting.wasm"); - // assert - expect(res).toHaveLength(3); - }); + // act + const res = await aqua["service_removed"](peer, { + file_path: wasm, + }); + + // assert + expect(res).toMatch("No service found for service call"); }); + }); - it('Correct error for removed services', async () => { - await withPeer(async (peer) => { - // arrange - registerNodeUtils(peer, 'node_utils', new NodeUtils(peer)); - const wasm = path.join(__dirname, '../../../data_for_test/greeting.wasm'); + it("Correct error for file not found", async () => { + await withPeer(async (peer) => { + // arrange + registerNodeUtils(peer, "node_utils", new NodeUtils(peer)); - // act - const res = await aqua.service_removed(peer, { file_path: wasm }); + // act + const res = await aqua["file_not_found"](peer, {}); - // assert - expect(res).toMatch('No service found for service call'); - }); + // assert + expect(res).toMatch( + "ENOENT: no such file or directory, open '/random/incorrect/file'", + ); }); + }); - it('Correct error for file not found', async () => { - await withPeer(async (peer) => { - // arrange - registerNodeUtils(peer, 'node_utils', new NodeUtils(peer)); + it("Correct error for removing non existing service", async () => { + await withPeer(async (peer) => { + // arrange + registerNodeUtils(peer, "node_utils", new NodeUtils(peer)); - // act - const res = await aqua.file_not_found(peer, {}); + // act + const res = await aqua["removing_non_exiting"](peer, {}); - // assert - expect(res).toMatch("ENOENT: no such file or directory, open '/random/incorrect/file'"); - }); - }); - - it('Correct error for removing non existing service', async () => { - await withPeer(async (peer) => { - // arrange - registerNodeUtils(peer, 'node_utils', new NodeUtils(peer)); - - // act - const res = await aqua.removing_non_exiting(peer, {}); - - // assert - expect(res).toMatch('Service with id random_id not found'); - }); + // assert + expect(res).toMatch("Service with id random_id not found"); }); + }); }); diff --git a/packages/core/js-client/src/services/_aqua/node-utils.ts b/packages/core/js-client/src/services/_aqua/node-utils.ts index d95db446..c9debf71 100644 --- a/packages/core/js-client/src/services/_aqua/node-utils.ts +++ b/packages/core/js-client/src/services/_aqua/node-utils.ts @@ -1,75 +1,104 @@ +/** + * Copyright 2023 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. + */ + /** * This compiled aqua file was modified to make it work in monorepo */ -import { CallParams, IFluenceInternalApi } from '@fluencelabs/interfaces'; -import { registerService } from '../../compilerSupport/registerService.js'; +import { CallParams, ServiceImpl } from "@fluencelabs/interfaces"; + +import { registerService } from "../../compilerSupport/registerService.js"; +import { FluencePeer } from "../../jsPeer/FluencePeer.js"; +import { NodeUtils } from "../NodeUtils.js"; // Services export interface NodeUtilsDef { - read_file: ( - path: string, - callParams: CallParams<'path'>, - ) => - | { content: string | null; error: string | null; success: boolean } - | Promise<{ content: string | null; error: string | null; success: boolean }>; + read_file: ( + path: string, + callParams: CallParams<"path">, + ) => + | { content: string | null; error: string | null; success: boolean } + | Promise<{ + content: string | null; + error: string | null; + success: boolean; + }>; } -export function registerNodeUtils(peer: IFluenceInternalApi, serviceId: string, service: any) { - registerService({ - peer, - service, - serviceId, - def: { - defaultServiceId: 'node_utils', - functions: { - tag: 'labeledProduct', - fields: { - read_file: { - tag: 'arrow', - domain: { - tag: 'labeledProduct', - fields: { - path: { - tag: 'scalar', - name: 'string', - }, - }, - }, - codomain: { - tag: 'unlabeledProduct', - items: [ - { - tag: 'struct', - name: 'ReadFileResult', - fields: { - content: { - tag: 'option', - type: { - tag: 'scalar', - name: 'string', - }, - }, - error: { - tag: 'option', - type: { - tag: 'scalar', - name: 'string', - }, - }, - success: { - tag: 'scalar', - name: 'bool', - }, - }, - }, - ], - }, - }, +export function registerNodeUtils( + peer: FluencePeer, + serviceId: string, + service: NodeUtils, +) { + registerService({ + peer, + // TODO: fix this after changing registerService signature + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + service: service as unknown as ServiceImpl, + serviceId, + def: { + defaultServiceId: "node_utils", + functions: { + tag: "labeledProduct", + fields: { + read_file: { + tag: "arrow", + domain: { + tag: "labeledProduct", + fields: { + path: { + tag: "scalar", + name: "string", }, + }, }, + codomain: { + tag: "unlabeledProduct", + items: [ + { + tag: "struct", + name: "ReadFileResult", + fields: { + content: { + tag: "option", + type: { + tag: "scalar", + name: "string", + }, + }, + error: { + tag: "option", + type: { + tag: "scalar", + name: "string", + }, + }, + success: { + tag: "scalar", + name: "bool", + }, + }, + }, + ], + }, + }, }, - }); + }, + }, + }); } // Functions diff --git a/packages/core/js-client/src/services/_aqua/services.ts b/packages/core/js-client/src/services/_aqua/services.ts index c99b978a..896ad0a8 100644 --- a/packages/core/js-client/src/services/_aqua/services.ts +++ b/packages/core/js-client/src/services/_aqua/services.ts @@ -1,133 +1,162 @@ +/** + * Copyright 2023 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. + */ + /** * This compiled aqua file was modified to make it work in monorepo */ -import { CallParams, IFluenceInternalApi } from '@fluencelabs/interfaces'; -import { registerService } from '../../compilerSupport/registerService.js'; +import { CallParams, ServiceImpl } from "@fluencelabs/interfaces"; + +import { registerService } from "../../compilerSupport/registerService.js"; +import { FluencePeer } from "../../jsPeer/FluencePeer.js"; +import { Sig } from "../Sig.js"; // Services export interface SigDef { - get_peer_id: (callParams: CallParams) => string | Promise; - sign: ( - data: number[], - callParams: CallParams<'data'>, - ) => - | { error: string | null; signature: number[] | null; success: boolean } - | Promise<{ error: string | null; signature: number[] | null; success: boolean }>; - verify: ( - signature: number[], - data: number[], - callParams: CallParams<'signature' | 'data'>, - ) => boolean | Promise; + get_peer_id: (callParams: CallParams) => string | Promise; + sign: ( + data: number[], + callParams: CallParams<"data">, + ) => + | { error: string | null; signature: number[] | null; success: boolean } + | Promise<{ + error: string | null; + signature: number[] | null; + success: boolean; + }>; + verify: ( + signature: number[], + data: number[], + callParams: CallParams<"signature" | "data">, + ) => boolean | Promise; } -export function registerSig(peer: IFluenceInternalApi, serviceId: string, service: any) { - registerService({ - peer, - service, - serviceId, - def: { - defaultServiceId: 'sig', - functions: { - tag: 'labeledProduct', - fields: { - get_peer_id: { - tag: 'arrow', - domain: { - tag: 'nil', - }, - codomain: { - tag: 'unlabeledProduct', - items: [ - { - tag: 'scalar', - name: 'string', - }, - ], - }, - }, - sign: { - tag: 'arrow', - domain: { - tag: 'labeledProduct', - fields: { - data: { - tag: 'array', - type: { - tag: 'scalar', - name: 'u8', - }, - }, - }, - }, - codomain: { - tag: 'unlabeledProduct', - items: [ - { - tag: 'struct', - name: 'SignResult', - fields: { - error: { - tag: 'option', - type: { - tag: 'scalar', - name: 'string', - }, - }, - signature: { - tag: 'option', - type: { - tag: 'array', - type: { - tag: 'scalar', - name: 'u8', - }, - }, - }, - success: { - tag: 'scalar', - name: 'bool', - }, - }, - }, - ], - }, - }, - verify: { - tag: 'arrow', - domain: { - tag: 'labeledProduct', - fields: { - signature: { - tag: 'array', - type: { - tag: 'scalar', - name: 'u8', - }, - }, - data: { - tag: 'array', - type: { - tag: 'scalar', - name: 'u8', - }, - }, - }, - }, - codomain: { - tag: 'unlabeledProduct', - items: [ - { - tag: 'scalar', - name: 'bool', - }, - ], - }, - }, - }, +export function registerSig( + peer: FluencePeer, + serviceId: string, + service: Sig, +) { + registerService({ + peer, + // TODO: fix this after changing registerService signature + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + service: service as unknown as ServiceImpl, + serviceId, + def: { + defaultServiceId: "sig", + functions: { + tag: "labeledProduct", + fields: { + get_peer_id: { + tag: "arrow", + domain: { + tag: "nil", }, + codomain: { + tag: "unlabeledProduct", + items: [ + { + tag: "scalar", + name: "string", + }, + ], + }, + }, + sign: { + tag: "arrow", + domain: { + tag: "labeledProduct", + fields: { + data: { + tag: "array", + type: { + tag: "scalar", + name: "u8", + }, + }, + }, + }, + codomain: { + tag: "unlabeledProduct", + items: [ + { + tag: "struct", + name: "SignResult", + fields: { + error: { + tag: "option", + type: { + tag: "scalar", + name: "string", + }, + }, + signature: { + tag: "option", + type: { + tag: "array", + type: { + tag: "scalar", + name: "u8", + }, + }, + }, + success: { + tag: "scalar", + name: "bool", + }, + }, + }, + ], + }, + }, + verify: { + tag: "arrow", + domain: { + tag: "labeledProduct", + fields: { + signature: { + tag: "array", + type: { + tag: "scalar", + name: "u8", + }, + }, + data: { + tag: "array", + type: { + tag: "scalar", + name: "u8", + }, + }, + }, + }, + codomain: { + tag: "unlabeledProduct", + items: [ + { + tag: "scalar", + name: "bool", + }, + ], + }, + }, }, - }); + }, + }, + }); } // Functions diff --git a/packages/core/js-client/src/services/_aqua/single-module-srv.ts b/packages/core/js-client/src/services/_aqua/single-module-srv.ts index b97e6656..b82665ba 100644 --- a/packages/core/js-client/src/services/_aqua/single-module-srv.ts +++ b/packages/core/js-client/src/services/_aqua/single-module-srv.ts @@ -1,132 +1,163 @@ +/** + * Copyright 2023 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. + */ + /** * This compiled aqua file was modified to make it work in monorepo */ -import { CallParams, IFluenceInternalApi } from '@fluencelabs/interfaces'; -import { registerService } from '../../compilerSupport/registerService.js'; +import { CallParams, ServiceImpl } from "@fluencelabs/interfaces"; + +import { registerService } from "../../compilerSupport/registerService.js"; +import { FluencePeer } from "../../jsPeer/FluencePeer.js"; +import { Srv } from "../SingleModuleSrv.js"; // Services export interface SrvDef { - create: ( - wasm_b64_content: string, - callParams: CallParams<'wasm_b64_content'>, - ) => - | { error: string | null; service_id: string | null; success: boolean } - | Promise<{ error: string | null; service_id: string | null; success: boolean }>; - list: (callParams: CallParams) => string[] | Promise; - remove: ( - service_id: string, - callParams: CallParams<'service_id'>, - ) => { error: string | null; success: boolean } | Promise<{ error: string | null; success: boolean }>; + create: ( + wasm_b64_content: string, + callParams: CallParams<"wasm_b64_content">, + ) => + | { error: string | null; service_id: string | null; success: boolean } + | Promise<{ + error: string | null; + service_id: string | null; + success: boolean; + }>; + list: (callParams: CallParams) => string[] | Promise; + remove: ( + service_id: string, + callParams: CallParams<"service_id">, + ) => + | { error: string | null; success: boolean } + | Promise<{ error: string | null; success: boolean }>; } -export function registerSrv(peer: IFluenceInternalApi, serviceId: string, service: any) { - registerService({ - peer, - serviceId, - service, - def: { - defaultServiceId: 'single_module_srv', - functions: { - tag: 'labeledProduct', - fields: { - create: { - tag: 'arrow', - domain: { - tag: 'labeledProduct', - fields: { - wasm_b64_content: { - tag: 'scalar', - name: 'string', - }, - }, - }, - codomain: { - tag: 'unlabeledProduct', - items: [ - { - tag: 'struct', - name: 'ServiceCreationResult', - fields: { - error: { - tag: 'option', - type: { - tag: 'scalar', - name: 'string', - }, - }, - service_id: { - tag: 'option', - type: { - tag: 'scalar', - name: 'string', - }, - }, - success: { - tag: 'scalar', - name: 'bool', - }, - }, - }, - ], - }, - }, - list: { - tag: 'arrow', - domain: { - tag: 'nil', - }, - codomain: { - tag: 'unlabeledProduct', - items: [ - { - tag: 'array', - type: { - tag: 'scalar', - name: 'string', - }, - }, - ], - }, - }, - remove: { - tag: 'arrow', - domain: { - tag: 'labeledProduct', - fields: { - service_id: { - tag: 'scalar', - name: 'string', - }, - }, - }, - codomain: { - tag: 'unlabeledProduct', - items: [ - { - tag: 'struct', - name: 'RemoveResult', - fields: { - error: { - tag: 'option', - type: { - tag: 'scalar', - name: 'string', - }, - }, - success: { - tag: 'scalar', - name: 'bool', - }, - }, - }, - ], - }, - }, +export function registerSrv( + peer: FluencePeer, + serviceId: string, + service: Srv, +) { + registerService({ + peer, + serviceId, + // TODO: fix this after changing registerService signature + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + service: service as unknown as ServiceImpl, + def: { + defaultServiceId: "single_module_srv", + functions: { + tag: "labeledProduct", + fields: { + create: { + tag: "arrow", + domain: { + tag: "labeledProduct", + fields: { + wasm_b64_content: { + tag: "scalar", + name: "string", }, + }, }, + codomain: { + tag: "unlabeledProduct", + items: [ + { + tag: "struct", + name: "ServiceCreationResult", + fields: { + error: { + tag: "option", + type: { + tag: "scalar", + name: "string", + }, + }, + service_id: { + tag: "option", + type: { + tag: "scalar", + name: "string", + }, + }, + success: { + tag: "scalar", + name: "bool", + }, + }, + }, + ], + }, + }, + list: { + tag: "arrow", + domain: { + tag: "nil", + }, + codomain: { + tag: "unlabeledProduct", + items: [ + { + tag: "array", + type: { + tag: "scalar", + name: "string", + }, + }, + ], + }, + }, + remove: { + tag: "arrow", + domain: { + tag: "labeledProduct", + fields: { + service_id: { + tag: "scalar", + name: "string", + }, + }, + }, + codomain: { + tag: "unlabeledProduct", + items: [ + { + tag: "struct", + name: "RemoveResult", + fields: { + error: { + tag: "option", + type: { + tag: "scalar", + name: "string", + }, + }, + success: { + tag: "scalar", + name: "bool", + }, + }, + }, + ], + }, + }, }, - }); + }, + }, + }); } // Functions diff --git a/packages/core/js-client/src/services/_aqua/tracing.ts b/packages/core/js-client/src/services/_aqua/tracing.ts index 405326b9..b87c8835 100644 --- a/packages/core/js-client/src/services/_aqua/tracing.ts +++ b/packages/core/js-client/src/services/_aqua/tracing.ts @@ -1,52 +1,77 @@ +/** + * Copyright 2023 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. + */ + /** * This compiled aqua file was modified to make it work in monorepo */ -import { CallParams, IFluenceInternalApi } from '@fluencelabs/interfaces'; -import { registerService } from '../../compilerSupport/registerService.js'; +import { CallParams, ServiceImpl } from "@fluencelabs/interfaces"; + +import { registerService } from "../../compilerSupport/registerService.js"; +import { FluencePeer } from "../../jsPeer/FluencePeer.js"; +import { Tracing } from "../Tracing.js"; // Services export interface TracingDef { - tracingEvent: ( - arrowName: string, - event: string, - callParams: CallParams<'arrowName' | 'event'>, - ) => void | Promise; + tracingEvent: ( + arrowName: string, + event: string, + callParams: CallParams<"arrowName" | "event">, + ) => void | Promise; } -export function registerTracing(peer: IFluenceInternalApi, serviceId: string, service: any) { - registerService({ - peer, - serviceId, - service, - def: { - defaultServiceId: 'tracingSrv', - functions: { - tag: 'labeledProduct', - fields: { - tracingEvent: { - tag: 'arrow', - domain: { - tag: 'labeledProduct', - fields: { - arrowName: { - tag: 'scalar', - name: 'string', - }, - event: { - tag: 'scalar', - name: 'string', - }, - }, - }, - codomain: { - tag: 'nil', - }, - }, +export function registerTracing( + peer: FluencePeer, + serviceId: string, + service: Tracing, +) { + registerService({ + peer, + serviceId, + // TODO: fix this after changing registerService signature + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + service: service as unknown as ServiceImpl, + def: { + defaultServiceId: "tracingSrv", + functions: { + tag: "labeledProduct", + fields: { + tracingEvent: { + tag: "arrow", + domain: { + tag: "labeledProduct", + fields: { + arrowName: { + tag: "scalar", + name: "string", }, + event: { + tag: "scalar", + name: "string", + }, + }, }, + codomain: { + tag: "nil", + }, + }, }, - }); + }, + }, + }); } // Functions diff --git a/packages/core/js-client/src/services/builtins.ts b/packages/core/js-client/src/services/builtins.ts index bdbf80c7..7ee3f68d 100644 --- a/packages/core/js-client/src/services/builtins.ts +++ b/packages/core/js-client/src/services/builtins.ts @@ -1,5 +1,5 @@ -/* - * Copyright 2021 Fluence Labs Limited +/** + * Copyright 2023 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. @@ -14,591 +14,794 @@ * limitations under the License. */ -import * as bs58 from 'bs58'; +import assert from "assert"; +import { Buffer } from "buffer"; -import { sha256 } from 'multiformats/hashes/sha2'; -import { CallServiceResult } from '@fluencelabs/avm'; +import { JSONValue } from "@fluencelabs/interfaces"; +import bs58 from "bs58"; +import { sha256 } from "multiformats/hashes/sha2"; -import { isString, jsonify } from '../util/utils.js'; -import { Buffer } from 'buffer'; -import { GenericCallServiceHandler, ResultCodes } from '../jsServiceHost/interfaces.js'; +import { + CallServiceResult, + CallServiceResultType, + GenericCallServiceHandler, + ResultCodes, +} from "../jsServiceHost/interfaces.js"; +import { getErrorMessage, isString, jsonify } from "../util/utils.js"; -//@ts-ignore -const { encode, decode } = bs58.default; - -const success = (result: any): CallServiceResult => { - return { - result: result, - retCode: ResultCodes.success, - }; +const success = ( + // TODO: Remove unknown after adding validation to builtin inputs + // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents + result: CallServiceResultType | unknown, +): CallServiceResult => { + return { + // TODO: Remove type assertion after adding validation to builtin inputs + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + result: result as CallServiceResultType, + retCode: ResultCodes.success, + }; }; -const error = (error: string): CallServiceResult => { - return { - result: error, - retCode: ResultCodes.error, - }; +const error = ( + // TODO: Remove unknown after adding validation to builtin inputs + // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents + error: CallServiceResultType | unknown, +): CallServiceResult => { + return { + // TODO: Remove type assertion after adding validation to builtin inputs + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + result: error as CallServiceResultType, + retCode: ResultCodes.error, + }; }; const errorNotImpl = (methodName: string) => { - return error(`The JS implementation of Peer does not support "${methodName}"`); + return error( + `The JS implementation of Peer does not support "${methodName}"`, + ); }; -const makeJsonImpl = (args: Array) => { - const [obj, ...kvs] = args; +const makeJsonImpl = (args: [Record, ...JSONValue[]]) => { + const [obj, ...kvs] = args; - const toMerge: Record = {}; - for (let i = 0; i < kvs.length / 2; i++) { - const k = kvs[i * 2]; - if (!isString(k)) { - return error(`Argument ${k} is expected to be string`); - } - const v = kvs[i * 2 + 1]; - toMerge[k] = v; + const toMerge: Record = {}; + + for (let i = 0; i < kvs.length / 2; i++) { + const k = kvs[i * 2]; + + if (!isString(k)) { + return error(`Argument ${i * 2 + 1} is expected to be string`); } - const res = { ...obj, ...toMerge }; - return success(res); + const v = kvs[i * 2 + 1]; + toMerge[k] = v; + } + + const res = { ...obj, ...toMerge }; + return success(res); }; -export const builtInServices: Record> = { - peer: { - identify: () => { - return success({ - external_addresses: [], - // TODO: remove hardcoded values - node_version: 'js-0.23.0', - air_version: 'js-0.24.2', - }); - }, - - timestamp_ms: () => { - return success(Date.now()); - }, - - timestamp_sec: () => { - return success(Math.floor(Date.now() / 1000)); - }, - - is_connected: () => { - return errorNotImpl('peer.is_connected'); - }, - - connect: () => { - return errorNotImpl('peer.connect'); - }, - - get_contact: () => { - return errorNotImpl('peer.get_contact'); - }, - - timeout: (req) => { - if (req.args.length !== 2) { - return error('timeout accepts exactly two arguments: timeout duration in ms and a message string'); - } - const durationMs = req.args[0]; - const message = req.args[1]; - - return new Promise((resolve) => { - setTimeout(() => { - const res = success(message); - resolve(res); - }, durationMs); - }); - }, +// TODO: These assert made for silencing more stricter ts rules. Will be fixed in DXJ-493 +export const builtInServices: Record< + string, + Record +> = { + peer: { + identify: () => { + return success({ + external_addresses: [], + // TODO: remove hardcoded values + node_version: "js-0.23.0", + air_version: "js-0.24.2", + }); }, - kad: { - neighborhood: () => { - return errorNotImpl('kad.neighborhood'); - }, - - merge: () => { - return errorNotImpl('kad.merge'); - }, + timestamp_ms: () => { + return success(Date.now()); }, - srv: { - list: () => { - return errorNotImpl('srv.list'); - }, - - create: () => { - return errorNotImpl('srv.create'); - }, - - get_interface: () => { - return errorNotImpl('srv.get_interface'); - }, - - resolve_alias: () => { - return errorNotImpl('srv.resolve_alias'); - }, - - add_alias: () => { - return errorNotImpl('srv.add_alias'); - }, - - remove: () => { - return errorNotImpl('srv.remove'); - }, + timestamp_sec: () => { + return success(Math.floor(Date.now() / 1000)); }, - dist: { - add_module_from_vault: () => { - return errorNotImpl('dist.add_module_from_vault'); - }, - - add_module: () => { - return errorNotImpl('dist.add_module'); - }, - - add_blueprint: () => { - return errorNotImpl('dist.add_blueprint'); - }, - - make_module_config: () => { - return errorNotImpl('dist.make_module_config'); - }, - - load_module_config: () => { - return errorNotImpl('dist.load_module_config'); - }, - - default_module_config: () => { - return errorNotImpl('dist.default_module_config'); - }, - - make_blueprint: () => { - return errorNotImpl('dist.make_blueprint'); - }, - - load_blueprint: () => { - return errorNotImpl('dist.load_blueprint'); - }, - - list_modules: () => { - return errorNotImpl('dist.list_modules'); - }, - - get_module_interface: () => { - return errorNotImpl('dist.get_module_interface'); - }, - - list_blueprints: () => { - return errorNotImpl('dist.list_blueprints'); - }, + is_connected: () => { + return errorNotImpl("peer.is_connected"); }, - script: { - add: () => { - return errorNotImpl('script.add'); - }, - - remove: () => { - return errorNotImpl('script.remove'); - }, - - list: () => { - return errorNotImpl('script.list'); - }, + connect: () => { + return errorNotImpl("peer.connect"); }, - op: { - noop: () => { - return success({}); - }, - - array: (req) => { - return success(req.args); - }, - - array_length: (req) => { - if (req.args.length !== 1) { - return error('array_length accepts exactly one argument, found: ' + req.args.length); - } else { - return success(req.args[0].length); - } - }, - - identity: (req) => { - if (req.args.length > 1) { - return error(`identity accepts up to 1 arguments, received ${req.args.length} arguments`); - } else { - return success(req.args.length === 0 ? {} : req.args[0]); - } - }, - - concat: (req) => { - const incorrectArgIndices = req.args // - .map((x, i) => [Array.isArray(x), i]) - .filter(([isArray, _]) => !isArray) - .map(([_, index]) => index); - - if (incorrectArgIndices.length > 0) { - const str = incorrectArgIndices.join(', '); - return error(`All arguments of 'concat' must be arrays: arguments ${str} are not`); - } else { - return success([].concat.apply([], req.args)); - } - }, - - string_to_b58: (req) => { - if (req.args.length !== 1) { - return error('string_to_b58 accepts only one string argument'); - } else { - return success(encode(new TextEncoder().encode(req.args[0]))); - } - }, - - string_from_b58: (req) => { - if (req.args.length !== 1) { - return error('string_from_b58 accepts only one string argument'); - } else { - return success(new TextDecoder().decode(decode(req.args[0]))); - } - }, - - bytes_to_b58: (req) => { - if (req.args.length !== 1 || !Array.isArray(req.args[0])) { - return error('bytes_to_b58 accepts only single argument: array of numbers'); - } else { - const argumentArray = req.args[0] as number[]; - return success(encode(new Uint8Array(argumentArray))); - } - }, - - bytes_from_b58: (req) => { - if (req.args.length !== 1) { - return error('bytes_from_b58 accepts only one string argument'); - } else { - return success(Array.from(decode(req.args[0]))); - } - }, - - sha256_string: async (req) => { - if (req.args.length < 1 || req.args.length > 3) { - return error(`sha256_string accepts 1-3 arguments, found: ${req.args.length}`); - } else { - const [input, digestOnly, asBytes] = req.args; - const inBuffer = Buffer.from(input); - const multihash = await sha256.digest(inBuffer); - - const outBytes = digestOnly ? multihash.digest : multihash.bytes; - const res = asBytes ? Array.from(outBytes) : encode(outBytes); - - return success(res); - } - }, - - concat_strings: (req) => { - const res = ''.concat(...req.args); - return success(res); - }, + get_contact: () => { + return errorNotImpl("peer.get_contact"); }, - debug: { - stringify: (req) => { - let out; + timeout: (req) => { + if (req.args.length !== 2) { + return error( + "timeout accepts exactly two arguments: timeout duration in ms and a message string", + ); + } - if (req.args.length === 0) { - out = ''; - } else if (req.args.length === 1) { - out = req.args[0]; - } else { - out = req.args; - } + const durationMs = req.args[0]; + const message = req.args[1]; - return success(jsonify(out)); - }, + if (typeof durationMs !== "number" || typeof message !== "string") { + return error( + "timeout accepts exactly two arguments: timeout duration in ms and a message string", + ); + } + + return new Promise((resolve) => { + setTimeout(() => { + const res = success(message); + resolve(res); + }, durationMs); + }); + }, + }, + + kad: { + neighborhood: () => { + return errorNotImpl("kad.neighborhood"); }, - math: { - add: (req) => { - let err; - if ((err = checkForArgumentsCount(req, 2))) { - return err; - } - const [x, y] = req.args; - return success(x + y); - }, + merge: () => { + return errorNotImpl("kad.merge"); + }, + }, - sub: (req) => { - let err; - if ((err = checkForArgumentsCount(req, 2))) { - return err; - } - const [x, y] = req.args; - return success(x - y); - }, - - mul: (req) => { - let err; - if ((err = checkForArgumentsCount(req, 2))) { - return err; - } - const [x, y] = req.args; - return success(x * y); - }, - - fmul: (req) => { - let err; - if ((err = checkForArgumentsCount(req, 2))) { - return err; - } - const [x, y] = req.args; - return success(Math.floor(x * y)); - }, - - div: (req) => { - let err; - if ((err = checkForArgumentsCount(req, 2))) { - return err; - } - const [x, y] = req.args; - return success(Math.floor(x / y)); - }, - - rem: (req) => { - let err; - if ((err = checkForArgumentsCount(req, 2))) { - return err; - } - const [x, y] = req.args; - return success(x % y); - }, - - pow: (req) => { - let err; - if ((err = checkForArgumentsCount(req, 2))) { - return err; - } - const [x, y] = req.args; - return success(Math.pow(x, y)); - }, - - log: (req) => { - let err; - if ((err = checkForArgumentsCount(req, 2))) { - return err; - } - const [x, y] = req.args; - return success(Math.log(y) / Math.log(x)); - }, + srv: { + list: () => { + return errorNotImpl("srv.list"); }, - cmp: { - gt: (req) => { - let err; - if ((err = checkForArgumentsCount(req, 2))) { - return err; - } - const [x, y] = req.args; - return success(x > y); - }, - - gte: (req) => { - let err; - if ((err = checkForArgumentsCount(req, 2))) { - return err; - } - const [x, y] = req.args; - return success(x >= y); - }, - - lt: (req) => { - let err; - if ((err = checkForArgumentsCount(req, 2))) { - return err; - } - const [x, y] = req.args; - return success(x < y); - }, - - lte: (req) => { - let err; - if ((err = checkForArgumentsCount(req, 2))) { - return err; - } - const [x, y] = req.args; - return success(x <= y); - }, - - cmp: (req) => { - let err; - if ((err = checkForArgumentsCount(req, 2))) { - return err; - } - const [x, y] = req.args; - return success(x === y ? 0 : x > y ? 1 : -1); - }, + create: () => { + return errorNotImpl("srv.create"); }, - array: { - sum: (req) => { - let err; - if ((err = checkForArgumentsCount(req, 1))) { - return err; - } - const [xs] = req.args; - return success(xs.reduce((agg: any, cur: any) => agg + cur, 0)); - }, - - dedup: (req) => { - let err; - if ((err = checkForArgumentsCount(req, 1))) { - return err; - } - const [xs] = req.args; - const set = new Set(xs); - return success(Array.from(set)); - }, - - intersect: (req) => { - let err; - if ((err = checkForArgumentsCount(req, 2))) { - return err; - } - const [xs, ys] = req.args; - const intersection = xs.filter((x: any) => ys.includes(x)); - return success(intersection); - }, - - diff: (req) => { - let err; - if ((err = checkForArgumentsCount(req, 2))) { - return err; - } - const [xs, ys] = req.args; - const diff = xs.filter((x: unknown) => !ys.includes(x)); - return success(diff); - }, - - sdiff: (req) => { - let err; - if ((err = checkForArgumentsCount(req, 2))) { - return err; - } - const [xs, ys] = req.args; - const sdiff = [ - // force new line - ...xs.filter((y: unknown) => !ys.includes(y)), - ...ys.filter((x: unknown) => !xs.includes(x)), - ]; - return success(sdiff); - }, + get_interface: () => { + return errorNotImpl("srv.get_interface"); }, - json: { - obj: (req) => { - let err; - if ((err = checkForArgumentsCountEven(req, 1))) { - return err; - } - - return makeJsonImpl([{}, ...req.args]); - }, - - put: (req) => { - let err; - if ((err = checkForArgumentsCount(req, 3))) { - return err; - } - - if ((err = checkForArgumentType(req, 0, 'object'))) { - return err; - } - - return makeJsonImpl(req.args); - }, - - puts: (req) => { - let err; - if ((err = checkForArgumentsCountOdd(req, 1))) { - return err; - } - - if ((err = checkForArgumentsCountMoreThan(req, 3))) { - return err; - } - - if ((err = checkForArgumentType(req, 0, 'object'))) { - return err; - } - - return makeJsonImpl(req.args); - }, - - stringify: (req) => { - let err; - if ((err = checkForArgumentsCount(req, 1))) { - return err; - } - - if ((err = checkForArgumentType(req, 0, 'object'))) { - return err; - } - - const [json] = req.args; - const res = JSON.stringify(json); - return success(res); - }, - - parse: (req) => { - let err; - if ((err = checkForArgumentsCount(req, 1))) { - return err; - } - - if ((err = checkForArgumentType(req, 0, 'string'))) { - return err; - } - - const [raw] = req.args; - try { - const json = JSON.parse(raw); - return success(json); - } catch (err: any) { - return error(err.message); - } - }, + resolve_alias: () => { + return errorNotImpl("srv.resolve_alias"); }, - 'run-console': { - print: (req) => { - console.log(...req.args); - return success({}); - }, + add_alias: () => { + return errorNotImpl("srv.add_alias"); }, + + remove: () => { + return errorNotImpl("srv.remove"); + }, + }, + + dist: { + add_module_from_vault: () => { + return errorNotImpl("dist.add_module_from_vault"); + }, + + add_module: () => { + return errorNotImpl("dist.add_module"); + }, + + add_blueprint: () => { + return errorNotImpl("dist.add_blueprint"); + }, + + make_module_config: () => { + return errorNotImpl("dist.make_module_config"); + }, + + load_module_config: () => { + return errorNotImpl("dist.load_module_config"); + }, + + default_module_config: () => { + return errorNotImpl("dist.default_module_config"); + }, + + make_blueprint: () => { + return errorNotImpl("dist.make_blueprint"); + }, + + load_blueprint: () => { + return errorNotImpl("dist.load_blueprint"); + }, + + list_modules: () => { + return errorNotImpl("dist.list_modules"); + }, + + get_module_interface: () => { + return errorNotImpl("dist.get_module_interface"); + }, + + list_blueprints: () => { + return errorNotImpl("dist.list_blueprints"); + }, + }, + + script: { + add: () => { + return errorNotImpl("script.add"); + }, + + remove: () => { + return errorNotImpl("script.remove"); + }, + + list: () => { + return errorNotImpl("script.list"); + }, + }, + + op: { + noop: () => { + return success({}); + }, + + array: (req) => { + return success(req.args); + }, + + array_length: (req) => { + if (req.args.length !== 1) { + return error( + "array_length accepts exactly one argument, found: " + + req.args.length, + ); + } else { + assert(Array.isArray(req.args[0])); + return success(req.args[0].length); + } + }, + + identity: (req) => { + if (req.args.length > 1) { + return error( + `identity accepts up to 1 arguments, received ${req.args.length} arguments`, + ); + } else { + return success(req.args.length === 0 ? {} : req.args[0]); + } + }, + + concat: (req) => { + const incorrectArgIndices = req.args // + .map((x, i): [boolean, number] => { + return [Array.isArray(x), i]; + }) + .filter(([isArray]) => { + return !isArray; + }) + .map(([, index]) => { + return index; + }); + + if (incorrectArgIndices.length > 0) { + const str = incorrectArgIndices.join(", "); + return error( + `All arguments of 'concat' must be arrays: arguments ${str} are not`, + ); + } else { + // TODO: remove after adding validation + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return success([].concat(...(req.args as never[][]))); + } + }, + + string_to_b58: (req) => { + if (req.args.length !== 1) { + return error("string_to_b58 accepts only one string argument"); + } else { + const [input] = req.args; + // TODO: remove after adding validation + assert(typeof input === "string"); + return success(bs58.encode(new TextEncoder().encode(input))); + } + }, + + string_from_b58: (req) => { + if (req.args.length !== 1) { + return error("string_from_b58 accepts only one string argument"); + } else { + const [input] = req.args; + // TODO: remove after adding validation + assert(typeof input === "string"); + return success(new TextDecoder().decode(bs58.decode(input))); + } + }, + + bytes_to_b58: (req) => { + if (req.args.length !== 1 || !Array.isArray(req.args[0])) { + return error( + "bytes_to_b58 accepts only single argument: array of numbers", + ); + } else { + // TODO: remove after adding validation + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const argumentArray = req.args[0] as number[]; + return success(bs58.encode(new Uint8Array(argumentArray))); + } + }, + + bytes_from_b58: (req) => { + if (req.args.length !== 1) { + return error("bytes_from_b58 accepts only one string argument"); + } else { + const [input] = req.args; + // TODO: remove after adding validation + assert(typeof input === "string"); + return success(Array.from(bs58.decode(input))); + } + }, + + sha256_string: async (req) => { + if (req.args.length !== 1) { + return error( + `sha256_string accepts 1 argument, found: ${req.args.length}`, + ); + } else { + const [input] = req.args; + // TODO: remove after adding validation + assert(typeof input === "string"); + const inBuffer = Buffer.from(input); + const multihash = await sha256.digest(inBuffer); + + return success(bs58.encode(multihash.bytes)); + } + }, + + concat_strings: (req) => { + // TODO: remove after adding validation + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const res = "".concat(...(req.args as string[])); + return success(res); + }, + }, + + debug: { + stringify: (req) => { + let out; + + if (req.args.length === 0) { + out = ""; + } else if (req.args.length === 1) { + out = req.args[0]; + } else { + out = req.args; + } + + return success(jsonify(out)); + }, + }, + + math: { + add: (req) => { + let err; + + if ((err = checkForArgumentsCount(req, 2)) != null) { + return err; + } + + const [x, y] = req.args; + // TODO: Remove after adding validation + assert(typeof x === "number" && typeof y === "number"); + return success(x + y); + }, + + sub: (req) => { + let err; + + if ((err = checkForArgumentsCount(req, 2)) != null) { + return err; + } + + const [x, y] = req.args; + // TODO: Remove after adding validation + assert(typeof x === "number" && typeof y === "number"); + return success(x - y); + }, + + mul: (req) => { + let err; + + if ((err = checkForArgumentsCount(req, 2)) != null) { + return err; + } + + const [x, y] = req.args; + // TODO: Remove after adding validation + assert(typeof x === "number" && typeof y === "number"); + return success(x * y); + }, + + fmul: (req) => { + let err; + + if ((err = checkForArgumentsCount(req, 2)) != null) { + return err; + } + + const [x, y] = req.args; + // TODO: Remove after adding validation + assert(typeof x === "number" && typeof y === "number"); + return success(Math.floor(x * y)); + }, + + div: (req) => { + let err; + + if ((err = checkForArgumentsCount(req, 2)) != null) { + return err; + } + + const [x, y] = req.args; + // TODO: Remove after adding validation + assert(typeof x === "number" && typeof y === "number"); + return success(Math.floor(x / y)); + }, + + rem: (req) => { + let err; + + if ((err = checkForArgumentsCount(req, 2)) != null) { + return err; + } + + const [x, y] = req.args; + // TODO: Remove after adding validation + assert(typeof x === "number" && typeof y === "number"); + return success(x % y); + }, + + pow: (req) => { + let err; + + if ((err = checkForArgumentsCount(req, 2)) != null) { + return err; + } + + const [x, y] = req.args; + // TODO: Remove after adding validation + assert(typeof x === "number" && typeof y === "number"); + return success(Math.pow(x, y)); + }, + + log: (req) => { + let err; + + if ((err = checkForArgumentsCount(req, 2)) != null) { + return err; + } + + const [x, y] = req.args; + // TODO: Remove after adding validation + assert(typeof x === "number" && typeof y === "number"); + return success(Math.log(y) / Math.log(x)); + }, + }, + + cmp: { + gt: (req) => { + let err; + + if ((err = checkForArgumentsCount(req, 2)) != null) { + return err; + } + + const [x, y] = req.args; + // TODO: Remove after adding validation + assert(typeof x === "number" && typeof y === "number"); + return success(x > y); + }, + + gte: (req) => { + let err; + + if ((err = checkForArgumentsCount(req, 2)) != null) { + return err; + } + + const [x, y] = req.args; + // TODO: Remove after adding validation + assert(typeof x === "number" && typeof y === "number"); + return success(x >= y); + }, + + lt: (req) => { + let err; + + if ((err = checkForArgumentsCount(req, 2)) != null) { + return err; + } + + const [x, y] = req.args; + // TODO: Remove after adding validation + assert(typeof x === "number" && typeof y === "number"); + return success(x < y); + }, + + lte: (req) => { + let err; + + if ((err = checkForArgumentsCount(req, 2)) != null) { + return err; + } + + const [x, y] = req.args; + // TODO: Remove after adding validation + assert(typeof x === "number" && typeof y === "number"); + return success(x <= y); + }, + + cmp: (req) => { + let err; + + if ((err = checkForArgumentsCount(req, 2)) != null) { + return err; + } + + const [x, y] = req.args; + // TODO: Remove after adding validation + assert(typeof x === "number" && typeof y === "number"); + return success(x === y ? 0 : x > y ? 1 : -1); + }, + }, + + array: { + sum: (req) => { + let err; + + if ((err = checkForArgumentsCount(req, 1)) != null) { + return err; + } + + // TODO: Remove after adding validation + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const [xs] = req.args as [number[]]; + return success( + xs.reduce((agg, cur) => { + return agg + cur; + }, 0), + ); + }, + + dedup: (req) => { + let err; + + if ((err = checkForArgumentsCount(req, 1)) != null) { + return err; + } + + const [xs] = req.args; + // TODO: Remove after adding validation + assert(Array.isArray(xs)); + const set = new Set(xs); + return success(Array.from(set)); + }, + + intersect: (req) => { + let err; + + if ((err = checkForArgumentsCount(req, 2)) != null) { + return err; + } + + const [xs, ys] = req.args; + // TODO: Remove after adding validation + assert(Array.isArray(xs) && Array.isArray(ys)); + + const intersection = xs.filter((x) => { + return ys.includes(x); + }); + + return success(intersection); + }, + + diff: (req) => { + let err; + + if ((err = checkForArgumentsCount(req, 2)) != null) { + return err; + } + + const [xs, ys] = req.args; + // TODO: Remove after adding validation + assert(Array.isArray(xs) && Array.isArray(ys)); + + const diff = xs.filter((x) => { + return !ys.includes(x); + }); + + return success(diff); + }, + + sdiff: (req) => { + let err; + + if ((err = checkForArgumentsCount(req, 2)) != null) { + return err; + } + + const [xs, ys] = req.args; + // TODO: Remove after adding validation + assert(Array.isArray(xs) && Array.isArray(ys)); + + const sdiff = [ + // force new line + ...xs.filter((y) => { + return !ys.includes(y); + }), + ...ys.filter((x) => { + return !xs.includes(x); + }), + ]; + + return success(sdiff); + }, + }, + + json: { + obj: (req) => { + let err; + + if ((err = checkForArgumentsCountEven(req)) != null) { + return err; + } + + // TODO: remove after adding validation + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return makeJsonImpl([{}, ...req.args] as [ + Record, + ...JSONValue[], + ]); + }, + + put: (req) => { + let err; + + if ((err = checkForArgumentsCount(req, 3)) != null) { + return err; + } + + if ((err = checkForArgumentType(req, 0, "object")) != null) { + return err; + } + + return makeJsonImpl( + // TODO: remove after adding validation + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + req.args as [Record, ...JSONValue[]], + ); + }, + + puts: (req) => { + let err; + + if ((err = checkForArgumentsCountOdd(req)) != null) { + return err; + } + + if ((err = checkForArgumentsCountMoreThan(req, 3)) != null) { + return err; + } + + if ((err = checkForArgumentType(req, 0, "object")) != null) { + return err; + } + + return makeJsonImpl( + // TODO: remove after adding validation + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + req.args as [Record, ...JSONValue[]], + ); + }, + + stringify: (req) => { + let err; + + if ((err = checkForArgumentsCount(req, 1)) != null) { + return err; + } + + if ((err = checkForArgumentType(req, 0, "object")) != null) { + return err; + } + + const [json] = req.args; + const res = JSON.stringify(json); + return success(res); + }, + + parse: (req) => { + let err; + + if ((err = checkForArgumentsCount(req, 1)) != null) { + return err; + } + + if ((err = checkForArgumentType(req, 0, "string")) != null) { + return err; + } + + const [raw] = req.args; + + try { + // TODO: Remove after adding validation + assert(typeof raw === "string"); + const json = JSON.parse(raw); + return success(json); + } catch (err: unknown) { + return error(getErrorMessage(err)); + } + }, + }, + + "run-console": { + print: (req) => { + // This log is intentional + // eslint-disable-next-line no-console + console.log(...req.args); + return success({}); + }, + }, } as const; -const checkForArgumentsCount = (req: { args: Array }, count: number) => { - if (req.args.length !== count) { - return error(`Expected ${count} argument(s). Got ${req.args.length}`); - } +const checkForArgumentsCount = ( + req: { args: Array }, + count: number, +) => { + if (req.args.length !== count) { + return error(`Expected ${count} argument(s). Got ${req.args.length}`); + } + + return null; }; -const checkForArgumentsCountMoreThan = (req: { args: Array }, count: number) => { - if (req.args.length < count) { - return error(`Expected more than ${count} argument(s). Got ${req.args.length}`); - } +const checkForArgumentsCountMoreThan = ( + req: { args: Array }, + count: number, +) => { + if (req.args.length < count) { + return error( + `Expected more than ${count} argument(s). Got ${req.args.length}`, + ); + } + + return null; }; -const checkForArgumentsCountEven = (req: { args: Array }, count: number) => { - if (req.args.length % 2 === 1) { - return error(`Expected even number of argument(s). Got ${req.args.length}`); - } +const checkForArgumentsCountEven = (req: { args: Array }) => { + if (req.args.length % 2 === 1) { + return error(`Expected even number of argument(s). Got ${req.args.length}`); + } + + return null; }; -const checkForArgumentsCountOdd = (req: { args: Array }, count: number) => { - if (req.args.length % 2 === 0) { - return error(`Expected odd number of argument(s). Got ${req.args.length}`); - } +const checkForArgumentsCountOdd = (req: { args: Array }) => { + if (req.args.length % 2 === 0) { + return error(`Expected odd number of argument(s). Got ${req.args.length}`); + } + + return null; }; -const checkForArgumentType = (req: { args: Array }, index: number, type: string) => { - const actual = typeof req.args[index]; - if (actual !== type) { - return error(`Argument ${index} expected to be of type ${type}, Got ${actual}`); - } +const checkForArgumentType = ( + req: { args: Array }, + index: number, + type: string, +) => { + const actual = typeof req.args[index]; + + if (actual !== type) { + return error( + `Argument ${index} expected to be of type ${type}, Got ${actual}`, + ); + } + + return null; }; diff --git a/packages/core/js-client/src/services/securityGuard.ts b/packages/core/js-client/src/services/securityGuard.ts index 896505b8..02462cde 100644 --- a/packages/core/js-client/src/services/securityGuard.ts +++ b/packages/core/js-client/src/services/securityGuard.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,67 +14,88 @@ * limitations under the License. */ -import { SecurityTetraplet } from '@fluencelabs/avm'; -import { CallParams, PeerIdB58 } from '@fluencelabs/interfaces'; +import { SecurityTetraplet } from "@fluencelabs/avm"; +import { CallParams, PeerIdB58 } from "@fluencelabs/interfaces"; type ArgName = string | null; /** * A predicate of call params for sig service's sign method which determines whether signing operation is allowed or not */ -export type SecurityGuard = (params: CallParams) => boolean; +export type SecurityGuard = ( + params: CallParams, +) => boolean; /** * Only allow calls when tetraplet for 'data' argument satisfies the predicate */ export const allowTetraplet = ( - pred: (tetraplet: SecurityTetraplet) => boolean, + pred: (tetraplet: SecurityTetraplet) => boolean, ): SecurityGuard => { - return (params) => { - const t = params.tetraplets.data[0]; - return pred(t); - }; + return (params) => { + const t = params.tetraplets["data"][0]; + return pred(t); + }; }; /** * Only allow data which comes from the specified serviceId and fnName */ -export const allowServiceFn = (serviceId: string, fnName: string): SecurityGuard => { - return allowTetraplet((t) => { - return t.service_id === serviceId && t.function_name === fnName; - }); +export const allowServiceFn = ( + serviceId: string, + fnName: string, +): SecurityGuard => { + return allowTetraplet((t) => { + return t.service_id === serviceId && t.function_name === fnName; + }); }; /** * Only allow data originated from the specified json_path */ -export const allowExactJsonPath = (jsonPath: string): SecurityGuard => { - return allowTetraplet((t) => { - return t.json_path === jsonPath; - }); +export const allowExactJsonPath = ( + jsonPath: string, +): SecurityGuard => { + return allowTetraplet((t) => { + return t.json_path === jsonPath; + }); }; /** * Only allow signing when particle is initiated at the specified peer */ -export const allowOnlyParticleOriginatedAt = (peerId: PeerIdB58): SecurityGuard => { - return (params) => { - return params.initPeerId === peerId; - }; +export const allowOnlyParticleOriginatedAt = ( + peerId: PeerIdB58, +): SecurityGuard => { + return (params) => { + return params.initPeerId === peerId; + }; }; /** * Only allow signing when all of the predicates are satisfied. * Useful for predicates reuse */ -export const and = (...predicates: SecurityGuard[]): SecurityGuard => { - return (params) => predicates.every((x) => x(params)); +export const and = ( + ...predicates: SecurityGuard[] +): SecurityGuard => { + return (params) => { + return predicates.every((x) => { + return x(params); + }); + }; }; /** * Only allow signing when any of the predicates are satisfied. * Useful for predicates reuse */ -export const or = (...predicates: SecurityGuard[]): SecurityGuard => { - return (params) => predicates.some((x) => x(params)); +export const or = ( + ...predicates: SecurityGuard[] +): SecurityGuard => { + return (params) => { + return predicates.some((x) => { + return x(params); + }); + }; }; diff --git a/packages/core/js-client/src/util/bytes.ts b/packages/core/js-client/src/util/bytes.ts index 36c37f09..561d6d65 100644 --- a/packages/core/js-client/src/util/bytes.ts +++ b/packages/core/js-client/src/util/bytes.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,21 +14,21 @@ * limitations under the License. */ -type Size = 'u32' | 'u64'; +type Size = "u32" | "u64"; const sizeMap = { - 'u32': 4, - 'u64': 8 + u32: 4, + u64: 8, } as const; function numberToBytes(n: number, s: Size, littleEndian: boolean) { - const size = sizeMap[s]; - const buffer = new ArrayBuffer(8); - const dv = new DataView(buffer); - dv.setBigUint64(0, BigInt(n), littleEndian); - return new Uint8Array(buffer.slice(0, size)); + const size = sizeMap[s]; + const buffer = new ArrayBuffer(8); + const dv = new DataView(buffer); + dv.setBigUint64(0, BigInt(n), littleEndian); + return new Uint8Array(buffer.slice(0, size)); } export function numberToLittleEndianBytes(n: number, s: Size) { - return numberToBytes(n, s, true); -} \ No newline at end of file + return numberToBytes(n, s, true); +} diff --git a/packages/core/js-client/src/util/commonTypes.ts b/packages/core/js-client/src/util/commonTypes.ts index bdbd5017..43469a2f 100644 --- a/packages/core/js-client/src/util/commonTypes.ts +++ b/packages/core/js-client/src/util/commonTypes.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,13 +14,9 @@ * limitations under the License. */ -export { CallParameters} from "@fluencelabs/marine-js/dist/types"; +export type { CallParameters } from "@fluencelabs/marine-js/dist/types"; export interface IStartable { - start(): Promise; - stop(): Promise; + start(): Promise; + stop(): Promise; } - -export type JSONValue = string | number | boolean | null | { [x: string]: JSONValue } | Array; -export type JSONArray = Array; -export type JSONObject = { [x: string]: JSONValue }; diff --git a/packages/core/js-client/src/util/libp2pUtils.ts b/packages/core/js-client/src/util/libp2pUtils.ts index 0cf84e7a..fd579d81 100644 --- a/packages/core/js-client/src/util/libp2pUtils.ts +++ b/packages/core/js-client/src/util/libp2pUtils.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,22 +14,26 @@ * limitations under the License. */ -import { RelayOptions } from '@fluencelabs/interfaces'; -import { multiaddr, Multiaddr } from '@multiformats/multiaddr'; -import { isString } from './utils.js'; +import { RelayOptions } from "@fluencelabs/interfaces"; +import { multiaddr, Multiaddr } from "@multiformats/multiaddr"; + +import { isString } from "./utils.js"; export function relayOptionToMultiaddr(relay: RelayOptions): Multiaddr { - const multiaddrString = isString(relay) ? relay : relay.multiaddr; - const ma = multiaddr(multiaddrString); + const multiaddrString = isString(relay) ? relay : relay.multiaddr; + const ma = multiaddr(multiaddrString); - throwIfHasNoPeerId(ma); + const peerId = ma.getPeerId(); - return ma; + if (peerId == null) { + throwHasNoPeerId(ma); + } + + return ma; } -export function throwIfHasNoPeerId(ma: Multiaddr): void { - const peerId = ma.getPeerId(); - if (!peerId) { - throw new Error('Specified multiaddr is invalid or missing peer id: ' + ma.toString()); - } +export function throwHasNoPeerId(ma: Multiaddr): never { + throw new Error( + "Specified multiaddr is invalid or missing peer id: " + ma.toString(), + ); } diff --git a/packages/core/js-client/src/util/loadClient.ts b/packages/core/js-client/src/util/loadClient.ts deleted file mode 100644 index ff9fac9f..00000000 --- a/packages/core/js-client/src/util/loadClient.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2023 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 type { - CallAquaFunctionType, - ClientConfig, - IFluenceClient, - RegisterServiceType, - RelayOptions, -} from '@fluencelabs/interfaces'; - -type PublicFluenceInterface = { - defaultClient: IFluenceClient | undefined; - clientFactory: (relay: RelayOptions, config?: ClientConfig) => Promise; - callAquaFunction: CallAquaFunctionType; - registerService: RegisterServiceType; -}; - -export const getFluenceInterfaceFromGlobalThis = (): PublicFluenceInterface | undefined => { - // @ts-ignore - return globalThis.fluence; -}; - -// TODO: fix link DXJ-271 -const REJECT_MESSAGE = `Could not load Fluence JS Client library. -If you are using Node.js that probably means that you forgot in install or import the @fluencelabs/js-client.node package. -If you are using a browser, then you probably forgot to add the