1
1
import { SIG_EXPIRATION_BUFFER_MS } from '../constants.js'
2
2
import type { RenegadeConfig } from '../createConfig.js'
3
+ import {
4
+ SocketClosedError ,
5
+ WebSocketConnectionError ,
6
+ WebSocketRequestError ,
7
+ } from '../errors/websocket.js'
3
8
import { addExpiringAuthToHeaders } from './http.js'
4
9
5
10
export enum AuthType {
@@ -48,6 +53,24 @@ export class RelayerWebsocket {
48
53
49
54
private ws : WebSocket | null = null
50
55
56
+ private handleOpen = ( event : Event ) => {
57
+ if ( ! this . ws ) return
58
+ const message = this . buildSubscriptionMessage ( )
59
+ this . request ( message )
60
+
61
+ return this . onopenCallback ?. call ( this . ws , event )
62
+ }
63
+
64
+ private handleClose = ( event : CloseEvent ) => {
65
+ this . cleanup ( )
66
+ return this . oncloseCallback ?. call ( this . ws ! , event )
67
+ }
68
+
69
+ private handleError = async ( event : Event ) => {
70
+ this . cleanup ( )
71
+ return this . onerrorCallback ?. call ( this . ws ! , event )
72
+ }
73
+
51
74
constructor ( params : RelayerWebsocketParams ) {
52
75
this . config = params . config
53
76
this . topic = params . topic
@@ -62,33 +85,35 @@ export class RelayerWebsocket {
62
85
// | Public API |
63
86
// --------------
64
87
65
- public connect ( ) : void {
88
+ public async connect ( ) : Promise < void > {
66
89
if ( this . ws ) {
67
90
throw new Error (
68
91
'WebSocket connection attempt aborted: already connected.' ,
69
92
)
70
93
}
71
94
72
- const instance = this
73
- instance . ws = new WebSocket ( this . config . getWebsocketBaseUrl ( ) )
74
-
75
- instance . ws . onopen = function ( this : WebSocket , event : Event ) {
76
- const message = instance . buildSubscriptionMessage ( )
77
- this . send ( JSON . stringify ( message ) )
78
-
79
- return instance . onopenCallback ?. call ( this , event )
80
- }
81
-
82
- instance . ws . onmessage = instance . onmessage
83
-
84
- instance . ws . onclose = function ( this : WebSocket , event : CloseEvent ) {
85
- instance . cleanup ( )
86
- return instance . oncloseCallback ?. call ( this , event )
87
- }
88
-
89
- instance . ws . onerror = function ( this : WebSocket , event : Event ) {
90
- instance . cleanup ( )
91
- return instance . onerrorCallback ?. call ( this , event )
95
+ const WebSocket = await import ( 'isows' ) . then ( ( module ) => module . WebSocket )
96
+ const url = this . config . getWebsocketBaseUrl ( )
97
+ this . ws = new WebSocket ( url )
98
+
99
+ this . ws . addEventListener ( 'open' , this . handleOpen )
100
+ this . ws . addEventListener ( 'message' , this . onmessage )
101
+ this . ws . addEventListener ( 'close' , this . handleClose )
102
+ this . ws . addEventListener ( 'error' , this . handleError )
103
+
104
+ // Wait for the socket to open.
105
+ if ( this . ws ?. readyState === WebSocket . CONNECTING ) {
106
+ await new Promise ( ( resolve , reject ) => {
107
+ if ( ! this . ws ) return
108
+ this . ws . onopen = ( event ) => resolve ( event )
109
+ this . ws . onerror = ( error ) =>
110
+ reject (
111
+ new WebSocketConnectionError ( {
112
+ url,
113
+ cause : error as unknown as Error ,
114
+ } ) ,
115
+ )
116
+ } )
92
117
}
93
118
}
94
119
@@ -98,7 +123,7 @@ export class RelayerWebsocket {
98
123
}
99
124
100
125
const message = this . buildUnsubscriptionMessage ( )
101
- this . ws . send ( JSON . stringify ( message ) )
126
+ this . request ( message )
102
127
103
128
this . ws . close ( )
104
129
}
@@ -107,6 +132,21 @@ export class RelayerWebsocket {
107
132
// | Private API |
108
133
// ---------------
109
134
135
+ private request ( message : SubscriptionMessage | UnsubscriptionMessage ) : void {
136
+ if (
137
+ this . ws ?. readyState === this . ws ?. CLOSED ||
138
+ this . ws ?. readyState === this . ws ?. CLOSING
139
+ ) {
140
+ throw new WebSocketRequestError ( {
141
+ body : message ,
142
+ url : this . ws ?. url || '' ,
143
+ cause : new SocketClosedError ( { url : this . ws ?. url } ) ,
144
+ } )
145
+ }
146
+
147
+ this . ws ?. send ( JSON . stringify ( message ) )
148
+ }
149
+
110
150
private buildSubscriptionMessage ( ) : SubscriptionMessage {
111
151
const body = {
112
152
method : 'subscribe' as const ,
@@ -147,6 +187,15 @@ export class RelayerWebsocket {
147
187
}
148
188
149
189
private cleanup ( ) : void {
190
+ // Remove all event listeners before nullifying the reference
191
+ if ( this . ws ) {
192
+ this . ws . removeEventListener ( 'open' , this . handleOpen )
193
+ this . ws . removeEventListener ( 'message' , this . onmessage )
194
+ this . ws . removeEventListener ( 'close' , this . handleClose )
195
+ this . ws . removeEventListener ( 'error' , this . handleError )
196
+ }
197
+
198
+ // Nullify the WebSocket instance
150
199
this . ws = null
151
200
}
152
201
}
0 commit comments