Compare commits
No commits in common. "main" and "v1.0.0" have entirely different histories.
|
@ -1,6 +1,5 @@
|
||||||
node_modules/*
|
node_modules/*
|
||||||
dist/*
|
dist/*
|
||||||
index.html
|
index.html
|
||||||
!examples/**/*index.html*
|
|
||||||
*.sh*
|
*.sh*
|
||||||
wasmcloud-rs-js/pkg/
|
wasmcloud-rs-js/pkg/
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/prettierrc",
|
"$schema": "https://json.schemastore.org/prettierrc",
|
||||||
"trailingComma": "none",
|
"trailingComma": "all",
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"semi": true,
|
"semi": true,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
|
|
113
README.md
113
README.md
|
@ -1,6 +1,3 @@
|
||||||
> [!IMPORTANT]
|
|
||||||
> This host is **experimental** and does not implement all features or security settings. As a result, this host should not be used in production. For deploying wasmCloud to production, use the [primary host runtime](https://github.com/wasmCloud/wasmCloud/tree/main/crates/host).
|
|
||||||
|
|
||||||
# wasmCloud Host in JavaScript/Browser
|
# wasmCloud Host in JavaScript/Browser
|
||||||
|
|
||||||
This is the JavaScript implementation of the wasmCloud host for the browser (NodeJS support in progress). The library runs a host inside a web browser/application that connects to a remote lattice via NATS and can run wasmCloud actors in the browser. The host will automatically listen for actor start/stop from NATS and will initialize the actor using `wapcJS`. Any invocations will be handled by the browser actor and returned to the requesting host via NATS. Users can pass callbacks to handle invocation and event data.
|
This is the JavaScript implementation of the wasmCloud host for the browser (NodeJS support in progress). The library runs a host inside a web browser/application that connects to a remote lattice via NATS and can run wasmCloud actors in the browser. The host will automatically listen for actor start/stop from NATS and will initialize the actor using `wapcJS`. Any invocations will be handled by the browser actor and returned to the requesting host via NATS. Users can pass callbacks to handle invocation and event data.
|
||||||
|
@ -12,7 +9,7 @@ In this demonstration video we will demonstration the following:
|
||||||
* Load an HTTP Server capability into a wasmCloud Host running on a machine
|
* Load an HTTP Server capability into a wasmCloud Host running on a machine
|
||||||
* Load an the wasmcloud-js host into a web browser
|
* Load an the wasmcloud-js host into a web browser
|
||||||
* Load an 'echo' actor into the web browser
|
* Load an 'echo' actor into the web browser
|
||||||
* **seamlessly** bind the actor to the capability provider through Lattice
|
* **seemlessly** bind the actor to the capability provider through Lattice
|
||||||
* Access the webserver, which in turn delivers the request to the actor, processes it, and returns it to the requestion client via the capability
|
* Access the webserver, which in turn delivers the request to the actor, processes it, and returns it to the requestion client via the capability
|
||||||
* Unload the actor
|
* Unload the actor
|
||||||
|
|
||||||
|
@ -20,6 +17,7 @@ https://user-images.githubusercontent.com/1530656/130013412-b9a9daa6-fc71-424b-8
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Prerequisities
|
## Prerequisities
|
||||||
|
|
||||||
* NATS with WebSockets enabled
|
* NATS with WebSockets enabled
|
||||||
|
@ -47,107 +45,50 @@ $ npm install @wasmcloud/wasmcloud-js
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
More examples can be found in the [examples](examples/) directory, including sample `webpack` and `esbuild` configurations
|
|
||||||
|
|
||||||
**Browser**
|
**Browser**
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script src="https://unpkg.com/@wasmcloud/wasmcloud-js@<VERSION>/dist/wasmcloud.js"></script>
|
<script src="dist/index.bundle.js"></script>
|
||||||
<script>
|
<script>
|
||||||
(async () => {
|
(async () => {
|
||||||
// Start the host passing the name, registry tls enabled, a list of nats ws/wss hosts or the natsConnection object, and an optional host heartbeat interval (default is 30 seconds)
|
// start the host passing the name, registry tls enabled, a list of nats ws/wss hosts or the natsConnection object, a map of invocation callbacks, and a host heartbeat interval (default is 30 seconds)
|
||||||
const host = await wasmcloudjs.startHost("default", false, ["ws://localhost:4222"], 30000);
|
const host = await wasmcloudjs.startHost("default", false, ["ws://localhost:4222"], {}, 30000);
|
||||||
// The host will automatically listen for actors start & stop messages, to manually listen for these messages the following methods are exposed
|
// the host will automatically listen for actors start & stop messages, to manually listen for these messages
|
||||||
// only call these methods if your host is not listening for actor start/stop
|
|
||||||
// actor invocations are automatically returned to the host. if a user wants to handle the data, they can pass a map of callbacks using the actor ref/wasm file name as the key with a callback(data, result) function. The data contains the invocation data and the result contains the invocation result
|
// actor invocations are automatically returned to the host. if a user wants to handle the data, they can pass a map of callbacks using the actor ref/wasm file name as the key with a callback(data, result) function. The data contains the invocation data and the result contains the invocation result
|
||||||
// (async() => {
|
(async() => {
|
||||||
// await host.listenLaunchActor(
|
await host.listenLaunchActor(
|
||||||
// {
|
{
|
||||||
// "localhost:5000/echo:0.2.2": (data, result) => console.log(data.operation, result);
|
"localhost:5000/echo:0.2.2": (data, result) => console.log(data.operation, result);
|
||||||
// }
|
}
|
||||||
// );
|
);
|
||||||
// await host.listenStopActor();
|
await host.listenStopActor();
|
||||||
// })();
|
})();
|
||||||
// To launch an actor manually from the library from a registry, optionally a callback can be passed to handle the invocation results. In addition, a hostCall callback and writer can be passed.
|
// to launch an actor manually from the library from a registry
|
||||||
// The hostCallback format is as follows:
|
await host.launchActor("registry.com/actor:0.1.1")
|
||||||
// ```
|
// to launch an actrom manually from local disk (note the .wasm is required)
|
||||||
// (binding, namespace, operation, payload) => {
|
|
||||||
// return Uint8Array(payload);
|
|
||||||
// })
|
|
||||||
// ```
|
|
||||||
await host.launchActor("registry.com/actor:0.1.1", (data) => { /* handle data */})
|
|
||||||
// Launch an actor with the hostCallback
|
|
||||||
await host.launchActor("registry.com/actor:0.1.1", (data) => { /* handle data */}, (binding, namespace, operation, payload) => {
|
|
||||||
// decode payload via messagepack
|
|
||||||
// const decoded = decode(payload);
|
|
||||||
return new Uint8Array(payload);
|
|
||||||
})
|
|
||||||
// To launch an actrom manually from local disk (note the .wasm is required)
|
|
||||||
await host.launchActor("./actor.wasm");
|
await host.launchActor("./actor.wasm");
|
||||||
// To listen for events, you can call the subscribeToEvents and pass an optional callback to handle the event data
|
// to listen for events, you can call the subscribeToEvents and pass an optional callback to handle the event data
|
||||||
await host.subscribeToEvents((eventData) => console.log(eventData, eventData.source));
|
await host.subscribeToEvents((eventData) => console.log(eventData, eventData.source));
|
||||||
// To unsubscribe, call the unsubscribeEvents
|
// to unsubscribe, call the unsubscribeEvents
|
||||||
await host.unsubscribeEvents();
|
await host.unsubscribeEvents();
|
||||||
// To start & stop the heartbeat events
|
// to start & stop the heartbeat events
|
||||||
await host.startHeartbeat();
|
await host.startHeartbeat(heartbeatInterval?);
|
||||||
await host.stopHeartbeat();
|
await host.stopHeartbeat();
|
||||||
// The host will automatically connect to nats on start. to connect/reconnect to nats
|
// the host will automatically connect to nats on start. to connect/reconnect to nats
|
||||||
await host.connectNATS();
|
await host.connectNATS(["ws://locahost:4222/"], {});
|
||||||
// To close/drain all connections from nats, call the disconnectNATS() method
|
// to close/drain all connections from nats, call the disconnectNATS() method
|
||||||
await host.disconnectNATS();
|
await host.disconnectNATS();
|
||||||
// Stop the host
|
// stop the host
|
||||||
await host.stopHost();
|
await host.stopHost();
|
||||||
// Restart the host (this only needs to be called if the host is stopped, it is automatically called on the constructor)
|
// restart the host (this only needs to be called if the host is stopped, it is automatically called on the constructor)
|
||||||
await host.startHost();
|
await host.startHost();
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
**With a bundler**
|
|
||||||
|
|
||||||
There are some caveats to using with a bundler:
|
|
||||||
|
|
||||||
* The module contains `.wasm` files that need to be present alongside the final build output. Using `webpack-copy-plugin` (or `fs.copyFile` with other bundlers) can solve this issue.
|
|
||||||
|
|
||||||
* If using with `create-react-app`, the webpack config will need to be ejected via `npm run eject` OR an npm library like `react-app-rewired` can handle the config injection.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// as esm -- this will grant you access to the types/params
|
|
||||||
import { startHost } from '@wasmcloud/wasmcloud-js';
|
|
||||||
// as cjs
|
|
||||||
// const wasmcloudjs = require('@wasmcloud/wasmcloud-js);
|
|
||||||
|
|
||||||
async function cjsHost() {
|
|
||||||
const host = await wasmcloudjs.startHost('default', false, ['ws://localhost:4222'])
|
|
||||||
console.log(host);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function esmHost() {
|
|
||||||
const host = await startHost('default', false, ['ws://localhost:4222'])
|
|
||||||
console.log(host);
|
|
||||||
}
|
|
||||||
|
|
||||||
cjsHost();
|
|
||||||
esmHost();
|
|
||||||
```
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// webpack config, add this to the plugin section
|
|
||||||
plugins: [
|
|
||||||
new CopyPlugin({
|
|
||||||
patterns: [
|
|
||||||
{
|
|
||||||
from: 'node_modules/@wasmcloud/wasmcloud-js/dist/wasmcloud-rs-js/pkg/*.wasm',
|
|
||||||
to: '[name].wasm'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Node**
|
**Node**
|
||||||
|
|
||||||
*IN PROGRESS* - NodeJS does not support WebSockets natively (required by nats.ws)
|
In progress--wasm-pack compile issues with nodeJS.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
# wasmcloud-js Examples
|
|
||||||
|
|
||||||
This directory contains examples of using the `wasmcloud-js` library with sample `webpack` and `esbuild` configurations.
|
|
||||||
|
|
||||||
## Prerequisities
|
|
||||||
|
|
||||||
* NATS with WebSockets enabled
|
|
||||||
|
|
||||||
* There is sample infra via docker in the `test/infra` directory of this repo, `cd test/infra && docker-compose up`
|
|
||||||
|
|
||||||
* wasmCloud lattice (OTP Version)
|
|
||||||
|
|
||||||
* (OPTIONAL) Docker Registry with CORS configured
|
|
||||||
|
|
||||||
* If launching actors from remote registries in the browser host, CORS must be configured on the registry server
|
|
||||||
|
|
||||||
* NodeJS, NPM, npx
|
|
||||||
|
|
||||||
## Build
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ npm install # this will run and build the rust deps
|
|
||||||
$ npm install webpack esbuild copy-webpack-plugin fs
|
|
||||||
$ #### if you want to use esbuild, follow these next 2 steps ####
|
|
||||||
$ node esbuild.js # this produces the esbuild version
|
|
||||||
$ mv out-esbuild.js out.js # rename the esbuild version
|
|
||||||
$ #### if you want to use webpack, follow the steps below ####
|
|
||||||
$ npx webpack --config=example-webpack.config.js # this produces the webpack output
|
|
||||||
$ mv out-webpack.js out.js #rename the webpack version to out.js
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
1. Build the code with esbuild or webpack
|
|
||||||
|
|
||||||
2. Rename the output file to `out.js`
|
|
||||||
|
|
||||||
3. Start a web server inside this directory (e.g `python3 -m http.server` or `npx serve`)
|
|
||||||
|
|
||||||
3. Navigate to a browser `localhost:<PORT>`
|
|
||||||
|
|
||||||
4. Open the developer console to view the host output
|
|
||||||
|
|
||||||
5. In the dev tools run `host` and you will get the full host object and methods
|
|
||||||
|
|
||||||
6. Start an actor with `host.launchActor('registry:5000/image', (data) => console.log(data))`. Echo actor is recommended (the 2nd parameter here is an invocation callback to handle the data from an invocation)
|
|
||||||
|
|
||||||
7. Link the actor with a provider running in Wasmcloud (eg `httpserver`)
|
|
||||||
|
|
||||||
8. Run a `curl localhost:port/echo` to see the response in the console (based off the invocation callback).
|
|
|
@ -1,18 +0,0 @@
|
||||||
// Use this path in projects using the node import
|
|
||||||
let defaultWasmFileLocation = './node_modules/@wasmcloud/wasmcloud-js/dist/wasmcloud-rs-js/pkgwasmcloud.wasm'
|
|
||||||
let wasmFileLocationForLocal = '../../dist/wasmcloud-rs-js/pkg/wasmcloud.wasm'
|
|
||||||
|
|
||||||
let copyPlugin = {
|
|
||||||
name: 'copy',
|
|
||||||
setup(build) {
|
|
||||||
require('fs').copyFile(wasmFileLocationForLocal, `${process.cwd()}/wasmcloud.wasm`, (err) => {
|
|
||||||
if (err) throw err;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
require('esbuild').build({
|
|
||||||
entryPoints: ['main.js'],
|
|
||||||
bundle: true,
|
|
||||||
outfile: 'out-esbuild.js',
|
|
||||||
plugins: [copyPlugin]
|
|
||||||
}).catch(() => process.exit(1))
|
|
|
@ -1,41 +0,0 @@
|
||||||
const path = require('path');
|
|
||||||
const CopyPlugin = require('copy-webpack-plugin')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
stats: { assets: false, modules: false },
|
|
||||||
entry: './main.js',
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.tsx?$/,
|
|
||||||
use: 'ts-loader',
|
|
||||||
exclude: /node_modules/
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
// this is needed to copy the wasm file used by the js code to initiate a host key/extract the token from a signed actor
|
|
||||||
// this SHOULD go away once the upstream webpack build issues are resolved (webpack will automatically pick up the webpack file without needing a copy)
|
|
||||||
plugins: [
|
|
||||||
new CopyPlugin({
|
|
||||||
patterns: [
|
|
||||||
{
|
|
||||||
// the node_module path should be referenced in projects using the node import
|
|
||||||
// from: 'node_modules/@wasmcloud/wasmcloud-js/dist/*.wasm',
|
|
||||||
from: '../../dist/wasmcloud-rs-js/pkg/*.wasm',
|
|
||||||
to: '[name].wasm'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
],
|
|
||||||
mode: 'production',
|
|
||||||
resolve: {
|
|
||||||
extensions: ['.tsx', '.ts', '.js', '.wasm']
|
|
||||||
},
|
|
||||||
experiments: {
|
|
||||||
asyncWebAssembly: true
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
filename: 'out-webpack.js',
|
|
||||||
path: path.resolve(__dirname, '')
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<title>Hello World - wasmcloudjs</title>
|
|
||||||
<!-- <script src="https://unpkg.com/@wasmcloud/wasmcloud-js@1.0.4/dist/wasmcloud.js"></script> -->
|
|
||||||
<script src="./out.js"></script>
|
|
||||||
|
|
||||||
<!-- this uses the npm library-->
|
|
||||||
<!-- <script src="./out-webpack.js"></script> -->
|
|
||||||
<!-- <script src="./out-esbuild.js"></script> -->
|
|
||||||
<script>
|
|
||||||
// (async() => {
|
|
||||||
// console.log("USING THE BROWSER BUNDLE")
|
|
||||||
// const host = await wasmcloudjs.startHost("default", false, ["ws://localhost:4222"])
|
|
||||||
// console.log(host);
|
|
||||||
// })()
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,12 +0,0 @@
|
||||||
import { startHost } from '../../dist/src'
|
|
||||||
|
|
||||||
// Importing inside of a project
|
|
||||||
// import { startHost } from '@wasmcloud/wasmcloud-js';
|
|
||||||
// const { startHost } = require('@wasmcloud/wasmcloud-js');
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
console.log('USING A JS BUNDLER')
|
|
||||||
const host = await startHost('default', false, ['ws://localhost:6222'])
|
|
||||||
window.host = host;
|
|
||||||
console.log(host);
|
|
||||||
})()
|
|
File diff suppressed because it is too large
Load Diff
27
package.json
27
package.json
|
@ -1,32 +1,31 @@
|
||||||
{
|
{
|
||||||
"name": "@wasmcloud/wasmcloud-js",
|
"name": "@wasmcloud/wasmcloud-js",
|
||||||
"version": "1.0.6",
|
"version": "1.0.0",
|
||||||
"description": "wasmcloud host in JavaScript/Browser",
|
"description": "wasmcloud host in JavaScript/Browser",
|
||||||
"main": "dist/src/index.js",
|
"main": "dist/src/index.js",
|
||||||
"types": "dist/src/index.d.ts",
|
"types": "dist/src/index.d.ts",
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
"src",
|
"src",
|
||||||
"README.md",
|
"wasmcloud-rs-js",
|
||||||
"wasmcloud-rs-js"
|
"README.md"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm run clean && npm run build:browser && npm run build:cjs",
|
"build": "npm run clean && npm run build:browser && npm run build:cjs",
|
||||||
"build:browser": "webpack",
|
"build:browser": "webpack --mode=production",
|
||||||
"build:cjs": "tsc --declaration && webpack --env target=cjs",
|
"build:cjs": "tsc --declaration",
|
||||||
"build:wasm": "cd wasmcloud-rs-js && wasm-pack build",
|
"build:wasm": "cd wasmcloud-rs-js && wasm-pack build",
|
||||||
"clean": "rm -rf ./dist/ && rm -rf ./wasmcloud-rs-js/pkg/",
|
"clean": "rm -rf ./dist/ && rm -rf ./wasmcloud-rs-js/pkg/",
|
||||||
"lint": "eslint --ext .ts src test",
|
"lint": "eslint --ext .ts src test",
|
||||||
"format": "prettier --write 'src/**/*.ts' 'test/**/*.ts'",
|
"format": "prettier --write 'src/**/*.ts' 'test/**/*.ts'",
|
||||||
"test": "mocha --require ts-node/register",
|
"test": "mocha",
|
||||||
"watch": "npm run clean && tsc -w --declrataion",
|
"watch": "npm run clean && tsc -w --declrataion",
|
||||||
"prepare": "npm run build"
|
"prepare": "npm run build"
|
||||||
},
|
},
|
||||||
"prettier": "./.prettierrc.json",
|
"prettier": ".prettierrc.json",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"wasmcloud",
|
"wasmcloud",
|
||||||
"wasmcloud-host",
|
"wasmcloud-host",
|
||||||
"wasmcloud-js",
|
|
||||||
"wasm"
|
"wasm"
|
||||||
],
|
],
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
|
@ -46,26 +45,24 @@
|
||||||
"extension": [
|
"extension": [
|
||||||
"ts"
|
"ts"
|
||||||
],
|
],
|
||||||
"spec": "test/**/*.test.ts"
|
"spec": "test/**/*.test.ts",
|
||||||
|
"require": "ts-node/register"
|
||||||
},
|
},
|
||||||
"author": "ks2211 <kaushik@cosmonic.com>",
|
"author": "ks2211 <kaushik@cosmonic.com>",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.15.0",
|
|
||||||
"@babel/preset-env": "^7.15.0",
|
|
||||||
"@types/chai": "^4.2.21",
|
"@types/chai": "^4.2.21",
|
||||||
"@types/chai-as-promised": "^7.1.4",
|
"@types/chai-as-promised": "^7.1.4",
|
||||||
"@types/mocha": "^9.0.0",
|
"@types/mocha": "^9.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.22.0",
|
"@typescript-eslint/eslint-plugin": "^4.22.0",
|
||||||
"@typescript-eslint/parser": "^4.29.2",
|
"@typescript-eslint/parser": "^4.29.2",
|
||||||
"@wasm-tool/wasm-pack-plugin": "^1.5.0",
|
"@wasm-tool/wasm-pack-plugin": "^1.5.0",
|
||||||
"babel-loader": "^8.2.2",
|
|
||||||
"chai": "^4.3.4",
|
"chai": "^4.3.4",
|
||||||
"chai-as-promised": "^7.1.1",
|
"chai-as-promised": "^7.1.1",
|
||||||
|
"esbuild": "^0.12.20",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^7.32.0",
|
||||||
"mocha": "^9.0.3",
|
"mocha": "^9.0.3",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"prettier": "^2.3.2",
|
|
||||||
"ts-loader": "^9.2.5",
|
"ts-loader": "^9.2.5",
|
||||||
"ts-node": "^10.2.1",
|
"ts-node": "^10.2.1",
|
||||||
"typescript": "^4.3.5",
|
"typescript": "^4.3.5",
|
||||||
|
@ -73,9 +70,9 @@
|
||||||
"webpack-cli": "^4.8.0"
|
"webpack-cli": "^4.8.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@msgpack/msgpack": "^2.7.1",
|
"@msgpack/msgpack": "^2.7.0",
|
||||||
"@wapc/host": "0.0.2",
|
"@wapc/host": "0.0.2",
|
||||||
"axios": "^0.24.0",
|
"axios": "^0.21.1",
|
||||||
"nats.ws": "^1.2.0"
|
"nats.ws": "^1.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
124
src/actor.ts
124
src/actor.ts
|
@ -1,42 +1,24 @@
|
||||||
import { encode, decode } from '@msgpack/msgpack';
|
import { encode, decode } from '@msgpack/msgpack';
|
||||||
|
import { instantiate, WapcHost } from '@wapc/host';
|
||||||
import { NatsConnection, Subscription } from 'nats.ws';
|
import { NatsConnection, Subscription } from 'nats.ws';
|
||||||
|
|
||||||
import { instantiate, Wasmbus } from './wasmbus';
|
|
||||||
import { createEventMessage, EventType } from './events';
|
import { createEventMessage, EventType } from './events';
|
||||||
import {
|
import {
|
||||||
ActorClaims,
|
ActorClaims, ActorClaimsMessage, ActorStartedMessage, ActorHealthCheckPassMessage,
|
||||||
ActorClaimsMessage,
|
|
||||||
ActorStartedMessage,
|
|
||||||
ActorHealthCheckPassMessage,
|
|
||||||
InvocationMessage,
|
InvocationMessage,
|
||||||
StopActorMessage,
|
StopActorMessage
|
||||||
HostCall,
|
|
||||||
Writer
|
|
||||||
} from './types';
|
} from './types';
|
||||||
import { jsonEncode, parseJwt, uuidv4 } from './util';
|
import { jsonEncode, parseJwt, uuidv4 } from './util';
|
||||||
|
|
||||||
/**
|
|
||||||
* Actor holds the actor wasm module
|
|
||||||
*/
|
|
||||||
export class Actor {
|
export class Actor {
|
||||||
claims: ActorClaims;
|
claims: ActorClaims;
|
||||||
key: string;
|
key: string;
|
||||||
module!: Wasmbus;
|
module!: WapcHost;
|
||||||
hostKey: string;
|
hostKey: string;
|
||||||
hostName: string;
|
hostName: string;
|
||||||
wasm: any;
|
wasm: any;
|
||||||
invocationCallback?: Function;
|
|
||||||
hostCall?: HostCall;
|
|
||||||
writer?: Writer;
|
|
||||||
|
|
||||||
constructor(
|
constructor(hostName: string = 'default', hostKey: string, wasm: any) {
|
||||||
hostName: string = 'default',
|
|
||||||
hostKey: string,
|
|
||||||
wasm: any,
|
|
||||||
invocationCallback?: Function,
|
|
||||||
hostCall?: HostCall,
|
|
||||||
writer?: Writer
|
|
||||||
) {
|
|
||||||
this.key = '';
|
this.key = '';
|
||||||
this.hostName = hostName;
|
this.hostName = hostName;
|
||||||
this.hostKey = hostKey;
|
this.hostKey = hostKey;
|
||||||
|
@ -55,16 +37,8 @@ export class Actor {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.wasm = wasm;
|
this.wasm = wasm;
|
||||||
this.invocationCallback = invocationCallback;
|
|
||||||
this.hostCall = hostCall;
|
|
||||||
this.writer = writer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* startActor takes an actor wasm uint8array, extracts the jwt, validates the jwt, and uses wapcJS to instantiate the module
|
|
||||||
*
|
|
||||||
* @param {Uint8Array} actorBuffer - the wasm actor module as uint8array
|
|
||||||
*/
|
|
||||||
async startActor(actorBuffer: Uint8Array) {
|
async startActor(actorBuffer: Uint8Array) {
|
||||||
const token: string = await this.wasm.extract_jwt(actorBuffer);
|
const token: string = await this.wasm.extract_jwt(actorBuffer);
|
||||||
const valid: boolean = await this.wasm.validate_jwt(token);
|
const valid: boolean = await this.wasm.validate_jwt(token);
|
||||||
|
@ -73,27 +47,17 @@ export class Actor {
|
||||||
}
|
}
|
||||||
this.claims = parseJwt(token);
|
this.claims = parseJwt(token);
|
||||||
this.key = this.claims.sub;
|
this.key = this.claims.sub;
|
||||||
this.module = await instantiate(actorBuffer, this.hostCall, this.writer);
|
this.module = await instantiate(actorBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* stopActor publishes the stop_actor message
|
|
||||||
*
|
|
||||||
* @param {NatsConnection} natsConn - the nats connection object
|
|
||||||
*/
|
|
||||||
async stopActor(natsConn: NatsConnection) {
|
async stopActor(natsConn: NatsConnection) {
|
||||||
const actorToStop: StopActorMessage = {
|
const actorToStop: StopActorMessage = {
|
||||||
host_id: this.hostKey,
|
host_id: this.hostKey,
|
||||||
actor_ref: this.key
|
actor_ref: this.key
|
||||||
};
|
}
|
||||||
natsConn.publish(`wasmbus.ctl.${this.hostName}.cmd.${this.hostKey}.sa`, jsonEncode(actorToStop));
|
natsConn.publish(`wasmbus.ctl.${this.hostName}.cmd.${this.hostKey}.sa`, jsonEncode(actorToStop))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* publishActorStarted publishes the claims, the actor_started, and health_check_pass messages
|
|
||||||
*
|
|
||||||
* @param {NatsConnection} natsConn - the natsConnection object
|
|
||||||
*/
|
|
||||||
async publishActorStarted(natsConn: NatsConnection) {
|
async publishActorStarted(natsConn: NatsConnection) {
|
||||||
// publish claims
|
// publish claims
|
||||||
const claims: ActorClaimsMessage = {
|
const claims: ActorClaimsMessage = {
|
||||||
|
@ -105,84 +69,58 @@ export class Actor {
|
||||||
sub: this.claims.sub,
|
sub: this.claims.sub,
|
||||||
tags: '',
|
tags: '',
|
||||||
version: this.claims.wascap.ver
|
version: this.claims.wascap.ver
|
||||||
};
|
}
|
||||||
natsConn.publish(`lc.${this.hostName}.claims.${this.key}`, jsonEncode(claims));
|
natsConn.publish(`lc.${this.hostName}.claims.${this.key}`, jsonEncode(claims))
|
||||||
|
|
||||||
// publish actor_started
|
// publish actor_started
|
||||||
const actorStarted: ActorStartedMessage = {
|
const actorStarted: ActorStartedMessage = {
|
||||||
api_version: 0,
|
api_version: 0,
|
||||||
instance_id: uuidv4(),
|
instance_id: uuidv4(),
|
||||||
public_key: this.key
|
public_key: this.key
|
||||||
};
|
}
|
||||||
natsConn.publish(
|
natsConn.publish(`wasmbus.evt.${this.hostName}`, jsonEncode(createEventMessage(this.hostKey, EventType.ActorStarted, actorStarted)));
|
||||||
`wasmbus.evt.${this.hostName}`,
|
|
||||||
jsonEncode(createEventMessage(this.hostKey, EventType.ActorStarted, actorStarted))
|
|
||||||
);
|
|
||||||
|
|
||||||
// publish actor health_check
|
// publish actor health_check
|
||||||
const actorHealthCheck: ActorHealthCheckPassMessage = {
|
const actorHealthCheck: ActorHealthCheckPassMessage = {
|
||||||
instance_id: uuidv4(),
|
instance_id: uuidv4(),
|
||||||
public_key: this.key
|
public_key: this.key
|
||||||
};
|
}
|
||||||
natsConn.publish(
|
natsConn.publish(`wasmbus.evt.${this.hostName}`, jsonEncode(createEventMessage(this.hostKey, EventType.HealthCheckPass, actorHealthCheck)));
|
||||||
`wasmbus.evt.${this.hostName}`,
|
|
||||||
jsonEncode(createEventMessage(this.hostKey, EventType.HealthCheckPass, actorHealthCheck))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async subscribeInvocations(natsConn: NatsConnection, invocationCallback?: Function) {
|
||||||
* subscribeInvocations does a subscribe on nats for invocations
|
|
||||||
*
|
|
||||||
* @param {NatsConnection} natsConn the nats connection object
|
|
||||||
*/
|
|
||||||
async subscribeInvocations(natsConn: NatsConnection) {
|
|
||||||
// subscribe to topic, wait for invokes, invoke the host, if callback set, send message
|
// subscribe to topic, wait for invokes, invoke the host, if callback set, send message
|
||||||
const invocationsTopic: Subscription = natsConn.subscribe(`wasmbus.rpc.${this.hostName}.${this.key}`);
|
const invocationsTopic: Subscription = natsConn.subscribe(`wasmbus.rpc.${this.hostName}.${this.key}`);
|
||||||
for await (const invocationMessage of invocationsTopic) {
|
for await (const invocationMessage of invocationsTopic) {
|
||||||
const invocationData = decode(invocationMessage.data);
|
const invocationData = decode(invocationMessage.data);
|
||||||
const invocation: InvocationMessage = invocationData as InvocationMessage;
|
const invocation: InvocationMessage = (invocationData as InvocationMessage)
|
||||||
const invocationResult: Uint8Array = await this.module.invoke(invocation.operation, invocation.msg);
|
const invocationResult: Uint8Array = await this.module.invoke(invocation.operation, invocation.msg);
|
||||||
invocationMessage.respond(
|
invocationMessage.respond(encode({
|
||||||
encode({
|
invocation_id: (invocationData as any).id,
|
||||||
invocation_id: (invocationData as any).id,
|
instance_id: uuidv4(),
|
||||||
instance_id: uuidv4(),
|
msg: invocationResult
|
||||||
msg: invocationResult
|
}));
|
||||||
})
|
if (invocationCallback) {
|
||||||
);
|
invocationCallback(invocationResult);
|
||||||
if (this.invocationCallback) {
|
|
||||||
this.invocationCallback(invocationResult);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new Error('actor.inovcation subscription closed');
|
throw new Error('actor.inovcation subscription closed');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* startActor initializes an actor and listens for invocation messages
|
export async function newActor(hostName: string, hostKey: string,
|
||||||
*
|
|
||||||
* @param {string} hostName - the name of the host
|
|
||||||
* @param {string} hostKey - the publickey of the host
|
|
||||||
* @param {Uint8Array} actorModule - the wasm module of the actor
|
|
||||||
* @param {NatsConnection} natsConn - the nats connection object
|
|
||||||
* @param {any} wasm - the rust wasm module
|
|
||||||
* @param {Function} invocationCallback - an optional function to call when the invocation is successful
|
|
||||||
* @param {HostCall} hostCall - the hostCallback
|
|
||||||
* @param {Writer} writer - the hostCallback writer
|
|
||||||
* @returns {Actor}
|
|
||||||
*/
|
|
||||||
export async function startActor(
|
|
||||||
hostName: string,
|
|
||||||
hostKey: string,
|
|
||||||
actorModule: Uint8Array,
|
actorModule: Uint8Array,
|
||||||
natsConn: NatsConnection,
|
natsConn: NatsConnection,
|
||||||
wasm: any,
|
wasm: any,
|
||||||
invocationCallback?: Function,
|
invocationCallback?: Function
|
||||||
hostCall?: HostCall,
|
|
||||||
writer?: Writer
|
|
||||||
): Promise<Actor> {
|
): Promise<Actor> {
|
||||||
const actor: Actor = new Actor(hostName, hostKey, wasm, invocationCallback, hostCall, writer);
|
const actor: Actor = new Actor(hostName, hostKey, wasm);
|
||||||
await actor.startActor(actorModule);
|
await actor.startActor(actorModule);
|
||||||
await actor.publishActorStarted(natsConn);
|
await actor.publishActorStarted(natsConn);
|
||||||
Promise.all([actor.subscribeInvocations(natsConn)]).catch(err => {
|
Promise.all([
|
||||||
|
actor.subscribeInvocations(natsConn, invocationCallback)
|
||||||
|
]).catch((err) => {
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
return actor;
|
return actor;
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { uuidv4 } from './util';
|
import { uuidv4 } from './util'
|
||||||
|
|
||||||
export enum EventType {
|
export enum EventType {
|
||||||
HeartBeat = 'com.wasmcloud.lattice.host_heartbeat',
|
HeartBeat = 'com.wasmcloud.lattice.host_heartbeat',
|
||||||
ActorStarted = 'com.wasmcloud.lattice.actor_started',
|
ActorStarted = 'com.wasmcloud.lattice.actor_started',
|
||||||
ActorStopped = 'com.wasmcloud.lattice.actor_stopped',
|
ActorStopped = 'com.wasmcloud.lattice.actor_stopped',
|
||||||
HealthCheckPass = 'com.wasmcloud.lattice.health_check_passed'
|
HealthCheckPass = 'com.wasmcloud.lattice.health_check_passed',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EventData = {
|
export type EventData = {
|
||||||
|
@ -15,16 +15,8 @@ export type EventData = {
|
||||||
time: string;
|
time: string;
|
||||||
type: EventType;
|
type: EventType;
|
||||||
data: any;
|
data: any;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* createEventMessage is a helper function to create a message for "wasmbus.evt.{host}"
|
|
||||||
*
|
|
||||||
* @param {string} hostKey - the host public key
|
|
||||||
* @param {EventType} eventType - the event type using the EventType enum
|
|
||||||
* @param {any} data - the json data object
|
|
||||||
* @returns {EventData}
|
|
||||||
*/
|
|
||||||
export function createEventMessage(hostKey: string, eventType: EventType, data: any): EventData {
|
export function createEventMessage(hostKey: string, eventType: EventType, data: any): EventData {
|
||||||
return {
|
return {
|
||||||
data: data,
|
data: data,
|
||||||
|
@ -34,5 +26,5 @@ export function createEventMessage(hostKey: string, eventType: EventType, data:
|
||||||
specversion: '1.0',
|
specversion: '1.0',
|
||||||
time: new Date().toISOString(),
|
time: new Date().toISOString(),
|
||||||
type: eventType
|
type: eventType
|
||||||
};
|
}
|
||||||
}
|
}
|
43
src/fetch.ts
43
src/fetch.ts
|
@ -4,7 +4,7 @@ export type ImageDigest = {
|
||||||
name: string;
|
name: string;
|
||||||
digest: string;
|
digest: string;
|
||||||
registry: string;
|
registry: string;
|
||||||
};
|
}
|
||||||
|
|
||||||
type FetchActorDigestResponse = {
|
type FetchActorDigestResponse = {
|
||||||
schemaVersion: number;
|
schemaVersion: number;
|
||||||
|
@ -19,7 +19,7 @@ type FetchActorDigestResponse = {
|
||||||
layers: Array<{
|
layers: Array<{
|
||||||
annotations: {
|
annotations: {
|
||||||
['org.opencontainers.image.title']: string;
|
['org.opencontainers.image.title']: string;
|
||||||
};
|
}
|
||||||
digest: string;
|
digest: string;
|
||||||
mediaType: string;
|
mediaType: string;
|
||||||
size: number;
|
size: number;
|
||||||
|
@ -28,27 +28,18 @@ type FetchActorDigestResponse = {
|
||||||
annotations: any;
|
annotations: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* fetchActorDigest fetches the actor digest from a registry (sha)
|
|
||||||
*
|
|
||||||
* @param {string} actorRef - the actor url e.g host:port/image:version
|
|
||||||
* @param {boolean} withTLS - whether or not the registry uses tls
|
|
||||||
* @returns {ImageDigest}
|
|
||||||
*/
|
|
||||||
export async function fetchActorDigest(actorRef: string, withTLS?: boolean): Promise<ImageDigest> {
|
export async function fetchActorDigest(actorRef: string, withTLS?: boolean): Promise<ImageDigest> {
|
||||||
const image: Array<string> = actorRef.split('/');
|
const image: Array<string> = actorRef.split('/');
|
||||||
const registry: string = image[0];
|
const registry: string = image[0];
|
||||||
const [name, version] = image[1].split(':');
|
const [name, version] = image[1].split(':');
|
||||||
|
|
||||||
const response: AxiosResponse = await axios
|
const response: AxiosResponse = await axios.get(
|
||||||
.get(`${withTLS ? 'https://' : 'http://'}${registry}/v2/${name}/manifests/${version}`, {
|
`${withTLS ? 'https://' : 'http://'}${registry}/v2/${name}/manifests/${version}`,
|
||||||
|
{
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/vnd.oci.image.manifest.v1+json'
|
'Accept': 'application/vnd.oci.image.manifest.v1+json'
|
||||||
}
|
}
|
||||||
})
|
}).catch((err) => { throw err });
|
||||||
.catch(err => {
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
const layers: FetchActorDigestResponse = response.data;
|
const layers: FetchActorDigestResponse = response.data;
|
||||||
|
|
||||||
if (layers.layers.length === 0) {
|
if (layers.layers.length === 0) {
|
||||||
|
@ -59,23 +50,17 @@ export async function fetchActorDigest(actorRef: string, withTLS?: boolean): Pro
|
||||||
name,
|
name,
|
||||||
digest: layers.layers[0].digest,
|
digest: layers.layers[0].digest,
|
||||||
registry
|
registry
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* fetchActor fetches an actor from either the local disk or a registry and returns it as uint8array
|
|
||||||
*
|
|
||||||
* @param {string} url - the url of the actor module
|
|
||||||
* @returns {Uint8Array}
|
|
||||||
*/
|
|
||||||
export async function fetchActor(url: string): Promise<Uint8Array> {
|
export async function fetchActor(url: string): Promise<Uint8Array> {
|
||||||
const response: AxiosResponse = await axios
|
const response: AxiosResponse = await axios.get(
|
||||||
.get(url, {
|
url,
|
||||||
|
{
|
||||||
responseType: 'arraybuffer'
|
responseType: 'arraybuffer'
|
||||||
})
|
}
|
||||||
.catch(err => {
|
).catch((err) => { throw err });
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Uint8Array(response.data);
|
return new Uint8Array(response.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
180
src/host.ts
180
src/host.ts
|
@ -1,7 +1,11 @@
|
||||||
import { encode } from '@msgpack/msgpack';
|
import { encode } from '@msgpack/msgpack';
|
||||||
import { connect, ConnectionOptions, NatsConnection, Subscription } from 'nats.ws';
|
import {
|
||||||
|
connect, ConnectionOptions,
|
||||||
|
NatsConnection,
|
||||||
|
Subscription
|
||||||
|
} from 'nats.ws';
|
||||||
|
|
||||||
import { Actor, startActor } from './actor';
|
import { Actor, newActor } from './actor';
|
||||||
import { createEventMessage, EventType } from './events';
|
import { createEventMessage, EventType } from './events';
|
||||||
import { fetchActor, fetchActorDigest, ImageDigest } from './fetch';
|
import { fetchActor, fetchActorDigest, ImageDigest } from './fetch';
|
||||||
import {
|
import {
|
||||||
|
@ -10,23 +14,19 @@ import {
|
||||||
HeartbeatMessage,
|
HeartbeatMessage,
|
||||||
InvocationCallbacks,
|
InvocationCallbacks,
|
||||||
LaunchActorMessage,
|
LaunchActorMessage,
|
||||||
StopActorMessage,
|
StopActorMessage
|
||||||
HostCall,
|
|
||||||
Writer
|
|
||||||
} from './types';
|
} from './types';
|
||||||
import { jsonDecode, jsonEncode, uuidv4 } from './util';
|
import { jsonDecode, jsonEncode, uuidv4 } from './util';
|
||||||
|
|
||||||
const HOST_HEARTBEAT_INTERVAL: number = 30000;
|
const HOST_HEARTBEAT_INTERVAL: number = 30000;
|
||||||
|
|
||||||
/**
|
|
||||||
* Host holds the js/browser host
|
|
||||||
*/
|
|
||||||
export class Host {
|
export class Host {
|
||||||
name: string;
|
name: string;
|
||||||
key: string;
|
key: string;
|
||||||
seed: string;
|
seed: string;
|
||||||
heartbeatInterval: number;
|
heartbeatInterval: number;
|
||||||
heartbeatIntervalId: any;
|
heartbeatIntervalId: any;
|
||||||
|
invocationCallbacks?: InvocationCallbacks;
|
||||||
withRegistryTLS: boolean;
|
withRegistryTLS: boolean;
|
||||||
actors: {
|
actors: {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
|
@ -38,20 +38,14 @@ export class Host {
|
||||||
natsConn!: NatsConnection;
|
natsConn!: NatsConnection;
|
||||||
eventsSubscription!: Subscription | null;
|
eventsSubscription!: Subscription | null;
|
||||||
wasm: any;
|
wasm: any;
|
||||||
invocationCallbacks?: InvocationCallbacks;
|
|
||||||
hostCalls?: {
|
|
||||||
[key: string]: HostCall;
|
|
||||||
};
|
|
||||||
writers?: {
|
|
||||||
[key: string]: Writer;
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
name: string = 'default',
|
constructor(name: string = 'default',
|
||||||
withRegistryTLS: boolean,
|
withRegistryTLS: boolean,
|
||||||
heartbeatInterval: number,
|
heartbeatInterval: number,
|
||||||
natsConnOpts: Array<string> | ConnectionOptions,
|
natsConnOpts: Array<string> | ConnectionOptions,
|
||||||
wasm: any
|
wasm: any,
|
||||||
|
invocationCallbacks?: InvocationCallbacks
|
||||||
) {
|
) {
|
||||||
const hostKey = new wasm.HostKey();
|
const hostKey = new wasm.HostKey();
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
@ -62,68 +56,46 @@ export class Host {
|
||||||
this.wasm = wasm;
|
this.wasm = wasm;
|
||||||
this.heartbeatInterval = heartbeatInterval;
|
this.heartbeatInterval = heartbeatInterval;
|
||||||
this.natsConnOpts = natsConnOpts;
|
this.natsConnOpts = natsConnOpts;
|
||||||
this.invocationCallbacks = {};
|
this.invocationCallbacks = invocationCallbacks;
|
||||||
this.hostCalls = {};
|
|
||||||
this.writers = {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* connectNATS connects to nats using either the array of servers or the connection options object
|
|
||||||
*/
|
|
||||||
async connectNATS() {
|
async connectNATS() {
|
||||||
const opts: ConnectionOptions = Array.isArray(this.natsConnOpts)
|
const opts: ConnectionOptions = (Array.isArray(this.natsConnOpts)) ? {
|
||||||
? {
|
servers: this.natsConnOpts
|
||||||
servers: this.natsConnOpts
|
} : this.natsConnOpts;
|
||||||
}
|
|
||||||
: this.natsConnOpts;
|
|
||||||
this.natsConn = await connect(opts);
|
this.natsConn = await connect(opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* disconnectNATS disconnects from nats
|
|
||||||
*/
|
|
||||||
async disconnectNATS() {
|
async disconnectNATS() {
|
||||||
this.natsConn.close();
|
this.natsConn.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* startHeartbeat starts a heartbeat publish message every X seconds based on the interval
|
|
||||||
*/
|
|
||||||
async startHeartbeat() {
|
async startHeartbeat() {
|
||||||
this.heartbeatIntervalId;
|
this.heartbeatIntervalId;
|
||||||
const heartbeat: HeartbeatMessage = {
|
const heartbeat: HeartbeatMessage = {
|
||||||
actors: [],
|
actors: [],
|
||||||
providers: []
|
providers: []
|
||||||
};
|
}
|
||||||
for (const actor in this.actors) {
|
for (const actor in this.actors) {
|
||||||
heartbeat.actors.push({
|
heartbeat.actors.push({
|
||||||
actor: actor,
|
actor: actor,
|
||||||
instances: 1
|
instances: 1
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
this.heartbeatIntervalId = await setInterval(() => {
|
this.heartbeatIntervalId = await setInterval(() => {
|
||||||
this.natsConn.publish(
|
this.natsConn.publish(`wasmbus.evt.${this.name}`,
|
||||||
`wasmbus.evt.${this.name}`,
|
|
||||||
jsonEncode(createEventMessage(this.key, EventType.HeartBeat, heartbeat))
|
jsonEncode(createEventMessage(this.key, EventType.HeartBeat, heartbeat))
|
||||||
);
|
);
|
||||||
}, this.heartbeatInterval);
|
}, this.heartbeatInterval)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* stopHeartbeat clears the heartbeat interval
|
|
||||||
*/
|
|
||||||
async stopHeartbeat() {
|
async stopHeartbeat() {
|
||||||
clearInterval(this.heartbeatIntervalId);
|
clearInterval(this.heartbeatIntervalId);
|
||||||
this.heartbeatIntervalId = null;
|
this.heartbeatIntervalId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* subscribeToEvents subscribes to the events on the host
|
|
||||||
*
|
|
||||||
* @param eventCallback - an optional callback(data) to handle the event message
|
|
||||||
*/
|
|
||||||
async subscribeToEvents(eventCallback?: Function) {
|
async subscribeToEvents(eventCallback?: Function) {
|
||||||
this.eventsSubscription = this.natsConn.subscribe(`wasmbus.evt.${this.name}`);
|
this.eventsSubscription = this.natsConn.subscribe(`wasmbus.evt.${this.name}`)
|
||||||
for await (const event of this.eventsSubscription) {
|
for await (const event of this.eventsSubscription) {
|
||||||
const eventData = jsonDecode(event.data);
|
const eventData = jsonDecode(event.data);
|
||||||
if (eventCallback) {
|
if (eventCallback) {
|
||||||
|
@ -133,55 +105,28 @@ export class Host {
|
||||||
throw new Error('evt subscription was closed');
|
throw new Error('evt subscription was closed');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* unsubscribeEvents unsubscribes and removes the events subscription
|
|
||||||
*/
|
|
||||||
async unsubscribeEvents() {
|
async unsubscribeEvents() {
|
||||||
this.eventsSubscription?.unsubscribe();
|
this.eventsSubscription?.unsubscribe();
|
||||||
this.eventsSubscription = null;
|
this.eventsSubscription = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* launchActor launches an actor via the launch actor message
|
async launchActor(actorRef: string) {
|
||||||
*
|
|
||||||
* @param actorRef - the actor to start
|
|
||||||
* @param invocationCallback - an optional callback(data) to handle the invocation
|
|
||||||
* @param hostCall - the hostCallback
|
|
||||||
* @param writer - writer for the hostCallback, can be undefined
|
|
||||||
*/
|
|
||||||
async launchActor(actorRef: string, invocationCallback?: Function, hostCall?: HostCall, writer?: Writer) {
|
|
||||||
const actor: LaunchActorMessage = {
|
const actor: LaunchActorMessage = {
|
||||||
actor_ref: actorRef,
|
actor_ref: actorRef,
|
||||||
host_id: this.key
|
host_id: this.key
|
||||||
};
|
}
|
||||||
this.natsConn.publish(`wasmbus.ctl.${this.name}.cmd.${this.key}.la`, jsonEncode(actor));
|
this.natsConn.publish(`wasmbus.ctl.${this.name}.cmd.${this.key}.la`, jsonEncode(actor));
|
||||||
if (invocationCallback) {
|
|
||||||
this.invocationCallbacks![actorRef] = invocationCallback;
|
|
||||||
}
|
|
||||||
if (hostCall) {
|
|
||||||
this.hostCalls![actorRef] = hostCall;
|
|
||||||
}
|
|
||||||
if (writer) {
|
|
||||||
this.writers![actorRef] = writer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* stopActor stops an actor by publishing the sa message
|
|
||||||
*
|
|
||||||
* @param {string} actorRef - the actor to stop
|
|
||||||
*/
|
|
||||||
async stopActor(actorRef: string) {
|
async stopActor(actorRef: string) {
|
||||||
const actorToStop: StopActorMessage = {
|
const actorToStop: StopActorMessage = {
|
||||||
host_id: this.key,
|
host_id: this.key,
|
||||||
actor_ref: actorRef
|
actor_ref: actorRef
|
||||||
};
|
}
|
||||||
this.natsConn.publish(`wasmbus.ctl.${this.name}.cmd.${this.key}.sa`, jsonEncode(actorToStop));
|
this.natsConn.publish(`wasmbus.ctl.${this.name}.cmd.${this.key}.sa`, jsonEncode(actorToStop))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* listenLaunchActor listens for start actor message and will fetch the actor (either from disk or registry) and initialize the actor
|
|
||||||
*/
|
|
||||||
async listenLaunchActor() {
|
async listenLaunchActor() {
|
||||||
// subscribe to the .la topic `wasmbus.ctl.${this.name}.cmd.${this.key}.la`
|
// subscribe to the .la topic `wasmbus.ctl.${this.name}.cmd.${this.key}.la`
|
||||||
// decode the data
|
// decode the data
|
||||||
|
@ -197,22 +142,16 @@ export class Host {
|
||||||
let url: string;
|
let url: string;
|
||||||
if (usingRegistry) {
|
if (usingRegistry) {
|
||||||
const actorDigest: ImageDigest = await fetchActorDigest(actorRef);
|
const actorDigest: ImageDigest = await fetchActorDigest(actorRef);
|
||||||
url = `${this.withRegistryTLS ? 'https://' : 'http://'}${actorDigest.registry}/v2/${actorDigest.name}/blobs/${
|
url = `${this.withRegistryTLS ? 'https://' : 'http://'}${actorDigest.registry}/v2/${actorDigest.name}/blobs/${actorDigest.digest}`
|
||||||
actorDigest.digest
|
|
||||||
}`;
|
|
||||||
} else {
|
} else {
|
||||||
url = actorRef;
|
url = actorRef;
|
||||||
}
|
}
|
||||||
const actorModule: Uint8Array = await fetchActor(url);
|
const actorModule: Uint8Array = await fetchActor(url);
|
||||||
const actor: Actor = await startActor(
|
const actor: Actor = await newActor(this.name, this.key,
|
||||||
this.name,
|
|
||||||
this.key,
|
|
||||||
actorModule,
|
actorModule,
|
||||||
this.natsConn,
|
this.natsConn,
|
||||||
this.wasm,
|
this.wasm,
|
||||||
this.invocationCallbacks?.[actorRef],
|
this.invocationCallbacks?.[actorRef]
|
||||||
this.hostCalls?.[actorRef],
|
|
||||||
this.writers?.[actorRef]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.actors[actorRef]) {
|
if (this.actors[actorRef]) {
|
||||||
|
@ -221,49 +160,34 @@ export class Host {
|
||||||
this.actors[actorRef] = {
|
this.actors[actorRef] = {
|
||||||
count: 1,
|
count: 1,
|
||||||
actor: actor
|
actor: actor
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// TODO: error handling
|
// TODO: error handling
|
||||||
console.log('error', err);
|
console.log('error', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new Error('la.subscription was closed');
|
throw new Error('la.subscription was closed')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* listenStopActor listens for the actor stopped message and will tear down the actor on message receive
|
|
||||||
*/
|
|
||||||
async listenStopActor() {
|
async listenStopActor() {
|
||||||
// listen for stop actor message, decode the data
|
// listen for stop actor message, decode the data
|
||||||
// publish actor_stopped to the lattice
|
// publish actor_stopped to the lattice
|
||||||
// delete the actor from the host and remove the invocation callback
|
// delete the actor from the host
|
||||||
const actorsTopic: Subscription = this.natsConn.subscribe(`wasmbus.ctl.${this.name}.cmd.${this.key}.sa`);
|
const actorsTopic: Subscription = this.natsConn.subscribe(`wasmbus.ctl.${this.name}.cmd.${this.key}.sa`);
|
||||||
for await (const actorMessage of actorsTopic) {
|
for await (const actorMessage of actorsTopic) {
|
||||||
const actorData = jsonDecode(actorMessage.data);
|
const actorData = jsonDecode(actorMessage.data);
|
||||||
const actorStop: ActorStoppedMessage = {
|
const actorStop: ActorStoppedMessage = {
|
||||||
instance_id: uuidv4(),
|
instance_id: uuidv4(),
|
||||||
public_key: this.actors[(actorData as StopActorMessage).actor_ref].actor.key
|
public_key: this.actors[(actorData as StopActorMessage).actor_ref].actor.key
|
||||||
};
|
}
|
||||||
this.natsConn.publish(
|
this.natsConn.publish(`wasmbus.evt.${this.name}`,
|
||||||
`wasmbus.evt.${this.name}`,
|
jsonEncode(createEventMessage(this.key, EventType.ActorStopped, actorStop)))
|
||||||
jsonEncode(createEventMessage(this.key, EventType.ActorStopped, actorStop))
|
|
||||||
);
|
|
||||||
delete this.actors[(actorData as StopActorMessage).actor_ref];
|
delete this.actors[(actorData as StopActorMessage).actor_ref];
|
||||||
delete this.invocationCallbacks![(actorData as StopActorMessage).actor_ref];
|
|
||||||
}
|
}
|
||||||
throw new Error('sa.subscription was closed');
|
throw new Error('sa.subscription was closed')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* createLinkDefinition creates a link definition between an actor and a provider (unused)
|
|
||||||
*
|
|
||||||
* @param {string} actorKey - the actor key
|
|
||||||
* @param {string} providerKey - the provider public key
|
|
||||||
* @param {string} linkName - the name of the link
|
|
||||||
* @param {string} contractId - the contract id of the linkdef
|
|
||||||
* @param {any} values - list of key/value pairs to pass for the linkdef
|
|
||||||
*/
|
|
||||||
async createLinkDefinition(actorKey: string, providerKey: string, linkName: string, contractId: string, values: any) {
|
async createLinkDefinition(actorKey: string, providerKey: string, linkName: string, contractId: string, values: any) {
|
||||||
const linkDefinition: CreateLinkDefMessage = {
|
const linkDefinition: CreateLinkDefMessage = {
|
||||||
actor_id: actorKey,
|
actor_id: actorKey,
|
||||||
|
@ -271,23 +195,21 @@ export class Host {
|
||||||
link_name: linkName,
|
link_name: linkName,
|
||||||
contract_id: contractId,
|
contract_id: contractId,
|
||||||
values: values
|
values: values
|
||||||
};
|
}
|
||||||
this.natsConn.publish(`wasmbus.rpc.${this.name}.${providerKey}.${linkName}.linkdefs.put`, encode(linkDefinition));
|
this.natsConn.publish(`wasmbus.rpc.${this.name}.${providerKey}.${linkName}.linkdefs.put`, encode(linkDefinition))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* startHost connects to nats, starts the heartbeat, listens for actors start/stop
|
|
||||||
*/
|
|
||||||
async startHost() {
|
async startHost() {
|
||||||
await this.connectNATS();
|
await this.connectNATS();
|
||||||
Promise.all([this.startHeartbeat(), this.listenLaunchActor(), this.listenStopActor()]).catch((err: Error) => {
|
Promise.all([
|
||||||
|
this.startHeartbeat(),
|
||||||
|
this.listenLaunchActor(),
|
||||||
|
this.listenStopActor()
|
||||||
|
]).catch((err: Error) => {
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* stopHost stops the heartbeat, stops all actors, drains the nats connections and disconnects from nats
|
|
||||||
*/
|
|
||||||
async stopHost() {
|
async stopHost() {
|
||||||
// stop the heartbeat
|
// stop the heartbeat
|
||||||
await this.stopHeartbeat();
|
await this.stopHeartbeat();
|
||||||
|
@ -304,29 +226,21 @@ export class Host {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* startHost is the main function to start the js/browser host
|
|
||||||
*
|
|
||||||
* @param {string} name - the name of the host (defaults to 'default')
|
|
||||||
* @param {boolean} withRegistryTLS - whether or not remote registries use tls
|
|
||||||
* @param {Array<string>|ConnectionOptions} natsConnection - an array of nats websocket servers OR a full nats connection object
|
|
||||||
* @param {number} heartbeatInterval - used to determine the heartbeat to the lattice (defaults to 30000 or 30 seconds)
|
|
||||||
* @returns {Host}
|
|
||||||
*/
|
|
||||||
export async function startHost(
|
export async function startHost(
|
||||||
name: string,
|
name: string,
|
||||||
withRegistryTLS: boolean = true,
|
withRegistryTLS: boolean = true,
|
||||||
natsConnection: Array<string> | ConnectionOptions,
|
natsConnection: Array<string> | ConnectionOptions,
|
||||||
|
invocationCallbacks?: InvocationCallbacks,
|
||||||
heartbeatInterval?: number
|
heartbeatInterval?: number
|
||||||
) {
|
) {
|
||||||
const wasmModule: any = await import('../wasmcloud-rs-js/pkg/');
|
const wasmModule: any = await import('../wasmcloud-rs-js/pkg/');
|
||||||
const wasm: any = await wasmModule.default;
|
const wasm: any = await wasmModule.default;
|
||||||
const host: Host = new Host(
|
const host: Host = new Host(name,
|
||||||
name,
|
|
||||||
withRegistryTLS,
|
withRegistryTLS,
|
||||||
heartbeatInterval ? heartbeatInterval : HOST_HEARTBEAT_INTERVAL,
|
heartbeatInterval ? heartbeatInterval : HOST_HEARTBEAT_INTERVAL,
|
||||||
natsConnection,
|
natsConnection,
|
||||||
wasm
|
wasm,
|
||||||
|
invocationCallbacks
|
||||||
);
|
);
|
||||||
await host.startHost();
|
await host.startHost();
|
||||||
return host;
|
return host;
|
||||||
|
|
27
src/types.ts
27
src/types.ts
|
@ -4,7 +4,7 @@ export type HeartbeatMessage = {
|
||||||
instances: number;
|
instances: number;
|
||||||
}>;
|
}>;
|
||||||
providers: [];
|
providers: [];
|
||||||
};
|
}
|
||||||
|
|
||||||
export type CreateLinkDefMessage = {
|
export type CreateLinkDefMessage = {
|
||||||
actor_id: string;
|
actor_id: string;
|
||||||
|
@ -12,7 +12,7 @@ export type CreateLinkDefMessage = {
|
||||||
link_name: string;
|
link_name: string;
|
||||||
contract_id: string;
|
contract_id: string;
|
||||||
values: any;
|
values: any;
|
||||||
};
|
}
|
||||||
|
|
||||||
export type ActorClaims = {
|
export type ActorClaims = {
|
||||||
jti: string;
|
jti: string;
|
||||||
|
@ -27,7 +27,7 @@ export type ActorClaims = {
|
||||||
ver: string;
|
ver: string;
|
||||||
prov: boolean;
|
prov: boolean;
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
export type ActorClaimsMessage = {
|
export type ActorClaimsMessage = {
|
||||||
call_alias: string;
|
call_alias: string;
|
||||||
|
@ -38,33 +38,33 @@ export type ActorClaimsMessage = {
|
||||||
sub: string;
|
sub: string;
|
||||||
tags: string;
|
tags: string;
|
||||||
version: string;
|
version: string;
|
||||||
};
|
}
|
||||||
|
|
||||||
export type LaunchActorMessage = {
|
export type LaunchActorMessage = {
|
||||||
actor_ref: string;
|
actor_ref: string;
|
||||||
host_id: string;
|
host_id: string;
|
||||||
};
|
}
|
||||||
|
|
||||||
export type ActorStartedMessage = {
|
export type ActorStartedMessage = {
|
||||||
api_version: number;
|
api_version: number;
|
||||||
instance_id: string;
|
instance_id: string;
|
||||||
public_key: string;
|
public_key: string;
|
||||||
};
|
}
|
||||||
|
|
||||||
export type ActorHealthCheckPassMessage = {
|
export type ActorHealthCheckPassMessage = {
|
||||||
instance_id: string;
|
instance_id: string;
|
||||||
public_key: string;
|
public_key: string;
|
||||||
};
|
}
|
||||||
|
|
||||||
export type StopActorMessage = {
|
export type StopActorMessage = {
|
||||||
host_id: string;
|
host_id: string;
|
||||||
actor_ref: string;
|
actor_ref: string;
|
||||||
};
|
}
|
||||||
|
|
||||||
export type ActorStoppedMessage = {
|
export type ActorStoppedMessage = {
|
||||||
public_key: string;
|
public_key: string;
|
||||||
instance_id: string;
|
instance_id: string;
|
||||||
};
|
}
|
||||||
|
|
||||||
export type InvocationMessage = {
|
export type InvocationMessage = {
|
||||||
encoded_claims: string;
|
encoded_claims: string;
|
||||||
|
@ -82,13 +82,8 @@ export type InvocationMessage = {
|
||||||
link_name: string;
|
link_name: string;
|
||||||
contract_id: string;
|
contract_id: string;
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
export type InvocationCallbacks = {
|
export type InvocationCallbacks = {
|
||||||
[key: string]: Function;
|
[key: string]: Function;
|
||||||
};
|
}
|
||||||
|
|
||||||
/* eslint-disable no-unused-vars */
|
|
||||||
export type HostCall = (binding: string, namespace: string, operation: string, payload: Uint8Array) => Uint8Array;
|
|
||||||
/* eslint-disable no-unused-vars */
|
|
||||||
export type Writer = (message: string) => void;
|
|
37
src/util.ts
37
src/util.ts
|
@ -2,56 +2,27 @@ import { JSONCodec } from 'nats.ws';
|
||||||
|
|
||||||
const jc = JSONCodec();
|
const jc = JSONCodec();
|
||||||
|
|
||||||
/**
|
|
||||||
* uuidv4 returns a uuid string
|
|
||||||
*
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
export function uuidv4(): string {
|
export function uuidv4(): string {
|
||||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||||
const r = (Math.random() * 16) | 0,
|
const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||||
v = c == 'x' ? r : (r & 0x3) | 0x8;
|
|
||||||
return v.toString(16);
|
return v.toString(16);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* parseJwt takes a jwt token and parses it into a json object
|
|
||||||
*
|
|
||||||
* @param token - the jwt token
|
|
||||||
* @returns {any} - the parsed jwt token with claims
|
|
||||||
*/
|
|
||||||
export function parseJwt(token: string) {
|
export function parseJwt(token: string) {
|
||||||
var base64Url = token.split('.')[1];
|
var base64Url = token.split('.')[1];
|
||||||
var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
||||||
var jsonPayload = decodeURIComponent(
|
var jsonPayload = decodeURIComponent(atob(base64).split('').map(function (c) {
|
||||||
atob(base64)
|
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
||||||
.split('')
|
}).join(''));
|
||||||
.map(function (c) {
|
|
||||||
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
|
||||||
})
|
|
||||||
.join('')
|
|
||||||
);
|
|
||||||
|
|
||||||
return JSON.parse(jsonPayload);
|
return JSON.parse(jsonPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* jsonEncode taks a json object and encodes it into uint8array for nats
|
|
||||||
*
|
|
||||||
* @param data - the data to encode
|
|
||||||
* @returns {Uint8Array}
|
|
||||||
*/
|
|
||||||
export function jsonEncode(data: any): Uint8Array {
|
export function jsonEncode(data: any): Uint8Array {
|
||||||
return jc.encode(data);
|
return jc.encode(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* jsonDecode decodes nats messages into json
|
|
||||||
*
|
|
||||||
* @param {Uint8Array} data - the nats encoded data
|
|
||||||
* @returns {any}
|
|
||||||
*/
|
|
||||||
export function jsonDecode(data: Uint8Array) {
|
export function jsonDecode(data: Uint8Array) {
|
||||||
return jc.decode(data);
|
return jc.decode(data);
|
||||||
}
|
}
|
|
@ -1,28 +0,0 @@
|
||||||
import { WapcHost } from '@wapc/host';
|
|
||||||
|
|
||||||
import { HostCall, Writer } from './types';
|
|
||||||
|
|
||||||
export async function instantiate(source: Uint8Array, hostCall?: HostCall, writer?: Writer): Promise<Wasmbus> {
|
|
||||||
const host = new Wasmbus(hostCall, writer);
|
|
||||||
return host.instantiate(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Wasmbus extends WapcHost {
|
|
||||||
constructor(hostCall?: HostCall, writer?: Writer) {
|
|
||||||
super(hostCall, writer);
|
|
||||||
}
|
|
||||||
|
|
||||||
async instantiate(source: Uint8Array): Promise<Wasmbus> {
|
|
||||||
const imports = super.getImports();
|
|
||||||
const result = await WebAssembly.instantiate(source, {
|
|
||||||
wasmbus: imports.wapc,
|
|
||||||
wasi: imports.wasi,
|
|
||||||
wasi_unstable: imports.wasi_unstable
|
|
||||||
}).catch(e => {
|
|
||||||
throw new Error(`Invalid wasm binary: ${e.message}`);
|
|
||||||
});
|
|
||||||
super.initialize(result.instance);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,11 +11,9 @@ const expect = chai.expect;
|
||||||
|
|
||||||
describe('wasmcloudjs', function () {
|
describe('wasmcloudjs', function () {
|
||||||
it('should initialize a host with the name and key set', async () => {
|
it('should initialize a host with the name and key set', async () => {
|
||||||
const host = await startHost('default', false, ['ws://localhost:4222']);
|
const host = await startHost('default', false, ["ws://localhost:4222"]);
|
||||||
expect(host.name).to.equal('default');
|
expect(host.name).to.equal('default');
|
||||||
expect(host.key)
|
expect(host.key).to.be.a('string').and.satisfy((key: string) => key.startsWith('N'));
|
||||||
.to.be.a('string')
|
|
||||||
.and.satisfy((key: string) => key.startsWith('N'));
|
|
||||||
expect(host.actors).to.equal({});
|
expect(host.actors).to.equal({});
|
||||||
});
|
})
|
||||||
});
|
})
|
|
@ -1,16 +0,0 @@
|
||||||
version: "3"
|
|
||||||
services:
|
|
||||||
registry:
|
|
||||||
image: registry:2
|
|
||||||
ports:
|
|
||||||
- "5001:5001"
|
|
||||||
volumes:
|
|
||||||
- ./docker-registry.yml:/etc/docker/registry/config.yml
|
|
||||||
nats:
|
|
||||||
image: nats:latest
|
|
||||||
ports:
|
|
||||||
- "4222:4222"
|
|
||||||
- "6222:6222"
|
|
||||||
volumes:
|
|
||||||
- ./nats.conf:/etc/nats.conf
|
|
||||||
command: "-c=/etc/nats.conf -js"
|
|
|
@ -1,23 +0,0 @@
|
||||||
version: 0.1
|
|
||||||
log:
|
|
||||||
fields:
|
|
||||||
service: registry
|
|
||||||
storage:
|
|
||||||
cache:
|
|
||||||
blobdescriptor: inmemory
|
|
||||||
filesystem:
|
|
||||||
rootdirectory: /var/lib/registry
|
|
||||||
http:
|
|
||||||
addr: :5000
|
|
||||||
headers:
|
|
||||||
X-Content-Type-Options: [nosniff]
|
|
||||||
Access-Control-Allow-Origin: ["*"]
|
|
||||||
health:
|
|
||||||
storagedriver:
|
|
||||||
enabled: true
|
|
||||||
interval: 10s
|
|
||||||
threshold: 3
|
|
||||||
cors:
|
|
||||||
origins: ["*"]
|
|
||||||
methods: ["GET", "PUT", "POST", "DELETE"]
|
|
||||||
headers: ["Access-Control-Allow-Origin", "Content-Type"]
|
|
|
@ -1,6 +0,0 @@
|
||||||
listen: localhost:4222
|
|
||||||
websocket {
|
|
||||||
# host: "hostname"
|
|
||||||
port: 6222
|
|
||||||
no_tls: true
|
|
||||||
}
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*.ts",
|
"src/**/*.ts",
|
||||||
|
"wasmcloud-rs-js/**/*.ts"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules"
|
||||||
|
@ -24,7 +25,6 @@
|
||||||
"./wasmcloud-rs-js/pkg/"
|
"./wasmcloud-rs-js/pkg/"
|
||||||
],
|
],
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"forceConsistentCasingInFileNames": true
|
"forceConsistentCasingInFileNames": true
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@ crate-type = ["cdylib", "rlib"]
|
||||||
default = ["console_error_panic_hook"]
|
default = ["console_error_panic_hook"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wasm-bindgen = "0.2.76"
|
wasm-bindgen = "0.2.63"
|
||||||
wascap = "0.6.0"
|
wascap = "0.6.0"
|
||||||
getrandom = { version = "0.2", features = ["js"] }
|
getrandom = { version = "0.2", features = ["js"] }
|
||||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||||
|
|
|
@ -1,25 +1,9 @@
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin');
|
const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin');
|
||||||
|
|
||||||
const sharedConfig = {
|
const baseConfig = {
|
||||||
stats: { assets: false, modules: false, errors: true },
|
stats: { assets: false, modules: false },
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
resolve: {
|
|
||||||
extensions: ['.tsx', '.ts', '.js', '.wasm']
|
|
||||||
},
|
|
||||||
experiments: {
|
|
||||||
asyncWebAssembly: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is specifically to use in a script tag
|
|
||||||
const browserConfig = {
|
|
||||||
output: {
|
|
||||||
webassemblyModuleFilename: 'wasmcloud.wasm',
|
|
||||||
filename: 'wasmcloud.js',
|
|
||||||
path: path.resolve(__dirname, 'dist'),
|
|
||||||
library: 'wasmcloudjs'
|
|
||||||
},
|
|
||||||
entry: './src/index.ts',
|
entry: './src/index.ts',
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
|
@ -30,48 +14,44 @@ const browserConfig = {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.tsx', '.ts', '.js']
|
||||||
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new WasmPackPlugin({
|
new WasmPackPlugin({
|
||||||
crateDirectory: path.resolve(__dirname, 'wasmcloud-rs-js'),
|
crateDirectory: path.resolve(__dirname, 'wasmcloud-rs-js'),
|
||||||
extraArgs: '--target bundler',
|
extraArgs: '--target bundler',
|
||||||
outDir: path.resolve(__dirname, 'wasmcloud-rs-js', 'pkg')
|
outDir: path.resolve(__dirname, 'wasmcloud-rs-js', 'pkg'),
|
||||||
|
outName: 'wasmcloud_rs_js'
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
...sharedConfig
|
experiments: {
|
||||||
}
|
asyncWebAssembly: true
|
||||||
|
|
||||||
// this is used to bundle the rust wasm code in order to properly import into the compiled typescript code in the dist/src dir
|
|
||||||
// the tsc compiler handles the src code to cjs
|
|
||||||
const commonJSConfig = {
|
|
||||||
entry: './wasmcloud-rs-js/pkg/index.js',
|
|
||||||
output: {
|
|
||||||
webassemblyModuleFilename: 'wasmcloud.wasm',
|
|
||||||
filename: 'index.js',
|
|
||||||
libraryTarget: 'commonjs2',
|
|
||||||
path: path.resolve(__dirname, 'dist', 'wasmcloud-rs-js', 'pkg')
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
exclude: /(node_modules)/,
|
|
||||||
use: {
|
|
||||||
loader: 'babel-loader',
|
|
||||||
options: {
|
|
||||||
presets: ['@babel/preset-env']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
...sharedConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = (env) => {
|
|
||||||
switch (env.target) {
|
|
||||||
case 'cjs':
|
|
||||||
return commonJSConfig
|
|
||||||
default:
|
|
||||||
return browserConfig
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeConfig = {
|
||||||
|
target: 'node',
|
||||||
|
output: {
|
||||||
|
filename: 'index.node.js',
|
||||||
|
path: path.resolve(__dirname, 'dist', 'src'),
|
||||||
|
libraryTarget: 'umd',
|
||||||
|
libraryExport: 'default',
|
||||||
|
library: 'wasmcloudjs'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const browserConfig = {
|
||||||
|
output: {
|
||||||
|
filename: 'index.bundle.js',
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
library: 'wasmcloudjs'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = () => {
|
||||||
|
Object.assign(nodeConfig, baseConfig);
|
||||||
|
Object.assign(browserConfig, baseConfig);
|
||||||
|
return [browserConfig]
|
||||||
|
// return [browserConfig, nodeConfig];
|
||||||
};
|
};
|
Loading…
Reference in New Issue