import { EventEmitter } from 'events' ; import { CustomError } from './constants/errors' ; import { MESSAGES , RESPONSES } from './constants' ; import { create as createDeferred , Deferred } from './utils/deferred' ; import { BlockchainSettings } from './types' ; import * as ResponseTypes from './types/responses' ; import * as MessageTypes from './types/messages' ; import { Events } from './types/events' ; const workerWrapper = ( factory ) : Worker => { if ( typeof factory === 'function' ) return factory ( ) ; if ( typeof factory === 'string' && typeof Worker !== 'undefined' ) return new Worker ( factory ) ; throw new CustomError ( 'worker_not_found' ) ; } ; const initWorker = ( { worker : factory , ... settings } ) => { const dfd = createDeferred ( - 1 ) ; const worker = workerWrapper ( factory ) ; if ( typeof worker !== 'object' || typeof worker . postMessage !== 'function' ) { throw new CustomError ( 'worker_invalid' ) ; } const timeout = setTimeout ( ( ) => { worker . onmessage = null ; worker . onerror = null ; dfd . reject ( new CustomError ( 'worker_timeout' ) ) ; } , settings . timeout || 30000 ) ; worker . onmessage = ( message ) => { if ( message . data . type !== MESSAGES . HANDSHAKE ) return ; clearTimeout ( timeout ) ; worker . postMessage ( { type : MESSAGES . HANDSHAKE , settings , } ) ; dfd . resolve ( worker ) ; } ; worker . onerror = ( error ) => { clearTimeout ( timeout ) ; worker . onmessage = null ; worker . onerror = null ; try { worker . terminate ( ) ; } catch ( error ) { } const message = error . message ? `Worker runtime error: Line ${ error . lineno } in ${ error . filename }: ${ error . message }` : 'Worker handshake error' ; dfd . reject ( new CustomError ( 'worker_runtime' , message ) ) ; } ; return dfd . promise ; } ; declare interface BlockchainLink { on < K extends keyof Events > ( type , listener ) : this ; off < K extends keyof Events > ( type , listener ) : this ; emit < K extends keyof Events > ( type , ... args ) : boolean ; } class BlockchainLink extends EventEmitter { settings : BlockchainSettings ; messageId = 0 ; worker : Worker | undefined ; deferred : Deferred < any > [ ] = [ ] ; constructor ( settings ) { super ( ) ; this . settings = settings ; } async getWorker ( ) { if ( ! this . worker ) { this . worker = await initWorker ( this . settings ) ; this . worker . onmessage = this . onMessage . bind ( this ) ; this . worker . onerror = this . onError . bind ( this ) ; } return this . worker ; } async sendMessage < R > ( message ) { const worker = await this . getWorker ( ) ; const dfd = createDeferred ( this . messageId ) ; this . deferred . push ( dfd ) ; worker . postMessage ( { id : this . messageId , ... message } ) ; this . messageId ++ ; return dfd . promise as Promise < R > ; } connect ( ) { return this . sendMessage ( { type : MESSAGES . CONNECT , } ) ; } /** Get general information about the network, like current block-height, name, smallest possible division, etc. */ getInfo ( ) { return this . sendMessage ( { type : MESSAGES . GET_INFO , } ) ; } /** Get hash of a block of given height. */ getBlockHash ( payload ) { return this . sendMessage ( { type : MESSAGES . GET_BLOCK_HASH , payload , } ) ; } /** Get info about an account, eg. derived addresses, balance, transaction history etc. */ getAccountInfo ( payload ) { return this . sendMessage ( { type : MESSAGES . GET_ACCOUNT_INFO , payload , } ) ; } /** Get unspent inputs for given account. Works only for bitcoin(like) networks. */ getAccountUtxo ( payload ) { return this . sendMessage ( { type : MESSAGES . GET_ACCOUNT_UTXO , payload , } ) ; } /**
     * Get info about a given transaction.
     * Return value of this method is not identical across networks.
     * this method exposes raw response from the BE.
     */ getTransaction ( payload ) { return this . sendMessage ( { type : MESSAGES . GET_TRANSACTION , payload , } ) ; } /**
     * Get historical progression of given account's balance
     * Used for rendering a graph in Suite's dashboard.
     */ getAccountBalanceHistory ( payload ) { return this . sendMessage ( { type : MESSAGES . GET_ACCOUNT_BALANCE_HISTORY , payload , } ) ; } getCurrentFiatRates ( payload ) { return this . sendMessage ( { type : MESSAGES . GET_CURRENT_FIAT_RATES , payload , } ) ; } /** Get historical fiat rates. Only some backends support this. */ getFiatRatesForTimestamps ( payload ) { return this . sendMessage ( { type : MESSAGES . GET_FIAT_RATES_FOR_TIMESTAMPS , payload , } ) ; } /** Get fiat currencies for which rates are available. */ getFiatRatesTickersList ( payload ) { return this . sendMessage ( { type : MESSAGES . GET_FIAT_RATES_TICKERS_LIST , payload , } ) ; } /** Get ‘recommended’ fee value inferred from current traffic in the network. */ estimateFee ( payload ) { return this . sendMessage ( { type : MESSAGES . ESTIMATE_FEE , payload , } ) ; } /**
     * Subscribe for live changes in
     * - blockchain i.e new blocks mined.
     * - accounts, addresses i.e. new transactions broadcasted or mined.
     * - fiatRates
     * - connection to BE (CONNECT, DISCONNECT)
     *
     * Handling subscription state is left to the user. Hence the purpose of CONNECT and DISCONNECT notifications.
     */ subscribe ( payload ) { return this . sendMessage ( { type : MESSAGES . SUBSCRIBE , payload , } ) ; } /** Discard a subscription. */ unsubscribe ( payload ) { return this . sendMessage ( { type : MESSAGES . UNSUBSCRIBE , payload , } ) ; } /** Broadcast a transaction to the network. */ pushTransaction ( payload ) { return this . sendMessage ( { type : MESSAGES . PUSH_TRANSACTION , payload , } ) ; } async disconnect ( ) { if ( ! this . worker ) return true ; return this . sendMessage ( { type : MESSAGES . DISCONNECT , } ) ; } onMessage : ( event : { data : ResponseTypes . Response ; } ) => void = event => { if ( ! event . data ) return ; const { data } = event ; if ( data . id === - 1 ) { this . onEvent ( data ) ; return ; } const dfd = this . deferred . find ( d => d . id === data . id ) ; if ( ! dfd ) { return ; } if ( data . type === RESPONSES . ERROR ) { dfd . reject ( new CustomError ( data . payload . code , data . payload . message ) ) ; } else { dfd . resolve ( data . payload ) ; } this . deferred = this . deferred . filter ( d => d !== dfd ) ; } ; onEvent : ( data : ResponseTypes . Response ) => void = data => { if ( data . type === RESPONSES . CONNECTED ) { this . emit ( 'connected' ) ; } if ( data . type === RESPONSES . DISCONNECTED ) { this . emit ( 'disconnected' ) ; } if ( data . type === RESPONSES . NOTIFICATION ) { this . emit ( data . payload . type , data . payload . payload ) ; } } ; onError : ( error : { message ? : string ; lineno : number ; filename : string ; } ) => void = error => { const message = error . message ? `Worker runtime error: Line ${ error . lineno } in ${ error . filename }: ${ error . message }` : 'Worker handshake error' ; const e = new CustomError ( 'worker_runtime' , message ) ; this . deferred . forEach ( d => { d . reject ( e ) ; } ) ; this . deferred = [ ] ; } ; dispose ( ) { this . removeAllListeners ( ) ; const { worker } = this ; if ( worker ) { worker . terminate ( ) ; delete this . worker ; } } } export default BlockchainLink ; export { Message } from './types/messages' ; export { Response } from './types/responses' ; export * from './types/common' ; export * from './types/events' ;