11import { EventEmitter } from 'node:events'
22import type { InterfaceAbi } from 'ethers'
33import { 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'
67import { debug } from '../debug'
78import { RekuContractManager } from './contract'
89
@@ -39,13 +40,17 @@ export class RekuProviderManager {
3940 }
4041
4142 connect ( ) {
42- const url = new URL ( this . providerUrl )
43- if ( url . protocol === 'ws:' || url . protocol === 'wss:' )
43+ if ( this . isWebSocketProviderUrl )
4444 this . _provider = new ethers . WebSocketProvider ( this . providerUrl )
4545 else
4646 this . _provider = new ethers . JsonRpcProvider ( this . providerUrl )
4747 }
4848
49+ get isWebSocketProviderUrl ( ) {
50+ const url = new URL ( this . providerUrl )
51+ return url . protocol === 'ws:' || url . protocol === 'wss:'
52+ }
53+
4954 get provider ( ) {
5055 return this . _provider as ethers . JsonRpcProvider | WebSocketProvider
5156 }
@@ -54,6 +59,16 @@ export class RekuProviderManager {
5459 return this . _contracts
5560 }
5661
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+
5772 addContract ( address : ContractAddress , contract : ethers . Contract ) : RekuContractManager | undefined
5873 addContract ( address : ContractAddress , abi : Interface | InterfaceAbi ) : RekuContractManager | undefined
5974 addContract ( address : ContractAddress , abi : Interface | InterfaceAbi | ethers . Contract ) : RekuContractManager | undefined {
@@ -156,7 +171,18 @@ export class RekuProviderManager {
156171 socket . onerror = null
157172 debug ( 'remove all listeners of websocket provider' )
158173 }
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+
160186 this . _provider = undefined
161187
162188 setTimeout ( ( ) => {
@@ -186,21 +212,41 @@ export class RekuProviderManager {
186212 if ( this . _options ?. disabledHeartbeat )
187213 return
188214 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+ }
201236 } , this . _heartbeatInterval )
202237 }
203238
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+
204250 private _clearHeartbeat ( ) {
205251 if ( this . _heartbeatTimer ) {
206252 debug ( 'clear heartbeat' )
0 commit comments