@@ -24,7 +24,7 @@ const reportWebSocketError = async (error: string) => {
2424 } catch ( reportError ) {
2525 console . log ( 'Unable to report WebSocket error' , error , reportError ) ;
2626 }
27- }
27+ } ;
2828
2929const openWebSocket = ( currentLocation : Location ) => {
3030 try {
@@ -34,55 +34,86 @@ const openWebSocket = (currentLocation: Location) => {
3434 } catch ( e ) {
3535 // WebSocket URL error or WebSocket is not supported by browser.
3636 // 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 } ` ) ;
3939
4040 return null ;
4141 }
42- }
42+ } ;
4343
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 ) ;
4646
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 ;
5153
52- socket . addEventListener ( 'close' , ( ) => {
53- store . dispatch ( websocketDisconnected ( ) ) ;
54- } ) ;
54+ const connect = ( ) => {
55+ socket = openWebSocket ( window . location ) ;
5556
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+ } ) ;
6363
64- // TODO: reconnect on error? (if ever connected? if < n failures?)
64+ socket . addEventListener ( 'close' , ( event ) => {
65+ store . dispatch ( websocketDisconnected ( ) ) ;
6566
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+ } ) ;
7392 }
74- } ) ;
75- }
93+ } ;
94+
95+ const reconnect = ( ) => {
96+ if ( socket && socket . readyState == socket . OPEN ) {
97+ return ;
98+ }
99+
100+ connect ( ) ;
76101
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+ }
82114
83- next ( action ) ;
115+ next ( action ) ;
116+ } ;
84117 } ;
85- }
86118
87- const sendActionOnWebsocket = ( action : any ) : boolean =>
88- action . type === ActionType . WSExecuteRequest ;
119+ const sendActionOnWebsocket = ( action : any ) : boolean => action . type === ActionType . WSExecuteRequest ;
0 commit comments