@clonegod/ttd-bsc-common 1.0.20 → 1.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,18 @@ export interface BlockUpdateEvent {
5
5
  previousBlockNumber: number;
6
6
  timestamp: number;
7
7
  }
8
+ export interface ConnectionDiagnostics {
9
+ isConnected: boolean;
10
+ isReconnecting: boolean;
11
+ isStarted: boolean;
12
+ reconnectAttempts: number;
13
+ ws_endpoint: string;
14
+ wsReadyState: number | null;
15
+ trackingPools: number;
16
+ lastProcessedBlock: number;
17
+ lastMessageTime: number;
18
+ timeSinceLastMessage: number;
19
+ }
8
20
  export declare class PoolEventListener {
9
21
  private appConfig;
10
22
  private wsProvider;
@@ -14,20 +26,28 @@ export declare class PoolEventListener {
14
26
  private isReconnecting;
15
27
  private isStarted;
16
28
  private reconnectAttempts;
17
- private maxReconnectAttempts;
18
- private baseReconnectDelay;
19
- private maxReconnectDelay;
20
29
  private lastProcessedBlockNumber;
21
30
  private isBlockListenerActive;
31
+ private lastMessageTime;
32
+ private messageMonitorInterval;
33
+ private diagnosticInterval;
34
+ private heartbeatInterval;
35
+ private lastHeartbeatResponse;
22
36
  constructor(appConfig: AppConfig, ws_endpoint: string);
23
37
  init(poolList: StandardPoolInfoType[]): Promise<void>;
24
38
  start(): Promise<void>;
25
39
  stop(): Promise<void>;
26
40
  connect(): Promise<void>;
41
+ private setupWebSocketListeners;
42
+ private startHeartbeat;
27
43
  private handleConnectionIssue;
28
44
  private startBlockListener;
45
+ private startMessageMonitor;
46
+ private startDiagnosticMonitor;
47
+ private logDiagnostics;
48
+ private getWebSocketState;
29
49
  private stopBlockListener;
30
50
  private cleanup;
31
51
  isWSConnected(): boolean;
32
- getConnectionDiagnostics(): object;
52
+ getConnectionDiagnostics(): ConnectionDiagnostics;
33
53
  }
@@ -13,6 +13,19 @@ exports.PoolEventListener = void 0;
13
13
  const ethers_1 = require("ethers");
14
14
  const dist_1 = require("@clonegod/ttd-core/dist");
15
15
  const common_1 = require("../../common");
16
+ const CONFIG = {
17
+ MAX_RETRIES: 3,
18
+ CONNECTION_TIMEOUT: 10000,
19
+ MESSAGE_TIMEOUT: 2 * 60 * 1000,
20
+ DIAGNOSTIC_INTERVAL: 30000,
21
+ MESSAGE_CHECK_INTERVAL: 10000,
22
+ BASE_RECONNECT_DELAY: 500,
23
+ MAX_RECONNECT_DELAY: 30000,
24
+ MAX_RECONNECT_ATTEMPTS: 10,
25
+ RETRY_DELAY_MULTIPLIER: 1000,
26
+ HEARTBEAT_INTERVAL: 30000,
27
+ HEARTBEAT_TIMEOUT: 90000,
28
+ };
16
29
  class PoolEventListener {
17
30
  constructor(appConfig, ws_endpoint) {
18
31
  this.wsProvider = null;
@@ -21,11 +34,13 @@ class PoolEventListener {
21
34
  this.isReconnecting = false;
22
35
  this.isStarted = false;
23
36
  this.reconnectAttempts = 0;
24
- this.maxReconnectAttempts = 10;
25
- this.baseReconnectDelay = 500;
26
- this.maxReconnectDelay = 30000;
27
37
  this.lastProcessedBlockNumber = 0;
28
38
  this.isBlockListenerActive = false;
39
+ this.lastMessageTime = Date.now();
40
+ this.messageMonitorInterval = null;
41
+ this.diagnosticInterval = null;
42
+ this.heartbeatInterval = null;
43
+ this.lastHeartbeatResponse = Date.now();
29
44
  this.appConfig = appConfig;
30
45
  this.ws_endpoint = ws_endpoint;
31
46
  }
@@ -33,111 +48,155 @@ class PoolEventListener {
33
48
  return __awaiter(this, void 0, void 0, function* () {
34
49
  this.poolList = poolList.filter(pool => ethers_1.ethers.utils.isAddress(pool.pool_address));
35
50
  if (this.poolList.length !== poolList.length) {
36
- (0, dist_1.log_warn)(`发现 ${poolList.length - this.poolList.length} 个无效池地址,已过滤`, '');
51
+ (0, dist_1.log_warn)(`Found ${poolList.length - this.poolList.length} invalid pool addresses, filtered out`, '');
37
52
  }
38
53
  yield this.connect();
39
54
  });
40
55
  }
41
56
  start() {
42
57
  return __awaiter(this, void 0, void 0, function* () {
43
- (0, dist_1.log_info)(`开始启动区块监听...`);
58
+ (0, dist_1.log_info)(`Starting block listener...`);
44
59
  if (!this.isConnected || !this.wsProvider) {
45
- (0, dist_1.log_warn)('WebSocket 尚未连接,无法启动监听', '');
60
+ (0, dist_1.log_warn)('WebSocket not connected, cannot start listener', '');
46
61
  return;
47
62
  }
48
63
  yield this.startBlockListener();
49
64
  this.isStarted = true;
50
- (0, dist_1.log_info)('区块监听已成功启动');
65
+ (0, dist_1.log_info)('Block listener started successfully');
51
66
  });
52
67
  }
53
68
  stop() {
54
69
  return __awaiter(this, void 0, void 0, function* () {
55
- (0, dist_1.log_info)(`停止区块监听...`);
70
+ (0, dist_1.log_info)(`Stopping block listener...`);
56
71
  this.stopBlockListener();
57
72
  this.cleanup();
58
73
  this.isStarted = false;
59
- (0, dist_1.log_info)(`区块监听已停止`);
74
+ (0, dist_1.log_info)(`Block listener stopped`);
60
75
  });
61
76
  }
62
77
  connect() {
63
78
  return __awaiter(this, void 0, void 0, function* () {
64
- const maxRetries = 3;
65
- for (let i = 0; i < maxRetries; i++) {
79
+ for (let i = 0; i < CONFIG.MAX_RETRIES; i++) {
66
80
  try {
67
- (0, dist_1.log_info)(`正在连接 WebSocket: ${this.ws_endpoint} (尝试 ${i + 1}/${maxRetries})`);
81
+ (0, dist_1.log_info)(`Connecting to WebSocket: ${this.ws_endpoint} (Attempt ${i + 1}/${CONFIG.MAX_RETRIES})`);
68
82
  this.wsProvider = new ethers_1.ethers.providers.WebSocketProvider(this.ws_endpoint);
69
- const timeout = 10000;
70
83
  const wsPromise = this.wsProvider.ready;
71
84
  const timeoutPromise = new Promise((_, reject) => {
72
- setTimeout(() => reject(new Error('WebSocket connection timeout')), timeout);
85
+ setTimeout(() => reject(new Error('WebSocket connection timeout')), CONFIG.CONNECTION_TIMEOUT);
73
86
  });
74
87
  yield Promise.race([wsPromise, timeoutPromise]);
75
88
  this.isConnected = true;
76
89
  this.reconnectAttempts = 0;
77
- (0, dist_1.log_info)(`WebSocket 已连接: ${this.ws_endpoint}`);
78
- this.wsProvider.on('error', (err) => {
79
- (0, dist_1.log_error)(`WebSocket 错误:`, err, '');
80
- this.handleConnectionIssue();
81
- });
82
- this.wsProvider._websocket.on('close', (code, reason) => {
83
- (0, dist_1.log_warn)(`WebSocket 连接关闭, 代码: ${code}, 原因: ${reason || '未知'}`);
84
- this.handleConnectionIssue();
85
- });
90
+ (0, dist_1.log_info)(`WebSocket connected: ${this.ws_endpoint}`);
91
+ this.setupWebSocketListeners();
86
92
  if (this.isStarted) {
87
93
  yield this.startBlockListener();
88
94
  }
89
95
  return;
90
96
  }
91
97
  catch (error) {
92
- (0, dist_1.log_error)(`WebSocket 连接失败 (尝试 ${i + 1}/${maxRetries}):`, error);
93
- if (i === maxRetries - 1) {
98
+ (0, dist_1.log_error)(`WebSocket connection failed (Attempt ${i + 1}/${CONFIG.MAX_RETRIES}):`, error);
99
+ if (i === CONFIG.MAX_RETRIES - 1) {
94
100
  throw error;
95
101
  }
96
- const delay = 1000 * (i + 1);
97
- (0, dist_1.log_info)(`等待 ${delay / 1000} 秒后重试...`);
102
+ const delay = CONFIG.RETRY_DELAY_MULTIPLIER * (i + 1);
103
+ (0, dist_1.log_info)(`Waiting ${delay / 1000} seconds before retry...`);
98
104
  yield new Promise(resolve => setTimeout(resolve, delay));
99
105
  }
100
106
  }
101
107
  });
102
108
  }
109
+ setupWebSocketListeners() {
110
+ if (!this.wsProvider)
111
+ return;
112
+ this.wsProvider.on('error', (err) => {
113
+ (0, dist_1.log_error)(`WebSocket error:`, err, '');
114
+ this.handleConnectionIssue();
115
+ });
116
+ this.wsProvider._websocket.on('close', (code, reason) => {
117
+ (0, dist_1.log_warn)(`WebSocket connection closed, code: ${code}, reason: ${reason || 'unknown'}`);
118
+ this.handleConnectionIssue();
119
+ });
120
+ this.wsProvider._websocket.on('open', () => {
121
+ (0, dist_1.log_info)('WebSocket connection opened');
122
+ this.isConnected = true;
123
+ this.reconnectAttempts = 0;
124
+ });
125
+ this.startHeartbeat();
126
+ }
127
+ startHeartbeat() {
128
+ if (this.heartbeatInterval) {
129
+ clearInterval(this.heartbeatInterval);
130
+ }
131
+ this.heartbeatInterval = setInterval(() => __awaiter(this, void 0, void 0, function* () {
132
+ try {
133
+ if (!this.wsProvider || !this.isConnected) {
134
+ this.handleConnectionIssue();
135
+ return;
136
+ }
137
+ this.wsProvider._websocket.ping();
138
+ this.lastHeartbeatResponse = Date.now();
139
+ const timeSinceLastHeartbeat = Date.now() - this.lastHeartbeatResponse;
140
+ if (timeSinceLastHeartbeat > CONFIG.HEARTBEAT_TIMEOUT) {
141
+ (0, dist_1.log_warn)(`No heartbeat response for ${timeSinceLastHeartbeat / 1000} seconds`);
142
+ this.handleConnectionIssue();
143
+ }
144
+ }
145
+ catch (error) {
146
+ (0, dist_1.log_error)('Heartbeat check failed:', error);
147
+ this.handleConnectionIssue();
148
+ }
149
+ }), CONFIG.HEARTBEAT_INTERVAL);
150
+ }
103
151
  handleConnectionIssue() {
104
152
  return __awaiter(this, void 0, void 0, function* () {
105
- if (this.isReconnecting)
153
+ if (this.isReconnecting) {
154
+ (0, dist_1.log_info)('Already attempting to reconnect, skipping...');
106
155
  return;
156
+ }
107
157
  this.isReconnecting = true;
108
158
  this.isConnected = false;
109
- this.stopBlockListener();
110
- this.cleanup();
111
- this.reconnectAttempts++;
112
- const delay = Math.min(this.baseReconnectDelay * Math.pow(2, this.reconnectAttempts), this.maxReconnectDelay);
113
- (0, dist_1.log_info)(`尝试重连 ${this.reconnectAttempts}/${this.maxReconnectAttempts}, 延迟 ${delay}ms, 节点: ${this.ws_endpoint}`);
114
- if (this.reconnectAttempts > this.maxReconnectAttempts) {
115
- (0, dist_1.log_error)(`重连失败,尝试次数达到 ${this.maxReconnectAttempts} 次,程序退出`, new Error('Max reconnect attempts failed'), '');
116
- this.appConfig.emit(common_1.EVENT_NAMES.WS_CONNECTION_FAILED, {
117
- endpoint: this.ws_endpoint,
118
- attempts: this.maxReconnectAttempts,
119
- });
120
- process.exit(1);
121
- }
122
- yield (0, dist_1.sleep)(delay);
123
- yield this.connect();
124
- this.isReconnecting = false;
159
+ try {
160
+ this.stopBlockListener();
161
+ this.cleanup();
162
+ this.reconnectAttempts++;
163
+ const delay = Math.min(CONFIG.BASE_RECONNECT_DELAY * Math.pow(2, this.reconnectAttempts), CONFIG.MAX_RECONNECT_DELAY);
164
+ (0, dist_1.log_info)(`Attempting to reconnect ${this.reconnectAttempts}/${CONFIG.MAX_RECONNECT_ATTEMPTS}, delay ${delay}ms, node: ${this.ws_endpoint}`);
165
+ if (this.reconnectAttempts > CONFIG.MAX_RECONNECT_ATTEMPTS) {
166
+ (0, dist_1.log_error)(`Reconnection failed, reached maximum attempts (${CONFIG.MAX_RECONNECT_ATTEMPTS}), exiting`, new Error('Max reconnect attempts failed'), '');
167
+ this.appConfig.emit(common_1.EVENT_NAMES.WS_CONNECTION_FAILED, {
168
+ endpoint: this.ws_endpoint,
169
+ attempts: CONFIG.MAX_RECONNECT_ATTEMPTS,
170
+ });
171
+ process.exit(1);
172
+ }
173
+ yield (0, dist_1.sleep)(delay);
174
+ yield this.connect();
175
+ }
176
+ catch (error) {
177
+ (0, dist_1.log_error)('Reconnection attempt failed:', error);
178
+ setTimeout(() => this.handleConnectionIssue(), CONFIG.BASE_RECONNECT_DELAY);
179
+ }
180
+ finally {
181
+ this.isReconnecting = false;
182
+ }
125
183
  });
126
184
  }
127
185
  startBlockListener() {
128
186
  return __awaiter(this, void 0, void 0, function* () {
129
187
  if (!this.wsProvider || !this.isConnected) {
130
- (0, dist_1.log_warn)(`无法启动区块监听: WebSocket 未连接`);
188
+ (0, dist_1.log_warn)(`Cannot start block listener: WebSocket not connected`);
131
189
  return;
132
190
  }
133
191
  try {
134
192
  const blockNumber = yield this.wsProvider.getBlockNumber();
135
193
  this.lastProcessedBlockNumber = blockNumber;
136
- (0, dist_1.log_info)(`初始化区块监听,当前区块: ${blockNumber}`);
194
+ (0, dist_1.log_info)(`Initializing block listener, current block: ${blockNumber}`);
137
195
  this.wsProvider.on('block', (blockNumber) => __awaiter(this, void 0, void 0, function* () {
138
196
  try {
139
197
  if (!this.isConnected || !this.isBlockListenerActive)
140
198
  return;
199
+ this.lastMessageTime = Date.now();
141
200
  (0, dist_1.log_info)(`------- Block: ${blockNumber} -------`);
142
201
  const previousBlockNumber = this.lastProcessedBlockNumber;
143
202
  this.lastProcessedBlockNumber = blockNumber;
@@ -149,23 +208,78 @@ class PoolEventListener {
149
208
  this.appConfig.emit(common_1.EVENT_NAMES.BLOCK_UPDATE, blockUpdateEvent);
150
209
  }
151
210
  catch (error) {
152
- (0, dist_1.log_error)(`处理区块事件出错:`, error, '');
211
+ (0, dist_1.log_error)(`Error processing block event:`, error, '');
153
212
  }
154
213
  }));
214
+ this.startMessageMonitor();
215
+ this.startDiagnosticMonitor();
155
216
  this.isBlockListenerActive = true;
156
- (0, dist_1.log_info)(`区块监听已启动,使用 WebSocket 事件订阅方式`);
217
+ (0, dist_1.log_info)(`Block listener started using WebSocket event subscription`);
157
218
  }
158
219
  catch (error) {
159
- (0, dist_1.log_error)(`启动区块监听失败:`, error, '');
220
+ (0, dist_1.log_error)(`Failed to start block listener:`, error, '');
160
221
  this.isBlockListenerActive = false;
161
222
  }
162
223
  });
163
224
  }
225
+ startMessageMonitor() {
226
+ if (this.messageMonitorInterval) {
227
+ clearInterval(this.messageMonitorInterval);
228
+ }
229
+ this.messageMonitorInterval = setInterval(() => {
230
+ const timeSinceLastMessage = Date.now() - this.lastMessageTime;
231
+ if (timeSinceLastMessage > CONFIG.MESSAGE_TIMEOUT) {
232
+ (0, dist_1.log_warn)(`No messages received for ${CONFIG.MESSAGE_TIMEOUT / 1000} seconds, preparing to resubscribe`);
233
+ this.handleConnectionIssue();
234
+ }
235
+ }, CONFIG.MESSAGE_CHECK_INTERVAL);
236
+ }
237
+ startDiagnosticMonitor() {
238
+ if (this.diagnosticInterval) {
239
+ clearInterval(this.diagnosticInterval);
240
+ }
241
+ this.diagnosticInterval = setInterval(() => {
242
+ const diagnostics = this.getConnectionDiagnostics();
243
+ this.logDiagnostics(diagnostics);
244
+ }, CONFIG.DIAGNOSTIC_INTERVAL);
245
+ }
246
+ logDiagnostics(diagnostics) {
247
+ (0, dist_1.log_info)(`WebSocket Connection Diagnostics:
248
+ Connection Status: ${diagnostics.isConnected ? 'Connected' : 'Disconnected'}
249
+ Reconnection Status: ${diagnostics.isReconnecting ? 'Reconnecting' : 'Not Reconnecting'}
250
+ Listener Status: ${diagnostics.isStarted ? 'Started' : 'Not Started'}
251
+ Reconnection Attempts: ${diagnostics.reconnectAttempts}
252
+ WebSocket State: ${this.getWebSocketState(diagnostics.wsReadyState)}
253
+ Tracking Pools: ${diagnostics.trackingPools}
254
+ Last Processed Block: ${diagnostics.lastProcessedBlock}
255
+ Last Message Time: ${new Date(diagnostics.lastMessageTime).toLocaleString()}
256
+ Time Since Last Message: ${Math.floor(diagnostics.timeSinceLastMessage / 1000)} seconds
257
+ `);
258
+ }
259
+ getWebSocketState(state) {
260
+ if (state === null)
261
+ return 'Not Initialized';
262
+ switch (state) {
263
+ case 0: return 'Connecting';
264
+ case 1: return 'Connected';
265
+ case 2: return 'Closing';
266
+ case 3: return 'Closed';
267
+ default: return 'Unknown State';
268
+ }
269
+ }
164
270
  stopBlockListener() {
165
271
  if (this.isBlockListenerActive && this.wsProvider) {
166
272
  this.wsProvider.removeListener('block', () => { });
167
273
  this.isBlockListenerActive = false;
168
- (0, dist_1.log_info)(`区块监听已停止`);
274
+ if (this.messageMonitorInterval) {
275
+ clearInterval(this.messageMonitorInterval);
276
+ this.messageMonitorInterval = null;
277
+ }
278
+ if (this.diagnosticInterval) {
279
+ clearInterval(this.diagnosticInterval);
280
+ this.diagnosticInterval = null;
281
+ }
282
+ (0, dist_1.log_info)(`Block listener stopped`);
169
283
  }
170
284
  }
171
285
  cleanup() {
@@ -177,10 +291,22 @@ class PoolEventListener {
177
291
  }
178
292
  }
179
293
  catch (error) {
180
- (0, dist_1.log_error)(`清理 WebSocket 资源失败:`, error, '');
294
+ (0, dist_1.log_error)(`Failed to cleanup WebSocket resources:`, error, '');
181
295
  }
182
296
  this.wsProvider = null;
183
297
  }
298
+ if (this.messageMonitorInterval) {
299
+ clearInterval(this.messageMonitorInterval);
300
+ this.messageMonitorInterval = null;
301
+ }
302
+ if (this.diagnosticInterval) {
303
+ clearInterval(this.diagnosticInterval);
304
+ this.diagnosticInterval = null;
305
+ }
306
+ if (this.heartbeatInterval) {
307
+ clearInterval(this.heartbeatInterval);
308
+ this.heartbeatInterval = null;
309
+ }
184
310
  this.isConnected = false;
185
311
  this.isReconnecting = false;
186
312
  }
@@ -200,6 +326,8 @@ class PoolEventListener {
200
326
  : null,
201
327
  trackingPools: this.poolList.length,
202
328
  lastProcessedBlock: this.lastProcessedBlockNumber,
329
+ lastMessageTime: this.lastMessageTime,
330
+ timeSinceLastMessage: Date.now() - this.lastMessageTime,
203
331
  };
204
332
  }
205
333
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clonegod/ttd-bsc-common",
3
- "version": "1.0.20",
3
+ "version": "1.0.21",
4
4
  "description": "BSC common library",
5
5
  "license": "UNLICENSED",
6
6
  "main": "dist/index.js",