2021-12-29 18:08:41 -05:00
import { parse } from 'cookie' ;
import * as cookieParser from 'cookie-parser' ;
import * as crypto from 'crypto' ;
2022-08-09 23:27:55 -07:00
import { WebSocketServer } from 'ws' ;
2021-12-29 18:08:41 -05:00
import { Logger , LoggerService } from './logger.js' ;
import { Common , CommonService } from './common.js' ;
import { verifyWSUser } from './authCheck.js' ;
import { EventEmitter } from 'events' ;
2024-06-10 12:40:37 -07:00
import { SelectedNode } from '../models/config.model.js' ;
2021-12-29 18:08:41 -05:00
2022-08-09 23:27:55 -07:00
export class RTLWebSocketServer {
2021-12-29 18:08:41 -05:00
public logger : LoggerService = Logger ;
public common : CommonService = Common ;
public clientDetails : Array < { index : number , sessionIds : Array < string > } > = [ ] ;
2022-05-01 13:35:20 -04:00
public eventEmitterCLN = new EventEmitter ( ) ;
2021-12-29 18:08:41 -05:00
public eventEmitterECL = new EventEmitter ( ) ;
public eventEmitterLND = new EventEmitter ( ) ;
public webSocketServer = null ;
public pingInterval = setInterval ( ( ) = > {
if ( this . webSocketServer . clients . size && this . webSocketServer . clients . size > 0 ) {
this . webSocketServer . clients . forEach ( ( client ) = > {
if ( client . isAlive === false ) {
this . updateLNWSClientDetails ( client . sessionId , - 1 , client . clientNodeIndex ) ;
return client . terminate ( ) ;
}
client . isAlive = false ;
client . ping ( ) ;
} ) ;
}
} , 1000 * 60 * 60 ) ; // Terminate broken connections every hour
2022-03-05 12:13:02 -05:00
public mount = ( httpServer ) = > {
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : this.common.selectedNode , level : 'INFO' , fileName : 'WebSocketServer' , msg : 'Connecting Websocket Server..' } ) ;
2022-08-09 23:27:55 -07:00
this . webSocketServer = new WebSocketServer ( { noServer : true , path : this.common.baseHref + '/api/ws' , verifyClient : ( process . env . NODE_ENV === 'development' ) ? null : verifyWSUser } ) ;
2021-12-29 18:08:41 -05:00
httpServer . on ( 'upgrade' , ( request , socket , head ) = > {
if ( request . headers [ 'upgrade' ] !== 'websocket' ) {
socket . end ( 'HTTP/1.1 400 Bad Request' ) ;
return ;
}
const acceptKey = request . headers [ 'sec-websocket-key' ] ;
const hash = this . generateAcceptValue ( acceptKey ) ;
const responseHeaders = [ 'HTTP/1.1 101 Web Socket Protocol Handshake' , 'Upgrade: WebSocket' , 'Connection: Upgrade' , 'Sec-WebSocket-Accept: ' + hash ] ;
2022-08-17 15:48:04 -07:00
const protocols = ! request . headers [ 'sec-websocket-protocol' ] ? [ ] : request . headers [ 'sec-websocket-protocol' ] . split ( ',' ) ? . map ( ( s ) = > s . trim ( ) ) ;
2021-12-29 18:08:41 -05:00
if ( protocols . includes ( 'json' ) ) { responseHeaders . push ( 'Sec-WebSocket-Protocol: json' ) ; }
this . webSocketServer . handleUpgrade ( request , socket , head , this . upgradeCallback ) ;
} ) ;
this . webSocketServer . on ( 'connection' , this . mountEventsOnConnection ) ;
this . webSocketServer . on ( 'close' , ( ) = > clearInterval ( this . pingInterval ) ) ;
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : this.common.selectedNode , level : 'INFO' , fileName : 'WebSocketServer' , msg : 'Websocket Server Connected' } ) ;
2022-08-11 02:17:43 -07:00
} ;
2021-12-29 18:08:41 -05:00
public upgradeCallback = ( websocket , request ) = > {
this . webSocketServer . emit ( 'connection' , websocket , request ) ;
} ;
public mountEventsOnConnection = ( websocket , request ) = > {
2022-08-17 15:48:04 -07:00
const protocols = ! request . headers [ 'sec-websocket-protocol' ] ? [ ] : request . headers [ 'sec-websocket-protocol' ] . split ( ',' ) ? . map ( ( s ) = > s . trim ( ) ) ;
2022-09-21 17:19:25 -07:00
const cookies = request . headers . cookie ? parse ( request . headers . cookie ) : null ;
2021-12-29 18:08:41 -05:00
websocket . clientId = Date . now ( ) ;
websocket . isAlive = true ;
2022-09-21 17:19:25 -07:00
websocket . sessionId = cookies && cookies [ 'connect.sid' ] ? cookieParser . signedCookie ( cookies [ 'connect.sid' ] , this . common . secret_key ) : null ;
2021-12-29 18:08:41 -05:00
websocket . clientNodeIndex = + protocols [ 1 ] ;
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : this.common.selectedNode , level : 'INFO' , fileName : 'WebSocketServer' , msg : 'Connected: ' + websocket . clientId + ', Total WS clients: ' + this . webSocketServer . clients . size } ) ;
2021-12-29 18:08:41 -05:00
websocket . on ( 'error' , this . sendErrorToAllLNClients ) ;
websocket . on ( 'message' , this . sendEventsToAllLNClients ) ;
websocket . on ( 'pong' , ( ) = > { websocket . isAlive = true ; } ) ;
websocket . on ( 'close' , ( code , reason ) = > {
this . updateLNWSClientDetails ( websocket . sessionId , - 1 , websocket . clientNodeIndex ) ;
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : this.common.selectedNode , level : 'INFO' , fileName : 'WebSocketServer' , msg : 'Disconnected due to ' + code + ' : ' + websocket . clientId + ', Total WS clients: ' + this . webSocketServer . clients . size } ) ;
2021-12-29 18:08:41 -05:00
} ) ;
} ;
public updateLNWSClientDetails = ( sessionId : string , currNodeIndex : number , prevNodeIndex : number ) = > {
if ( prevNodeIndex >= 0 && currNodeIndex >= 0 ) {
this . webSocketServer . clients . forEach ( ( client ) = > {
if ( client . sessionId === sessionId ) {
client . clientNodeIndex = currNodeIndex ;
}
} ) ;
this . disconnectFromNodeClient ( sessionId , prevNodeIndex ) ;
this . connectToNodeClient ( sessionId , currNodeIndex ) ;
} else if ( prevNodeIndex >= 0 && currNodeIndex < 0 ) {
this . disconnectFromNodeClient ( sessionId , prevNodeIndex ) ;
} else if ( prevNodeIndex < 0 && currNodeIndex >= 0 ) {
this . connectToNodeClient ( sessionId , currNodeIndex ) ;
} else {
const selectedNode = this . common . findNode ( currNodeIndex ) ;
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : ! selectedNode ? this . common.selectedNode : selectedNode , level : 'ERROR' , fileName : 'WebSocketServer' , msg : 'Invalid Node Selection. Previous and current node indices can not be less than zero.' } ) ;
2021-12-29 18:08:41 -05:00
}
2022-08-11 02:17:43 -07:00
} ;
2021-12-29 18:08:41 -05:00
public disconnectFromNodeClient = ( sessionId : string , prevNodeIndex : number ) = > {
const foundClient = this . clientDetails . find ( ( clientDetail ) = > clientDetail . index === + prevNodeIndex ) ;
if ( foundClient ) {
const foundSessionIdx = foundClient . sessionIds . findIndex ( ( sid ) = > sid === sessionId ) ;
if ( foundSessionIdx > - 1 ) {
foundClient . sessionIds . splice ( foundSessionIdx , 1 ) ;
}
if ( foundClient . sessionIds . length === 0 ) {
const foundClientIdx = this . clientDetails . findIndex ( ( clientDetail ) = > clientDetail . index === + prevNodeIndex ) ;
this . clientDetails . splice ( foundClientIdx , 1 ) ;
const prevSelectedNode = this . common . findNode ( prevNodeIndex ) ;
2024-06-10 12:40:37 -07:00
if ( prevSelectedNode && prevSelectedNode . lnImplementation ) {
switch ( prevSelectedNode . lnImplementation ) {
2021-12-29 18:08:41 -05:00
case 'LND' :
this . eventEmitterLND . emit ( 'DISCONNECT' , prevNodeIndex ) ;
break ;
2022-05-01 13:35:20 -04:00
case 'CLN' :
this . eventEmitterCLN . emit ( 'DISCONNECT' , prevNodeIndex ) ;
2021-12-29 18:08:41 -05:00
break ;
case 'ECL' :
this . eventEmitterECL . emit ( 'DISCONNECT' , prevNodeIndex ) ;
break ;
default :
break ;
}
}
}
}
2022-08-11 02:17:43 -07:00
} ;
2021-12-29 18:08:41 -05:00
public connectToNodeClient = ( sessionId : string , currNodeIndex : number ) = > {
let foundClient = this . clientDetails . find ( ( clientDetail ) = > clientDetail . index === + currNodeIndex ) ;
if ( foundClient ) {
const foundSessionIdx = foundClient . sessionIds . findIndex ( ( sid ) = > sid === sessionId ) ;
if ( foundSessionIdx < 0 ) {
foundClient . sessionIds . push ( sessionId ) ;
}
} else {
const currSelectedNode = this . common . findNode ( currNodeIndex ) ;
foundClient = { index : currNodeIndex , sessionIds : [ sessionId ] } ;
this . clientDetails . push ( foundClient ) ;
2024-06-10 12:40:37 -07:00
if ( currSelectedNode && currSelectedNode . lnImplementation ) {
switch ( currSelectedNode . lnImplementation ) {
2021-12-29 18:08:41 -05:00
case 'LND' :
this . eventEmitterLND . emit ( 'CONNECT' , currNodeIndex ) ;
break ;
2022-05-01 13:35:20 -04:00
case 'CLN' :
this . eventEmitterCLN . emit ( 'CONNECT' , currNodeIndex ) ;
2021-12-29 18:08:41 -05:00
break ;
case 'ECL' :
this . eventEmitterECL . emit ( 'CONNECT' , currNodeIndex ) ;
break ;
default :
break ;
}
}
}
2022-08-11 02:17:43 -07:00
} ;
2021-12-29 18:08:41 -05:00
2024-06-10 12:40:37 -07:00
public sendErrorToAllLNClients = ( serverError , selectedNode : SelectedNode ) = > {
2021-12-29 18:08:41 -05:00
try {
this . webSocketServer . clients . forEach ( ( client ) = > {
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : ! selectedNode ? this . common.selectedNode : selectedNode , level : 'ERROR' , fileName : 'WebSocketServer' , msg : 'Broadcasting error to clients...: ' + serverError } ) ;
2021-12-29 18:08:41 -05:00
if ( + client . clientNodeIndex === + selectedNode . index ) {
client . send ( serverError ) ;
}
} ) ;
} catch ( err ) {
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : ! selectedNode ? this . common.selectedNode : selectedNode , level : 'ERROR' , fileName : 'WebSocketServer' , msg : 'Error while broadcasting message: ' + JSON . stringify ( err ) } ) ;
2021-12-29 18:08:41 -05:00
}
} ;
2024-06-10 12:40:37 -07:00
public sendEventsToAllLNClients = ( newMessage , selectedNode : SelectedNode ) = > {
2021-12-29 18:08:41 -05:00
try {
this . webSocketServer . clients . forEach ( ( client ) = > {
if ( + client . clientNodeIndex === + selectedNode . index ) {
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : ! selectedNode ? this . common.selectedNode : selectedNode , level : 'DEBUG' , fileName : 'WebSocketServer' , msg : 'Broadcasting message to client...: ' + client . clientId + ', Message: ' + newMessage } ) ;
2021-12-29 18:08:41 -05:00
client . send ( newMessage ) ;
}
} ) ;
} catch ( err ) {
2024-06-10 12:40:37 -07:00
this . logger . log ( { selectedNode : ! selectedNode ? this . common.selectedNode : selectedNode , level : 'ERROR' , fileName : 'WebSocketServer' , msg : 'Error while broadcasting message: ' + JSON . stringify ( err ) } ) ;
2021-12-29 18:08:41 -05:00
}
} ;
2022-11-29 13:30:58 -08:00
public generateAcceptValue = ( acceptKey ) = > crypto . createHash ( 'sha1' ) . update ( acceptKey + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' , 'binary' ) . digest ( 'base64' ) ;
2021-12-29 18:08:41 -05:00
public getClients = ( ) = > this . webSocketServer . clients ;
}
2022-08-09 23:27:55 -07:00
export const WSServer = new RTLWebSocketServer ( ) ;
2021-12-29 18:08:41 -05:00