diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/components/meeting-lobby/meeting-lobby.component.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/components/meeting-lobby/meeting-lobby.component.ts index 4a5d6dd4..94bcec87 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/components/meeting-lobby/meeting-lobby.component.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/components/meeting-lobby/meeting-lobby.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from '@angular/common'; -import { Component, computed, inject } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; @@ -8,7 +8,6 @@ import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { ShareMeetingLinkComponent } from '../../components/share-meeting-link/share-meeting-link.component'; import { MeetingLobbyService } from '../../services/meeting-lobby.service'; -import { MeetingService } from '../../services/meeting.service'; /** * Reusable component for the meeting lobby page. @@ -32,30 +31,19 @@ import { MeetingService } from '../../services/meeting.service'; }) export class MeetingLobbyComponent { protected lobbyService = inject(MeetingLobbyService); - protected meetingService = inject(MeetingService); - protected roomName = computed(() => this.lobbyService.state().room?.roomName); - protected meetingUrl = computed(() => this.lobbyService.meetingUrl()); - protected roomClosed = computed(() => this.lobbyService.state().roomClosed); - protected showRecordingCard = computed(() => this.lobbyService.state().showRecordingCard); - protected showShareLink = computed(() => { - const state = this.lobbyService.state(); - const canModerate = this.lobbyService.canModerateRoom(); - return !!state.room && !state.roomClosed && canModerate; - }); - protected showBackButton = computed(() => this.lobbyService.state().showBackButton); - protected backButtonText = computed(() => this.lobbyService.state().backButtonText); - protected isE2EEEnabled = computed(() => this.lobbyService.state().hasRoomE2EEEnabled); - protected participantForm = computed(() => this.lobbyService.state().participantForm); - /** - * Computed signal to determine if the E2EE key input should be shown. - * When E2EE key is provided via URL query param, the control is disabled and should not be displayed. - */ - protected showE2EEKeyInput = computed(() => { - const form = this.lobbyService.state().participantForm; - const e2eeKeyControl = form.get('e2eeKey'); - return this.isE2EEEnabled() && e2eeKeyControl?.enabled; - }); + protected roomName = this.lobbyService.roomName; + protected roomClosed = this.lobbyService.roomClosed; + protected isE2EEEnabled = this.lobbyService.hasRoomE2EEEnabled; + + protected participantForm = this.lobbyService.participantForm; + protected showE2EEKeyInput = this.lobbyService.showE2EEKeyInput; + + protected showRecordingCard = this.lobbyService.showRecordingCard; + protected showShareLink = this.lobbyService.showShareLink; + protected meetingUrl = this.lobbyService.meetingUrl; + protected showBackButton = this.lobbyService.showBackButton; + protected backButtonText = this.lobbyService.backButtonText; async onFormSubmit(): Promise { await this.lobbyService.submitAccess(); diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/models/index.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/models/index.ts index c179358b..1243d269 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/models/index.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/models/index.ts @@ -1,4 +1,3 @@ export * from './captions.model'; export * from './custom-participant.model'; export * from './layout.model'; -export * from './lobby.model'; diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/models/lobby.model.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/models/lobby.model.ts deleted file mode 100644 index e791e986..00000000 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/models/lobby.model.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { FormGroup } from '@angular/forms'; -import { MeetRoom } from '@openvidu-meet/typings'; - -/** - * State interface representing the lobby phase of a meeting. - * - * IMPORTANT: This state is ONLY relevant during the lobby phase (before joining the meeting). - * Once the participant joins the meeting, MeetingContextService becomes the single source of truth. - */ -export interface LobbyState { - room?: MeetRoom; - roomId?: string; - roomClosed: boolean; - showRecordingCard: boolean; - showBackButton: boolean; - backButtonText: string; - hasRoomE2EEEnabled: boolean; - participantForm: FormGroup; - roomMemberToken?: string; -} diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-context.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-context.service.ts index 9ad8b499..5f9e3c83 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-context.service.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-context.service.ts @@ -19,108 +19,65 @@ export class MeetingContextService { private readonly viewportService = inject(ViewportService); private readonly sessionStorageService = inject(SessionStorageService); - private readonly _meetRoom = signal(undefined); - private readonly _lkRoom = signal(undefined); private readonly _roomId = signal(undefined); + private readonly _meetRoom = signal(undefined); private readonly _meetingUrl = signal(''); + private readonly _roomSecret = signal(undefined); private readonly _e2eeKey = signal(''); private readonly _isE2eeKeyFromUrl = signal(false); - private readonly _roomSecret = signal(undefined); private readonly _hasRecordings = signal(false); private readonly _meetingEndedBy = signal<'self' | 'other' | null>(null); + private readonly _lkRoom = signal(undefined); private readonly _participantsVersion = signal(0); private readonly _localParticipant = signal(undefined); private readonly _remoteParticipants = signal([]); - /** - * Readonly signal for the current room - */ + /** Readonly signal for the current room ID */ + readonly roomId = this._roomId.asReadonly(); + /** Readonly signal for the current room */ readonly meetRoom = this._meetRoom.asReadonly(); - /** - * Readonly signal for the current room ID - */ - readonly roomId = this._roomId.asReadonly(); - - /** - * Readonly signal for the current lk room - */ - readonly lkRoom = this._lkRoom.asReadonly(); - - /** - * Readonly signal for the meeting URL - */ + /** Readonly signal for the current meeting URL */ readonly meetingUrl = this._meetingUrl.asReadonly(); - - /** - * Readonly signal for the E2EE key - */ + /** Readonly signal for the room secret (if any) */ + readonly roomSecret = this._roomSecret.asReadonly(); + /** Readonly signal for the stored E2EE key (if any) */ readonly e2eeKey = this._e2eeKey.asReadonly(); - - /** - * Readonly signal for whether the E2EE key came from a URL parameter - */ + /** Readonly signal for whether the E2EE key came from a URL parameter */ readonly isE2eeKeyFromUrl = this._isE2eeKeyFromUrl.asReadonly(); - /** - * Readonly signal for the room secret - */ - readonly roomSecret = this._roomSecret.asReadonly(); - - /** - * Readonly signal for whether the room has recordings - */ + /** Readonly signal for whether the room has recordings */ readonly hasRecordings = this._hasRecordings.asReadonly(); - - /** - * Readonly signal for who ended the meeting ('self', 'other', or null) - */ + /** Readonly signal for who ended the meeting ('self', 'other', or null) */ readonly meetingEndedBy = this._meetingEndedBy.asReadonly(); + /** Readonly signal for the current LiveKit room */ + readonly lkRoom = this._lkRoom.asReadonly(); /** * Readonly signal for participants version (increments on role changes) * Used to trigger reactivity when participant properties change without array reference changes */ readonly participantsVersion = this._participantsVersion.asReadonly(); - - /** - * Readonly signal for the local participant - */ + /** Readonly signal for the local participant */ readonly localParticipant = this._localParticipant.asReadonly(); - - /** - * Readonly signal for the remote participants - */ + /** Readonly signal for the remote participants */ readonly remoteParticipants = this._remoteParticipants.asReadonly(); - - /** - * Computed signal that combines local and remote participants - */ + /** Computed signal that combines local and remote participants */ readonly allParticipants = computed(() => { const local = this._localParticipant(); const remotes = this._remoteParticipants(); return local ? [local, ...remotes] : remotes; }); - /** - * Computed signal for whether the current user can moderate the room - */ + /** Computed signal for whether the current user can moderate the room */ readonly canModerateRoom = computed(() => this.roomFeatureService.features().canModerateRoom); - - /** - * Computed signal for whether layout switching is allowed - */ + /** Computed signal for whether layout switching is allowed */ readonly allowLayoutSwitching = computed(() => this.roomFeatureService.features().allowLayoutSwitching); - - /** - * Computed signal for captions status based on room and global configuration - */ + /** Computed signal for captions status based on room and global configuration */ readonly getCaptionsStatus = computed(() => this.roomFeatureService.features().captionsStatus); - /** - * Computed signal for whether the device is mobile - */ - readonly isMobile = computed(() => this.viewportService.isMobile()); + /** Readonly signal for whether the device is mobile */ + readonly isMobile = this.viewportService.isMobile; constructor() { // Setup automatic synchronization with ParticipantService signals @@ -145,32 +102,6 @@ export class MeetingContextService { this.setMeetingUrl(room.accessUrl); } - /** - * Sets the LiveKit Room instance in context - * @param room - */ - setLkRoom(room: Room) { - this._lkRoom.set(room); - } - - /** - * Synchronizes participants from OpenVidu Components ParticipantService using signals. - * Effects are automatically cleaned up when the service is destroyed. - */ - private setupParticipantSynchronization(): void { - // Sync local participant signal - effect(() => { - const localParticipant = this.ovParticipantService.localParticipantSignal(); - this._localParticipant.set(localParticipant as CustomParticipantModel); - }); - - // Sync remote participants signal - effect(() => { - const remoteParticipants = this.ovParticipantService.remoteParticipantsSignal(); - this._remoteParticipants.set(remoteParticipants as CustomParticipantModel[]); - }); - } - /** * Updates the meeting URL based on room access URL * @param accessUrl The room access URL @@ -182,6 +113,19 @@ export class MeetingContextService { this._meetingUrl.set(meetingUrl); } + /** + * Sets the room secret in context + * @param secret The room secret + * @param updateStorage Whether to persist in SessionStorage (default: false) + */ + setRoomSecret(secret: string, updateStorage = false): void { + if (updateStorage) { + this.sessionStorageService.setRoomSecret(secret); + } + + this._roomSecret.set(secret); + } + /** * Stores the E2EE key in context * @param key The E2EE key @@ -204,19 +148,6 @@ export class MeetingContextService { } } - /** - * Sets the room secret in context - * @param secret The room secret - * @param updateStorage Whether to persist in SessionStorage (default: false) - */ - setRoomSecret(secret: string, updateStorage = false): void { - if (updateStorage) { - this.sessionStorageService.setRoomSecret(secret); - } - - this._roomSecret.set(secret); - } - /** * Updates whether the room has recordings * @param hasRecordings True if recordings exist @@ -233,6 +164,14 @@ export class MeetingContextService { this._meetingEndedBy.set(by); } + /** + * Sets the LiveKit Room instance in context + * @param room + */ + setLkRoom(room: Room) { + this._lkRoom.set(room); + } + /** * Increments the participants version counter * Used to trigger reactivity when participant properties (like role) change @@ -241,6 +180,24 @@ export class MeetingContextService { this._participantsVersion.update((v) => v + 1); } + /** + * Synchronizes participants from OpenVidu Components ParticipantService using signals. + * Effects are automatically cleaned up when the service is destroyed. + */ + private setupParticipantSynchronization(): void { + // Sync local participant signal + effect(() => { + const localParticipant = this.ovParticipantService.localParticipantSignal(); + this._localParticipant.set(localParticipant as CustomParticipantModel); + }); + + // Sync remote participants signal + effect(() => { + const remoteParticipants = this.ovParticipantService.remoteParticipantsSignal(); + this._remoteParticipants.set(remoteParticipants as CustomParticipantModel[]); + }); + } + /** * Clears the meeting context */ diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-lobby.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-lobby.service.ts index 514f5d26..820cbd67 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-lobby.service.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-lobby.service.ts @@ -1,7 +1,7 @@ import { computed, inject, Injectable, signal } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; -import { MeetRoomStatus } from '@openvidu-meet/typings'; +import { MeetRoom, MeetRoomStatus } from '@openvidu-meet/typings'; import { LoggerService } from 'openvidu-components-angular'; import { NavigationErrorReason } from '../../../shared/models/navigation.model'; import { AppContextService } from '../../../shared/services/app-context.service'; @@ -10,84 +10,122 @@ import { AuthService } from '../../auth/services/auth.service'; import { RecordingService } from '../../recordings/services/recording.service'; import { RoomMemberContextService } from '../../room-members/services/room-member-context.service'; import { RoomService } from '../../rooms/services/room.service'; -import { LobbyState } from '../models/lobby.model'; import { MeetingContextService } from './meeting-context.service'; import { MeetingWebComponentManagerService } from './meeting-webcomponent-manager.service'; import { MeetingService } from './meeting.service'; /** * Service that manages the meeting lobby phase state and operations. - * * This service is ONLY responsible for the LOBBY PHASE - the period before a participant joins the meeting. - * */ @Injectable() export class MeetingLobbyService { - /** - * Reactive signal for lobby state. - * This state is only relevant during the lobby phase. - */ - private readonly _state = signal({ - roomId: undefined, - roomClosed: false, - showRecordingCard: false, - showBackButton: true, - backButtonText: 'Back', - hasRoomE2EEEnabled: false, - participantForm: new FormGroup({ - name: new FormControl('', [Validators.required]), - e2eeKey: new FormControl('') - }), - roomMemberToken: undefined - }); - - protected roomService: RoomService = inject(RoomService); - protected meetingContextService: MeetingContextService = inject(MeetingContextService); - protected meetingService: MeetingService = inject(MeetingService); - protected recordingService: RecordingService = inject(RecordingService); - protected authService: AuthService = inject(AuthService); - protected roomMemberContextService: RoomMemberContextService = inject(RoomMemberContextService); - protected navigationService: NavigationService = inject(NavigationService); - protected appCtxService: AppContextService = inject(AppContextService); - protected wcManagerService: MeetingWebComponentManagerService = inject(MeetingWebComponentManagerService); + protected roomService = inject(RoomService); + protected meetingContextService = inject(MeetingContextService); + protected meetingService = inject(MeetingService); + protected recordingService = inject(RecordingService); + protected authService = inject(AuthService); + protected roomMemberContextService = inject(RoomMemberContextService); + protected navigationService = inject(NavigationService); + protected appCtxService = inject(AppContextService); + protected wcManagerService = inject(MeetingWebComponentManagerService); + protected route = inject(ActivatedRoute); protected loggerService = inject(LoggerService); protected log = this.loggerService.get('OpenVidu Meet - MeetingLobbyService'); - protected route: ActivatedRoute = inject(ActivatedRoute); /** - * Readonly signal for lobby state - components can use computed() with this + * Individual signals for lobby state. + * This state is only relevant during the lobby phase - before a participant joins the meeting. */ - readonly state = this._state.asReadonly(); + private readonly _roomId = signal(undefined); + private readonly _room = signal(undefined); + private readonly _showRecordingCard = signal(false); + private readonly _showBackButton = signal(true); + private readonly _backButtonText = signal('Back'); + private readonly _roomMemberToken = signal(undefined); + private readonly _participantForm = signal( + new FormGroup({ + name: new FormControl('', [Validators.required]), + e2eeKey: new FormControl('') + }) + ); + + /** Readonly signal for the room */ + readonly room = this._room.asReadonly(); + /** Readonly signal for the room ID */ + readonly roomId = this._roomId.asReadonly(); + /** Readonly signal for room name */ + readonly roomName = computed(() => this._room()?.roomName); + + /** Readonly signal for whether the room is closed */ + readonly roomClosed = computed(() => this._room()?.status === MeetRoomStatus.CLOSED); + /** Readonly signal for whether E2EE is enabled in the room */ + readonly hasRoomE2EEEnabled = computed(() => this._room()?.config.e2ee.enabled ?? false); + /** + * Computed signal to determine if the E2EE key input should be shown. + * When E2EE key is provided via URL query param, the control is disabled and should not be displayed. + */ + readonly showE2EEKeyInput = computed(() => { + const form = this._participantForm(); + const e2eeKeyControl = form.get('e2eeKey'); + return this.hasRoomE2EEEnabled() && e2eeKeyControl?.enabled; + }); + + /** Readonly signal for whether to show the recording card */ + readonly showRecordingCard = this._showRecordingCard.asReadonly(); + /** Readonly signal for whether to show the back button */ + readonly showBackButton = this._showBackButton.asReadonly(); + /** Readonly signal for the back button text */ + readonly backButtonText = this._backButtonText.asReadonly(); /** - * Computed signal for meeting URL derived from MeetingContextService - * This ensures a single source of truth for the meeting URL + * Computed signal to determine if the share link option should be shown. + * The share link is shown only if the room is not closed and the user has permissions to moderate the room */ + readonly showShareLink = computed(() => { + return !this.roomClosed() && this.meetingContextService.canModerateRoom(); + }); + /** Computed signal for meeting URL derived from MeetingContextService */ readonly meetingUrl = computed(() => this.meetingContextService.meetingUrl()); + /** Readonly signal for the room member token */ + readonly roomMemberToken = this._roomMemberToken.asReadonly(); + + /** Readonly signal for the participant form */ + readonly participantForm = this._participantForm.asReadonly(); + /** - * Computed signal for whether the current user can moderate the room - * Derived from MeetingContextService + * Setter for participant name */ - readonly canModerateRoom = computed(() => this.meetingContextService.canModerateRoom()); + setParticipantName(name: string): void { + this._participantForm().get('name')?.setValue(name); + } /** * Computed signal for participant name - optimized to avoid repeated form access */ readonly participantName = computed(() => { - const { valid, value } = this._state().participantForm; + const form = this._participantForm(); + const { valid, value } = form; if (!valid || !value.name?.trim()) { return ''; } return value.name.trim(); }); + /** + * Setter for E2EE key + */ + setE2eeKey(key: string): void { + this._participantForm().get('e2eeKey')?.setValue(key); + } + /** * Computed signal for E2EE key - optimized to avoid repeated form access * Uses getRawValue() to get the value even when the control is disabled (e.g., when set from URL param) */ readonly e2eeKeyValue = computed(() => { - const form = this._state().participantForm; + const form = this._participantForm(); const rawValue = form.getRawValue(); if (!form.valid || !rawValue.e2eeKey?.trim()) { return ''; @@ -95,47 +133,6 @@ export class MeetingLobbyService { return rawValue.e2eeKey.trim(); }); - /** - * Computed signal for room member token - */ - readonly roomMemberToken = computed(() => this._state().roomMemberToken); - - /** - * Computed signal for room ID - */ - readonly roomId = computed(() => this._state().roomId); - - /** - * Computed signal for room secret. - * Obtained from MeetingContextService - */ - readonly roomSecret = computed(() => this.meetingContextService.roomSecret()); - - /** - * Computed signal for room name - */ - readonly roomName = computed(() => this._state().room?.roomName); - - /** - * Computed signal for has recordings. - * Obtained from MeetingContextService - */ - readonly hasRecordings = computed(() => this.meetingContextService.hasRecordings()); - - /** - * Setter for participant name - */ - setParticipantName(name: string): void { - this._state().participantForm.get('name')?.setValue(name); - } - - /** - * Setter for E2EE key - */ - setE2eeKey(key: string): void { - this._state().participantForm.get('e2eeKey')?.setValue(key); - } - /** * Initializes the lobby state by fetching room data and configuring UI */ @@ -144,7 +141,7 @@ export class MeetingLobbyService { const roomId = this.meetingContextService.roomId(); if (!roomId) throw new Error('Room ID is not set in Meeting Context'); - this._state.update((state) => ({ ...state, roomId })); + this._roomId.set(roomId); const [room] = await Promise.all([ this.roomService.getRoom(roomId, { @@ -155,22 +152,12 @@ export class MeetingLobbyService { this.checkForRecordings(), this.initializeParticipantName() ]); - - const roomClosed = room.status === MeetRoomStatus.CLOSED; - const hasRoomE2EEEnabled = room.config?.e2ee?.enabled || false; - - this._state.update((state) => ({ - ...state, - room, - roomClosed, - hasRoomE2EEEnabled - })); - + this._room.set(room); this.meetingContextService.setMeetRoom(room); - if (hasRoomE2EEEnabled) { + if (this.hasRoomE2EEEnabled()) { // If E2EE is enabled, make the e2eeKey form control required - const form = this._state().participantForm; + const form = this._participantForm(); form.get('e2eeKey')?.setValidators([Validators.required]); const contextE2eeKey = this.meetingContextService.e2eeKey(); if (contextE2eeKey) { @@ -192,7 +179,7 @@ export class MeetingLobbyService { * Copies the meeting speaker link to clipboard */ copyMeetingSpeakerLink() { - const { room } = this.state(); + const room = this._room(); if (room) { this.meetingService.copyMeetingSpeakerLink(room); } @@ -231,27 +218,22 @@ export class MeetingLobbyService { */ async goToRecordings(): Promise { try { - const roomId = this._state().roomId; - const roomSecret = this.meetingContextService.roomSecret(); - await this.navigationService.navigateTo(`room/${roomId}/recordings`, { - secret: roomSecret - }); + const roomId = this.roomId(); + await this.navigationService.navigateTo(`room/${roomId}/recordings`); } catch (error) { this.log.e('Error navigating to recordings:', error); } } async submitAccess(): Promise { - const sanitized = this.participantName().trim(); // remove leading/trailing spaces - - if (!sanitized) { + const name = this.participantName(); + if (!name) { this.log.e('Participant form is invalid. Cannot access meeting.'); throw new Error('Participant form is invalid'); } - this.setParticipantName(sanitized); // For E2EE rooms, validate passkey - const { hasRoomE2EEEnabled, roomId } = this._state(); + const hasRoomE2EEEnabled = this.hasRoomE2EEEnabled(); if (hasRoomE2EEEnabled) { const e2eeKey = this.e2eeKeyValue(); if (!e2eeKey) { @@ -262,7 +244,7 @@ export class MeetingLobbyService { } await this.generateRoomMemberToken(); - await Promise.all([this.addParticipantNameToUrl(), this.roomService.loadRoomConfig(roomId!)]); + await Promise.all([this.addParticipantNameToUrl(), this.roomService.loadRoomConfig(this._roomId()!)]); } /** @@ -276,12 +258,13 @@ export class MeetingLobbyService { // If in standalone mode without redirection and user is not authenticated, // hide back button (user has no where to go back to) if (isStandaloneMode && !redirection && !isAuthenticated) { - this._state.update((state) => ({ ...state, showBackButton: false })); + this._showBackButton.set(false); return; } const backButtonText = isStandaloneMode && !redirection && isAuthenticated ? 'Back to Rooms' : 'Back'; - this._state.update((state) => ({ ...state, showBackButton: true, backButtonText })); + this._showBackButton.set(true); + this._backButtonText.set(backButtonText); } /** @@ -297,11 +280,11 @@ export class MeetingLobbyService { const canRetrieveRecordings = this.roomMemberContextService.hasPermission('canRetrieveRecordings'); if (!canRetrieveRecordings) { - this._state.update((state) => ({ ...state, showRecordingCard: false })); + this._showRecordingCard.set(false); return; } - const { roomId } = this._state(); + const roomId = this._roomId(); if (!roomId) throw new Error('Room ID is not set in lobby state'); const { recordings } = await this.recordingService.listRecordings({ maxItems: 1, @@ -315,13 +298,10 @@ export class MeetingLobbyService { this.meetingContextService.setHasRecordings(hasRecordings); // Update only UI flag locally - this._state.update((state) => ({ - ...state, - showRecordingCard: hasRecordings - })); + this._showRecordingCard.set(hasRecordings); } catch (error) { this.log.e('Error checking for recordings:', error); - this._state.update((state) => ({ ...state, showRecordingCard: false })); + this._showRecordingCard.set(false); } } @@ -352,7 +332,7 @@ export class MeetingLobbyService { */ protected async generateRoomMemberToken() { try { - const roomId = this._state().roomId; + const roomId = this._roomId(); const roomSecret = this.meetingContextService.roomSecret(); const roomMemberToken = await this.roomMemberContextService.generateToken( roomId!, @@ -365,7 +345,7 @@ export class MeetingLobbyService { ); const updatedName = this.roomMemberContextService.getParticipantName()!; this.setParticipantName(updatedName); - this._state.update((state) => ({ ...state, roomMemberToken })); + this._roomMemberToken.set(roomMemberToken); } catch (error: any) { this.log.e('Error generating room member token for joining meeting:', error); const message = error?.error?.message || error.message || 'Unknown error'; @@ -418,18 +398,17 @@ export class MeetingLobbyService { } protected clearLobbyState() { - this._state.set({ - roomId: undefined, - roomClosed: false, - showRecordingCard: false, - showBackButton: true, - backButtonText: 'Back', - hasRoomE2EEEnabled: false, - participantForm: new FormGroup({ + this._room.set(undefined); + this._roomId.set(undefined); + this._showRecordingCard.set(false); + this._showBackButton.set(true); + this._backButtonText.set('Back'); + this._roomMemberToken.set(undefined); + this._participantForm.set( + new FormGroup({ name: new FormControl('', [Validators.required]), e2eeKey: new FormControl('') - }), - roomMemberToken: undefined - }); + }) + ); } }