backend: introduce FrontendEventService for frontend communication and update dependencies
This commit is contained in:
parent
273ad8c577
commit
7361b71a7a
@ -23,7 +23,8 @@ import {
|
||||
DistributedEventService,
|
||||
TaskSchedulerService,
|
||||
TokenService,
|
||||
UserService
|
||||
UserService,
|
||||
FrontendEventService
|
||||
} from '../services/index.js';
|
||||
|
||||
export const container: Container = new Container();
|
||||
@ -57,6 +58,7 @@ export const registerDependencies = () => {
|
||||
container.bind(UserService).toSelf().inSingletonScope();
|
||||
container.bind(AuthService).toSelf().inSingletonScope();
|
||||
|
||||
container.bind(FrontendEventService).toSelf().inSingletonScope();
|
||||
container.bind(LiveKitService).toSelf().inSingletonScope();
|
||||
container.bind(RoomService).toSelf().inSingletonScope();
|
||||
container.bind(ParticipantService).toSelf().inSingletonScope();
|
||||
|
||||
103
backend/src/services/frontend-event.service.ts
Normal file
103
backend/src/services/frontend-event.service.ts
Normal file
@ -0,0 +1,103 @@
|
||||
import { MeetRoom, MeetRecordingInfo } from '@typings-ce';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { SendDataOptions } from 'livekit-server-sdk';
|
||||
import { OpenViduComponentsAdapterHelper } from '../helpers/index.js';
|
||||
import { LiveKitService, LoggerService } from './index.js';
|
||||
import { MeetSignalType } from '../typings/ce/event.model.js';
|
||||
|
||||
/**
|
||||
* Service responsible for all communication with the frontend
|
||||
* Centralizes all signals and events sent to the frontend
|
||||
*/
|
||||
@injectable()
|
||||
export class FrontendEventService {
|
||||
constructor(
|
||||
@inject(LoggerService) protected logger: LoggerService,
|
||||
@inject(LiveKitService) protected livekitService: LiveKitService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Sends a recording signal to OpenVidu Components within a specified room.
|
||||
*
|
||||
* This method constructs a signal with the appropriate topic and payload,
|
||||
* and sends it to the OpenVidu Components in the given room. The payload
|
||||
* is adapted to match the expected format for OpenVidu Components.
|
||||
*/
|
||||
async sendRecordingSignalToOpenViduComponents(roomId: string, recordingInfo: MeetRecordingInfo) {
|
||||
this.logger.debug(`Sending recording signal to OpenVidu Components for room '${roomId}'`);
|
||||
const { payload, options } = OpenViduComponentsAdapterHelper.generateRecordingSignal(recordingInfo);
|
||||
|
||||
try {
|
||||
await this.sendSignal(roomId, payload, options);
|
||||
} catch (error) {
|
||||
this.logger.debug(`Error sending recording signal to OpenVidu Components for room '${roomId}': ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a room status signal to OpenVidu Components.
|
||||
*
|
||||
* This method checks if recording is started in the room and sends a signal
|
||||
* with the room status to OpenVidu Components. If recording is not started,
|
||||
* it skips sending the signal.
|
||||
*/
|
||||
async sendRoomStatusSignalToOpenViduComponents(roomId: string, participantSid: string) {
|
||||
this.logger.debug(`Sending room status signal for room ${roomId} to OpenVidu Components.`);
|
||||
|
||||
try {
|
||||
// Check if recording is started in the room
|
||||
const activeEgressArray = await this.livekitService.getActiveEgress(roomId);
|
||||
const isRecordingStarted = activeEgressArray.length > 0;
|
||||
|
||||
// Skip if recording is not started
|
||||
if (!isRecordingStarted) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Construct the payload and signal options
|
||||
const { payload, options } = OpenViduComponentsAdapterHelper.generateRoomStatusSignal(
|
||||
isRecordingStarted,
|
||||
participantSid
|
||||
);
|
||||
|
||||
await this.sendSignal(roomId, payload, options);
|
||||
} catch (error) {
|
||||
this.logger.debug(`Error sending room status signal for room ${roomId}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a signal to notify participants in a room about updated room preferences.
|
||||
*/
|
||||
async sendRoomPreferencesUpdatedSignal(roomId: string, updatedRoom: MeetRoom): Promise<void> {
|
||||
this.logger.debug(`Sending room preferences updated signal for room ${roomId}`);
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
roomId,
|
||||
preferences: updatedRoom.preferences
|
||||
};
|
||||
|
||||
const options: SendDataOptions = {
|
||||
topic: MeetSignalType.MEET_ROOM_PREFERENCES_UPDATED
|
||||
};
|
||||
|
||||
await this.sendSignal(roomId, payload, options);
|
||||
} catch (error) {
|
||||
this.logger.error(`Error sending room preferences updated signal for room ${roomId}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic method to send signals to the frontend
|
||||
*/
|
||||
|
||||
protected async sendSignal(
|
||||
roomId: string,
|
||||
rawData: Record<string, unknown>,
|
||||
options: SendDataOptions
|
||||
): Promise<void> {
|
||||
this.logger.verbose(`Notifying participants in room ${roomId}: "${options.topic}".`);
|
||||
await this.livekitService.sendData(roomId, rawData, options);
|
||||
}
|
||||
}
|
||||
@ -11,6 +11,7 @@ export * from './user.service.js';
|
||||
export * from './auth.service.js';
|
||||
|
||||
export * from './livekit.service.js';
|
||||
export * from './frontend-event.service.js';
|
||||
export * from './room.service.js';
|
||||
export * from './participant.service.js';
|
||||
export * from './recording.service.js';
|
||||
|
||||
@ -14,6 +14,7 @@ import {
|
||||
RoomService,
|
||||
DistributedEventService
|
||||
} from './index.js';
|
||||
import { FrontendEventService } from './frontend-event.service.js';
|
||||
|
||||
@injectable()
|
||||
export class LivekitWebhookService {
|
||||
@ -26,6 +27,7 @@ export class LivekitWebhookService {
|
||||
@inject(OpenViduWebhookService) protected openViduWebhookService: OpenViduWebhookService,
|
||||
@inject(MutexService) protected mutexService: MutexService,
|
||||
@inject(DistributedEventService) protected distributedEventService: DistributedEventService,
|
||||
@inject(FrontendEventService) protected frontendEventService: FrontendEventService,
|
||||
@inject(LoggerService) protected logger: LoggerService
|
||||
) {
|
||||
this.webhookReceiver = new WebhookReceiver(LIVEKIT_API_KEY, LIVEKIT_API_SECRET);
|
||||
@ -139,7 +141,7 @@ export class LivekitWebhookService {
|
||||
if (this.livekitService.isEgressParticipant(participant)) return;
|
||||
|
||||
try {
|
||||
await this.roomService.sendRoomStatusSignalToOpenViduComponents(room.name, participant.sid);
|
||||
await this.frontendEventService.sendRoomStatusSignalToOpenViduComponents(room.name, participant.sid);
|
||||
} catch (error) {
|
||||
this.logger.error('Error sending room status signal on participant join:', error);
|
||||
}
|
||||
@ -229,7 +231,7 @@ export class LivekitWebhookService {
|
||||
// Common tasks for all webhook types
|
||||
const commonTasks = [
|
||||
this.storageService.saveRecordingMetadata(recordingInfo),
|
||||
this.recordingService.sendRecordingSignalToOpenViduComponents(roomId, recordingInfo)
|
||||
this.frontendEventService.sendRecordingSignalToOpenViduComponents(roomId, recordingInfo)
|
||||
];
|
||||
|
||||
const specificTasks: Promise<unknown>[] = [];
|
||||
|
||||
@ -6,8 +6,9 @@ import { Readable } from 'stream';
|
||||
import { uid } from 'uid';
|
||||
import INTERNAL_CONFIG from '../config/internal-config.js';
|
||||
import { MEET_S3_SUBBUCKET } from '../environment.js';
|
||||
import { MeetLock, OpenViduComponentsAdapterHelper, RecordingHelper, UtilsHelper } from '../helpers/index.js';
|
||||
import { MeetLock, RecordingHelper, UtilsHelper } from '../helpers/index.js';
|
||||
import {
|
||||
DistributedEventType,
|
||||
errorRecordingAlreadyStarted,
|
||||
errorRecordingAlreadyStopped,
|
||||
errorRecordingCannotBeStoppedWhileStarting,
|
||||
@ -19,10 +20,10 @@ import {
|
||||
isErrorRecordingAlreadyStopped,
|
||||
isErrorRecordingCannotBeStoppedWhileStarting,
|
||||
isErrorRecordingNotFound,
|
||||
OpenViduMeetError,
|
||||
DistributedEventType
|
||||
OpenViduMeetError
|
||||
} from '../models/index.js';
|
||||
import {
|
||||
DistributedEventService,
|
||||
IScheduledTask,
|
||||
LiveKitService,
|
||||
LoggerService,
|
||||
@ -30,7 +31,6 @@ import {
|
||||
MutexService,
|
||||
RedisLock,
|
||||
RoomService,
|
||||
DistributedEventService,
|
||||
TaskSchedulerService
|
||||
} from './index.js';
|
||||
|
||||
@ -254,7 +254,10 @@ export class RecordingService {
|
||||
|
||||
if (recRoomId !== roomId) {
|
||||
this.logger.warn(`Skipping recording '${recordingId}' as it does not belong to room '${roomId}'`);
|
||||
notDeletedRecordings.add({ recordingId, error: `Recording '${recordingId}' does not belong to room '${roomId}'` });
|
||||
notDeletedRecordings.add({
|
||||
recordingId,
|
||||
error: `Recording '${recordingId}' does not belong to room '${roomId}'`
|
||||
});
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@ -496,24 +499,6 @@ export class RecordingService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a recording signal to OpenVidu Components within a specified room.
|
||||
*
|
||||
* This method constructs a signal with the appropriate topic and payload,
|
||||
* and sends it to the OpenVidu Components in the given room. The payload
|
||||
* is adapted to match the expected format for OpenVidu Components.
|
||||
*/
|
||||
async sendRecordingSignalToOpenViduComponents(roomId: string, recordingInfo: MeetRecordingInfo) {
|
||||
this.logger.debug(`Sending recording signal to OpenVidu Components for room '${roomId}'`);
|
||||
const { payload, options } = OpenViduComponentsAdapterHelper.generateRecordingSignal(recordingInfo);
|
||||
|
||||
try {
|
||||
await this.roomService.sendSignal(roomId, payload, options);
|
||||
} catch (error) {
|
||||
this.logger.debug(`Error sending recording signal to OpenVidu Components for room '${roomId}': ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
protected generateCompositeOptionsFromRequest(layout = 'grid'): RoomCompositeOptions {
|
||||
return {
|
||||
layout: layout
|
||||
|
||||
@ -8,13 +8,13 @@ import {
|
||||
RecordingPermissions
|
||||
} from '@typings-ce';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { CreateOptions, Room, SendDataOptions } from 'livekit-server-sdk';
|
||||
import { CreateOptions, Room } from 'livekit-server-sdk';
|
||||
import ms from 'ms';
|
||||
import { uid as secureUid } from 'uid/secure';
|
||||
import { uid } from 'uid/single';
|
||||
import INTERNAL_CONFIG from '../config/internal-config.js';
|
||||
import { MEET_NAME_ID } from '../environment.js';
|
||||
import { MeetRoomHelper, OpenViduComponentsAdapterHelper, UtilsHelper } from '../helpers/index.js';
|
||||
import { MeetRoomHelper, UtilsHelper } from '../helpers/index.js';
|
||||
import {
|
||||
errorInvalidRoomSecret,
|
||||
errorRoomMetadataNotFound,
|
||||
@ -22,13 +22,14 @@ import {
|
||||
internalError
|
||||
} from '../models/error.model.js';
|
||||
import {
|
||||
DistributedEventService,
|
||||
IScheduledTask,
|
||||
LiveKitService,
|
||||
LoggerService,
|
||||
MeetStorageService,
|
||||
DistributedEventService,
|
||||
TaskSchedulerService,
|
||||
TokenService
|
||||
TokenService,
|
||||
FrontendEventService
|
||||
} from './index.js';
|
||||
|
||||
/**
|
||||
@ -44,6 +45,7 @@ export class RoomService {
|
||||
@inject(MeetStorageService) protected storageService: MeetStorageService,
|
||||
@inject(LiveKitService) protected livekitService: LiveKitService,
|
||||
@inject(DistributedEventService) protected distributedEventService: DistributedEventService,
|
||||
@inject(FrontendEventService) protected frontendEventService: FrontendEventService,
|
||||
@inject(TaskSchedulerService) protected taskSchedulerService: TaskSchedulerService,
|
||||
@inject(TokenService) protected tokenService: TokenService
|
||||
) {
|
||||
@ -131,7 +133,10 @@ export class RoomService {
|
||||
|
||||
await this.storageService.saveMeetRoom(room);
|
||||
// Update the archived room metadata if it exists
|
||||
await this.storageService.archiveRoomMetadata(roomId, true);
|
||||
await Promise.all([
|
||||
this.storageService.archiveRoomMetadata(roomId, true),
|
||||
this.frontendEventService.sendRoomPreferencesUpdatedSignal(roomId, room)
|
||||
]);
|
||||
return room;
|
||||
}
|
||||
|
||||
@ -298,44 +303,6 @@ export class RoomService {
|
||||
};
|
||||
}
|
||||
|
||||
async sendRoomStatusSignalToOpenViduComponents(roomId: string, participantSid: string) {
|
||||
this.logger.debug(`Sending room status signal for room ${roomId} to OpenVidu Components.`);
|
||||
|
||||
try {
|
||||
// Check if recording is started in the room
|
||||
const activeEgressArray = await this.livekitService.getActiveEgress(roomId);
|
||||
const isRecordingStarted = activeEgressArray.length > 0;
|
||||
|
||||
// Skip if recording is not started
|
||||
if (!isRecordingStarted) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Construct the payload and signal options
|
||||
const { payload, options } = OpenViduComponentsAdapterHelper.generateRoomStatusSignal(
|
||||
isRecordingStarted,
|
||||
participantSid
|
||||
);
|
||||
|
||||
await this.sendSignal(roomId, payload, options);
|
||||
} catch (error) {
|
||||
this.logger.debug(`Error sending room status signal for room ${roomId}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a signal to participants in a specified room.
|
||||
*
|
||||
* @param roomId - The name of the room where the signal will be sent.
|
||||
* @param rawData - The raw data to be sent as the signal.
|
||||
* @param options - Options for sending the data, including the topic and destination identities.
|
||||
* @returns A promise that resolves when the signal has been sent.
|
||||
*/
|
||||
async sendSignal(roomId: string, rawData: Record<string, unknown>, options: SendDataOptions): Promise<void> {
|
||||
this.logger.verbose(`Notifying participants in room ${roomId}: "${options.topic}".`);
|
||||
await this.livekitService.sendData(roomId, rawData, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Classifies rooms into those that should be deleted immediately vs marked for deletion
|
||||
*/
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { afterEach, beforeAll, describe, expect, it } from '@jest/globals';
|
||||
import { afterEach, beforeAll, describe, expect, it, jest } from '@jest/globals';
|
||||
import { MeetRecordingAccess } from '../../../../src/typings/ce/index.js';
|
||||
import {
|
||||
createRoom,
|
||||
@ -7,6 +7,9 @@ import {
|
||||
startTestServer,
|
||||
updateRoomPreferences
|
||||
} from '../../../helpers/request-helpers.js';
|
||||
import { FrontendEventService } from '../../../../src/services/index.js';
|
||||
import { container } from '../../../../src/config/index.js';
|
||||
import { MeetSignalType } from '../../../../src/typings/ce/event.model.js';
|
||||
|
||||
describe('Room API Tests', () => {
|
||||
beforeAll(() => {
|
||||
@ -19,7 +22,15 @@ describe('Room API Tests', () => {
|
||||
});
|
||||
|
||||
describe('Update Room Tests', () => {
|
||||
let frontendEventService: FrontendEventService;
|
||||
|
||||
beforeAll(() => {
|
||||
// Ensure the FrontendEventService is registered
|
||||
frontendEventService = container.get(FrontendEventService);
|
||||
});
|
||||
|
||||
it('should successfully update room preferences', async () => {
|
||||
const sendSignalSpy = jest.spyOn(frontendEventService as any, 'sendSignal');
|
||||
const createdRoom = await createRoom({
|
||||
roomIdPrefix: 'update-test',
|
||||
preferences: {
|
||||
@ -44,6 +55,18 @@ describe('Room API Tests', () => {
|
||||
|
||||
const updateResponse = await updateRoomPreferences(createdRoom.roomId, updatedPreferences);
|
||||
|
||||
// Verify a method of frontend event service is called
|
||||
expect(sendSignalSpy).toHaveBeenCalledWith(
|
||||
createdRoom.roomId,
|
||||
{
|
||||
roomId: createdRoom.roomId,
|
||||
preferences: updatedPreferences
|
||||
},
|
||||
{
|
||||
topic: MeetSignalType.MEET_ROOM_PREFERENCES_UPDATED
|
||||
}
|
||||
);
|
||||
|
||||
// Verify update response
|
||||
expect(updateResponse.status).toBe(200);
|
||||
expect(updateResponse.body).toBeDefined();
|
||||
|
||||
3
typings/src/event.model.ts
Normal file
3
typings/src/event.model.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export enum MeetSignalType {
|
||||
MEET_ROOM_PREFERENCES_UPDATED = 'meet_room_preferences_updated',
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user