diff --git a/meet-ce/backend/src/controllers/meeting.controller.ts b/meet-ce/backend/src/controllers/meeting.controller.ts index 3c200483..930e0e86 100644 --- a/meet-ce/backend/src/controllers/meeting.controller.ts +++ b/meet-ce/backend/src/controllers/meeting.controller.ts @@ -1,3 +1,4 @@ +import { MeetParticipantModerationAction } from '@openvidu-meet/typings'; import { Request, Response } from 'express'; import { container } from '../config/dependency-injector.config.js'; import { handleError } from '../models/error.model.js'; @@ -34,14 +35,22 @@ export const updateParticipantRole = async (req: Request, res: Response) => { const logger = container.get(LoggerService); const roomMemberService = container.get(RoomMemberService); const { roomId, participantIdentity } = req.params; - const { role } = req.body; + const { action } = req.body as { action: MeetParticipantModerationAction }; try { - logger.verbose(`Changing role of participant '${participantIdentity}' in room '${roomId}' to '${role}'`); - await roomMemberService.updateParticipantRole(roomId, participantIdentity, role); - res.status(200).json({ message: `Participant '${participantIdentity}' role updated to '${role}'` }); + logger.verbose( + `Applying moderation action '${action}' for participant '${participantIdentity}' in room '${roomId}'` + ); + await roomMemberService.updateParticipantRole(roomId, participantIdentity, action); + res.status(200).json({ + message: `Moderation action '${action}' applied to participant '${participantIdentity}'` + }); } catch (error) { - handleError(res, error, `changing role for participant '${participantIdentity}' in room '${roomId}'`); + handleError( + res, + error, + `applying moderation action for participant '${participantIdentity}' in room '${roomId}'` + ); } }; diff --git a/meet-ce/backend/src/models/zod-schemas/meeting.schema.ts b/meet-ce/backend/src/models/zod-schemas/meeting.schema.ts index 0d724dfa..e4d6cd18 100644 --- a/meet-ce/backend/src/models/zod-schemas/meeting.schema.ts +++ b/meet-ce/backend/src/models/zod-schemas/meeting.schema.ts @@ -1,6 +1,6 @@ -import { MeetRoomMemberRole } from '@openvidu-meet/typings'; +import { MeetParticipantModerationAction } from '@openvidu-meet/typings'; import { z } from 'zod'; export const UpdateParticipantRoleReqSchema = z.object({ - role: z.nativeEnum(MeetRoomMemberRole) + action: z.nativeEnum(MeetParticipantModerationAction) }); diff --git a/meet-ce/backend/src/services/frontend-event.service.ts b/meet-ce/backend/src/services/frontend-event.service.ts index bc3d3702..55603a50 100644 --- a/meet-ce/backend/src/services/frontend-event.service.ts +++ b/meet-ce/backend/src/services/frontend-event.service.ts @@ -3,7 +3,7 @@ import { MeetRecordingInfo, MeetRoom, MeetRoomConfigUpdatedPayload, - MeetRoomMemberRole, + MeetRoomMemberUIBadge, MeetSignalPayload, MeetSignalType } from '@openvidu-meet/typings'; @@ -99,41 +99,24 @@ export class FrontendEventService { async sendParticipantRoleUpdatedSignal( roomId: string, participantIdentity: string, - newRole: MeetRoomMemberRole, - secret: string + newBadge: MeetRoomMemberUIBadge ): Promise { this.logger.debug( `Sending participant role updated signal for participant '${participantIdentity}' in room '${roomId}'` ); - const basePayload: MeetParticipantRoleUpdatedPayload = { + const signalPayload: MeetParticipantRoleUpdatedPayload = { roomId, participantIdentity, - newRole, + newBadge, timestamp: Date.now() }; - - const baseOptions: SendDataOptions = { + const singalOptions: SendDataOptions = { topic: MeetSignalType.MEET_PARTICIPANT_ROLE_UPDATED }; - // Send signal with secret to the participant whose role has been updated - await this.sendSignal( - roomId, - { ...basePayload, secret }, - { ...baseOptions, destinationIdentities: [participantIdentity] } - ); - - // Broadcast the role update to all other participants without the secret - const participants = await this.livekitService.listRoomParticipants(roomId); - const otherParticipantIdentities = participants - .filter((p) => p.identity !== participantIdentity) - .map((p) => p.identity); - - await this.sendSignal(roomId, basePayload, { - ...baseOptions, - destinationIdentities: otherParticipantIdentities - }); + // Broadcast the role update to all participants in the meeting + await this.sendSignal(roomId, signalPayload, singalOptions); } /** diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-event-handler.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-event-handler.service.ts index ae63e3af..03801d4f 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-event-handler.service.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-event-handler.service.ts @@ -2,7 +2,7 @@ import { Injectable, inject } from '@angular/core'; import { LeftEventReason, MeetParticipantRoleUpdatedPayload, - MeetRoomMemberRole, + MeetRoomMemberUIBadge, MeetSignalType, WebComponentEvent, WebComponentOutboundEventMessage @@ -20,9 +20,7 @@ import { } from 'openvidu-components-angular'; import { NavigationService } from '../../../shared/services/navigation.service'; import { NotificationService } from '../../../shared/services/notification.service'; -import { SessionStorageService } from '../../../shared/services/session-storage.service'; import { SoundService } from '../../../shared/services/sound.service'; -import { TokenStorageService } from '../../../shared/services/token-storage.service'; import { RecordingService } from '../../recordings/services/recording.service'; import { RoomMemberContextService } from '../../room-members/services/room-member-context.service'; import { RoomFeatureService } from '../../rooms/services/room-feature.service'; @@ -41,8 +39,6 @@ export class MeetingEventHandlerService { protected roomFeatureService = inject(RoomFeatureService); protected recordingService = inject(RecordingService); protected roomMemberContextService = inject(RoomMemberContextService); - protected sessionStorageService = inject(SessionStorageService); - protected tokenStorageService = inject(TokenStorageService); protected wcManagerService = inject(MeetingWebComponentManagerService); protected navigationService = inject(NavigationService); protected notificationService = inject(NotificationService); @@ -186,48 +182,55 @@ export class MeetingEventHandlerService { * Obtains all necessary data from MeetingContextService. */ private async handleParticipantRoleUpdated(event: MeetParticipantRoleUpdatedPayload): Promise { - const { participantIdentity, newRole, secret } = event; + const { participantIdentity, newBadge } = event; const roomId = this.meetingContext.roomId(); const local = this.meetingContext.localParticipant(); const participantName = this.roomMemberContextService.participantName(); + const isPromotedModerator = newBadge === MeetRoomMemberUIBadge.MODERATOR; // Check if the role update is for the local participant if (local && participantIdentity === local.identity) { - if (!secret || !roomId) return; - - // Update room secret in context (without updating session storage) - this.meetingContext.setRoomSecret(secret); + if (!roomId) return; try { - // Refresh participant token with new role + // Refresh room member token to get updated permissions based on new role await this.roomMemberContextService.generateToken(roomId, { - secret, joinMeeting: true, participantName, - participantIdentity + participantIdentity, + useParticipantMetadata: true }); - // Update local participant role - local.meetRole = newRole; - this.showParticipantRoleUpdatedNotification(newRole); + local.meetBadge = newBadge; + + local.promotedModerator = isPromotedModerator; + this.roomMemberContextService.setPromotedModerator(isPromotedModerator); + this.showParticipantRoleUpdatedNotification(isPromotedModerator); } catch (error) { console.error('Error refreshing room member token:', error); } } else { - // Update remote participant role + // Update remote participant badge const remoteParticipants = this.meetingContext.remoteParticipants(); const participant = remoteParticipants.find((p) => p.identity === participantIdentity); if (participant) { - participant.meetRole = newRole; + participant.meetBadge = newBadge; + participant.promotedModerator = isPromotedModerator; } } } - private showParticipantRoleUpdatedNotification(newRole: MeetRoomMemberRole): void { - this.notificationService.showSnackbar(`You have been assigned the role of ${newRole.toUpperCase()}`); - newRole === MeetRoomMemberRole.MODERATOR - ? this.soundService.playParticipantRoleUpgradedSound() - : this.soundService.playParticipantRoleDowngradedSound(); + private showParticipantRoleUpdatedNotification(isPromotedModerator: boolean): void { + const message = isPromotedModerator + ? 'You have been promoted to moderator' + : 'Your moderator role has been removed'; + this.notificationService.showSnackbar(message); + + if (isPromotedModerator) { + this.soundService.playParticipantRoleUpgradedSound(); + } else { + this.soundService.playParticipantRoleDowngradedSound(); + } } /** diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting.service.ts index 5344b85a..37fe9b28 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting.service.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting.service.ts @@ -1,6 +1,6 @@ import { Clipboard } from '@angular/cdk/clipboard'; import { inject, Injectable } from '@angular/core'; -import { MeetRoom } from '@openvidu-meet/typings'; +import { MeetParticipantModerationAction, MeetRoom } from '@openvidu-meet/typings'; import { LoggerService } from 'openvidu-components-angular'; import { HttpService } from '../../../shared/services/http.service'; import { NotificationService } from '../../../shared/services/notification.service'; @@ -55,12 +55,16 @@ export class MeetingService { * * @param roomId - The unique identifier of the meeting room * @param participantIdentity - The identity of the participant whose role is to be changed - * @param newRole - The new role to be assigned to the participant + * @param action - Moderation action to apply */ - async changeParticipantRole(roomId: string, participantIdentity: string, newRole: string): Promise { + async changeParticipantRole( + roomId: string, + participantIdentity: string, + action: MeetParticipantModerationAction + ): Promise { const path = `${this.MEETINGS_API}/${roomId}/participants/${participantIdentity}/role`; - const body = { role: newRole }; + const body = { action }; await this.httpService.putRequest(path, body); - this.log.d(`Changed role of participant '${participantIdentity}' to '${newRole}' in room '${roomId}'`); + this.log.d(`Applied moderation action '${action}' to participant '${participantIdentity}' in room '${roomId}'`); } } diff --git a/meet-ce/typings/src/frontend-signal.ts b/meet-ce/typings/src/frontend-signal.ts index 534df9c2..5abcb8ba 100644 --- a/meet-ce/typings/src/frontend-signal.ts +++ b/meet-ce/typings/src/frontend-signal.ts @@ -1,5 +1,5 @@ import { MeetRoomConfig } from './database/room-config.js'; -import { MeetRoomMemberRole } from './database/room-member.entity.js'; +import { MeetRoomMemberUIBadge } from './response/room-member-response.js'; /** * Interface representing a signal emitted by OpenVidu Meet to notify clients about real-time updates in the meeting. @@ -26,17 +26,15 @@ export interface MeetRoomConfigUpdatedPayload { /** * Payload for MEET_PARTICIPANT_ROLE_UPDATED signal, - * containing information about the participant whose role was updated and the new role. + * containing information about the participant whose role was updated and the new badge. */ export interface MeetParticipantRoleUpdatedPayload { /** ID of the room where the participant's role was updated */ roomId: string; /** Identity of the participant whose role was updated */ participantIdentity: string; - /** New role assigned to the participant */ - newRole: MeetRoomMemberRole; - /** Optional secret for regenerating the participant's token if needed */ - secret?: string; + /** New badge assigned to the participant */ + newBadge: MeetRoomMemberUIBadge; /** Timestamp in milliseconds when the role update occurred */ timestamp: number; }