From f70bd04497b5cac277e83c3a59245fb228aeb936 Mon Sep 17 00:00:00 2001 From: juancarmore Date: Fri, 6 Mar 2026 11:41:15 +0100 Subject: [PATCH] Refactors participant role updates to use moderation actions Unifies backend and frontend handling of participant role changes by shifting from direct role assignment to moderation actions and UI badges. Simplifies signal payloads, removes token/secret mechanics, and clarifies notifications for moderator promotions and removals. Improves maintainability and aligns with updated moderation model. --- .../src/controllers/meeting.controller.ts | 19 +++++-- .../src/models/zod-schemas/meeting.schema.ts | 4 +- .../src/services/frontend-event.service.ts | 31 +++--------- .../services/meeting-event-handler.service.ts | 49 ++++++++++--------- .../meeting/services/meeting.service.ts | 14 ++++-- meet-ce/typings/src/frontend-signal.ts | 10 ++-- 6 files changed, 62 insertions(+), 65 deletions(-) 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; }