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