mirror of
https://github.com/fluencelabs/fluence-js.git
synced 2025-06-12 07:31:21 +00:00
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 <shamsartem@gmail.com> * refactoring * refactoring fix * optimize import * Update packages/@tests/smoke/node/src/index.ts Co-authored-by: shamsartem <shamsartem@gmail.com> * 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 <shamsartem@gmail.com>
This commit is contained in:
@ -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
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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<any>,
|
||||
def: FunctionCallDef,
|
||||
script: string,
|
||||
rawFnArgs: unknown[],
|
||||
def: FunctionCallDef,
|
||||
script: string,
|
||||
): Promise<unknown> => {
|
||||
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<unknown> => {
|
||||
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,
|
||||
};
|
||||
};
|
||||
|
@ -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<void> {
|
||||
return this.start();
|
||||
}
|
||||
/**
|
||||
* Connect to the Fluence network
|
||||
*/
|
||||
async connect(): Promise<void> {
|
||||
return this.start();
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Disconnect from the Fluence network
|
||||
// */
|
||||
async disconnect(): Promise<void> {
|
||||
return this.stop();
|
||||
}
|
||||
// /**
|
||||
// * Disconnect from the Fluence network
|
||||
// */
|
||||
async disconnect(): Promise<void> {
|
||||
return this.stop();
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
log.trace('disconnecting from Fluence network');
|
||||
this.changeConnectionState('disconnecting');
|
||||
await super.stop();
|
||||
this.changeConnectionState('disconnected');
|
||||
log.trace('disconnected');
|
||||
}
|
||||
override async stop(): Promise<void> {
|
||||
log.trace("disconnecting from Fluence network");
|
||||
this.changeConnectionState("disconnecting");
|
||||
await super.stop();
|
||||
this.changeConnectionState("disconnected");
|
||||
log.trace("disconnected");
|
||||
}
|
||||
}
|
||||
|
@ -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<string>((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<JSONValue>((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)",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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<boolean> => {
|
||||
const msg = Math.random().toString(36).substring(7);
|
||||
export const checkConnection = async (
|
||||
peer: ClientPeer,
|
||||
ttl?: number,
|
||||
): Promise<boolean> => {
|
||||
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<b
|
||||
(call %init_peer_id% ("callback" "error") [%last_error%])
|
||||
)
|
||||
)`;
|
||||
const particle = await peer.internals.createNewParticle(script, ttl);
|
||||
|
||||
const promise = new Promise<string>((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<JSONValue>((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;
|
||||
}
|
||||
};
|
||||
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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})`,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -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<NonArrowType>) => {
|
||||
if (arrowType.codomain.tag === 'nil') {
|
||||
return {};
|
||||
}
|
||||
export const returnType2Aqua = (
|
||||
returnValue: any,
|
||||
arrowType: ArrowType<NonArrowType>,
|
||||
) => {
|
||||
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<NonArrowT
|
||||
* @param arrow - aqua type definition
|
||||
* @returns response value in typescript representation
|
||||
*/
|
||||
export const responseServiceValue2ts = (req: CallServiceData, arrow: ArrowType<any>) => {
|
||||
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<any>,
|
||||
) => {
|
||||
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();
|
||||
};
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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<unknown>) => Promise<unknown>,
|
||||
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<string>] = [
|
||||
...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<any> => {
|
||||
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<string> => {
|
||||
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<string, SecurityTetraplet[]> = {};
|
||||
for (let i = 0; i < req.args.length; i++) {
|
||||
if (names[i]) {
|
||||
tetraplets[names[i]] = req.tetraplets[i];
|
||||
}
|
||||
const tetraplets: Record<string, SecurityTetraplet[]> = {};
|
||||
|
||||
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,
|
||||
);
|
||||
};
|
||||
|
@ -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<IParticle>();
|
||||
|
||||
async start(): Promise<void> {
|
||||
// check if already started
|
||||
if (this.lib2p2Peer !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
supportsRelay(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
particleSource = new Subject<IParticle>();
|
||||
|
||||
async start(): Promise<void> {
|
||||
// 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<void> {
|
||||
// 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<void> {
|
||||
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<void> {
|
||||
// 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<void> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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<IParticle>;
|
||||
/**
|
||||
* Observable that emits particles received from the connection.
|
||||
*/
|
||||
particleSource: Subscribable<IParticle>;
|
||||
|
||||
/**
|
||||
* 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<void>;
|
||||
/**
|
||||
* 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<void>;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
@ -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<string>((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<string>((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");
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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<PeerIdB58, IEphemeralConnection>;
|
||||
receiveParticle(particle: Particle): void;
|
||||
readonly selfPeerId: PeerIdB58;
|
||||
readonly connections: Map<PeerIdB58, IEphemeralConnection>;
|
||||
receiveParticle(particle: Particle): void;
|
||||
}
|
||||
|
||||
export class EphemeralConnection implements IEphemeralConnection {
|
||||
readonly selfPeerId: PeerIdB58;
|
||||
readonly connections: Map<PeerIdB58, IEphemeralConnection> = new Map();
|
||||
readonly selfPeerId: PeerIdB58;
|
||||
readonly connections: Map<PeerIdB58, IEphemeralConnection> = new Map();
|
||||
|
||||
constructor(selfPeerId: PeerIdB58) {
|
||||
this.selfPeerId = selfPeerId;
|
||||
}
|
||||
|
||||
start(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
constructor(selfPeerId: PeerIdB58) {
|
||||
this.selfPeerId = selfPeerId;
|
||||
}
|
||||
|
||||
start(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
stop(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
connectToOther(other: IEphemeralConnection) {
|
||||
if (other.selfPeerId === this.selfPeerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
stop(): Promise<void> {
|
||||
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<Particle>();
|
||||
|
||||
receiveParticle(particle: Particle): void {
|
||||
this.particleSource.next(particle);
|
||||
}
|
||||
|
||||
sendParticle(nextPeerIds: string[], particle: Particle): Promise<void> {
|
||||
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<Particle>();
|
||||
|
||||
receiveParticle(particle: Particle): void {
|
||||
this.particleSource.next(Particle.fromString(particle.toString()));
|
||||
}
|
||||
|
||||
async sendParticle(nextPeerIds: string[], particle: Particle): Promise<void> {
|
||||
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<PeerIdB58, EphemeralPeer> = new Map();
|
||||
private peers: Map<PeerIdB58, EphemeralPeer> = 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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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<string, string | undefined>;
|
||||
devDependencies: Record<string, string | undefined>;
|
||||
dependencies: Record<string, string | undefined>;
|
||||
devDependencies: Record<string, string | undefined>;
|
||||
}
|
||||
|
||||
// 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),
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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<ArrayBuffer>((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<ArrayBuffer>((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",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -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<IFluenceClient> => {
|
||||
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<void> {
|
||||
return Promise.resolve(undefined);
|
||||
}, stop(): Promise<void> {
|
||||
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<ClientPeer> => {
|
||||
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<void> {
|
||||
return Promise.resolve(undefined);
|
||||
}, stop(): Promise<void> {
|
||||
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<void> {
|
||||
return Promise.resolve(undefined);
|
||||
},
|
||||
stop(): Promise<void> {
|
||||
return Promise.resolve(undefined);
|
||||
},
|
||||
},
|
||||
{
|
||||
getValue() {
|
||||
return avmWasm;
|
||||
},
|
||||
start(): Promise<void> {
|
||||
return Promise.resolve(undefined);
|
||||
},
|
||||
stop(): Promise<void> {
|
||||
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<void> {
|
||||
const client = await createClient(relay, config);
|
||||
this.defaultClient = client;
|
||||
},
|
||||
interface FluencePublicApi {
|
||||
defaultClient: ClientPeer | undefined;
|
||||
connect: (relay: RelayOptions, config: ClientConfig) => Promise<void>;
|
||||
disconnect: () => Promise<void>;
|
||||
onConnectionStateChange: (
|
||||
handler: (state: ConnectionState) => void,
|
||||
) => ConnectionState;
|
||||
getClient: () => ClientPeer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from the Fluence network
|
||||
*/
|
||||
disconnect: async function(): Promise<void> {
|
||||
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<void> {
|
||||
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<IFluenceClient> {
|
||||
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";
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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<string>((resolve, reject) => {
|
||||
if (particle instanceof Error) {
|
||||
return reject(particle.message);
|
||||
}
|
||||
|
||||
registerHandlersHelper(peer, particle, {
|
||||
print: {
|
||||
print: (args: Array<string>) => {
|
||||
const [res] = args;
|
||||
resolve(res);
|
||||
},
|
||||
},
|
||||
});
|
||||
const particle = await peer.internals.createNewParticle(script);
|
||||
|
||||
peer.internals.initiateParticle(particle, handleTimeout(reject));
|
||||
});
|
||||
const res = await new Promise<JSONValue>((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<string[]>((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<JSONValue[]>((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");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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<any>((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);
|
||||
});
|
||||
|
||||
peer.internals.initiateParticle(particle, handleTimeout(reject));
|
||||
});
|
||||
|
||||
expect(res).toEqual(expect.arrayContaining(["test1", "test1"]));
|
||||
});
|
||||
}, 1500);
|
||||
});
|
||||
|
@ -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"),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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<string>((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<any>((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<any>((_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<never>((_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<string[]> {
|
||||
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<any[]>((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<unknown[]>((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));
|
||||
});
|
||||
}
|
||||
|
@ -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<string, Map<string, GenericCallServiceHandler>>();
|
||||
private commonHandlers = new Map<string, GenericCallServiceHandler>();
|
||||
private particleScopeHandlers = new Map<
|
||||
string,
|
||||
Map<string, GenericCallServiceHandler>
|
||||
>();
|
||||
private commonHandlers = new Map<string, GenericCallServiceHandler>();
|
||||
|
||||
/**
|
||||
* 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<CallServiceResult | null> {
|
||||
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<string, GenericCallServiceHandler>();
|
||||
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<CallServiceResult | null> {
|
||||
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<string, GenericCallServiceHandler>();
|
||||
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}`;
|
||||
}
|
||||
|
@ -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<CallServiceResult | null>;
|
||||
/**
|
||||
* Execute service call for specified call service data
|
||||
*/
|
||||
callService(req: CallServiceData): Promise<CallServiceResult | null>;
|
||||
|
||||
/**
|
||||
* 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<CallServiceResult>;
|
||||
export type GenericCallServiceHandler = (
|
||||
req: CallServiceData,
|
||||
) => CallServiceResult | Promise<CallServiceResult>;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
@ -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<unknown>) => 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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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==",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -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<string, CompiledFnCall>;
|
||||
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");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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<WorkerImplementation> {
|
||||
constructor(b64script: string) {
|
||||
super(() => {
|
||||
const script = fromBase64(b64script);
|
||||
return BlobWorker.fromText(script);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class InlinedWasmLoader extends LazyLoader<Buffer> {
|
||||
constructor(b64wasm: string) {
|
||||
super(() => {
|
||||
const wasm = toUint8Array(b64wasm);
|
||||
return Buffer.from(wasm);
|
||||
});
|
||||
}
|
||||
}
|
@ -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<SharedArrayBuffer> => {
|
||||
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<SharedArrayBuffer> => {
|
||||
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<SharedArrayBuffer> => {
|
||||
const buffer = await fs.promises.readFile(filePath);
|
||||
return bufferToSharedArrayBuffer(buffer);
|
||||
export const loadWasmFromFileSystem = async (
|
||||
filePath: string,
|
||||
): Promise<SharedArrayBuffer> => {
|
||||
const buffer = await fs.promises.readFile(filePath);
|
||||
return bufferToSharedArrayBuffer(buffer);
|
||||
};
|
||||
|
||||
export class WasmLoaderFromFs extends LazyLoader<SharedArrayBuffer> {
|
||||
constructor(filePath: string) {
|
||||
super(() => loadWasmFromFileSystem(filePath));
|
||||
}
|
||||
constructor(filePath: string) {
|
||||
super(() => {
|
||||
return loadWasmFromFileSystem(filePath);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class WasmLoaderFromNpm extends LazyLoader<SharedArrayBuffer> {
|
||||
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<WorkerImplementation> {
|
||||
constructor(scriptPath: string) {
|
||||
super(() => new Worker(scriptPath));
|
||||
}
|
||||
constructor(scriptPath: string) {
|
||||
super(() => {
|
||||
return new Worker(scriptPath);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class WorkerLoaderFromNpm extends LazyLoader<WorkerImplementation> {
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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<SharedArrayBuffer | Buffer> => {
|
||||
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<SharedArrayBuffer | Buffer> {
|
||||
constructor(filePath: string) {
|
||||
super(() => loadWasmFromUrl(filePath));
|
||||
}
|
||||
}
|
||||
|
||||
export class WorkerLoaderFromUrl extends LazyLoader<WorkerImplementation> {
|
||||
constructor(scriptPath: string) {
|
||||
super(() => new Worker(scriptPath));
|
||||
}
|
||||
}
|
@ -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<void>;
|
||||
/**
|
||||
* Creates marine service from the given module and service id
|
||||
*/
|
||||
createService(
|
||||
serviceModule: ArrayBuffer | SharedArrayBuffer,
|
||||
serviceId: string,
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Removes marine service with the given service id
|
||||
*/
|
||||
removeService(serviceId: string): Promise<void>;
|
||||
/**
|
||||
* Removes marine service with the given service id
|
||||
*/
|
||||
removeService(serviceId: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Returns true if any service with the specified service id is registered
|
||||
*/
|
||||
hasService(serviceId: string): Promise<boolean>;
|
||||
/**
|
||||
* Returns true if any service with the specified service id is registered
|
||||
*/
|
||||
hasService(serviceId: string): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Calls the specified function of the specified service with the given arguments
|
||||
*/
|
||||
callService(
|
||||
serviceId: string,
|
||||
functionName: string,
|
||||
args: JSONArray | JSONObject,
|
||||
callParams: CallParameters,
|
||||
): Promise<unknown>;
|
||||
/**
|
||||
* Calls the specified function of the specified service with the given arguments
|
||||
*/
|
||||
callService(
|
||||
serviceId: string,
|
||||
functionName: string,
|
||||
args: JSONArray | JSONObject,
|
||||
callParams: CallParameters,
|
||||
): Promise<JSONValue>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<InterpreterResult | Error>;
|
||||
/**
|
||||
* Run AVM interpreter with the specified parameters
|
||||
*/
|
||||
run(
|
||||
runParams: RunParameters,
|
||||
air: string,
|
||||
prevData: Uint8Array,
|
||||
data: Uint8Array,
|
||||
callResults: CallResultsArray,
|
||||
): Promise<InterpreterResult | Error>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for something which can hold a value
|
||||
*/
|
||||
export interface IValueLoader<T> {
|
||||
getValue(): T;
|
||||
getValue(): T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for something which can load wasm files
|
||||
*/
|
||||
export interface IWasmLoader extends IValueLoader<ArrayBuffer | SharedArrayBuffer>, IStartable {}
|
||||
export interface IWasmLoader
|
||||
extends IValueLoader<ArrayBuffer | SharedArrayBuffer>,
|
||||
IStartable {}
|
||||
|
||||
/**
|
||||
* Interface for something which can thread.js based worker
|
||||
*/
|
||||
export interface IWorkerLoader extends IValueLoader<WorkerImplementation>, IStartable {}
|
||||
export interface IWorkerLoader
|
||||
extends IValueLoader<WorkerImplementation | Promise<WorkerImplementation>>,
|
||||
IStartable {}
|
||||
|
||||
/**
|
||||
* Lazy loader for some value. Value is loaded only when `start` method is called
|
||||
*/
|
||||
export class LazyLoader<T> implements IStartable, IValueLoader<T> {
|
||||
private value: T | null = null;
|
||||
private value: T | null = null;
|
||||
|
||||
constructor(private loadValue: () => Promise<T> | T) {}
|
||||
constructor(private loadValue: () => Promise<T> | 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() {}
|
||||
}
|
||||
|
@ -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<WorkerImplementation> {
|
||||
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",
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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<MarineBackgroundInterface>;
|
||||
|
||||
private loggers = new Map<string, MarineLogger>();
|
||||
private loggers = new Map<string, MarineLogger>();
|
||||
|
||||
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<void> {
|
||||
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<MarineBackgroundInterface>(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<void> {
|
||||
if (this.workerThread != null) {
|
||||
throw new Error("Worker thread already initialized");
|
||||
}
|
||||
|
||||
async createService(serviceModule: ArrayBuffer | SharedArrayBuffer, serviceId: string): Promise<void> {
|
||||
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<MarineBackgroundInterface> =
|
||||
await spawn<MarineBackgroundInterface>(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<void> {
|
||||
if (this.workerThread == null) {
|
||||
throw new Error("Worker is not initialized");
|
||||
}
|
||||
|
||||
async callService(
|
||||
serviceId: string,
|
||||
functionName: string,
|
||||
args: JSONArray | JSONObject,
|
||||
callParams: CallParameters,
|
||||
): Promise<unknown> {
|
||||
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<JSONValue> {
|
||||
if (this.workerThread == null) {
|
||||
throw new Error("Worker is not initialized");
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
if (!this.workerThread) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.workerThread.terminate();
|
||||
await Thread.terminate(this.workerThread);
|
||||
return this.workerThread.callService(
|
||||
serviceId,
|
||||
functionName,
|
||||
args,
|
||||
callParams,
|
||||
);
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
if (this.workerThread == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.workerThread.terminate();
|
||||
await Thread.terminate(this.workerThread);
|
||||
}
|
||||
}
|
||||
|
@ -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<boolean> => {
|
||||
// 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();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<Buffer>((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<Buffer>((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));
|
||||
};
|
||||
|
@ -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<SignReturnType> {
|
||||
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<boolean> {
|
||||
return this.keyPair.verify(Uint8Array.from(data), Uint8Array.from(signature))
|
||||
}
|
||||
/**
|
||||
* Verifies the signature. Required by aqua
|
||||
*/
|
||||
verify(signature: number[], data: number[]): Promise<boolean> {
|
||||
return this.keyPair.verify(
|
||||
Uint8Array.from(data),
|
||||
Uint8Array.from(signature),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const getDefaultSig = (peer: FluencePeer) => {
|
||||
peer.registerMarineService;
|
||||
};
|
||||
|
@ -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<any>(peer.keyPair.getPeerId());
|
||||
return allowOnlyParticleOriginatedAt(peer.keyPair.getPeerId());
|
||||
};
|
||||
|
||||
export class Srv implements SrvDef {
|
||||
private services: Set<string> = new Set();
|
||||
private services: Set<string> = 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());
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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} | ${'"<empty argument list>"'}
|
||||
${'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} | ${'"<empty argument list>"'}
|
||||
${"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);
|
||||
});
|
||||
});
|
||||
|
@ -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<any>((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<unknown[]>((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);
|
||||
});
|
||||
});
|
||||
|
@ -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<string, CompiledFnCall>;
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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<string, CompiledFnCall>;
|
||||
|
||||
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");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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
|
||||
|
@ -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<null>) => string | Promise<string>;
|
||||
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<boolean>;
|
||||
get_peer_id: (callParams: CallParams<null>) => string | Promise<string>;
|
||||
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<boolean>;
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -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<null>) => string[] | Promise<string[]>;
|
||||
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<null>) => string[] | Promise<string[]>;
|
||||
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
|
||||
|
@ -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<void>;
|
||||
tracingEvent: (
|
||||
arrowName: string,
|
||||
event: string,
|
||||
callParams: CallParams<"arrowName" | "event">,
|
||||
) => void | Promise<void>;
|
||||
}
|
||||
|
||||
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
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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<T extends ArgName> = (params: CallParams<T>) => boolean;
|
||||
export type SecurityGuard<T extends ArgName> = (
|
||||
params: CallParams<T>,
|
||||
) => boolean;
|
||||
|
||||
/**
|
||||
* Only allow calls when tetraplet for 'data' argument satisfies the predicate
|
||||
*/
|
||||
export const allowTetraplet = <T extends ArgName>(
|
||||
pred: (tetraplet: SecurityTetraplet) => boolean,
|
||||
pred: (tetraplet: SecurityTetraplet) => boolean,
|
||||
): SecurityGuard<T> => {
|
||||
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 = <T extends ArgName>(serviceId: string, fnName: string): SecurityGuard<T> => {
|
||||
return allowTetraplet((t) => {
|
||||
return t.service_id === serviceId && t.function_name === fnName;
|
||||
});
|
||||
export const allowServiceFn = <T extends ArgName>(
|
||||
serviceId: string,
|
||||
fnName: string,
|
||||
): SecurityGuard<T> => {
|
||||
return allowTetraplet((t) => {
|
||||
return t.service_id === serviceId && t.function_name === fnName;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Only allow data originated from the specified json_path
|
||||
*/
|
||||
export const allowExactJsonPath = <T extends ArgName>(jsonPath: string): SecurityGuard<T> => {
|
||||
return allowTetraplet((t) => {
|
||||
return t.json_path === jsonPath;
|
||||
});
|
||||
export const allowExactJsonPath = <T extends ArgName>(
|
||||
jsonPath: string,
|
||||
): SecurityGuard<T> => {
|
||||
return allowTetraplet((t) => {
|
||||
return t.json_path === jsonPath;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Only allow signing when particle is initiated at the specified peer
|
||||
*/
|
||||
export const allowOnlyParticleOriginatedAt = <T extends ArgName>(peerId: PeerIdB58): SecurityGuard<T> => {
|
||||
return (params) => {
|
||||
return params.initPeerId === peerId;
|
||||
};
|
||||
export const allowOnlyParticleOriginatedAt = <T extends ArgName>(
|
||||
peerId: PeerIdB58,
|
||||
): SecurityGuard<T> => {
|
||||
return (params) => {
|
||||
return params.initPeerId === peerId;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Only allow signing when all of the predicates are satisfied.
|
||||
* Useful for predicates reuse
|
||||
*/
|
||||
export const and = <T extends ArgName>(...predicates: SecurityGuard<T>[]): SecurityGuard<T> => {
|
||||
return (params) => predicates.every((x) => x(params));
|
||||
export const and = <T extends ArgName>(
|
||||
...predicates: SecurityGuard<T>[]
|
||||
): SecurityGuard<T> => {
|
||||
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 = <T extends ArgName>(...predicates: SecurityGuard<T>[]): SecurityGuard<T> => {
|
||||
return (params) => predicates.some((x) => x(params));
|
||||
export const or = <T extends ArgName>(
|
||||
...predicates: SecurityGuard<T>[]
|
||||
): SecurityGuard<T> => {
|
||||
return (params) => {
|
||||
return predicates.some((x) => {
|
||||
return x(params);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
return numberToBytes(n, s, true);
|
||||
}
|
||||
|
@ -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<void>;
|
||||
stop(): Promise<void>;
|
||||
start(): Promise<void>;
|
||||
stop(): Promise<void>;
|
||||
}
|
||||
|
||||
export type JSONValue = string | number | boolean | null | { [x: string]: JSONValue } | Array<JSONValue>;
|
||||
export type JSONArray = Array<JSONValue>;
|
||||
export type JSONObject = { [x: string]: JSONValue };
|
||||
|
@ -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(),
|
||||
);
|
||||
}
|
||||
|
@ -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<IFluenceClient>;
|
||||
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 <script> tag to your HTML.
|
||||
Please refer to the documentation page for more details: https://fluence.dev/docs/build/js-client/installation
|
||||
`;
|
||||
|
||||
// Let's assume that if the library has not been loaded in 5 seconds, then the user has forgotten to add the script tag
|
||||
const POLL_PEER_TIMEOUT = 5000;
|
||||
|
||||
// The script might be cached so need to try loading it ASAP, thus short interval
|
||||
const POLL_PEER_INTERVAL = 100;
|
||||
|
||||
/**
|
||||
* Wait until the js client script it loaded and return the default peer from globalThis
|
||||
*/
|
||||
export const getFluenceInterface = (): Promise<PublicFluenceInterface> => {
|
||||
// If the script is already loaded, then return the value immediately
|
||||
const optimisticResult = getFluenceInterfaceFromGlobalThis();
|
||||
if (optimisticResult) {
|
||||
return Promise.resolve(optimisticResult);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// This function is internal
|
||||
// Make it sure that would be zero way for unnecessary types
|
||||
// to break out into the public API
|
||||
let interval: any;
|
||||
let hits = POLL_PEER_TIMEOUT / POLL_PEER_INTERVAL;
|
||||
interval = setInterval(() => {
|
||||
if (hits === 0) {
|
||||
clearInterval(interval);
|
||||
reject(REJECT_MESSAGE);
|
||||
}
|
||||
|
||||
let res = getFluenceInterfaceFromGlobalThis();
|
||||
if (res) {
|
||||
clearInterval(interval);
|
||||
resolve(res);
|
||||
}
|
||||
hits--;
|
||||
}, POLL_PEER_INTERVAL);
|
||||
});
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
/*
|
||||
/**
|
||||
* Copyright 2023 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -14,51 +14,51 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import debug from 'debug';
|
||||
import debug from "debug";
|
||||
|
||||
type Logger = (formatter: any, ...args: any[]) => void;
|
||||
type Logger = (formatter: unknown, ...args: unknown[]) => void;
|
||||
|
||||
export interface CommonLogger {
|
||||
error: Logger;
|
||||
trace: Logger;
|
||||
debug: Logger;
|
||||
error: Logger;
|
||||
trace: Logger;
|
||||
debug: Logger;
|
||||
}
|
||||
|
||||
export interface MarineLogger {
|
||||
warn: Logger;
|
||||
error: Logger;
|
||||
debug: Logger;
|
||||
trace: Logger;
|
||||
info: Logger;
|
||||
warn: Logger;
|
||||
error: Logger;
|
||||
debug: Logger;
|
||||
trace: Logger;
|
||||
info: Logger;
|
||||
}
|
||||
|
||||
export function logger(name: string): CommonLogger {
|
||||
return {
|
||||
error: debug(`fluence:${name}:error`),
|
||||
trace: debug(`fluence:${name}:trace`),
|
||||
debug: debug(`fluence:${name}:debug`),
|
||||
};
|
||||
return {
|
||||
error: debug(`fluence:${name}:error`),
|
||||
trace: debug(`fluence:${name}:trace`),
|
||||
debug: debug(`fluence:${name}:debug`),
|
||||
};
|
||||
}
|
||||
|
||||
export function marineLogger(serviceId: string): MarineLogger {
|
||||
const name = `fluence:marine:${serviceId}`;
|
||||
return {
|
||||
warn: debug(`${name}:warn`),
|
||||
error: debug(`${name}:error`),
|
||||
debug: debug(`${name}:debug`),
|
||||
trace: debug(`${name}:trace`),
|
||||
info: debug(`${name}:info`),
|
||||
};
|
||||
const name = `fluence:marine:${serviceId}`;
|
||||
return {
|
||||
warn: debug(`${name}:warn`),
|
||||
error: debug(`${name}:error`),
|
||||
debug: debug(`${name}:debug`),
|
||||
trace: debug(`${name}:trace`),
|
||||
info: debug(`${name}:info`),
|
||||
};
|
||||
}
|
||||
|
||||
export function disable() {
|
||||
debug.disable();
|
||||
debug.disable();
|
||||
}
|
||||
|
||||
export function enable(namespaces: string) {
|
||||
debug.enable(namespaces);
|
||||
debug.enable(namespaces);
|
||||
}
|
||||
|
||||
export function enabled(namespaces: string) {
|
||||
return debug.enabled(namespaces);
|
||||
return debug.enabled(namespaces);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/*
|
||||
/**
|
||||
* Copyright 2023 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -14,135 +14,199 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as api from '@fluencelabs/aqua-api/aqua-api.js';
|
||||
import { promises as fs } from "fs";
|
||||
|
||||
import { promises as fs } from 'fs';
|
||||
import { DEFAULT_CONFIG, FluencePeer, PeerConfig } from '../jsPeer/FluencePeer.js';
|
||||
import { Particle } from '../particle/Particle.js';
|
||||
import { ClientConfig, IFluenceClient, RelayOptions, ServiceDef } from '@fluencelabs/interfaces';
|
||||
import { callAquaFunction } from '../compilerSupport/callFunction.js';
|
||||
import * as api from "@fluencelabs/aqua-api/aqua-api.js";
|
||||
import {
|
||||
ClientConfig,
|
||||
FunctionCallDef,
|
||||
JSONArray,
|
||||
PassedArgs,
|
||||
RelayOptions,
|
||||
ServiceDef,
|
||||
} from "@fluencelabs/interfaces";
|
||||
import { Subject, Subscribable } from "rxjs";
|
||||
|
||||
import { MarineBackgroundRunner } from '../marine/worker/index.js';
|
||||
import { WorkerLoader } from '../marine/worker-script/workerLoader.js';
|
||||
import { KeyPair } from '../keypair/index.js';
|
||||
import { Subject, Subscribable } from 'rxjs';
|
||||
import { WrapFnIntoServiceCall } from '../jsServiceHost/serviceUtils.js';
|
||||
import { JsServiceHost } from '../jsServiceHost/JsServiceHost.js';
|
||||
import { ClientPeer, makeClientPeerConfig } from '../clientPeer/ClientPeer.js';
|
||||
import { WasmLoaderFromNpm, WorkerLoaderFromNpm } from '../marine/deps-loader/node.js';
|
||||
import { IConnection } from '../connection/interfaces.js';
|
||||
import { ClientPeer, makeClientPeerConfig } from "../clientPeer/ClientPeer.js";
|
||||
import { callAquaFunction } from "../compilerSupport/callFunction.js";
|
||||
import { IConnection } from "../connection/interfaces.js";
|
||||
import { DEFAULT_CONFIG, FluencePeer } from "../jsPeer/FluencePeer.js";
|
||||
import { CallServiceResultType } from "../jsServiceHost/interfaces.js";
|
||||
import { JsServiceHost } from "../jsServiceHost/JsServiceHost.js";
|
||||
import { WrapFnIntoServiceCall } from "../jsServiceHost/serviceUtils.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 { Particle } from "../particle/Particle.js";
|
||||
|
||||
export const registerHandlersHelper = (
|
||||
peer: FluencePeer,
|
||||
particle: Particle,
|
||||
handlers: Record<string, Record<string, any>>,
|
||||
peer: FluencePeer,
|
||||
particle: Particle,
|
||||
handlers: Record<
|
||||
string,
|
||||
Record<string, (args: JSONArray) => CallServiceResultType | undefined>
|
||||
>,
|
||||
) => {
|
||||
Object.entries(handlers).forEach(([serviceId, service]) => {
|
||||
Object.entries(service).forEach(([fnName, fn]) => {
|
||||
peer.internals.regHandler.forParticle(particle.id, serviceId, fnName, WrapFnIntoServiceCall(fn));
|
||||
});
|
||||
Object.entries(handlers).forEach(([serviceId, service]) => {
|
||||
Object.entries(service).forEach(([fnName, fn]) => {
|
||||
peer.internals.regHandler.forParticle(
|
||||
particle.id,
|
||||
serviceId,
|
||||
fnName,
|
||||
WrapFnIntoServiceCall(fn),
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export type CompiledFnCall = (peer: IFluenceClient, args: { [key: string]: any }) => Promise<unknown>;
|
||||
export type CompiledFnCall = (
|
||||
peer: FluencePeer,
|
||||
args: PassedArgs,
|
||||
) => Promise<unknown>;
|
||||
export type CompiledFile = {
|
||||
functions: { [key: string]: CompiledFnCall };
|
||||
services: { [key: string]: ServiceDef };
|
||||
functions: { [key: string]: CompiledFnCall };
|
||||
services: { [key: string]: ServiceDef };
|
||||
};
|
||||
|
||||
export const compileAqua = async (aquaFile: string): Promise<CompiledFile> => {
|
||||
await fs.access(aquaFile);
|
||||
await fs.access(aquaFile);
|
||||
|
||||
const compilationResult = await api.Aqua.compile(new api.Path(aquaFile), [], undefined);
|
||||
const compilationResult = await api.Aqua.compile(
|
||||
new api.Path(aquaFile),
|
||||
[],
|
||||
undefined,
|
||||
);
|
||||
|
||||
if (compilationResult.errors.length > 0) {
|
||||
throw new Error('Aqua compilation failed. Error: ' + compilationResult.errors.join('/n'));
|
||||
}
|
||||
if (compilationResult.errors.length > 0) {
|
||||
throw new Error(
|
||||
"Aqua compilation failed. Error: " + compilationResult.errors.join("/n"),
|
||||
);
|
||||
}
|
||||
|
||||
const functions = Object.entries(compilationResult.functions)
|
||||
.map(([name, fnInfo]) => {
|
||||
const callFn = (peer: IFluenceClient, args: { [key: string]: any }) => {
|
||||
return callAquaFunction({
|
||||
def: fnInfo.funcDef,
|
||||
script: fnInfo.script,
|
||||
config: {},
|
||||
peer: peer,
|
||||
args,
|
||||
});
|
||||
};
|
||||
return { [name]: callFn };
|
||||
})
|
||||
.reduce((agg, obj) => {
|
||||
return { ...agg, ...obj };
|
||||
}, {});
|
||||
const functions = Object.entries(compilationResult.functions)
|
||||
.map(([name, fnInfo]) => {
|
||||
const callFn = (peer: FluencePeer, args: PassedArgs) => {
|
||||
return callAquaFunction({
|
||||
// TODO: Set our compiler here and fix this
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
def: fnInfo.funcDef as FunctionCallDef,
|
||||
script: fnInfo.script,
|
||||
config: {},
|
||||
peer: peer,
|
||||
args,
|
||||
});
|
||||
};
|
||||
|
||||
return { functions, services: compilationResult.services };
|
||||
return { [name]: callFn };
|
||||
})
|
||||
.reduce((agg, obj) => {
|
||||
return { ...agg, ...obj };
|
||||
}, {});
|
||||
|
||||
return {
|
||||
functions,
|
||||
// TODO: set our compiler here and fix this
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
services: compilationResult.services as Record<string, ServiceDef>,
|
||||
};
|
||||
};
|
||||
|
||||
class NoopConnection implements IConnection {
|
||||
start(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
start(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
stop(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
getRelayPeerId(): string {
|
||||
return 'nothing_here';
|
||||
}
|
||||
supportsRelay(): boolean {
|
||||
return true;
|
||||
}
|
||||
particleSource: Subscribable<Particle> = new Subject<Particle>();
|
||||
stop(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
sendParticle(nextPeerIds: string[], particle: Particle): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
getRelayPeerId(): string {
|
||||
return "nothing_here";
|
||||
}
|
||||
supportsRelay(): boolean {
|
||||
return true;
|
||||
}
|
||||
particleSource: Subscribable<Particle> = new Subject<Particle>();
|
||||
|
||||
sendParticle(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
export class TestPeer extends FluencePeer {
|
||||
constructor(keyPair: KeyPair, connection: IConnection) {
|
||||
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 jsHost = new JsServiceHost();
|
||||
super(DEFAULT_CONFIG, keyPair, marine, jsHost, connection);
|
||||
}
|
||||
constructor(keyPair: KeyPair, connection: IConnection) {
|
||||
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 jsHost = new JsServiceHost();
|
||||
super(DEFAULT_CONFIG, keyPair, marine, jsHost, connection);
|
||||
}
|
||||
}
|
||||
|
||||
export const mkTestPeer = async () => {
|
||||
const kp = await KeyPair.randomEd25519();
|
||||
const conn = new NoopConnection();
|
||||
return new TestPeer(kp, conn);
|
||||
const kp = await KeyPair.randomEd25519();
|
||||
const conn = new NoopConnection();
|
||||
return new TestPeer(kp, conn);
|
||||
};
|
||||
|
||||
export const withPeer = async (action: (p: FluencePeer) => Promise<void>) => {
|
||||
const p = await mkTestPeer();
|
||||
try {
|
||||
await p.start();
|
||||
await action(p);
|
||||
} finally {
|
||||
await p.stop();
|
||||
}
|
||||
const p = await mkTestPeer();
|
||||
|
||||
try {
|
||||
await p.start();
|
||||
await action(p);
|
||||
} finally {
|
||||
await p.stop();
|
||||
}
|
||||
};
|
||||
|
||||
export const withClient = async (
|
||||
relay: RelayOptions,
|
||||
config: ClientConfig,
|
||||
action: (client: ClientPeer) => Promise<void>,
|
||||
relay: RelayOptions,
|
||||
config: ClientConfig,
|
||||
action: (client: ClientPeer) => Promise<void>,
|
||||
) => {
|
||||
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 { keyPair, peerConfig, relayConfig } = await makeClientPeerConfig(relay, config);
|
||||
const client = new ClientPeer(peerConfig, relayConfig, keyPair, marine);
|
||||
try {
|
||||
await client.connect();
|
||||
await action(client);
|
||||
} finally {
|
||||
await client.disconnect();
|
||||
}
|
||||
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 { keyPair, peerConfig, relayConfig } = await makeClientPeerConfig(
|
||||
relay,
|
||||
config,
|
||||
);
|
||||
|
||||
const client = new ClientPeer(peerConfig, relayConfig, keyPair, marine);
|
||||
|
||||
try {
|
||||
await client.connect();
|
||||
await action(client);
|
||||
} finally {
|
||||
await client.disconnect();
|
||||
}
|
||||
};
|
||||
|
@ -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.
|
||||
@ -15,13 +15,21 @@
|
||||
*/
|
||||
|
||||
export function jsonify(obj: unknown) {
|
||||
return JSON.stringify(obj, null, 4);
|
||||
return JSON.stringify(obj, null, 4);
|
||||
}
|
||||
|
||||
export const isString = (unknown: unknown): unknown is string => {
|
||||
return unknown !== null && typeof unknown === 'string';
|
||||
return unknown !== null && typeof unknown === "string";
|
||||
};
|
||||
|
||||
export const isObject = (unknown: unknown): unknown is object => {
|
||||
return unknown !== null && typeof unknown === 'object';
|
||||
return unknown !== null && typeof unknown === "object";
|
||||
};
|
||||
|
||||
export const getErrorMessage = (error: unknown) => {
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
}
|
||||
|
||||
return String(error);
|
||||
};
|
||||
|
@ -1,11 +1,12 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": ["vite/client", "node"],
|
||||
"outDir": "./dist",
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": ["vite/client"],
|
||||
"outDir": "./dist",
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
@ -14,46 +14,50 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import inject from '@rollup/plugin-inject';
|
||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||
import { createRequire } from 'module';
|
||||
import { readFileSync } from 'fs';
|
||||
import inject from "@rollup/plugin-inject";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
import { createRequire } from "module";
|
||||
import { readFileSync } from "fs";
|
||||
import { UserConfig } from "vite";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const esbuildShim = require.resolve('node-stdlib-browser/helpers/esbuild/shim');
|
||||
const esbuildShim = require.resolve("node-stdlib-browser/helpers/esbuild/shim");
|
||||
|
||||
export default {
|
||||
build: {
|
||||
target: 'modules',
|
||||
minify: 'esbuild',
|
||||
lib: {
|
||||
entry: './src/index.ts',
|
||||
name: 'js-client',
|
||||
fileName: 'index',
|
||||
const config: UserConfig = {
|
||||
build: {
|
||||
target: "modules",
|
||||
minify: "esbuild",
|
||||
lib: {
|
||||
entry: "./src/index.ts",
|
||||
name: "js-client",
|
||||
fileName: "index",
|
||||
},
|
||||
outDir: "./dist/browser",
|
||||
rollupOptions: {
|
||||
plugins: [
|
||||
{
|
||||
// @ts-ignore
|
||||
...inject({
|
||||
global: [esbuildShim, "global"],
|
||||
process: [esbuildShim, "process"],
|
||||
Buffer: [esbuildShim, "Buffer"],
|
||||
}),
|
||||
enforce: "post",
|
||||
},
|
||||
outDir: './dist/browser',
|
||||
rollupOptions: {
|
||||
plugins: [
|
||||
{
|
||||
// @ts-ignore
|
||||
...inject({
|
||||
global: [esbuildShim, 'global'],
|
||||
process: [esbuildShim, 'process'],
|
||||
Buffer: [esbuildShim, 'Buffer']
|
||||
}), enforce: 'post'
|
||||
}
|
||||
],
|
||||
}
|
||||
],
|
||||
},
|
||||
plugins: [tsconfigPaths()],
|
||||
optimizeDeps: {
|
||||
esbuildOptions: {
|
||||
define: {
|
||||
global: 'globalThis',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [tsconfigPaths()],
|
||||
optimizeDeps: {
|
||||
esbuildOptions: {
|
||||
define: {
|
||||
global: "globalThis",
|
||||
},
|
||||
},
|
||||
define: {
|
||||
__PACKAGE_JSON_CONTENT__: readFileSync('./package.json', 'utf-8')
|
||||
},
|
||||
}
|
||||
},
|
||||
define: {
|
||||
__PACKAGE_JSON_CONTENT__: readFileSync("./package.json", "utf-8"),
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
Reference in New Issue
Block a user