Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions examples/components/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
## Example Components

Most (if not all) individual example projects are standard Javascript projects commonplace ecosystem tooling, whether
for server side ([NodeJS][nodejs], etc) or the browser.
Most (if not all) individual example projects are standard JavaScript projects commonplace ecosystem tooling, whether
for server side ([NodeJS][nodejs], etc.) or the browser.

A brief description of the examples contained in this folder:

Expand All @@ -14,6 +14,7 @@ A brief description of the examples contained in this folder:
| [`http-hello-world`](./http-hello-world) | HTTP server using the [`wasi:http/incoming-handler`][wasi-http], the hard way. |
| [`http-server-fetch-handler`](./http-server-fetch-handler) | HTTP server using standards-forward `fetch()` event handling built into [StarlingMonkey][sm] |
| [`http-server-hono`](./http-server-hono) | HTTP server using the standards-forward [Hono][hono] framework |
| [`native-messaging`](./native-messaging-host) | [Native Messaging][native-messaging] host using `wasi:cli`
| [`node-fetch`](./node-fetch) | Performs a HTTP request using `fetch()` |
| [`string-reverse-upper`](./string-reverse-upper) | `import`s functionality to build more advanced computation to `export` |
| [`string-reverse`](./string-reverse) | `export`s basic functionality with a slightly more involved WIT interface and more complex types |
Expand All @@ -23,6 +24,7 @@ A brief description of the examples contained in this folder:
| [`webidl-book-library`](./webidl-book-library) | Showcases [WebIDL][webidl] support using [`webidl2wit`][webidl2wit] |

[hono]: https://hono.dev
[native-messaging]: https://github.com/guest271314/NativeMessagingHosts#native-messaging-documentation
[nodejs]: https://nodejs.org
[sm]: https://github.com/bytecodealliance/StarlingMonkey
[wasi-http]: https://github.com/WebAssembly/WASI/tree/main/proposals/http
Expand Down
87 changes: 87 additions & 0 deletions examples/components/native-messaging/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
### Install dependencies

```shell
bun install --cache-dir ./.bun-cache @bytecodealliance/componentize-js
```

```shell
cargo bininstall wkg
wkg wit fetch -d . --cache ./.wit-cache
```

### Compile to WASM

```shell
bun x componentize-js nm_compoentize_js.js --wit . --world-name native-messaging-componentize-js -d http -d fetch-event --out nm_componentize_js.wasm
```

Using `jco`

```shell
bun x jco componentize nm_componentize_js.js --wit . --world-name native-messaging-componentize-js -d http -d fetch-event --out nm_jco.wasm

```

### Installation and usage on Chrome and Chromium

1. Navigate to `chrome://extensions`.
2. Toggle `Developer mode`.
3. Click `Load unpacked`.
4. Select `native-messaging` folder.
5. Note the generated extension ID.
6. Open `nm_componentize_js.json` in a text editor, set `"path"` to absolute path of `nm_componentize_js.sh` and `chrome-extension://<ID>/` using ID from 5 in `"allowed_origins"` array, and set `nm_componentize_js.sh` permission to executable. When compiled with `jco` adjust the file name executed by `wasmtime` in `nm_componentize_js.sh` to `nm_jco.wasm`.
7. Copy the file to Chrome or Chromium configuration folder, e.g., Chromium on Linux `~/.config/chromium/NativeMessagingHosts`; Chrome Dev Channel on Linux `~/.config/google-chrome-unstable/NativeMessagingHosts`.
8. To test click `service worker` link in panel of unpacked extension which is DevTools for `background.js` in MV3 `ServiceWorker`, observe echo'ed message from Bun Native Messaging host. To disconnect run `port.disconnect()`.

The Native Messaging host echoes back the message passed.



### Examples


The maximum message length sent from the client to the host in one message is 64 MiB of JSON. The host can only send 1 MiB of valid JSON back to the client per message. This host is implemented to handle JSON `Array` messages from the client over 1 MiB; since a JSON `Array` is similar in structure to a `Uint8Array` (essentially any data type can be converted to a `u8` type and written to a `Uint8Array`) facilitating streaming data back and forth between browser and native applications.


Round trip 64 MiB of JSON, disconnect the host (terminate the process) after all bytes are echoed. Connecting to the host with `connectNative()` keeps the host alive for the life of the tab/window/browser process. Once `connectNative()` is executed, the host can send messages and stream data to the client, without the client necessarily needing to send any messages to the host. `disconnect()` disconnects the client from the host and terminates the native process.

```javascript
var data = Array(209715*64);
var len = data.length;
var n = 0;
var port = chrome.runtime.connectNative("nm_componentize_js");
port.onMessage.addListener((message) => {
n += message.length;
if (n === len) {
console.log({n, len});
port.disconnect();
}
});
port.onDisconnect.addListener((_) => {
console.log("Disconnected");
if (chrome.runtime.lastError) {
console.log(chrome.runtime.lastError);
}
});
port.postMessage(data);
data.length = 0;
```

Using `sendNativeMessage` sends one message at a time, the callback receives the message from the host, and then the process exits. Send arbitrary command to the host to execute and send results back, e.g., a REPL (requires modification of the base echo/roundtrip example host `nm_componentize_js.js`).

```javascript
chrome.runtime.sendNativeMessage(
"nm_componentize_js",
{ command: "deno eval 'Deno.stdout.write(new TextEncoder().encode(JSON.stringify(Deno.version)))'" },
(message) => {
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError);
}
console.log(message);
});
```

For differences between OS and browser implementations see [Chrome incompatibilities](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Chrome_incompatibilities#native_messaging).



10 changes: 10 additions & 0 deletions examples/components/native-messaging/background.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
globalThis.name = chrome.runtime.getManifest().short_name;

globalThis.port = chrome.runtime.connectNative(globalThis.name);
port.onMessage.addListener((message) => console.log(message));
port.onDisconnect.addListener((p) => console.log(chrome.runtime.lastError));
port.postMessage(new Array(209715));

chrome.runtime.onInstalled.addListener((reason) => {
console.log(reason);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
package wasi:cli@0.2.10;

@since(version = 0.2.0)
interface environment {
/// Get the POSIX-style environment variables.
///
/// Each environment variable is provided as a pair of string variable names
/// and string value.
///
/// Morally, these are a value import, but until value imports are available
/// in the component model, this import function should return the same
/// values each time it is called.
@since(version = 0.2.0)
get-environment: func() -> list<tuple<string, string>>;

/// Get the POSIX-style arguments to the program.
@since(version = 0.2.0)
get-arguments: func() -> list<string>;

/// Return a path that programs should use as their initial current working
/// directory, interpreting `.` as shorthand for this.
@since(version = 0.2.0)
initial-cwd: func() -> option<string>;
}

@since(version = 0.2.0)
interface exit {
/// Exit the current instance and any linked instances.
@since(version = 0.2.0)
exit: func(status: result);

/// Exit the current instance and any linked instances, reporting the
/// specified status code to the host.
///
/// The meaning of the code depends on the context, with 0 usually meaning
/// "success", and other values indicating various types of failure.
///
/// This function does not return; the effect is analogous to a trap, but
/// without the connotation that something bad has happened.
@unstable(feature = cli-exit-with-code)
exit-with-code: func(status-code: u8);
}

@since(version = 0.2.0)
interface run {
/// Run the program.
@since(version = 0.2.0)
run: func() -> result;
}

@since(version = 0.2.0)
interface stdin {
@since(version = 0.2.0)
use wasi:io/streams@0.2.10.{input-stream};

@since(version = 0.2.0)
get-stdin: func() -> input-stream;
}

@since(version = 0.2.0)
interface stdout {
@since(version = 0.2.0)
use wasi:io/streams@0.2.10.{output-stream};

@since(version = 0.2.0)
get-stdout: func() -> output-stream;
}

@since(version = 0.2.0)
interface stderr {
@since(version = 0.2.0)
use wasi:io/streams@0.2.10.{output-stream};

@since(version = 0.2.0)
get-stderr: func() -> output-stream;
}

/// Terminal input.
///
/// In the future, this may include functions for disabling echoing,
/// disabling input buffering so that keyboard events are sent through
/// immediately, querying supported features, and so on.
@since(version = 0.2.0)
interface terminal-input {
/// The input side of a terminal.
@since(version = 0.2.0)
resource terminal-input;
}

/// Terminal output.
///
/// In the future, this may include functions for querying the terminal
/// size, being notified of terminal size changes, querying supported
/// features, and so on.
@since(version = 0.2.0)
interface terminal-output {
/// The output side of a terminal.
@since(version = 0.2.0)
resource terminal-output;
}

/// An interface providing an optional `terminal-input` for stdin as a
/// link-time authority.
@since(version = 0.2.0)
interface terminal-stdin {
@since(version = 0.2.0)
use terminal-input.{terminal-input};

/// If stdin is connected to a terminal, return a `terminal-input` handle
/// allowing further interaction with it.
@since(version = 0.2.0)
get-terminal-stdin: func() -> option<terminal-input>;
}

/// An interface providing an optional `terminal-output` for stdout as a
/// link-time authority.
@since(version = 0.2.0)
interface terminal-stdout {
@since(version = 0.2.0)
use terminal-output.{terminal-output};

/// If stdout is connected to a terminal, return a `terminal-output` handle
/// allowing further interaction with it.
@since(version = 0.2.0)
get-terminal-stdout: func() -> option<terminal-output>;
}

/// An interface providing an optional `terminal-output` for stderr as a
/// link-time authority.
@since(version = 0.2.0)
interface terminal-stderr {
@since(version = 0.2.0)
use terminal-output.{terminal-output};

/// If stderr is connected to a terminal, return a `terminal-output` handle
/// allowing further interaction with it.
@since(version = 0.2.0)
get-terminal-stderr: func() -> option<terminal-output>;
}

@since(version = 0.2.0)
world imports {
@since(version = 0.2.0)
import environment;
@since(version = 0.2.0)
import exit;
@since(version = 0.2.0)
import wasi:io/error@0.2.10;
@since(version = 0.2.0)
import wasi:io/poll@0.2.10;
@since(version = 0.2.0)
import wasi:io/streams@0.2.10;
@since(version = 0.2.0)
import stdin;
@since(version = 0.2.0)
import stdout;
@since(version = 0.2.0)
import stderr;
@since(version = 0.2.0)
import terminal-input;
@since(version = 0.2.0)
import terminal-output;
@since(version = 0.2.0)
import terminal-stdin;
@since(version = 0.2.0)
import terminal-stdout;
@since(version = 0.2.0)
import terminal-stderr;
import wasi:clocks/monotonic-clock@0.2.10;
import wasi:clocks/wall-clock@0.2.10;
@unstable(feature = clocks-timezone)
import wasi:clocks/timezone@0.2.10;
import wasi:filesystem/types@0.2.10;
import wasi:filesystem/preopens@0.2.10;
import wasi:sockets/network@0.2.10;
import wasi:sockets/instance-network@0.2.10;
import wasi:sockets/udp@0.2.10;
import wasi:sockets/udp-create-socket@0.2.10;
import wasi:sockets/tcp@0.2.10;
import wasi:sockets/tcp-create-socket@0.2.10;
import wasi:sockets/ip-name-lookup@0.2.10;
import wasi:random/random@0.2.10;
import wasi:random/insecure@0.2.10;
import wasi:random/insecure-seed@0.2.10;
}
@since(version = 0.2.0)
world command {
@since(version = 0.2.0)
import environment;
@since(version = 0.2.0)
import exit;
@since(version = 0.2.0)
import wasi:io/error@0.2.10;
@since(version = 0.2.0)
import wasi:io/poll@0.2.10;
@since(version = 0.2.0)
import wasi:io/streams@0.2.10;
@since(version = 0.2.0)
import stdin;
@since(version = 0.2.0)
import stdout;
@since(version = 0.2.0)
import stderr;
@since(version = 0.2.0)
import terminal-input;
@since(version = 0.2.0)
import terminal-output;
@since(version = 0.2.0)
import terminal-stdin;
@since(version = 0.2.0)
import terminal-stdout;
@since(version = 0.2.0)
import terminal-stderr;
import wasi:clocks/monotonic-clock@0.2.10;
import wasi:clocks/wall-clock@0.2.10;
@unstable(feature = clocks-timezone)
import wasi:clocks/timezone@0.2.10;
import wasi:filesystem/types@0.2.10;
import wasi:filesystem/preopens@0.2.10;
import wasi:sockets/network@0.2.10;
import wasi:sockets/instance-network@0.2.10;
import wasi:sockets/udp@0.2.10;
import wasi:sockets/udp-create-socket@0.2.10;
import wasi:sockets/tcp@0.2.10;
import wasi:sockets/tcp-create-socket@0.2.10;
import wasi:sockets/ip-name-lookup@0.2.10;
import wasi:random/random@0.2.10;
import wasi:random/insecure@0.2.10;
import wasi:random/insecure-seed@0.2.10;

@since(version = 0.2.0)
export run;
}
Loading