import { DecodedLogEntryEvent , LogTopic } from "web3" ; import { fnVoid , Hash } from "./commonTypes" ; import { IEventSubscription , PubSubEventService } from "./pubSubEventService" ; import { TransactionService } from "./transactionService" ; import { Utils } from "./utils" ; import { UtilsInternal } from "./utilsInternal" ; /**
 * Support for working with events that originate from Arc contracts
 * and are served up by Web3.
 *
 * For more information, see [Web3 Events](/Events.md#web3events).
 */ export class Web3EventService { param baseEvent param preProcessEvent param baseArgFilter type TEventArgs public createEventFetcherFactory < TEventArgs > ( baseEvent , preProcessEvent ? , baseArgFilter = { } ) { if ( ! baseEvent ) { throw new Error ( "baseEvent was not supplied" ) ; } /**
         * This is the function that returns the EventFetcher<TEventArgs>
         * argFilter - Optional event argument filter, like `{ _proposalId: [someHash] }`.
         * filterObject - Optional event filter.  Default is `{ fromBlock: "latest" }`
         * callback.
         * immediateWatchCallback - when supplied, starts watch immediately
         * immediateRequiredDepth - only used when immediateWatchCallback is supplied. If set
         * then will not invoke the callback until the transaction has been mined to the requiredDepth.
         */ return ( argFilter = { } , filterObject = { } , immediateWatchCallback ? , immediateRequiredDepth = 0 ) : EventFetcher < TEventArgs > => { const handleEvent = this . createBaseWeb3EventHandler ( filterObject . suppressDups , preProcessEvent ) ; const baseFetcher = baseEvent ( Object . assign ( argFilter , baseArgFilter ) , filterObject ) ; /**
             * If `immediateWatchCallback` is defined then we should start watching immediately.
             */ if ( immediateWatchCallback ) { baseFetcher . watch ( async ( error , log ) => { await handleEvent ( error , log , true , immediateWatchCallback , immediateRequiredDepth ) ; } ) ; } /**
             * return the fetcher
             */ return { get ( callback ? , requiredDepth = 0 ) { return new Promise < Array < DecodedLogEntryEvent < TEventArgs > > > ( ( resolve , reject ) : void => { baseFetcher . get ( async ( error , log ) : Promise < void > => { if ( error ) { return reject ( error ) ; } resolve ( await handleEvent ( error , log , false , callback , requiredDepth ) ) ; } ) ; } ) ; } , watch ( callback , requiredDepth = 0 ) { baseFetcher . watch ( async ( error , log ) => { await handleEvent ( error , log , true , callback , requiredDepth ) ; } ) ; } , subscribe ( eventName , callback ? , requiredDepth = 0 ) { if ( ! callback ) { callback = ( ) : void => { } ; } const subscription = PubSubEventService . subscribe ( eventName , callback ) ; this . watch ( ( error , args ) => { PubSubEventService . publish ( eventName , args ) ; } , requiredDepth ) ; return new Web3EventSubscription ( subscription , baseFetcher ) ; } , stopWatching ( callback ? ) { baseFetcher . stopWatching ( callback ) ; } , stopWatchingAsync ( ) { return UtilsInternal . stopWatchingAsync ( baseFetcher ) ; } , } ; } ; } param eventFetcherFactory param transformEventCallback param givenCallback param baseArgFilter public createEntityFetcherFactory < TEntity , TEventArgs > ( eventFetcherFactory , transformEventCallback , baseArgFilter = { } ) { if ( ! eventFetcherFactory ) { throw new Error ( "eventFetcherFactory was not supplied" ) ; } if ( ! transformEventCallback ) { throw new Error ( "transformEventCallback was not supplied" ) ; } /**
         * This is the function that returns the EntityFetcher<TEventArgs>
         * argFilter - Optional event argument filter, like `{ _proposalId: [someHash] }`.
         * filterObject - Optional event filter.  Default is `{ fromBlock: "latest" }`
         * callback.
         * immediateWatchCallback - when supplied, starts watch immediately
         * immediateRequiredDepth - only used when immediateWatchCallback is supplied. If set
         * then will not invoke the callback until the transaction has been mined to the requiredDepth.
         */ return ( argFilter = { } , filterObject = { } , immediateWatchCallback ? , immediateRequiredDepth = 0 ) : EntityFetcher < TEntity , TEventArgs > => { const handleEvent = async ( error , log , singly , callback ? ) : Promise < Array < TEntity > > => { const promiseOfEntities = new Promise ( async ( resolve , reject ) : Promise < void > => { if ( error ) { return reject ( error ) ; } if ( ! Array . isArray ( log ) ) { log = [ log ] ; } const entities = new Array < TEntity > ( ) ; for ( const event of log ) { const transformedEntity = await transformEventCallback ( event ) ; if ( typeof transformedEntity !== "undefined" ) { if ( callback && singly ) { await callback ( error , transformedEntity ) ; } entities . push ( transformedEntity ) ; } } resolve ( entities ) ; } ) ; if ( callback && ! singly ) { await callback ( error , promiseOfEntities ) ; } return promiseOfEntities ; } ; const baseFetcher = eventFetcherFactory ( Object . assign ( argFilter , baseArgFilter ) , filterObject ) ; /**
             * If `immediateWatchCallback` is defined then we should start watching immediately.
             */ if ( immediateWatchCallback ) { baseFetcher . watch ( ( error , log ) => { handleEvent ( error , log , true , immediateWatchCallback ) ; } , immediateRequiredDepth ) ; } /**
             * return the fetcher
             */ return { get ( callback ? , requiredDepth = 0 ) { return new Promise < Array < TEntity > > ( ( resolve , reject ) : void => { baseFetcher . get ( async ( error , log ) : Promise < void > => { if ( error ) { return reject ( error ) ; } resolve ( await handleEvent ( error , log , false , callback ) ) ; } , requiredDepth ) ; } ) ; } , watch ( callback ? , requiredDepth = 0 ) { baseFetcher . watch ( ( error , log ) => { handleEvent ( error , log , true , callback ) ; } , requiredDepth ) ; } , subscribe ( eventName , callback ? , requiredDepth = 0 ) { if ( ! callback ) { callback = ( ) : void => { } ; } const subscription = PubSubEventService . subscribe ( eventName , callback ) ; this . watch ( ( error , entity ) => { PubSubEventService . publish ( eventName , entity ) ; } , requiredDepth ) ; return new Web3EventSubscription ( subscription , baseFetcher ) ; } , stopWatching ( callback ? ) { baseFetcher . stopWatching ( callback ) ; } , stopWatchingAsync ( ) { return UtilsInternal . stopWatchingAsync ( baseFetcher ) ; } , } ; } ; } param suppressDups param preProcessEvent private createBaseWeb3EventHandler < TEventArgs > ( suppressDups , preProcessEvent ? ) { let receivedEvents ; if ( ! ! suppressDups ) { receivedEvents = new Set < Hash > ( ) ; } return async ( error , log , singly , callback ? , requiredDepth = 0 ) : Promise < Array < DecodedLogEntryEvent < TEventArgs > > > => { /**
             * convert to an array
             */ if ( ! ! error ) { log = [ ] ; } else if ( ! Array . isArray ( log ) ) { log = [ log ] ; } /**
             * optionally prune duplicate events (see https://github.com/ethereum/web3.js/issues/398)
             */ if ( receivedEvents && log . length ) { log = log . filter ( ( evt ) => { if ( ! receivedEvents . has ( evt . transactionHash ) ) { receivedEvents . add ( evt . transactionHash ) ; return true ; } else { return false ; } } ) ; } if ( preProcessEvent ) { const processedResult = preProcessEvent ( error , log ) ; error = processedResult . error ; log = processedResult . log ; } if ( callback ) { for ( const e of log ) { if ( requiredDepth ) { if ( requiredDepth === - 1 ) { requiredDepth = undefined ; } await TransactionService . watchForConfirmedTransaction ( e . transactionHash , null , requiredDepth ) ; } if ( singly ) { callback ( error , e ) ; } } if ( ! singly ) { callback ( error , log ) ; } } return log ; } ; } } export interface EventPreProcessorReturn < TEventArgs > { error : Error ; log : Array < DecodedLogEntryEvent < TEventArgs > > ; } export type PreProcessEventCallback < TEventArgs > = ( error : Error , log : Array < DecodedLogEntryEvent < TEventArgs > > ) => EventPreProcessorReturn < TEventArgs > ; export type TransformEventCallback < TDest , TSrc > = ( event : DecodedLogEntryEvent < TSrc > ) => Promise < TDest | undefined > ; type TEntity export type EntityFetcherFactory < TDest , TSrc > = ( /**
 * Arg values by which you wish to filter the web3 event logs, e.g.
 * `{'valueA': 1, 'valueB': [myFirstAddress, mySecondAddress]}`.
 *
 * Note this always applies to the underlying web3 event values
 * not to property values of transformed entities.
 */ argsFilter ? : any , /**
 * Web3 event filter options.  Typically something like `{ fromBlock: 0 }`.
 * Note if you don't want Arc.js to suppress duplicate events, set `suppressDups` to false.
 */ filterObject ? : EventFetcherFilterObject , /**
 * Optional callback to immediately start start watching.
 * Without this you will call `get` or `watch`.
 */ callback ? : EntityWatchCallback < TDest > , /**
 * Optional and only used when callback is supplied. If set
 * then will not invoke the callback until the transaction has been mined to the requiredDepth.
 */ requiredDepth ? : number ) => EntityFetcher < TDest , TSrc > ; export type EntityWatchCallback < TEntity > = ( error : Error , entity : TEntity ) => void ; export type EntityGetCallback < TEntity > = ( error : Error , entity : Promise < Array < TEntity > > ) => void ; export type EntityWatchSubscriptionCallback < TEntity > = ( eventName : string , payload : TEntity ) => void ; /**
 * Returned by EntityFetcherFactory<TDest, TSrc>.
 */ export interface EntityFetcher < TDest , TSrc > { /**
     * Get an array of `TDest` from Web3, given the filter supplied to the EntityFetcherFactory.
     * You may supply a callback, which will be given the array, or you may
     * accept the promise of the array from the return value of `get`.
     * If `requiredDepth` is set then will not invoke the callback until the transaction has been mined to
     * the requiredDepth.
     */ get : ( callback ? : EntityGetCallback < TDest > , requiredDepth ? : number ) => Promise < Array < TDest > > ; /**
     * Watch for `TDest`s from Web3, given the filter supplied to the EntityFetcherFactory.
     * The callback is invoked once per event firing.
     * If `requiredDepth` is set then will not invoke the callback until the transaction has been mined to
     * the requiredDepth.
     */ watch : ( callback : EntityWatchCallback < TDest > , requiredDepth ? : number ) => void ; /**
     * Watch for `TDest`s from Web3, given the filter supplied to the EntityFetcherFactory.
     * The Pub.Sub is published once per event firing.
     * `subscribe` returns the subscription on which you must remember to call `unsubscribe` when you are
     * done watching.
     * If `requiredDepth` is set then will not invoke the callback until the transaction has been mined to
     * the requiredDepth.
     *
     * Supply whatever name you want for `eventName`.  This enables you to scope
     * event handlers across event types and schemes.
     */ subscribe : ( eventName : string , callback ? : EntityWatchSubscriptionCallback < TDest > , requiredDepth ? : number ) => IEventSubscription ; /**
     * Stop watching the event.
     */ stopWatching ( callback ? ) : void ; /**
     * Asynchronously stop watching the event, for environments where
     * synchronous methods are not allowed.
     */ stopWatchingAsync ( ) : Promise < void > ; } type TEventArgs export type EventFetcherFactory < TEventArgs > = ( /**
 * Values by which you wish to filter the logs, e.g.
 * `{'valueA': 1, 'valueB': [myFirstAddress, mySecondAddress]}`.
 */ argsFilter ? : any , /**
 * Additional filter options.  Typically something like `{ fromBlock: 0 }`.
 * Note if you don't want Arc.js to suppress duplicate events, set `suppressDups` to false.
 */ filterObject ? : EventFetcherFilterObject , /**
 * Optional callback to immediately start start watching.
 * Without this you will call `get` or `watch`.
 */ callback ? : EventWatchCallback < TEventArgs > , /**
 * Optional and only used when callback is supplied. If set
 * then will not invoke the callback until the transaction has been mined to the requiredDepth.
 */ requiredDepth ? : number ) => EventFetcher < TEventArgs > ; export type EventWatchCallback < TEventArgs > = ( error : Error , args : DecodedLogEntryEvent < TEventArgs > ) => void ; export type EventGetCallback < TEventArgs > = ( error : Error , args : Array < DecodedLogEntryEvent < TEventArgs > > ) => void ; export type EventWatchSubscriptionCallback < TEventArgs > = ( eventName : string , payload : DecodedLogEntryEvent < TEventArgs > ) => void ; type TEventArgs export interface EventFetcher < TEventArgs > { /**
     * Get an array of `DecodedLogEntryEvent` from Web3, given the filter supplied to the EventFetcherFactory.
     * You may supply a callback, which will be given the array, or you may
     * accept the promise of the array from the return value of `get`.
     * If `requiredDepth` is set then will not invoke the callback until the transaction has been mined to
     * the requiredDepth.
     */ get : ( callback ? : EventGetCallback < TEventArgs > , requiredDepth ? : number ) => Promise < Array < DecodedLogEntryEvent < TEventArgs > > > ; /**
     * Watch for `DecodedLogEntryEvent`s from Web3, given the filter supplied to the EventFetcherFactory.
     * The callback is invoked once per event firing.
     * If `requiredDepth` is set then will not invoke the callback until the transaction has been mined to
     * the requiredDepth.
     */ watch : ( callback : EventWatchCallback < TEventArgs > , requiredDepth ? : number ) => void ; /**
     * Watch for `DecodedLogEntryEvent`s from Web3, given the filter supplied to the EventFetcherFactory.
     * The Pub.Sub is published once per event firing.
     * `subscribe` returns the subscription on which you must remember to call `unsubscribe` when you are
     * done watching.
     * If `requiredDepth` is set then will not invoke the callback until the transaction has been mined to
     * the requiredDepth.
     *
     * Supply whatever name you want for `eventName`.  This enables you to scope
     * event handlers across event types and schemes.
     */ subscribe : ( eventName : string , callback ? : EventWatchSubscriptionCallback < TEventArgs > , requiredDepth ? : number ) => IEventSubscription ; /**
     * Stop watching the event.
     */ stopWatching ( callback ? ) : void ; /**
     * Asynchronously stop watching the event, for environments where
     * synchronous methods are not allowed.
     */ stopWatchingAsync ( ) : Promise < void > ; } /**
 * As implemented by Web3
 */ export interface Web3EventFetcher { get : ( callback : ( error : Error , args : DecodedLogEntryEvent < any > | Array < DecodedLogEntryEvent < any > > ) => void ) => void ; watch : ( callback : ( error : Error , args : DecodedLogEntryEvent < any > | Array < DecodedLogEntryEvent < any > > ) => void ) => void ; stopWatching ( callback ? ) : void ; stopWatchingAsync ( ) : Promise < void > ; } /**
 * Haven't figured out how to export EventFetcherFilterObject that extends FilterObject from web3.
 * Maybe will be easier with web3 v1.0, or perhaps using typescript's module augmentation feature.
 */ /**
 * Options supplied to `EventFetcherFactory` and thence to `get` and `watch`.
 */ export interface EventFetcherFilterObject { fromBlock ? : number | string ; toBlock ? : number | string ; address ? : string ; topics ? : Array < LogTopic > ; /**
     * true to suppress duplicate events (see https://github.com/ethereum/web3.js/issues/398).
     * The default is true.
     */ suppressDups ? : boolean ; } export class Web3EventSubscription < TEventArgs > implements IEventSubscription { constructor ( private subscription , private fetcher ) { } param milliseconds public unsubscribe ( milliseconds = - 1 ) { return new Promise ( ( resolve ) : Promise < void > => { return this . fetcher . stopWatchingAsync ( ) . then ( ( ) : void => { this . subscription . unsubscribe . call ( this . subscription , milliseconds ) . then ( ( ) => { resolve ( ) ; } ) ; } ) ; } ) ; } } type BaseWeb3EventCallback < T > = ( error : Error , log : DecodedLogEntryEvent < T > | Array < DecodedLogEntryEvent < T > > , singly : boolean , callback ? : ( error : Error , args : DecodedLogEntryEvent < T > | Array < DecodedLogEntryEvent < T > > ) => void , requiredDepth ? : number ) => Promise < Array < DecodedLogEntryEvent < T > > > ;