From b9ba416366de02600b771697fbc2a5b7e478f5f0 Mon Sep 17 00:00:00 2001 From: Carlos Valente Date: Mon, 2 Dec 2024 15:49:28 +0100 Subject: [PATCH] refactor: add prefix to relative assets refactor: add prefix to server entrypoints refactor: add prefix to express router refactor: add prefix to router basename --- apps/client/index.html | 54 +++++++++----------- apps/client/src/App.tsx | 3 +- apps/client/src/externals.ts | 24 +++++++-- apps/client/vite.config.js | 3 +- apps/server/src/adapters/WebsocketAdapter.ts | 4 +- apps/server/src/app.ts | 38 ++++++++------ apps/server/src/externals.ts | 18 ++++--- apps/server/src/setup/index.ts | 2 + 8 files changed, 86 insertions(+), 60 deletions(-) diff --git a/apps/client/index.html b/apps/client/index.html index 7254d879b..a37a61f8a 100644 --- a/apps/client/index.html +++ b/apps/client/index.html @@ -1,31 +1,27 @@ - - - - - - - - - - - - - ontime - - - - -
- - + + + + + + + + + + + + + ontime + + + + +
+ + diff --git a/apps/client/src/App.tsx b/apps/client/src/App.tsx index 6ebce9acb..c07768b94 100644 --- a/apps/client/src/App.tsx +++ b/apps/client/src/App.tsx @@ -11,6 +11,7 @@ import { connectSocket } from './common/utils/socket'; import theme from './theme/theme'; import { TranslationProvider } from './translation/TranslationProvider'; import AppRouter from './AppRouter'; +import { baseURI } from './externals'; connectSocket(); @@ -19,7 +20,7 @@ function App() { - +
diff --git a/apps/client/src/externals.ts b/apps/client/src/externals.ts index ec771ba29..1fd3e4618 100644 --- a/apps/client/src/externals.ts +++ b/apps/client/src/externals.ts @@ -22,7 +22,25 @@ export const isOntimeCloud = Boolean(import.meta.env.VITE_IS_CLOUD); const socketProtocol = window.location.protocol === 'https:' ? 'wss' : 'ws'; // resolve port -const STATIC_PORT = 4001; +const STATIC_PORT = 4001; // this is used as a fallback port for development export const serverPort = isProduction ? window.location.port : STATIC_PORT; -export const serverURL = `${window.location.protocol}//${location.hostname}:${serverPort}`; -export const websocketUrl = `${socketProtocol}://${location.hostname}:${serverPort}/ws`; +export const baseURI = resolveBaseURI(); +export const serverURL = `${window.location.protocol}//${window.location.hostname}:${serverPort}${baseURI}`; +export const websocketUrl = `${socketProtocol}://${window.location.hostname}:${serverPort}${baseURI}/ws`; + +/** + * Resolves a base URI for a client that is not at the root segment + * ie: https://cloud.getontime.com/client-hash/timer + * This is necessary for ontime cloud and should otherwise not affect the client + */ +function resolveBaseURI() { + if (!isOntimeCloud) { + return ''; + } + const [_, base, location] = window.location.pathname.split('/'); + if (!location) { + return ''; + } + + return `/${base}`; +} diff --git a/apps/client/vite.config.js b/apps/client/vite.config.js index d8e53fa85..25b2d5dc4 100644 --- a/apps/client/vite.config.js +++ b/apps/client/vite.config.js @@ -11,6 +11,7 @@ const sentryAuthToken = process.env.SENTRY_AUTH_TOKEN; const isDev = process.env.NODE_ENV === 'local' || process.env.NODE_ENV === 'development'; export default defineConfig({ + base: './', // Ontime cloud: we use relative paths to allow them to reference a dynamic base set at runtime plugins: [ react(), svgrPlugin(), @@ -33,7 +34,7 @@ export default defineConfig({ }), compression({ algorithm: 'brotliCompress', - exclude: /\.(html)$/, // Exclude HTML files from compression so we can change the base property at runtime + exclude: /\.(html)$/, // Ontime cloud: Exclude HTML files from compression so we can change the base property at runtime }), ], server: { diff --git a/apps/server/src/adapters/WebsocketAdapter.ts b/apps/server/src/adapters/WebsocketAdapter.ts index 6fd5e7274..cc87be0de 100644 --- a/apps/server/src/adapters/WebsocketAdapter.ts +++ b/apps/server/src/adapters/WebsocketAdapter.ts @@ -47,8 +47,8 @@ export class SocketServer implements IAdapter { this.wss = null; } - init(server: Server) { - this.wss = new WebSocketServer({ path: '/ws', server, maxPayload: this.MAX_PAYLOAD }); + init(server: Server, prefix?: string) { + this.wss = new WebSocketServer({ path: `${prefix}/ws`, server, maxPayload: this.MAX_PAYLOAD }); this.wss.on('connection', (ws) => { const clientId = generateId(); diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts index 751808032..6f6cf9ac5 100644 --- a/apps/server/src/app.ts +++ b/apps/server/src/app.ts @@ -6,10 +6,10 @@ import expressStaticGzip from 'express-static-gzip'; import http, { type Server } from 'http'; import cors from 'cors'; import serverTiming from 'server-timing'; -import { extname, resolve } from 'path'; +import { extname } from 'node:path'; // import utils -import { publicDir, srcDir } from './setup/index.js'; +import { publicDir, srcDir, srcFiles } from './setup/index.js'; import { environment, isProduction, updateRouterPrefix } from './externals.js'; import { ONTIME_VERSION } from './ONTIME_VERSION.js'; import { consoleSuccess, consoleHighlight, consoleError } from './utils/console.js'; @@ -53,8 +53,14 @@ if (!canLog) { console.log(`Ontime public directory at ${publicDir.root} `); } -// calls an update to the client router prefix -updateRouterPrefix(); +/** + * When running in Ontime cloud, the client is not at the root segment + * ie: https://cloud.getontime.com/client-hash/timer + * This means: + * - changing the base path in the index.html file + * - prepending all express routes with the given prefix + */ +const prefix = updateRouterPrefix(); // Create express APP const app = express(); @@ -75,20 +81,20 @@ app.use(express.urlencoded({ extended: true })); app.use(express.json({ limit: '1mb' })); // Implement route endpoints -app.use('/data', appRouter); // router for application data -app.use('/api', integrationRouter); // router for integrations +app.use(`${prefix}/data`, appRouter); // router for application data +app.use(`${prefix}/api`, integrationRouter); // router for integrations // serve static external files -app.use('/external', express.static(publicDir.externalDir)); -app.use('/user', express.static(publicDir.userDir)); - -// if the user reaches to the root, we show a 404 -app.use('/external', (req, res) => { +app.use(`${prefix}/external`, express.static(publicDir.externalDir)); +app.use(`${prefix}/external`, (req, res) => { + // if the user reaches to the root, we show a 404 res.status(404).send(`${req.originalUrl} not found`); }); +app.use(`${prefix}/user`, express.static(publicDir.userDir)); // serve static - react, in dev/test mode we fetch the React app from module app.use( + prefix, expressStaticGzip(srcDir.clientDir, { enableBrotli: true, orderPreference: ['br'], @@ -111,8 +117,8 @@ app.use( }), ); -app.get('*', (_req, res) => { - res.sendFile(resolve(srcDir.clientDir, 'index.html')); +app.get(`${prefix}/*`, (_req, res) => { + res.sendFile(srcFiles.clientIndexHtml); }); // Implement catch all @@ -176,7 +182,7 @@ export const startServer = async ( const { serverPort } = getDataProvider().getSettings(); expressServer = http.createServer(app); - socket.init(expressServer); + socket.init(expressServer, prefix); /** * Module initialises the services and provides initial payload for the store @@ -221,10 +227,10 @@ export const startServer = async ( expressServer.listen(serverPort, '0.0.0.0', () => { const nif = getNetworkInterfaces(); - consoleSuccess(`Local: http://localhost:${serverPort}/editor`); + consoleSuccess(`Local: http://localhost:${serverPort}${prefix}/editor`); for (const key in nif) { const address = nif[key].address; - consoleSuccess(`Network: http://${address}:${serverPort}/editor`); + consoleSuccess(`Network: http://${address}:${serverPort}${prefix}/editor`); } }); diff --git a/apps/server/src/externals.ts b/apps/server/src/externals.ts index 83f02ccb3..6bbbab2dd 100644 --- a/apps/server/src/externals.ts +++ b/apps/server/src/externals.ts @@ -3,7 +3,8 @@ */ import { readFileSync, writeFileSync } from 'node:fs'; -import { resolve } from 'node:path'; + +import { srcFiles } from './setup/index.js'; // ================================================= // resolve running environment @@ -19,19 +20,20 @@ export const isProduction = isDocker || (env === 'production' && !isTest); * This is only needed in the cloud environment where the client is not at the root segment * ie: https://cloud.getontime.com/client-hash/timer */ -export function updateRouterPrefix(prefix: string | undefined = process.env.ROUTER_PREFIX) { +export function updateRouterPrefix(prefix: string | undefined = process.env.ROUTER_PREFIX): string { if (!prefix) { - return; + return ''; } - const indexFile = resolve('.', 'client', 'index.html'); try { - const data = readFileSync(indexFile, { encoding: 'utf-8', flag: 'r' }).replace( - //g, - ``, + const data = readFileSync(srcFiles.clientIndexHtml, { encoding: 'utf-8', flag: 'r' }).replace( + '', + ``, ); - writeFileSync(indexFile, data, { encoding: 'utf-8', flag: 'w' }); + writeFileSync(srcFiles.clientIndexHtml, data, { encoding: 'utf-8', flag: 'w' }); } catch (_error) { /** unhandled */ } + + return `/${prefix}`; } diff --git a/apps/server/src/setup/index.ts b/apps/server/src/setup/index.ts index 3a1c7c96e..0990d69c0 100644 --- a/apps/server/src/setup/index.ts +++ b/apps/server/src/setup/index.ts @@ -77,6 +77,8 @@ export const srcDir = { } as const; export const srcFiles = { + /** Path to start index.html */ + clientIndexHtml: join(srcDir.clientDir, 'index.html'), /** Path to bundled CSS */ cssOverride: join(srcDir.root, config.user, config.styles.directory, config.styles.filename), /** Path to bundled external readme */