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.
This commit is contained in:
parent
1223e3d53b
commit
f70bd04497
@ -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}'`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -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)
|
||||
});
|
||||
|
||||
@ -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<void> {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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<void> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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<void> {
|
||||
async changeParticipantRole(
|
||||
roomId: string,
|
||||
participantIdentity: string,
|
||||
action: MeetParticipantModerationAction
|
||||
): Promise<void> {
|
||||
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}'`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user