diff options
author | 2022-11-10 14:35:24 +0100 | |
---|---|---|
committer | 2022-11-10 14:35:24 +0100 | |
commit | 4971c864a603e0c01f7ad84a23697905d096283b (patch) | |
tree | fc2ed2ced39aa50aa98602ad4bd2a9d877577dcd /subprojects/frontend | |
parent | refactor: rename CallKind to Polarity (diff) | |
download | refinery-4971c864a603e0c01f7ad84a23697905d096283b.tar.gz refinery-4971c864a603e0c01f7ad84a23697905d096283b.tar.zst refinery-4971c864a603e0c01f7ad84a23697905d096283b.zip |
feat(web): backend URL configuration
To point the frontend to a backend server, update the config.json
file in the website root.
The config.json is generated automatically in debug mode and when
running from a standalone jar.
Diffstat (limited to 'subprojects/frontend')
-rw-r--r-- | subprojects/frontend/package.json | 20 | ||||
-rw-r--r-- | subprojects/frontend/src/RootStore.ts | 21 | ||||
-rw-r--r-- | subprojects/frontend/src/xtext/XtextClient.ts | 3 | ||||
-rw-r--r-- | subprojects/frontend/src/xtext/XtextWebSocketClient.ts | 65 | ||||
-rw-r--r-- | subprojects/frontend/src/xtext/fetchBackendConfig.ts | 16 | ||||
-rw-r--r-- | subprojects/frontend/src/xtext/webSocketMachine.ts | 36 | ||||
-rw-r--r-- | subprojects/frontend/types/ImportMeta.d.ts | 1 | ||||
-rw-r--r-- | subprojects/frontend/vite.config.ts | 25 |
8 files changed, 99 insertions, 88 deletions
diff --git a/subprojects/frontend/package.json b/subprojects/frontend/package.json index 10de68ee..ed4b101b 100644 --- a/subprojects/frontend/package.json +++ b/subprojects/frontend/package.json | |||
@@ -29,21 +29,21 @@ | |||
29 | "@codemirror/lint": "^6.0.0", | 29 | "@codemirror/lint": "^6.0.0", |
30 | "@codemirror/search": "^6.2.2", | 30 | "@codemirror/search": "^6.2.2", |
31 | "@codemirror/state": "^6.1.2", | 31 | "@codemirror/state": "^6.1.2", |
32 | "@codemirror/view": "^6.4.0", | 32 | "@codemirror/view": "^6.4.1", |
33 | "@emotion/react": "^11.10.5", | 33 | "@emotion/react": "^11.10.5", |
34 | "@emotion/styled": "^11.10.5", | 34 | "@emotion/styled": "^11.10.5", |
35 | "@fontsource/inter": "^4.5.14", | 35 | "@fontsource/inter": "^4.5.14", |
36 | "@fontsource/jetbrains-mono": "^4.5.11", | 36 | "@fontsource/jetbrains-mono": "^4.5.11", |
37 | "@lezer/common": "^1.0.1", | 37 | "@lezer/common": "^1.0.1", |
38 | "@lezer/highlight": "^1.1.2", | 38 | "@lezer/highlight": "^1.1.2", |
39 | "@lezer/lr": "^1.2.3", | 39 | "@lezer/lr": "^1.2.4", |
40 | "@material-icons/svg": "^1.0.33", | 40 | "@material-icons/svg": "^1.0.33", |
41 | "@mui/icons-material": "5.10.9", | 41 | "@mui/icons-material": "5.10.9", |
42 | "@mui/material": "5.10.12", | 42 | "@mui/material": "5.10.13", |
43 | "ansi-styles": "^6.2.1", | 43 | "ansi-styles": "^6.2.1", |
44 | "escape-string-regexp": "^5.0.0", | 44 | "escape-string-regexp": "^5.0.0", |
45 | "lodash-es": "^4.17.21", | 45 | "lodash-es": "^4.17.21", |
46 | "loglevel": "^1.8.0", | 46 | "loglevel": "^1.8.1", |
47 | "loglevel-plugin-prefix": "^0.8.4", | 47 | "loglevel-plugin-prefix": "^0.8.4", |
48 | "mobx": "^6.6.2", | 48 | "mobx": "^6.6.2", |
49 | "mobx-react-lite": "^3.4.0", | 49 | "mobx-react-lite": "^3.4.0", |
@@ -56,7 +56,7 @@ | |||
56 | "zod": "^3.19.1" | 56 | "zod": "^3.19.1" |
57 | }, | 57 | }, |
58 | "devDependencies": { | 58 | "devDependencies": { |
59 | "@lezer/generator": "^1.1.1", | 59 | "@lezer/generator": "^1.1.3", |
60 | "@types/eslint": "^8.4.10", | 60 | "@types/eslint": "^8.4.10", |
61 | "@types/html-minifier-terser": "^7.0.0", | 61 | "@types/html-minifier-terser": "^7.0.0", |
62 | "@types/lodash-es": "^4.17.6", | 62 | "@types/lodash-es": "^4.17.6", |
@@ -65,12 +65,12 @@ | |||
65 | "@types/prettier": "^2.7.1", | 65 | "@types/prettier": "^2.7.1", |
66 | "@types/react": "^18.0.25", | 66 | "@types/react": "^18.0.25", |
67 | "@types/react-dom": "^18.0.8", | 67 | "@types/react-dom": "^18.0.8", |
68 | "@typescript-eslint/eslint-plugin": "^5.42.0", | 68 | "@typescript-eslint/eslint-plugin": "^5.42.1", |
69 | "@typescript-eslint/parser": "^5.42.0", | 69 | "@typescript-eslint/parser": "^5.42.1", |
70 | "@vitejs/plugin-react": "^2.2.0", | 70 | "@vitejs/plugin-react": "^2.2.0", |
71 | "@xstate/cli": "^0.3.3", | 71 | "@xstate/cli": "^0.3.3", |
72 | "cross-env": "^7.0.3", | 72 | "cross-env": "^7.0.3", |
73 | "eslint": "^8.26.0", | 73 | "eslint": "^8.27.0", |
74 | "eslint-config-airbnb": "^19.0.4", | 74 | "eslint-config-airbnb": "^19.0.4", |
75 | "eslint-config-airbnb-typescript": "^17.0.0", | 75 | "eslint-config-airbnb-typescript": "^17.0.0", |
76 | "eslint-config-prettier": "^8.5.0", | 76 | "eslint-config-prettier": "^8.5.0", |
@@ -84,9 +84,9 @@ | |||
84 | "html-minifier-terser": "^7.0.0", | 84 | "html-minifier-terser": "^7.0.0", |
85 | "prettier": "^2.7.1", | 85 | "prettier": "^2.7.1", |
86 | "typescript": "4.8.4", | 86 | "typescript": "4.8.4", |
87 | "vite": "^3.2.2", | 87 | "vite": "^3.2.3", |
88 | "vite-plugin-inject-preload": "^1.1.0", | 88 | "vite-plugin-inject-preload": "^1.1.0", |
89 | "vite-plugin-pwa": "^0.13.2", | 89 | "vite-plugin-pwa": "^0.13.3", |
90 | "workbox-window": "^6.5.4" | 90 | "workbox-window": "^6.5.4" |
91 | } | 91 | } |
92 | } | 92 | } |
diff --git a/subprojects/frontend/src/RootStore.ts b/subprojects/frontend/src/RootStore.ts index 54a80501..2e76d66d 100644 --- a/subprojects/frontend/src/RootStore.ts +++ b/subprojects/frontend/src/RootStore.ts | |||
@@ -23,18 +23,17 @@ export default class RootStore { | |||
23 | pwaStore: false, | 23 | pwaStore: false, |
24 | themeStore: false, | 24 | themeStore: false, |
25 | }); | 25 | }); |
26 | import('./editor/EditorStore') | 26 | (async () => { |
27 | .then(({ default: EditorStore }) => { | 27 | const { default: EditorStore } = await import('./editor/EditorStore'); |
28 | runInAction(() => { | 28 | runInAction(() => { |
29 | if (this.disposed) { | 29 | if (this.disposed) { |
30 | return; | 30 | return; |
31 | } | 31 | } |
32 | this.editorStore = new EditorStore(initialValue, this.pwaStore); | 32 | this.editorStore = new EditorStore(initialValue, this.pwaStore); |
33 | }); | ||
34 | }) | ||
35 | .catch((error) => { | ||
36 | log.error('Failed to load EditorStore', error); | ||
37 | }); | 33 | }); |
34 | })().catch((error) => { | ||
35 | log.error('Failed to load EditorStore', error); | ||
36 | }); | ||
38 | } | 37 | } |
39 | 38 | ||
40 | dispose(): void { | 39 | dispose(): void { |
diff --git a/subprojects/frontend/src/xtext/XtextClient.ts b/subprojects/frontend/src/xtext/XtextClient.ts index e7d26ae6..14fb2430 100644 --- a/subprojects/frontend/src/xtext/XtextClient.ts +++ b/subprojects/frontend/src/xtext/XtextClient.ts | |||
@@ -35,8 +35,7 @@ export default class XtextClient { | |||
35 | this.webSocketClient = new XtextWebSocketClient( | 35 | this.webSocketClient = new XtextWebSocketClient( |
36 | () => this.onReconnect(), | 36 | () => this.onReconnect(), |
37 | () => this.onDisconnect(), | 37 | () => this.onDisconnect(), |
38 | (resource, stateId, service, push) => | 38 | this.onPush.bind(this), |
39 | this.onPush(resource, stateId, service, push), | ||
40 | ); | 39 | ); |
41 | this.updateService = new UpdateService(store, this.webSocketClient); | 40 | this.updateService = new UpdateService(store, this.webSocketClient); |
42 | this.contentAssistService = new ContentAssistService(this.updateService); | 41 | this.contentAssistService = new ContentAssistService(this.updateService); |
diff --git a/subprojects/frontend/src/xtext/XtextWebSocketClient.ts b/subprojects/frontend/src/xtext/XtextWebSocketClient.ts index a39620cb..6b734546 100644 --- a/subprojects/frontend/src/xtext/XtextWebSocketClient.ts +++ b/subprojects/frontend/src/xtext/XtextWebSocketClient.ts | |||
@@ -7,7 +7,8 @@ import CancelledError from '../utils/CancelledError'; | |||
7 | import PendingTask from '../utils/PendingTask'; | 7 | import PendingTask from '../utils/PendingTask'; |
8 | import getLogger from '../utils/getLogger'; | 8 | import getLogger from '../utils/getLogger'; |
9 | 9 | ||
10 | import webSocketMachine, { isWebSocketURLLocal } from './webSocketMachine'; | 10 | import fetchBackendConfig from './fetchBackendConfig'; |
11 | import webSocketMachine from './webSocketMachine'; | ||
11 | import { | 12 | import { |
12 | type XtextWebPushService, | 13 | type XtextWebPushService, |
13 | XtextResponse, | 14 | XtextResponse, |
@@ -42,26 +43,18 @@ export default class XtextWebSocketClient { | |||
42 | private readonly pendingRequests = new Map<string, PendingTask<unknown>>(); | 43 | private readonly pendingRequests = new Map<string, PendingTask<unknown>>(); |
43 | 44 | ||
44 | private readonly interpreter = interpret( | 45 | private readonly interpreter = interpret( |
45 | webSocketMachine | 46 | webSocketMachine.withConfig({ |
46 | .withContext({ | 47 | actions: { |
47 | ...webSocketMachine.context, | 48 | openWebSocket: () => this.openWebSocket(), |
48 | webSocketURL: `${window.location.origin.replace( | 49 | closeWebSocket: () => this.closeWebSocket(), |
49 | /^http/, | 50 | notifyReconnect: () => this.onReconnect(), |
50 | 'ws', | 51 | notifyDisconnect: () => this.onDisconnect(), |
51 | )}/xtext-service`, | 52 | cancelPendingRequests: () => this.cancelPendingRequests(), |
52 | }) | 53 | }, |
53 | .withConfig({ | 54 | services: { |
54 | actions: { | 55 | pingService: () => this.sendPing(), |
55 | openWebSocket: ({ webSocketURL }) => this.openWebSocket(webSocketURL), | 56 | }, |
56 | closeWebSocket: () => this.closeWebSocket(), | 57 | }), |
57 | notifyReconnect: () => this.onReconnect(), | ||
58 | notifyDisconnect: () => this.onDisconnect(), | ||
59 | cancelPendingRequests: () => this.cancelPendingRequests(), | ||
60 | }, | ||
61 | services: { | ||
62 | pingService: () => this.sendPing(), | ||
63 | }, | ||
64 | }), | ||
65 | { | 58 | { |
66 | logger: log.log.bind(log), | 59 | logger: log.log.bind(log), |
67 | }, | 60 | }, |
@@ -151,6 +144,7 @@ export default class XtextWebSocketClient { | |||
151 | | 'webSocket' | 144 | | 'webSocket' |
152 | | 'interpreter' | 145 | | 'interpreter' |
153 | | 'openListener' | 146 | | 'openListener' |
147 | | 'openWebSocket' | ||
154 | | 'errorListener' | 148 | | 'errorListener' |
155 | | 'closeListener' | 149 | | 'closeListener' |
156 | | 'messageListener' | 150 | | 'messageListener' |
@@ -160,6 +154,7 @@ export default class XtextWebSocketClient { | |||
160 | webSocket: observable.ref, | 154 | webSocket: observable.ref, |
161 | interpreter: false, | 155 | interpreter: false, |
162 | openListener: false, | 156 | openListener: false, |
157 | openWebSocket: false, | ||
163 | errorListener: false, | 158 | errorListener: false, |
164 | closeListener: false, | 159 | closeListener: false, |
165 | messageListener: false, | 160 | messageListener: false, |
@@ -221,9 +216,7 @@ export default class XtextWebSocketClient { | |||
221 | get networkMissing(): boolean { | 216 | get networkMissing(): boolean { |
222 | return ( | 217 | return ( |
223 | this.state.matches('connection.temporarilyOffline') || | 218 | this.state.matches('connection.temporarilyOffline') || |
224 | (this.disconnectedByUser && | 219 | (this.disconnectedByUser && this.state.matches('network.offline')) |
225 | this.state.matches('network.offline') && | ||
226 | !isWebSocketURLLocal(this.state.context.webSocketURL)) | ||
227 | ); | 220 | ); |
228 | } | 221 | } |
229 | 222 | ||
@@ -275,17 +268,27 @@ export default class XtextWebSocketClient { | |||
275 | this.interpreter.send(document.hidden ? 'TAB_HIDDEN' : 'TAB_VISIBLE'); | 268 | this.interpreter.send(document.hidden ? 'TAB_HIDDEN' : 'TAB_VISIBLE'); |
276 | } | 269 | } |
277 | 270 | ||
278 | private openWebSocket(webSocketURL: string | undefined): void { | 271 | private openWebSocket(): void { |
279 | if (this.webSocket !== undefined) { | 272 | if (this.webSocket !== undefined) { |
280 | throw new Error('WebSocket already open'); | 273 | throw new Error('WebSocket already open'); |
281 | } | 274 | } |
282 | 275 | ||
283 | if (webSocketURL === undefined) { | ||
284 | throw new Error('URL not configured'); | ||
285 | } | ||
286 | |||
287 | log.debug('Creating WebSocket'); | 276 | log.debug('Creating WebSocket'); |
288 | 277 | ||
278 | (async () => { | ||
279 | const { webSocketURL } = await fetchBackendConfig(); | ||
280 | this.openWebSocketWithURL(webSocketURL); | ||
281 | })().catch((error) => { | ||
282 | log.error('Error while initializing connection', error); | ||
283 | const message = error instanceof Error ? error.message : String(error); | ||
284 | this.interpreter.send({ | ||
285 | type: 'ERROR', | ||
286 | message, | ||
287 | }); | ||
288 | }); | ||
289 | } | ||
290 | |||
291 | private openWebSocketWithURL(webSocketURL: string): void { | ||
289 | this.webSocket = new WebSocket(webSocketURL, XTEXT_SUBPROTOCOL_V1); | 292 | this.webSocket = new WebSocket(webSocketURL, XTEXT_SUBPROTOCOL_V1); |
290 | this.webSocket.addEventListener('open', this.openListener); | 293 | this.webSocket.addEventListener('open', this.openListener); |
291 | this.webSocket.addEventListener('close', this.closeListener); | 294 | this.webSocket.addEventListener('close', this.closeListener); |
@@ -295,7 +298,9 @@ export default class XtextWebSocketClient { | |||
295 | 298 | ||
296 | private closeWebSocket() { | 299 | private closeWebSocket() { |
297 | if (this.webSocket === undefined) { | 300 | if (this.webSocket === undefined) { |
298 | throw new Error('WebSocket already closed'); | 301 | // We might get here when there is a network error before the socket is initialized |
302 | // and we don't have to do anything to close it. | ||
303 | return; | ||
299 | } | 304 | } |
300 | 305 | ||
301 | log.debug('Closing WebSocket'); | 306 | log.debug('Closing WebSocket'); |
diff --git a/subprojects/frontend/src/xtext/fetchBackendConfig.ts b/subprojects/frontend/src/xtext/fetchBackendConfig.ts new file mode 100644 index 00000000..f8087a70 --- /dev/null +++ b/subprojects/frontend/src/xtext/fetchBackendConfig.ts | |||
@@ -0,0 +1,16 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-redeclare -- Declare types with their companion objects */ | ||
2 | |||
3 | import { z } from 'zod'; | ||
4 | |||
5 | export const BackendConfig = z.object({ | ||
6 | webSocketURL: z.string().url(), | ||
7 | }); | ||
8 | |||
9 | export type BackendConfig = z.infer<typeof BackendConfig>; | ||
10 | |||
11 | export default async function fetchBackendConfig(): Promise<BackendConfig> { | ||
12 | const configURL = `${import.meta.env.BASE_URL}config.json`; | ||
13 | const response = await fetch(configURL); | ||
14 | const rawConfig = (await response.json()) as unknown; | ||
15 | return BackendConfig.parse(rawConfig); | ||
16 | } | ||
diff --git a/subprojects/frontend/src/xtext/webSocketMachine.ts b/subprojects/frontend/src/xtext/webSocketMachine.ts index b5b40d11..5f6bc604 100644 --- a/subprojects/frontend/src/xtext/webSocketMachine.ts +++ b/subprojects/frontend/src/xtext/webSocketMachine.ts | |||
@@ -6,12 +6,10 @@ const { raise } = actions; | |||
6 | const ERROR_WAIT_TIMES = ['200', '1s', '5s', '30s'].map(ms); | 6 | const ERROR_WAIT_TIMES = ['200', '1s', '5s', '30s'].map(ms); |
7 | 7 | ||
8 | export interface WebSocketContext { | 8 | export interface WebSocketContext { |
9 | webSocketURL: string | undefined; | ||
10 | errors: string[]; | 9 | errors: string[]; |
11 | } | 10 | } |
12 | 11 | ||
13 | export type WebSocketEvent = | 12 | export type WebSocketEvent = |
14 | | { type: 'CONFIGURE'; webSocketURL: string } | ||
15 | | { type: 'CONNECT' } | 13 | | { type: 'CONNECT' } |
16 | | { type: 'DISCONNECT' } | 14 | | { type: 'DISCONNECT' } |
17 | | { type: 'OPENED' } | 15 | | { type: 'OPENED' } |
@@ -25,24 +23,6 @@ export type WebSocketEvent = | |||
25 | | { type: 'OFFLINE' } | 23 | | { type: 'OFFLINE' } |
26 | | { type: 'ERROR'; message: string }; | 24 | | { type: 'ERROR'; message: string }; |
27 | 25 | ||
28 | export function isWebSocketURLLocal(webSocketURL: string | undefined): boolean { | ||
29 | if (webSocketURL === undefined) { | ||
30 | return false; | ||
31 | } | ||
32 | let hostname: string; | ||
33 | try { | ||
34 | ({ hostname } = new URL(webSocketURL)); | ||
35 | } catch { | ||
36 | return false; | ||
37 | } | ||
38 | // https://stackoverflow.com/a/57949518 | ||
39 | return ( | ||
40 | hostname === 'localhost' || | ||
41 | hostname === '[::1]' || | ||
42 | hostname.match(/^127(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d\d?)){3}$/) !== null | ||
43 | ); | ||
44 | } | ||
45 | |||
46 | export default createMachine( | 26 | export default createMachine( |
47 | { | 27 | { |
48 | id: 'webSocket', | 28 | id: 'webSocket', |
@@ -53,7 +33,6 @@ export default createMachine( | |||
53 | }, | 33 | }, |
54 | tsTypes: {} as import('./webSocketMachine.typegen').Typegen0, | 34 | tsTypes: {} as import('./webSocketMachine.typegen').Typegen0, |
55 | context: { | 35 | context: { |
56 | webSocketURL: undefined, | ||
57 | errors: [], | 36 | errors: [], |
58 | }, | 37 | }, |
59 | type: 'parallel', | 38 | type: 'parallel', |
@@ -65,16 +44,12 @@ export default createMachine( | |||
65 | disconnected: { | 44 | disconnected: { |
66 | id: 'disconnected', | 45 | id: 'disconnected', |
67 | entry: ['clearErrors', 'notifyDisconnect'], | 46 | entry: ['clearErrors', 'notifyDisconnect'], |
68 | on: { | ||
69 | CONFIGURE: { actions: 'configure' }, | ||
70 | }, | ||
71 | }, | 47 | }, |
72 | timedOut: { | 48 | timedOut: { |
73 | id: 'timedOut', | 49 | id: 'timedOut', |
74 | always: [ | 50 | always: [ |
75 | { | 51 | { |
76 | target: 'temporarilyOffline', | 52 | target: 'temporarilyOffline', |
77 | cond: 'needsNetwork', | ||
78 | in: '#offline', | 53 | in: '#offline', |
79 | }, | 54 | }, |
80 | { target: 'socketCreated', in: '#tabVisible' }, | 55 | { target: 'socketCreated', in: '#tabVisible' }, |
@@ -89,7 +64,6 @@ export default createMachine( | |||
89 | always: [ | 64 | always: [ |
90 | { | 65 | { |
91 | target: 'temporarilyOffline', | 66 | target: 'temporarilyOffline', |
92 | cond: 'needsNetwork', | ||
93 | in: '#offline', | 67 | in: '#offline', |
94 | }, | 68 | }, |
95 | ], | 69 | ], |
@@ -183,7 +157,7 @@ export default createMachine( | |||
183 | }, | 157 | }, |
184 | }, | 158 | }, |
185 | on: { | 159 | on: { |
186 | CONNECT: { target: '.timedOut', cond: 'hasWebSocketURL' }, | 160 | CONNECT: '.timedOut', |
187 | DISCONNECT: '.disconnected', | 161 | DISCONNECT: '.disconnected', |
188 | }, | 162 | }, |
189 | }, | 163 | }, |
@@ -224,10 +198,6 @@ export default createMachine( | |||
224 | }, | 198 | }, |
225 | }, | 199 | }, |
226 | { | 200 | { |
227 | guards: { | ||
228 | hasWebSocketURL: ({ webSocketURL }) => webSocketURL !== undefined, | ||
229 | needsNetwork: ({ webSocketURL }) => !isWebSocketURLLocal(webSocketURL), | ||
230 | }, | ||
231 | delays: { | 201 | delays: { |
232 | IDLE_TIMEOUT: ms('5m'), | 202 | IDLE_TIMEOUT: ms('5m'), |
233 | OPEN_TIMEOUT: ms('10s'), | 203 | OPEN_TIMEOUT: ms('10s'), |
@@ -239,10 +209,6 @@ export default createMachine( | |||
239 | }, | 209 | }, |
240 | }, | 210 | }, |
241 | actions: { | 211 | actions: { |
242 | configure: assign((context, { webSocketURL }) => ({ | ||
243 | ...context, | ||
244 | webSocketURL, | ||
245 | })), | ||
246 | pushError: assign((context, { message }) => ({ | 212 | pushError: assign((context, { message }) => ({ |
247 | ...context, | 213 | ...context, |
248 | errors: [...context.errors, message], | 214 | errors: [...context.errors, message], |
diff --git a/subprojects/frontend/types/ImportMeta.d.ts b/subprojects/frontend/types/ImportMeta.d.ts index 2008e268..c32b48f5 100644 --- a/subprojects/frontend/types/ImportMeta.d.ts +++ b/subprojects/frontend/types/ImportMeta.d.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | interface ImportMeta { | 1 | interface ImportMeta { |
2 | env: { | 2 | env: { |
3 | BASE_URL: string; | ||
3 | DEV: boolean; | 4 | DEV: boolean; |
4 | MODE: string; | 5 | MODE: string; |
5 | PROD: boolean; | 6 | PROD: boolean; |
diff --git a/subprojects/frontend/vite.config.ts b/subprojects/frontend/vite.config.ts index e690d005..526ea541 100644 --- a/subprojects/frontend/vite.config.ts +++ b/subprojects/frontend/vite.config.ts | |||
@@ -30,6 +30,7 @@ const apiPort = portNumberOrElse('API_PORT', 1312); | |||
30 | const apiSecure = apiPort === 443; | 30 | const apiSecure = apiPort === 443; |
31 | const publicHost = process.env.PUBLIC_HOST || listenHost; | 31 | const publicHost = process.env.PUBLIC_HOST || listenHost; |
32 | const publicPort = portNumberOrElse('PUBLIC_PORT', listenPort); | 32 | const publicPort = portNumberOrElse('PUBLIC_PORT', listenPort); |
33 | const publicSecure = publicPort === 443; | ||
33 | 34 | ||
34 | const { name: packageName, version: packageVersion } = JSON.parse( | 35 | const { name: packageName, version: packageVersion } = JSON.parse( |
35 | readFileSync(path.join(thisDir, 'package.json'), 'utf8'), | 36 | readFileSync(path.join(thisDir, 'package.json'), 'utf8'), |
@@ -56,6 +57,23 @@ const minifyPlugin: PluginOption = { | |||
56 | }, | 57 | }, |
57 | }; | 58 | }; |
58 | 59 | ||
60 | const backendConfigPlugin: PluginOption = { | ||
61 | name: 'backend-config', | ||
62 | configureServer(server) { | ||
63 | const protocol = publicSecure ? 'wss' : 'ws'; | ||
64 | const webSocketURL = `${protocol}://${publicHost}:${publicPort}/xtext-service`; | ||
65 | const config = JSON.stringify({ webSocketURL }); | ||
66 | server.middlewares.use((req, res, next) => { | ||
67 | if (req.url === '/config.json') { | ||
68 | res.setHeader('Content-Type', 'application/json'); | ||
69 | res.end(config); | ||
70 | } else { | ||
71 | next(); | ||
72 | } | ||
73 | }); | ||
74 | }, | ||
75 | }; | ||
76 | |||
59 | export default defineConfig({ | 77 | export default defineConfig({ |
60 | logLevel: 'info', | 78 | logLevel: 'info', |
61 | mode, | 79 | mode, |
@@ -63,6 +81,7 @@ export default defineConfig({ | |||
63 | cacheDir: path.join(thisDir, 'build/vite/cache'), | 81 | cacheDir: path.join(thisDir, 'build/vite/cache'), |
64 | plugins: [ | 82 | plugins: [ |
65 | minifyPlugin, | 83 | minifyPlugin, |
84 | backendConfigPlugin, | ||
66 | react(), | 85 | react(), |
67 | injectPreload({ | 86 | injectPreload({ |
68 | files: [ | 87 | files: [ |
@@ -90,6 +109,12 @@ export default defineConfig({ | |||
90 | ], | 109 | ], |
91 | dontCacheBustURLsMatching: /\.(?:css|js|woff2?)$/, | 110 | dontCacheBustURLsMatching: /\.(?:css|js|woff2?)$/, |
92 | navigateFallbackDenylist: [/^\/xtext-service/], | 111 | navigateFallbackDenylist: [/^\/xtext-service/], |
112 | runtimeCaching: [ | ||
113 | { | ||
114 | urlPattern: 'config.json', | ||
115 | handler: 'StaleWhileRevalidate', | ||
116 | }, | ||
117 | ], | ||
93 | }, | 118 | }, |
94 | includeAssets: ['apple-touch-icon.png', 'favicon.svg', 'mask-icon.svg'], | 119 | includeAssets: ['apple-touch-icon.png', 'favicon.svg', 'mask-icon.svg'], |
95 | manifest: { | 120 | manifest: { |