Skip to content

Instantly share code, notes, and snippets.

@guest271314
Created December 25, 2023 14:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save guest271314/952e571a6aede75600e7bbcf9ba11f42 to your computer and use it in GitHub Desktop.
Save guest271314/952e571a6aede75600e7bbcf9ba11f42 to your computer and use it in GitHub Desktop.
Let's create STDIO between the browser and Node.js using files

Node.js has watch and watchFile in fs module.

Chromium-based browsers (Chrome, Opera, Edge) have WICG File System Access implemented.

Let's create STDIO between the browser and Node.js using files.

Even though Node.js documentation recommends using watch(), we'll use watchFile() for this case. Just to see what happens. Hear the spin when interval is set to 5 so we get input of lowercase letters and output of lowercase letters transformed to uppercase letters in order.

The Node.js part.

This assumes our watcher.js script is in the local bin directory on a Linux OS, thus ../Documents to point to the files in Documents directory. When the input variable pointing to the file named stdin we create in the browser is modified we read the file synchronously, write the data to the writable side of a TransformStream piped through a TextDecoderStream(), then transform the data, in this case lower case letters, to uppercase letters and write that transformed data to the output variable, reflecting the file stdout that we create(d) in the browser, then we write an empty string to the input variable (stdin) so that when we are done the process will resemble FIFO streams, nothing in the files.

// watcher.js
// node --experimental-default-type=module watcher.js

import { watch, watchFile, readFileSync, writeFileSync } from "node:fs";

const input = "../Documents/stdin";
const output = "../Documents/stdout";
const { readable, writable } = new TransformStream();
const writer = writable.getWriter();

readable.pipeThrough(new TextDecoderStream()).pipeTo(
  new WritableStream({
    write(value) {
      writeFileSync(output, value.toUpperCase());
      writeFileSync(input, "");
    },
  }),
);

watchFile(input, { interval: 5 }, () => {
  writer.write(new Uint8Array(readFileSync(input)));
});

On the browser side we create two files, named stdin and stdout, respectively. FileSystemObserver is not implemented, so we'll essentially do the same thing we are doing in the Node.js script in the browser.

Technically we could use Blob().stream().pipeTo() in the FileSystemObserver callback to pipe the data to writable side of our TransformStream; however, during testing I encountered network errors. That's happended before during other experiments in the past whether the error is file not found or similar, e.g., Capture 50 minutes of audio to file. So we'll get an ArrayBuffer representation of the data, pass that data to Response and pipe Response.body to avoid such Blob based errors.

We pipe through a TextDecoderStream() then pipe to a WritableStream() and read our transformed data.

Notice the if (value === "z") {...}. Because that's the last of our input from the browser to the Node.js script we don't overwrite that last input with writeFileSync(), leaving that Z in output, our stdout file we created in the browser. So we'll go ahead and get rid of that value when we're done streaming.

Of course that part can be improved, for the case where we don't really know what the last transformed data from the STDIO stream will be.

// fs.js
var [input, output] = await Promise.all(
  ["stdin", "stdout"].map((stdio) =>
    showSaveFilePicker({
      startIn: "documents",
      suggestedName: stdio,
    })
  ),
);

const { readable: stdout, writable: stdin } = new TransformStream();

stdout.pipeThrough(new TextDecoderStream()).pipeTo(
  new WritableStream({
    async write(value) {
      console.log(value);
      if (value === "Z") {
        new Blob([]).stream().pipeTo(await output.createWritable());
      }
    },
  }),
);

var fso = new FileSystemObserver(
  async ([{ changedHandle, root, type }], record) => {
    try {
      if (type === "modified") {
        new Response(await(await output.getFile()).arrayBuffer()).body.pipeTo(stdin, {
          preventClose: true
        });
      }
    } catch (e) {
      console.warn(e);
    }
  },
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment