diff --git a/meet-ce/backend/src/environment.ts b/meet-ce/backend/src/environment.ts
index 8a2e2d2d..846c9bd5 100644
--- a/meet-ce/backend/src/environment.ts
+++ b/meet-ce/backend/src/environment.ts
@@ -23,6 +23,7 @@ export const MEET_ENV = {
LOG_LEVEL: process.env.MEET_LOG_LEVEL || 'info',
NAME_ID: process.env.MEET_NAME_ID || 'openviduMeet',
BASE_URL: process.env.MEET_BASE_URL ?? '',
+ BASE_PATH: process.env.MEET_BASE_PATH || '/meet',
EDITION: process.env.MEET_EDITION || 'CE',
// Authentication configuration
diff --git a/meet-ce/backend/src/server.ts b/meet-ce/backend/src/server.ts
index c1226a4e..831807a1 100644
--- a/meet-ce/backend/src/server.ts
+++ b/meet-ce/backend/src/server.ts
@@ -1,7 +1,7 @@
import chalk from 'chalk';
import cookieParser from 'cookie-parser';
import cors from 'cors';
-import express, { Express, Request, Response } from 'express';
+import express, { Express, Request, Response, Router } from 'express';
import { initializeEagerServices, registerDependencies } from './config/dependency-injector.config.js';
import { INTERNAL_CONFIG } from './config/internal-config.js';
import { MEET_ENV, logEnvVars } from './environment.js';
@@ -17,6 +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 {
frontendDirectoryPath,
frontendHtmlPath,
@@ -27,6 +28,7 @@ import {
const createApp = () => {
const app: Express = express();
+ const basePath = getBasePath();
// Enable CORS support
if (MEET_ENV.SERVER_CORS_ORIGIN) {
@@ -38,9 +40,6 @@ const createApp = () => {
);
}
- // Serve static files
- app.use(express.static(frontendDirectoryPath));
-
// Configure trust proxy based on deployment topology
// This is important for rate limiting and getting the real client IP
// Can be: true, false, a number (hops), or a custom function/string
@@ -69,55 +68,74 @@ const createApp = () => {
app.use(setBaseUrlFromRequest);
}
+ // Create a router for all app routes (to be mounted under base path)
+ const appRouter: Router = express.Router();
+
+ // Serve static files (disable automatic index.html serving so our catch-all can inject config)
+ appRouter.use(express.static(frontendDirectoryPath, { index: false }));
+
// Public API routes
- app.use(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/docs`, (_req: Request, res: Response) =>
+ appRouter.use(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/docs`, (_req: Request, res: Response) =>
res.sendFile(publicApiHtmlFilePath)
);
- app.use(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/rooms`, /*mediaTypeValidatorMiddleware,*/ roomRouter);
- app.use(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/recordings`, /*mediaTypeValidatorMiddleware,*/ recordingRouter);
+ appRouter.use(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/rooms`, /*mediaTypeValidatorMiddleware,*/ roomRouter);
+ appRouter.use(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/recordings`, /*mediaTypeValidatorMiddleware,*/ recordingRouter);
// Internal API routes
if (process.env.NODE_ENV === 'development') {
// Serve internal API docs only in development mode
- app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/docs`, (_req: Request, res: Response) =>
+ appRouter.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/docs`, (_req: Request, res: Response) =>
res.sendFile(internalApiHtmlFilePath)
);
}
- app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/auth`, authRouter);
- app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/api-keys`, apiKeyRouter);
- app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/users`, userRouter);
- app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/rooms`, internalRoomRouter);
- app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/meetings`, internalMeetingRouter);
- // app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/recordings`, internalRecordingRouter);
- app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config`, configRouter);
- app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/analytics`, analyticsRouter);
+ appRouter.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/auth`, authRouter);
+ appRouter.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/api-keys`, apiKeyRouter);
+ appRouter.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/users`, userRouter);
+ appRouter.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/rooms`, internalRoomRouter);
+ appRouter.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/meetings`, internalMeetingRouter);
+ // appRouter.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/recordings`, internalRecordingRouter);
+ appRouter.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/config`, configRouter);
+ appRouter.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/analytics`, analyticsRouter);
- app.use('/health', (_req: Request, res: Response) => res.status(200).send('OK'));
+ appRouter.use('/health', (_req: Request, res: Response) => res.status(200).send('OK'));
// LiveKit Webhook route
- app.use('/livekit/webhook', livekitWebhookRouter);
+ appRouter.use('/livekit/webhook', livekitWebhookRouter);
// Serve OpenVidu Meet webcomponent bundle file
- app.get('/v1/openvidu-meet.js', (_req: Request, res: Response) => res.sendFile(webcomponentBundlePath));
- // Serve OpenVidu Meet index.html file for all non-API routes
- app.get(/^(?!.*\/(api|internal-api)\/).*$/, (_req: Request, res: Response) => res.sendFile(frontendHtmlPath));
+ 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));
+ });
// Catch all other routes and return 404
- app.use((_req: Request, res: Response) =>
+ appRouter.use((_req: Request, res: Response) =>
res.status(404).json({ error: 'Path Not Found', message: 'API path not implemented' })
);
+ // Mount all routes under the configured base path
+ app.use(basePath, appRouter);
+
return app;
};
const startServer = (app: express.Application) => {
+ const basePath = getBasePath();
+ const basePathDisplay = basePath === '/' ? '' : basePath.slice(0, -1);
+
app.listen(MEET_ENV.SERVER_PORT, async () => {
console.log(' ');
console.log('---------------------------------------------------------');
console.log(' ');
console.log(`OpenVidu Meet ${MEET_ENV.EDITION} is listening on port`, chalk.cyanBright(MEET_ENV.SERVER_PORT));
+
+ if (basePath !== '/') {
+ console.log('Base Path:', chalk.cyanBright(basePath));
+ }
+
console.log(
'REST API Docs: ',
- chalk.cyanBright(`http://localhost:${MEET_ENV.SERVER_PORT}${INTERNAL_CONFIG.API_BASE_PATH_V1}/docs`)
+ chalk.cyanBright(`http://localhost:${MEET_ENV.SERVER_PORT}${basePathDisplay}${INTERNAL_CONFIG.API_BASE_PATH_V1}/docs`)
);
logEnvVars();
});
diff --git a/meet-ce/backend/src/utils/html-injection.utils.ts b/meet-ce/backend/src/utils/html-injection.utils.ts
new file mode 100644
index 00000000..200c1489
--- /dev/null
+++ b/meet-ce/backend/src/utils/html-injection.utils.ts
@@ -0,0 +1,111 @@
+import chalk from 'chalk';
+import fs from 'fs';
+import { MEET_ENV } from '../environment.js';
+
+let cachedHtml: string | null = null;
+let configValidated = false;
+
+/**
+ * Normalizes the base path to ensure it starts and ends with /
+ * @param basePath The base path to normalize
+ * @returns Normalized base path (e.g., '/', '/meet/', '/app/path/')
+ */
+export function normalizeBasePath(basePath: string): string {
+ let normalized = basePath.trim();
+
+ // Handle empty string
+ if (!normalized) {
+ return '/';
+ }
+
+ // Ensure it starts with /
+ if (!normalized.startsWith('/')) {
+ normalized = '/' + normalized;
+ }
+
+ // Ensure it ends with /
+ if (!normalized.endsWith('/')) {
+ normalized = normalized + '/';
+ }
+
+ return normalized;
+}
+
+/**
+ * Validates the BASE_URL and BASE_PATH configuration and warns about potential issues.
+ * Only runs once per process.
+ */
+function validateBasePathConfig(): void {
+ if (configValidated) return;
+
+ configValidated = true;
+
+ const baseUrl = MEET_ENV.BASE_URL;
+ const basePath = MEET_ENV.BASE_PATH;
+
+ if (baseUrl) {
+ try {
+ const url = new URL(baseUrl);
+
+ // Check if BASE_URL contains a path (other than just /)
+ if (url.pathname && url.pathname !== '/') {
+ console.warn(chalk.yellow('⚠️ WARNING: MEET_BASE_URL contains a path segment:'), chalk.cyan(url.pathname));
+ console.warn(chalk.yellow(' MEET_BASE_URL should only contain https protocol and host (e.g., https://example.com)'));
+ console.warn(chalk.yellow(' Use MEET_BASE_PATH for the deployment path (e.g., /meet/)'));
+
+ if (basePath && basePath !== '/') {
+ console.warn(chalk.red(` This may cause issues: BASE_URL path "${url.pathname}" + BASE_PATH "${basePath}"`));
+ }
+ }
+ } catch {
+ console.warn(chalk.yellow('⚠️ WARNING: MEET_BASE_URL is not a valid URL:'), chalk.cyan(baseUrl));
+ }
+ }
+}
+
+/**
+ * Gets the configured base path, normalized
+ * @returns The normalized base path from MEET_BASE_PATH environment variable
+ */
+export function getBasePath(): string {
+ validateBasePathConfig();
+ return normalizeBasePath(MEET_ENV.BASE_PATH);
+}
+
+/**
+ * Injects runtime configuration into the index.html
+ * - Replaces the tag with the configured base path
+ * - Injects 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 {
+ // In production, cache the result for performance
+ if (process.env.NODE_ENV === 'production' && cachedHtml) {
+ return cachedHtml;
+ }
+
+ const basePath = getBasePath();
+ let html = fs.readFileSync(htmlPath, 'utf-8');
+
+ // Replace the base href - handle both possible formats
+ html = html.replace(//i, ``);
+
+ // Inject runtime configuration script before the closing tag
+ const configScript = ``;
+ html = html.replace('', `${configScript}\n`);
+
+ if (process.env.NODE_ENV === 'production') {
+ cachedHtml = html;
+ }
+
+ return html;
+}
+
+/**
+ * Clears the cached HTML (useful for testing or config changes)
+ */
+export function clearHtmlCache(): void {
+ cachedHtml = null;
+}
diff --git a/meet-ce/backend/src/utils/url.utils.ts b/meet-ce/backend/src/utils/url.utils.ts
index 8a053618..1d0ac9cb 100644
--- a/meet-ce/backend/src/utils/url.utils.ts
+++ b/meet-ce/backend/src/utils/url.utils.ts
@@ -1,33 +1,46 @@
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';
/**
- * Returns the base URL for the application.
+ * Returns the base URL for the application, including the configured base path.
*
* If the global `BASE_URL` variable is defined, it returns its value,
* ensuring there is no trailing slash and removing default ports (443 for HTTPS, 80 for HTTP).
* Otherwise, it retrieves the base URL from the `HttpContextService` instance.
*
- * @returns {string} The base URL as a string.
+ * The configured BASE_PATH is appended to the URL (without trailing slash).
+ *
+ * @returns {string} The base URL as a string (e.g., 'https://example.com/meet').
*/
export const getBaseUrl = (): string => {
+ let hostUrl: string;
+
if (MEET_ENV.BASE_URL) {
- let baseUrl = MEET_ENV.BASE_URL.endsWith('/') ? MEET_ENV.BASE_URL.slice(0, -1) : MEET_ENV.BASE_URL;
+ hostUrl = MEET_ENV.BASE_URL.endsWith('/') ? MEET_ENV.BASE_URL.slice(0, -1) : MEET_ENV.BASE_URL;
// Remove default port 443 for HTTPS URLs
- if (baseUrl.startsWith('https://') && baseUrl.includes(':443')) {
- baseUrl = baseUrl.replace(':443', '');
+ if (hostUrl.startsWith('https://') && hostUrl.includes(':443')) {
+ hostUrl = hostUrl.replace(':443', '');
}
// Remove default port 80 for HTTP URLs
- if (baseUrl.startsWith('http://') && baseUrl.includes(':80')) {
- baseUrl = baseUrl.replace(':80', '');
+ if (hostUrl.startsWith('http://') && hostUrl.includes(':80')) {
+ hostUrl = hostUrl.replace(':80', '');
}
-
- return baseUrl;
+ } else {
+ const baseUrlService = container.get(BaseUrlService);
+ hostUrl = baseUrlService.getBaseUrl();
}
- const baseUrlService = container.get(BaseUrlService);
- return baseUrlService.getBaseUrl();
+ // Append the base path (without trailing slash)
+ const basePath = getBasePath();
+
+ if (basePath === '/') {
+ return hostUrl;
+ }
+
+ // Remove trailing slash from base path for the final URL
+ return `${hostUrl}${basePath.slice(0, -1)}`;
};
diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/pages/meeting/meeting.component.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/pages/meeting/meeting.component.ts
index a3462edf..5265fae8 100644
--- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/pages/meeting/meeting.component.ts
+++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/pages/meeting/meeting.component.ts
@@ -12,6 +12,7 @@ import {
} from 'openvidu-components-angular';
import { Subject } from 'rxjs';
import { ApplicationFeatures } from '../../../../shared/models/app.model';
+import { AppConfigService } from '../../../../shared/services/app-config.service';
import { FeatureConfigurationService } from '../../../../shared/services/feature-configuration.service';
import { GlobalConfigService } from '../../../../shared/services/global-config.service';
import { NotificationService } from '../../../../shared/services/notification.service';
@@ -72,6 +73,7 @@ export class MeetingComponent implements OnInit {
protected eventHandlerService = inject(MeetingEventHandlerService);
protected captionsService = inject(MeetingCaptionsService);
protected soundService = inject(SoundService);
+ protected appConfigService = inject(AppConfigService);
protected destroy$ = new Subject();
// === LOBBY PHASE COMPUTED SIGNALS (when showLobby = true) ===
@@ -208,7 +210,9 @@ export class MeetingComponent implements OnInit {
// }
async onViewRecordingsClicked() {
- window.open(`/room/${this.roomId()}/recordings?secret=${this.roomSecret()}`, '_blank');
+ const basePath = this.appConfigService.basePath;
+ const basePathForUrl = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;
+ window.open(`${basePathForUrl}/room/${this.roomId()}/recordings?secret=${this.roomSecret()}`, '_blank');
}
onParticipantConnected(event: any): void {
diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-context.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-context.service.ts
index 7f8a38c1..7d1cd922 100644
--- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-context.service.ts
+++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-context.service.ts
@@ -1,6 +1,7 @@
import { computed, effect, inject, Injectable, signal } from '@angular/core';
import { MeetRoom } from 'node_modules/@openvidu-meet/typings/dist/room';
import { ParticipantService, Room, ViewportService } from 'openvidu-components-angular';
+import { AppConfigService } from '../../../shared/services/app-config.service';
import { FeatureConfigurationService } from '../../../shared/services/feature-configuration.service';
import { SessionStorageService } from '../../../shared/services/session-storage.service';
import { CustomParticipantModel } from '../models';
@@ -18,6 +19,7 @@ export class MeetingContextService {
private readonly featureConfigService = inject(FeatureConfigurationService);
private readonly viewportService = inject(ViewportService);
private readonly sessionStorageService = inject(SessionStorageService);
+ private readonly appConfigService = inject(AppConfigService);
private readonly _meetRoom = signal(undefined);
private readonly _lkRoom = signal(undefined);
@@ -166,7 +168,10 @@ export class MeetingContextService {
*/
private setMeetingUrl(roomId: string): void {
const hostname = window.location.origin.replace('http://', '').replace('https://', '');
- const meetingUrl = roomId ? `${hostname}/room/${roomId}` : '';
+ const basePath = this.appConfigService.basePath;
+ // Remove trailing slash from base path for URL construction
+ const basePathForUrl = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;
+ const meetingUrl = roomId ? `${hostname}${basePathForUrl}/room/${roomId}` : '';
this._meetingUrl.set(meetingUrl);
}
diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/app-config.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/app-config.service.ts
new file mode 100644
index 00000000..3c552746
--- /dev/null
+++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/app-config.service.ts
@@ -0,0 +1,59 @@
+import { Injectable } from '@angular/core';
+
+declare global {
+ interface Window {
+ __OPENVIDU_MEET_CONFIG__?: {
+ basePath: string;
+ };
+ }
+}
+
+/**
+ * Service that provides access to runtime application configuration.
+ * Reads configuration injected by the server at runtime.
+ */
+@Injectable({
+ providedIn: 'root'
+})
+export class AppConfigService {
+ private _basePath: string;
+
+ constructor() {
+ // Read from injected config, fallback to document base element, then to '/'
+ this._basePath = window.__OPENVIDU_MEET_CONFIG__?.basePath || this.getBasePathFromDocument() || '/';
+ }
+
+ /**
+ * Gets the configured base path (e.g., '/', '/meet/', '/app/path/')
+ */
+ get basePath(): string {
+ return this._basePath;
+ }
+
+ /**
+ * Resolves an asset path relative to the base path.
+ * Use this for assets that need absolute paths (e.g., Audio elements).
+ *
+ * @param assetPath The asset path starting with 'assets/' (no leading slash)
+ * @returns The full path including the base path (e.g., '/meet/assets/sounds/file.mp3')
+ */
+ resolveAssetPath(assetPath: string): string {
+ // Remove leading slash if present
+ const cleanPath = assetPath.startsWith('/') ? assetPath.slice(1) : assetPath;
+
+ // Combine with base path
+ return `${this._basePath}${cleanPath}`;
+ }
+
+ private getBasePathFromDocument(): string | null {
+ try {
+ const baseElement = document.querySelector('base');
+ if (baseElement) {
+ return baseElement.getAttribute('href') || null;
+ }
+ } catch (e) {
+ console.warn('Could not read base element:', e);
+ }
+ return null;
+ }
+}
diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/index.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/index.ts
index a94ffef1..982c1175 100644
--- a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/index.ts
+++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/index.ts
@@ -1,5 +1,6 @@
export * from './analytics.service';
export * from './api-key.service';
+export * from './app-config.service';
export * from './app-data.service';
export * from './feature-configuration.service';
export * from './global-config.service';
diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/sound.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/sound.service.ts
index b926b163..21987cd2 100644
--- a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/sound.service.ts
+++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/sound.service.ts
@@ -1,17 +1,20 @@
-import { Injectable } from '@angular/core';
+import { inject, Injectable } from '@angular/core';
+import { AppConfigService } from './app-config.service';
/**
* Service responsible for managing sound effects within the application.
*/
@Injectable()
export class SoundService {
+ private appConfig = inject(AppConfigService);
+
constructor() {}
/**
* Plays a sound to indicate that a participant has joined the meeting.
*/
playParticipantJoinedSound(): void {
- const audio = new Audio('/assets/sounds/participant-joined.mp3');
+ const audio = new Audio(this.appConfig.resolveAssetPath('assets/sounds/participant-joined.mp3'));
audio.volume = 0.4;
audio.play();
}
@@ -20,7 +23,7 @@ export class SoundService {
* Plays a sound to indicate that a participant's role has been upgraded.
*/
playParticipantRoleUpgradedSound(): void {
- const audio = new Audio('/assets/sounds/role-upgraded.wav');
+ const audio = new Audio(this.appConfig.resolveAssetPath('assets/sounds/role-upgraded.wav'));
audio.volume = 0.4;
audio.play();
}
@@ -30,7 +33,7 @@ export class SoundService {
* Plays a sound to indicate that a participant's role has been downgraded.
*/
playParticipantRoleDowngradedSound(): void {
- const audio = new Audio('/assets/sounds/role-downgraded.wav');
+ const audio = new Audio(this.appConfig.resolveAssetPath('assets/sounds/role-downgraded.wav'));
audio.volume = 0.4;
audio.play();
}
diff --git a/meet-ce/frontend/webcomponent/tests/README.md b/meet-ce/frontend/webcomponent/tests/README.md
index 91d0cdfa..43fa88da 100644
--- a/meet-ce/frontend/webcomponent/tests/README.md
+++ b/meet-ce/frontend/webcomponent/tests/README.md
@@ -343,7 +343,7 @@ pnpm test:e2e-recording-access # Recording access control tests
Tests require the following configuration (defined in `tests/config.ts`):
```typescript
-MEET_API_URL # Backend API URL (default: http://localhost:6080)
+MEET_API_URL # Backend API URL (default: http://localhost:6080/meet)
MEET_API_KEY # API key for backend (default: meet-api-key)
MEET_TESTAPP_URL # Test application URL (default: http://localhost:5080)
RUN_MODE # Execution mode: 'CI' or 'development'
diff --git a/meet-ce/frontend/webcomponent/tests/config.ts b/meet-ce/frontend/webcomponent/tests/config.ts
index ef08c64a..9ceab143 100644
--- a/meet-ce/frontend/webcomponent/tests/config.ts
+++ b/meet-ce/frontend/webcomponent/tests/config.ts
@@ -1,4 +1,4 @@
export const RUN_MODE = process.env['RUN_MODE'] || 'development';
-export const MEET_API_URL = process.env['MEET_API_URL'] || 'http://localhost:6080';
+export const MEET_API_URL = process.env['MEET_API_URL'] || 'http://localhost:6080/meet';
export const MEET_API_KEY = process.env['MEET_API_KEY'] || 'meet-api-key';
export const MEET_TESTAPP_URL = process.env['MEET_TESTAPP_URL'] || 'http://localhost:5080';
diff --git a/testapp/.env b/testapp/.env
index c7504915..7625b106 100644
--- a/testapp/.env
+++ b/testapp/.env
@@ -1,4 +1,4 @@
SERVER_PORT=5080
-MEET_API_URL=http://localhost:6080/api/v1
+MEET_API_URL=http://localhost:6080/meet/api/v1
MEET_API_KEY=meet-api-key
-MEET_WEBCOMPONENT_SRC=http://localhost:6080/v1/openvidu-meet.js
+MEET_WEBCOMPONENT_SRC=http://localhost:6080/meet/v1/openvidu-meet.js
diff --git a/testapp/src/controllers/homeController.ts b/testapp/src/controllers/homeController.ts
index 247fbd5e..66f179e0 100644
--- a/testapp/src/controllers/homeController.ts
+++ b/testapp/src/controllers/homeController.ts
@@ -23,7 +23,7 @@ export const getHome = async (_req: Request, res: Response) => {
console.error('Error details:', {
message: error instanceof Error ? error.message : 'Unknown error',
stack: error instanceof Error ? error.stack : 'No stack trace',
- apiUrl: process.env.MEET_API_URL || 'http://localhost:6080/api/v1',
+ apiUrl: process.env.MEET_API_URL || 'http://localhost:6080/meet/api/v1',
apiKey: process.env.MEET_API_KEY || 'meet-api-key'
});
res.status(500).send(
diff --git a/testapp/src/services/configService.ts b/testapp/src/services/configService.ts
index 0356024f..e07c76d4 100644
--- a/testapp/src/services/configService.ts
+++ b/testapp/src/services/configService.ts
@@ -10,9 +10,9 @@ export class ConfigService {
constructor() {
this.serverPort = process.env.SERVER_PORT ? parseInt(process.env.SERVER_PORT, 10) : 5080;
- this.meetApiUrl = process.env.MEET_API_URL || 'http://localhost:6080/api/v1';
+ this.meetApiUrl = process.env.MEET_API_URL || 'http://localhost:6080/meet/api/v1';
this.meetApiKey = process.env.MEET_API_KEY || 'meet-api-key';
- this.meetWebhookSrc = process.env.MEET_WEBCOMPONENT_SRC || 'http://localhost:6080/v1/openvidu-meet.js';
+ this.meetWebhookSrc = process.env.MEET_WEBCOMPONENT_SRC || 'http://localhost:6080/meet/v1/openvidu-meet.js';
}
}