Skip to content

Instantly share code, notes, and snippets.

@guest271314
Last active September 11, 2024 02:19
Show Gist options
  • Save guest271314/b85eff9f1bc6f3e1f599affd31545efa to your computer and use it in GitHub Desktop.
Save guest271314/b85eff9f1bc6f3e1f599affd31545efa to your computer and use it in GitHub Desktop.
Compile Node.js Native Messaging host to Single executable application
// const { createRequire } = require("node:module");
// require = createRequire(__filename);
const spawn = require("node:child_process").spawn;
const Duplex = require("node:stream").Duplex;
const runtime = navigator.userAgent;
const buffer = new ArrayBuffer(0, {
maxByteLength: 1024 ** 2,
});
const view = new DataView(buffer);
const decoder = new TextDecoder();
const encoder = new TextEncoder();
// https://nodejs.org/api/stream.html#consuming-readable-streams-with-async-iterators
const readable = process.stdin;
const writable = new WritableStream({
write(value) {
process.stdout.write(value);
},
});
function encodeMessage(message) {
return encoder.encode(JSON.stringify(message));
}
async function* getMessage() {
let messageLength = 0;
let readOffset = 0;
for await (let message of readable) {
if (buffer.byteLength === 0 && messageLength === 0) {
buffer.resize(4);
for (let i = 0; i < 4; i++) {
view.setUint8(i, message[i]);
}
messageLength = view.getUint32(0, true);
message = message.subarray(4);
buffer.resize(0);
}
buffer.resize(buffer.byteLength + message.length);
for (let i = 0; i < message.length; i++, readOffset++) {
view.setUint8(readOffset, message[i]);
}
if (buffer.byteLength === messageLength) {
yield new Uint8Array(buffer);
messageLength = 0;
readOffset = 0;
buffer.resize(0);
}
}
}
async function sendMessage(message) {
await new Blob([
new Uint8Array(new Uint32Array([message.length]).buffer),
message,
])
.stream()
.pipeTo(writable, {
preventClose: true,
});
}
async function main() {
try {
for await (const message of getMessage()) {
/*
const script = JSON.parse(decoder.decode(message)).slice(1, -1);
const { stdout, stderr } = spawn(process.argv.at(0), ["-e", script]);
const { readable: r, writable: w } = Duplex.toWeb(stdout);
const data = encodeMessage(await new Response(r).text());
*/
await sendMessage(message);
}
} catch (e) {
process.exit();
}
}
main();
/*
export {
args,
encodeMessage,
exit,
getMessage,
readable,
sendMessage,
writable,
};
*/
@guest271314
Copy link
Author

https://nodejs.org/api/single-executable-applications.html#single-executable-application-creation-process

$ cp /home/user/bin/node node_eval
$ echo '{ "main": "nm_nodejs_eval.js", "output": "sea-prep.blob" }' > sea-config.json
$ node --no-warnings --experimental-sea-config sea-config.json 
$ bun install postject
$ ./node_modules/postject/dist/cli.js node_eval  NODE_SEA_BLOB sea-prep.blob --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 --overwrite

nm_nodejs_eval.json

{
  "name": "nm_nodejs_eval",
  "description": "Node.js eval Native Messaging Host",
  "path": "/home/user/Downloads/native-messaging-nodejs/node_eval",
  "type": "stdio",
  "allowed_origins": [
    "chrome-extension://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/"
  ]
}

@guest271314
Copy link
Author

Alternatively, to compile and use postject as an executable with deno

$ deno compile -A npm:postject
$ postject node_eval  NODE_SEA_BLOB sea-prep.blob --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 --overwrite

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment