UNPKG

matterbridge-shelly

Version:
135 lines (134 loc) 6.46 kB
import EventEmitter from 'node:events'; import { createServer } from 'node:http'; import { AnsiLogger, CYAN, db, er, hk, rs, wr, zb } from 'matterbridge/logger'; import WebSocket, { WebSocketServer } from 'ws'; import { ShellyDevice } from './shellyDevice.js'; export class WsServer extends EventEmitter { log; httpServer; wsServer; pingPeriod = 30000; pongPeriod = 30000; _isListening = false; constructor(logLevel = "info") { super(); this.log = new AnsiLogger({ logName: 'ShellyWsServer', logTimestampFormat: 4, logLevel }); } get isListening() { return this._isListening; } async listenForStatusUpdates(port = 8485) { try { this.httpServer = createServer(); this.wsServer = new WebSocketServer({ server: this.httpServer }); } catch (error) { this.log.error(`Failed to create the HttpServer and WebSocketServer: ${error}`); return; } this.wsServer.on('connection', (ws, req) => { const clientAddress = req.socket.remoteAddress; this.log.info(`WebSocketServer client connected host ${zb}${clientAddress}${db}.`); this.log.debug(`Start WebSocketServer PingPong.`); let pongTimeout = undefined; let pingInterval = undefined; ws.ping(); pingInterval = setInterval(() => { if (ws?.readyState === WebSocket.OPEN) { ws.ping(); pongTimeout = setTimeout(() => { this.log.error(`WebSocketServer pong not received.`); }, this.pongPeriod); } }, this.pingPeriod); ws.on('message', (data) => { this.log.debug(`Received message from WebSocketServer client ${zb}${clientAddress}${db}.`); try { const message = JSON.parse(data.toString()); if (message.method && (message.method === 'NotifyStatus' || message.method === 'NotifyFullStatus') && message.src && message.dst === 'ws') { message.src = ShellyDevice.normalizeId(message.src).id; this.log.debug(`Received ${CYAN}${message.method}${db} from ${hk}${message.src}${db} host ${zb}${clientAddress}${db}:${rs}\n`, message.params); this.emit('wssupdate', message.src, message.params); } else if (message.method && message.method === 'NotifyEvent' && message.src && message.dst === 'ws') { message.src = ShellyDevice.normalizeId(message.src).id; this.log.debug(`Received ${CYAN}NotifyEvent${db} from ${hk}${message.src}${db} host ${zb}${clientAddress}${db}:${rs}\n`, message.params); this.emit('wssevent', message.src, message.params); } else { this.log.debug(`WebSocketServer received an unknown message from ${hk}${message.src}${db} host ${zb}${clientAddress}${wr}:${rs}\n`, message); } } catch (error) { this.log.error(`WebSocketServer error parsing message from ${zb}${clientAddress}${er}: ${error instanceof Error ? error.message : error}`); } }); ws.on('pong', (_data) => { this.log.debug('WebSocketServer client sent a pong'); clearTimeout(pongTimeout); pongTimeout = undefined; }); ws.on('ping', (_data) => { this.log.debug('WebSocketServer client sent a ping'); ws.pong(); }); ws.on('close', (code, reason) => { this.log.info(`WebSocketServer client disconnected: code ${code} ${reason.toString('utf-8') === '' ? '' : 'reason ' + reason.toString('utf-8')}`); clearInterval(pingInterval); pingInterval = undefined; clearTimeout(pongTimeout); pongTimeout = undefined; }); ws.on('error', (error) => { this.log.error('WebSocketServer client error:', error); }); }); this.wsServer.on('error', (error) => { this.log.error(`WebSocketServer error: ${error instanceof Error ? error.message : error}`); this._isListening = false; }); this.wsServer.on('close', () => { this.log.debug(`WebSocketServer connection closed.`); this._isListening = false; }); this.httpServer.on('error', (error) => { this.log.error(`HttpServer error: ${error instanceof Error ? error.message : error}`); this.emit('error', error); this._isListening = false; }); this.httpServer.listen(port, () => { this._isListening = true; this.log.debug(`HttpServer for WebSocketServer is listening on port ${port}`); this.log.info(`Started WebSocket server for shelly devices.`); this.log.info(`WebSocket server for shelly devices is listening on port ${port}...`); this.emit('started'); }); } start(port = 8485) { if (this._isListening) { this.log.debug(`WebSocketServer is already listening.`); return; } this.log.info(`Starting WebSocket server for shelly devices...`); this.listenForStatusUpdates(port); } stop() { this.log.info(`Stopping WebSocket server (listening ${this._isListening}) for shelly devices...`); for (const client of this.wsServer?.clients || []) { client.terminate(); } this.wsServer?.close((err) => { this.log.debug(`WebSocket server for shelly devices stopped${err ? ' with error ' + err.message : ''}.`); this.wsServer?.removeAllListeners(); this.wsServer = undefined; }); this.httpServer?.close((err) => { this.log.debug(`HttpServer for WebSocketServer stopped${err ? ' with error ' + err.message : ''}.`); this.httpServer?.removeAllListeners(); this.httpServer = undefined; }); this._isListening = false; this.log.info(`Stopped WebSocket server for shelly devices...`); this.emit('stopped', undefined); } }