UNPKG

node-red-contrib-smartnora

Version:

Google Smart Home integration via Smart Nora https://smart-nora.eu/

134 lines (133 loc) 6.12 kB
"use strict"; 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();