@@ -24,7 +24,7 @@ const reportWebSocketError = async (error: string) => {
24
24
} catch ( reportError ) {
25
25
console . log ( 'Unable to report WebSocket error' , error , reportError ) ;
26
26
}
27
- }
27
+ } ;
28
28
29
29
const openWebSocket = ( currentLocation : Location ) => {
30
30
try {
@@ -34,55 +34,86 @@ const openWebSocket = (currentLocation: Location) => {
34
34
} catch ( e ) {
35
35
// WebSocket URL error or WebSocket is not supported by browser.
36
36
// Assume it's the second case since URL error is easy to notice.
37
- const detail = ( e instanceof Error ) ? e . toString ( ) : 'An unknown error occurred' ;
38
- reportWebSocketError ( `Could not create the WebSocket: ${ detail } ` )
37
+ const detail = e instanceof Error ? e . toString ( ) : 'An unknown error occurred' ;
38
+ reportWebSocketError ( `Could not create the WebSocket: ${ detail } ` ) ;
39
39
40
40
return null ;
41
41
}
42
- }
42
+ } ;
43
43
44
- export const websocketMiddleware = ( window : Window ) : Middleware => store => {
45
- const socket = openWebSocket ( window . location ) ;
44
+ // https://exponentialbackoffcalculator.com
45
+ const backoffMs = ( n : number ) => Math . min ( 100 * Math . pow ( 2 , n ) , 10000 ) ;
46
46
47
- if ( socket ) {
48
- socket . addEventListener ( 'open' , ( ) => {
49
- store . dispatch ( websocketConnected ( ) ) ;
50
- } ) ;
47
+ export const websocketMiddleware =
48
+ ( window : Window ) : Middleware =>
49
+ ( store ) => {
50
+ let socket : WebSocket | null = null ;
51
+ let wasConnected = false ;
52
+ let reconnectAttempt = 0 ;
51
53
52
- socket . addEventListener ( 'close' , ( ) => {
53
- store . dispatch ( websocketDisconnected ( ) ) ;
54
- } ) ;
54
+ const connect = ( ) => {
55
+ socket = openWebSocket ( window . location ) ;
55
56
56
- socket . addEventListener ( 'error' , ( ) => {
57
- // We cannot get detailed information about the failure
58
- // https://stackoverflow.com/a/31003057/155423
59
- const error = 'Generic WebSocket Error' ;
60
- store . dispatch ( websocketError ( error ) ) ;
61
- reportWebSocketError ( error ) ;
62
- } ) ;
57
+ if ( socket ) {
58
+ socket . addEventListener ( 'open' , ( ) => {
59
+ store . dispatch ( websocketConnected ( ) ) ;
60
+
61
+ wasConnected = true ;
62
+ } ) ;
63
63
64
- // TODO: reconnect on error? (if ever connected? if < n failures?)
64
+ socket . addEventListener ( 'close' , ( event ) => {
65
+ store . dispatch ( websocketDisconnected ( ) ) ;
65
66
66
- socket . addEventListener ( 'message' , ( event ) => {
67
- try {
68
- const rawMessage = JSON . parse ( event . data ) ;
69
- const message = WSMessageResponse . parse ( rawMessage ) ;
70
- store . dispatch ( message ) ;
71
- } catch ( e ) {
72
- console . log ( 'Unable to parse WebSocket message' , event . data , e ) ;
67
+ // Reconnect if we've previously connected
68
+ if ( wasConnected && ! event . wasClean ) {
69
+ wasConnected = false ;
70
+ reconnectAttempt = 0 ;
71
+ reconnect ( ) ;
72
+ }
73
+ } ) ;
74
+
75
+ socket . addEventListener ( 'error' , ( ) => {
76
+ // We cannot get detailed information about the failure
77
+ // https://stackoverflow.com/a/31003057/155423
78
+ const error = 'Generic WebSocket Error' ;
79
+ store . dispatch ( websocketError ( error ) ) ;
80
+ reportWebSocketError ( error ) ;
81
+ } ) ;
82
+
83
+ socket . addEventListener ( 'message' , ( event ) => {
84
+ try {
85
+ const rawMessage = JSON . parse ( event . data ) ;
86
+ const message = WSMessageResponse . parse ( rawMessage ) ;
87
+ store . dispatch ( message ) ;
88
+ } catch ( e ) {
89
+ console . log ( 'Unable to parse WebSocket message' , event . data , e ) ;
90
+ }
91
+ } ) ;
73
92
}
74
- } ) ;
75
- }
93
+ } ;
94
+
95
+ const reconnect = ( ) => {
96
+ if ( socket && socket . readyState == socket . OPEN ) {
97
+ return ;
98
+ }
99
+
100
+ connect ( ) ;
76
101
77
- return next => action => {
78
- if ( socket && socket . readyState == socket . OPEN && sendActionOnWebsocket ( action ) ) {
79
- const message = JSON . stringify ( action ) ;
80
- socket . send ( message ) ;
81
- }
102
+ const delay = backoffMs ( reconnectAttempt ) ;
103
+ reconnectAttempt += 1 ;
104
+ setTimeout ( reconnect , delay ) ;
105
+ } ;
106
+
107
+ connect ( ) ;
108
+
109
+ return ( next ) => ( action ) => {
110
+ if ( socket && socket . readyState == socket . OPEN && sendActionOnWebsocket ( action ) ) {
111
+ const message = JSON . stringify ( action ) ;
112
+ socket . send ( message ) ;
113
+ }
82
114
83
- next ( action ) ;
115
+ next ( action ) ;
116
+ } ;
84
117
} ;
85
- }
86
118
87
- const sendActionOnWebsocket = ( action : any ) : boolean =>
88
- action . type === ActionType . WSExecuteRequest ;
119
+ const sendActionOnWebsocket = ( action : any ) : boolean => action . type === ActionType . WSExecuteRequest ;
0 commit comments