iobroker.js-controller
Version:
Updated by reinstall.js on 2018-06-11T15:19:56.688Z
266 lines (265 loc) • 8.95 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var multihostServer_exports = {};
__export(multihostServer_exports, {
MHServer: () => MHServer
});
module.exports = __toCommonJS(multihostServer_exports);
var import_node_dgram = __toESM(require("node:dgram"), 1);
var import_node_crypto = require("node:crypto");
var import_js_controller_common = require("@iobroker/js-controller-common");
const PORT = 50005;
const MULTICAST_ADDR = "239.255.255.250";
class MHServer {
count = 0;
buffer = {};
lastFrame = {};
authList = {};
config;
logger;
info;
secret;
hostname;
server = null;
initTimer = null;
stopped = false;
constructor(hostname, logger, config, info, secret) {
this.hostname = hostname;
this.config = config;
this.logger = logger;
this.info = info;
this.secret = secret;
this.init();
}
send(msg, rinfo) {
if (this.server) {
setImmediate(() => {
const text = JSON.stringify(msg);
try {
this.server?.send(text, 0, text.length, rinfo.port, rinfo.address);
} catch (e) {
this.logger.warn(`host.${this.hostname} Multi-host discovery server: cannot send answer to ${rinfo.address}:${rinfo.port}: ${e}`);
}
});
}
}
// delete all old connections
checkAuthList(ts) {
ts = ts || (/* @__PURE__ */ new Date()).getTime();
for (const id of Object.keys(this.authList)) {
if (!this.authList[id]) {
delete this.authList[id];
} else if (ts - this.authList[id].ts > 31e3) {
delete this.authList[id];
}
}
}
sha(secret, salt, callback) {
const hash = (0, import_node_crypto.createHash)("sha256");
hash.on("readable", () => {
const data = hash.read();
if (data) {
callback(data.toString("hex"));
}
});
hash.write(secret + salt);
hash.end();
}
// hello => auth => browse
async process(msg, rinfo) {
if (!msg) {
return;
}
const ts = (/* @__PURE__ */ new Date()).getTime();
this.checkAuthList(ts);
const id = `${rinfo.address}:${rinfo.port}`;
switch (msg.cmd) {
case "browse":
if (this.secret && msg.password && this.authList[id]) {
this.sha(this.secret, this.authList[id].salt, async (shaText) => {
if (shaText !== msg.password) {
this.send({
auth: this.config.multihostService.secure,
cmd: "browse",
id: msg.id,
result: "invalid password"
}, rinfo);
} else {
this.authList[id].auth = true;
this.send({
auth: this.config.multihostService.secure,
cmd: "browse",
id: msg.id,
result: "ok",
objects: this.config.objects,
states: this.config.states,
info: this.info,
hostname: this.hostname,
slave: !await (0, import_js_controller_common.isLocalObjectsDbServer)(this.config.objects.type, this.config.objects.host)
}, rinfo);
}
});
return;
}
if (!this.config.multihostService.secure || this.authList[id] && this.authList[id].auth) {
this.send({
auth: this.config.multihostService.secure,
cmd: "browse",
id: msg.id,
result: "ok",
objects: this.config.objects,
states: this.config.states,
info: this.info,
hostname: this.hostname,
slave: !await (0, import_js_controller_common.isLocalObjectsDbServer)(this.config.objects.type, this.config.objects.host)
}, rinfo);
} else {
this.authList[id] = {
ts,
salt: (Math.random() * 1e6 + ts).toString().substring(0, 16),
auth: false
};
if (this.authList[id].salt.length < 16) {
this.authList[id].salt += new Array(16 - this.authList[id].salt.length).join("_");
}
this.send({
auth: this.config.multihostService.secure,
cmd: "browse",
id: msg.id,
result: "not authenticated",
salt: this.authList[id].salt
}, rinfo);
}
break;
default:
this.send({
cmd: msg.cmd,
id: msg.id,
result: "unknown command"
}, rinfo);
break;
}
}
init() {
this.stopped = false;
if (this.initTimer) {
clearTimeout(this.initTimer);
this.initTimer = null;
}
if (this.count > 10) {
return this.logger.warn(`host.${this.hostname} Multi-host discovery server: Port ${PORT} is occupied. Service stopped.`);
}
this.server = import_node_dgram.default.createSocket({ type: "udp4", reuseAddr: true });
this.server.on("error", (err) => {
this.logger.error(`host.${this.hostname} Multi-host discovery server: error: ${err.stack}`);
this.server?.close();
this.server = null;
this.initTimer = this.initTimer || setTimeout(() => {
this.initTimer = null;
this.init();
}, 5e3);
});
this.server.on("close", () => {
this.server = null;
if (!this.initTimer && !this.stopped) {
this.initTimer = setTimeout(() => {
this.initTimer = null;
this.init();
}, 5e3);
}
});
this.server.on("message", (msg, rinfo) => {
const text = msg.toString();
const now = (/* @__PURE__ */ new Date()).getTime();
const id = `${rinfo.address}:${rinfo.port}`;
for (const ids in this.buffer) {
if (!this.lastFrame[ids]) {
delete this.buffer[ids];
} else if (now - this.lastFrame[ids] > 1e3) {
delete this.buffer[ids];
delete this.lastFrame[ids];
}
}
if (this.lastFrame[id] && now - this.lastFrame[id] > 1e3) {
this.buffer[id] = "";
}
this.lastFrame[id] = now;
if (!this.buffer[id] && text[0] !== "{") {
this.logger.debug(`host.${this.hostname} Multi-host discovery server: Message from ${rinfo.address} ignored: ${text}`);
} else {
this.buffer[id] = (this.buffer[id] || "") + msg.toString();
if (this.buffer[id] && this.buffer[id][this.buffer[id].length - 1] === "}") {
try {
const data = JSON.parse(this.buffer[id]);
this.buffer[id] = "";
if (data) {
this.process(data, rinfo);
}
} catch {
}
}
}
});
this.server.on("listening", () => {
try {
this.server?.addMembership(MULTICAST_ADDR);
} catch {
this.logger.warn(`host.${this.hostname} Multi-host discovery server: Multicast membership could not be added.`);
}
const address = this.server?.address();
this.logger.info(`host.${this.hostname} Multi-host discovery server: service started on ${address?.address}:${address?.port}`);
});
this.server.bind(PORT);
}
close(callback) {
this.stopped = true;
if (this.initTimer) {
clearTimeout(this.initTimer);
this.initTimer = null;
}
if (this.server) {
try {
this.server.close(callback);
this.server = null;
} catch {
this.server = null;
if (callback) {
callback();
}
}
} else if (callback) {
callback();
}
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
MHServer
});
//# sourceMappingURL=multihostServer.js.map