From 5bb6672ecab6ff36c3079d7f164bf8e0ea537310 Mon Sep 17 00:00:00 2001 From: guest271314 Date: Sun, 14 Jun 2026 13:21:20 +0000 Subject: [PATCH 1/6] Native Messaging example Signed-off-by: guest271314 --- examples/components/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/components/README.md b/examples/components/README.md index 4f6b96a1b..deae002c8 100644 --- a/examples/components/README.md +++ b/examples/components/README.md @@ -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: @@ -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 | @@ -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 From 69d91b8c66a23014f3764299001d54881980eb19 Mon Sep 17 00:00:00 2001 From: guest271314 Date: Sun, 14 Jun 2026 13:26:22 +0000 Subject: [PATCH 2/6] Native Messaging example Signed-off-by: guest271314 --- examples/components/native-messaging/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 examples/components/native-messaging/README.md diff --git a/examples/components/native-messaging/README.md b/examples/components/native-messaging/README.md new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/examples/components/native-messaging/README.md @@ -0,0 +1 @@ + From e1a323b720d624a0fc02a1a27f3c4c5084ddd9ce Mon Sep 17 00:00:00 2001 From: guest271314 Date: Sun, 14 Jun 2026 13:57:21 +0000 Subject: [PATCH 3/6] Native Messaging example Signed-off-by: guest271314 --- .../components/native-messaging/README.md | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/examples/components/native-messaging/README.md b/examples/components/native-messaging/README.md index 8b1378917..210d44d68 100644 --- a/examples/components/native-messaging/README.md +++ b/examples/components/native-messaging/README.md @@ -1 +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:///` 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; +``` + +Send arbitrary command to the host to execute and send results back, e.g., a REPL. Using `sendNativeMessage` sends one message at a time, the callback receives the message from the host, and then the process exits. + +```javascript +chrome.runtime.sendNativeMessage( + "nm_componentize_js", + { command: `node -e 'console.log(require("fs").readFileSync("package.wit"))` }, + (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). + + From 7076e7911f1a8ef36a582c2578b320ff7f292e7f Mon Sep 17 00:00:00 2001 From: guest271314 Date: Sun, 14 Jun 2026 13:58:15 +0000 Subject: [PATCH 4/6] Native Messaging example Signed-off-by: guest271314 --- examples/components/native-messaging/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/components/native-messaging/README.md b/examples/components/native-messaging/README.md index 210d44d68..09c4b262e 100644 --- a/examples/components/native-messaging/README.md +++ b/examples/components/native-messaging/README.md @@ -22,7 +22,7 @@ bun x jco componentize nm_componentize_js.js --wit . --world-name native-messagi ``` -# Installation and usage on Chrome and Chromium +### Installation and usage on Chrome and Chromium 1. Navigate to `chrome://extensions`. 2. Toggle `Developer mode`. @@ -37,7 +37,7 @@ The Native Messaging host echoes back the message passed. -# Examples +### 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. From 1b3af996dbeed8fa802db48ea16c404370b8da77 Mon Sep 17 00:00:00 2001 From: guest271314 Date: Sun, 14 Jun 2026 14:04:46 +0000 Subject: [PATCH 5/6] Native Messaging example --- .../components/native-messaging/background.js | 10 + .../deps/wasi-cli-0.2.10/package.wit | 233 +++++++ .../deps/wasi-clocks-0.2.10/package.wit | 162 +++++ .../deps/wasi-filesystem-0.2.10/package.wit | 589 ++++++++++++++++++ .../deps/wasi-io-0.2.10/package.wit | 299 +++++++++ .../deps/wasi-random-0.2.10/package.wit | 92 +++ .../deps/wasi-sockets-0.2.10/package.wit | 183 ++++++ .../components/native-messaging/manifest.json | 14 + .../native-messaging/nm_componentize_js.js | 89 +++ .../native-messaging/nm_componentize_js.json | 9 + .../components/native-messaging/package.wit | 22 + examples/components/native-messaging/wkg.lock | 48 ++ 12 files changed, 1750 insertions(+) create mode 100644 examples/components/native-messaging/background.js create mode 100644 examples/components/native-messaging/deps/wasi-cli-0.2.10/package.wit create mode 100644 examples/components/native-messaging/deps/wasi-clocks-0.2.10/package.wit create mode 100644 examples/components/native-messaging/deps/wasi-filesystem-0.2.10/package.wit create mode 100644 examples/components/native-messaging/deps/wasi-io-0.2.10/package.wit create mode 100644 examples/components/native-messaging/deps/wasi-random-0.2.10/package.wit create mode 100644 examples/components/native-messaging/deps/wasi-sockets-0.2.10/package.wit create mode 100644 examples/components/native-messaging/manifest.json create mode 100644 examples/components/native-messaging/nm_componentize_js.js create mode 100644 examples/components/native-messaging/nm_componentize_js.json create mode 100644 examples/components/native-messaging/package.wit create mode 100644 examples/components/native-messaging/wkg.lock diff --git a/examples/components/native-messaging/background.js b/examples/components/native-messaging/background.js new file mode 100644 index 000000000..43184b1a8 --- /dev/null +++ b/examples/components/native-messaging/background.js @@ -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); +}); diff --git a/examples/components/native-messaging/deps/wasi-cli-0.2.10/package.wit b/examples/components/native-messaging/deps/wasi-cli-0.2.10/package.wit new file mode 100644 index 000000000..a7c5e04f1 --- /dev/null +++ b/examples/components/native-messaging/deps/wasi-cli-0.2.10/package.wit @@ -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>; + + /// Get the POSIX-style arguments to the program. + @since(version = 0.2.0) + get-arguments: func() -> list; + + /// 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; +} + +@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; +} + +/// 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; +} + +/// 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; +} + +@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; +} diff --git a/examples/components/native-messaging/deps/wasi-clocks-0.2.10/package.wit b/examples/components/native-messaging/deps/wasi-clocks-0.2.10/package.wit new file mode 100644 index 000000000..b267b6bff --- /dev/null +++ b/examples/components/native-messaging/deps/wasi-clocks-0.2.10/package.wit @@ -0,0 +1,162 @@ +package wasi:clocks@0.2.10; + +/// WASI Monotonic Clock is a clock API intended to let users measure elapsed +/// time. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +/// +/// A monotonic clock is a clock which has an unspecified initial value, and +/// successive reads of the clock will produce non-decreasing values. +@since(version = 0.2.0) +interface monotonic-clock { + @since(version = 0.2.0) + use wasi:io/poll@0.2.10.{pollable}; + + /// An instant in time, in nanoseconds. An instant is relative to an + /// unspecified initial value, and can only be compared to instances from + /// the same monotonic-clock. + @since(version = 0.2.0) + type instant = u64; + + /// A duration of time, in nanoseconds. + @since(version = 0.2.0) + type duration = u64; + + /// Read the current value of the clock. + /// + /// The clock is monotonic, therefore calling this function repeatedly will + /// produce a sequence of non-decreasing values. + /// + /// For completeness, this function traps if it's not possible to represent + /// the value of the clock in an `instant`. Consequently, implementations + /// should ensure that the starting time is low enough to avoid the + /// possibility of overflow in practice. + @since(version = 0.2.0) + now: func() -> instant; + + /// Query the resolution of the clock. Returns the duration of time + /// corresponding to a clock tick. + @since(version = 0.2.0) + resolution: func() -> duration; + + /// Create a `pollable` which will resolve once the specified instant + /// has occurred. + @since(version = 0.2.0) + subscribe-instant: func(when: instant) -> pollable; + + /// Create a `pollable` that will resolve after the specified duration has + /// elapsed from the time this function is invoked. + @since(version = 0.2.0) + subscribe-duration: func(when: duration) -> pollable; +} + +/// WASI Wall Clock is a clock API intended to let users query the current +/// time. The name "wall" makes an analogy to a "clock on the wall", which +/// is not necessarily monotonic as it may be reset. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +/// +/// A wall clock is a clock which measures the date and time according to +/// some external reference. +/// +/// External references may be reset, so this clock is not necessarily +/// monotonic, making it unsuitable for measuring elapsed time. +/// +/// It is intended for reporting the current date and time for humans. +@since(version = 0.2.0) +interface wall-clock { + /// A time and date in seconds plus nanoseconds. + @since(version = 0.2.0) + record datetime { + seconds: u64, + nanoseconds: u32, + } + + /// Read the current value of the clock. + /// + /// This clock is not monotonic, therefore calling this function repeatedly + /// will not necessarily produce a sequence of non-decreasing values. + /// + /// The returned timestamps represent the number of seconds since + /// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], + /// also known as [Unix Time]. + /// + /// The nanoseconds field of the output is always less than 1000000000. + /// + /// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 + /// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time + @since(version = 0.2.0) + now: func() -> datetime; + + /// Query the resolution of the clock. + /// + /// The nanoseconds field of the output is always less than 1000000000. + @since(version = 0.2.0) + resolution: func() -> datetime; +} + +@unstable(feature = clocks-timezone) +interface timezone { + @unstable(feature = clocks-timezone) + use wall-clock.{datetime}; + + /// Information useful for displaying the timezone of a specific `datetime`. + /// + /// This information may vary within a single `timezone` to reflect daylight + /// saving time adjustments. + @unstable(feature = clocks-timezone) + record timezone-display { + /// The number of seconds difference between UTC time and the local + /// time of the timezone. + /// + /// The returned value will always be less than 86400 which is the + /// number of seconds in a day (24*60*60). + /// + /// In implementations that do not expose an actual time zone, this + /// should return 0. + utc-offset: s32, + /// The abbreviated name of the timezone to display to a user. The name + /// `UTC` indicates Coordinated Universal Time. Otherwise, this should + /// reference local standards for the name of the time zone. + /// + /// In implementations that do not expose an actual time zone, this + /// should be the string `UTC`. + /// + /// In time zones that do not have an applicable name, a formatted + /// representation of the UTC offset may be returned, such as `-04:00`. + name: string, + /// Whether daylight saving time is active. + /// + /// In implementations that do not expose an actual time zone, this + /// should return false. + in-daylight-saving-time: bool, + } + + /// Return information needed to display the given `datetime`. This includes + /// the UTC offset, the time zone name, and a flag indicating whether + /// daylight saving time is active. + /// + /// If the timezone cannot be determined for the given `datetime`, return a + /// `timezone-display` for `UTC` with a `utc-offset` of 0 and no daylight + /// saving time. + @unstable(feature = clocks-timezone) + display: func(when: datetime) -> timezone-display; + + /// The same as `display`, but only return the UTC offset. + @unstable(feature = clocks-timezone) + utc-offset: func(when: datetime) -> s32; +} + +@since(version = 0.2.0) +world imports { + @since(version = 0.2.0) + import wasi:io/poll@0.2.10; + @since(version = 0.2.0) + import monotonic-clock; + @since(version = 0.2.0) + import wall-clock; + @unstable(feature = clocks-timezone) + import timezone; +} diff --git a/examples/components/native-messaging/deps/wasi-filesystem-0.2.10/package.wit b/examples/components/native-messaging/deps/wasi-filesystem-0.2.10/package.wit new file mode 100644 index 000000000..91e203fc7 --- /dev/null +++ b/examples/components/native-messaging/deps/wasi-filesystem-0.2.10/package.wit @@ -0,0 +1,589 @@ +package wasi:filesystem@0.2.10; + +/// WASI filesystem is a filesystem API primarily intended to let users run WASI +/// programs that access their files on their existing filesystems, without +/// significant overhead. +/// +/// It is intended to be roughly portable between Unix-family platforms and +/// Windows, though it does not hide many of the major differences. +/// +/// Paths are passed as interface-type `string`s, meaning they must consist of +/// a sequence of Unicode Scalar Values (USVs). Some filesystems may contain +/// paths which are not accessible by this API. +/// +/// The directory separator in WASI is always the forward-slash (`/`). +/// +/// All paths in WASI are relative paths, and are interpreted relative to a +/// `descriptor` referring to a base directory. If a `path` argument to any WASI +/// function starts with `/`, or if any step of resolving a `path`, including +/// `..` and symbolic link steps, reaches a directory outside of the base +/// directory, or reaches a symlink to an absolute or rooted path in the +/// underlying filesystem, the function fails with `error-code::not-permitted`. +/// +/// For more information about WASI path resolution and sandboxing, see +/// [WASI filesystem path resolution]. +/// +/// [WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md +@since(version = 0.2.0) +interface types { + @since(version = 0.2.0) + use wasi:io/streams@0.2.10.{input-stream, output-stream, error}; + @since(version = 0.2.0) + use wasi:clocks/wall-clock@0.2.10.{datetime}; + + /// File size or length of a region within a file. + @since(version = 0.2.0) + type filesize = u64; + + /// The type of a filesystem object referenced by a descriptor. + /// + /// Note: This was called `filetype` in earlier versions of WASI. + @since(version = 0.2.0) + enum descriptor-type { + /// The type of the descriptor or file is unknown or is different from + /// any of the other types specified. + unknown, + /// The descriptor refers to a block device inode. + block-device, + /// The descriptor refers to a character device inode. + character-device, + /// The descriptor refers to a directory inode. + directory, + /// The descriptor refers to a named pipe. + fifo, + /// The file refers to a symbolic link inode. + symbolic-link, + /// The descriptor refers to a regular file inode. + regular-file, + /// The descriptor refers to a socket. + socket, + } + + /// Descriptor flags. + /// + /// Note: This was called `fdflags` in earlier versions of WASI. + @since(version = 0.2.0) + flags descriptor-flags { + /// Read mode: Data can be read. + read, + /// Write mode: Data can be written to. + write, + /// Request that writes be performed according to synchronized I/O file + /// integrity completion. The data stored in the file and the file's + /// metadata are synchronized. This is similar to `O_SYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + file-integrity-sync, + /// Request that writes be performed according to synchronized I/O data + /// integrity completion. Only the data stored in the file is + /// synchronized. This is similar to `O_DSYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + data-integrity-sync, + /// Requests that reads be performed at the same level of integrity + /// requested for writes. This is similar to `O_RSYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + requested-write-sync, + /// Mutating directories mode: Directory contents may be mutated. + /// + /// When this flag is unset on a descriptor, operations using the + /// descriptor which would create, rename, delete, modify the data or + /// metadata of filesystem objects, or obtain another handle which + /// would permit any of those, shall fail with `error-code::read-only` if + /// they would otherwise succeed. + /// + /// This may only be set on directories. + mutate-directory, + } + + /// Flags determining the method of how paths are resolved. + @since(version = 0.2.0) + flags path-flags { + /// As long as the resolved path corresponds to a symbolic link, it is + /// expanded. + symlink-follow, + } + + /// Open flags used by `open-at`. + @since(version = 0.2.0) + flags open-flags { + /// Create file if it does not exist, similar to `O_CREAT` in POSIX. + create, + /// Fail if not a directory, similar to `O_DIRECTORY` in POSIX. + directory, + /// Fail if file already exists, similar to `O_EXCL` in POSIX. + exclusive, + /// Truncate file to size 0, similar to `O_TRUNC` in POSIX. + truncate, + } + + /// Number of hard links to an inode. + @since(version = 0.2.0) + type link-count = u64; + + /// File attributes. + /// + /// Note: This was called `filestat` in earlier versions of WASI. + @since(version = 0.2.0) + record descriptor-stat { + /// File type. + %type: descriptor-type, + /// Number of hard links to the file. + link-count: link-count, + /// For regular files, the file size in bytes. For symbolic links, the + /// length in bytes of the pathname contained in the symbolic link. + size: filesize, + /// Last data access timestamp. + /// + /// If the `option` is none, the platform doesn't maintain an access + /// timestamp for this file. + data-access-timestamp: option, + /// Last data modification timestamp. + /// + /// If the `option` is none, the platform doesn't maintain a + /// modification timestamp for this file. + data-modification-timestamp: option, + /// Last file status-change timestamp. + /// + /// If the `option` is none, the platform doesn't maintain a + /// status-change timestamp for this file. + status-change-timestamp: option, + } + + /// When setting a timestamp, this gives the value to set it to. + @since(version = 0.2.0) + variant new-timestamp { + /// Leave the timestamp set to its previous value. + no-change, + /// Set the timestamp to the current time of the system clock associated + /// with the filesystem. + now, + /// Set the timestamp to the given value. + timestamp(datetime), + } + + /// A directory entry. + @since(version = 0.2.0) + record directory-entry { + /// The type of the file referred to by this directory entry. + %type: descriptor-type, + /// The name of the object. + name: string, + } + + /// Error codes returned by functions, similar to `errno` in POSIX. + /// Not all of these error codes are returned by the functions provided by this + /// API; some are used in higher-level library layers, and others are provided + /// merely for alignment with POSIX. + @since(version = 0.2.0) + enum error-code { + /// Permission denied, similar to `EACCES` in POSIX. + access, + /// Resource unavailable, or operation would block, similar to `EAGAIN` and `EWOULDBLOCK` in POSIX. + would-block, + /// Connection already in progress, similar to `EALREADY` in POSIX. + already, + /// Bad descriptor, similar to `EBADF` in POSIX. + bad-descriptor, + /// Device or resource busy, similar to `EBUSY` in POSIX. + busy, + /// Resource deadlock would occur, similar to `EDEADLK` in POSIX. + deadlock, + /// Storage quota exceeded, similar to `EDQUOT` in POSIX. + quota, + /// File exists, similar to `EEXIST` in POSIX. + exist, + /// File too large, similar to `EFBIG` in POSIX. + file-too-large, + /// Illegal byte sequence, similar to `EILSEQ` in POSIX. + illegal-byte-sequence, + /// Operation in progress, similar to `EINPROGRESS` in POSIX. + in-progress, + /// Interrupted function, similar to `EINTR` in POSIX. + interrupted, + /// Invalid argument, similar to `EINVAL` in POSIX. + invalid, + /// I/O error, similar to `EIO` in POSIX. + io, + /// Is a directory, similar to `EISDIR` in POSIX. + is-directory, + /// Too many levels of symbolic links, similar to `ELOOP` in POSIX. + loop, + /// Too many links, similar to `EMLINK` in POSIX. + too-many-links, + /// Message too large, similar to `EMSGSIZE` in POSIX. + message-size, + /// Filename too long, similar to `ENAMETOOLONG` in POSIX. + name-too-long, + /// No such device, similar to `ENODEV` in POSIX. + no-device, + /// No such file or directory, similar to `ENOENT` in POSIX. + no-entry, + /// No locks available, similar to `ENOLCK` in POSIX. + no-lock, + /// Not enough space, similar to `ENOMEM` in POSIX. + insufficient-memory, + /// No space left on device, similar to `ENOSPC` in POSIX. + insufficient-space, + /// Not a directory or a symbolic link to a directory, similar to `ENOTDIR` in POSIX. + not-directory, + /// Directory not empty, similar to `ENOTEMPTY` in POSIX. + not-empty, + /// State not recoverable, similar to `ENOTRECOVERABLE` in POSIX. + not-recoverable, + /// Not supported, similar to `ENOTSUP` and `ENOSYS` in POSIX. + unsupported, + /// Inappropriate I/O control operation, similar to `ENOTTY` in POSIX. + no-tty, + /// No such device or address, similar to `ENXIO` in POSIX. + no-such-device, + /// Value too large to be stored in data type, similar to `EOVERFLOW` in POSIX. + overflow, + /// Operation not permitted, similar to `EPERM` in POSIX. + not-permitted, + /// Broken pipe, similar to `EPIPE` in POSIX. + pipe, + /// Read-only file system, similar to `EROFS` in POSIX. + read-only, + /// Invalid seek, similar to `ESPIPE` in POSIX. + invalid-seek, + /// Text file busy, similar to `ETXTBSY` in POSIX. + text-file-busy, + /// Cross-device link, similar to `EXDEV` in POSIX. + cross-device, + } + + /// File or memory access pattern advisory information. + @since(version = 0.2.0) + enum advice { + /// The application has no advice to give on its behavior with respect + /// to the specified data. + normal, + /// The application expects to access the specified data sequentially + /// from lower offsets to higher offsets. + sequential, + /// The application expects to access the specified data in a random + /// order. + random, + /// The application expects to access the specified data in the near + /// future. + will-need, + /// The application expects that it will not access the specified data + /// in the near future. + dont-need, + /// The application expects to access the specified data once and then + /// not reuse it thereafter. + no-reuse, + } + + /// A 128-bit hash value, split into parts because wasm doesn't have a + /// 128-bit integer type. + @since(version = 0.2.0) + record metadata-hash-value { + /// 64 bits of a 128-bit hash value. + lower: u64, + /// Another 64 bits of a 128-bit hash value. + upper: u64, + } + + /// A descriptor is a reference to a filesystem object, which may be a file, + /// directory, named pipe, special file, or other object on which filesystem + /// calls may be made. + @since(version = 0.2.0) + resource descriptor { + /// Return a stream for reading from a file, if available. + /// + /// May fail with an error-code describing why the file cannot be read. + /// + /// Multiple read, write, and append streams may be active on the same open + /// file and they do not interfere with each other. + /// + /// Note: This allows using `read-stream`, which is similar to `read` in POSIX. + @since(version = 0.2.0) + read-via-stream: func(offset: filesize) -> result; + /// Return a stream for writing to a file, if available. + /// + /// May fail with an error-code describing why the file cannot be written. + /// + /// Note: This allows using `write-stream`, which is similar to `write` in + /// POSIX. + @since(version = 0.2.0) + write-via-stream: func(offset: filesize) -> result; + /// Return a stream for appending to a file, if available. + /// + /// May fail with an error-code describing why the file cannot be appended. + /// + /// Note: This allows using `write-stream`, which is similar to `write` with + /// `O_APPEND` in POSIX. + @since(version = 0.2.0) + append-via-stream: func() -> result; + /// Provide file advisory information on a descriptor. + /// + /// This is similar to `posix_fadvise` in POSIX. + @since(version = 0.2.0) + advise: func(offset: filesize, length: filesize, advice: advice) -> result<_, error-code>; + /// Synchronize the data of a file to disk. + /// + /// This function succeeds with no effect if the file descriptor is not + /// opened for writing. + /// + /// Note: This is similar to `fdatasync` in POSIX. + @since(version = 0.2.0) + sync-data: func() -> result<_, error-code>; + /// Get flags associated with a descriptor. + /// + /// Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX. + /// + /// Note: This returns the value that was the `fs_flags` value returned + /// from `fdstat_get` in earlier versions of WASI. + @since(version = 0.2.0) + get-flags: func() -> result; + /// Get the dynamic type of a descriptor. + /// + /// Note: This returns the same value as the `type` field of the `fd-stat` + /// returned by `stat`, `stat-at` and similar. + /// + /// Note: This returns similar flags to the `st_mode & S_IFMT` value provided + /// by `fstat` in POSIX. + /// + /// Note: This returns the value that was the `fs_filetype` value returned + /// from `fdstat_get` in earlier versions of WASI. + @since(version = 0.2.0) + get-type: func() -> result; + /// Adjust the size of an open file. If this increases the file's size, the + /// extra bytes are filled with zeros. + /// + /// Note: This was called `fd_filestat_set_size` in earlier versions of WASI. + @since(version = 0.2.0) + set-size: func(size: filesize) -> result<_, error-code>; + /// Adjust the timestamps of an open file or directory. + /// + /// Note: This is similar to `futimens` in POSIX. + /// + /// Note: This was called `fd_filestat_set_times` in earlier versions of WASI. + @since(version = 0.2.0) + set-times: func(data-access-timestamp: new-timestamp, data-modification-timestamp: new-timestamp) -> result<_, error-code>; + /// Read from a descriptor, without using and updating the descriptor's offset. + /// + /// This function returns a list of bytes containing the data that was + /// read, along with a bool which, when true, indicates that the end of the + /// file was reached. The returned list will contain up to `length` bytes; it + /// may return fewer than requested, if the end of the file is reached or + /// if the I/O operation is interrupted. + /// + /// In the future, this may change to return a `stream`. + /// + /// Note: This is similar to `pread` in POSIX. + @since(version = 0.2.0) + read: func(length: filesize, offset: filesize) -> result, bool>, error-code>; + /// Write to a descriptor, without using and updating the descriptor's offset. + /// + /// It is valid to write past the end of a file; the file is extended to the + /// extent of the write, with bytes between the previous end and the start of + /// the write set to zero. + /// + /// In the future, this may change to take a `stream`. + /// + /// Note: This is similar to `pwrite` in POSIX. + @since(version = 0.2.0) + write: func(buffer: list, offset: filesize) -> result; + /// Read directory entries from a directory. + /// + /// On filesystems where directories contain entries referring to themselves + /// and their parents, often named `.` and `..` respectively, these entries + /// are omitted. + /// + /// This always returns a new stream which starts at the beginning of the + /// directory. Multiple streams may be active on the same directory, and they + /// do not interfere with each other. + @since(version = 0.2.0) + read-directory: func() -> result; + /// Synchronize the data and metadata of a file to disk. + /// + /// This function succeeds with no effect if the file descriptor is not + /// opened for writing. + /// + /// Note: This is similar to `fsync` in POSIX. + @since(version = 0.2.0) + sync: func() -> result<_, error-code>; + /// Create a directory. + /// + /// Note: This is similar to `mkdirat` in POSIX. + @since(version = 0.2.0) + create-directory-at: func(path: string) -> result<_, error-code>; + /// Return the attributes of an open file or directory. + /// + /// Note: This is similar to `fstat` in POSIX, except that it does not return + /// device and inode information. For testing whether two descriptors refer to + /// the same underlying filesystem object, use `is-same-object`. To obtain + /// additional data that can be used do determine whether a file has been + /// modified, use `metadata-hash`. + /// + /// Note: This was called `fd_filestat_get` in earlier versions of WASI. + @since(version = 0.2.0) + stat: func() -> result; + /// Return the attributes of a file or directory. + /// + /// Note: This is similar to `fstatat` in POSIX, except that it does not + /// return device and inode information. See the `stat` description for a + /// discussion of alternatives. + /// + /// Note: This was called `path_filestat_get` in earlier versions of WASI. + @since(version = 0.2.0) + stat-at: func(path-flags: path-flags, path: string) -> result; + /// Adjust the timestamps of a file or directory. + /// + /// Note: This is similar to `utimensat` in POSIX. + /// + /// Note: This was called `path_filestat_set_times` in earlier versions of + /// WASI. + @since(version = 0.2.0) + set-times-at: func(path-flags: path-flags, path: string, data-access-timestamp: new-timestamp, data-modification-timestamp: new-timestamp) -> result<_, error-code>; + /// Create a hard link. + /// + /// Fails with `error-code::no-entry` if the old path does not exist, + /// with `error-code::exist` if the new path already exists, and + /// `error-code::not-permitted` if the old path is not a file. + /// + /// Note: This is similar to `linkat` in POSIX. + @since(version = 0.2.0) + link-at: func(old-path-flags: path-flags, old-path: string, new-descriptor: borrow, new-path: string) -> result<_, error-code>; + /// Open a file or directory. + /// + /// If `flags` contains `descriptor-flags::mutate-directory`, and the base + /// descriptor doesn't have `descriptor-flags::mutate-directory` set, + /// `open-at` fails with `error-code::read-only`. + /// + /// If `flags` contains `write` or `mutate-directory`, or `open-flags` + /// contains `truncate` or `create`, and the base descriptor doesn't have + /// `descriptor-flags::mutate-directory` set, `open-at` fails with + /// `error-code::read-only`. + /// + /// Note: This is similar to `openat` in POSIX. + @since(version = 0.2.0) + open-at: func(path-flags: path-flags, path: string, open-flags: open-flags, %flags: descriptor-flags) -> result; + /// Read the contents of a symbolic link. + /// + /// If the contents contain an absolute or rooted path in the underlying + /// filesystem, this function fails with `error-code::not-permitted`. + /// + /// Note: This is similar to `readlinkat` in POSIX. + @since(version = 0.2.0) + readlink-at: func(path: string) -> result; + /// Remove a directory. + /// + /// Return `error-code::not-empty` if the directory is not empty. + /// + /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. + @since(version = 0.2.0) + remove-directory-at: func(path: string) -> result<_, error-code>; + /// Rename a filesystem object. + /// + /// Note: This is similar to `renameat` in POSIX. + @since(version = 0.2.0) + rename-at: func(old-path: string, new-descriptor: borrow, new-path: string) -> result<_, error-code>; + /// Create a symbolic link (also known as a "symlink"). + /// + /// If `old-path` starts with `/`, the function fails with + /// `error-code::not-permitted`. + /// + /// Note: This is similar to `symlinkat` in POSIX. + @since(version = 0.2.0) + symlink-at: func(old-path: string, new-path: string) -> result<_, error-code>; + /// Unlink a filesystem object that is not a directory. + /// + /// Return `error-code::is-directory` if the path refers to a directory. + /// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. + @since(version = 0.2.0) + unlink-file-at: func(path: string) -> result<_, error-code>; + /// Test whether two descriptors refer to the same filesystem object. + /// + /// In POSIX, this corresponds to testing whether the two descriptors have the + /// same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers. + /// wasi-filesystem does not expose device and inode numbers, so this function + /// may be used instead. + @since(version = 0.2.0) + is-same-object: func(other: borrow) -> bool; + /// Return a hash of the metadata associated with a filesystem object referred + /// to by a descriptor. + /// + /// This returns a hash of the last-modification timestamp and file size, and + /// may also include the inode number, device number, birth timestamp, and + /// other metadata fields that may change when the file is modified or + /// replaced. It may also include a secret value chosen by the + /// implementation and not otherwise exposed. + /// + /// Implementations are encouraged to provide the following properties: + /// + /// - If the file is not modified or replaced, the computed hash value should + /// usually not change. + /// - If the object is modified or replaced, the computed hash value should + /// usually change. + /// - The inputs to the hash should not be easily computable from the + /// computed hash. + /// + /// However, none of these is required. + @since(version = 0.2.0) + metadata-hash: func() -> result; + /// Return a hash of the metadata associated with a filesystem object referred + /// to by a directory descriptor and a relative path. + /// + /// This performs the same hash computation as `metadata-hash`. + @since(version = 0.2.0) + metadata-hash-at: func(path-flags: path-flags, path: string) -> result; + } + + /// A stream of directory entries. + @since(version = 0.2.0) + resource directory-entry-stream { + /// Read a single directory entry from a `directory-entry-stream`. + @since(version = 0.2.0) + read-directory-entry: func() -> result, error-code>; + } + + /// Attempts to extract a filesystem-related `error-code` from the stream + /// `error` provided. + /// + /// Stream operations which return `stream-error::last-operation-failed` + /// have a payload with more information about the operation that failed. + /// This payload can be passed through to this function to see if there's + /// filesystem-related information about the error to return. + /// + /// Note that this function is fallible because not all stream-related + /// errors are filesystem-related errors. + @since(version = 0.2.0) + filesystem-error-code: func(err: borrow) -> option; +} + +@since(version = 0.2.0) +interface preopens { + @since(version = 0.2.0) + use types.{descriptor}; + + /// Return the set of preopened directories, and their paths. + @since(version = 0.2.0) + get-directories: func() -> list>; +} + +@since(version = 0.2.0) +world imports { + @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 wasi:clocks/wall-clock@0.2.10; + @since(version = 0.2.0) + import types; + @since(version = 0.2.0) + import preopens; +} diff --git a/examples/components/native-messaging/deps/wasi-io-0.2.10/package.wit b/examples/components/native-messaging/deps/wasi-io-0.2.10/package.wit new file mode 100644 index 000000000..6dd338f90 --- /dev/null +++ b/examples/components/native-messaging/deps/wasi-io-0.2.10/package.wit @@ -0,0 +1,299 @@ +package wasi:io@0.2.10; + +@since(version = 0.2.0) +interface error { + /// A resource which represents some error information. + /// + /// The only method provided by this resource is `to-debug-string`, + /// which provides some human-readable information about the error. + /// + /// In the `wasi:io` package, this resource is returned through the + /// `wasi:io/streams.stream-error` type. + /// + /// To provide more specific error information, other interfaces may + /// offer functions to "downcast" this error into more specific types. For example, + /// errors returned from streams derived from filesystem types can be described using + /// the filesystem's own error-code type. This is done using the function + /// `wasi:filesystem/types.filesystem-error-code`, which takes a `borrow` + /// parameter and returns an `option`. + /// + /// The set of functions which can "downcast" an `error` into a more + /// concrete type is open. + @since(version = 0.2.0) + resource error { + /// Returns a string that is suitable to assist humans in debugging + /// this error. + /// + /// WARNING: The returned string should not be consumed mechanically! + /// It may change across platforms, hosts, or other implementation + /// details. Parsing this string is a major platform-compatibility + /// hazard. + @since(version = 0.2.0) + to-debug-string: func() -> string; + } +} + +/// A poll API intended to let users wait for I/O events on multiple handles +/// at once. +@since(version = 0.2.0) +interface poll { + /// `pollable` represents a single I/O event which may be ready, or not. + @since(version = 0.2.0) + resource pollable { + /// Return the readiness of a pollable. This function never blocks. + /// + /// Returns `true` when the pollable is ready, and `false` otherwise. + @since(version = 0.2.0) + ready: func() -> bool; + /// `block` returns immediately if the pollable is ready, and otherwise + /// blocks until ready. + /// + /// This function is equivalent to calling `poll.poll` on a list + /// containing only this pollable. + @since(version = 0.2.0) + block: func(); + } + + /// Poll for completion on a set of pollables. + /// + /// This function takes a list of pollables, which identify I/O sources of + /// interest, and waits until one or more of the events is ready for I/O. + /// + /// The result `list` contains one or more indices of handles in the + /// argument list that is ready for I/O. + /// + /// This function traps if either: + /// - the list is empty, or: + /// - the list contains more elements than can be indexed with a `u32` value. + /// + /// A timeout can be implemented by adding a pollable from the + /// wasi-clocks API to the list. + /// + /// This function does not return a `result`; polling in itself does not + /// do any I/O so it doesn't fail. If any of the I/O sources identified by + /// the pollables has an error, it is indicated by marking the source as + /// being ready for I/O. + @since(version = 0.2.0) + poll: func(in: list>) -> list; +} + +/// WASI I/O is an I/O abstraction API which is currently focused on providing +/// stream types. +/// +/// In the future, the component model is expected to add built-in stream types; +/// when it does, they are expected to subsume this API. +@since(version = 0.2.0) +interface streams { + @since(version = 0.2.0) + use error.{error}; + @since(version = 0.2.0) + use poll.{pollable}; + + /// An error for input-stream and output-stream operations. + @since(version = 0.2.0) + variant stream-error { + /// The last operation (a write or flush) failed before completion. + /// + /// More information is available in the `error` payload. + /// + /// After this, the stream will be closed. All future operations return + /// `stream-error::closed`. + last-operation-failed(error), + /// The stream is closed: no more input will be accepted by the + /// stream. A closed output-stream will return this error on all + /// future operations. + closed, + } + + /// An input bytestream. + /// + /// `input-stream`s are *non-blocking* to the extent practical on underlying + /// platforms. I/O operations always return promptly; if fewer bytes are + /// promptly available than requested, they return the number of bytes promptly + /// available, which could even be zero. To wait for data to be available, + /// use the `subscribe` function to obtain a `pollable` which can be polled + /// for using `wasi:io/poll`. + @since(version = 0.2.0) + resource input-stream { + /// Perform a non-blocking read from the stream. + /// + /// When the source of a `read` is binary data, the bytes from the source + /// are returned verbatim. When the source of a `read` is known to the + /// implementation to be text, bytes containing the UTF-8 encoding of the + /// text are returned. + /// + /// This function returns a list of bytes containing the read data, + /// when successful. The returned list will contain up to `len` bytes; + /// it may return fewer than requested, but not more. The list is + /// empty when no bytes are available for reading at this time. The + /// pollable given by `subscribe` will be ready when more bytes are + /// available. + /// + /// This function fails with a `stream-error` when the operation + /// encounters an error, giving `last-operation-failed`, or when the + /// stream is closed, giving `closed`. + /// + /// When the caller gives a `len` of 0, it represents a request to + /// read 0 bytes. If the stream is still open, this call should + /// succeed and return an empty list, or otherwise fail with `closed`. + /// + /// The `len` parameter is a `u64`, which could represent a list of u8 which + /// is not possible to allocate in wasm32, or not desirable to allocate as + /// as a return value by the callee. The callee may return a list of bytes + /// less than `len` in size while more bytes are available for reading. + @since(version = 0.2.0) + read: func(len: u64) -> result, stream-error>; + /// Read bytes from a stream, after blocking until at least one byte can + /// be read. Except for blocking, behavior is identical to `read`. + @since(version = 0.2.0) + blocking-read: func(len: u64) -> result, stream-error>; + /// Skip bytes from a stream. Returns number of bytes skipped. + /// + /// Behaves identical to `read`, except instead of returning a list + /// of bytes, returns the number of bytes consumed from the stream. + @since(version = 0.2.0) + skip: func(len: u64) -> result; + /// Skip bytes from a stream, after blocking until at least one byte + /// can be skipped. Except for blocking behavior, identical to `skip`. + @since(version = 0.2.0) + blocking-skip: func(len: u64) -> result; + /// Create a `pollable` which will resolve once either the specified stream + /// has bytes available to read or the other end of the stream has been + /// closed. + /// The created `pollable` is a child resource of the `input-stream`. + /// Implementations may trap if the `input-stream` is dropped before + /// all derived `pollable`s created with this function are dropped. + @since(version = 0.2.0) + subscribe: func() -> pollable; + } + + /// An output bytestream. + /// + /// `output-stream`s are *non-blocking* to the extent practical on + /// underlying platforms. Except where specified otherwise, I/O operations also + /// always return promptly, after the number of bytes that can be written + /// promptly, which could even be zero. To wait for the stream to be ready to + /// accept data, the `subscribe` function to obtain a `pollable` which can be + /// polled for using `wasi:io/poll`. + /// + /// Dropping an `output-stream` while there's still an active write in + /// progress may result in the data being lost. Before dropping the stream, + /// be sure to fully flush your writes. + @since(version = 0.2.0) + resource output-stream { + /// Check readiness for writing. This function never blocks. + /// + /// Returns the number of bytes permitted for the next call to `write`, + /// or an error. Calling `write` with more bytes than this function has + /// permitted will trap. + /// + /// When this function returns 0 bytes, the `subscribe` pollable will + /// become ready when this function will report at least 1 byte, or an + /// error. + @since(version = 0.2.0) + check-write: func() -> result; + /// Perform a write. This function never blocks. + /// + /// When the destination of a `write` is binary data, the bytes from + /// `contents` are written verbatim. When the destination of a `write` is + /// known to the implementation to be text, the bytes of `contents` are + /// transcoded from UTF-8 into the encoding of the destination and then + /// written. + /// + /// Precondition: check-write gave permit of Ok(n) and contents has a + /// length of less than or equal to n. Otherwise, this function will trap. + /// + /// returns Err(closed) without writing if the stream has closed since + /// the last call to check-write provided a permit. + @since(version = 0.2.0) + write: func(contents: list) -> result<_, stream-error>; + /// Perform a write of up to 4096 bytes, and then flush the stream. Block + /// until all of these operations are complete, or an error occurs. + /// + /// Returns success when all of the contents written are successfully + /// flushed to output. If an error occurs at any point before all + /// contents are successfully flushed, that error is returned as soon as + /// possible. If writing and flushing the complete contents causes the + /// stream to become closed, this call should return success, and + /// subsequent calls to check-write or other interfaces should return + /// stream-error::closed. + @since(version = 0.2.0) + blocking-write-and-flush: func(contents: list) -> result<_, stream-error>; + /// Request to flush buffered output. This function never blocks. + /// + /// This tells the output-stream that the caller intends any buffered + /// output to be flushed. the output which is expected to be flushed + /// is all that has been passed to `write` prior to this call. + /// + /// Upon calling this function, the `output-stream` will not accept any + /// writes (`check-write` will return `ok(0)`) until the flush has + /// completed. The `subscribe` pollable will become ready when the + /// flush has completed and the stream can accept more writes. + @since(version = 0.2.0) + flush: func() -> result<_, stream-error>; + /// Request to flush buffered output, and block until flush completes + /// and stream is ready for writing again. + @since(version = 0.2.0) + blocking-flush: func() -> result<_, stream-error>; + /// Create a `pollable` which will resolve once the output-stream + /// is ready for more writing, or an error has occurred. When this + /// pollable is ready, `check-write` will return `ok(n)` with n>0, or an + /// error. + /// + /// If the stream is closed, this pollable is always ready immediately. + /// + /// The created `pollable` is a child resource of the `output-stream`. + /// Implementations may trap if the `output-stream` is dropped before + /// all derived `pollable`s created with this function are dropped. + @since(version = 0.2.0) + subscribe: func() -> pollable; + /// Write zeroes to a stream. + /// + /// This should be used precisely like `write` with the exact same + /// preconditions (must use check-write first), but instead of + /// passing a list of bytes, you simply pass the number of zero-bytes + /// that should be written. + @since(version = 0.2.0) + write-zeroes: func(len: u64) -> result<_, stream-error>; + /// Perform a write of up to 4096 zeroes, and then flush the stream. + /// Block until all of these operations are complete, or an error + /// occurs. + /// + /// Functionality is equivelant to `blocking-write-and-flush` with + /// contents given as a list of len containing only zeroes. + @since(version = 0.2.0) + blocking-write-zeroes-and-flush: func(len: u64) -> result<_, stream-error>; + /// Read from one stream and write to another. + /// + /// The behavior of splice is equivalent to: + /// 1. calling `check-write` on the `output-stream` + /// 2. calling `read` on the `input-stream` with the smaller of the + /// `check-write` permitted length and the `len` provided to `splice` + /// 3. calling `write` on the `output-stream` with that read data. + /// + /// Any error reported by the call to `check-write`, `read`, or + /// `write` ends the splice and reports that error. + /// + /// This function returns the number of bytes transferred; it may be less + /// than `len`. + @since(version = 0.2.0) + splice: func(src: borrow, len: u64) -> result; + /// Read from one stream and write to another, with blocking. + /// + /// This is similar to `splice`, except that it blocks until the + /// `output-stream` is ready for writing, and the `input-stream` + /// is ready for reading, before performing the `splice`. + @since(version = 0.2.0) + blocking-splice: func(src: borrow, len: u64) -> result; + } +} + +@since(version = 0.2.0) +world imports { + @since(version = 0.2.0) + import error; + @since(version = 0.2.0) + import poll; + @since(version = 0.2.0) + import streams; +} diff --git a/examples/components/native-messaging/deps/wasi-random-0.2.10/package.wit b/examples/components/native-messaging/deps/wasi-random-0.2.10/package.wit new file mode 100644 index 000000000..ba5cbfc28 --- /dev/null +++ b/examples/components/native-messaging/deps/wasi-random-0.2.10/package.wit @@ -0,0 +1,92 @@ +package wasi:random@0.2.10; + +/// The insecure-seed interface for seeding hash-map DoS resistance. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +@since(version = 0.2.0) +interface insecure-seed { + /// Return a 128-bit value that may contain a pseudo-random value. + /// + /// The returned value is not required to be computed from a CSPRNG, and may + /// even be entirely deterministic. Host implementations are encouraged to + /// provide pseudo-random values to any program exposed to + /// attacker-controlled content, to enable DoS protection built into many + /// languages' hash-map implementations. + /// + /// This function is intended to only be called once, by a source language + /// to initialize Denial Of Service (DoS) protection in its hash-map + /// implementation. + /// + /// # Expected future evolution + /// + /// This will likely be changed to a value import, to prevent it from being + /// called multiple times and potentially used for purposes other than DoS + /// protection. + @since(version = 0.2.0) + insecure-seed: func() -> tuple; +} + +/// The insecure interface for insecure pseudo-random numbers. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +@since(version = 0.2.0) +interface insecure { + /// Return `len` insecure pseudo-random bytes. + /// + /// This function is not cryptographically secure. Do not use it for + /// anything related to security. + /// + /// There are no requirements on the values of the returned bytes, however + /// implementations are encouraged to return evenly distributed values with + /// a long period. + @since(version = 0.2.0) + get-insecure-random-bytes: func(len: u64) -> list; + + /// Return an insecure pseudo-random `u64` value. + /// + /// This function returns the same type of pseudo-random data as + /// `get-insecure-random-bytes`, represented as a `u64`. + @since(version = 0.2.0) + get-insecure-random-u64: func() -> u64; +} + +/// WASI Random is a random data API. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +@since(version = 0.2.0) +interface random { + /// Return `len` cryptographically-secure random or pseudo-random bytes. + /// + /// This function must produce data at least as cryptographically secure and + /// fast as an adequately seeded cryptographically-secure pseudo-random + /// number generator (CSPRNG). It must not block, from the perspective of + /// the calling program, under any circumstances, including on the first + /// request and on requests for numbers of bytes. The returned data must + /// always be unpredictable. + /// + /// This function must always return fresh data. Deterministic environments + /// must omit this function, rather than implementing it with deterministic + /// data. + @since(version = 0.2.0) + get-random-bytes: func(len: u64) -> list; + + /// Return a cryptographically-secure random or pseudo-random `u64` value. + /// + /// This function returns the same type of data as `get-random-bytes`, + /// represented as a `u64`. + @since(version = 0.2.0) + get-random-u64: func() -> u64; +} + +@since(version = 0.2.0) +world imports { + @since(version = 0.2.0) + import random; + @since(version = 0.2.0) + import insecure; + @since(version = 0.2.0) + import insecure-seed; +} diff --git a/examples/components/native-messaging/deps/wasi-sockets-0.2.10/package.wit b/examples/components/native-messaging/deps/wasi-sockets-0.2.10/package.wit new file mode 100644 index 000000000..9d92e9c9d --- /dev/null +++ b/examples/components/native-messaging/deps/wasi-sockets-0.2.10/package.wit @@ -0,0 +1,183 @@ +package wasi:sockets@0.2.10; + +interface network { + use wasi:io/error@0.2.10.{error}; + + resource network; + + enum error-code { + unknown, + access-denied, + not-supported, + invalid-argument, + out-of-memory, + timeout, + concurrency-conflict, + not-in-progress, + would-block, + invalid-state, + new-socket-limit, + address-not-bindable, + address-in-use, + remote-unreachable, + connection-refused, + connection-reset, + connection-aborted, + datagram-too-large, + name-unresolvable, + temporary-resolver-failure, + permanent-resolver-failure, + } + + enum ip-address-family { + ipv4, + ipv6, + } + + type ipv4-address = tuple; + + type ipv6-address = tuple; + + variant ip-address { + ipv4(ipv4-address), + ipv6(ipv6-address), + } + + record ipv4-socket-address { + port: u16, + address: ipv4-address, + } + + record ipv6-socket-address { + port: u16, + flow-info: u32, + address: ipv6-address, + scope-id: u32, + } + + variant ip-socket-address { + ipv4(ipv4-socket-address), + ipv6(ipv6-socket-address), + } + + network-error-code: func(err: borrow) -> option; +} + +interface instance-network { + use network.{network}; + + instance-network: func() -> network; +} + +interface udp { + use wasi:io/poll@0.2.10.{pollable}; + use network.{network, error-code, ip-socket-address, ip-address-family}; + + record incoming-datagram { + data: list, + remote-address: ip-socket-address, + } + + record outgoing-datagram { + data: list, + remote-address: option, + } + + resource udp-socket { + start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; + finish-bind: func() -> result<_, error-code>; + %stream: func(remote-address: option) -> result, error-code>; + local-address: func() -> result; + remote-address: func() -> result; + address-family: func() -> ip-address-family; + unicast-hop-limit: func() -> result; + set-unicast-hop-limit: func(value: u8) -> result<_, error-code>; + receive-buffer-size: func() -> result; + set-receive-buffer-size: func(value: u64) -> result<_, error-code>; + send-buffer-size: func() -> result; + set-send-buffer-size: func(value: u64) -> result<_, error-code>; + subscribe: func() -> pollable; + } + + resource incoming-datagram-stream { + receive: func(max-results: u64) -> result, error-code>; + subscribe: func() -> pollable; + } + + resource outgoing-datagram-stream { + check-send: func() -> result; + send: func(datagrams: list) -> result; + subscribe: func() -> pollable; + } +} + +interface udp-create-socket { + use network.{network, error-code, ip-address-family}; + use udp.{udp-socket}; + + create-udp-socket: func(address-family: ip-address-family) -> result; +} + +interface tcp { + use wasi:io/streams@0.2.10.{input-stream, output-stream}; + use wasi:io/poll@0.2.10.{pollable}; + use wasi:clocks/monotonic-clock@0.2.10.{duration}; + use network.{network, error-code, ip-socket-address, ip-address-family}; + + enum shutdown-type { + receive, + send, + both, + } + + resource tcp-socket { + start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; + finish-bind: func() -> result<_, error-code>; + start-connect: func(network: borrow, remote-address: ip-socket-address) -> result<_, error-code>; + finish-connect: func() -> result, error-code>; + start-listen: func() -> result<_, error-code>; + finish-listen: func() -> result<_, error-code>; + accept: func() -> result, error-code>; + local-address: func() -> result; + remote-address: func() -> result; + is-listening: func() -> bool; + address-family: func() -> ip-address-family; + set-listen-backlog-size: func(value: u64) -> result<_, error-code>; + keep-alive-enabled: func() -> result; + set-keep-alive-enabled: func(value: bool) -> result<_, error-code>; + keep-alive-idle-time: func() -> result; + set-keep-alive-idle-time: func(value: duration) -> result<_, error-code>; + keep-alive-interval: func() -> result; + set-keep-alive-interval: func(value: duration) -> result<_, error-code>; + keep-alive-count: func() -> result; + set-keep-alive-count: func(value: u32) -> result<_, error-code>; + hop-limit: func() -> result; + set-hop-limit: func(value: u8) -> result<_, error-code>; + receive-buffer-size: func() -> result; + set-receive-buffer-size: func(value: u64) -> result<_, error-code>; + send-buffer-size: func() -> result; + set-send-buffer-size: func(value: u64) -> result<_, error-code>; + subscribe: func() -> pollable; + shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code>; + } +} + +interface tcp-create-socket { + use network.{network, error-code, ip-address-family}; + use tcp.{tcp-socket}; + + create-tcp-socket: func(address-family: ip-address-family) -> result; +} + +interface ip-name-lookup { + use wasi:io/poll@0.2.10.{pollable}; + use network.{network, error-code, ip-address}; + + resource resolve-address-stream { + resolve-next-address: func() -> result, error-code>; + subscribe: func() -> pollable; + } + + resolve-addresses: func(network: borrow, name: string) -> result; +} + diff --git a/examples/components/native-messaging/manifest.json b/examples/components/native-messaging/manifest.json new file mode 100644 index 000000000..e0ff80545 --- /dev/null +++ b/examples/components/native-messaging/manifest.json @@ -0,0 +1,14 @@ +{ + "name": "nm-componentize-js", + "short_name": "nm_componentize_js", + "version": "1.0", + "manifest_version": 3, + "permissions": ["nativeMessaging"], + "background": { + "service_worker": "background.js", + "type": "module" + }, + "action": {}, + "author": "guest271314", + "homepage_url": "https://github.com/guest271314/native-messaging-componentize-js" +} diff --git a/examples/components/native-messaging/nm_componentize_js.js b/examples/components/native-messaging/nm_componentize_js.js new file mode 100644 index 000000000..bc8a79c1e --- /dev/null +++ b/examples/components/native-messaging/nm_componentize_js.js @@ -0,0 +1,89 @@ +//! componentize-js Native Messaging host +//! guest271314, andreiltd (https://github.com/andreiltd), 6-13-2026 +//! Based on nm_componentize_qjs.js +//! https://github.com/andreiltd/componentize-qjs/issues/1 + +import { getStdin } from "wasi:cli/stdin@0.2.10"; +import { getStdout } from "wasi:cli/stdout@0.2.10"; + +const WRITE_CHUNK = 4096; +const FRAME = 1024 * 1024; +const COMMA = 0x2c, + OPEN = 0x5b, + CLOSE = 0x5d; + +function readExact(input, n) { + const out = new Uint8Array(n); + let off = 0; + while (off < n) { + try { + const chunk = input.blockingRead(BigInt(n - off)); + if (!chunk || chunk.length === 0) { + return null; + } + out.set(chunk, off); + off += chunk.length; + } catch (error) { + return null; + } + } + return out; +} + +function readMessage(input) { + const header = readExact(input, 4); + if (header === null) return null; + + const view = new DataView(header.buffer, header.byteOffset, header.byteLength); + const len = view.getUint32(0, true); + + return len === 0 ? new Uint8Array(0) : readExact(input, len); +} + +function writeAll(output, data) { + for (let off = 0; off < data.length; off += WRITE_CHUNK) { + output.blockingWriteAndFlush(data.subarray(off, off + WRITE_CHUNK)); + } +} + +function writeFrame(output, body) { + const frame = new Uint8Array(4 + body.length); + const view = new DataView(frame.buffer, frame.byteOffset, frame.byteLength); + view.setUint32(0, body.length, true); + frame.set(body, 4); + writeAll(output, frame); +} + +function sendMessage(output, msg) { + if (msg.length <= FRAME) { + writeFrame(output, msg); + return; + } + // Handle 64 MiB JSON Array message input, 1 MiB JSON Array output + for (let i = 1, end = msg.length - 1; i < end;) { + let j = i + FRAME - 16; + if (j >= end) j = end; + else { + const c = msg.indexOf(COMMA, j); + j = c === -1 ? end : c; + } + const body = new Uint8Array(2 + (j - i)); + body[0] = OPEN; + body.set(msg.subarray(i, j), 1); + body[body.length - 1] = CLOSE; + writeFrame(output, body); + i = j + 1; + } +} + +export const run = { + run() { + const input = getStdin(); + const output = getStdout(); + + for (let msg; (msg = readMessage(input)) !== null;) { + sendMessage(output, msg); + } + return; + }, +}; diff --git a/examples/components/native-messaging/nm_componentize_js.json b/examples/components/native-messaging/nm_componentize_js.json new file mode 100644 index 000000000..d75317d81 --- /dev/null +++ b/examples/components/native-messaging/nm_componentize_js.json @@ -0,0 +1,9 @@ +{ + "name": "nm_componentize_js", + "description": "ComponentizeJS Native Messaging host", + "path": "/ABSOLUTE/PATH/TO/jco/examples/nm_componentize_js.sh", + "type": "stdio", + "allowed_origins": [ + "chrome-extension:///" + ] +} \ No newline at end of file diff --git a/examples/components/native-messaging/package.wit b/examples/components/native-messaging/package.wit new file mode 100644 index 000000000..6206bd633 --- /dev/null +++ b/examples/components/native-messaging/package.wit @@ -0,0 +1,22 @@ +package native-messaging:native-messaging-componentize-js; + +world native-messaging-componentize-js { + import wasi:io/error@0.2.10; + import wasi:io/poll@0.2.10; + import wasi:io/streams@0.2.10; + import wasi:cli/stdin@0.2.10; + import wasi:cli/stdout@0.2.10; + import wasi:cli/stderr@0.2.10; + import wasi:cli/terminal-input@0.2.10; + import wasi:cli/terminal-output@0.2.10; + import wasi:cli/terminal-stdin@0.2.10; + import wasi:cli/terminal-stdout@0.2.10; + import wasi:cli/terminal-stderr@0.2.10; + import wasi:clocks/monotonic-clock@0.2.10; + import wasi:clocks/wall-clock@0.2.10; + import wasi:filesystem/types@0.2.10; + import wasi:filesystem/preopens@0.2.10; + import wasi:random/random@0.2.10; + + export wasi:cli/run@0.2.10; +} \ No newline at end of file diff --git a/examples/components/native-messaging/wkg.lock b/examples/components/native-messaging/wkg.lock new file mode 100644 index 000000000..e7bc87d3c --- /dev/null +++ b/examples/components/native-messaging/wkg.lock @@ -0,0 +1,48 @@ +# This file is automatically generated. +# It is not intended for manual editing. +version = 1 + +[[packages]] +name = "wasi:cli" +registry = "wasi.dev" + +[[packages.versions]] +requirement = "=0.2.10" +version = "0.2.10" +digest = "sha256:0ce8714c597191cf9b7b37c4bd2033a33b086e5af486ac2f1a880308040cad1a" + +[[packages]] +name = "wasi:clocks" +registry = "wasi.dev" + +[[packages.versions]] +requirement = "=0.2.10" +version = "0.2.10" +digest = "sha256:b1bf6597fab005d7562f955b0ce6c5ae430450bd4a0b8d4f1b34ff2d3612b3f9" + +[[packages]] +name = "wasi:filesystem" +registry = "wasi.dev" + +[[packages.versions]] +requirement = "=0.2.10" +version = "0.2.10" +digest = "sha256:60e6779b212bbdf9946fdbc71ae4adf3c95132b455344aacdc39ed7f8889c51a" + +[[packages]] +name = "wasi:io" +registry = "wasi.dev" + +[[packages.versions]] +requirement = "=0.2.10" +version = "0.2.10" +digest = "sha256:c3fd3ac8f133431c6b8ee903303d43c86f72835045b55490493d0c17dd8b6dff" + +[[packages]] +name = "wasi:random" +registry = "wasi.dev" + +[[packages.versions]] +requirement = "=0.2.10" +version = "0.2.10" +digest = "sha256:cdba834d815cba4de886afae8e41d46307de14959636828df91a77e34100df5a" From eedb4a3a04bad96668fcfd49314ee382091a2280 Mon Sep 17 00:00:00 2001 From: guest271314 Date: Sun, 14 Jun 2026 15:40:59 +0000 Subject: [PATCH 6/6] Update README.md Signed-off-by: guest271314 --- examples/components/native-messaging/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/components/native-messaging/README.md b/examples/components/native-messaging/README.md index 09c4b262e..7e1be50ca 100644 --- a/examples/components/native-messaging/README.md +++ b/examples/components/native-messaging/README.md @@ -67,12 +67,12 @@ port.postMessage(data); data.length = 0; ``` -Send arbitrary command to the host to execute and send results back, e.g., a REPL. Using `sendNativeMessage` sends one message at a time, the callback receives the message from the host, and then the process exits. +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: `node -e 'console.log(require("fs").readFileSync("package.wit"))` }, + { command: "deno eval 'Deno.stdout.write(new TextEncoder().encode(JSON.stringify(Deno.version)))'" }, (message) => { if (chrome.runtime.lastError) { console.error(chrome.runtime.lastError);