From bfe97395d0c7de04f84a9727cf8eea5fd1919bd1 Mon Sep 17 00:00:00 2001 From: CSantosM <4a.santos@gmail.com> Date: Tue, 17 Feb 2026 16:56:39 +0100 Subject: [PATCH] frontend: Decouples room feature service from global config Removes direct dependency of the room feature service on the global config service and room member contexts. The global configs and room member data are now observed through signals, ensuring reactive updates and decoupling of concerns. This change allows for a more streamlined and testable architecture. --- .../pages/meeting/meeting.component.ts | 7 -- .../services/room-member-context.service.ts | 11 +-- .../shared/services/global-config.service.ts | 34 +++++---- .../shared/services/room-feature.service.ts | 71 ++++++------------- 4 files changed, 47 insertions(+), 76 deletions(-) diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/pages/meeting/meeting.component.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/pages/meeting/meeting.component.ts index 9e71258d..c7c2f0d0 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/pages/meeting/meeting.component.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/pages/meeting/meeting.component.ts @@ -5,7 +5,6 @@ import { MatIconModule } from '@angular/material/icon'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { OpenViduComponentsUiModule, OpenViduThemeMode, OpenViduThemeService, Room } from 'openvidu-components-angular'; import { Subject } from 'rxjs'; -import { GlobalConfigService } from '../../../../shared/services/global-config.service'; import { NotificationService } from '../../../../shared/services/notification.service'; import { RoomFeatureService } from '../../../../shared/services/room-feature.service'; import { RuntimeConfigService } from '../../../../shared/services/runtime-config.service'; @@ -37,7 +36,6 @@ export class MeetingComponent implements OnInit { protected lobbyService = inject(MeetingLobbyService); protected eventHandlerService = inject(MeetingEventHandlerService); protected captionsService = inject(MeetingCaptionsService); - protected configService = inject(GlobalConfigService); protected roomFeatureService = inject(RoomFeatureService); protected ovThemeService = inject(OpenViduThemeService); protected notificationService = inject(NotificationService); @@ -95,12 +93,7 @@ export class MeetingComponent implements OnInit { effect(async () => { const token = this.roomMemberToken(); if (token && this.showLobby) { - // The meeting view must be shown before loading the appearance config this.showLobby = false; - await Promise.all([ - this.configService.loadRoomsAppearanceConfig(), - this.configService.loadCaptionsConfig() - ]); } }); } diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/room-members/services/room-member-context.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/room-members/services/room-member-context.service.ts index 9289cdc1..9d265cfe 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/room-members/services/room-member-context.service.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/room-members/services/room-member-context.service.ts @@ -2,11 +2,11 @@ import { computed, Injectable, signal } from '@angular/core'; import { MeetRoomMember, MeetRoomMemberPermissions, + MeetRoomMemberRole, MeetRoomMemberTokenMetadata, MeetRoomMemberTokenOptions } from '@openvidu-meet/typings'; import { E2eeService, LoggerService } from 'openvidu-components-angular'; -import { RoomFeatureService } from '../../../shared/services/room-feature.service'; import { TokenStorageService } from '../../../shared/services/token-storage.service'; import { decodeToken } from '../../../shared/utils/token.utils'; import { RoomMemberService } from './room-member.service'; @@ -24,6 +24,7 @@ export class RoomMemberContextService { private readonly _participantName = signal(undefined); private readonly _isParticipantNameFromUrl = signal(false); private readonly _participantIdentity = signal(undefined); + private readonly _role = signal(undefined); private readonly _permissions = signal(undefined); private readonly _member = signal(undefined); @@ -35,6 +36,8 @@ export class RoomMemberContextService { readonly isParticipantNameFromUrl = this._isParticipantNameFromUrl.asReadonly(); /** Readonly signal for the participant identity */ readonly participantIdentity = this._participantIdentity.asReadonly(); + /** Readonly signal for the room member role */ + readonly role = this._role.asReadonly(); /** Readonly signal for the room member permissions */ readonly permissions = this._permissions.asReadonly(); /** Readonly signal for the room member info (when memberId is set) */ @@ -47,7 +50,6 @@ export class RoomMemberContextService { constructor( protected loggerService: LoggerService, protected roomMemberService: RoomMemberService, - protected roomFeatureService: RoomFeatureService, protected tokenStorageService: TokenStorageService, protected e2eeService: E2eeService ) { @@ -134,6 +136,7 @@ export class RoomMemberContextService { this._participantIdentity.set(decodedToken.sub); } + this._role.set(metadata.baseRole); this._permissions.set(metadata.effectivePermissions); // If token contains memberId, fetch and store member info @@ -146,9 +149,6 @@ export class RoomMemberContextService { } } - // Update feature configuration - this.roomFeatureService.setRoomMemberRole(metadata.baseRole); - this.roomFeatureService.setRoomMemberPermissions(metadata.effectivePermissions); } catch (error) { this.log.e('Error decoding room member token:', error); throw new Error('Invalid room member token'); @@ -163,6 +163,7 @@ export class RoomMemberContextService { this._participantName.set(undefined); this._isParticipantNameFromUrl.set(false); this._participantIdentity.set(undefined); + this._role.set(undefined); this._permissions.set(undefined); this._member.set(undefined); } diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/global-config.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/global-config.service.ts index 616bd311..123e03db 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/global-config.service.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/global-config.service.ts @@ -1,8 +1,7 @@ -import { inject, Injectable } from '@angular/core'; +import { inject, Injectable, signal } from '@angular/core'; import { MeetAppearanceConfig, SecurityConfig, WebhookConfig } from '@openvidu-meet/typings'; import { ILogger, LoggerService } from 'openvidu-components-angular'; import { HttpService } from './http.service'; -import { RoomFeatureService } from './room-feature.service'; @Injectable({ providedIn: 'root' @@ -12,10 +11,15 @@ export class GlobalConfigService { protected loggerService: LoggerService = inject(LoggerService); protected httpService: HttpService = inject(HttpService); - protected roomFeatureService: RoomFeatureService = inject(RoomFeatureService); protected log: ILogger = this.loggerService.get('OpenVidu Meet - GlobalConfigService'); + private readonly _roomAppearanceConfig = signal(undefined); + private readonly _captionsGlobalEnabled = signal(false); + + readonly roomAppearanceConfig = this._roomAppearanceConfig.asReadonly(); + readonly captionsGlobalEnabled = this._captionsGlobalEnabled.asReadonly(); + constructor() {} async getSecurityConfig(): Promise { @@ -50,14 +54,24 @@ export class GlobalConfigService { async loadRoomsAppearanceConfig(): Promise { try { - const config = await this.getRoomsAppearanceConfig(); - this.roomFeatureService.setAppearanceConfig(config.appearance); + const { appearance } = await this.getRoomsAppearanceConfig(); + this._roomAppearanceConfig.set(appearance); } catch (error) { this.log.e('Error loading rooms appearance config:', error); throw error; } } + async loadCaptionsConfig(): Promise { + try { + const { enabled } = await this.getCaptionsConfig(); + this._captionsGlobalEnabled.set(enabled); + } catch (error) { + this.log.e('Error loading captions config:', error); + throw error; + } + } + async saveRoomsAppearanceConfig(config: MeetAppearanceConfig) { const path = `${this.GLOBAL_CONFIG_API}/rooms/appearance`; await this.httpService.putRequest(path, { appearance: config }); @@ -67,14 +81,4 @@ export class GlobalConfigService { const path = `${this.GLOBAL_CONFIG_API}/captions`; return await this.httpService.getRequest<{ enabled: boolean }>(path); } - - async loadCaptionsConfig(): Promise { - try { - const config = await this.getCaptionsConfig(); - this.roomFeatureService.setCaptionsGlobalConfig(config.enabled); - } catch (error) { - this.log.e('Error loading captions config:', error); - throw error; - } - } } diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/room-feature.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/room-feature.service.ts index a16515da..3e39dff0 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/room-feature.service.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/room-feature.service.ts @@ -1,13 +1,9 @@ -import { computed, Injectable, signal } from '@angular/core'; -import { - MeetAppearanceConfig, - MeetRoomCaptionsConfig, - MeetRoomConfig, - MeetRoomMemberPermissions, - MeetRoomMemberRole -} from '@openvidu-meet/typings'; +import { computed, inject, Injectable, signal } from '@angular/core'; +import { MeetAppearanceConfig, MeetRoomCaptionsConfig, MeetRoomConfig, MeetRoomMemberPermissions, MeetRoomMemberRole } from '@openvidu-meet/typings'; import { LoggerService } from 'openvidu-components-angular'; +import { RoomMemberContextService } from '../../domains/room-members/services/room-member-context.service'; import { CaptionsStatus, RoomFeatures } from '../models/app.model'; +import { GlobalConfigService } from './global-config.service'; /** * Base configuration for features, used as a starting point before applying room-specific and user-specific configurations @@ -51,27 +47,26 @@ const DEFAULT_FEATURES: RoomFeatures = { }) export class RoomFeatureService { protected log; + protected globalConfigService = inject(GlobalConfigService); + protected roomMemberContextService = inject(RoomMemberContextService); // Signals to handle reactive state protected roomConfig = signal(undefined); - protected roomMemberRole = signal(undefined); - protected roomMemberPermissions = signal(undefined); - protected appearanceConfig = signal(undefined); - protected captionsGlobalConfig = signal(false); // Computed signal to derive features based on current configurations public readonly features = computed(() => this.calculateFeatures( this.roomConfig(), - this.roomMemberRole(), - this.roomMemberPermissions(), - this.appearanceConfig(), - this.captionsGlobalConfig() + this.roomMemberContextService.role(), + this.roomMemberContextService.permissions(), + this.globalConfigService.roomAppearanceConfig(), + this.globalConfigService.captionsGlobalEnabled() ) ); constructor(protected loggerService: LoggerService) { this.log = this.loggerService.get('OpenVidu Meet - RoomFeatureService'); + void this.loadGlobalFeatureConfigs(); } /** @@ -82,36 +77,18 @@ export class RoomFeatureService { this.roomConfig.set(config); } - /** - * Updates room member role - */ - setRoomMemberRole(role: MeetRoomMemberRole): void { - this.log.d('Updating room member role', role); - this.roomMemberRole.set(role); - } + protected async loadGlobalFeatureConfigs(): Promise { + const [appearanceResult, captionsResult] = await Promise.allSettled([ + this.globalConfigService.loadRoomsAppearanceConfig(), + this.globalConfigService.loadCaptionsConfig() + ]); - /** - * Updates room member permissions - */ - setRoomMemberPermissions(permissions: MeetRoomMemberPermissions): void { - this.log.d('Updating room member permissions', permissions); - this.roomMemberPermissions.set(permissions); - } - - /** - * Updates appearance config - */ - setAppearanceConfig(config: MeetAppearanceConfig): void { - this.log.d('Updating appearance config', config); - this.appearanceConfig.set(config); - } - - /** - * Updates captions global config - */ - setCaptionsGlobalConfig(enabled: boolean): void { - this.log.d('Updating captions global config', enabled); - this.captionsGlobalConfig.set(enabled); + if (appearanceResult.status === 'rejected') { + this.log.e('Could not load room appearance config for features:', appearanceResult.reason); + } + if (captionsResult.status === 'rejected') { + this.log.e('Could not load captions config for features:', captionsResult.reason); + } } /** @@ -205,9 +182,5 @@ export class RoomFeatureService { */ reset(): void { this.roomConfig.set(undefined); - this.roomMemberRole.set(undefined); - this.roomMemberPermissions.set(undefined); - this.appearanceConfig.set(undefined); - this.captionsGlobalConfig.set(false); } }