UNPKG

homebridge-smartsystem

Version:

SmartServer (Proxy Websockets to TCP sockets, Smappee MQTT, Duotecno IP Nodes, Homekit interface)

257 lines 10.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Context = exports.cleanStart = exports.makeNewCloudConnection = exports.setProxyConfig = exports.kEmptyProxy = void 0; const net = require("net"); const WebSocket = require("ws"); const child_process_1 = require("child_process"); const process_1 = require("process"); exports.kEmptyProxy = { cloudServer: "ws.duotecno.eu", cloudPort: 5098, masterAddress: "", masterPort: 5001, uniqueId: "", debug: false, kind: "solo" // default running under PM2 or other process manager -> don't restart yourself }; function setProxyConfig(c) { // set the proxy config if (c) { config.cloudServer = c.cloudServer || exports.kEmptyProxy.cloudServer; config.cloudPort = c.cloudPort || exports.kEmptyProxy.cloudPort; config.masterAddress = c.masterAddress || exports.kEmptyProxy.masterAddress; config.masterPort = c.masterPort || exports.kEmptyProxy.masterPort; config.uniqueId = c.uniqueId || exports.kEmptyProxy.uniqueId; config.debug = !!c.debug; config.kind = c.kind || exports.kEmptyProxy.kind; } else { config = Object.assign({}, exports.kEmptyProxy); } return config; } exports.setProxyConfig = setProxyConfig; let config; try { setProxyConfig(require('../config-proxy.json')); } catch (e) { setProxyConfig(); console.log("No config-proxy.json found, using default values from homebridge or other."); } const gCloudConnections = []; const kDebug = config.debug || false; // enable debug mode from config ///////////// // Logging // ///////////// function debug(str) { if (kDebug) { console.log(`DEBUG: ${str}`); } } function warning(str) { console.warn(`WARNING: ${str}`); } function log(str) { console.log(`LOG: ${str}`); } function error(str) { console.error(`ERROR: **** ${str} ****`); } /////////////////////////////// // Start up the proxy server // /////////////////////////////// // if (config.kind != "gw") { // makeNewCloudConnection(config.masterAddress, config.masterPort, config.cloudServer, config.cloudPort, config.uniqueId); // } function makeNewCloudConnection(master, masterPort, server, serverPort, uniqueId) { const cloudSocket = new WebSocket('ws://' + server + ':' + serverPort + "/" + uniqueId); cloudSocket.on('open', () => { log(`[PROXY] → [CLOUD] Connected to Cloud at ${server}:${serverPort}`); const context = new Context(cloudSocket, master, masterPort, server, serverPort, uniqueId); gCloudConnections.push(context); log(`[PROXY] → [CLOUD] New free connection #${gCloudConnections.length} to the Cloud at ${server}:${serverPort}`); }); log(`[PROXY] → [CLOUD] Attempting to start a new free connection for ${config.uniqueId} to the Cloud at ${server}:${serverPort}`); } exports.makeNewCloudConnection = makeNewCloudConnection; //////////////// // Restarting // //////////////// function cleanStart(restart = false) { if (gCloudConnections.length) { gCloudConnections.forEach(context => { if (context.deviceSocket) { context.deviceSocket.end(); context.deviceSocket = null; } context.cloudSocket.close(); context.cloudSocket = null; }); gCloudConnections.splice(0, gCloudConnections.length); // clear the array log(`[PROXY] → [CLOUD] Cleaned up all cloud connections.`); } if (restart) { if (config.uniqueId) { makeNewCloudConnection(config.masterAddress, config.masterPort, config.cloudServer, config.cloudPort, config.uniqueId); } else { log(`[PROXY] → [CLOUD] Not restarting, no unique ID configured, not starting the proxy.`); } } else { log(`[PROXY] → [CLOUD] Not restarting, no new cloud connection, just cleaning up.`); } } exports.cleanStart = cleanStart; class Context { constructor(cloudSocket, master, masterPort, server, serverPort, uniqueId) { this.deviceSocket = null; this.master = master; this.masterPort = masterPort; this.cloudSocket = cloudSocket; this.server = server; this.serverPort = serverPort; this.uniqueId = uniqueId; this.setupCloudSocket(); } setupCloudSocket() { // set up handlers for the cloud socket this.cloudSocket.on('message', message => { this.handleDataFromCloud(message); }); this.cloudSocket.on('close', (code, reason) => { warning('[CLOUD] Connection closed: ' + code + ' ' + reason); if (this.deviceSocket) { this.deviceSocket.end(); this.deviceSocket = null; } }); this.cloudSocket.on('error', err => { error(`[CLOUD] Error: ${err.message}`); if (this.deviceSocket) { this.deviceSocket.end(); this.deviceSocket = null; } }); } handleDataFromCloud(data) { debug(`[CLOUD] → [PROXY]: Data from Cloud: ${data.toString().slice(0, -1)}`); if (!this.deviceSocket) { // we either have a real client that wants to connect to the device, // or we receive a heartbeat from the cloud server if (this.isHeartbeatRequest(data)) { debug(`[CLOUD] → [PROXY]: Received heartbeat from Cloud, returning response.`); // respond to the heartbeat of the cloud server this.cloudSocket.send("[72,3]"); // command([Command.HeartbeatStatus, CommandMethod.Heartbeat.server]) } else { // There is incomming data, it's a fresh connection, so: a new client wants to connect to the device // 1) create a new (free) connection to the cloud server for the next client // 2) connect to the device and send the initial data log(`[PROXY] → [CLOUD] New client connection, creating new free connection for this proxy/device: ${this.uniqueId}.`); makeNewCloudConnection(this.master, this.masterPort, this.server, this.serverPort, this.uniqueId); this.makeDeviceConnection(this.master, this.masterPort, data); } } else if (this.deviceSocket.readyState === 'open') { this.deviceSocket.write(data); debug(`[PROXY] → [DEVICE] forwarding data to device: ${data.toString().slice(0, -1)}`); } else { warning(`[PROXY] → [DEVICE] Device socket is not open yet, waiting for connection... what to do with the data??`); } } makeDeviceConnection(address, port, data) { log(`[PROXY] → [DEVICE] Attempting to connect to local device at ${address}:${port}`); if (!this.deviceSocket) { this.deviceSocket = new net.Socket(); // Connect to local device and send the data that came in from the cloud this.deviceSocket.connect(port, address, () => { log(`[PROXY] → [DEVICE] Connected to local device at ${address}:${port}`); this.setUpDeviceSocket(); log(`[PROXY] → [DEVICE] Sending initial message: ${data.toString().slice(0, -1)}`); this.deviceSocket.write(data); }); } else if (this.deviceSocket.readyState === 'open') { warning(`[PROXY] → [DEVICE] Device socket already open, sending data directly -- STRANGE !!`); this.deviceSocket.write(data); } else { error(`[PROXY] → [DEVICE] Device socket exists, but is not open yet !!`); } } setUpDeviceSocket() { this.deviceSocket.on('data', (data) => { const message = data.toString('utf-8'); debug(`[DEVICE] → [PROXY] forwarding data to Cloud: ${message.slice(0, -1)}`); this.cloudSocket.send(message); }); this.deviceSocket.on('close', () => { log(`[DEVICE] → [PROXY] Device socket closed`); this.removeConnection(); }); this.deviceSocket.on('error', (err) => { error(`[DEVICE] → [PROXY] Device socket error: ${err.message}`); this.removeConnection(); }); } removeConnection() { this.deviceSocket = null; // remove this context and close the cloud socket const index = gCloudConnections.indexOf(this); if (index !== -1) { this.cloudSocket.close(); this.cloudSocket = null; gCloudConnections.splice(index, 1); log(`[PROXY] → [CLOUD] Closed socket to Cloud and removed context for device ${this.master}`); } if (gCloudConnections.length === 0) { log(`[PROXY] → [CLOUD] No more cloud connections, restarting proxy.`); // just to be sure: restart... if (config.kind === "pm2") { // PM2 should restart us. log(`[PROXY] → [CLOUD] PM2 should restart us, exiting now.`); process.exit(); } else if (config.kind === "solo") { log(`[PROXY] → [CLOUD] Running solo, restarting proxy.`); this.restart(); } else { log(`[PROXY] → [CLOUD] Running under gateway/homebridge, not restarting... not sure what to do here.`); } } } /////////////// // Utilities // /////////////// restart() { console.log('🔄 Restarting...'); (0, child_process_1.spawn)(process_1.execPath, process_1.argv.slice(1), { cwd: (0, process_1.cwd)(), detached: true, stdio: 'inherit', }); process.exit(); } // check if it's a heartbeat request from the server isHeartbeatRequest(data) { // heartbeatKind = (poll = 1, poll_old = 2, serve = 3) if (typeof data === 'string') { return data === "[215,3]"; } else { return ((data[0] === 91) && // [ (data[1] === 50) && // 2 (data[2] === 49) && // 1 (data[3] === 53) && // 5 (data[4] === 44) && // , (data[5] === 51) && // 3 (data[6] === 93)); // ] } } } exports.Context = Context; //# sourceMappingURL=proxy.js.map