backend: use env var MEET_BASE_URL to set base URL in moderatorUrl and speakerUrl of room objects dynamically instead of storing it
This commit is contained in:
parent
fa1582bee0
commit
0abbaddaf9
@ -3,6 +3,7 @@ import { Request, Response } from 'express';
|
|||||||
import { Readable } from 'stream';
|
import { Readable } from 'stream';
|
||||||
import { container } from '../config/index.js';
|
import { container } from '../config/index.js';
|
||||||
import INTERNAL_CONFIG from '../config/internal-config.js';
|
import INTERNAL_CONFIG from '../config/internal-config.js';
|
||||||
|
import { getBaseUrl } from '../environment.js';
|
||||||
import { RecordingHelper } from '../helpers/index.js';
|
import { RecordingHelper } from '../helpers/index.js';
|
||||||
import {
|
import {
|
||||||
errorRecordingNotFound,
|
errorRecordingNotFound,
|
||||||
@ -23,7 +24,7 @@ export const startRecording = async (req: Request, res: Response) => {
|
|||||||
const recordingInfo = await recordingService.startRecording(roomId);
|
const recordingInfo = await recordingService.startRecording(roomId);
|
||||||
res.setHeader(
|
res.setHeader(
|
||||||
'Location',
|
'Location',
|
||||||
`${req.protocol}://${req.get('host')}${INTERNAL_CONFIG.API_BASE_PATH_V1}/recordings/${recordingInfo.recordingId}`
|
`${getBaseUrl()}${INTERNAL_CONFIG.API_BASE_PATH_V1}/recordings/${recordingInfo.recordingId}`
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(201).json(recordingInfo);
|
return res.status(201).json(recordingInfo);
|
||||||
@ -126,7 +127,7 @@ export const stopRecording = async (req: Request, res: Response) => {
|
|||||||
const recordingInfo = await recordingService.stopRecording(recordingId);
|
const recordingInfo = await recordingService.stopRecording(recordingId);
|
||||||
res.setHeader(
|
res.setHeader(
|
||||||
'Location',
|
'Location',
|
||||||
`${req.protocol}://${req.get('host')}${INTERNAL_CONFIG.API_BASE_PATH_V1}/recordings/${recordingId}`
|
`${getBaseUrl()}${INTERNAL_CONFIG.API_BASE_PATH_V1}/recordings/${recordingId}`
|
||||||
);
|
);
|
||||||
return res.status(202).json(recordingInfo);
|
return res.status(202).json(recordingInfo);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -250,7 +251,7 @@ export const getRecordingUrl = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const secret = privateAccess ? recordingSecrets.privateAccessSecret : recordingSecrets.publicAccessSecret;
|
const secret = privateAccess ? recordingSecrets.privateAccessSecret : recordingSecrets.publicAccessSecret;
|
||||||
const recordingUrl = `${req.protocol}://${req.get('host')}/recording/${recordingId}?secret=${secret}`;
|
const recordingUrl = `${getBaseUrl()}/recording/${recordingId}?secret=${secret}`;
|
||||||
|
|
||||||
return res.status(200).json({ url: recordingUrl });
|
return res.status(200).json({ url: recordingUrl });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import {
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { container } from '../config/index.js';
|
import { container } from '../config/index.js';
|
||||||
import INTERNAL_CONFIG from '../config/internal-config.js';
|
import INTERNAL_CONFIG from '../config/internal-config.js';
|
||||||
|
import { getBaseUrl } from '../environment.js';
|
||||||
import { handleError } from '../models/error.model.js';
|
import { handleError } from '../models/error.model.js';
|
||||||
import { LoggerService, ParticipantService, RoomService } from '../services/index.js';
|
import { LoggerService, ParticipantService, RoomService } from '../services/index.js';
|
||||||
import { getCookieOptions } from '../utils/cookie-utils.js';
|
import { getCookieOptions } from '../utils/cookie-utils.js';
|
||||||
@ -21,10 +22,9 @@ export const createRoom = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
logger.verbose(`Creating room with options '${JSON.stringify(options)}'`);
|
logger.verbose(`Creating room with options '${JSON.stringify(options)}'`);
|
||||||
const baseUrl = `${req.protocol}://${req.get('host')}`;
|
|
||||||
|
|
||||||
const room = await roomService.createMeetRoom(baseUrl, options);
|
const room = await roomService.createMeetRoom(options);
|
||||||
res.set('Location', `${baseUrl}${INTERNAL_CONFIG.API_BASE_PATH_V1}/rooms/${room.roomId}`);
|
res.set('Location', `${getBaseUrl()}${INTERNAL_CONFIG.API_BASE_PATH_V1}/rooms/${room.roomId}`);
|
||||||
return res.status(201).json(room);
|
return res.status(201).json(room);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(res, error, 'creating room');
|
handleError(res, error, 'creating room');
|
||||||
|
|||||||
@ -20,6 +20,7 @@ export const {
|
|||||||
SERVER_CORS_ORIGIN = '*',
|
SERVER_CORS_ORIGIN = '*',
|
||||||
MEET_LOG_LEVEL = 'info',
|
MEET_LOG_LEVEL = 'info',
|
||||||
MEET_NAME_ID = 'openviduMeet',
|
MEET_NAME_ID = 'openviduMeet',
|
||||||
|
MEET_BASE_URL = `http://localhost:${SERVER_PORT}`,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authentication configuration
|
* Authentication configuration
|
||||||
@ -85,6 +86,13 @@ export const {
|
|||||||
ENABLED_MODULES = ''
|
ENABLED_MODULES = ''
|
||||||
} = process.env;
|
} = process.env;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the base URL without trailing slash
|
||||||
|
*/
|
||||||
|
export const getBaseUrl = (): string => {
|
||||||
|
return MEET_BASE_URL.endsWith('/') ? MEET_BASE_URL.slice(0, -1) : MEET_BASE_URL;
|
||||||
|
};
|
||||||
|
|
||||||
export function checkModuleEnabled() {
|
export function checkModuleEnabled() {
|
||||||
if (MODULES_FILE) {
|
if (MODULES_FILE) {
|
||||||
const moduleName = MODULE_NAME;
|
const moduleName = MODULE_NAME;
|
||||||
|
|||||||
@ -72,14 +72,13 @@ export class RoomService {
|
|||||||
/**
|
/**
|
||||||
* Creates an OpenVidu Meet room with the specified options.
|
* Creates an OpenVidu Meet room with the specified options.
|
||||||
*
|
*
|
||||||
* @param {string} baseUrl - The base URL for the room.
|
|
||||||
* @param {MeetRoomOptions} options - The options for creating the OpenVidu room.
|
* @param {MeetRoomOptions} options - The options for creating the OpenVidu room.
|
||||||
* @returns {Promise<MeetRoom>} A promise that resolves to the created OpenVidu room.
|
* @returns {Promise<MeetRoom>} A promise that resolves to the created OpenVidu room.
|
||||||
*
|
*
|
||||||
* @throws {Error} If the room creation fails.
|
* @throws {Error} If the room creation fails.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async createMeetRoom(baseUrl: string, roomOptions: MeetRoomOptions): Promise<MeetRoom> {
|
async createMeetRoom(roomOptions: MeetRoomOptions): Promise<MeetRoom> {
|
||||||
const { roomName, autoDeletionDate, autoDeletionPolicy, config } = roomOptions;
|
const { roomName, autoDeletionDate, autoDeletionPolicy, config } = roomOptions;
|
||||||
const roomIdPrefix = roomName!.replace(/\s+/g, ''); // Remove all spaces
|
const roomIdPrefix = roomName!.replace(/\s+/g, ''); // Remove all spaces
|
||||||
const roomId = `${roomIdPrefix}-${uid(15)}`; // Generate a unique room ID based on the room name
|
const roomId = `${roomIdPrefix}-${uid(15)}`; // Generate a unique room ID based on the room name
|
||||||
@ -92,14 +91,12 @@ export class RoomService {
|
|||||||
autoDeletionDate,
|
autoDeletionDate,
|
||||||
autoDeletionPolicy,
|
autoDeletionPolicy,
|
||||||
config: config!,
|
config: config!,
|
||||||
moderatorUrl: `${baseUrl}/room/${roomId}?secret=${secureUid(10)}`,
|
moderatorUrl: `/room/${roomId}?secret=${secureUid(10)}`,
|
||||||
speakerUrl: `${baseUrl}/room/${roomId}?secret=${secureUid(10)}`,
|
speakerUrl: `/room/${roomId}?secret=${secureUid(10)}`,
|
||||||
status: MeetRoomStatus.OPEN,
|
status: MeetRoomStatus.OPEN,
|
||||||
meetingEndAction: MeetingEndAction.NONE
|
meetingEndAction: MeetingEndAction.NONE
|
||||||
};
|
};
|
||||||
|
return await this.storageService.saveMeetRoom(meetRoom);
|
||||||
await this.storageService.saveMeetRoom(meetRoom);
|
|
||||||
return meetRoom;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { inject, injectable } from 'inversify';
|
|||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
import { Readable } from 'stream';
|
import { Readable } from 'stream';
|
||||||
import {
|
import {
|
||||||
|
getBaseUrl,
|
||||||
MEET_INITIAL_ADMIN_PASSWORD,
|
MEET_INITIAL_ADMIN_PASSWORD,
|
||||||
MEET_INITIAL_ADMIN_USER,
|
MEET_INITIAL_ADMIN_USER,
|
||||||
MEET_INITIAL_API_KEY,
|
MEET_INITIAL_API_KEY,
|
||||||
@ -168,7 +169,12 @@ export class MeetStorageService<
|
|||||||
const redisKey = RedisKeyName.ROOM + roomId;
|
const redisKey = RedisKeyName.ROOM + roomId;
|
||||||
const storageKey = this.keyBuilder.buildMeetRoomKey(roomId);
|
const storageKey = this.keyBuilder.buildMeetRoomKey(roomId);
|
||||||
|
|
||||||
return await this.saveCacheAndStorage<MRoom>(redisKey, storageKey, meetRoom);
|
// Normalize room data for storage (ensure only paths are stored)
|
||||||
|
const normalizedRoom = this.normalizeRoomForStorage(meetRoom);
|
||||||
|
await this.saveCacheAndStorage<MRoom>(redisKey, storageKey, normalizedRoom);
|
||||||
|
|
||||||
|
// Return room with full URLs
|
||||||
|
return this.enrichRoomWithBaseUrls(normalizedRoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -206,7 +212,13 @@ export class MeetStorageService<
|
|||||||
if (item.Key && item.Key.endsWith('.json')) {
|
if (item.Key && item.Key.endsWith('.json')) {
|
||||||
try {
|
try {
|
||||||
const room = await this.storageProvider.getObject<MRoom>(item.Key);
|
const room = await this.storageProvider.getObject<MRoom>(item.Key);
|
||||||
return room;
|
|
||||||
|
if (!room) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add base URL to moderator and speaker URLs
|
||||||
|
return this.enrichRoomWithBaseUrls(room);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.warn(`Failed to load room from ${item.Key}: ${error}`);
|
this.logger.warn(`Failed to load room from ${item.Key}: ${error}`);
|
||||||
return null;
|
return null;
|
||||||
@ -235,7 +247,14 @@ export class MeetStorageService<
|
|||||||
const redisKey = RedisKeyName.ROOM + roomId;
|
const redisKey = RedisKeyName.ROOM + roomId;
|
||||||
const storageKey = this.keyBuilder.buildMeetRoomKey(roomId);
|
const storageKey = this.keyBuilder.buildMeetRoomKey(roomId);
|
||||||
|
|
||||||
return await this.getFromCacheAndStorage<MRoom>(redisKey, storageKey);
|
const room = await this.getFromCacheAndStorage<MRoom>(redisKey, storageKey);
|
||||||
|
|
||||||
|
if (!room) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add base URL to moderator and speaker URLs
|
||||||
|
return this.enrichRoomWithBaseUrls(room);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteMeetRooms(roomIds: string[]): Promise<void> {
|
async deleteMeetRooms(roomIds: string[]): Promise<void> {
|
||||||
@ -253,7 +272,14 @@ export class MeetStorageService<
|
|||||||
const redisKey = RedisKeyName.ARCHIVED_ROOM + roomId;
|
const redisKey = RedisKeyName.ARCHIVED_ROOM + roomId;
|
||||||
const storageKey = this.keyBuilder.buildArchivedMeetRoomKey(roomId);
|
const storageKey = this.keyBuilder.buildArchivedMeetRoomKey(roomId);
|
||||||
|
|
||||||
return await this.getFromCacheAndStorage<Partial<MRoom>>(redisKey, storageKey);
|
const archivedRoom = await this.getFromCacheAndStorage<Partial<MRoom>>(redisKey, storageKey);
|
||||||
|
|
||||||
|
if (!archivedRoom) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add base URL to moderator and speaker URLs
|
||||||
|
return this.enrichRoomWithBaseUrls(archivedRoom as MRoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -298,7 +324,9 @@ export class MeetStorageService<
|
|||||||
}
|
}
|
||||||
} as Partial<MRoom>;
|
} as Partial<MRoom>;
|
||||||
|
|
||||||
await this.saveCacheAndStorage<Partial<MRoom>>(redisKey, storageKey, archivedRoom);
|
// Normalize room data for storage (ensure only paths are stored)
|
||||||
|
const normalizedRoom = this.normalizeRoomForStorage(archivedRoom as MRoom);
|
||||||
|
await this.saveCacheAndStorage<Partial<MRoom>>(redisKey, storageKey, normalizedRoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteArchivedRoomMetadata(roomId: string): Promise<void> {
|
async deleteArchivedRoomMetadata(roomId: string): Promise<void> {
|
||||||
@ -731,6 +759,53 @@ export class MeetStorageService<
|
|||||||
this.logger.info('API key initialized');
|
this.logger.info('API key initialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes room data for storage by ensuring URLs contain only paths
|
||||||
|
* @param room - The room object to normalize
|
||||||
|
* @returns The room object with path-only URLs for storage
|
||||||
|
*/
|
||||||
|
private normalizeRoomForStorage(room: MRoom): MRoom {
|
||||||
|
return {
|
||||||
|
...room,
|
||||||
|
moderatorUrl: this.extractPathFromUrl(room.moderatorUrl),
|
||||||
|
speakerUrl: this.extractPathFromUrl(room.speakerUrl)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts path from URL, handling both full URLs and path-only strings
|
||||||
|
* @param url - The URL or path to process
|
||||||
|
* @returns The path portion of the URL
|
||||||
|
*/
|
||||||
|
private extractPathFromUrl(url: string): string {
|
||||||
|
// If it's already a path (starts with /), return as-is
|
||||||
|
if (url.startsWith('/')) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's a full URL, extract the path
|
||||||
|
try {
|
||||||
|
const urlObj = new URL(url);
|
||||||
|
return urlObj.pathname + urlObj.search + urlObj.hash;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.warn(`Failed to parse URL for path extraction: ${url}. Treating as path.`);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enriches a room object with base URLs for moderator and speaker URLs
|
||||||
|
* @param room - The room object to enrich
|
||||||
|
* @returns The room object with full URLs
|
||||||
|
*/
|
||||||
|
protected enrichRoomWithBaseUrls(room: MRoom): MRoom {
|
||||||
|
return {
|
||||||
|
...room,
|
||||||
|
moderatorUrl: `${getBaseUrl()}${room.moderatorUrl}`,
|
||||||
|
speakerUrl: `${getBaseUrl()}${room.speakerUrl}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
protected async getRecordingFileSize(key: string, recordingId: string): Promise<number> {
|
protected async getRecordingFileSize(key: string, recordingId: string): Promise<number> {
|
||||||
const { contentLength: fileSize } = await this.storageProvider.getObjectHeaders(key);
|
const { contentLength: fileSize } = await this.storageProvider.getObjectHeaders(key);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user