Create CLightningClient class
This commit is contained in:
		
							parent
							
								
									b6ba3c5781
								
							
						
					
					
						commit
						3f83e517f0
					
				
							
								
								
									
										249
									
								
								backend/src/rpc-api/core-lightning/jsonrpc.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										249
									
								
								backend/src/rpc-api/core-lightning/jsonrpc.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,249 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| const methods = [ | ||||
|   'addgossip', | ||||
|   'autocleaninvoice', | ||||
|   'check', | ||||
|   'checkmessage', | ||||
|   'close', | ||||
|   'connect', | ||||
|   'createinvoice', | ||||
|   'createinvoicerequest', | ||||
|   'createoffer', | ||||
|   'createonion', | ||||
|   'decode', | ||||
|   'decodepay', | ||||
|   'delexpiredinvoice', | ||||
|   'delinvoice', | ||||
|   'delpay', | ||||
|   'dev-listaddrs', | ||||
|   'dev-rescan-outputs', | ||||
|   'disableoffer', | ||||
|   'disconnect', | ||||
|   'estimatefees', | ||||
|   'feerates', | ||||
|   'fetchinvoice', | ||||
|   'fundchannel', | ||||
|   'fundchannel_cancel', | ||||
|   'fundchannel_complete', | ||||
|   'fundchannel_start', | ||||
|   'fundpsbt', | ||||
|   'getchaininfo', | ||||
|   'getinfo', | ||||
|   'getlog', | ||||
|   'getrawblockbyheight', | ||||
|   'getroute', | ||||
|   'getsharedsecret', | ||||
|   'getutxout', | ||||
|   'help', | ||||
|   'invoice', | ||||
|   'keysend', | ||||
|   'legacypay', | ||||
|   'listchannels', | ||||
|   'listconfigs', | ||||
|   'listforwards', | ||||
|   'listfunds', | ||||
|   'listinvoices', | ||||
|   'listnodes', | ||||
|   'listoffers', | ||||
|   'listpays', | ||||
|   'listpeers', | ||||
|   'listsendpays', | ||||
|   'listtransactions', | ||||
|   'multifundchannel', | ||||
|   'multiwithdraw', | ||||
|   'newaddr', | ||||
|   'notifications', | ||||
|   'offer', | ||||
|   'offerout', | ||||
|   'openchannel_abort', | ||||
|   'openchannel_bump', | ||||
|   'openchannel_init', | ||||
|   'openchannel_signed', | ||||
|   'openchannel_update', | ||||
|   'pay', | ||||
|   'payersign', | ||||
|   'paystatus', | ||||
|   'ping', | ||||
|   'plugin', | ||||
|   'reserveinputs', | ||||
|   'sendinvoice', | ||||
|   'sendonion', | ||||
|   'sendonionmessage', | ||||
|   'sendpay', | ||||
|   'sendpsbt', | ||||
|   'sendrawtransaction', | ||||
|   'setchannelfee', | ||||
|   'signmessage', | ||||
|   'signpsbt', | ||||
|   'stop', | ||||
|   'txdiscard', | ||||
|   'txprepare', | ||||
|   'txsend', | ||||
|   'unreserveinputs', | ||||
|   'utxopsbt', | ||||
|   'waitanyinvoice', | ||||
|   'waitblockheight', | ||||
|   'waitinvoice', | ||||
|   'waitsendpay', | ||||
|   'withdraw' | ||||
| ]; | ||||
| 
 | ||||
| 
 | ||||
| import EventEmitter from 'events'; | ||||
| import { existsSync, statSync } from 'fs'; | ||||
| import { createConnection, Socket } from 'net'; | ||||
| import { homedir } from 'os'; | ||||
| import path from 'path'; | ||||
| import { createInterface, Interface } from 'readline'; | ||||
| import logger from '../../logger'; | ||||
| 
 | ||||
| class LightningError extends Error { | ||||
|   type: string = 'lightning'; | ||||
|   message: string = 'lightning-client error'; | ||||
| 
 | ||||
|   constructor(error) { | ||||
|     super(); | ||||
|     this.type = error.type; | ||||
|     this.message = error.message; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const defaultRpcPath = path.join(homedir(), '.lightning') | ||||
|   , fStat = (...p) => statSync(path.join(...p)) | ||||
|   , fExists = (...p) => existsSync(path.join(...p)) | ||||
| 
 | ||||
| class CLightningClient extends EventEmitter { | ||||
|   private rpcPath: string; | ||||
|   private reconnectWait: number; | ||||
|   private reconnectTimeout; | ||||
|   private reqcount: number; | ||||
|   private client: Socket; | ||||
|   private rl: Interface; | ||||
|   private clientConnectionPromise: Promise<unknown>; | ||||
| 
 | ||||
|   constructor(rpcPath = defaultRpcPath) { | ||||
|     if (!path.isAbsolute(rpcPath)) { | ||||
|       throw new Error('The rpcPath must be an absolute path'); | ||||
|     } | ||||
| 
 | ||||
|     if (!fExists(rpcPath) || !fStat(rpcPath).isSocket()) { | ||||
|       // network directory provided, use the lightning-rpc within in
 | ||||
|       if (fExists(rpcPath, 'lightning-rpc')) { | ||||
|         rpcPath = path.join(rpcPath, 'lightning-rpc'); | ||||
|       } | ||||
| 
 | ||||
|       // main data directory provided, default to using the bitcoin mainnet subdirectory
 | ||||
|       // to be removed in v0.2.0
 | ||||
|       else if (fExists(rpcPath, 'bitcoin', 'lightning-rpc')) { | ||||
|         logger.warn(`[CLightningClient] ${rpcPath}/lightning-rpc is missing, using the bitcoin mainnet subdirectory at ${rpcPath}/bitcoin instead.`) | ||||
|         logger.warn(`[CLightningClient] specifying the main lightning data directory is deprecated, please specify the network directory explicitly.\n`) | ||||
|         rpcPath = path.join(rpcPath, 'bitcoin', 'lightning-rpc') | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     logger.debug(`[CLightningClient] Connecting to ${rpcPath}`); | ||||
| 
 | ||||
|     super(); | ||||
|     this.rpcPath = rpcPath; | ||||
|     this.reconnectWait = 0.5; | ||||
|     this.reconnectTimeout = null; | ||||
|     this.reqcount = 0; | ||||
| 
 | ||||
|     const _self = this; | ||||
| 
 | ||||
|     this.client = createConnection(rpcPath); | ||||
|     this.rl = createInterface({ input: this.client }) | ||||
| 
 | ||||
|     this.clientConnectionPromise = new Promise<void>(resolve => { | ||||
|       _self.client.on('connect', () => { | ||||
|         logger.debug(`[CLightningClient] Lightning client connected`); | ||||
|         _self.reconnectWait = 1; | ||||
|         resolve(); | ||||
|       }); | ||||
| 
 | ||||
|       _self.client.on('end', () => { | ||||
|         logger.err('[CLightningClient] Lightning client connection closed, reconnecting'); | ||||
|         _self.increaseWaitTime(); | ||||
|         _self.reconnect(); | ||||
|       }); | ||||
| 
 | ||||
|       _self.client.on('error', error => { | ||||
|         logger.err(`[CLightningClient] Lightning client connection error: ${error}`); | ||||
|         _self.emit('error', error); | ||||
|         _self.increaseWaitTime(); | ||||
|         _self.reconnect(); | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     this.rl.on('line', line => { | ||||
|       line = line.trim(); | ||||
|       if (!line) { | ||||
|         return; | ||||
|       } | ||||
|       const data = JSON.parse(line); | ||||
|       logger.debug(`[CLightningClient] #${data.id} <-- ${JSON.stringify(data.error || data.result)}`); | ||||
|       _self.emit('res:' + data.id, data); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   increaseWaitTime(): void { | ||||
|     if (this.reconnectWait >= 16) { | ||||
|       this.reconnectWait = 16; | ||||
|     } else { | ||||
|       this.reconnectWait *= 2; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   reconnect(): void { | ||||
|     const _self = this; | ||||
| 
 | ||||
|     if (this.reconnectTimeout) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     this.reconnectTimeout = setTimeout(() => { | ||||
|       logger.debug('[CLightningClient] Trying to reconnect...'); | ||||
| 
 | ||||
|       _self.client.connect(_self.rpcPath); | ||||
|       _self.reconnectTimeout = null; | ||||
|     }, this.reconnectWait * 1000); | ||||
|   } | ||||
| 
 | ||||
|   call(method, args = []): Promise<unknown> { | ||||
|     const _self = this; | ||||
| 
 | ||||
|     const callInt = ++this.reqcount; | ||||
|     const sendObj = { | ||||
|       jsonrpc: '2.0', | ||||
|       method, | ||||
|       params: args, | ||||
|       id: '' + callInt | ||||
|     }; | ||||
| 
 | ||||
|     logger.debug(`[CLightningClient] #${callInt} --> ${method} ${args}`); | ||||
| 
 | ||||
|     // Wait for the client to connect
 | ||||
|     return this.clientConnectionPromise | ||||
|       .then(() => new Promise((resolve, reject) => { | ||||
|         // Wait for a response
 | ||||
|         this.once('res:' + callInt, res => res.error == null | ||||
|           ? resolve(res.result) | ||||
|           : reject(new LightningError(res.error)) | ||||
|         ); | ||||
| 
 | ||||
|         // Send the command
 | ||||
|         _self.client.write(JSON.stringify(sendObj)); | ||||
|       })); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const protify = s => s.replace(/-([a-z])/g, m => m[1].toUpperCase()); | ||||
| 
 | ||||
| methods.forEach(k => { | ||||
|   CLightningClient.prototype[protify(k)] = function (...args: any) { | ||||
|     return this.call(k, args); | ||||
|   }; | ||||
| }); | ||||
| 
 | ||||
| export default new CLightningClient(); | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user