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.ts79
1 files changed, 59 insertions, 20 deletions
diff --git a/subprojects/frontend/src/xtext/XtextWebSocketClient.ts b/subprojects/frontend/src/xtext/XtextWebSocketClient.ts
index b69e1d6c..cba6f064 100644
--- a/subprojects/frontend/src/xtext/XtextWebSocketClient.ts
+++ b/subprojects/frontend/src/xtext/XtextWebSocketClient.ts
@@ -6,7 +6,7 @@ import CancelledError from '../utils/CancelledError';
6import PendingTask from '../utils/PendingTask'; 6import PendingTask from '../utils/PendingTask';
7import getLogger from '../utils/getLogger'; 7import getLogger from '../utils/getLogger';
8 8
9import webSocketMachine from './webSocketMachine'; 9import webSocketMachine, { isWebSocketURLLocal } from './webSocketMachine';
10import { 10import {
11 type XtextWebPushService, 11 type XtextWebPushService,
12 XtextResponse, 12 XtextResponse,
@@ -16,7 +16,9 @@ import { PongResult } from './xtextServiceResults';
16 16
17const XTEXT_SUBPROTOCOL_V1 = 'tools.refinery.language.web.xtext.v1'; 17const XTEXT_SUBPROTOCOL_V1 = 'tools.refinery.language.web.xtext.v1';
18 18
19const REQUEST_TIMEOUT = 1000; 19// Use a large enough timeout so that a request can complete successfully
20// even if the browser has throttled the background tab.
21const REQUEST_TIMEOUT = 5000;
20 22
21const log = getLogger('xtext.XtextWebSocketClient'); 23const log = getLogger('xtext.XtextWebSocketClient');
22 24
@@ -52,6 +54,7 @@ export default class XtextWebSocketClient {
52 openWebSocket: ({ webSocketURL }) => this.openWebSocket(webSocketURL), 54 openWebSocket: ({ webSocketURL }) => this.openWebSocket(webSocketURL),
53 closeWebSocket: () => this.closeWebSocket(), 55 closeWebSocket: () => this.closeWebSocket(),
54 notifyReconnect: () => this.onReconnect(), 56 notifyReconnect: () => this.onReconnect(),
57 notifyDisconnect: () => this.onDisconnect(),
55 cancelPendingRequests: () => this.cancelPendingRequests(), 58 cancelPendingRequests: () => this.cancelPendingRequests(),
56 }, 59 },
57 services: { 60 services: {
@@ -141,20 +144,6 @@ export default class XtextWebSocketClient {
141 private readonly onDisconnect: DisconnectHandler, 144 private readonly onDisconnect: DisconnectHandler,
142 private readonly onPush: PushHandler, 145 private readonly onPush: PushHandler,
143 ) { 146 ) {
144 this.interpreter
145 .onTransition((state, event) => {
146 log.trace('WebSocke state transition', state.value, 'on event', event);
147 this.stateAtom.reportChanged();
148 })
149 .start();
150
151 this.updateVisibility();
152 document.addEventListener('visibilitychange', () =>
153 this.updateVisibility(),
154 );
155
156 this.interpreter.send('CONNECT');
157
158 makeAutoObservable< 147 makeAutoObservable<
159 XtextWebSocketClient, 148 XtextWebSocketClient,
160 | 'stateAtom' 149 | 'stateAtom'
@@ -177,6 +166,40 @@ export default class XtextWebSocketClient {
177 }); 166 });
178 } 167 }
179 168
169 start(): void {
170 this.interpreter
171 .onTransition((state, event) => {
172 log.trace('WebSocke state transition', state.value, 'on event', event);
173 this.stateAtom.reportChanged();
174 })
175 .start();
176
177 this.interpreter.send(window.navigator.onLine ? 'ONLINE' : 'OFFLINE');
178 window.addEventListener('offline', () => this.interpreter.send('OFFLINE'));
179 window.addEventListener('online', () => this.interpreter.send('ONLINE'));
180 this.updateVisibility();
181 document.addEventListener('visibilitychange', () =>
182 this.updateVisibility(),
183 );
184 window.addEventListener('pagehide', () =>
185 this.interpreter.send('PAGE_HIDE'),
186 );
187 window.addEventListener('pageshow', () => {
188 this.updateVisibility();
189 this.interpreter.send('PAGE_SHOW');
190 });
191 // https://developer.chrome.com/blog/page-lifecycle-api/#new-features-added-in-chrome-68
192 if ('wasDiscarded' in document) {
193 document.addEventListener('freeze', () =>
194 this.interpreter.send('PAGE_FREEZE'),
195 );
196 document.addEventListener('resume', () =>
197 this.interpreter.send('PAGE_RESUME'),
198 );
199 }
200 this.interpreter.send('CONNECT');
201 }
202
180 get state() { 203 get state() {
181 this.stateAtom.reportObserved(); 204 this.stateAtom.reportObserved();
182 return this.interpreter.state; 205 return this.interpreter.state;
@@ -190,6 +213,19 @@ export default class XtextWebSocketClient {
190 return this.state.matches('connection.socketCreated.open.opened'); 213 return this.state.matches('connection.socketCreated.open.opened');
191 } 214 }
192 215
216 get disconnectedByUser(): boolean {
217 return this.state.matches('connection.disconnected');
218 }
219
220 get networkMissing(): boolean {
221 return (
222 this.state.matches('connection.temporarilyOffline') ||
223 (this.disconnectedByUser &&
224 this.state.matches('network.offline') &&
225 !isWebSocketURLLocal(this.state.context.webSocketURL))
226 );
227 }
228
193 get errors(): string[] { 229 get errors(): string[] {
194 return this.state.context.errors; 230 return this.state.context.errors;
195 } 231 }
@@ -217,9 +253,13 @@ export default class XtextWebSocketClient {
217 const id = nanoid(); 253 const id = nanoid();
218 254
219 const promise = new Promise((resolve, reject) => { 255 const promise = new Promise((resolve, reject) => {
220 const task = new PendingTask(resolve, reject, REQUEST_TIMEOUT, () => 256 const task = new PendingTask(resolve, reject, REQUEST_TIMEOUT, () => {
221 this.removeTask(id), 257 this.interpreter.send({
222 ); 258 type: 'ERROR',
259 message: 'Connection timed out',
260 });
261 this.removeTask(id);
262 });
223 this.pendingRequests.set(id, task); 263 this.pendingRequests.set(id, task);
224 }); 264 });
225 265
@@ -272,7 +312,6 @@ export default class XtextWebSocketClient {
272 } 312 }
273 313
274 private cancelPendingRequests(): void { 314 private cancelPendingRequests(): void {
275 this.onDisconnect();
276 this.pendingRequests.forEach((task) => 315 this.pendingRequests.forEach((task) =>
277 task.reject(new CancelledError('Closing connection')), 316 task.reject(new CancelledError('Closing connection')),
278 ); 317 );