txiki.js: Can't read from from process and write to client in Native Messaging Host

I have working C++

// C++ Native Messaging host
// https://browserext.github.io/native-messaging/
// https://developer.chrome.com/docs/apps/nativeMessaging/
// https://discourse.mozilla.org/t/webextension-with-native-messaging-c-app-side/30821
#include <iostream>
using namespace std;

void sendMessage(string message) {
  uint32_t size = uint32_t(message.size());
  char *length = reinterpret_cast<char *>(&size);
  fwrite(length, 4, sizeof(char), stdout);
  fwrite(message.c_str(), message.length(), sizeof(char), stdout);
  fflush(stdout);
}

string getMessage() {
  char length[4];
  fread(length, 4, sizeof(char), stdin);
  uint32_t len = *reinterpret_cast<uint32_t *>(length);
  if (!len) {
    exit(EXIT_SUCCESS);
  }
  char message[len];
  fread(message, len, sizeof(char), stdin);
  string content(message, message + sizeof(message) / sizeof(message[0]));
  return content;
}

int main() {
  string message = getMessage();
  // Exclude double quotation marks from beginning and end of string
  string input = message.substr(1, message.length() - 2);
  FILE *pipe = popen(input.c_str(), "r");
  while (true) {
    unsigned char buffer[1764]; // 441 * 4
    int count = fread(buffer, 1, sizeof(buffer), pipe);
    string output;
    output += "[";
    for (int i = 0; i < count; i++) {
      output += to_string((int)buffer[i]);
      if (i < count - 1) {
        output += ",";
      }
    }
    output += "]";
    sendMessage(output);
  }
}

and Python code (https://github.com/guest271314/captureSystemAudio/blob/master/native_messaging/capture_system_audio/capture_system_audio.py) to open a new subprocess and stream stdout from the process to client (browser).

QuickJS and txiki.js do not appear capable of handling reading from the subprocess and writing to the client.

I have tried different approaches, none achieve the expected result.

#!tjs
// txiki.js Native Messaging host
// guest271314, 5-6-2022
(async () => {

  let encoder = new TextEncoder();
  let decoder = new TextDecoder();

  async function getMessage() {
    const header = new Uint8Array(4);
    const input = await tjs.stdin.read(header);
    // https://stackoverflow.com/a/26140545
    const length = new Uint32Array(header.buffer).reduce(
      (a, b, index) => a | (b << (index * 8)),
      0
    );
    const output = new Uint8Array(length);
    await tjs.stdin.read(output);
    return output;
  }

  async function sendMessage(message) {
    // https://stackoverflow.com/a/24777120
    const header = Uint32Array.from(
      {
        length: 4,
      },
      (_, index) => (message.length >> (index * 8)) & 0xff
    );
    const output = new Uint8Array(header.length + message.length);
    output.set(header, 0);
    output.set(message, 4);
    await tjs.stdout.write(output);
    return true;
  }
  
  let message, proc, data;
  while (true) {
    // message = await getMessage();
    // await sendMessage(message);
    proc = tjs.spawn(['parec', '-d', '@DEFAULT_MONITOR@'], { stdout: 'pipe' });
    data = new Uint8Array(1024);
    while (true) {
      await proc.stdout.read(data);
      // await sendMessage(data);
      console.log(data);
    }
  }
})();

Using QuickJS

#!/usr/bin/env -S /path/to/qjs -m --std
// QuickJS Native Messaging host
// guest271314, 5-6-2022
import * as std from 'std';
import * as os from 'os';

async function getMessage() {
  const header = new Uint8Array(4);
  std.in.read(header.buffer, 0, header.length);
  // https://stackoverflow.com/a/26140545
  const length = new Uint32Array(header.buffer).reduce(
    (a, b, index) => a | (b << (index * 8)),
    0
  );
  const output = new Uint8Array(length);
  std.in.read(output.buffer, 0, length);
  return output;
}

async function encodeMessage(message) {
  // https://stackoverflow.com/a/24777120
  const header = Uint32Array.from(
    {
      length: 4,
    },
    (_, index) => (message.length >> (index * 8)) & 0xff
  );
  const output = new Uint8Array(header.length + message.length);
  output.set(header, 0);
  output.set(message, 4);
  return output;

}

async function sendMessage(message) {
  std.out.write(message.buffer, 0, message.length);
  std.out.flush();
  return true;
}

async function main() {  
  let message = await getMessage();
  let fds = os.pipe();
  let pid = os.exec(["sh", "-c", "parec -d @DEFAULT_MONITOR@"], {
      stdout: fds[1],
      block: false
  });
  os.close(fds[1]); 
  let f = std.fdopen(fds[0], "r");

  while (true) {
     let data = new Uint8Array([...f.readAsString(1024)].map(s => s.codePointAt(0) & 0xff));
     await sendMessage(await encodeMessage(data));
     // console.log(data);
  }
}

main();

The expected result is that the message is passed to Native Messaging Host, parsed to string, or array if necessary, passed to spawn() and stdout from that process is sent to the Native Messaging client.

When the file is run standalone, removing getMessage() and sendMessage() calls, substituting console.log(data) for sendMessage(data), the process is started, and an object representaion of Uint8Array are printed at terminal.

(I am not sure if Uint8Array is completely implemented?)

Why does read() block in this case?

What do I need to adjust to achieve the same or similar result I do using C++ and Python?

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 28 (28 by maintainers)

Most upvoted comments

Got the Uint8Array() and write()` version working as expected

#!/usr/bin/env -S qjs -m --std
// QuickJS Native Messaging host
// guest271314, 5-6-2022
function getMessage() {
  const header = new Uint32Array(1);
  std.in.read(header.buffer, 0, 4);
  const [length] = header;
  const output = new Uint8Array(length);
  std.in.read(output.buffer, 0, length);
  return output;
}

function sendMessage(json) {
  const header = Uint32Array.from(
    {
      length: 4,
    },
    (_, index) => (json.length >> (index * 8)) & 0xff
  );
  const output = new Uint8Array(header.length + json.length);
  output.set(header, 0);
  output.set(json, 4);
  std.out.write(output.buffer, 0, output.length);
  std.out.flush();
  return true;
}

function main() {
  const message = getMessage();
  const data = new Uint8Array(1764);
  const pipe = std.popen(
    JSON.parse(String.fromCharCode.apply(null, message)),
    'r'
  );
  while (pipe.read(data.buffer, 0, data.length)) {
    sendMessage([...`[${data}]`].map((s) => s.charCodeAt()));
    pipe.flush();
  }
}
try {
  main();
} catch (e) {
  std.exit(0);
}

Got the code working without importing the .so file substituting sending JSON string with puts() for Uint8Array() with write().



#!/usr/bin/env -S qjs -m --std
// QuickJS Native Messaging host
// guest271314, 6-19-2022
import * as std from 'std';

function getMessage() {
  const header = new Uint32Array(1);
  std.in.read(header.buffer, 0, header.byteLength);
  const length = header[0];
  const output = new Uint8Array(length);
  std.in.read(output.buffer, 0, length);
  return output;
}

function sendMessage(json) {
  const header = Uint32Array.from(
    {
      length: 4,
    },
    (_, index) => (json.length >> (index * 8)) & 0xff
  );
  const output = new Uint8Array(header.length);
  output.set(header, 0);
  std.out.write(output.buffer, 0, output.length);
  std.out.puts(json);
  std.out.flush();
  return true;
}

function main() {
  const message = getMessage();
  const size = 1764; // 441 * 4
  let data = new Uint8Array(size),
    count = 0;
  const pipe = std.popen(
    JSON.parse(String.fromCharCode.apply(null, message)),
    'r'
  );
  while ((count = pipe.read(data.buffer, 0, data.length))) {
    sendMessage(`[${data}]`);
    pipe.flush();
  }
}
try {
  main();
} catch (e) {
  std.exit(0);
}