import socketIOClient from 'socket.io-client'; import { Logger } from '../../utils/logger.js'; import { Common } from '../../utils/common.js'; import { WSServer } from '../../utils/webSocketServer.js'; export class CLWebSocketClient { constructor() { this.logger = Logger; this.common = Common; this.wsServer = WSServer; this.webSocketClients = []; this.reconnectTimeOut = null; this.waitTime = 0.5; this.reconnect = (clWsClt) => { if (this.reconnectTimeOut) { return; } this.waitTime = (this.waitTime >= 64) ? 64 : (this.waitTime * 2); this.reconnectTimeOut = setTimeout(() => { if (clWsClt.selectedNode) { this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Reconnecting to the Core Lightning\'s Websocket Server..' }); this.connect(clWsClt.selectedNode); } this.reconnectTimeOut = null; }, this.waitTime * 1000); }; this.connect = (selectedNode) => { try { const clientExists = this.webSocketClients.find((wsc) => wsc.selectedNode.index === selectedNode.index); if (!clientExists) { if (selectedNode.ln_server_url) { const newWebSocketClient = { selectedNode: selectedNode, reConnect: true, webSocketClient: null }; this.connectWithClient(newWebSocketClient); this.webSocketClients.push(newWebSocketClient); } } else { if ((!clientExists.webSocketClient || !clientExists.webSocketClient.connected) && selectedNode.ln_server_url) { clientExists.reConnect = true; this.connectWithClient(clientExists); } } } catch (err) { throw new Error(err); } }; this.connectWithClient = (clWsClt) => { this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Connecting to the Core Lightning\'s Websocket Server..' }); try { if (!clWsClt.selectedNode.rune_value) { clWsClt.selectedNode.rune_value = this.common.getRuneValue(clWsClt.selectedNode.rune_path); } clWsClt.webSocketClient = socketIOClient(clWsClt.selectedNode.ln_server_url, { extraHeaders: { rune: clWsClt.selectedNode.rune_value }, transports: ['websocket'], secure: true, rejectUnauthorized: false }); } catch (err) { throw new Error(err); } clWsClt.webSocketClient.on('connect', () => { this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Connected to the Core Lightning\'s Websocket Server..' }); this.waitTime = 0.5; }); clWsClt.webSocketClient.on('disconnect', (reason) => { if (clWsClt && clWsClt.selectedNode && clWsClt.selectedNode.ln_implementation === 'CLN') { this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Web socket disconnected, will reconnect again...', data: reason }); clWsClt.webSocketClient.close(); if (clWsClt.reConnect) { this.reconnect(clWsClt); } } }); clWsClt.webSocketClient.on('message', (msg) => { this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'DEBUG', fileName: 'CLWebSocket', msg: 'Received message from the server..', data: msg.data }); this.wsServer.sendEventsToAllLNClients(JSON.stringify({ source: 'CLN', data: msg }), clWsClt.selectedNode); }); clWsClt.webSocketClient.on('error', (err) => { this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'ERROR', fileName: 'CLWebSocket', msg: 'Web socket error', error: err }); const errStr = ((typeof err === 'object' && err.message) ? JSON.stringify({ error: err.message }) : (typeof err === 'object') ? JSON.stringify({ error: err }) : ('{ "error": ' + err + ' }')); this.wsServer.sendErrorToAllLNClients(errStr, clWsClt.selectedNode); clWsClt.webSocketClient.close(); if (clWsClt.reConnect) { this.reconnect(clWsClt); } }); }; this.disconnect = (selectedNode) => { const clientExists = this.webSocketClients.find((wsc) => wsc.selectedNode.index === selectedNode.index); if (clientExists && clientExists.webSocketClient && clientExists.webSocketClient.connected) { this.logger.log({ selectedNode: clientExists.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Disconnecting from the Core Lightning\'s Websocket Server..' }); clientExists.reConnect = false; clientExists.webSocketClient.close(); const clientIdx = this.webSocketClients.findIndex((wsc) => wsc.selectedNode.index === selectedNode.index); this.webSocketClients.splice(clientIdx, 1); } }; this.updateSelectedNode = (newSelectedNode) => { const clientIdx = this.webSocketClients.findIndex((wsc) => +wsc.selectedNode.index === +newSelectedNode.index); let newClient = this.webSocketClients[clientIdx]; if (!newClient) { newClient = { selectedNode: null, reConnect: true, webSocketClient: null }; } newClient.selectedNode = JSON.parse(JSON.stringify(newSelectedNode)); this.webSocketClients[clientIdx] = newClient; }; this.wsServer.eventEmitterCLN.on('CONNECT', (nodeIndex) => { this.connect(this.common.findNode(+nodeIndex)); }); this.wsServer.eventEmitterCLN.on('DISCONNECT', (nodeIndex) => { this.disconnect(this.common.findNode(+nodeIndex)); }); } } export const CLWSClient = new CLWebSocketClient();