backend: apply dynamic base path to OpenAPI docs server URLs
When deployed under a base path (e.g. /meet), the Stoplight "Try It" requests were hitting /api/v1 instead of /meet/api/v1. This applies the base path to the embedded OpenAPI spec's servers array at serve time, following the same pattern used for the frontend index.html. Also renames html-injection.utils to html-dynamic-base-path.utils and updates function names for better wording.
This commit is contained in:
parent
366632741c
commit
a853aa02a2
@ -2,7 +2,7 @@ import { MeetRoom, MeetRoomFilters, MeetRoomStatus } from '@openvidu-meet/typing
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { MeetRoomDocument, MeetRoomModel } from '../models/mongoose-schemas/room.schema.js';
|
||||
import { LoggerService } from '../services/logger.service.js';
|
||||
import { getBasePath } from '../utils/html-injection.utils.js';
|
||||
import { getBasePath } from '../utils/html-dynamic-base-path.utils.js';
|
||||
import { getBaseUrl } from '../utils/url.utils.js';
|
||||
import { BaseRepository } from './base.repository.js';
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ import { internalMeetingRouter } from './routes/meeting.routes.js';
|
||||
import { recordingRouter } from './routes/recording.routes.js';
|
||||
import { internalRoomRouter, roomRouter } from './routes/room.routes.js';
|
||||
import { userRouter } from './routes/user.routes.js';
|
||||
import { getBasePath, getInjectedHtml } from './utils/html-injection.utils.js';
|
||||
import { getBasePath, getHtmlWithBasePath, getOpenApiHtmlWithBasePath } from './utils/html-dynamic-base-path.utils.js';
|
||||
import {
|
||||
frontendDirectoryPath,
|
||||
frontendHtmlPath,
|
||||
@ -76,7 +76,7 @@ const createApp = () => {
|
||||
|
||||
// Public API routes
|
||||
appRouter.use(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/docs`, (_req: Request, res: Response) =>
|
||||
res.sendFile(publicApiHtmlFilePath)
|
||||
res.type('html').send(getOpenApiHtmlWithBasePath(publicApiHtmlFilePath, INTERNAL_CONFIG.API_BASE_PATH_V1))
|
||||
);
|
||||
appRouter.use(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/rooms`, /*mediaTypeValidatorMiddleware,*/ roomRouter);
|
||||
appRouter.use(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/recordings`, /*mediaTypeValidatorMiddleware,*/ recordingRouter);
|
||||
@ -85,7 +85,7 @@ const createApp = () => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// Serve internal API docs only in development mode
|
||||
appRouter.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/docs`, (_req: Request, res: Response) =>
|
||||
res.sendFile(internalApiHtmlFilePath)
|
||||
res.type('html').send(getOpenApiHtmlWithBasePath(internalApiHtmlFilePath, INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1))
|
||||
);
|
||||
}
|
||||
|
||||
@ -104,7 +104,7 @@ const createApp = () => {
|
||||
appRouter.get('/v1/openvidu-meet.js', (_req: Request, res: Response) => res.sendFile(webcomponentBundlePath));
|
||||
// Serve OpenVidu Meet index.html file for all non-API routes (with dynamic base path injection)
|
||||
appRouter.get(/^(?!.*\/(api|internal-api)\/).*$/, (_req: Request, res: Response) => {
|
||||
res.type('html').send(getInjectedHtml(frontendHtmlPath));
|
||||
res.type('html').send(getHtmlWithBasePath(frontendHtmlPath));
|
||||
});
|
||||
// Catch all other routes and return 404
|
||||
appRouter.use((_req: Request, res: Response) =>
|
||||
|
||||
@ -3,6 +3,7 @@ import fs from 'fs';
|
||||
import { MEET_ENV } from '../environment.js';
|
||||
|
||||
let cachedHtml: string | null = null;
|
||||
const cachedOpenApiHtml = new Map<string, string>();
|
||||
let configValidated = false;
|
||||
|
||||
/**
|
||||
@ -73,14 +74,14 @@ export function getBasePath(): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects runtime configuration into the index.html
|
||||
* Applies runtime base path configuration to the index.html
|
||||
* - Replaces the <base href="/"> tag with the configured base path
|
||||
* - Injects a script with window.__OPENVIDU_MEET_CONFIG__ for frontend access
|
||||
* - Adds a script with window.__OPENVIDU_MEET_CONFIG__ for frontend access
|
||||
*
|
||||
* @param htmlPath Path to the index.html file
|
||||
* @returns The modified HTML content
|
||||
*/
|
||||
export function getInjectedHtml(htmlPath: string): string {
|
||||
export function getHtmlWithBasePath(htmlPath: string): string {
|
||||
// In production, cache the result for performance
|
||||
if (process.env.NODE_ENV === 'production' && cachedHtml) {
|
||||
return cachedHtml;
|
||||
@ -103,9 +104,44 @@ export function getInjectedHtml(htmlPath: string): string {
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the runtime base path to the OpenAPI documentation HTML.
|
||||
* Replaces the servers URL in the embedded OpenAPI spec so that "Try It" requests
|
||||
* use the correct path when deployed under a base path (e.g. /meet/api/v1).
|
||||
*
|
||||
* @param htmlPath Path to the OpenAPI HTML file
|
||||
* @param apiBasePath The API base path (e.g. /api/v1 or /internal-api/v1)
|
||||
* @returns The modified HTML content
|
||||
*/
|
||||
export function getOpenApiHtmlWithBasePath(htmlPath: string, apiBasePath: string): string {
|
||||
if (process.env.NODE_ENV === 'production' && cachedOpenApiHtml.has(htmlPath)) {
|
||||
return cachedOpenApiHtml.get(htmlPath)!;
|
||||
}
|
||||
|
||||
const basePath = getBasePath();
|
||||
// Build full server URL: strip trailing slash from basePath to avoid double slashes
|
||||
const fullServerUrl = basePath.replace(/\/$/, '') + apiBasePath;
|
||||
|
||||
let html = fs.readFileSync(htmlPath, 'utf-8');
|
||||
|
||||
// Replace the servers URL in the embedded OpenAPI JSON
|
||||
// Matches "servers":[{"url":"<any-url>" and replaces the URL with the full path
|
||||
html = html.replace(
|
||||
/("servers":\[\{"url":")[^"]*(")/,
|
||||
`$1${fullServerUrl}$2`
|
||||
);
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
cachedOpenApiHtml.set(htmlPath, html);
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the cached HTML (useful for testing or config changes)
|
||||
*/
|
||||
export function clearHtmlCache(): void {
|
||||
cachedHtml = null;
|
||||
cachedOpenApiHtml.clear();
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import { container } from '../config/dependency-injector.config.js';
|
||||
import { MEET_ENV } from '../environment.js';
|
||||
import { BaseUrlService } from '../services/base-url.service.js';
|
||||
import { getBasePath } from './html-injection.utils.js';
|
||||
import { getBasePath } from './html-dynamic-base-path.utils.js';
|
||||
|
||||
/**
|
||||
* Returns the base URL for the application, including the configured base path.
|
||||
|
||||
@ -31,7 +31,7 @@ import { ApiKeyService } from '../../src/services/api-key.service.js';
|
||||
import { GlobalConfigService } from '../../src/services/global-config.service.js';
|
||||
import { RecordingService } from '../../src/services/recording.service.js';
|
||||
import { RoomScheduledTasksService } from '../../src/services/room-scheduled-tasks.service.js';
|
||||
import { getBasePath } from '../../src/utils/html-injection.utils.js';
|
||||
import { getBasePath } from '../../src/utils/html-dynamic-base-path.utils.js';
|
||||
|
||||
/**
|
||||
* Constructs the full API path by prepending the base path.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user