1
1
import { EventEmitter } from 'node:events'
2
2
import type { InterfaceAbi } from 'ethers'
3
3
import { Interface , WebSocketProvider , ethers } from 'ethers'
4
- import type { ErrorEvent , WebSocket } from 'ws'
5
- import type { ContractAddress } from '@ora-io/utils'
4
+ import { WebSocket } from 'ws'
5
+ import type { ErrorEvent } from 'ws'
6
+ import { type ContractAddress , isInstanceof , to } from '@ora-io/utils'
6
7
import { debug } from '../debug'
7
8
import { RekuContractManager } from './contract'
8
9
@@ -39,13 +40,17 @@ export class RekuProviderManager {
39
40
}
40
41
41
42
connect ( ) {
42
- const url = new URL ( this . providerUrl )
43
- if ( url . protocol === 'ws:' || url . protocol === 'wss:' )
43
+ if ( this . isWebSocketProviderUrl )
44
44
this . _provider = new ethers . WebSocketProvider ( this . providerUrl )
45
45
else
46
46
this . _provider = new ethers . JsonRpcProvider ( this . providerUrl )
47
47
}
48
48
49
+ get isWebSocketProviderUrl ( ) {
50
+ const url = new URL ( this . providerUrl )
51
+ return url . protocol === 'ws:' || url . protocol === 'wss:'
52
+ }
53
+
49
54
get provider ( ) {
50
55
return this . _provider as ethers . JsonRpcProvider | WebSocketProvider
51
56
}
@@ -54,6 +59,16 @@ export class RekuProviderManager {
54
59
return this . _contracts
55
60
}
56
61
62
+ get websocket ( ) {
63
+ if ( isInstanceof ( this . _provider , ethers . WebSocketProvider ) )
64
+ return this . _provider . websocket
65
+ return undefined
66
+ }
67
+
68
+ get destroyed ( ) {
69
+ return this . _provider ?. destroyed
70
+ }
71
+
57
72
addContract ( address : ContractAddress , contract : ethers . Contract ) : RekuContractManager | undefined
58
73
addContract ( address : ContractAddress , abi : Interface | InterfaceAbi ) : RekuContractManager | undefined
59
74
addContract ( address : ContractAddress , abi : Interface | InterfaceAbi | ethers . Contract ) : RekuContractManager | undefined {
@@ -156,7 +171,18 @@ export class RekuProviderManager {
156
171
socket . onerror = null
157
172
debug ( 'remove all listeners of websocket provider' )
158
173
}
159
- this . _provider ?. destroy ( )
174
+ debug ( 'reconnect destroyed: %s' , this . _provider ?. destroyed )
175
+ if ( this . _provider && ! this . _provider . destroyed ) {
176
+ if ( isInstanceof ( this . _provider , ethers . WebSocketProvider ) ) {
177
+ debug ( 'reconnect websocket readyState: %s' , this . websocket ?. readyState )
178
+ if ( this . websocket ?. readyState !== WebSocket . CONNECTING )
179
+ to ( Promise . resolve ( this . _provider . destroy ( ) ) )
180
+ }
181
+ else {
182
+ to ( Promise . resolve ( this . _provider . destroy ( ) ) )
183
+ }
184
+ }
185
+
160
186
this . _provider = undefined
161
187
162
188
setTimeout ( ( ) => {
@@ -186,21 +212,41 @@ export class RekuProviderManager {
186
212
if ( this . _options ?. disabledHeartbeat )
187
213
return
188
214
debug ( 'start heartbeat' )
189
- this . _heartbeatTimer = setInterval ( ( ) => {
190
- debug ( 'heartbeat running...' )
191
- debug ( 'heartbeat has provider: %s' , ! ! this . _provider )
192
- this . _provider ?. send ( 'net_version' , [ ] )
193
- . then ( ( res ) => {
194
- debug ( 'heartbeat response: %s' , res )
195
- } )
196
- . catch ( ( err ) => {
197
- this . reconnect ( )
198
- this . _event ?. emit ( 'error' , err )
199
- debug ( 'heartbeat error: %s' , err )
200
- } )
215
+ this . _heartbeatTimer = setInterval ( async ( ) => {
216
+ if ( ! this . destroyed ) {
217
+ debug ( 'heartbeat running...' )
218
+ const hasProvider = this . _hasProvider ( )
219
+ debug ( 'heartbeat has provider: %s' , hasProvider )
220
+ this . _provider ?. send ( 'net_version' , [ ] )
221
+ . then ( ( res ) => {
222
+ debug ( 'heartbeat response: %s' , res )
223
+ } )
224
+ . catch ( ( err ) => {
225
+ this . reconnect ( )
226
+ this . _event ?. emit ( 'error' , err )
227
+ debug ( 'heartbeat error: %s' , err )
228
+ } )
229
+ . finally ( ( ) => {
230
+ debug ( 'heartbeat finally' )
231
+ } )
232
+ }
233
+ else {
234
+ debug ( 'heartbeat destroyed' )
235
+ }
201
236
} , this . _heartbeatInterval )
202
237
}
203
238
239
+ private _hasProvider ( ) {
240
+ const hasProvider = ! ! this . _provider && ! ! this . _provider . provider
241
+ let isInstance = false
242
+ if ( this . isWebSocketProviderUrl )
243
+ isInstance = isInstanceof ( this . _provider , ethers . WebSocketProvider ) && isInstanceof ( this . _provider . provider , ethers . WebSocketProvider )
244
+ else
245
+ isInstance = isInstanceof ( this . _provider , ethers . JsonRpcProvider ) && isInstanceof ( this . _provider . provider , ethers . JsonRpcProvider )
246
+
247
+ return hasProvider && isInstance && ! this . _provider ?. destroyed
248
+ }
249
+
204
250
private _clearHeartbeat ( ) {
205
251
if ( this . _heartbeatTimer ) {
206
252
debug ( 'clear heartbeat' )
0 commit comments