aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/frontend/src/xtext/XtextWebSocketClient.ts
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/frontend/src/xtext/XtextWebSocketClient.ts')
-rw-r--r--subprojects/frontend/src/xtext/XtextWebSocketClient.ts71
1 files changed, 42 insertions, 29 deletions
diff --git a/subprojects/frontend/src/xtext/XtextWebSocketClient.ts b/subprojects/frontend/src/xtext/XtextWebSocketClient.ts
index ceb1f3fd..60bf6ba9 100644
--- a/subprojects/frontend/src/xtext/XtextWebSocketClient.ts
+++ b/subprojects/frontend/src/xtext/XtextWebSocketClient.ts
@@ -17,6 +17,8 @@ const XTEXT_SUBPROTOCOL_V1 = 'tools.refinery.language.web.xtext.v1';
17 17
18const WEBSOCKET_CLOSE_OK = 1000; 18const WEBSOCKET_CLOSE_OK = 1000;
19 19
20const WEBSOCKET_CLOSE_GOING_AWAY = 1001;
21
20const RECONNECT_DELAY_MS = [200, 1000, 5000, 30_000]; 22const RECONNECT_DELAY_MS = [200, 1000, 5000, 30_000];
21 23
22const MAX_RECONNECT_DELAY_MS = 24const MAX_RECONNECT_DELAY_MS =
@@ -44,9 +46,9 @@ enum State {
44 Opening, 46 Opening,
45 TabVisible, 47 TabVisible,
46 TabHiddenIdle, 48 TabHiddenIdle,
47 TabHiddenWaiting, 49 TabHiddenWaitingToClose,
48 Error, 50 Error,
49 TimedOut, 51 ClosedDueToInactivity,
50} 52}
51 53
52export default class XtextWebSocketClient { 54export default class XtextWebSocketClient {
@@ -86,14 +88,16 @@ export default class XtextWebSocketClient {
86 } 88 }
87 89
88 private get isLogicallyClosed(): boolean { 90 private get isLogicallyClosed(): boolean {
89 return this.state === State.Error || this.state === State.TimedOut; 91 return (
92 this.state === State.Error || this.state === State.ClosedDueToInactivity
93 );
90 } 94 }
91 95
92 get isOpen(): boolean { 96 get isOpen(): boolean {
93 return ( 97 return (
94 this.state === State.TabVisible || 98 this.state === State.TabVisible ||
95 this.state === State.TabHiddenIdle || 99 this.state === State.TabHiddenIdle ||
96 this.state === State.TabHiddenWaiting 100 this.state === State.TabHiddenWaitingToClose
97 ); 101 );
98 } 102 }
99 103
@@ -134,11 +138,15 @@ export default class XtextWebSocketClient {
134 this.handleMessage(event.data); 138 this.handleMessage(event.data);
135 }); 139 });
136 this.connection.addEventListener('close', (event) => { 140 this.connection.addEventListener('close', (event) => {
137 if ( 141 const closedOnRequest =
138 this.isLogicallyClosed && 142 this.isLogicallyClosed &&
139 event.code === WEBSOCKET_CLOSE_OK && 143 event.code === WEBSOCKET_CLOSE_OK &&
140 this.pendingRequests.size === 0 144 this.pendingRequests.size === 0;
141 ) { 145 const closedOnNavigation = event.code === WEBSOCKET_CLOSE_GOING_AWAY;
146 if (closedOnNavigation) {
147 this.state = State.ClosedDueToInactivity;
148 }
149 if (closedOnRequest || closedOnNavigation) {
142 log.info('Websocket closed'); 150 log.info('Websocket closed');
143 return; 151 return;
144 } 152 }
@@ -157,12 +165,12 @@ export default class XtextWebSocketClient {
157 this.idleTimer.cancel(); 165 this.idleTimer.cancel();
158 if ( 166 if (
159 this.state === State.TabHiddenIdle || 167 this.state === State.TabHiddenIdle ||
160 this.state === State.TabHiddenWaiting 168 this.state === State.TabHiddenWaitingToClose
161 ) { 169 ) {
162 this.handleTabVisibleConnected(); 170 this.handleTabVisibleConnected();
163 return; 171 return;
164 } 172 }
165 if (this.state === State.TimedOut) { 173 if (this.state === State.ClosedDueToInactivity) {
166 this.reconnect(); 174 this.reconnect();
167 } 175 }
168 } 176 }
@@ -181,19 +189,19 @@ export default class XtextWebSocketClient {
181 private handleIdleTimeout() { 189 private handleIdleTimeout() {
182 log.trace('Waiting for pending tasks before disconnect'); 190 log.trace('Waiting for pending tasks before disconnect');
183 if (this.state === State.TabHiddenIdle) { 191 if (this.state === State.TabHiddenIdle) {
184 this.state = State.TabHiddenWaiting; 192 this.state = State.TabHiddenWaitingToClose;
185 this.handleWaitingForDisconnect(); 193 this.handleWaitingForDisconnect();
186 } 194 }
187 } 195 }
188 196
189 private handleWaitingForDisconnect() { 197 private handleWaitingForDisconnect() {
190 if (this.state !== State.TabHiddenWaiting) { 198 if (this.state !== State.TabHiddenWaitingToClose) {
191 return; 199 return;
192 } 200 }
193 const pending = this.pendingRequests.size; 201 const pending = this.pendingRequests.size;
194 if (pending === 0) { 202 if (pending === 0) {
195 log.info('Closing idle websocket'); 203 log.info('Closing idle websocket');
196 this.state = State.TimedOut; 204 this.state = State.ClosedDueToInactivity;
197 this.closeConnection(1000, 'idle timeout'); 205 this.closeConnection(1000, 'idle timeout');
198 return; 206 return;
199 } 207 }
@@ -334,17 +342,31 @@ export default class XtextWebSocketClient {
334 if (this.isLogicallyClosed) { 342 if (this.isLogicallyClosed) {
335 return; 343 return;
336 } 344 }
337 this.abortPendingRequests();
338 this.closeConnection(1000, 'reconnecting due to error');
339 log.error('Reconnecting after delay due to error');
340 this.handleErrorState();
341 }
342
343 private abortPendingRequests() {
344 this.pendingRequests.forEach((request) => { 345 this.pendingRequests.forEach((request) => {
345 request.reject(new Error('Websocket disconnect')); 346 request.reject(new Error('Websocket disconnect'));
346 }); 347 });
347 this.pendingRequests.clear(); 348 this.pendingRequests.clear();
349 this.closeConnection(1000, 'reconnecting due to error');
350 if (this.state === State.Error) {
351 // We are already handling this error condition.
352 return;
353 }
354 if (
355 this.state === State.TabHiddenIdle ||
356 this.state === State.TabHiddenWaitingToClose
357 ) {
358 log.error('Will reconned due to error once the tab becomes visible');
359 this.idleTimer.cancel();
360 this.state = State.ClosedDueToInactivity;
361 return;
362 }
363 log.error('Reconnecting after delay due to error');
364 this.state = State.Error;
365 this.reconnectTryCount += 1;
366 const delay =
367 RECONNECT_DELAY_MS[this.reconnectTryCount - 1] ?? MAX_RECONNECT_DELAY_MS;
368 log.info('Reconnecting in', delay, 'ms');
369 this.reconnectTimer.schedule(delay);
348 } 370 }
349 371
350 private closeConnection(code: number, reason: string) { 372 private closeConnection(code: number, reason: string) {
@@ -355,22 +377,13 @@ export default class XtextWebSocketClient {
355 } 377 }
356 } 378 }
357 379
358 private handleErrorState() {
359 this.state = State.Error;
360 this.reconnectTryCount += 1;
361 const delay =
362 RECONNECT_DELAY_MS[this.reconnectTryCount - 1] || MAX_RECONNECT_DELAY_MS;
363 log.info('Reconnecting in', delay, 'ms');
364 this.reconnectTimer.schedule(delay);
365 }
366
367 private handleReconnect() { 380 private handleReconnect() {
368 if (this.state !== State.Error) { 381 if (this.state !== State.Error) {
369 log.error('Unexpected reconnect in', this.state); 382 log.error('Unexpected reconnect in', this.state);
370 return; 383 return;
371 } 384 }
372 if (document.visibilityState === 'hidden') { 385 if (document.visibilityState === 'hidden') {
373 this.state = State.TimedOut; 386 this.state = State.ClosedDueToInactivity;
374 } else { 387 } else {
375 this.reconnect(); 388 this.reconnect();
376 } 389 }