Introduce base path configuration and update related services
This commit is contained in:
parent
ba7600bfc5
commit
b0c7dcbc9a
@ -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
|
||||
|
||||
@ -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();
|
||||
});
|
||||
|
||||
111
meet-ce/backend/src/utils/html-injection.utils.ts
Normal file
111
meet-ce/backend/src/utils/html-injection.utils.ts
Normal file
@ -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 <base href="/"> 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(/<base href="[^"]*"\s*\/?>/i, `<base href="${basePath}">`);
|
||||
|
||||
// Inject runtime configuration script before the closing </head> tag
|
||||
const configScript = `<script>window.__OPENVIDU_MEET_CONFIG__={basePath:"${basePath}"};</script>`;
|
||||
html = html.replace('</head>', `${configScript}\n</head>`);
|
||||
|
||||
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;
|
||||
}
|
||||
@ -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)}`;
|
||||
};
|
||||
|
||||
@ -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<void>();
|
||||
|
||||
// === 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 {
|
||||
|
||||
@ -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<MeetRoom | undefined>(undefined);
|
||||
private readonly _lkRoom = signal<Room | undefined>(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);
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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';
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user