/** * 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 };