Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
guest271314 opened this issue Jun 20, 2023 · 3 comments

Comments

@guest271314
Copy link
Contributor

No description provided.

@guest271314
Copy link
Contributor Author

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
Copy link
Contributor Author

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
Copy link
Contributor Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants
@guest271314 and others