Native Messaging host using Mozilla SpiderMonkey JavaScript/WebAssembly engine.
Documentations
- Chrome Developers
- MDN Web Docs
- Microsoft Edge Developer documentation
- Messaging between the app and JavaScript in a Safari web extension
Native messaging protocol (Chrome Developers)
In pertinent part
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.
Some development background Why is this SpiderMonkey Native Messsaging host not sending stdout to the client?.
ECMA-262 does not specify reading stdin or writing to stdout for JavaScript. At the JavaScript engine level what that means is that a JavaScript engine can, or cannot have I/O implemented and still be perfectly ECMA-262 test conformant. QuickJS includes a way to read stdin and write to stdout. Mozilla SpiderMonkey is little trickier.
- I have to send an additional
"\r\n\r\n"message from the client (browser) to the host (native application) for the host to get the message. So where in other languages and engines I can just sendport.postMessage(new Array(209715)), with SpiderMonkey I have to also send and additional message at the end of every message I sendport.postMessage("\r\n\r\n"). - Don't ask me to explain those regular expressions. I read the string that gets to the host and kept creating regular expressions, line-by-line, until I got what I was expecting.
- Using
/proc/self/fd/1is not ideal, as Apple OS's don't have that, and neither do Microsoft OS's; works on Debian and Ubuntu Linux operating systems.
Note, an Array with 209715 length in JavaScript winds up being 1 MB serialized to JSON, accounting for the null value set at indexes 0-209714.
Here's the working code
#!/usr/bin/env -S JS_STDERR=err.txt /home/user/.esvu/engines/jsshell/js
// SpiderMonkey Shell Native Messaging host (W.I.P.)
// guest271314 7-7-2023, 6-16-2024
function encodeMessage(str) {
return new Uint8Array([...str].map((s) => s.codePointAt()));
}
// Call readline() N times to catch `\r\n\r\n"` from 2d port.postMessage()
let done = false;
let reads = 0;
function getMessage() {
let stdin;
while (true) {
stdin = readline();
if (stdin !== null) {
break;
}
}
let data = `${stdin}`.replace(/[\r\n]+|\\x([0-9A-Fa-f]{2,4})/gu, "")
.replace(/[^A-Za-z0-9\s\[,\]\{\}:_"]+/igu, "")
.replace(/^"rnrn/gu, "")
.replace(/^[#\r\n\}_]+(?=\[)/gu, "")
.replace(/^"(?=["\{]+)|^"(?!"$)/gu, "")
.replace(/^\[(?=\[(?!.*\]{2}$))/gu, "")
.replace(/^\{(?!\}|.+\}$)/gu, "")
.replace(/^[0-9A-Z]+(?=[\[\{"])/igu, "")
.replace(/^[\]\}](?=\[)/i, "")
.trimStart().trim();
return encodeMessage(data);
}
function sendMessage(message) {
os.file.writeTypedArrayToFile(
"/proc/self/fd/1",
new Uint32Array([message.length]),
);
os.file.writeTypedArrayToFile("/proc/self/fd/1", message);
}
function main() {
while (true) {
const message = getMessage();
sendMessage(message);
}
}
try {
main();
} catch (e) {
os.file.writeTypedArrayToFile(
"caught.txt",
encodeMessage(JSON.stringify(e.message)),
);
quit();
}