From e2b9fcd5320d53d35c14164bb8368ecfa5fc155b Mon Sep 17 00:00:00 2001 From: Carlos Santos <4a.santos@gmail.com> Date: Wed, 7 May 2025 14:25:22 +0200 Subject: [PATCH] backend: Enhance webhook events creator checking --- backend/src/helpers/room.helper.ts | 17 +++++++ .../src/services/livekit-webhook.service.ts | 48 ++++++++++++------- backend/src/services/livekit.service.ts | 20 ++++++++ backend/src/services/room.service.ts | 20 ++++++++ 4 files changed, 89 insertions(+), 16 deletions(-) diff --git a/backend/src/helpers/room.helper.ts b/backend/src/helpers/room.helper.ts index 4e87390..45e3f2e 100644 --- a/backend/src/helpers/room.helper.ts +++ b/backend/src/helpers/room.helper.ts @@ -1,4 +1,5 @@ import { MeetRoom, MeetRoomOptions } from '@typings-ce'; +import { MEET_NAME_ID } from '../environment.js'; export class MeetRoomHelper { private constructor() { @@ -40,4 +41,20 @@ export class MeetRoomHelper { const moderatorSecret = moderatorUrl.searchParams.get('secret') || ''; return { publisherSecret, moderatorSecret }; } + + /** + * Safely parses JSON metadata and checks if createdBy matches MEET_NAME_ID. + * @returns true if metadata indicates OpenVidu Meet as creator, false otherwise + */ + static checkIfMeetingBelogsToOpenViduMeet(metadata?: string): boolean { + if (!metadata) return false; + + try { + const parsed = JSON.parse(metadata); + const isOurs = parsed?.createdBy === MEET_NAME_ID; + return isOurs; + } catch (err: unknown) { + return false; + } + } } diff --git a/backend/src/services/livekit-webhook.service.ts b/backend/src/services/livekit-webhook.service.ts index 5d903d1..6af9fb0 100644 --- a/backend/src/services/livekit-webhook.service.ts +++ b/backend/src/services/livekit-webhook.service.ts @@ -2,7 +2,7 @@ import { MeetRecordingInfo, MeetRecordingStatus } from '@typings-ce'; import { inject, injectable } from 'inversify'; import { EgressInfo, ParticipantInfo, Room, WebhookEvent, WebhookReceiver } from 'livekit-server-sdk'; import { LIVEKIT_API_KEY, LIVEKIT_API_SECRET } from '../environment.js'; -import { RecordingHelper } from '../helpers/index.js'; +import { MeetRoomHelper, RecordingHelper } from '../helpers/index.js'; import { SystemEventType } from '../models/system-event.model.js'; import { LiveKitService, @@ -49,33 +49,49 @@ export class LivekitWebhookService { } /** - * Checks if the webhook event belongs to OpenVidu Meet by verifying if the room exist in OpenVidu Meet. + * Checks if the webhook event belongs to OpenVidu Meet. + * Uses a systematic approach to verify through different sources. + * !KNOWN ISSUE: Room metadata may be empty when track_publish and track_unpublish events are received. */ async webhookEventBelongsToOpenViduMeet(webhookEvent: WebhookEvent): Promise { // Extract relevant properties from the webhook event const { room, egressInfo, ingressInfo } = webhookEvent; + this.logger.debug(`[webhookEventBelongsToOpenViduMeet] Checking webhook event: ${webhookEvent.event}`); - // Determine the room name from room object or egress/ingress info - const roomName = room?.name ?? egressInfo?.roomName ?? ingressInfo?.roomName; + // Case 1: Check using room object from the event + if (room) { + this.logger.debug(`[webhookEventBelongsToOpenViduMeet] Checking room metadata for room: ${room.name}`); - if (!roomName) { - this.logger.debug('Room name not found in webhook event'); - return false; - } + if (!room.metadata) { + this.logger.debug(`[webhookEventBelongsToOpenViduMeet] Room metadata is empty for room: ${room.name}`); - try { - const meetRoom = await this.roomService.getMeetRoom(roomName); + const updatedMetadata = await this.livekitService.getRoomMetadata(room.name); - if (!meetRoom) { - this.logger.debug(`Room ${roomName} not found in OpenVidu Meet.`); - return false; + if (MeetRoomHelper.checkIfMeetingBelogsToOpenViduMeet(updatedMetadata)) return true; + + return await this.roomService.meetRoomExists(room.name); } - return true; - } catch (error) { - this.logger.error(`Error checking if room ${roomName} was created by OpenVidu Meet:`, error); + this.logger.debug(`[webhookEventBelongsToOpenViduMeet] Room metadata found for room: ${room.name}`); + return ( + MeetRoomHelper.checkIfMeetingBelogsToOpenViduMeet(room.metadata) || + (await this.roomService.meetRoomExists(room.name)) + ); + } + + // Case 2: No room in event - use roomName from egress/ingress info + const roomName = egressInfo?.roomName ?? ingressInfo?.roomName; + + if (!roomName) { + this.logger.debug('[webhookEventBelongsToOpenViduMeet] Room name not found in webhook event'); return false; } + + const updatedMetadata = await this.livekitService.getRoomMetadata(roomName); + + if (MeetRoomHelper.checkIfMeetingBelogsToOpenViduMeet(updatedMetadata)) return true; + + return await this.roomService.meetRoomExists(roomName); } /** diff --git a/backend/src/services/livekit.service.ts b/backend/src/services/livekit.service.ts index 88e6ca4..9dda4e6 100644 --- a/backend/src/services/livekit.service.ts +++ b/backend/src/services/livekit.service.ts @@ -100,6 +100,26 @@ export class LiveKitService { return rooms[0]; } + /** + * Retrieves the metadata associated with a LiveKit room. + * + * @param roomName - The name of the room to get metadata from + * @returns The room's metadata as a string if it exists, or undefined if the room has no metadata or an error occurs + */ + async getRoomMetadata(roomName: string): Promise { + try { + const room = await this.getRoom(roomName); + + if (room.metadata) { + return room.metadata; + } + + return undefined; + } catch (error) { + return undefined; + } + } + async listRooms(): Promise { try { return await this.roomClient.listRooms(); diff --git a/backend/src/services/room.service.ts b/backend/src/services/room.service.ts index 4dc5a44..734add7 100644 --- a/backend/src/services/room.service.ts +++ b/backend/src/services/room.service.ts @@ -24,6 +24,7 @@ import { TokenService } from './index.js'; import ms from 'ms'; +import { MEET_NAME_ID } from '../environment.js'; /** * Service for managing OpenVidu Meet rooms. @@ -99,6 +100,7 @@ export class RoomService { const livekitRoomOptions: CreateOptions = { name: roomId, metadata: JSON.stringify({ + createdBy: MEET_NAME_ID, roomOptions: MeetRoomHelper.toOpenViduOptions(meetRoom) }), emptyTimeout: MEETING_EMPTY_TIMEOUT ? ms(MEETING_EMPTY_TIMEOUT) / 1000 : undefined, @@ -125,6 +127,24 @@ export class RoomService { return await this.storageService.saveMeetRoom(room); } + /** + * Checks if a meeting room with the specified name exists + * + * @param roomName - The name of the meeting room to check + * @returns A Promise that resolves to true if the room exists, false otherwise + */ + async meetRoomExists(roomName: string): Promise { + try { + const meetRoom = await this.getMeetRoom(roomName); + + if (meetRoom) return true; + + return false; + } catch (err: unknown) { + return false; + } + } + /** * Retrieves a list of rooms. * @returns A Promise that resolves to an array of {@link MeetRoom} objects.