packageDocumentation import mkdirp from 'mkdirp' ; import process from 'process' ; import net from 'net' ; import _ from 'lodash' ; import { EventEmitter } from 'tsee' ; import { Logger , prependName } from './logging' ; import { Service , ServiceExitStatus , ServiceStatus , StartService , setupService , serviceExitStatusMessage , defaultTimeoutSeconds , } from './service' ; import { DirPath , passthroughErrorLogger , ignorePromiseRejection , } from './common' ; import { LaunchConfig , cardanoWalletStartService , WalletStartService , } from './cardanoWallet' ; import { startCardanoNode } from './cardanoNode' ; import Signals = NodeJS . Signals ; export { ServiceStatus , ServiceExitStatus , serviceExitStatusMessage , Service , } from './service' ; export { LaunchConfig } from './cardanoWallet' ; /*******************************************************************************
 * Api
 ******************************************************************************/ export interface RequestParams { port : number ; path : string ; hostname : string ; protocol : string ; } /**
 * Connection parameters for the `cardano-wallet` API.
 * These should be used to build the HTTP requests.
 */ export interface Api { /**
     * API base URL, including trailling slash.
     */ baseUrl : string ; /**
     * URL components which can be used with the HTTP client library of
     * your choice.
     */ requestParams : RequestParams ; } class V2Api implements Api { /** URL of the API, including a trailing slash. */ readonly baseUrl : string ; /** URL components which can be used with the HTTP client library of
     * your choice. */ readonly requestParams : RequestParams ; constructor ( port , protocol = 'http:' ) { const hostname = '127.0.0.1' ; const path = '/v2/' ; this . baseUrl = `${ protocol }//${ hostname }:${ port }${ path }` ; this . requestParams = { port , path , hostname , protocol } ; } } /*******************************************************************************
 * Exit status types
 ******************************************************************************/ /**
 * The result after the launched wallet backend has finished.
 */ export interface ExitStatus { wallet : ServiceExitStatus ; node : ServiceExitStatus ; } /**
 * Format an [[ExitStatus]] as a multiline human-readable string.
 */ export function exitStatusMessage ( status ) { return _ . map ( status , serviceExitStatusMessage ) . join ( '\n' ) ; } /**
 * This instance of [[Error]] will be returned when the
 * `Launcher.start()` promise is rejected.
 */ export class BackendExitedError extends Error { status : ExitStatus ; constructor ( status ) { super ( exitStatusMessage ( status ) ) ; Object . setPrototypeOf ( this , new . target . prototype ) ; this . status = status ; } } function noop ( ) { } /*******************************************************************************
 * Launching
 ******************************************************************************/ /**
 * This is the main object which controls the launched wallet backend
 * and its node.
 *
 * Example:
 *
 * ```javascript
 * var launcher = new cardanoLauncher.Launcher({
 *   networkName: "mainnet",
 *   stateDir: "/tmp/state-launcher",
 *   nodeConfig: {
 *     kind: "shelley",
 *     configurationDir: "/home/user/cardano-node/configuration/defaults/mainnet",
 *     network: {
 *       configFile: "configuration.yaml",
 *       topologyFile: "topology.json"
 *     }
 *   }
 *   childProcessLogWriteStream: fs.createWriteStream('./logs')
 * });
 * ```
 *
 * Initially, the backend is not started. Use [[Launcher.start]] for that.
 */ export class Launcher { /**
     * Use this attribute to monitor and control the `cardano-wallet` process.
     */ readonly walletService : Service ; /**
     * Use this to access the `cardano-wallet` API server.
     */ readonly walletBackend : WalletBackend ; /**
     * Use this to monitor the `cardano-node` process.
     */ readonly nodeService : Service ; /** Logging adapter */ protected logger : Logger ; /** Wallet API server port - set once it's known. */ private apiPort = 0 ; /** A state flag for whether the backend services have exited yet. */ private exited = false ; /** Removes process signal handlers, if they were installed. */ private cleanupSignalHandlers : ( ) => void = noop ; param config param logger constructor ( config , logger = console ) { logger . debug ( 'Launcher init' ) ; const { childProcessLogWriteStreams , installSignalHandlers = true } = config ; this . logger = logger ; const start = Launcher . makeServiceCommands ( config , logger ) ; this . walletService = setupService ( start . wallet , prependName ( logger , 'wallet' ) , childProcessLogWriteStreams ?. wallet ) ; this . nodeService = setupService ( start . node , prependName ( logger , 'node' ) , childProcessLogWriteStreams ?. node ) ; this . walletBackend = new WalletBackend ( new V2Api ( this . apiPort , config . tlsConfiguration !== undefined ? 'https:' : 'http:' ) ) ; start . wallet . then ( ( startService ) => { this . apiPort = startService . apiPort ; this . walletBackend . api = new V2Api ( this . apiPort , config . tlsConfiguration !== undefined ? 'https:' : 'http:' ) ; } ) . catch ( passthroughErrorLogger ) ; this . walletService . events . on ( 'statusChanged' , status => { if ( status === ServiceStatus . Stopped ) { this . logger . debug ( 'wallet exited' ) ; this . stop ( ) . catch ( passthroughErrorLogger ) ; } } ) ; this . nodeService . events . on ( 'statusChanged' , status => { if ( status === ServiceStatus . Stopped ) { this . logger . debug ( 'node exited' ) ; this . stop ( ) . catch ( passthroughErrorLogger ) ; } } ) ; if ( installSignalHandlers ) this . installSignalHandlers ( ) ; } return start ( ) { const stopWaiting = ( ) : boolean => this . nodeService . getStatus ( ) > ServiceStatus . Started || this . walletService . getStatus ( ) > ServiceStatus . Started ; return new Promise ( ( resolve , reject ) => { this . nodeService . start ( ) . catch ( ignorePromiseRejection ) ; this . walletService . start ( ) . catch ( ignorePromiseRejection ) ; this . waitForApi ( stopWaiting , ( ) => { this . walletBackend . events . ready ( this . walletBackend . api ) ; } ) ; this . walletBackend . events . on ( 'ready' , resolve ) ; this . walletBackend . events . on ( 'exit' , st => reject ( new BackendExitedError ( st ) ) ) ; } ) ; } param stop param ready private waitForApi ( stop , ready ) { this . logger . debug ( 'waitForApi' ) ; let addr ; let client ; const timer = setInterval ( ( ) => { if ( stop ( ) ) { clearInterval ( timer ) ; } else if ( this . apiPort ) { if ( ! addr ) { addr = { port : this . apiPort , host : '127.0.0.1' } ; this . logger . info ( `Waiting for tcp port ${ addr . host }:${ addr . port } to accept connections...` ) ; } if ( client ) { client . destroy ( ) ; } client = new net . Socket ( ) ; client . connect ( addr , ( ) => { this . logger . info ( `... port is ready.` ) ; clearInterval ( timer ) ; ready ( ) ; } ) ; client . on ( 'error' , err => { this . logger . debug ( `waitForApi: not ready yet: ${ err }` ) ; } ) ; } } , 250 ) ; } param timeoutSeconds return event stop ( timeoutSeconds = defaultTimeoutSeconds ) { this . logger . debug ( `Launcher.stop: stopping wallet and node` ) ; return Promise . all ( [ this . walletService . stop ( timeoutSeconds ) , this . nodeService . stop ( timeoutSeconds ) , ] ) . then ( ( [ wallet , node ] ) => { const status = { wallet , node } ; this . logger . debug ( `Launcher.stop: both services are stopped.` , status ) ; if ( ! this . exited ) { this . walletBackend . events . exit ( status ) ; this . exited = true ; } this . cleanupSignalHandlers ( ) ; return status ; } ) ; } /**
     * Stop services when this process gets killed.
     */ private installSignalHandlers ( ) { const signals = [ 'SIGINT' , 'SIGTERM' , 'SIGHUP' , 'SIGBREAK' ] ; const handler = ( signal ) : void => { this . logger . info ( `Received ${ signal } - stopping services...` ) ; this . walletService . stop ( 0 ) . catch ( passthroughErrorLogger ) ; this . nodeService . stop ( 0 ) . catch ( passthroughErrorLogger ) ; } ; signals . forEach ( signal => process . on ( signal , handler ) ) ; this . cleanupSignalHandlers = ( ) : void => { signals . forEach ( signal => process . off ( signal , handler ) ) ; this . cleanupSignalHandlers = noop ; } ; } private static makeServiceCommands ( config , logger ) { logger . info ( `Creating state directory ${ config . stateDir } (if it doesn't already exist)` ) ; const node = mkdirp ( config . stateDir ) . then ( ( ) => Launcher . nodeExe ( config . stateDir , config ) ) ; const wallet = node . then ( ( ) => Launcher . walletExe ( config . stateDir , config ) ) ; return { wallet , node } ; } private static async walletExe ( baseDir , config ) { return cardanoWalletStartService ( baseDir , config ) ; } private static nodeExe ( baseDir , config ) { switch ( config . nodeConfig . kind ) { case 'shelley' : return startCardanoNode ( baseDir , config . nodeConfig , config . networkName ) ; } } } /**
 * Represents the API service of `cardano-wallet`.
 */ export class WalletBackend { api : Api ; constructor ( api ) { this . api = api ; this . events = new WalletBackendEvents ( ) ; } deprecated return getApi ( ) { return this . api ; } /**
     * An [`EventEmitter`](https://nodejs.org/api/events.html#class-eventemitter)
     * that can be used to register handlers when
     * the process changes status.
     *
     * ### Example
     * ```typescript
     * launcher.walletBackend.events.on('ready', (api: Api) => { ... });
     * launcher.walletBackend.events.on('exit', (status: ExitStatus) => { ... });
     * ```
     */ events : WalletBackendEvents ; } /**
 * The type of events for [[WalletBackend]].
 */ export class WalletBackendEvents extends EventEmitter < { ready : ( api : Api ) => void ; exit : ( status : ExitStatus ) => void ; } > { ignore constructor ( ) { super ( ) ; } param api event ready ( api ) { this . emit ( 'ready' , api ) ; } param stats event exit ( status ) { this . emit ( 'exit' , status ) ; } }