homebridge-smartsystem
Version:
SmartServer (Proxy Websockets to TCP sockets, Smappee MQTT, Duotecno IP Nodes, Homekit interface)
257 lines • 10.5 kB
JavaScript
;
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