Closed mStirner closed 1 year ago
Possible solution (need to be tested):
system/bridge.js
const { EOL } = require("os");
const WebSocket = require("ws");
function createStream(upstream, settings, cb = () => { }) {
// cleanup
upstream.unpipe();
let stream = require(`../sockets/${settings.socket}`)(settings);
stream.once("error", (err) => {
if (["ECONNRESET", "ECONNREFUSED"].includes(err.code) && process.env.ALLOW_HALF_OPEN === "true") {
console.error(err, "half open enabled, wait for data from upstream!");
// waiting for data from websocket connection
// before re-try to connect
upstream.unpipe();
stream.unpipe();
upstream.once("readable", () => {
// read first chunk to not lose anyhting
let chunk = upstream.read();
// try again and create new underlaying network stream
createStream(upstream, settings, cb).write(chunk);
});
cb();
}
});
upstream.pipe(stream);
stream.pipe(upstream);
return stream;
}
/**
* @function bridge
* Birdges a WebSocket endpoint to a network socket
*
* @param {String} uri WebSocket URL endpoint
*/
function bridge(uri, settings, options) {
try {
let counter = 0;
let ws = new WebSocket(uri);
let upstream = WebSocket.createWebSocketStream(ws, options);
createStream(upstream, settings, () => {
counter++;
if (counter >= 10) {
console.error("To many retries!");
process.exit(643);
}
});
upstream.on("error", (err) => {
console.error(`Could not bridge interface ${settings.socket}://${settings.host}:${settings.port}${EOL}`, err);
});
} catch (err) {
console.error("Error happend %s", uri, err);
}
}
module.exports = bridge;
bridge.js
#! /usr/bin/node
const { EOL } = require("os");
const {
isMainThread,
workerData
} = require("worker_threads");
const minimist = require("minimist");
const bridge = require("./system/bridge.js");
// TODO Handle cli args and use as "standalone"
// Usage: bridge.js --upstream="..." --socket="tcp" --host="192.168.125.24" --port="443" --settings='{"foo": "bar"}' --options='{"bar": "baz"}'
// TODO If host&port are missing read/write from/to stdin/stdout
// Usefull for serial communiction or debugging
const argv = minimist(process.argv, {
boolean: ["help"],
default: {
upstream: null,
socket: "tcp",
host: null,
port: null,
settings: "{}",
options: "{}"
}
});
// override arguments with json parsed string
argv.settings = JSON.parse(argv.settings);
argv.options = JSON.parse(argv.options);
// check arguments if used as cli client
if ((isMainThread && (!argv.upstream || !argv.host || !argv.port)) || argv.help) {
console.group("Usage of bridge.js as cli tool:", EOL);
console.log(`bridge.js --upstream="http://example.com" --host="127.0.0.1" --port="8080"`, EOL);
console.log("--upstream\tWebSocket upstream to backend");
console.log("--socket\tNetwork socket type: tcp|udp|raw");
console.log("--host\tHost to connect to");
console.log("--port\tHost port to connect to");
console.log("[--settings]\tSettings object from interface as json string");
console.log("[--options]\tDuplex stream options passed to WebSocket.createWebSocketStream");
console.log("");
process.exit(1);
}
// NOTE add `&& require.main.filename === __filename` to check?!
if (isMainThread && argv.upstream && argv.host && argv.port) {
let settings = Object.assign({
host: argv.host,
port: argv.port,
socket: argv.socket
}, argv.settings);
let options = Object.assign({
// duplex stream options
// passed to WebSocket.createWebSocketStream
}, argv.options);
// bridge the websocket stream to underlaying network socket
bridge(argv.upstream, settings, options);
} else {
// deconstruct arguments
let { upstream, settings, options } = workerData;
// bridge the websocket stream to underlaying network socket
bridge(upstream, settings, options);
}
A autoconnection is not allways possible, for example, smasung TVs provide a WebSocket interface, which is only available if the TV is turned on (Over WoL or via IR).
Currently the connector would try over and over to connect to the websocket. Try to connect only if some data is received of the upstream and not automatically.