1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
|
import { intervalUtils } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as _ from 'lodash';
import * as Web3 from 'web3';
import { BlockParamLiteral } from '@0xproject/types';
import { EventWatcherCallback, ZeroExError } from '../types';
import { assert } from '../utils/assert';
const DEFAULT_EVENT_POLLING_INTERVAL_MS = 200;
enum LogEventState {
Removed,
Added,
}
/*
* The EventWatcher watches for blockchain events at the specified block confirmation
* depth.
*/
export class EventWatcher {
private _web3Wrapper: Web3Wrapper;
private _pollingIntervalMs: number;
private _intervalIdIfExists?: NodeJS.Timer;
private _lastEvents: Web3.LogEntry[] = [];
constructor(web3Wrapper: Web3Wrapper, pollingIntervalIfExistsMs: undefined | number) {
this._web3Wrapper = web3Wrapper;
this._pollingIntervalMs = _.isUndefined(pollingIntervalIfExistsMs)
? DEFAULT_EVENT_POLLING_INTERVAL_MS
: pollingIntervalIfExistsMs;
}
public subscribe(callback: EventWatcherCallback): void {
assert.isFunction('callback', callback);
if (!_.isUndefined(this._intervalIdIfExists)) {
throw new Error(ZeroExError.SubscriptionAlreadyPresent);
}
this._intervalIdIfExists = intervalUtils.setAsyncExcludingInterval(
this._pollForBlockchainEventsAsync.bind(this, callback),
this._pollingIntervalMs,
(err: Error) => {
this.unsubscribe();
callback(err);
},
);
}
public unsubscribe(): void {
this._lastEvents = [];
if (!_.isUndefined(this._intervalIdIfExists)) {
intervalUtils.clearAsyncExcludingInterval(this._intervalIdIfExists);
delete this._intervalIdIfExists;
}
}
private async _pollForBlockchainEventsAsync(callback: EventWatcherCallback): Promise<void> {
const pendingEvents = await this._getEventsAsync();
if (_.isUndefined(pendingEvents)) {
// HACK: This should never happen, but happens frequently on CI due to a ganache bug
return;
}
if (pendingEvents.length === 0) {
// HACK: Sometimes when node rebuilds the pending block we get back the empty result.
// We don't want to emit a lot of removal events and bring them back after a couple of miliseconds,
// that's why we just ignore those cases.
return;
}
const removedEvents = _.differenceBy(this._lastEvents, pendingEvents, JSON.stringify);
const newEvents = _.differenceBy(pendingEvents, this._lastEvents, JSON.stringify);
await this._emitDifferencesAsync(removedEvents, LogEventState.Removed, callback);
await this._emitDifferencesAsync(newEvents, LogEventState.Added, callback);
this._lastEvents = pendingEvents;
}
private async _getEventsAsync(): Promise<Web3.LogEntry[]> {
const eventFilter = {
fromBlock: BlockParamLiteral.Pending,
toBlock: BlockParamLiteral.Pending,
};
const events = await this._web3Wrapper.getLogsAsync(eventFilter);
return events;
}
private async _emitDifferencesAsync(
logs: Web3.LogEntry[],
logEventState: LogEventState,
callback: EventWatcherCallback,
): Promise<void> {
for (const log of logs) {
const logEvent = {
removed: logEventState === LogEventState.Removed,
...log,
};
if (!_.isUndefined(this._intervalIdIfExists)) {
callback(null, logEvent);
}
}
}
}
|