From 58dcd832291d771d1720850e95369c51f105da49 Mon Sep 17 00:00:00 2001 From: Carlos Santos <4a.santos@gmail.com> Date: Fri, 11 Apr 2025 17:08:03 +0200 Subject: [PATCH] backend: Centralize configuration values in internal-config module --- backend/src/config/internal-config.ts | 42 ++++++++++++++++++ backend/src/controllers/auth.controller.ts | 20 ++++----- .../src/controllers/participant.controller.ts | 9 ++-- backend/src/environment.ts | 43 ++++--------------- backend/src/middlewares/auth.middleware.ts | 22 ++++------ backend/src/server.ts | 23 +++++----- .../src/services/livekit-webhook.service.ts | 9 ++-- backend/src/services/recording.service.ts | 23 +++++----- backend/src/services/room.service.ts | 6 +-- .../storage/providers/s3-storage.provider.ts | 10 ++--- backend/tests/utils/helpers.ts | 37 +++++++++------- 11 files changed, 128 insertions(+), 116 deletions(-) create mode 100644 backend/src/config/internal-config.ts diff --git a/backend/src/config/internal-config.ts b/backend/src/config/internal-config.ts new file mode 100644 index 0000000..be2d846 --- /dev/null +++ b/backend/src/config/internal-config.ts @@ -0,0 +1,42 @@ +import { StringValue } from 'ms'; + +const INTERNAL_CONFIG = { + // Base paths for the API + API_BASE_PATH: '/meet/api', + INTERNAL_API_BASE_PATH_V1: '/meet/internal-api/v1', + API_BASE_PATH_V1: '/meet/api/v1', + + // Cookie names + PARTICIPANT_TOKEN_COOKIE_NAME: 'OvMeetParticipantToken', + ACCESS_TOKEN_COOKIE_NAME: 'OvMeetAccessToken', + REFRESH_TOKEN_COOKIE_NAME: 'OvMeetRefreshToken', + + // Headers for API requests + API_KEY_HEADER: 'x-api-key', + + // Fixed usernames + ANONYMOUS_USER: 'anonymous', + API_USER: 'api-user', + + // S3 prefixes + S3_ROOMS_PREFIX: 'rooms', + S3_RECORDINGS_PREFIX: 'recordings', + + // Garbage collection and recording lock intervals + ROOM_GC_INTERVAL: '1h' as StringValue, // e.g. garbage collector interval for rooms + RECORDING_LOCK_TTL: '6h' as StringValue, // TTL for recording lock in Redis + RECORDING_STARTED_TIMEOUT: '30s' as StringValue, // Timeout for recording start + RECORDING_LOCK_GC_INTERVAL: '30m' as StringValue, // Garbage collection interval for recording locks + + // Additional intervals + MIN_FUTURE_TIME_FOR_ROOM_AUTODELETION_DATE: '1h' as StringValue +}; + +// This function is used to set private configuration values for testing purposes. +// It allows you to override the default values defined in the INTERNAL_CONFIG object. +// This is useful for testing different scenarios without modifying the actual configuration file. +export const setPrivateConfig = (overrides: Partial): void => { + Object.assign(INTERNAL_CONFIG, overrides); +}; + +export default INTERNAL_CONFIG; diff --git a/backend/src/controllers/auth.controller.ts b/backend/src/controllers/auth.controller.ts index 1678bc3..f10ba3e 100644 --- a/backend/src/controllers/auth.controller.ts +++ b/backend/src/controllers/auth.controller.ts @@ -4,15 +4,13 @@ import { AuthService } from '../services/auth.service.js'; import { TokenService } from '../services/token.service.js'; import { LoggerService } from '../services/logger.service.js'; import { - ACCESS_TOKEN_COOKIE_NAME, MEET_ACCESS_TOKEN_EXPIRATION, - MEET_INTERNAL_API_BASE_PATH_V1, MEET_REFRESH_TOKEN_EXPIRATION, - REFRESH_TOKEN_COOKIE_NAME } from '../environment.js'; import { ClaimGrants } from 'livekit-server-sdk'; import { getCookieOptions } from '../utils/cookie-utils.js'; import { UserService } from '../services/user.service.js'; +import INTERNAL_CONFIG from '../config/internal-config.js'; export const login = async (req: Request, res: Response) => { const logger = container.get(LoggerService); @@ -31,11 +29,11 @@ export const login = async (req: Request, res: Response) => { const tokenService = container.get(TokenService); const accessToken = await tokenService.generateAccessToken(user); const refreshToken = await tokenService.generateRefreshToken(user); - res.cookie(ACCESS_TOKEN_COOKIE_NAME, accessToken, getCookieOptions('/', MEET_ACCESS_TOKEN_EXPIRATION)); + res.cookie(INTERNAL_CONFIG.ACCESS_TOKEN_COOKIE_NAME, accessToken, getCookieOptions('/', MEET_ACCESS_TOKEN_EXPIRATION)); res.cookie( - REFRESH_TOKEN_COOKIE_NAME, + INTERNAL_CONFIG.REFRESH_TOKEN_COOKIE_NAME, refreshToken, - getCookieOptions(`${MEET_INTERNAL_API_BASE_PATH_V1}/auth`, MEET_REFRESH_TOKEN_EXPIRATION) + getCookieOptions(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/auth`, MEET_REFRESH_TOKEN_EXPIRATION) ); logger.info(`Login succeeded for user ${username}`); return res.status(200).json({ message: 'Login succeeded' }); @@ -46,9 +44,9 @@ export const login = async (req: Request, res: Response) => { }; export const logout = (_req: Request, res: Response) => { - res.clearCookie(ACCESS_TOKEN_COOKIE_NAME); - res.clearCookie(REFRESH_TOKEN_COOKIE_NAME, { - path: `${MEET_INTERNAL_API_BASE_PATH_V1}/auth` + res.clearCookie(INTERNAL_CONFIG.ACCESS_TOKEN_COOKIE_NAME); + res.clearCookie(INTERNAL_CONFIG.REFRESH_TOKEN_COOKIE_NAME, { + path: `${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/auth` }); return res.status(200).json({ message: 'Logout successful' }); }; @@ -56,7 +54,7 @@ export const logout = (_req: Request, res: Response) => { export const refreshToken = async (req: Request, res: Response) => { const logger = container.get(LoggerService); logger.verbose('Refresh token request received'); - const refreshToken = req.cookies[REFRESH_TOKEN_COOKIE_NAME]; + const refreshToken = req.cookies[INTERNAL_CONFIG.REFRESH_TOKEN_COOKIE_NAME]; if (!refreshToken) { logger.warn('No refresh token provided'); @@ -84,7 +82,7 @@ export const refreshToken = async (req: Request, res: Response) => { try { const accessToken = await tokenService.generateAccessToken(user); - res.cookie(ACCESS_TOKEN_COOKIE_NAME, accessToken, getCookieOptions('/', MEET_ACCESS_TOKEN_EXPIRATION)); + res.cookie(INTERNAL_CONFIG.ACCESS_TOKEN_COOKIE_NAME, accessToken, getCookieOptions('/', MEET_ACCESS_TOKEN_EXPIRATION)); logger.info(`Token refreshed for user ${username}`); return res.status(200).json({ message: 'Token refreshed' }); } catch (error) { diff --git a/backend/src/controllers/participant.controller.ts b/backend/src/controllers/participant.controller.ts index f778ef8..aea231a 100644 --- a/backend/src/controllers/participant.controller.ts +++ b/backend/src/controllers/participant.controller.ts @@ -4,10 +4,11 @@ import { LoggerService } from '../services/logger.service.js'; import { TokenOptions } from '@typings-ce'; import { OpenViduMeetError } from '../models/index.js'; import { ParticipantService } from '../services/participant.service.js'; -import { MEET_PARTICIPANT_TOKEN_EXPIRATION, PARTICIPANT_TOKEN_COOKIE_NAME } from '../environment.js'; +import { MEET_PARTICIPANT_TOKEN_EXPIRATION } from '../environment.js'; import { getCookieOptions } from '../utils/cookie-utils.js'; import { TokenService } from '../services/token.service.js'; import { RoomService } from '../services/room.service.js'; +import INTERNAL_CONFIG from '../config/internal-config.js'; export const generateParticipantToken = async (req: Request, res: Response) => { const logger = container.get(LoggerService); @@ -21,7 +22,7 @@ export const generateParticipantToken = async (req: Request, res: Response) => { await roomService.createLivekitRoom(roomId); const token = await participantService.generateOrRefreshParticipantToken(tokenOptions); - res.cookie(PARTICIPANT_TOKEN_COOKIE_NAME, token, getCookieOptions('/', MEET_PARTICIPANT_TOKEN_EXPIRATION)); + res.cookie(INTERNAL_CONFIG.PARTICIPANT_TOKEN_COOKIE_NAME, token, getCookieOptions('/', MEET_PARTICIPANT_TOKEN_EXPIRATION)); return res.status(200).json({ token }); } catch (error) { logger.error(`Error generating participant token for room: ${roomId}`); @@ -33,7 +34,7 @@ export const refreshParticipantToken = async (req: Request, res: Response) => { const logger = container.get(LoggerService); // Check if there is a previous token and if it is valid - const previousToken = req.cookies[PARTICIPANT_TOKEN_COOKIE_NAME]; + const previousToken = req.cookies[INTERNAL_CONFIG.PARTICIPANT_TOKEN_COOKIE_NAME]; if (previousToken) { logger.verbose('Previous participant token found. Checking validity'); @@ -56,7 +57,7 @@ export const refreshParticipantToken = async (req: Request, res: Response) => { logger.verbose(`Refreshing participant token for room ${roomId}`); const token = await participantService.generateOrRefreshParticipantToken(tokenOptions, true); - res.cookie(PARTICIPANT_TOKEN_COOKIE_NAME, token, getCookieOptions('/', MEET_PARTICIPANT_TOKEN_EXPIRATION)); + res.cookie(INTERNAL_CONFIG.PARTICIPANT_TOKEN_COOKIE_NAME, token, getCookieOptions('/', MEET_PARTICIPANT_TOKEN_EXPIRATION)); logger.verbose(`Participant token refreshed for room ${roomId}`); return res.status(200).json({ token }); } catch (error) { diff --git a/backend/src/environment.ts b/backend/src/environment.ts index df9ce48..b51289a 100644 --- a/backend/src/environment.ts +++ b/backend/src/environment.ts @@ -1,12 +1,15 @@ import dotenv from 'dotenv'; import chalk from 'chalk'; -import ms from 'ms'; -const envPath = process.env.MEET_CONFIG_DIR - ? process.env.MEET_CONFIG_DIR - : process.env.NODE_ENV === 'development' - ? '.env.development' - : undefined; +let envPath: string | undefined; + +if (process.env.MEET_CONFIG_DIR) { + envPath = process.env.MEET_CONFIG_DIR; +} else if (process.env.NODE_ENV === 'development') { + envPath = '.env.development'; +} else { + envPath = undefined; +} dotenv.config(envPath ? { path: envPath } : {}); @@ -62,34 +65,6 @@ export const { ENABLED_MODULES = '' } = process.env; -// Base paths for the API -export const MEET_API_BASE_PATH = '/meet/api'; -export const MEET_INTERNAL_API_BASE_PATH_V1 = '/meet/internal-api/v1'; -export const MEET_API_BASE_PATH_V1 = MEET_API_BASE_PATH + '/v1'; - -// Cookie names -export const PARTICIPANT_TOKEN_COOKIE_NAME = 'OvMeetParticipantToken'; -export const ACCESS_TOKEN_COOKIE_NAME = 'OvMeetAccessToken'; -export const REFRESH_TOKEN_COOKIE_NAME = 'OvMeetRefreshToken'; - -// Headers for API requests -export const API_KEY_HEADER = 'x-api-key'; - -// Fixed usernames -export const MEET_ANONYMOUS_USER = 'anonymous'; -export const MEET_API_USER = 'api-user'; - -// S3 prefixes -export const MEET_S3_ROOMS_PREFIX = 'rooms'; -export const MEET_S3_RECORDINGS_PREFIX = 'recordings'; - -export const MEET_ROOM_GC_INTERVAL: ms.StringValue = '1h'; - -// Time to live for the active recording lock in Redis -export const MEET_RECORDING_LOCK_TTL: ms.StringValue = '6h'; -export const MEET_RECORDING_STARTED_TIMEOUT: ms.StringValue = '30s'; -export const MEET_RECORDING_LOCK_GC_INTERVAL: ms.StringValue = '30m'; - export function checkModuleEnabled() { if (MODULES_FILE) { const moduleName = MODULE_NAME; diff --git a/backend/src/middlewares/auth.middleware.ts b/backend/src/middlewares/auth.middleware.ts index dd27f00..328122e 100644 --- a/backend/src/middlewares/auth.middleware.ts +++ b/backend/src/middlewares/auth.middleware.ts @@ -1,13 +1,6 @@ import { NextFunction, Request, RequestHandler, Response } from 'express'; import { LoggerService, TokenService, UserService } from '../services/index.js'; -import { - ACCESS_TOKEN_COOKIE_NAME, - API_KEY_HEADER, - MEET_ANONYMOUS_USER, - MEET_API_KEY, - MEET_API_USER, - PARTICIPANT_TOKEN_COOKIE_NAME -} from '../environment.js'; +import { MEET_API_KEY } from '../environment.js'; import { container } from '../config/dependency-injector.config.js'; import { ClaimGrants } from 'livekit-server-sdk'; import { User, UserRole } from '@typings-ce'; @@ -21,6 +14,7 @@ import { } from '../models/index.js'; import rateLimit from 'express-rate-limit'; import ms from 'ms'; +import INTERNAL_CONFIG from '../config/internal-config.js'; /** * This middleware allows to chain multiple validators to check if the request is authorized. @@ -58,7 +52,7 @@ export const withAuth = (...validators: ((req: Request) => Promise)[]): Re // Configure token validatior for role-based access export const tokenAndRoleValidator = (role: UserRole) => { return async (req: Request) => { - const token = req.cookies[ACCESS_TOKEN_COOKIE_NAME]; + const token = req.cookies[INTERNAL_CONFIG.ACCESS_TOKEN_COOKIE_NAME]; if (!token) { throw errorUnauthorized(); @@ -92,7 +86,7 @@ export const tokenAndRoleValidator = (role: UserRole) => { // Configure token validatior for participant access export const participantTokenValidator = async (req: Request) => { - const token = req.cookies[PARTICIPANT_TOKEN_COOKIE_NAME]; + const token = req.cookies[INTERNAL_CONFIG.PARTICIPANT_TOKEN_COOKIE_NAME]; if (!token) { throw errorUnauthorized(); @@ -111,7 +105,7 @@ export const participantTokenValidator = async (req: Request) => { // Configure API key validatior export const apiKeyValidator = async (req: Request) => { - const apiKey = req.headers[API_KEY_HEADER]; + const apiKey = req.headers[INTERNAL_CONFIG.API_KEY_HEADER]; if (!apiKey) { throw errorUnauthorized(); @@ -122,7 +116,7 @@ export const apiKeyValidator = async (req: Request) => { } const apiUser = { - username: MEET_API_USER, + username: INTERNAL_CONFIG.API_USER, role: UserRole.APP }; @@ -135,7 +129,7 @@ export const allowAnonymous = async (req: Request) => { let user: User | null = null; // Check if there is a user already authenticated - const token = req.cookies[ACCESS_TOKEN_COOKIE_NAME]; + const token = req.cookies[INTERNAL_CONFIG.ACCESS_TOKEN_COOKIE_NAME]; if (token) { try { @@ -152,7 +146,7 @@ export const allowAnonymous = async (req: Request) => { if (!user) { user = { - username: MEET_ANONYMOUS_USER, + username: INTERNAL_CONFIG.ANONYMOUS_USER, role: UserRole.USER }; } diff --git a/backend/src/server.ts b/backend/src/server.ts index ac791c2..3cd9d47 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -6,8 +6,6 @@ import { SERVER_PORT, SERVER_CORS_ORIGIN, logEnvVars, - MEET_API_BASE_PATH_V1, - MEET_INTERNAL_API_BASE_PATH_V1 } from './environment.js'; import { publicApiHtmlFilePath, @@ -28,6 +26,7 @@ import { import { internalParticipantsRouter } from './routes/participants.routes.js'; import cookieParser from 'cookie-parser'; import { jsonSyntaxErrorHandler } from './middlewares/content-type.middleware.js'; +import INTERNAL_CONFIG from './config/internal-config.js'; const createApp = () => { const app: Express = express(); @@ -49,19 +48,19 @@ const createApp = () => { app.use(cookieParser()); // Public API routes - app.use(`${MEET_API_BASE_PATH_V1}/docs`, (_req: Request, res: Response) => res.sendFile(publicApiHtmlFilePath)); - app.use(`${MEET_API_BASE_PATH_V1}/rooms`, /*mediaTypeValidatorMiddleware,*/ roomRouter); - app.use(`${MEET_API_BASE_PATH_V1}/recordings`, /*mediaTypeValidatorMiddleware,*/ recordingRouter); + app.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); // Internal API routes - app.use(`${MEET_INTERNAL_API_BASE_PATH_V1}/docs`, (_req: Request, res: Response) => + app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/docs`, (_req: Request, res: Response) => res.sendFile(internalApiHtmlFilePath) ); - app.use(`${MEET_INTERNAL_API_BASE_PATH_V1}/auth`, authRouter); - app.use(`${MEET_INTERNAL_API_BASE_PATH_V1}/rooms`, internalRoomRouter); - app.use(`${MEET_INTERNAL_API_BASE_PATH_V1}/participants`, internalParticipantsRouter); - app.use(`${MEET_INTERNAL_API_BASE_PATH_V1}/recordings`, internalRecordingRouter); - app.use(`${MEET_INTERNAL_API_BASE_PATH_V1}/preferences`, preferencesRouter); + app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/auth`, authRouter); + app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/rooms`, internalRoomRouter); + app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/participants`, internalParticipantsRouter); + app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/recordings`, internalRecordingRouter); + app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/preferences`, preferencesRouter); app.use('/meet/health', (_req: Request, res: Response) => res.status(200).send('OK')); @@ -85,7 +84,7 @@ const startServer = (app: express.Application) => { console.log('OpenVidu Meet is listening on port', chalk.cyanBright(SERVER_PORT)); console.log( 'REST API Docs: ', - chalk.cyanBright(`http://localhost:${SERVER_PORT}${MEET_API_BASE_PATH_V1}/docs`) + chalk.cyanBright(`http://localhost:${SERVER_PORT}${INTERNAL_CONFIG.API_BASE_PATH_V1}/docs`) ); logEnvVars(); }); diff --git a/backend/src/services/livekit-webhook.service.ts b/backend/src/services/livekit-webhook.service.ts index ac719a2..d9113d3 100644 --- a/backend/src/services/livekit-webhook.service.ts +++ b/backend/src/services/livekit-webhook.service.ts @@ -3,7 +3,7 @@ import { EgressInfo, ParticipantInfo, Room, WebhookEvent, WebhookReceiver } from import { RecordingHelper } from '../helpers/recording.helper.js'; import { LiveKitService } from './livekit.service.js'; import { MeetRecordingInfo, MeetRecordingStatus } from '@typings-ce'; -import { LIVEKIT_API_KEY, LIVEKIT_API_SECRET, MEET_NAME_ID, MEET_S3_RECORDINGS_PREFIX } from '../environment.js'; +import { LIVEKIT_API_KEY, LIVEKIT_API_SECRET, MEET_NAME_ID } from '../environment.js'; import { LoggerService } from './logger.service.js'; import { RoomService } from './room.service.js'; import { S3Service } from './s3.service.js'; @@ -13,6 +13,7 @@ import { MutexService } from './mutex.service.js'; import { SystemEventService } from './system-event.service.js'; import { SystemEventType } from '../models/system-event.model.js'; import { MeetRoomHelper } from '../helpers/room.helper.js'; +import INTERNAL_CONFIG from '../config/internal-config.js'; @injectable() export class LivekitWebhookService { @@ -239,11 +240,11 @@ export class LivekitWebhookService { * This method checks if secrets for the specified room exist in the S3 storage. * If they don't exist, it retrieves the room information, extracts the publisher * and moderator secrets, and saves them to an S3 bucket under the path - * `${MEET_S3_RECORDINGS_PREFIX}/.metadata/${roomId}/secrets.json`. + * `${INTERNAL_CONFIG.S3_RECORDINGS_PREFIX}/.metadata/${roomId}/secrets.json`. */ protected async saveRoomSecretsFileIfNeeded(roomId: string): Promise { try { - const filePath = `${MEET_S3_RECORDINGS_PREFIX}/.metadata/${roomId}/secrets.json`; + const filePath = `${INTERNAL_CONFIG.S3_RECORDINGS_PREFIX}/.metadata/${roomId}/secrets.json`; const fileExists = await this.s3Service.exists(filePath); if (fileExists) { @@ -270,6 +271,6 @@ export class LivekitWebhookService { protected buildMetadataFilePath(recordingId: string): string { const { roomId, egressId, uid } = RecordingHelper.extractInfoFromRecordingId(recordingId); - return `${MEET_S3_RECORDINGS_PREFIX}/.metadata/${roomId}/${egressId}/${uid}.json`; + return `${INTERNAL_CONFIG.S3_RECORDINGS_PREFIX}/.metadata/${roomId}/${egressId}/${uid}.json`; } } diff --git a/backend/src/services/recording.service.ts b/backend/src/services/recording.service.ts index 94f6bb2..9d47639 100644 --- a/backend/src/services/recording.service.ts +++ b/backend/src/services/recording.service.ts @@ -22,11 +22,7 @@ import { LoggerService } from './logger.service.js'; import { MeetRecordingFilters, MeetRecordingInfo, MeetRecordingStatus } from '@typings-ce'; import { RecordingHelper } from '../helpers/recording.helper.js'; import { - MEET_RECORDING_LOCK_GC_INTERVAL, - MEET_RECORDING_LOCK_TTL, - MEET_RECORDING_STARTED_TIMEOUT, MEET_S3_BUCKET, - MEET_S3_RECORDINGS_PREFIX, MEET_S3_SUBBUCKET } from '../environment.js'; import { RoomService } from './room.service.js'; @@ -38,6 +34,7 @@ import { IScheduledTask, TaskSchedulerService } from './task-scheduler.service.j import { SystemEventService } from './system-event.service.js'; import { SystemEventType } from '../models/system-event.model.js'; import { UtilsHelper } from '../helpers/utils.helper.js'; +import INTERNAL_CONFIG from '../config/internal-config.js'; @injectable() export class RecordingService { @@ -54,7 +51,7 @@ export class RecordingService { const recordingGarbageCollectorTask: IScheduledTask = { name: 'activeRecordingGarbageCollector', type: 'cron', - scheduleOrDelay: MEET_RECORDING_LOCK_GC_INTERVAL, + scheduleOrDelay: INTERNAL_CONFIG.RECORDING_LOCK_GC_INTERVAL, callback: this.deleteOrphanLocks.bind(this) }; this.taskSchedulerService.registerTask(recordingGarbageCollectorTask); @@ -93,7 +90,7 @@ export class RecordingService { this.taskSchedulerService.registerTask({ name: `${roomId}_recording_timeout`, type: 'timeout', - scheduleOrDelay: MEET_RECORDING_STARTED_TIMEOUT, + scheduleOrDelay: INTERNAL_CONFIG.RECORDING_STARTED_TIMEOUT, callback: this.handleRecordingLockTimeout.bind(this, recordingId, roomId, reject) }); @@ -257,7 +254,7 @@ export class RecordingService { // Retrieve the recordings from the S3 bucket const { Contents, IsTruncated, NextContinuationToken } = await this.s3Service.listObjectsPaginated( - `${MEET_S3_RECORDINGS_PREFIX}/.metadata${roomPrefix}`, + `${INTERNAL_CONFIG.S3_RECORDINGS_PREFIX}/.metadata${roomPrefix}`, maxItems, nextPageToken ); @@ -294,7 +291,7 @@ export class RecordingService { ): Promise<{ fileSize: number | undefined; fileStream: Readable; start?: number; end?: number }> { const RECORDING_FILE_PORTION_SIZE = 5 * 1024 * 1024; // 5MB const recordingInfo: MeetRecordingInfo = await this.getRecording(recordingId); - const recordingPath = `${MEET_S3_RECORDINGS_PREFIX}/${RecordingHelper.extractFilename(recordingInfo)}`; + const recordingPath = `${INTERNAL_CONFIG.S3_RECORDINGS_PREFIX}/${RecordingHelper.extractFilename(recordingInfo)}`; if (!recordingPath) throw new Error(`Error extracting path from recording ${recordingId}`); @@ -332,7 +329,7 @@ export class RecordingService { const lockName = MeetLock.getRecordingActiveLock(roomId); try { - const lock = await this.mutexService.acquire(lockName, ms(MEET_RECORDING_LOCK_TTL)); + const lock = await this.mutexService.acquire(lockName, ms(INTERNAL_CONFIG.RECORDING_LOCK_TTL)); return lock; } catch (error) { this.logger.warn(`Error acquiring lock ${lockName} on egress started: ${error}`); @@ -422,7 +419,7 @@ export class RecordingService { ): Promise<{ metadataFilePath: string; recordingInfo: MeetRecordingInfo }> { const { roomId, egressId, uid } = RecordingHelper.extractInfoFromRecordingId(recordingId); - const metadataPath = `${MEET_S3_RECORDINGS_PREFIX}/.metadata/${roomId}/${egressId}/${uid}.json`; + const metadataPath = `${INTERNAL_CONFIG.S3_RECORDINGS_PREFIX}/.metadata/${roomId}/${egressId}/${uid}.json`; this.logger.debug(`Retrieving metadata for recording ${recordingId} from ${metadataPath}`); const recordingInfo = (await this.s3Service.getObjectAsJson(metadataPath)) as MeetRecordingInfo; @@ -455,7 +452,7 @@ export class RecordingService { const recordingName = `${roomId}--${uid(10)}`; // Generate the file path with the openviud-meet subbucket and the recording prefix - const filepath = `${MEET_S3_SUBBUCKET}/${MEET_S3_RECORDINGS_PREFIX}/${roomId}/${recordingName}`; + const filepath = `${MEET_S3_SUBBUCKET}/${INTERNAL_CONFIG.S3_RECORDINGS_PREFIX}/${roomId}/${recordingName}`; return new EncodedFileOutput({ fileType: EncodedFileType.DEFAULT_FILETYPE, @@ -551,7 +548,7 @@ export class RecordingService { try { // List all objects in the metadata directory for the room const { Contents } = await this.s3Service.listObjectsPaginated( - `${MEET_S3_RECORDINGS_PREFIX}/.metadata/${roomId}` + `${INTERNAL_CONFIG.S3_RECORDINGS_PREFIX}/.metadata/${roomId}` ); // Check if the contents number are valid. @@ -566,7 +563,7 @@ export class RecordingService { // If the only other file is the secrets.json file, add it to the filesToDelete array if (otherFiles.length === 1 && otherFiles[0].Key?.endsWith('secrets.json')) { - return `${MEET_S3_RECORDINGS_PREFIX}/.metadata/${roomId}/secrets.json`; + return `${INTERNAL_CONFIG.S3_RECORDINGS_PREFIX}/.metadata/${roomId}/secrets.json`; } return null; diff --git a/backend/src/services/room.service.ts b/backend/src/services/room.service.ts index c58af66..b30ceef 100644 --- a/backend/src/services/room.service.ts +++ b/backend/src/services/room.service.ts @@ -11,9 +11,9 @@ import { IScheduledTask, TaskSchedulerService } from './task-scheduler.service.j import { errorParticipantUnauthorized, internalError } from '../models/error.model.js'; import { OpenViduComponentsAdapterHelper } from '../helpers/index.js'; import { uid } from 'uid/single'; -import { MEET_NAME_ID, MEET_ROOM_GC_INTERVAL } from '../environment.js'; -import ms from 'ms'; +import { MEET_NAME_ID } from '../environment.js'; import { UtilsHelper } from '../helpers/utils.helper.js'; +import INTERNAL_CONFIG from '../config/internal-config.js'; /** * Service for managing OpenVidu Meet rooms. @@ -33,7 +33,7 @@ export class RoomService { const roomGarbageCollectorTask: IScheduledTask = { name: 'roomGarbageCollector', type: 'cron', - scheduleOrDelay: MEET_ROOM_GC_INTERVAL, + scheduleOrDelay: INTERNAL_CONFIG.ROOM_GC_INTERVAL, callback: this.deleteExpiredRooms.bind(this) }; this.taskSchedulerService.registerTask(roomGarbageCollectorTask); diff --git a/backend/src/services/storage/providers/s3-storage.provider.ts b/backend/src/services/storage/providers/s3-storage.provider.ts index 0373a98..caa76bd 100644 --- a/backend/src/services/storage/providers/s3-storage.provider.ts +++ b/backend/src/services/storage/providers/s3-storage.provider.ts @@ -5,9 +5,9 @@ import { LoggerService } from '../../logger.service.js'; import { RedisService } from '../../redis.service.js'; import { OpenViduMeetError } from '../../../models/error.model.js'; import { inject, injectable } from '../../../config/dependency-injector.config.js'; -import { MEET_S3_ROOMS_PREFIX } from '../../../environment.js'; import { RedisKeyName } from '../../../models/redis.model.js'; import { PutObjectCommandOutput } from '@aws-sdk/client-s3'; +import INTERNAL_CONFIG from '../../../config/internal-config.js'; /** * Implementation of the StorageProvider interface using AWS S3 for persistent storage @@ -142,7 +142,7 @@ export class S3StorageProvider { const { roomId } = ovRoom; - const s3Path = `${MEET_S3_ROOMS_PREFIX}/${roomId}/${roomId}.json`; + const s3Path = `${INTERNAL_CONFIG.S3_ROOMS_PREFIX}/${roomId}/${roomId}.json`; const redisPayload = JSON.stringify(ovRoom); const redisKey = RedisKeyName.ROOM + roomId; @@ -186,7 +186,7 @@ export class S3StorageProvider(roomId); if (!room) { - const s3RoomPath = `${MEET_S3_ROOMS_PREFIX}/${roomId}/${roomId}.json`; + const s3RoomPath = `${INTERNAL_CONFIG.S3_ROOMS_PREFIX}/${roomId}/${roomId}.json`; this.logger.debug(`Room ${roomId} not found in Redis. Fetching from S3 at ${s3RoomPath}...`); return await this.getFromS3(s3RoomPath); @@ -241,7 +241,7 @@ export class S3StorageProvider { - const roomsToDelete = roomIds.map((id) => `${MEET_S3_ROOMS_PREFIX}/${id}/${id}.json`); + const roomsToDelete = roomIds.map((id) => `${INTERNAL_CONFIG.S3_ROOMS_PREFIX}/${id}/${id}.json`); const redisKeysToDelete = roomIds.map((id) => RedisKeyName.ROOM + id); try { diff --git a/backend/tests/utils/helpers.ts b/backend/tests/utils/helpers.ts index aa55b0b..e434b8a 100644 --- a/backend/tests/utils/helpers.ts +++ b/backend/tests/utils/helpers.ts @@ -5,17 +5,15 @@ import { Server } from 'http'; import { createApp, registerDependencies } from '../../src/server.js'; import { SERVER_PORT, - MEET_API_BASE_PATH_V1, - MEET_INTERNAL_API_BASE_PATH_V1, MEET_API_KEY, MEET_USER, MEET_SECRET, MEET_ADMIN_USER, - MEET_ADMIN_SECRET, - API_KEY_HEADER + MEET_ADMIN_SECRET } from '../../src/environment.js'; import { AuthMode, AuthType, MeetRoom, UserRole, MeetRoomOptions } from '../../src/typings/ce/index.js'; import { expect } from '@jest/globals'; +import INTERNAL_CONFIG from '../../src/config/internal-config.js'; const CREDENTIALS = { user: { @@ -96,7 +94,7 @@ export const changeSecurityPreferences = async ( } await request(app) - .put(`${MEET_INTERNAL_API_BASE_PATH_V1}/preferences/security`) + .put(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/preferences/security`) .set('Cookie', adminCookie) .send({ roomCreationPolicy: { @@ -123,7 +121,7 @@ export const loginUserAsRole = async (role: UserRole): Promise => { const credentials = role === UserRole.ADMIN ? CREDENTIALS.admin : CREDENTIALS.user; const response = await request(app) - .post(`${MEET_INTERNAL_API_BASE_PATH_V1}/auth/login`) + .post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/auth/login`) .send(credentials) .expect(200); @@ -141,8 +139,8 @@ export const createRoom = async (options: MeetRoomOptions = {}): Promise = {}) => { throw new Error('App instance is not defined'); } - return await request(app).get(`${MEET_API_BASE_PATH_V1}/rooms`).set(API_KEY_HEADER, MEET_API_KEY).query(query); + return await request(app) + .get(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/rooms`) + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_API_KEY) + .query(query); }; /** @@ -208,8 +209,8 @@ export const getRoom = async (roomId: string, fields?: string) => { } return await request(app) - .get(`${MEET_API_BASE_PATH_V1}/rooms/${roomId}`) - .set(API_KEY_HEADER, MEET_API_KEY) + .get(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/rooms/${roomId}`) + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_API_KEY) .query({ fields }); }; @@ -235,9 +236,9 @@ export const deleteAllRooms = async () => { do { const response: any = await request(app) - .get(`${MEET_API_BASE_PATH_V1}/rooms`) + .get(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/rooms`) .query({ fields: 'roomId', maxItems: 100, nextPageToken }) - .set(API_KEY_HEADER, MEET_API_KEY) + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_API_KEY) .expect(200); nextPageToken = response.body.pagination?.nextPageToken ?? undefined; @@ -248,9 +249,9 @@ export const deleteAllRooms = async () => { } await request(app) - .delete(`${MEET_API_BASE_PATH_V1}/rooms`) + .delete(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/rooms`) .query({ roomIds: roomIds.join(','), force: true }) - .set(API_KEY_HEADER, MEET_API_KEY); + .set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_API_KEY); await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait for 1 second } while (nextPageToken); @@ -272,7 +273,7 @@ export const generateParticipantToken = async ( // Generate the participant token const response = await request(app) - .post(`${MEET_INTERNAL_API_BASE_PATH_V1}/participants/token`) + .post(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/participants/token`) .send({ roomId, participantName, @@ -285,3 +286,7 @@ export const generateParticipantToken = async ( const participantTokenCookie = cookies.find((cookie) => cookie.startsWith('OvMeetParticipantToken=')) as string; return participantTokenCookie; }; + +export const sleep = (ms: number) => { + return new Promise((resolve) => setTimeout(resolve, ms)); +};