node-red-contrib-smartnora
Version:
Google Smart Home integration via Smart Nora https://smart-nora.eu/
134 lines (133 loc) • 6.12 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.LocalExecution = void 0;
const dgram_1 = require("dgram");
const http_1 = require("http");
const os_1 = require("os");
const cbor_1 = require("cbor");
const nora_firebase_common_1 = require("@andrei-tatar/nora-firebase-common");
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
const __1 = require("..");
const DISCOVERY_PACKET = Buffer.from('021dfa122e51acb0b9ea5fbce02741ba69a37a203bd91027978cf29557cbb5b6', 'hex');
const DISCOVERY_PORT = 6988;
const DISCOVERY_REPLY_PORT = 6989;
const HTTP_PORT = 6987;
class LocalExecution {
constructor() {
this.devices$ = new rxjs_1.BehaviorSubject([]);
this.discovery$ = new rxjs_1.Observable(observer => {
const socket = (0, dgram_1.createSocket)('udp4');
socket.on('message', (msg, rinfo) => observer.next({ socket, data: msg, from: rinfo.address }));
socket.bind(DISCOVERY_PORT);
return () => socket.close();
}).pipe((0, operators_1.filter)(msg => msg.data.compare(DISCOVERY_PACKET) === 0), (0, operators_1.switchMap)(async ({ socket, from }) => {
var _a;
(_a = LocalExecution.logger) === null || _a === void 0 ? void 0 : _a.trace('[nora][local-execution] Received discovery packet, sending reply');
const responsePacket = await (0, cbor_1.encodeAsync)({
type: 'proxy',
proxyId: LocalExecution.proxyId,
port: HTTP_PORT,
});
socket.send(responsePacket, DISCOVERY_REPLY_PORT, from);
}));
this.server$ = new rxjs_1.Observable(_ => {
const server = (0, http_1.createServer)(async (req, res) => {
var _a;
if (req.url === '/nora-local-execution' && req.method === 'POST') {
const body = await this.readBody(req);
switch (body.type) {
case 'EXECUTE': {
(_a = LocalExecution.logger) === null || _a === void 0 ? void 0 : _a.trace(`[nora][local-execution] Executing ${body.command} - device: ${body.deviceId}`);
const device = this.devices$.value.find(d => d.cloudId === body.deviceId);
try {
if (device) {
const state = await device.executeCommand(body.command, body.params);
this.sendJson(res, state);
}
else {
this.sendJson(res, { errorCode: 'deviceNotFound' });
}
}
catch (err) {
if (err instanceof nora_firebase_common_1.ExecuteCommandError) {
this.sendJson(res, { errorCode: err.errorCode });
}
else {
this.sendJson(res, { errorCode: 'deviceNotReady' });
}
}
return;
}
}
}
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('NOT FOUND');
});
server.listen(HTTP_PORT);
return () => server.close();
});
this.services$ = (0, rxjs_1.merge)(this.discovery$, this.server$).pipe((0, __1.publishReplayRefCountWithDelay)(1000));
}
static getUniqueId() {
const interfaces = (0, os_1.networkInterfaces)();
for (const [_, networks] of Object.entries(interfaces)) {
for (const net of (networks !== null && networks !== void 0 ? networks : [])) {
if (net.mac !== '00:00:00:00:00:00') {
return net.mac.replace(/:/gm, '');
}
}
}
const random = new Array(16).fill(0).map(_ => Math.floor(Math.random() * 255));
return Buffer.from(random).toString('hex');
}
static withLogger(logger) {
var _a;
(_a = this.logger) !== null && _a !== void 0 ? _a : (this.logger = logger);
return this;
}
registerDeviceForLocalExecution(device) {
var _a;
if (!(0, nora_firebase_common_1.deviceSupportsLocalExecution)(device.device)) {
(_a = LocalExecution.logger) === null || _a === void 0 ? void 0 : _a.trace(`[nora][local-execution] ${device.device.name.name}, doesn't support local execution, skipping`);
return rxjs_1.EMPTY;
}
device.device.otherDeviceIds = [{ deviceId: device.cloudId }];
device.device.customData = {
proxyId: LocalExecution.proxyId,
};
return (0, rxjs_1.merge)(this.services$, new rxjs_1.Observable(_ => {
this.devices$.next(this.devices$.value.concat(device));
return () => {
this.devices$.next(this.devices$.value.filter(v => v !== device));
};
})).pipe((0, operators_1.ignoreElements)());
}
readBody(request) {
return new Promise((resolve, reject) => {
const body = [];
request
.on('data', (chunk) => body.push(chunk))
.on('error', (err) => reject(err))
.on('end', () => {
try {
const bodyString = Buffer.concat(body).toString();
resolve(JSON.parse(bodyString));
}
catch (err) {
reject(err);
}
});
});
}
sendJson(res, body) {
res.writeHead(200, {
'content-type': 'application/json'
});
res.write(JSON.stringify(body));
res.end();
}
}
exports.LocalExecution = LocalExecution;
LocalExecution.proxyId = LocalExecution.getUniqueId();
LocalExecution.instance = new LocalExecution();