1- import WebSocket , { type ClientOptions } from "isomorphic-ws" ;
21import type { ClientRequestArgs } from "node:http" ;
2+
3+ import WebSocket , { type ClientOptions , type ErrorEvent } from "isomorphic-ws" ;
34import type { Logger } from "ts-log" ;
45
56// Reconnect with expo backoff if we don't get a message or ping for 10 seconds
6- const HEARTBEAT_TIMEOUT_DURATION = 10000 ;
7+ const HEARTBEAT_TIMEOUT_DURATION = 10_000 ;
78
89/**
910 * This class wraps websocket to provide a resilient web socket client.
@@ -24,7 +25,7 @@ export class ResilientWebSocket {
2425 private heartbeatTimeout : undefined | NodeJS . Timeout ;
2526 private logger : undefined | Logger ;
2627
27- onError : ( error : Error ) => void ;
28+ onError : ( error : ErrorEvent ) => void ;
2829 onMessage : ( data : WebSocket . Data ) => void ;
2930 onReconnect : ( ) => void ;
3031 constructor (
@@ -37,16 +38,20 @@ export class ResilientWebSocket {
3738 this . logger = logger ;
3839
3940 this . wsFailedAttempts = 0 ;
40- this . onError = ( error : Error ) => {
41- this . logger ?. error ( error ) ;
41+ this . onError = ( error : ErrorEvent ) => {
42+ this . logger ?. error ( error . error ) ;
4243 } ;
4344 this . wsUserClosed = true ;
44- this . onMessage = ( ) => { } ;
45- this . onReconnect = ( ) => { } ;
45+ this . onMessage = ( data : WebSocket . Data ) : void => {
46+ void data ;
47+ } ;
48+ this . onReconnect = ( ) : void => {
49+ // Empty function, can be set by the user.
50+ } ;
4651 }
4752
48- async send ( data : any ) {
49- this . logger ?. info ( `Sending ${ data } ` ) ;
53+ async send ( data : string | Buffer ) {
54+ this . logger ?. info ( `Sending message ` ) ;
5055
5156 await this . waitForMaybeReadyWebSocket ( ) ;
5257
@@ -55,11 +60,11 @@ export class ResilientWebSocket {
5560 "Couldn't connect to the websocket server. Error callback is called."
5661 ) ;
5762 } else {
58- this . wsClient ? .send ( data ) ;
63+ this . wsClient . send ( data ) ;
5964 }
6065 }
6166
62- async startWebSocket ( ) {
67+ startWebSocket ( ) : void {
6368 if ( this . wsClient !== undefined ) {
6469 return ;
6570 }
@@ -69,42 +74,26 @@ export class ResilientWebSocket {
6974 this . wsClient = new WebSocket ( this . endpoint , this . wsOptions ) ;
7075 this . wsUserClosed = false ;
7176
72- this . wsClient . onopen = ( ) => {
77+ this . wsClient . addEventListener ( "open" , ( ) => {
7378 this . wsFailedAttempts = 0 ;
7479 this . resetHeartbeat ( ) ;
75- } ;
80+ } ) ;
7681
77- this . wsClient . onerror = ( event ) => {
78- this . onError ( event . error ) ;
79- } ;
82+ this . wsClient . addEventListener ( "error" , ( event ) => {
83+ this . onError ( event ) ;
84+ } ) ;
8085
81- this . wsClient . onmessage = ( event ) => {
86+ this . wsClient . addEventListener ( "message" , ( event ) => {
8287 this . resetHeartbeat ( ) ;
8388 this . onMessage ( event . data ) ;
84- } ;
89+ } ) ;
8590
86- this . wsClient . onclose = async ( ) => {
87- if ( this . heartbeatTimeout !== undefined ) {
88- clearTimeout ( this . heartbeatTimeout ) ;
89- }
90-
91- if ( this . wsUserClosed === false ) {
92- this . wsFailedAttempts += 1 ;
93- this . wsClient = undefined ;
94- const waitTime = expoBackoff ( this . wsFailedAttempts ) ;
95-
96- this . logger ?. error (
97- `Connection closed unexpectedly or because of timeout. Reconnecting after ${ waitTime } ms.`
98- ) ;
99-
100- await sleep ( waitTime ) ;
101- this . restartUnexpectedClosedWebsocket ( ) ;
102- } else {
103- this . logger ?. info ( "The connection has been closed successfully." ) ;
104- }
105- } ;
91+ this . wsClient . addEventListener ( "close" , ( ) => {
92+ void this . handleClose ( ) ;
93+ } ) ;
10694
107- if ( this . wsClient . on !== undefined ) {
95+ // Handle ping events if supported (Node.js only)
96+ if ( "on" in this . wsClient ) {
10897 // Ping handler is undefined in browser side
10998 this . wsClient . on ( "ping" , ( ) => {
11099 this . logger ?. info ( "Ping received" ) ;
@@ -118,19 +107,19 @@ export class ResilientWebSocket {
118107 * from the server. If we don't receive any message within HEARTBEAT_TIMEOUT_DURATION,
119108 * we assume the connection is dead and reconnect.
120109 */
121- private resetHeartbeat ( ) {
110+ private resetHeartbeat ( ) : void {
122111 if ( this . heartbeatTimeout !== undefined ) {
123112 clearTimeout ( this . heartbeatTimeout ) ;
124113 }
125114
126115 this . heartbeatTimeout = setTimeout ( ( ) => {
127- this . logger ?. warn ( ` Connection timed out. Reconnecting...` ) ;
116+ this . logger ?. warn ( " Connection timed out. Reconnecting..." ) ;
128117 this . wsClient ?. terminate ( ) ;
129- this . restartUnexpectedClosedWebsocket ( ) ;
118+ void this . restartUnexpectedClosedWebsocket ( ) ;
130119 } , HEARTBEAT_TIMEOUT_DURATION ) ;
131120 }
132121
133- private async waitForMaybeReadyWebSocket ( ) {
122+ private async waitForMaybeReadyWebSocket ( ) : Promise < void > {
134123 let waitedTime = 0 ;
135124 while (
136125 this . wsClient !== undefined &&
@@ -146,12 +135,35 @@ export class ResilientWebSocket {
146135 }
147136 }
148137
149- private async restartUnexpectedClosedWebsocket ( ) {
150- if ( this . wsUserClosed === true ) {
138+ private async handleClose ( ) : Promise < void > {
139+ if ( this . heartbeatTimeout !== undefined ) {
140+ clearTimeout ( this . heartbeatTimeout ) ;
141+ }
142+
143+ if ( this . wsUserClosed ) {
144+ this . logger ?. info ( "The connection has been closed successfully." ) ;
145+ } else {
146+ this . wsFailedAttempts += 1 ;
147+ this . wsClient = undefined ;
148+ const waitTime = expoBackoff ( this . wsFailedAttempts ) ;
149+
150+ this . logger ?. error (
151+ "Connection closed unexpectedly or because of timeout. Reconnecting after " +
152+ String ( waitTime ) +
153+ "ms."
154+ ) ;
155+
156+ await sleep ( waitTime ) ;
157+ await this . restartUnexpectedClosedWebsocket ( ) ;
158+ }
159+ }
160+
161+ private async restartUnexpectedClosedWebsocket ( ) : Promise < void > {
162+ if ( this . wsUserClosed ) {
151163 return ;
152164 }
153165
154- await this . startWebSocket ( ) ;
166+ this . startWebSocket ( ) ;
155167 await this . waitForMaybeReadyWebSocket ( ) ;
156168
157169 if ( this . wsClient === undefined ) {
@@ -164,7 +176,7 @@ export class ResilientWebSocket {
164176 this . onReconnect ( ) ;
165177 }
166178
167- closeWebSocket ( ) {
179+ closeWebSocket ( ) : void {
168180 if ( this . wsClient !== undefined ) {
169181 const client = this . wsClient ;
170182 this . wsClient = undefined ;
@@ -174,7 +186,7 @@ export class ResilientWebSocket {
174186 }
175187}
176188
177- async function sleep ( ms : number ) {
189+ async function sleep ( ms : number ) : Promise < void > {
178190 return new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ;
179191}
180192
0 commit comments