backend: Centralize configuration values in internal-config module
This commit is contained in:
parent
299497120d
commit
58dcd83229
42
backend/src/config/internal-config.ts
Normal file
42
backend/src/config/internal-config.ts
Normal file
@ -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<typeof INTERNAL_CONFIG>): void => {
|
||||
Object.assign(INTERNAL_CONFIG, overrides);
|
||||
};
|
||||
|
||||
export default INTERNAL_CONFIG;
|
||||
@ -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) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<void>)[]): 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
|
||||
};
|
||||
}
|
||||
|
||||
@ -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();
|
||||
});
|
||||
|
||||
@ -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<void> {
|
||||
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`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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<G extends GlobalPreferences = GlobalPreferences,
|
||||
*/
|
||||
async saveMeetRoom(ovRoom: R): Promise<R> {
|
||||
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<G extends GlobalPreferences = GlobalPreferences,
|
||||
Contents: roomFiles,
|
||||
IsTruncated,
|
||||
NextContinuationToken
|
||||
} = await this.s3Service.listObjectsPaginated(MEET_S3_ROOMS_PREFIX, maxItems, nextPageToken);
|
||||
} = await this.s3Service.listObjectsPaginated(INTERNAL_CONFIG.S3_ROOMS_PREFIX, maxItems, nextPageToken);
|
||||
|
||||
if (!roomFiles || roomFiles.length === 0) {
|
||||
this.logger.verbose('No room files found in S3.');
|
||||
@ -226,7 +226,7 @@ export class S3StorageProvider<G extends GlobalPreferences = GlobalPreferences,
|
||||
const room: R | null = await this.getFromRedis<R>(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<R>(s3RoomPath);
|
||||
@ -241,7 +241,7 @@ export class S3StorageProvider<G extends GlobalPreferences = GlobalPreferences,
|
||||
}
|
||||
|
||||
async deleteMeetRooms(roomIds: string[]): Promise<void> {
|
||||
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 {
|
||||
|
||||
@ -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<string> => {
|
||||
|
||||
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<MeetRoo
|
||||
}
|
||||
|
||||
const response = await request(app)
|
||||
.post(`${MEET_API_BASE_PATH_V1}/rooms`)
|
||||
.set(API_KEY_HEADER, MEET_API_KEY)
|
||||
.post(`${INTERNAL_CONFIG.API_BASE_PATH_V1}/rooms`)
|
||||
.set(INTERNAL_CONFIG.API_KEY_HEADER, MEET_API_KEY)
|
||||
.send(options)
|
||||
.expect(200);
|
||||
return response.body;
|
||||
@ -157,7 +155,10 @@ export const getRooms = async (query: Record<string, any> = {}) => {
|
||||
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));
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user