Files
holesail-browser/native-host/messenger.js
T
Raven Scott d58a0b6e2d
CI / Build & Test (push) Has been cancelled
first commit
2026-02-27 18:13:59 -05:00

92 lines
2.4 KiB
JavaScript

/**
* Native Messaging protocol: Chrome/Firefox spec.
* - 4-byte little-endian unsigned 32-bit length prefix
* - UTF-8 JSON payload
* - Max 1 MB message from native host to browser
*/
const MAX_MESSAGE_LENGTH = 1024 * 1024; // 1 MB
/**
* Create a messenger that reads length-prefixed JSON from input and writes to output.
* @param {object} options
* @param {import('stream').Readable} options.input - e.g. process.stdin
* @param {import('stream').Writable} options.output - e.g. process.stdout
* @param {function(object): void} options.onMessage - called with parsed JSON message
* @param {function(Error): void} [options.onError] - called on parse/read errors
*/
function createMessenger({ input, output, onMessage, onError }) {
let buffer = Buffer.alloc(0);
let needed = 4; // first read: 4-byte length
let readingLength = true;
function fail(err) {
if (onError) onError(err);
else console.error(err);
}
function processChunk(chunk) {
if (!chunk || chunk.length === 0) return;
buffer = Buffer.concat([buffer, chunk]);
while (buffer.length >= needed) {
const slice = buffer.subarray(0, needed);
buffer = buffer.subarray(needed);
if (readingLength) {
readingLength = false;
needed = slice.readUInt32LE(0);
if (needed > MAX_MESSAGE_LENGTH) {
fail(new Error(`Message length ${needed} exceeds max ${MAX_MESSAGE_LENGTH}`));
return;
}
if (needed === 0) {
readingLength = true;
needed = 4;
}
continue;
}
try {
const msg = JSON.parse(slice.toString('utf8'));
onMessage(msg);
} catch (e) {
fail(e);
}
readingLength = true;
needed = 4;
}
}
input.on('data', processChunk);
input.on('error', fail);
input.resume?.();
return {
/**
* Send a message to the browser (length prefix + JSON).
* @param {object} msg - JSON-serializable object
*/
send(msg) {
try {
const json = JSON.stringify(msg);
const buf = Buffer.from(json, 'utf8');
const len = Buffer.allocUnsafe(4);
len.writeUInt32LE(buf.length, 0);
output.write(len);
output.write(buf);
} catch (e) {
fail(e);
}
},
destroy() {
input.removeAllListeners('data');
input.removeAllListeners('error');
},
};
}
module.exports = { createMessenger, MAX_MESSAGE_LENGTH };