From e75b21fa49bca3ff182c7e2b13dd962ea6268f19 Mon Sep 17 00:00:00 2001 From: Carlos Santos <4a.santos@gmail.com> Date: Wed, 7 May 2025 14:25:39 +0200 Subject: [PATCH] backend: Refactor room finished and recording webhook handling for improved clarity and error logging --- .../src/services/livekit-webhook.service.ts | 118 +++++++++--------- 1 file changed, 62 insertions(+), 56 deletions(-) diff --git a/backend/src/services/livekit-webhook.service.ts b/backend/src/services/livekit-webhook.service.ts index 6af9fb0..8d6a6aa 100644 --- a/backend/src/services/livekit-webhook.service.ts +++ b/backend/src/services/livekit-webhook.service.ts @@ -170,26 +170,32 @@ export class LivekitWebhookService { * @param {Room} room - The room object that has finished. * @returns {Promise} A promise that resolves when the webhook has been sent. */ - async handleRoomFinished(room: Room): Promise { + async handleRoomFinished({ name: roomName }: Room): Promise { try { - const meetRoom = await this.roomService.getMeetRoom(room.name); + const meetRoom = await this.roomService.getMeetRoom(roomName); if (!meetRoom) { - this.logger.warn(`Room ${room.name} not found in OpenVidu Meet.`); + this.logger.warn(`Room ${roomName} not found in OpenVidu Meet.`); return; } - await Promise.all([ - this.recordingService.releaseRecordingLockIfNoEgress(room.name), + this.logger.info(`Processing room_finished event for room: ${roomName}`); + + const results = await Promise.allSettled([ + this.recordingService.releaseRecordingLockIfNoEgress(roomName), this.openViduWebhookService.sendMeetingEndedWebhook(meetRoom) ]); + results.forEach((result) => { + if (result.status === 'rejected') { + this.logger.error(`Error processing room_finished event: ${result.reason}`); + } + }); + if (meetRoom.markedForDeletion) { // If the room is marked for deletion, we need to delete it - this.logger.info( - `Deleting room ${room.name} after meeting finished because it was marked for deletion` - ); - this.roomService.bulkDeleteRooms([room.name], true); + this.logger.info(`Deleting room ${roomName} after meeting finished because it was marked for deletion`); + this.roomService.bulkDeleteRooms([roomName], true); } } catch (error) { this.logger.error(`Error handling room finished event: ${error}`); @@ -210,58 +216,58 @@ export class LivekitWebhookService { ): Promise { if (!RecordingHelper.isRecordingEgress(egressInfo)) return; - this.logger.debug(`Handling recording_${webhookAction} webhook.`); - - const recordingInfo: MeetRecordingInfo = RecordingHelper.toRecordingInfo(egressInfo); - const { roomId, recordingId, status } = recordingInfo; - const metadataPath = RecordingHelper.buildMetadataFilePath(recordingId); - - this.logger.debug(`Recording '${recordingId}' status: '${status}'`); - - const tasks: Promise[] = []; - - // Update recording metadata - tasks.push( - this.s3Service.saveObject(metadataPath, recordingInfo), - this.recordingService.sendRecordingSignalToOpenViduComponents(roomId, recordingInfo) - ); - - // Send webhook notification - switch (webhookAction) { - case 'started': - tasks.push( - this.storageService.archiveRoomMetadata(roomId), - this.openViduWebhookService.sendRecordingStartedWebhook(recordingInfo) - ); - break; - case 'updated': - tasks.push(this.openViduWebhookService.sendRecordingUpdatedWebhook(recordingInfo)); - - if (recordingInfo.status === MeetRecordingStatus.ACTIVE) { - // Send system event for active recording with the aim of cancelling the cleanup timer - tasks.push( - this.systemEventService.publishEvent( - SystemEventType.RECORDING_ACTIVE, - recordingInfo as unknown as Record - ) - ); - } - - break; - case 'ended': - tasks.push( - this.openViduWebhookService.sendRecordingEndedWebhook(recordingInfo), - this.recordingService.releaseRecordingLockIfNoEgress(roomId) - ); - break; - } + this.logger.debug(`Processing recording_${webhookAction} webhook for egress: ${egressInfo.egressId}.`); try { + const recordingInfo: MeetRecordingInfo = RecordingHelper.toRecordingInfo(egressInfo); + const { roomId, recordingId, status } = recordingInfo; + const metadataPath = RecordingHelper.buildMetadataFilePath(recordingId); + + this.logger.debug(`Recording '${recordingId}' in room '${roomId}' status: '${status}'`); + + // Common tasks for all webhook types + const commonTasks = [ + this.s3Service.saveObject(metadataPath, recordingInfo), + this.recordingService.sendRecordingSignalToOpenViduComponents(roomId, recordingInfo) + ]; + + const specificTasks: Promise[] = []; + + // Send webhook notification + switch (webhookAction) { + case 'started': + specificTasks.push( + this.storageService.archiveRoomMetadata(roomId), + this.openViduWebhookService.sendRecordingStartedWebhook(recordingInfo) + ); + break; + case 'updated': + specificTasks.push(this.openViduWebhookService.sendRecordingUpdatedWebhook(recordingInfo)); + + if (recordingInfo.status === MeetRecordingStatus.ACTIVE) { + // Send system event for active recording with the aim of cancelling the cleanup timer + specificTasks.push( + this.systemEventService.publishEvent( + SystemEventType.RECORDING_ACTIVE, + recordingInfo as unknown as Record + ) + ); + } + + break; + case 'ended': + specificTasks.push( + this.openViduWebhookService.sendRecordingEndedWebhook(recordingInfo), + this.recordingService.releaseRecordingLockIfNoEgress(roomId) + ); + break; + } + // Wait for all promises to resolve - await Promise.all(tasks); + await Promise.all([...commonTasks, ...specificTasks]); } catch (error) { this.logger.warn( - `Error processing recording ${webhookAction} webhook for egress ${egressInfo.egressId}: ${error}` + `Error processing recording_${webhookAction} webhook for egress ${egressInfo.egressId}: ${error}` ); } }