11import { ApiPromise , WsProvider } from "@polkadot/api" ;
22import EventEmitter from "eventemitter3" ;
33
4- import logger from "./logger" ;
5- import { sleep } from "./util" ;
6- import { POLKADOT_API_TIMEOUT } from "./constants" ;
4+ import logger from ".. /logger" ;
5+ import { sleep } from "../utils /util" ;
6+ import { API_PROVIDER_TIMEOUT , POLKADOT_API_TIMEOUT } from ". ./constants" ;
77
88export const apiLabel = { label : "ApiHandler" } ;
99
@@ -12,166 +12,219 @@ export const apiLabel = { label: "ApiHandler" };
1212 * to a different provider if one proves troublesome.
1313 */
1414class ApiHandler extends EventEmitter {
15- private _api : ApiPromise ;
16- private _endpoints : string [ ] ;
17- private _reconnectLock : boolean ;
18- private _reconnectTries = 0 ;
19- static isConnected : any ;
20- static _reconnect : any ;
21-
22- constructor ( api : ApiPromise , endpoints ? : string [ ] ) {
15+ private _wsProvider ?: WsProvider ;
16+ private _api : ApiPromise | null = null ;
17+ private readonly _endpoints : string [ ] = [ ] ;
18+ static isConnected = false ;
19+ private healthCheckInProgress = false ;
20+ private _currentEndpoint ?: string ;
21+ public upSince : number = Date . now ( ) ;
22+ constructor ( endpoints : string [ ] ) {
2323 super ( ) ;
24- this . _api = api ;
25- // this._endpoints = endpoints.sort(() => Math.random() - 0.5);
26- this . _registerEventHandlers ( api ) ;
24+ this . _endpoints = endpoints . sort ( ( ) => Math . random ( ) - 0.5 ) ;
25+ this . upSince = Date . now ( ) ;
2726 }
2827
29- static async createApi ( endpoints , reconnectTries = 0 ) {
30- const timeout = 12 ;
31- let api , wsProvider ;
32- const healthCheck = async ( api ) => {
33- logger . info ( `Performing health check for WS Provider for rpc.` , apiLabel ) ;
28+ async healthCheck ( retries = 0 ) : Promise < boolean > {
29+ if ( retries < 10 ) {
30+ try {
31+ this . healthCheckInProgress = true ;
32+ let chain ;
3433
35- await sleep ( timeout * 1000 ) ;
36- if ( api && api ?. isConnected ) {
37- logger . info ( `All good. Connected` , apiLabel ) ;
38- return true ;
39- } else {
40- logger . info (
41- `rpc endpoint still disconnected after ${ timeout } seconds. Disconnecting ` ,
34+ const isConnected = this . _wsProvider ?. isConnected ;
35+ if ( isConnected && ! this . _api ?. isConnected ) {
36+ try {
37+ chain = await this . _api ?. rpc . system . chain ( ) ;
38+ } catch ( e ) {
39+ await sleep ( API_PROVIDER_TIMEOUT ) ;
40+ }
41+ }
42+ chain = await this . _api ?. rpc . system . chain ( ) ;
43+
44+ if ( isConnected && chain ) {
45+ this . healthCheckInProgress = false ;
46+ return true ;
47+ } else {
48+ await sleep ( API_PROVIDER_TIMEOUT ) ;
49+ logger . info ( `api still disconnected, disconnecting.` , apiLabel ) ;
50+ await this . _wsProvider ?. disconnect ( ) ;
51+ await this . getProvider ( this . _endpoints ) ;
52+ await this . getAPI ( ) ;
53+ return false ;
54+ }
55+ } catch ( e : unknown ) {
56+ const errorMessage =
57+ e instanceof Error ? e . message : "An unknown error occurred" ;
58+ logger . error (
59+ `Error in health check for WS Provider for rpc. ${ errorMessage } ` ,
4260 apiLabel ,
4361 ) ;
44- await api . disconnect ( ) ;
45-
46- throw new Error (
47- `rpc endpoint still disconnected after ${ timeout } seconds.` ,
48- ) ;
62+ this . healthCheckInProgress = false ;
63+ return false ;
4964 }
50- } ;
65+ }
66+ return false ;
67+ }
5168
52- try {
53- wsProvider = new WsProvider (
69+ public currentEndpoint ( ) {
70+ return this . _currentEndpoint ;
71+ }
72+
73+ async getProvider ( endpoints : string [ ] ) : Promise < WsProvider > {
74+ return await new Promise < WsProvider > ( ( resolve , reject ) => {
75+ const wsProvider = new WsProvider (
5476 endpoints ,
55- undefined ,
77+ 5000 ,
5678 undefined ,
5779 POLKADOT_API_TIMEOUT ,
5880 ) ;
5981
60- api = new ApiPromise ( {
61- provider : new WsProvider (
62- endpoints ,
63- undefined ,
64- undefined ,
65- POLKADOT_API_TIMEOUT ,
66- ) ,
67- // throwOnConnect: true,
82+ wsProvider . on ( "disconnected" , async ( ) => {
83+ try {
84+ const isHealthy = await this . healthCheck ( ) ;
85+ logger . info (
86+ `[Disconnection] ${ this . _currentEndpoint } } Health check result: ${ isHealthy } ` ,
87+ apiLabel ,
88+ ) ;
89+ resolve ( wsProvider ) ;
90+ } catch ( error : any ) {
91+ logger . warn (
92+ `WS provider for rpc ${ endpoints [ 0 ] } disconnected!` ,
93+ apiLabel ,
94+ ) ;
95+ reject ( error ) ;
96+ }
6897 } ) ;
98+ wsProvider . on ( "connected" , ( ) => {
99+ logger . info ( `WS provider for rpc ${ endpoints [ 0 ] } connected` , apiLabel ) ;
100+ this . _currentEndpoint = endpoints [ 0 ] ;
101+ resolve ( wsProvider ) ;
102+ } ) ;
103+ wsProvider . on ( "error" , async ( ) => {
104+ try {
105+ const isHealthy = await this . healthCheck ( ) ;
106+ logger . info (
107+ `[Error] ${ this . _currentEndpoint } Health check result: ${ isHealthy } ` ,
108+ apiLabel ,
109+ ) ;
110+ resolve ( wsProvider ) ;
111+ } catch ( error : any ) {
112+ logger . error ( `Error thrown for rpc ${ this . _endpoints [ 0 ] } ` , apiLabel ) ;
113+ reject ( error ) ;
114+ }
115+ } ) ;
116+ } ) ;
117+ }
69118
70- api
71- . on ( "connected" , ( ) => {
72- logger . info ( `Connected to chain ${ endpoints [ 0 ] } ` , apiLabel ) ;
73- } )
74- . on ( "disconnected" , async ( ) => {
75- logger . warn ( `Disconnected from chain` , apiLabel ) ;
76- try {
77- await healthCheck ( wsProvider ) ;
78- await Promise . resolve ( api ) ;
79- } catch ( error : any ) {
80- await Promise . reject ( error ) ;
81- }
82- } )
83- . on ( "ready" , ( ) => {
84- logger . info ( `API connection ready ${ endpoints [ 0 ] } ` , apiLabel ) ;
85- } )
86- . on ( "error" , async ( error ) => {
87- logger . warn ( "The API has an error" , apiLabel ) ;
88- logger . error ( error , apiLabel ) ;
89- logger . warn ( `attempting to reconnect to ${ endpoints [ 0 ] } ` , apiLabel ) ;
90- try {
91- await healthCheck ( wsProvider ) ;
92- await Promise . resolve ( api ) ;
93- } catch ( error : any ) {
94- await Promise . reject ( error ) ;
95- }
96- } ) ;
97-
98- if ( api ) {
99- await api . isReadyOrError . catch ( logger . error ) ;
119+ async getAPI ( retries = 0 ) : Promise < ApiPromise > {
120+ if ( this . _wsProvider && this . _api && this . _api ?. isConnected ) {
121+ return this . _api ;
122+ }
123+ const endpoints = this . _endpoints . sort ( ( ) => Math . random ( ) - 0.5 ) ;
100124
101- return api ;
102- }
125+ try {
126+ logger . info (
127+ `[getAPI]: try ${ retries } creating provider with endpoint ${ endpoints [ 0 ] } ` ,
128+ apiLabel ,
129+ ) ;
130+ const provider = await this . getProvider ( endpoints ) ;
131+ this . _wsProvider = provider ;
132+ logger . info (
133+ `[getAPI]: provider created with endpoint: ${ endpoints [ 0 ] } ` ,
134+ apiLabel ,
135+ ) ;
136+ const api = await ApiPromise . create ( {
137+ provider : provider ,
138+ noInitWarn : true ,
139+ } ) ;
140+ await api . isReadyOrError ;
141+ logger . info ( `[getApi] Api is ready` , apiLabel ) ;
142+ return api ;
103143 } catch ( e ) {
104- logger . error ( `there was an error: ` , apiLabel ) ;
105- logger . error ( e , apiLabel ) ;
106- if ( reconnectTries < 10 ) {
107- return await this . createApi (
108- endpoints . sort ( ( ) => Math . random ( ) - 0.5 ) ,
109- reconnectTries + 1 ,
110- ) ;
144+ if ( retries < 15 ) {
145+ return await this . getAPI ( retries + 1 ) ;
111146 } else {
112- return api ;
147+ const provider = await this . getProvider ( endpoints ) ;
148+ return await ApiPromise . create ( {
149+ provider : provider ,
150+ noInitWarn : true ,
151+ } ) ;
113152 }
114153 }
115154 }
116155
117- static async create ( endpoints : string [ ] ) : Promise < ApiHandler > {
118- try {
119- const api = await this . createApi (
120- endpoints . sort ( ( ) => Math . random ( ) - 0.5 ) ,
121- ) ;
122-
123- return new ApiHandler ( api , endpoints ) ;
124- } catch ( e ) {
125- logger . info ( `there was an error: ` , apiLabel ) ;
126- logger . error ( e , apiLabel ) ;
127- }
156+ async setAPI ( ) {
157+ const api = await this . getAPI ( 0 ) ;
158+ this . _api = api ;
159+ this . _registerEventHandlers ( this . _api ) ;
160+ return api ;
128161 }
129162
130163 isConnected ( ) : boolean {
131- return this . _api . isConnected ;
164+ return this . _wsProvider ? .isConnected || false ;
132165 }
133166
134- getApi ( ) : ApiPromise {
135- return this . _api ;
167+ getApi ( ) : ApiPromise | null {
168+ if ( ! this . _api ) {
169+ return null ;
170+ } else {
171+ return this . _api ;
172+ }
136173 }
137174
138175 _registerEventHandlers ( api : ApiPromise ) : void {
176+ if ( ! api ) {
177+ logger . warn ( `API is null, cannot register event handlers.` , apiLabel ) ;
178+ return ;
179+ }
180+
181+ logger . info ( `Registering event handlers...` , apiLabel ) ;
182+
139183 api . query . system . events ( ( events ) => {
140- // Loop through the Vec<EventRecord>
141184 events . forEach ( ( record ) => {
142- // Extract the phase, event and the event types
143185 const { event } = record ;
144186
145- if ( event . section == "session" && event . method == "NewSession" ) {
146- const [ session_index ] = event . data ;
147-
148- this . emit ( "newSession" , {
149- sessionIndex : session_index . toString ( ) ,
150- } ) ;
187+ if ( event . section === "session" && event . method === "NewSession" ) {
188+ const sessionIndex = Number ( event ?. data [ 0 ] ?. toString ( ) ) || 0 ;
189+ this . handleNewSessionEvent ( sessionIndex ) ;
151190 }
152191
153192 if (
154- event . section == "staking" &&
155- ( event . method == "Reward" || event . method == "Rewarded" )
193+ event . section === "staking" &&
194+ ( event . method === "Reward" || event . method = == "Rewarded" )
156195 ) {
157196 const [ stash , amount ] = event . data ;
158-
159- this . emit ( "reward" , {
160- stash : stash . toString ( ) ,
161- amount : amount . toString ( ) ,
162- } ) ;
197+ this . handleRewardEvent ( stash . toString ( ) , amount . toString ( ) ) ;
163198 }
164199
165200 if ( event . section === "imOnline" && event . method === "SomeOffline" ) {
166- const offlineVals = event . data . toJSON ( ) [ 0 ] . map ( ( val ) => val [ 0 ] ) ;
167-
168- this . emit ( "someOffline" , {
169- offlineVals : offlineVals ,
170- } ) ;
201+ const data = event . data . toJSON ( ) ;
202+ const offlineVals =
203+ Array . isArray ( data ) && Array . isArray ( data [ 0 ] )
204+ ? data [ 0 ] . reduce ( ( acc : string [ ] , val ) => {
205+ if ( Array . isArray ( val ) && typeof val [ 0 ] === "string" ) {
206+ acc . push ( val [ 0 ] ) ;
207+ }
208+ return acc ;
209+ } , [ ] )
210+ : [ ] ;
211+ this . handleSomeOfflineEvent ( offlineVals as string [ ] ) ;
171212 }
172213 } ) ;
173214 } ) ;
174215 }
216+
217+ handleNewSessionEvent ( sessionIndex : number ) : void {
218+ this . emit ( "newSession" , { sessionIndex } ) ;
219+ }
220+
221+ handleRewardEvent ( stash : string , amount : string ) : void {
222+ this . emit ( "reward" , { stash, amount } ) ;
223+ }
224+
225+ handleSomeOfflineEvent ( offlineVals : string [ ] ) : void {
226+ this . emit ( "someOffline" , { offlineVals } ) ;
227+ }
175228}
176229
177230export default ApiHandler ;
0 commit comments