92 lines
2.4 KiB
JavaScript
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 };
|