Compare commits
24 Commits
Author | SHA1 | Date |
---|---|---|
|
e9ec9adf81 | |
|
b241df0590 | |
|
de97386d27 | |
|
44c14b73cb | |
|
daa3a7e099 | |
|
1d33dd97f7 | |
|
8d870d1546 | |
|
197e0ce212 | |
|
2457fdf000 | |
|
474559a0a6 | |
|
52fb0475ec | |
|
6d807b1892 | |
|
2f5719cca1 | |
|
e9f6d37854 | |
|
28f1232235 | |
|
8ac0419b59 | |
|
51a68841a7 | |
|
0810351fda | |
|
43979140b4 | |
|
fdbc218b04 | |
|
c55c160a83 | |
|
aaaf614c0f | |
|
e9f626a6f2 | |
|
7fabaadf28 |
|
@ -1,5 +1,6 @@
|
||||||
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": "all",
|
"trailingComma": "none",
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"semi": true,
|
"semi": true,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
|
|
113
README.md
113
README.md
|
@ -1,3 +1,6 @@
|
||||||
|
> [!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.
|
||||||
|
@ -9,7 +12,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
|
||||||
* **seemlessly** bind the actor to the capability provider through Lattice
|
* **seamlessly** 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
|
||||||
|
|
||||||
|
@ -17,7 +20,6 @@ https://user-images.githubusercontent.com/1530656/130013412-b9a9daa6-fc71-424b-8
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Prerequisities
|
## Prerequisities
|
||||||
|
|
||||||
* NATS with WebSockets enabled
|
* NATS with WebSockets enabled
|
||||||
|
@ -45,50 +47,107 @@ $ 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="dist/index.bundle.js"></script>
|
<script src="https://unpkg.com/@wasmcloud/wasmcloud-js@<VERSION>/dist/wasmcloud.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, a map of invocation callbacks, and a 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, and an optional 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 host will automatically listen for actors start & stop messages, to manually listen for these messages the following methods are exposed
|
||||||
|
// 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
|
// 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.
|
||||||
await host.launchActor("registry.com/actor:0.1.1")
|
// The hostCallback format is as follows:
|
||||||
// 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(heartbeatInterval?);
|
await host.startHeartbeat();
|
||||||
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(["ws://locahost:4222/"], {});
|
await host.connectNATS();
|
||||||
// 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--wasm-pack compile issues with nodeJS.
|
*IN PROGRESS* - NodeJS does not support WebSockets natively (required by nats.ws)
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
# 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).
|
|
@ -0,0 +1,18 @@
|
||||||
|
// 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))
|
|
@ -0,0 +1,41 @@
|
||||||
|
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, '')
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
<!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>
|
|
@ -0,0 +1,12 @@
|
||||||
|
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,31 +1,32 @@
|
||||||
{
|
{
|
||||||
"name": "@wasmcloud/wasmcloud-js",
|
"name": "@wasmcloud/wasmcloud-js",
|
||||||
"version": "1.0.0",
|
"version": "1.0.6",
|
||||||
"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",
|
||||||
"wasmcloud-rs-js",
|
"README.md",
|
||||||
"README.md"
|
"wasmcloud-rs-js"
|
||||||
],
|
],
|
||||||
"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 --mode=production",
|
"build:browser": "webpack",
|
||||||
"build:cjs": "tsc --declaration",
|
"build:cjs": "tsc --declaration && webpack --env target=cjs",
|
||||||
"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",
|
"test": "mocha --require ts-node/register",
|
||||||
"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": {
|
||||||
|
@ -45,24 +46,26 @@
|
||||||
"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",
|
||||||
|
@ -70,9 +73,9 @@
|
||||||
"webpack-cli": "^4.8.0"
|
"webpack-cli": "^4.8.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@msgpack/msgpack": "^2.7.0",
|
"@msgpack/msgpack": "^2.7.1",
|
||||||
"@wapc/host": "0.0.2",
|
"@wapc/host": "0.0.2",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.24.0",
|
||||||
"nats.ws": "^1.2.0"
|
"nats.ws": "^1.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
126
src/actor.ts
126
src/actor.ts
|
@ -1,24 +1,42 @@
|
||||||
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, ActorClaimsMessage, ActorStartedMessage, ActorHealthCheckPassMessage,
|
ActorClaims,
|
||||||
|
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!: WapcHost;
|
module!: Wasmbus;
|
||||||
hostKey: string;
|
hostKey: string;
|
||||||
hostName: string;
|
hostName: string;
|
||||||
wasm: any;
|
wasm: any;
|
||||||
|
invocationCallback?: Function;
|
||||||
|
hostCall?: HostCall;
|
||||||
|
writer?: Writer;
|
||||||
|
|
||||||
constructor(hostName: string = 'default', hostKey: string, wasm: any) {
|
constructor(
|
||||||
|
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;
|
||||||
|
@ -37,8 +55,16 @@ 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);
|
||||||
|
@ -47,17 +73,27 @@ 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.module = await instantiate(actorBuffer, this.hostCall, this.writer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = {
|
||||||
|
@ -69,59 +105,85 @@ 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(`wasmbus.evt.${this.hostName}`, jsonEncode(createEventMessage(this.hostKey, EventType.ActorStarted, actorStarted)));
|
natsConn.publish(
|
||||||
|
`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(`wasmbus.evt.${this.hostName}`, jsonEncode(createEventMessage(this.hostKey, EventType.HealthCheckPass, actorHealthCheck)));
|
natsConn.publish(
|
||||||
|
`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(encode({
|
invocationMessage.respond(
|
||||||
invocation_id: (invocationData as any).id,
|
encode({
|
||||||
instance_id: uuidv4(),
|
invocation_id: (invocationData as any).id,
|
||||||
msg: invocationResult
|
instance_id: uuidv4(),
|
||||||
}));
|
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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
export async function newActor(hostName: string, hostKey: string,
|
* startActor initializes an actor and listens for invocation messages
|
||||||
|
*
|
||||||
|
* @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);
|
const actor: Actor = new Actor(hostName, hostKey, wasm, invocationCallback, hostCall, writer);
|
||||||
await actor.startActor(actorModule);
|
await actor.startActor(actorModule);
|
||||||
await actor.publishActorStarted(natsConn);
|
await actor.publishActorStarted(natsConn);
|
||||||
Promise.all([
|
Promise.all([actor.subscribeInvocations(natsConn)]).catch(err => {
|
||||||
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,8 +15,16 @@ 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,
|
||||||
|
@ -26,5 +34,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,18 +28,27 @@ 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.get(
|
const response: AxiosResponse = await axios
|
||||||
`${withTLS ? 'https://' : 'http://'}${registry}/v2/${name}/manifests/${version}`,
|
.get(`${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) {
|
||||||
|
@ -50,17 +59,23 @@ 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.get(
|
const response: AxiosResponse = await axios
|
||||||
url,
|
.get(url, {
|
||||||
{
|
|
||||||
responseType: 'arraybuffer'
|
responseType: 'arraybuffer'
|
||||||
}
|
})
|
||||||
).catch((err) => { throw err });
|
.catch(err => {
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
|
||||||
return new Uint8Array(response.data);
|
return new Uint8Array(response.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
182
src/host.ts
182
src/host.ts
|
@ -1,11 +1,7 @@
|
||||||
import { encode } from '@msgpack/msgpack';
|
import { encode } from '@msgpack/msgpack';
|
||||||
import {
|
import { connect, ConnectionOptions, NatsConnection, Subscription } from 'nats.ws';
|
||||||
connect, ConnectionOptions,
|
|
||||||
NatsConnection,
|
|
||||||
Subscription
|
|
||||||
} from 'nats.ws';
|
|
||||||
|
|
||||||
import { Actor, newActor } from './actor';
|
import { Actor, startActor } 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 {
|
||||||
|
@ -14,19 +10,23 @@ 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,14 +38,20 @@ 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(
|
||||||
constructor(name: string = 'default',
|
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;
|
||||||
|
@ -56,46 +62,68 @@ export class Host {
|
||||||
this.wasm = wasm;
|
this.wasm = wasm;
|
||||||
this.heartbeatInterval = heartbeatInterval;
|
this.heartbeatInterval = heartbeatInterval;
|
||||||
this.natsConnOpts = natsConnOpts;
|
this.natsConnOpts = natsConnOpts;
|
||||||
this.invocationCallbacks = invocationCallbacks;
|
this.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
|
? {
|
||||||
} : this.natsConnOpts;
|
servers: 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(`wasmbus.evt.${this.name}`,
|
this.natsConn.publish(
|
||||||
|
`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) {
|
||||||
|
@ -105,28 +133,55 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
async launchActor(actorRef: string) {
|
* launchActor launches an actor via the launch actor message
|
||||||
|
*
|
||||||
|
* @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
|
||||||
|
@ -142,16 +197,22 @@ 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/${actorDigest.digest}`
|
url = `${this.withRegistryTLS ? 'https://' : 'http://'}${actorDigest.registry}/v2/${actorDigest.name}/blobs/${
|
||||||
|
actorDigest.digest
|
||||||
|
}`;
|
||||||
} else {
|
} else {
|
||||||
url = actorRef;
|
url = actorRef;
|
||||||
}
|
}
|
||||||
const actorModule: Uint8Array = await fetchActor(url);
|
const actorModule: Uint8Array = await fetchActor(url);
|
||||||
const actor: Actor = await newActor(this.name, this.key,
|
const actor: Actor = await startActor(
|
||||||
|
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]) {
|
||||||
|
@ -160,34 +221,49 @@ 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
|
// delete the actor from the host and remove the invocation callback
|
||||||
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(`wasmbus.evt.${this.name}`,
|
this.natsConn.publish(
|
||||||
jsonEncode(createEventMessage(this.key, EventType.ActorStopped, actorStop)))
|
`wasmbus.evt.${this.name}`,
|
||||||
|
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,
|
||||||
|
@ -195,21 +271,23 @@ 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([
|
Promise.all([this.startHeartbeat(), this.listenLaunchActor(), this.listenStopActor()]).catch((err: Error) => {
|
||||||
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();
|
||||||
|
@ -226,22 +304,30 @@ 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(name,
|
const host: Host = new Host(
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
export { startHost } from './host';
|
export { startHost } from './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,8 +82,13 @@ 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;
|
||||||
|
|
39
src/util.ts
39
src/util.ts
|
@ -2,27 +2,56 @@ 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, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
const r = (Math.random() * 16) | 0,
|
||||||
|
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(atob(base64).split('').map(function (c) {
|
var jsonPayload = decodeURIComponent(
|
||||||
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
atob(base64)
|
||||||
}).join(''));
|
.split('')
|
||||||
|
.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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
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,9 +11,11 @@ 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).to.be.a('string').and.satisfy((key: string) => key.startsWith('N'));
|
expect(host.key)
|
||||||
|
.to.be.a('string')
|
||||||
|
.and.satisfy((key: string) => key.startsWith('N'));
|
||||||
expect(host.actors).to.equal({});
|
expect(host.actors).to.equal({});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
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"
|
|
@ -0,0 +1,23 @@
|
||||||
|
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"]
|
|
@ -0,0 +1,6 @@
|
||||||
|
listen: localhost:4222
|
||||||
|
websocket {
|
||||||
|
# host: "hostname"
|
||||||
|
port: 6222
|
||||||
|
no_tls: true
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*.ts",
|
"src/**/*.ts",
|
||||||
"wasmcloud-rs-js/**/*.ts"
|
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules"
|
||||||
|
@ -25,6 +24,7 @@
|
||||||
"./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.63"
|
wasm-bindgen = "0.2.76"
|
||||||
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,9 +1,25 @@
|
||||||
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 baseConfig = {
|
const sharedConfig = {
|
||||||
stats: { assets: false, modules: false },
|
stats: { assets: false, modules: false, errors: true },
|
||||||
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: [
|
||||||
|
@ -14,44 +30,48 @@ const baseConfig = {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
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'
|
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
experiments: {
|
...sharedConfig
|
||||||
asyncWebAssembly: true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeConfig = {
|
// this is used to bundle the rust wasm code in order to properly import into the compiled typescript code in the dist/src dir
|
||||||
target: 'node',
|
// the tsc compiler handles the src code to cjs
|
||||||
|
const commonJSConfig = {
|
||||||
|
entry: './wasmcloud-rs-js/pkg/index.js',
|
||||||
output: {
|
output: {
|
||||||
filename: 'index.node.js',
|
webassemblyModuleFilename: 'wasmcloud.wasm',
|
||||||
path: path.resolve(__dirname, 'dist', 'src'),
|
filename: 'index.js',
|
||||||
libraryTarget: 'umd',
|
libraryTarget: 'commonjs2',
|
||||||
libraryExport: 'default',
|
path: path.resolve(__dirname, 'dist', 'wasmcloud-rs-js', 'pkg')
|
||||||
library: 'wasmcloudjs'
|
},
|
||||||
}
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
exclude: /(node_modules)/,
|
||||||
|
use: {
|
||||||
|
loader: 'babel-loader',
|
||||||
|
options: {
|
||||||
|
presets: ['@babel/preset-env']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
...sharedConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
const browserConfig = {
|
module.exports = (env) => {
|
||||||
output: {
|
switch (env.target) {
|
||||||
filename: 'index.bundle.js',
|
case 'cjs':
|
||||||
path: path.resolve(__dirname, 'dist'),
|
return commonJSConfig
|
||||||
library: 'wasmcloudjs'
|
default:
|
||||||
|
return browserConfig
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = () => {
|
|
||||||
Object.assign(nodeConfig, baseConfig);
|
|
||||||
Object.assign(browserConfig, baseConfig);
|
|
||||||
return [browserConfig]
|
|
||||||
// return [browserConfig, nodeConfig];
|
|
||||||
};
|
};
|
Loading…
Reference in New Issue