WebAssembly / wasi-messaging

Messaging proposal for WASI
18 stars 9 forks source link

Investigate using Native Messaging protocol for network (cloud) messaging #13

Open guest271314 opened 1 year ago

guest271314 commented 1 year ago

This is the Native messaging protocol

Chrome starts each native messaging host in a separate process and communicates with it using standard input (stdin) and standard output (stdout). The same format is used to send messages in both directions; each message is serialized using JSON, UTF-8 encoded and is preceded with 32-bit message length in native byte order. The maximum size of a single message from the native messaging host is 1 MB, mainly to protect Chrome from misbehaving native applications. The maximum size of the message sent to the native messaging host is 4 GB.

This is what a manifest looks like

{
  "name": "nm_c_wasm",
  "description": "WebAssembly Native Messaging host",
  "path": "",
  "type": "stdio",
  "allowed_origins": []
}

We connect to the host using either connectNative() for extended, potentially persistent connections

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

or sending (potentially receiving) a single message

const message = await chrome.runtime.sendNativeMessage('nm_c_wasm', {
  "message": "data"
});

There is also batively_connectable which is not widely documented or implemented https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/extensions/api/messaging/native_messaging_launch_from_native.cc where the host or extension automatically makes a connection.

The message format is JSON, which is a format capable of sending and receiving essentially any type of serialized data in the format [0, ..., 255] etc.

Each message is preceded by the length of the JSON message, in each direction.

In the context of WASI messaging this is a very simple and extensible messaging protocol. We can connect to any host using the name of the host, e.g.,

const {readable, writable} = new TransformStream();
const abortable = new AbortController();
const {signal} = abortable;
readable.pipeTo(new WritableStream({
  await write(value) {
     // Parse raw PCM
     // const audioData = ...;
     await audioWriter.write(new Uint8Array(audioData));
  }, 
  close() {
    console.log('Stream closed');
  },
  abort(reason) {
    console.log(reason);
  }
}, {signal})).catch(console.dir);
const writer = await writable.getWriter();
const audioStream = new MediaStreamTrackGenerator({kind: 'audio'});
const {writable: audioWritable} = audioStream;
const audioWriter = await audioWritable.getWriter();
const radio = chrome.runtime.connectNative('internet_radio_station_0');
radio.onMessage.addListener(async(message) => {
   await writer.write(new Uint8Array(message));
});
radio.onDisconnect.addListener((port) => {
  if (chrome.runtime.lastError) {
    console.warn(chrome.runtime.lastError);
  }
});
// Abort processing message data
// abortable.abort();
// Disconnect from host
// radio.disconnect();

Working example of above pattern https://github.com/guest271314/captureSystemAudio/tree/master/native_messaging/capture_system_audio.

For the use case of getting lists of possible radio stations, or other fields of interest a single message to the host from the client and from the host to the client (or whtever terminology we want to use here, e.g., "peers") using the name of the service or peer who provides such a list of possible message channels or peers

const message = await chrome.runtime.sendNativeMessage('list_peers', {
  "message": ["radio", "street_art", "engineering", "history"]
});
// {"radio": ["blues_station_0",...], "art": ["Soon", "Futura", ...], ...}
const art = chrome.connectNative('internet_radio_station_0');
// ...

We can use whatever underlying transport we want. HTTP/2, HTTP/3, QUIC, WebRTC, etc.

Very simple to use. Nothing unnecessarily complicated about the protocol nor configuration. Achieves real-time audio streaming without gaps or glitches using JSON (or a JavaScript object).

guest271314 commented 1 year ago

This is a Native Messaging host written in C https://github.com/guest271314/native-messaging-webassembly/blob/main/nm_c.c (I also compiled a C++ version to WebAssembly), compiled to WASI using WASI-SDK that just echoes 1 MB of data.

I have written Native Messaging hosts using Python, JavaScript (QuickJS; txiki.js; Bun; Node.js; Deno; Bash version is W.I.P.) https://github.com/guest271314/NativeMessagingHosts, in part to gain empirical information about the resources and dependencies, if any, each programming language or runtime uses to achieve the same result, and in another part, though the totoality of my reasoning, to not become attached to one programming language - though I write most of my code in JavaScript.

guest271314 commented 3 weeks ago

@danbugs Some progress. Native Messaging protocol standalone test nm_standalone_test.js.

You can compile to a standalone executable with

deno compile -A https://raw.githubusercontent.com/guest271314/NativeMessagingHosts/refs/heads/main/nm_standalone_test.js

Then test the protocol on the commad-line outside of the browser using multiple programming languages, including Rust https://github.com/guest271314/NativeMessagingHosts/blob/main/nm_rust.rs, and WebAssembly. Some experiments with Bytecode Aliiance's Javy https://github.com/guest271314/native-messaging-webassembly/edit/main/README.md

javy compile nm_javy.js -o nm_javy.wasm
wasmtime compile --optimize opt-level=s nm_javy.wasm
nm_standalone_test ~/localscripts/native-messaging-webassembly/nm_wasm.sh

which executes the host as a child process, communicating using the protocol

#!/usr/bin/env -S /home/user/native-messaging-webassembly/wasmtime -C cache=n --allow-precompiled /home/user/native-messaging-webassembly/nm_javy.cwasm
{ messageLength: 1048576 }
[
  null, null, null, null, null, null, null, null, null, null,
  null, null, null, null, null, null, null, null, null, null,
  null, null, null, null, null, null, null, null, null, null,
  null, null, null, null, null, null, null, null, null, null,
  null, null, null, null, null, null, null, null, null, null,
  null, null, null, null, null, null, null, null, null, null,
  null, null, null, null, null, null, null, null, null, null,
  null, null, null, null, null, null, null, null, null, null,
  null, null, null, null, null, null, null, null, null, null,
  null, null, null, null, null, null, null, null, null, null,
  ... 209615 more items
]
{ messageLength: 6 }
test
{ messageLength: 2 }

{ messageLength: 1 }
1
{ messageLength: 8 }
{ "0": 97 }