172 lines
5.9 KiB
TypeScript
172 lines
5.9 KiB
TypeScript
import {
|
|
MeetTokenMetadata,
|
|
OpenViduMeetPermissions,
|
|
ParticipantOptions,
|
|
ParticipantPermissions,
|
|
ParticipantRole
|
|
} from '@typings-ce';
|
|
import { inject, injectable } from 'inversify';
|
|
import { ParticipantInfo } from 'livekit-server-sdk';
|
|
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 {
|
|
constructor(
|
|
@inject(LoggerService) protected logger: LoggerService,
|
|
@inject(RoomService) protected roomService: RoomService,
|
|
@inject(LiveKitService) protected livekitService: LiveKitService,
|
|
@inject(FrontendEventService) protected frontendEventService: FrontendEventService,
|
|
@inject(TokenService) protected tokenService: TokenService
|
|
) {}
|
|
|
|
async generateOrRefreshParticipantToken(
|
|
participantOptions: ParticipantOptions,
|
|
currentRoles: { role: ParticipantRole; permissions: OpenViduMeetPermissions }[],
|
|
refresh = false
|
|
): Promise<string> {
|
|
const { roomId, participantName, secret } = participantOptions;
|
|
|
|
if (participantName) {
|
|
// Check if participant with same participantName exists in the room
|
|
const participantExists = await this.participantExists(roomId, participantName);
|
|
|
|
if (!refresh && participantExists) {
|
|
this.logger.verbose(`Participant '${participantName}' already exists in room '${roomId}'`);
|
|
throw errorParticipantAlreadyExists(participantName, roomId);
|
|
}
|
|
|
|
if (refresh && !participantExists) {
|
|
this.logger.verbose(`Participant '${participantName}' does not exist in room '${roomId}'`);
|
|
throw errorParticipantNotFound(participantName, roomId);
|
|
}
|
|
}
|
|
|
|
const role = await this.roomService.getRoomRoleBySecret(roomId, secret);
|
|
const token = await this.generateParticipantToken(participantOptions, role, currentRoles);
|
|
this.logger.verbose(`Participant token generated for room '${roomId}'`);
|
|
return token;
|
|
}
|
|
|
|
protected async generateParticipantToken(
|
|
participantOptions: ParticipantOptions,
|
|
role: ParticipantRole,
|
|
currentRoles: { role: ParticipantRole; permissions: OpenViduMeetPermissions }[]
|
|
): Promise<string> {
|
|
const { roomId, participantName } = participantOptions;
|
|
const permissions = this.getParticipantPermissions(roomId, role, !!participantName);
|
|
|
|
if (!currentRoles.some((r) => r.role === role)) {
|
|
currentRoles.push({ role, permissions: permissions.openvidu });
|
|
}
|
|
|
|
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 participantExists(roomId: string, participantName: string): Promise<boolean> {
|
|
this.logger.verbose(`Checking if participant '${participantName}' exists in room '${roomId}'`);
|
|
|
|
try {
|
|
const participant = await this.getParticipant(roomId, participantName);
|
|
return participant !== null;
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async deleteParticipant(roomId: string, participantName: string): Promise<void> {
|
|
this.logger.verbose(`Deleting participant '${participantName}' from room '${roomId}'`);
|
|
|
|
return this.livekitService.deleteParticipant(participantName, roomId);
|
|
}
|
|
|
|
getParticipantPermissions(roomId: string, role: ParticipantRole, addJoinPermission = true): ParticipantPermissions {
|
|
switch (role) {
|
|
case ParticipantRole.MODERATOR:
|
|
return this.generateModeratorPermissions(roomId, addJoinPermission);
|
|
case ParticipantRole.SPEAKER:
|
|
return this.generateSpeakerPermissions(roomId, addJoinPermission);
|
|
default:
|
|
throw new Error(`Role ${role} not supported`);
|
|
}
|
|
}
|
|
|
|
async updateParticipantRole(roomId: string, participantName: string, newRole: ParticipantRole): Promise<void> {
|
|
try {
|
|
const meetRoom = await this.roomService.getMeetRoom(roomId);
|
|
|
|
const participant = await this.getParticipant(roomId, participantName);
|
|
|
|
const metadata: MeetTokenMetadata = this.parseMetadata(participant!.metadata);
|
|
|
|
if (!metadata || typeof metadata !== 'object') {
|
|
throw new Error(`Invalid metadata for participant ${participantName}`);
|
|
}
|
|
|
|
// TODO: Should we update the roles array as well?
|
|
metadata.selectedRole = newRole;
|
|
|
|
await this.livekitService.updateParticipantMetadata(roomId, participantName, JSON.stringify(metadata));
|
|
|
|
const { speakerSecret, moderatorSecret } = MeetRoomHelper.extractSecretsFromRoom(meetRoom);
|
|
|
|
const secret = newRole === ParticipantRole.MODERATOR ? moderatorSecret : speakerSecret;
|
|
await this.frontendEventService.sendParticipantRoleUpdatedSignal(roomId, participantName, newRole, secret);
|
|
} catch (error) {
|
|
this.logger.error('Error changing participant role:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
protected parseMetadata(metadata: string): MeetTokenMetadata {
|
|
try {
|
|
return JSON.parse(metadata);
|
|
} catch (error) {
|
|
this.logger.error('Failed to parse participant metadata:', error);
|
|
throw new Error('Invalid participant metadata format');
|
|
}
|
|
}
|
|
|
|
protected generateModeratorPermissions(roomId: string, addJoinPermission = true): ParticipantPermissions {
|
|
return {
|
|
livekit: {
|
|
roomJoin: addJoinPermission,
|
|
room: roomId,
|
|
canPublish: true,
|
|
canSubscribe: true,
|
|
canPublishData: true,
|
|
canUpdateOwnMetadata: true
|
|
},
|
|
openvidu: {
|
|
canRecord: true,
|
|
canChat: true,
|
|
canChangeVirtualBackground: true
|
|
}
|
|
};
|
|
}
|
|
|
|
protected generateSpeakerPermissions(roomId: string, addJoinPermission = true): ParticipantPermissions {
|
|
return {
|
|
livekit: {
|
|
roomJoin: addJoinPermission,
|
|
room: roomId,
|
|
canPublish: true,
|
|
canSubscribe: true,
|
|
canPublishData: true,
|
|
canUpdateOwnMetadata: true
|
|
},
|
|
openvidu: {
|
|
canRecord: false,
|
|
canChat: true,
|
|
canChangeVirtualBackground: true
|
|
}
|
|
};
|
|
}
|
|
}
|