OpenHausIO / connector

Connector between backend & local network
MIT License
1 stars 2 forks source link

Implement "autoconnect" setting #7

Closed mStirner closed 1 year ago

mStirner commented 2 years ago

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.

mStirner commented 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);

}