refactor: replace participantName with participantIdentity for consistency

This commit is contained in:
juancarmore 2025-08-13 17:20:16 +02:00
parent 1071c4c97e
commit 972f6f4f90
5 changed files with 113 additions and 86 deletions

View File

@ -31,7 +31,7 @@ export const generateParticipantToken = async (req: Request, res: Response) => {
try {
const claims = tokenService.getClaimsIgnoringExpiration(previousToken);
const metadata = JSON.parse(claims.metadata || '{}');
const metadata = participantService.parseMetadata(claims.metadata || '{}');
currentRoles = metadata.roles;
} catch (error) {
logger.verbose('Error extracting roles from previous token:', error);
@ -80,11 +80,12 @@ export const refreshParticipantToken = async (req: Request, res: Response) => {
}
// Extract roles from the previous token
const participantService = container.get(ParticipantService);
let currentRoles: { role: ParticipantRole; permissions: OpenViduMeetPermissions }[] = [];
try {
const claims = tokenService.getClaimsIgnoringExpiration(previousToken);
const metadata = JSON.parse(claims.metadata || '{}');
const metadata = participantService.parseMetadata(claims.metadata || '{}');
currentRoles = metadata.roles;
} catch (err) {
logger.verbose('Error extracting roles from previous token:', err);
@ -94,7 +95,6 @@ export const refreshParticipantToken = async (req: Request, res: Response) => {
const participantOptions: ParticipantOptions = req.body;
const { roomId } = participantOptions;
const participantService = container.get(ParticipantService);
try {
logger.verbose(`Refreshing participant token for room '${roomId}'`);
@ -111,11 +111,26 @@ export const refreshParticipantToken = async (req: Request, res: Response) => {
}
};
export const updateParticipant = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const participantService = container.get(ParticipantService);
const { roomId, participantIdentity } = req.params;
const { role } = req.body;
try {
logger.verbose(`Changing role of participant '${participantIdentity}' in room '${roomId}' to '${role}'`);
await participantService.updateParticipantRole(roomId, participantIdentity, role);
res.status(200).json({ message: `Participant '${participantIdentity}' role updated to '${role}'` });
} catch (error) {
handleError(res, error, `changing role for participant '${participantIdentity}' in room '${roomId}'`);
}
};
export const deleteParticipant = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const roomService = container.get(RoomService);
const participantService = container.get(ParticipantService);
const { roomId, participantName } = req.params;
const { roomId, participantIdentity } = req.params;
// Check if the room exists
try {
@ -125,25 +140,10 @@ export const deleteParticipant = async (req: Request, res: Response) => {
}
try {
logger.verbose(`Deleting participant '${participantName}' from room '${roomId}'`);
await participantService.deleteParticipant(roomId, participantName);
logger.verbose(`Deleting participant '${participantIdentity}' from room '${roomId}'`);
await participantService.deleteParticipant(roomId, participantIdentity);
res.status(200).json({ message: 'Participant deleted' });
} catch (error) {
handleError(res, error, `deleting participant '${participantName}' from room '${roomId}'`);
}
};
export const updateParticipant = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
const participantService = container.get(ParticipantService);
const { roomId, participantName } = req.params;
const { role } = req.body;
try {
logger.verbose(`Changing role of participant '${participantName}' in room '${roomId}' to '${role}'`);
await participantService.updateParticipantRole(roomId, participantName, role);
res.status(200).json({ message: `Participant '${participantName}' role updated to ${role}` });
} catch (error) {
handleError(res, error, `changing role for participant '${participantName}' in room '${roomId}'`);
handleError(res, error, `deleting participant '${participantIdentity}' from room '${roomId}'`);
}
};

View File

@ -2,7 +2,13 @@ import bodyParser from 'body-parser';
import { Router } from 'express';
import * as meetingCtrl from '../controllers/meeting.controller.js';
import * as participantCtrl from '../controllers/participant.controller.js';
import { participantTokenValidator, withAuth, withModeratorPermissions, withValidParticipantRole, withValidRoomId } from '../middlewares/index.js';
import {
participantTokenValidator,
validateUpdateParticipantRequest,
withAuth,
withModeratorPermissions,
withValidRoomId
} from '../middlewares/index.js';
export const internalMeetingRouter = Router();
internalMeetingRouter.use(bodyParser.urlencoded({ extended: true }));
@ -12,23 +18,22 @@ internalMeetingRouter.use(bodyParser.json());
internalMeetingRouter.delete(
'/:roomId',
withAuth(participantTokenValidator),
withModeratorPermissions,
withValidRoomId,
withModeratorPermissions,
meetingCtrl.endMeeting
);
internalMeetingRouter.delete(
'/:roomId/participants/:participantName',
withAuth(participantTokenValidator),
withModeratorPermissions,
withValidRoomId,
participantCtrl.deleteParticipant
);
internalMeetingRouter.patch(
'/:roomId/participants/:participantName',
'/:roomId/participants/:participantIdentity',
withAuth(participantTokenValidator),
withModeratorPermissions,
withValidRoomId,
withValidParticipantRole,
withModeratorPermissions,
validateUpdateParticipantRequest,
participantCtrl.updateParticipant
);
internalMeetingRouter.delete(
'/:roomId/participants/:participantIdentity',
withAuth(participantTokenValidator),
withValidRoomId,
withModeratorPermissions,
participantCtrl.deleteParticipant
);

View File

@ -23,8 +23,8 @@ import {
internalError,
OpenViduMeetError
} from '../models/error.model.js';
import { LoggerService } from './index.js';
import { chunkArray } from '../utils/array.utils.js';
import { LoggerService } from './index.js';
@injectable()
export class LiveKitService {
@ -77,7 +77,7 @@ export class LiveKitService {
*/
async roomHasParticipants(roomName: string): Promise<boolean> {
try {
const participants = await this.roomClient.listParticipants(roomName);
const participants = await this.listRoomParticipants(roomName);
return participants.length > 0;
} catch (error) {
return false;
@ -167,20 +167,35 @@ export class LiveKitService {
}
}
/**
* Lists all participants in a LiveKit room.
*
* @param roomName - The name of the room to list participants from
* @returns A promise that resolves to an array of participant information
*/
async listRoomParticipants(roomName: string): Promise<ParticipantInfo[]> {
try {
return await this.roomClient.listParticipants(roomName);
} catch (error) {
this.logger.error(`Error listing participants for room '${roomName}': ${error}`);
throw internalError(`listing participants for room '${roomName}'`);
}
}
/**
* Retrieves information about a specific participant in a LiveKit room.
*
* @param roomName - The name of the room where the participant is located
* @param participantName - The name of the participant to retrieve
* @param participantIdentity - The identity of the participant to retrieve
* @returns A Promise that resolves to the participant's information
* @throws An internal error if the participant cannot be found or another error occurs
*/
async getParticipant(roomName: string, participantName: string): Promise<ParticipantInfo> {
async getParticipant(roomName: string, participantIdentity: string): Promise<ParticipantInfo> {
try {
return await this.roomClient.getParticipant(roomName, participantName);
return await this.roomClient.getParticipant(roomName, participantIdentity);
} catch (error) {
this.logger.warn(`Participant ${participantName} not found in room ${roomName}: ${error}`);
throw errorParticipantNotFound(participantName, roomName);
this.logger.warn(`Participant ${participantIdentity} not found in room ${roomName}: ${error}`);
throw errorParticipantNotFound(participantIdentity, roomName);
}
}
@ -188,31 +203,31 @@ export class LiveKitService {
* Updates the metadata of a participant in a LiveKit room.
*
* @param roomName - The name of the room where the participant is located
* @param participantName - The name of the participant whose metadata will be updated
* @param participantIdentity - The identity of the participant whose metadata will be updated
* @param metadata - The new metadata to set for the participant
* @returns A Promise that resolves when the metadata has been successfully updated
* @throws An internal error if there is an issue updating the metadata
*/
async updateParticipantMetadata(roomName: string, participantName: string, metadata: string): Promise<void> {
async updateParticipantMetadata(roomName: string, participantIdentity: string, metadata: string): Promise<void> {
try {
await this.roomClient.updateParticipant(roomName, participantName, metadata);
this.logger.verbose(`Updated metadata for participant ${participantName} in room ${roomName}`);
await this.roomClient.updateParticipant(roomName, participantIdentity, metadata);
this.logger.verbose(`Updated metadata for participant '${participantIdentity}' in room '${roomName}'`);
} catch (error) {
this.logger.error(
`Error updating metadata for participant ${participantName} in room ${roomName}: ${error}`
`Error updating metadata for participant '${participantIdentity}' in room '${roomName}': ${error}`
);
throw internalError(`updating metadata for participant '${participantName}' in room '${roomName}'`);
throw internalError(`updating metadata for participant '${participantIdentity}' in room '${roomName}'`);
}
}
async deleteParticipant(participantName: string, roomName: string): Promise<void> {
const participantExists = await this.participantExists(roomName, participantName);
async deleteParticipant(roomName: string, participantIdentity: string): Promise<void> {
const participantExists = await this.participantExists(roomName, participantIdentity);
if (!participantExists) {
throw errorParticipantNotFound(participantName, roomName);
throw errorParticipantNotFound(participantIdentity, roomName);
}
await this.roomClient.removeParticipant(roomName, participantName);
await this.roomClient.removeParticipant(roomName, participantIdentity);
}
async sendData(roomName: string, rawData: Record<string, any>, options: SendDataOptions): Promise<void> {
@ -369,10 +384,10 @@ export class LiveKitService {
return participant.identity.startsWith('EG_') && participant.permission?.recorder === true;
}
private async participantExists(roomName: string, participantName: string): Promise<boolean> {
private async participantExists(roomName: string, participantIdentity: string): Promise<boolean> {
try {
const participants: ParticipantInfo[] = await this.roomClient.listParticipants(roomName);
return participants.some((participant) => participant.identity === participantName);
const participants: ParticipantInfo[] = await this.listRoomParticipants(roomName);
return participants.some((participant) => participant.identity === participantIdentity);
} catch (error: any) {
this.logger.error(error);

View File

@ -7,9 +7,10 @@ import {
} from '@typings-ce';
import { inject, injectable } from 'inversify';
import { ParticipantInfo } from 'livekit-server-sdk';
import { MeetRoomHelper } from '../helpers/room.helper.js';
import { validateMeetTokenMetadata } from '../middlewares/index.js';
import { errorParticipantAlreadyExists, errorParticipantNotFound } from '../models/error.model.js';
import { FrontendEventService, LiveKitService, LoggerService, RoomService, TokenService } from './index.js';
import { MeetRoomHelper } from '../helpers/room.helper.js';
@injectable()
export class ParticipantService {
@ -64,26 +65,26 @@ export class ParticipantService {
return this.tokenService.generateParticipantToken(participantOptions, permissions.livekit, currentRoles, role);
}
async getParticipant(roomId: string, participantName: string): Promise<ParticipantInfo | null> {
this.logger.verbose(`Fetching participant '${participantName}'`);
return this.livekitService.getParticipant(roomId, participantName);
async getParticipant(roomId: string, participantIdentity: string): Promise<ParticipantInfo> {
this.logger.verbose(`Fetching participant '${participantIdentity}'`);
return this.livekitService.getParticipant(roomId, participantIdentity);
}
async participantExists(roomId: string, participantName: string): Promise<boolean> {
this.logger.verbose(`Checking if participant '${participantName}' exists in room '${roomId}'`);
async participantExists(roomId: string, participantIdentity: string): Promise<boolean> {
this.logger.verbose(`Checking if participant '${participantIdentity}' exists in room '${roomId}'`);
try {
const participant = await this.getParticipant(roomId, participantName);
return participant !== null;
await this.getParticipant(roomId, participantIdentity);
return true;
} catch (error) {
return false;
}
}
async deleteParticipant(roomId: string, participantName: string): Promise<void> {
this.logger.verbose(`Deleting participant '${participantName}' from room '${roomId}'`);
async deleteParticipant(roomId: string, participantIdentity: string): Promise<void> {
this.logger.verbose(`Deleting participant '${participantIdentity}' from room '${roomId}'`);
return this.livekitService.deleteParticipant(participantName, roomId);
return this.livekitService.deleteParticipant(roomId, participantIdentity);
}
getParticipantPermissions(roomId: string, role: ParticipantRole, addJoinPermission = true): ParticipantPermissions {
@ -97,36 +98,42 @@ export class ParticipantService {
}
}
async updateParticipantRole(roomId: string, participantName: string, newRole: ParticipantRole): Promise<void> {
async updateParticipantRole(roomId: string, participantIdentity: string, newRole: ParticipantRole): Promise<void> {
try {
const meetRoom = await this.roomService.getMeetRoom(roomId);
const participant = await this.getParticipant(roomId, participantName);
const participant = await this.getParticipant(roomId, participantIdentity);
const metadata: MeetTokenMetadata = this.parseMetadata(participant.metadata);
const metadata: MeetTokenMetadata = this.parseMetadata(participant!.metadata);
// Update selected role and roles array
metadata.selectedRole = newRole;
const currentRoles = metadata.roles;
if (!metadata || typeof metadata !== 'object') {
throw new Error(`Invalid metadata for participant ${participantName}`);
if (!currentRoles.some((r) => r.role === newRole)) {
const { openvidu } = this.getParticipantPermissions(roomId, newRole);
currentRoles.push({ role: newRole, permissions: openvidu });
}
// TODO: Should we update the roles array as well?
metadata.selectedRole = newRole;
await this.livekitService.updateParticipantMetadata(roomId, participantName, JSON.stringify(metadata));
await this.livekitService.updateParticipantMetadata(roomId, participantIdentity, JSON.stringify(metadata));
const { speakerSecret, moderatorSecret } = MeetRoomHelper.extractSecretsFromRoom(meetRoom);
const secret = newRole === ParticipantRole.MODERATOR ? moderatorSecret : speakerSecret;
await this.frontendEventService.sendParticipantRoleUpdatedSignal(roomId, participantName, newRole, secret);
await this.frontendEventService.sendParticipantRoleUpdatedSignal(
roomId,
participantIdentity,
newRole,
secret
);
} catch (error) {
this.logger.error('Error changing participant role:', error);
this.logger.error('Error updating participant role:', error);
throw error;
}
}
protected parseMetadata(metadata: string): MeetTokenMetadata {
parseMetadata(metadata: string): MeetTokenMetadata {
try {
return JSON.parse(metadata);
const parsedMetadata = JSON.parse(metadata);
return validateMeetTokenMetadata(parsedMetadata);
} catch (error) {
this.logger.error('Failed to parse participant metadata:', error);
throw new Error('Invalid participant metadata format');

View File

@ -11,7 +11,7 @@ import {
} from '../../../helpers/request-helpers.js';
import { RoomData, setupSingleRoom } from '../../../helpers/test-scenarios.js';
const participantName = 'TEST_PARTICIPANT';
const participantIdentity = 'TEST_PARTICIPANT';
describe('Meetings API Tests', () => {
let livekitService: LiveKitService;
@ -34,17 +34,17 @@ describe('Meetings API Tests', () => {
it('should remove participant from LiveKit room', async () => {
// Check if participant exists before deletion
const participant = await livekitService.getParticipant(roomData.room.roomId, participantName);
const participant = await livekitService.getParticipant(roomData.room.roomId, participantIdentity);
expect(participant).toBeDefined();
expect(participant.identity).toBe(participantName);
expect(participant.identity).toBe(participantIdentity);
// Delete the participant
const response = await deleteParticipant(roomData.room.roomId, participantName, roomData.moderatorCookie);
const response = await deleteParticipant(roomData.room.roomId, participantIdentity, roomData.moderatorCookie);
expect(response.status).toBe(200);
// Check if the participant has been removed from LiveKit
try {
await livekitService.getParticipant(roomData.room.roomId, participantName);
await livekitService.getParticipant(roomData.room.roomId, participantIdentity);
} catch (error) {
expect((error as OpenViduMeetError).statusCode).toBe(404);
}
@ -65,7 +65,7 @@ describe('Meetings API Tests', () => {
let response = await deleteRoom(roomData.room.roomId, { force: true });
expect(response.status).toBe(204);
response = await deleteParticipant(roomData.room.roomId, participantName, roomData.moderatorCookie);
response = await deleteParticipant(roomData.room.roomId, participantIdentity, roomData.moderatorCookie);
expect(response.status).toBe(404);
expect(response.body.error).toBe('Room Error');
});