backend: refactor code to generate participantIdentity based on name and unique ID
This commit is contained in:
parent
2478845fb6
commit
13c88d201c
@ -7,7 +7,8 @@ import { nonEmptySanitizedRoomId } from './room-validator.middleware.js';
|
|||||||
const ParticipantTokenRequestSchema: z.ZodType<ParticipantOptions> = z.object({
|
const ParticipantTokenRequestSchema: z.ZodType<ParticipantOptions> = z.object({
|
||||||
roomId: nonEmptySanitizedRoomId('roomId'),
|
roomId: nonEmptySanitizedRoomId('roomId'),
|
||||||
secret: z.string().nonempty('Secret is required'),
|
secret: z.string().nonempty('Secret is required'),
|
||||||
participantName: z.string().optional()
|
participantName: z.string().optional(),
|
||||||
|
participantIdentity: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
const UpdateParticipantRequestSchema = z.object({
|
const UpdateParticipantRequestSchema = z.object({
|
||||||
|
|||||||
@ -242,6 +242,10 @@ export const errorInvalidParticipantRole = (): OpenViduMeetError => {
|
|||||||
return new OpenViduMeetError('Participant', 'No valid participant role provided', 400);
|
return new OpenViduMeetError('Participant', 'No valid participant role provided', 400);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const errorParticipantIdentityNotProvided = (): OpenViduMeetError => {
|
||||||
|
return new OpenViduMeetError('Participant', 'No participant identity provided', 400);
|
||||||
|
};
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
|
|
||||||
export const handleError = (res: Response, error: OpenViduMeetError | unknown, operationDescription: string) => {
|
export const handleError = (res: Response, error: OpenViduMeetError | unknown, operationDescription: string) => {
|
||||||
|
|||||||
@ -182,6 +182,34 @@ export class LiveKitService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async participantExists(
|
||||||
|
roomName: string,
|
||||||
|
participantNameOrIdentity: string,
|
||||||
|
participantField: 'name' | 'identity' = 'identity'
|
||||||
|
): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const participants: ParticipantInfo[] = await this.listRoomParticipants(roomName);
|
||||||
|
return participants.some((participant) => {
|
||||||
|
let fieldValue = participant[participantField];
|
||||||
|
|
||||||
|
// If the field is empty or undefined, use identity as a fallback
|
||||||
|
if (!fieldValue && participantField === 'name') {
|
||||||
|
fieldValue = participant.identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fieldValue === participantNameOrIdentity;
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
this.logger.error(error);
|
||||||
|
|
||||||
|
if (error?.cause?.code === 'ECONNREFUSED') {
|
||||||
|
throw errorLivekitNotAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves information about a specific participant in a LiveKit room.
|
* Retrieves information about a specific participant in a LiveKit room.
|
||||||
*
|
*
|
||||||
@ -383,19 +411,4 @@ export class LiveKitService {
|
|||||||
// TODO: Remove deprecated warning by using ParticipantInfo_Kind: participant.kind === ParticipantInfo_Kind.EGRESS;
|
// TODO: Remove deprecated warning by using ParticipantInfo_Kind: participant.kind === ParticipantInfo_Kind.EGRESS;
|
||||||
return participant.identity.startsWith('EG_') && participant.permission?.recorder === true;
|
return participant.identity.startsWith('EG_') && participant.permission?.recorder === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async participantExists(roomName: string, participantIdentity: string): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
const participants: ParticipantInfo[] = await this.listRoomParticipants(roomName);
|
|
||||||
return participants.some((participant) => participant.identity === participantIdentity);
|
|
||||||
} catch (error: any) {
|
|
||||||
this.logger.error(error);
|
|
||||||
|
|
||||||
if (error?.cause?.code === 'ECONNREFUSED') {
|
|
||||||
throw errorLivekitNotAvailable();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,11 @@ import { inject, injectable } from 'inversify';
|
|||||||
import { ParticipantInfo } from 'livekit-server-sdk';
|
import { ParticipantInfo } from 'livekit-server-sdk';
|
||||||
import { MeetRoomHelper } from '../helpers/room.helper.js';
|
import { MeetRoomHelper } from '../helpers/room.helper.js';
|
||||||
import { validateMeetTokenMetadata } from '../middlewares/index.js';
|
import { validateMeetTokenMetadata } from '../middlewares/index.js';
|
||||||
import { errorParticipantAlreadyExists, errorParticipantNotFound } from '../models/error.model.js';
|
import {
|
||||||
|
errorParticipantAlreadyExists,
|
||||||
|
errorParticipantIdentityNotProvided,
|
||||||
|
errorParticipantNotFound
|
||||||
|
} from '../models/error.model.js';
|
||||||
import { FrontendEventService, LiveKitService, LoggerService, RoomService, TokenService } from './index.js';
|
import { FrontendEventService, LiveKitService, LoggerService, RoomService, TokenService } from './index.js';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
@ -27,20 +31,29 @@ export class ParticipantService {
|
|||||||
currentRoles: { role: ParticipantRole; permissions: OpenViduMeetPermissions }[],
|
currentRoles: { role: ParticipantRole; permissions: OpenViduMeetPermissions }[],
|
||||||
refresh = false
|
refresh = false
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const { roomId, participantName, secret } = participantOptions;
|
const { roomId, secret, participantName, participantIdentity } = participantOptions;
|
||||||
|
|
||||||
if (participantName) {
|
if (participantName) {
|
||||||
// Check if participant with same participantName exists in the room
|
if (!refresh) {
|
||||||
const participantExists = await this.participantExists(roomId, participantName);
|
// Check if participant with same participantName exists in the room
|
||||||
|
const participantExists = await this.participantExists(roomId, participantName, 'name');
|
||||||
|
|
||||||
if (!refresh && participantExists) {
|
if (participantExists) {
|
||||||
this.logger.verbose(`Participant '${participantName}' already exists in room '${roomId}'`);
|
this.logger.verbose(`Participant '${participantName}' already exists in room '${roomId}'`);
|
||||||
throw errorParticipantAlreadyExists(participantName, roomId);
|
throw errorParticipantAlreadyExists(participantName, roomId);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (!participantIdentity) {
|
||||||
|
throw errorParticipantIdentityNotProvided();
|
||||||
|
}
|
||||||
|
|
||||||
if (refresh && !participantExists) {
|
// Check if participant with same participantIdentity exists in the room
|
||||||
this.logger.verbose(`Participant '${participantName}' does not exist in room '${roomId}'`);
|
const participantExists = await this.participantExists(roomId, participantIdentity, 'identity');
|
||||||
throw errorParticipantNotFound(participantName, roomId);
|
|
||||||
|
if (!participantExists) {
|
||||||
|
this.logger.verbose(`Participant '${participantName}' does not exist in room '${roomId}'`);
|
||||||
|
throw errorParticipantNotFound(participantName, roomId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,15 +83,13 @@ export class ParticipantService {
|
|||||||
return this.livekitService.getParticipant(roomId, participantIdentity);
|
return this.livekitService.getParticipant(roomId, participantIdentity);
|
||||||
}
|
}
|
||||||
|
|
||||||
async participantExists(roomId: string, participantIdentity: string): Promise<boolean> {
|
async participantExists(
|
||||||
this.logger.verbose(`Checking if participant '${participantIdentity}' exists in room '${roomId}'`);
|
roomId: string,
|
||||||
|
participantNameOrIdentity: string,
|
||||||
try {
|
participantField: 'name' | 'identity' = 'identity'
|
||||||
await this.getParticipant(roomId, participantIdentity);
|
): Promise<boolean> {
|
||||||
return true;
|
this.logger.verbose(`Checking if participant '${participantNameOrIdentity}' exists in room '${roomId}'`);
|
||||||
} catch (error) {
|
return this.livekitService.participantExists(roomId, participantNameOrIdentity, participantField);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteParticipant(roomId: string, participantIdentity: string): Promise<void> {
|
async deleteParticipant(roomId: string, participantIdentity: string): Promise<void> {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { AccessToken, AccessTokenOptions, ClaimGrants, TokenVerifier, VideoGrant
|
|||||||
import INTERNAL_CONFIG from '../config/internal-config.js';
|
import INTERNAL_CONFIG from '../config/internal-config.js';
|
||||||
import { LIVEKIT_API_KEY, LIVEKIT_API_SECRET, LIVEKIT_URL } from '../environment.js';
|
import { LIVEKIT_API_KEY, LIVEKIT_API_SECRET, LIVEKIT_URL } from '../environment.js';
|
||||||
import { LoggerService } from './index.js';
|
import { LoggerService } from './index.js';
|
||||||
|
import { uid } from 'uid';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class TokenService {
|
export class TokenService {
|
||||||
@ -47,7 +48,17 @@ export class TokenService {
|
|||||||
selectedRole: ParticipantRole
|
selectedRole: ParticipantRole
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const { roomId, participantName } = participantOptions;
|
const { roomId, participantName } = participantOptions;
|
||||||
this.logger.info(`Generating token for room '${roomId}'`);
|
this.logger.info(
|
||||||
|
`Generating token for room '${roomId}'` + (participantName ? ` and participant '${participantName}'` : '')
|
||||||
|
);
|
||||||
|
|
||||||
|
let { participantIdentity } = participantOptions;
|
||||||
|
|
||||||
|
if (participantName && !participantIdentity) {
|
||||||
|
// Generate participant identity based on name and unique ID
|
||||||
|
const identityPrefix = participantName.replace(/\s+/g, ''); // Remove all spaces
|
||||||
|
participantIdentity = `${identityPrefix}-${uid(5)}`;
|
||||||
|
}
|
||||||
|
|
||||||
const metadata: MeetTokenMetadata = {
|
const metadata: MeetTokenMetadata = {
|
||||||
livekitUrl: LIVEKIT_URL,
|
livekitUrl: LIVEKIT_URL,
|
||||||
@ -55,7 +66,7 @@ export class TokenService {
|
|||||||
selectedRole
|
selectedRole
|
||||||
};
|
};
|
||||||
const tokenOptions: AccessTokenOptions = {
|
const tokenOptions: AccessTokenOptions = {
|
||||||
identity: participantName,
|
identity: participantIdentity,
|
||||||
name: participantName,
|
name: participantName,
|
||||||
ttl: INTERNAL_CONFIG.PARTICIPANT_TOKEN_EXPIRATION,
|
ttl: INTERNAL_CONFIG.PARTICIPANT_TOKEN_EXPIRATION,
|
||||||
metadata: JSON.stringify(metadata)
|
metadata: JSON.stringify(metadata)
|
||||||
|
|||||||
@ -17,6 +17,10 @@ export interface ParticipantOptions {
|
|||||||
* The name of the participant.
|
* The name of the participant.
|
||||||
*/
|
*/
|
||||||
participantName?: string;
|
participantName?: string;
|
||||||
|
/**
|
||||||
|
* The identity of the participant.
|
||||||
|
*/
|
||||||
|
participantIdentity?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user