Compare commits

...

24 Commits
v1.0.0 ... main

Author SHA1 Message Date
Connor Smith e9ec9adf81 docs: add note about experimental status
Signed-off-by: Connor Smith <connor.smith.256@gmail.com>
2023-10-31 12:19:40 -06:00
Brooks Townsend b241df0590
Merge pull request #26 from wasmCloud/ks2211/updates
Update examples
2022-12-19 16:29:15 -05:00
Kaushik Shanadi de97386d27 update examples
Signed-off-by: Brooks Townsend <brooks@cosmonic.com>
2022-12-19 08:54:52 -05:00
Kaushik Shanadi 44c14b73cb
Merge pull request #20 from wasmCloud/snyk-upgrade-f7bfd93940c4d6a9de6e53510b435dd2
[Snyk] Upgrade axios from 0.21.3 to 0.24.0
2022-01-03 13:39:25 -05:00
Kaushik Shanadi daa3a7e099
Merge pull request #21 from wasmCloud/snyk-upgrade-5a5a87f3ce0884c20cb36e0039f28d09
[Snyk] Upgrade @msgpack/msgpack from 2.7.0 to 2.7.1
2022-01-03 13:39:10 -05:00
snyk-bot 1d33dd97f7
fix: upgrade @msgpack/msgpack from 2.7.0 to 2.7.1
Snyk has created this PR to upgrade @msgpack/msgpack from 2.7.0 to 2.7.1.

See this package in npm:
https://www.npmjs.com/package/@msgpack/msgpack

See this project in Snyk:
https://app.snyk.io/org/wasmcloud-automation/project/7349e195-0622-4c5b-add5-7f34587e7853?utm_source=github&utm_medium=referral&page=upgrade-pr
2021-12-15 20:16:10 +00:00
snyk-bot 8d870d1546
fix: upgrade axios from 0.21.3 to 0.24.0
Snyk has created this PR to upgrade axios from 0.21.3 to 0.24.0.

See this package in npm:
https://www.npmjs.com/package/axios

See this project in Snyk:
https://app.snyk.io/org/wasmcloud-automation/project/7349e195-0622-4c5b-add5-7f34587e7853?utm_source=github&utm_medium=referral&page=upgrade-pr
2021-12-15 20:16:04 +00:00
Kaushik Shanadi 197e0ce212
Merge pull request #18 from wasmCloud/snyk-fix-702adb67744f2233deb4be91f95e8cc6
[Snyk] Security upgrade axios from 0.21.1 to 0.21.3
2021-12-14 20:15:45 -05:00
snyk-bot 2457fdf000
fix: package.json & package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-AXIOS-1579269
2021-12-15 01:09:49 +00:00
Kaushik Shanadi 474559a0a6 update version
Signed-off-by: Kaushik Shanadi <kaushik@cosmonic.com>
2021-10-04 14:21:04 -04:00
Kaushik Shanadi 52fb0475ec
Merge pull request #16 from wasmCloud/ks2211/wapc_fix
Ks2211/wapc fix
2021-10-04 14:07:51 -04:00
Kaushik Shanadi 6d807b1892 format code
Signed-off-by: Kaushik Shanadi <kaushik@cosmonic.com>
2021-10-04 14:04:07 -04:00
Kaushik Shanadi 2f5719cca1 extend wapc-js for wasmbus, add hostcall and writer types, remove invocation callback map from startHost -- moved to launchActor call, add hostCall+writer params to actor
Signed-off-by: Kaushik Shanadi <kaushik@cosmonic.com>
2021-09-30 19:01:32 -04:00
Kaushik Shanadi e9f6d37854
Merge pull request #13 from wasmCloud/ks2211/examples
Ks2211/examples
2021-09-01 12:01:50 -04:00
ks2211 28f1232235 add jsdoc comments
Signed-off-by: ks2211 <kaushik@cosmonic.com>
2021-09-01 10:42:44 -04:00
ks2211 8ac0419b59 fix cjs build, update readme
Signed-off-by: ks2211 <kaushik@cosmonic.com>
2021-08-31 19:42:45 -04:00
ks2211 51a68841a7 add simple example, update readme for bundler instructions
Signed-off-by: ks2211 <kaushik@cosmonic.com>
2021-08-31 14:06:37 -04:00
Kaushik Shanadi 0810351fda
Merge pull request #12 from wasmCloud/ks2211/prettier
Ks2211/prettier
2021-08-31 12:06:00 -04:00
ks2211 43979140b4 webpack fixes for wasm code (can import it as a library), add invocation callbacks to launchActor (and cleanup), fix test infra docker
Signed-off-by: ks2211 <kaushik@cosmonic.com>
2021-08-31 10:51:36 -04:00
ks2211 fdbc218b04 add prettier/format code, add infra docker-compose to tests, update package.json
Signed-off-by: ks2211 <kaushik@cosmonic.com>
2021-08-25 10:54:52 -04:00
ks2211 c55c160a83 update readme for bundler instructions, upgrade to 0.2.76 bindgen, drop filename for rs files (needed for cjs)
Signed-off-by: ks2211 <kaushik@cosmonic.com>
2021-08-24 17:52:19 -04:00
ks2211 aaaf614c0f bump package version
Signed-off-by: ks2211 <kaushik@cosmonic.com>
2021-08-23 18:04:03 -04:00
ks2211 e9f626a6f2 fix wasm filename from hashing, fix gitignore
Signed-off-by: ks2211 <kaushik@cosmonic.com>
2021-08-23 18:03:27 -04:00
ks2211 7fabaadf28 cjs build fixes, generate proper library, update library bundle names, update readme
Signed-off-by: ks2211 <kaushik@cosmonic.com>
2021-08-21 09:32:36 -04:00
25 changed files with 2226 additions and 210 deletions

3
.gitignore vendored
View File

@ -1,5 +1,6 @@
node_modules/*
dist/*
index.html
!examples/**/*index.html*
*.sh*
wasmcloud-rs-js/pkg/
wasmcloud-rs-js/pkg/

View File

@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"trailingComma": "all",
"trailingComma": "none",
"tabWidth": 2,
"semi": true,
"singleQuote": true,

113
README.md
View File

@ -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
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 the wasmcloud-js host into a 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
* Unload the actor
@ -17,7 +20,6 @@ https://user-images.githubusercontent.com/1530656/130013412-b9a9daa6-fc71-424b-8
## Prerequisities
* NATS with WebSockets enabled
@ -45,50 +47,107 @@ $ npm install @wasmcloud/wasmcloud-js
## Usage
More examples can be found in the [examples](examples/) directory, including sample `webpack` and `esbuild` configurations
**Browser**
```html
<script src="dist/index.bundle.js"></script>
<script src="https://unpkg.com/@wasmcloud/wasmcloud-js@<VERSION>/dist/wasmcloud.js"></script>
<script>
(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)
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
// 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);
// 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
(async() => {
await host.listenLaunchActor(
{
"localhost:5000/echo:0.2.2": (data, result) => console.log(data.operation, result);
}
);
await host.listenStopActor();
})();
// to launch an actor manually from the library from a registry
await host.launchActor("registry.com/actor:0.1.1")
// to launch an actrom manually from local disk (note the .wasm is required)
// (async() => {
// await host.listenLaunchActor(
// {
// "localhost:5000/echo:0.2.2": (data, result) => console.log(data.operation, result);
// }
// );
// 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.
// The hostCallback format is as follows:
// ```
// (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");
// 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));
// to unsubscribe, call the unsubscribeEvents
// To unsubscribe, call the unsubscribeEvents
await host.unsubscribeEvents();
// to start & stop the heartbeat events
await host.startHeartbeat(heartbeatInterval?);
// To start & stop the heartbeat events
await host.startHeartbeat();
await host.stopHeartbeat();
// the host will automatically connect to nats on start. to connect/reconnect to nats
await host.connectNATS(["ws://locahost:4222/"], {});
// to close/drain all connections from nats, call the disconnectNATS() method
// The host will automatically connect to nats on start. to connect/reconnect to nats
await host.connectNATS();
// To close/drain all connections from nats, call the disconnectNATS() method
await host.disconnectNATS();
// stop the host
// Stop the host
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();
})();
</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**
In progress--wasm-pack compile issues with nodeJS.
*IN PROGRESS* - NodeJS does not support WebSockets natively (required by nats.ws)
## Contributing

50
examples/simple/README.md Normal file
View File

@ -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).

View File

@ -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))

View File

@ -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, '')
}
}

View File

@ -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>

12
examples/simple/main.js Normal file
View File

@ -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);
})()

1544
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,31 +1,32 @@
{
"name": "@wasmcloud/wasmcloud-js",
"version": "1.0.0",
"version": "1.0.6",
"description": "wasmcloud host in JavaScript/Browser",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
"files": [
"dist",
"src",
"wasmcloud-rs-js",
"README.md"
"README.md",
"wasmcloud-rs-js"
],
"scripts": {
"build": "npm run clean && npm run build:browser && npm run build:cjs",
"build:browser": "webpack --mode=production",
"build:cjs": "tsc --declaration",
"build:browser": "webpack",
"build:cjs": "tsc --declaration && webpack --env target=cjs",
"build:wasm": "cd wasmcloud-rs-js && wasm-pack build",
"clean": "rm -rf ./dist/ && rm -rf ./wasmcloud-rs-js/pkg/",
"lint": "eslint --ext .ts src test",
"format": "prettier --write 'src/**/*.ts' 'test/**/*.ts'",
"test": "mocha",
"test": "mocha --require ts-node/register",
"watch": "npm run clean && tsc -w --declrataion",
"prepare": "npm run build"
},
"prettier": ".prettierrc.json",
"prettier": "./.prettierrc.json",
"keywords": [
"wasmcloud",
"wasmcloud-host",
"wasmcloud-js",
"wasm"
],
"eslintConfig": {
@ -45,24 +46,26 @@
"extension": [
"ts"
],
"spec": "test/**/*.test.ts",
"require": "ts-node/register"
"spec": "test/**/*.test.ts"
},
"author": "ks2211 <kaushik@cosmonic.com>",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.15.0",
"@babel/preset-env": "^7.15.0",
"@types/chai": "^4.2.21",
"@types/chai-as-promised": "^7.1.4",
"@types/mocha": "^9.0.0",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.29.2",
"@wasm-tool/wasm-pack-plugin": "^1.5.0",
"babel-loader": "^8.2.2",
"chai": "^4.3.4",
"chai-as-promised": "^7.1.1",
"esbuild": "^0.12.20",
"eslint": "^7.32.0",
"mocha": "^9.0.3",
"path": "^0.12.7",
"prettier": "^2.3.2",
"ts-loader": "^9.2.5",
"ts-node": "^10.2.1",
"typescript": "^4.3.5",
@ -70,9 +73,9 @@
"webpack-cli": "^4.8.0"
},
"dependencies": {
"@msgpack/msgpack": "^2.7.0",
"@msgpack/msgpack": "^2.7.1",
"@wapc/host": "0.0.2",
"axios": "^0.21.1",
"axios": "^0.24.0",
"nats.ws": "^1.2.0"
}
}

View File

@ -1,24 +1,42 @@
import { encode, decode } from '@msgpack/msgpack';
import { instantiate, WapcHost } from '@wapc/host';
import { NatsConnection, Subscription } from 'nats.ws';
import { instantiate, Wasmbus } from './wasmbus';
import { createEventMessage, EventType } from './events';
import {
ActorClaims, ActorClaimsMessage, ActorStartedMessage, ActorHealthCheckPassMessage,
ActorClaims,
ActorClaimsMessage,
ActorStartedMessage,
ActorHealthCheckPassMessage,
InvocationMessage,
StopActorMessage
StopActorMessage,
HostCall,
Writer
} from './types';
import { jsonEncode, parseJwt, uuidv4 } from './util';
/**
* Actor holds the actor wasm module
*/
export class Actor {
claims: ActorClaims;
key: string;
module!: WapcHost;
module!: Wasmbus;
hostKey: string;
hostName: string;
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.hostName = hostName;
this.hostKey = hostKey;
@ -37,8 +55,16 @@ export class Actor {
}
};
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) {
const token: string = await this.wasm.extract_jwt(actorBuffer);
const valid: boolean = await this.wasm.validate_jwt(token);
@ -47,17 +73,27 @@ export class Actor {
}
this.claims = parseJwt(token);
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) {
const actorToStop: StopActorMessage = {
host_id: this.hostKey,
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) {
// publish claims
const claims: ActorClaimsMessage = {
@ -69,59 +105,85 @@ export class Actor {
sub: this.claims.sub,
tags: '',
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
const actorStarted: ActorStartedMessage = {
api_version: 0,
instance_id: uuidv4(),
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
const actorHealthCheck: ActorHealthCheckPassMessage = {
instance_id: uuidv4(),
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
const invocationsTopic: Subscription = natsConn.subscribe(`wasmbus.rpc.${this.hostName}.${this.key}`);
for await (const invocationMessage of invocationsTopic) {
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);
invocationMessage.respond(encode({
invocation_id: (invocationData as any).id,
instance_id: uuidv4(),
msg: invocationResult
}));
if (invocationCallback) {
invocationCallback(invocationResult);
invocationMessage.respond(
encode({
invocation_id: (invocationData as any).id,
instance_id: uuidv4(),
msg: invocationResult
})
);
if (this.invocationCallback) {
this.invocationCallback(invocationResult);
}
}
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,
natsConn: NatsConnection,
wasm: any,
invocationCallback?: Function
invocationCallback?: Function,
hostCall?: HostCall,
writer?: Writer
): 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.publishActorStarted(natsConn);
Promise.all([
actor.subscribeInvocations(natsConn, invocationCallback)
]).catch((err) => {
Promise.all([actor.subscribeInvocations(natsConn)]).catch(err => {
throw err;
});
return actor;
}
}

View File

@ -1,10 +1,10 @@
import { uuidv4 } from './util'
import { uuidv4 } from './util';
export enum EventType {
HeartBeat = 'com.wasmcloud.lattice.host_heartbeat',
ActorStarted = 'com.wasmcloud.lattice.actor_started',
ActorStopped = 'com.wasmcloud.lattice.actor_stopped',
HealthCheckPass = 'com.wasmcloud.lattice.health_check_passed',
HealthCheckPass = 'com.wasmcloud.lattice.health_check_passed'
}
export type EventData = {
@ -15,8 +15,16 @@ export type EventData = {
time: string;
type: EventType;
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 {
return {
data: data,
@ -26,5 +34,5 @@ export function createEventMessage(hostKey: string, eventType: EventType, data:
specversion: '1.0',
time: new Date().toISOString(),
type: eventType
}
}
};
}

View File

@ -4,7 +4,7 @@ export type ImageDigest = {
name: string;
digest: string;
registry: string;
}
};
type FetchActorDigestResponse = {
schemaVersion: number;
@ -19,7 +19,7 @@ type FetchActorDigestResponse = {
layers: Array<{
annotations: {
['org.opencontainers.image.title']: string;
}
};
digest: string;
mediaType: string;
size: number;
@ -28,18 +28,27 @@ type FetchActorDigestResponse = {
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> {
const image: Array<string> = actorRef.split('/');
const registry: string = image[0];
const [name, version] = image[1].split(':');
const response: AxiosResponse = await axios.get(
`${withTLS ? 'https://' : 'http://'}${registry}/v2/${name}/manifests/${version}`,
{
const response: AxiosResponse = await axios
.get(`${withTLS ? 'https://' : 'http://'}${registry}/v2/${name}/manifests/${version}`, {
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;
if (layers.layers.length === 0) {
@ -50,17 +59,23 @@ export async function fetchActorDigest(actorRef: string, withTLS?: boolean): Pro
name,
digest: layers.layers[0].digest,
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> {
const response: AxiosResponse = await axios.get(
url,
{
const response: AxiosResponse = await axios
.get(url, {
responseType: 'arraybuffer'
}
).catch((err) => { throw err });
})
.catch(err => {
throw err;
});
return new Uint8Array(response.data);
}

View File

@ -1,11 +1,7 @@
import { encode } from '@msgpack/msgpack';
import {
connect, ConnectionOptions,
NatsConnection,
Subscription
} from 'nats.ws';
import { connect, ConnectionOptions, NatsConnection, Subscription } from 'nats.ws';
import { Actor, newActor } from './actor';
import { Actor, startActor } from './actor';
import { createEventMessage, EventType } from './events';
import { fetchActor, fetchActorDigest, ImageDigest } from './fetch';
import {
@ -14,19 +10,23 @@ import {
HeartbeatMessage,
InvocationCallbacks,
LaunchActorMessage,
StopActorMessage
StopActorMessage,
HostCall,
Writer
} from './types';
import { jsonDecode, jsonEncode, uuidv4 } from './util';
const HOST_HEARTBEAT_INTERVAL: number = 30000;
/**
* Host holds the js/browser host
*/
export class Host {
name: string;
key: string;
seed: string;
heartbeatInterval: number;
heartbeatIntervalId: any;
invocationCallbacks?: InvocationCallbacks;
withRegistryTLS: boolean;
actors: {
[key: string]: {
@ -38,14 +38,20 @@ export class Host {
natsConn!: NatsConnection;
eventsSubscription!: Subscription | null;
wasm: any;
invocationCallbacks?: InvocationCallbacks;
hostCalls?: {
[key: string]: HostCall;
};
writers?: {
[key: string]: Writer;
};
constructor(name: string = 'default',
constructor(
name: string = 'default',
withRegistryTLS: boolean,
heartbeatInterval: number,
natsConnOpts: Array<string> | ConnectionOptions,
wasm: any,
invocationCallbacks?: InvocationCallbacks
wasm: any
) {
const hostKey = new wasm.HostKey();
this.name = name;
@ -56,46 +62,68 @@ export class Host {
this.wasm = wasm;
this.heartbeatInterval = heartbeatInterval;
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() {
const opts: ConnectionOptions = (Array.isArray(this.natsConnOpts)) ? {
servers: this.natsConnOpts
} : this.natsConnOpts;
const opts: ConnectionOptions = Array.isArray(this.natsConnOpts)
? {
servers: this.natsConnOpts
}
: this.natsConnOpts;
this.natsConn = await connect(opts);
}
/**
* disconnectNATS disconnects from nats
*/
async disconnectNATS() {
this.natsConn.close();
}
/**
* startHeartbeat starts a heartbeat publish message every X seconds based on the interval
*/
async startHeartbeat() {
this.heartbeatIntervalId;
const heartbeat: HeartbeatMessage = {
actors: [],
providers: []
}
};
for (const actor in this.actors) {
heartbeat.actors.push({
actor: actor,
instances: 1
})
});
}
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))
);
}, this.heartbeatInterval)
}, this.heartbeatInterval);
}
/**
* stopHeartbeat clears the heartbeat interval
*/
async stopHeartbeat() {
clearInterval(this.heartbeatIntervalId);
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) {
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) {
const eventData = jsonDecode(event.data);
if (eventCallback) {
@ -105,28 +133,55 @@ export class Host {
throw new Error('evt subscription was closed');
}
/**
* unsubscribeEvents unsubscribes and removes the events subscription
*/
async unsubscribeEvents() {
this.eventsSubscription?.unsubscribe();
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 = {
actor_ref: actorRef,
host_id: this.key
}
};
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) {
const actorToStop: StopActorMessage = {
host_id: this.key,
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() {
// subscribe to the .la topic `wasmbus.ctl.${this.name}.cmd.${this.key}.la`
// decode the data
@ -142,16 +197,22 @@ export class Host {
let url: string;
if (usingRegistry) {
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 {
url = actorRef;
}
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,
this.natsConn,
this.wasm,
this.invocationCallbacks?.[actorRef]
this.invocationCallbacks?.[actorRef],
this.hostCalls?.[actorRef],
this.writers?.[actorRef]
);
if (this.actors[actorRef]) {
@ -160,34 +221,49 @@ export class Host {
this.actors[actorRef] = {
count: 1,
actor: actor
}
};
}
} catch (err) {
// TODO: error handling
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() {
// listen for stop actor message, decode the data
// 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`);
for await (const actorMessage of actorsTopic) {
const actorData = jsonDecode(actorMessage.data);
const actorStop: ActorStoppedMessage = {
instance_id: uuidv4(),
public_key: this.actors[(actorData as StopActorMessage).actor_ref].actor.key
}
this.natsConn.publish(`wasmbus.evt.${this.name}`,
jsonEncode(createEventMessage(this.key, EventType.ActorStopped, actorStop)))
};
this.natsConn.publish(
`wasmbus.evt.${this.name}`,
jsonEncode(createEventMessage(this.key, EventType.ActorStopped, actorStop))
);
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) {
const linkDefinition: CreateLinkDefMessage = {
actor_id: actorKey,
@ -195,21 +271,23 @@ export class Host {
link_name: linkName,
contract_id: contractId,
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() {
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;
});
}
/**
* stopHost stops the heartbeat, stops all actors, drains the nats connections and disconnects from nats
*/
async stopHost() {
// stop the heartbeat
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(
name: string,
withRegistryTLS: boolean = true,
natsConnection: Array<string> | ConnectionOptions,
invocationCallbacks?: InvocationCallbacks,
heartbeatInterval?: number
) {
const wasmModule: any = await import('../wasmcloud-rs-js/pkg/');
const wasm: any = await wasmModule.default;
const host: Host = new Host(name,
const host: Host = new Host(
name,
withRegistryTLS,
heartbeatInterval ? heartbeatInterval : HOST_HEARTBEAT_INTERVAL,
natsConnection,
wasm,
invocationCallbacks
wasm
);
await host.startHost();
return host;
}
}

View File

@ -1 +1 @@
export { startHost } from './host';
export { startHost } from './host';

View File

@ -4,7 +4,7 @@ export type HeartbeatMessage = {
instances: number;
}>;
providers: [];
}
};
export type CreateLinkDefMessage = {
actor_id: string;
@ -12,7 +12,7 @@ export type CreateLinkDefMessage = {
link_name: string;
contract_id: string;
values: any;
}
};
export type ActorClaims = {
jti: string;
@ -27,7 +27,7 @@ export type ActorClaims = {
ver: string;
prov: boolean;
};
}
};
export type ActorClaimsMessage = {
call_alias: string;
@ -38,33 +38,33 @@ export type ActorClaimsMessage = {
sub: string;
tags: string;
version: string;
}
};
export type LaunchActorMessage = {
actor_ref: string;
host_id: string;
}
};
export type ActorStartedMessage = {
api_version: number;
instance_id: string;
public_key: string;
}
};
export type ActorHealthCheckPassMessage = {
instance_id: string;
public_key: string;
}
};
export type StopActorMessage = {
host_id: string;
actor_ref: string;
}
};
export type ActorStoppedMessage = {
public_key: string;
instance_id: string;
}
};
export type InvocationMessage = {
encoded_claims: string;
@ -82,8 +82,13 @@ export type InvocationMessage = {
link_name: string;
contract_id: string;
};
}
};
export type InvocationCallbacks = {
[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;

View File

@ -2,27 +2,56 @@ import { JSONCodec } from 'nats.ws';
const jc = JSONCodec();
/**
* uuidv4 returns a uuid string
*
* @returns {string}
*/
export function uuidv4(): string {
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);
});
}
/**
* 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) {
var base64Url = token.split('.')[1];
var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
var jsonPayload = decodeURIComponent(atob(base64).split('').map(function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
var jsonPayload = decodeURIComponent(
atob(base64)
.split('')
.map(function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
})
.join('')
);
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 {
return jc.encode(data);
}
/**
* jsonDecode decodes nats messages into json
*
* @param {Uint8Array} data - the nats encoded data
* @returns {any}
*/
export function jsonDecode(data: Uint8Array) {
return jc.decode(data);
}
}

28
src/wasmbus.ts Normal file
View File

@ -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;
}
}

View File

@ -11,9 +11,11 @@ const expect = chai.expect;
describe('wasmcloudjs', function () {
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.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({});
})
})
});
});

View File

@ -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"

View File

@ -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"]

6
test/infra/nats.conf Normal file
View File

@ -0,0 +1,6 @@
listen: localhost:4222
websocket {
# host: "hostname"
port: 6222
no_tls: true
}

View File

@ -1,7 +1,6 @@
{
"include": [
"src/**/*.ts",
"wasmcloud-rs-js/**/*.ts"
],
"exclude": [
"node_modules"
@ -25,6 +24,7 @@
"./wasmcloud-rs-js/pkg/"
],
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true
}
}

View File

@ -14,7 +14,7 @@ crate-type = ["cdylib", "rlib"]
default = ["console_error_panic_hook"]
[dependencies]
wasm-bindgen = "0.2.63"
wasm-bindgen = "0.2.76"
wascap = "0.6.0"
getrandom = { version = "0.2", features = ["js"] }
rand = { version = "0.7.3", features = ["wasm-bindgen"] }

View File

@ -1,9 +1,25 @@
const path = require('path');
const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin');
const baseConfig = {
stats: { assets: false, modules: false },
const sharedConfig = {
stats: { assets: false, modules: false, errors: true },
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',
module: {
rules: [
@ -14,44 +30,48 @@ const baseConfig = {
}
]
},
resolve: {
extensions: ['.tsx', '.ts', '.js']
},
plugins: [
new WasmPackPlugin({
crateDirectory: path.resolve(__dirname, 'wasmcloud-rs-js'),
extraArgs: '--target bundler',
outDir: path.resolve(__dirname, 'wasmcloud-rs-js', 'pkg'),
outName: 'wasmcloud_rs_js'
outDir: path.resolve(__dirname, 'wasmcloud-rs-js', 'pkg')
})
],
experiments: {
asyncWebAssembly: true
}
...sharedConfig
}
const nodeConfig = {
target: 'node',
// 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: {
filename: 'index.node.js',
path: path.resolve(__dirname, 'dist', 'src'),
libraryTarget: 'umd',
libraryExport: 'default',
library: 'wasmcloudjs'
}
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
}
const browserConfig = {
output: {
filename: 'index.bundle.js',
path: path.resolve(__dirname, 'dist'),
library: 'wasmcloudjs'
module.exports = (env) => {
switch (env.target) {
case 'cjs':
return commonJSConfig
default:
return browserConfig
}
}
module.exports = () => {
Object.assign(nodeConfig, baseConfig);
Object.assign(browserConfig, baseConfig);
return [browserConfig]
// return [browserConfig, nodeConfig];
};