diff options
Diffstat (limited to 'subprojects/frontend/src/xtext/XtextWebSocketClient.ts')
-rw-r--r-- | subprojects/frontend/src/xtext/XtextWebSocketClient.ts | 79 |
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'; | |||
6 | import PendingTask from '../utils/PendingTask'; | 6 | import PendingTask from '../utils/PendingTask'; |
7 | import getLogger from '../utils/getLogger'; | 7 | import getLogger from '../utils/getLogger'; |
8 | 8 | ||
9 | import webSocketMachine from './webSocketMachine'; | 9 | import webSocketMachine, { isWebSocketURLLocal } from './webSocketMachine'; |
10 | import { | 10 | import { |
11 | type XtextWebPushService, | 11 | type XtextWebPushService, |
12 | XtextResponse, | 12 | XtextResponse, |
@@ -16,7 +16,9 @@ import { PongResult } from './xtextServiceResults'; | |||
16 | 16 | ||
17 | const XTEXT_SUBPROTOCOL_V1 = 'tools.refinery.language.web.xtext.v1'; | 17 | const XTEXT_SUBPROTOCOL_V1 = 'tools.refinery.language.web.xtext.v1'; |
18 | 18 | ||
19 | const 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. | ||
21 | const REQUEST_TIMEOUT = 5000; | ||
20 | 22 | ||
21 | const log = getLogger('xtext.XtextWebSocketClient'); | 23 | const 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 | ); |