diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/guards/extract-params.guard.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/guards/extract-params.guard.ts index bdca1acf..ac477ed5 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/guards/extract-params.guard.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/guards/extract-params.guard.ts @@ -40,8 +40,13 @@ export const extractRoomParamsGuard: CanActivateFn = (route: ActivatedRouteSnaps meetingContextService.loadE2eeKeyFromStorage(); } + // Handle participant name: prioritize query param, fallback to storage if (participantName) { - roomMemberContextService.setParticipantName(participantName); + // Participant name came from URL parameter + roomMemberContextService.setParticipantName(participantName, true); + } else { + // Try to load participant name from storage + roomMemberContextService.loadParticipantNameFromStorage(); } // If the showOnlyRecordings flag is set, redirect to the recordings page for the room diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-event-handler.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-event-handler.service.ts index 4086318a..2267d612 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-event-handler.service.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/meeting-event-handler.service.ts @@ -190,7 +190,7 @@ export class MeetingEventHandlerService { const { participantIdentity, newRole, secret } = event; const roomId = this.meetingContext.roomId(); const local = this.meetingContext.localParticipant(); - const participantName = this.roomMemberContextService.getParticipantName(); + const participantName = this.roomMemberContextService.participantName(); // Check if the role update is for the local participant if (local && participantIdentity === local.identity) { diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/services/recording.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/services/recording.service.ts index 6be731ec..9b7514a4 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/services/recording.service.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/services/recording.service.ts @@ -131,7 +131,7 @@ export class RecordingService { params.append('accessToken', accessToken); } - const roomMemberToken = this.roomMemberContextService.getRoomMemberToken(); + const roomMemberToken = this.roomMemberContextService.roomMemberToken(); if (roomMemberToken) { params.append('roomMemberToken', roomMemberToken); } @@ -227,7 +227,7 @@ export class RecordingService { params.append('accessToken', accessToken); } - const roomMemberToken = this.roomMemberContextService.getRoomMemberToken(); + const roomMemberToken = this.roomMemberContextService.roomMemberToken(); if (roomMemberToken) { params.append('roomMemberToken', roomMemberToken); } diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/room-members/interceptor-handlers/room-member-error-handler.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/room-members/interceptor-handlers/room-member-error-handler.service.ts index 0f5f82da..92f09724 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/room-members/interceptor-handlers/room-member-error-handler.service.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/room-members/interceptor-handlers/room-member-error-handler.service.ts @@ -72,8 +72,8 @@ export class RoomMemberInterceptorErrorHandlerService implements HttpErrorHandle } const secret = this.meetingContextService.roomSecret(); - const participantName = this.roomMemberContextService.getParticipantName(); - const participantIdentity = this.roomMemberContextService.getParticipantIdentity(); + const participantName = this.roomMemberContextService.participantName(); + const participantIdentity = this.roomMemberContextService.participantIdentity(); const joinMeeting = !!participantIdentity; // Grant join permission if identity is set return from( @@ -86,7 +86,7 @@ export class RoomMemberInterceptorErrorHandlerService implements HttpErrorHandle ).pipe( switchMap(() => { console.log('Room member token refreshed'); - + // Update the request with the new token const headers = this.roomMemberHeaderProvider.provideHeaders(); const updatedRequest = headers ? originalRequest.clone({ setHeaders: headers }) : originalRequest; diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/room-members/interceptor-handlers/room-member-header-provider.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/room-members/interceptor-handlers/room-member-header-provider.service.ts index d9651012..ffafa61a 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/room-members/interceptor-handlers/room-member-header-provider.service.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/room-members/interceptor-handlers/room-member-header-provider.service.ts @@ -31,14 +31,14 @@ export class RoomMemberHeaderProviderService implements HttpHeaderProvider { // Only provide if: // 1. Room member token exists // 2. Current page is a room page (starts with /room/) - return !!this.roomMemberContextService.getRoomMemberToken() && context.pageUrl.startsWith('/room/'); + return !!this.roomMemberContextService.roomMemberToken() && context.pageUrl.startsWith('/room/'); } /** * Provides the room member token header */ provideHeaders(): Record | null { - const roomMemberToken = this.roomMemberContextService.getRoomMemberToken(); + const roomMemberToken = this.roomMemberContextService.roomMemberToken(); if (!roomMemberToken) { return null; } 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 e5f59a1b..9289cdc1 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 @@ -1,5 +1,6 @@ -import { Injectable } from '@angular/core'; +import { computed, Injectable, signal } from '@angular/core'; import { + MeetRoomMember, MeetRoomMemberPermissions, MeetRoomMemberTokenMetadata, MeetRoomMemberTokenOptions @@ -16,10 +17,30 @@ import { RoomMemberService } from './room-member.service'; export class RoomMemberContextService { protected readonly PARTICIPANT_NAME_KEY = 'ovMeet-participantName'; - protected roomMemberToken?: string; - protected participantName?: string; - protected participantIdentity?: string; - protected permissions?: MeetRoomMemberPermissions; + /** + * Individual signals for room member context + */ + private readonly _roomMemberToken = signal(undefined); + private readonly _participantName = signal(undefined); + private readonly _isParticipantNameFromUrl = signal(false); + private readonly _participantIdentity = signal(undefined); + private readonly _permissions = signal(undefined); + private readonly _member = signal(undefined); + + /** Readonly signal for the room member token */ + readonly roomMemberToken = this._roomMemberToken.asReadonly(); + /** Readonly signal for the participant name */ + readonly participantName = this._participantName.asReadonly(); + /** Readonly signal for whether the participant name came from a URL parameter */ + readonly isParticipantNameFromUrl = this._isParticipantNameFromUrl.asReadonly(); + /** Readonly signal for the participant identity */ + readonly participantIdentity = this._participantIdentity.asReadonly(); + /** Readonly signal for the room member permissions */ + readonly permissions = this._permissions.asReadonly(); + /** Readonly signal for the room member info (when memberId is set) */ + readonly member = this._member.asReadonly(); + /** Computed signal for the room member's display name */ + readonly memberName = computed(() => this._member()?.name); protected log; @@ -34,40 +55,34 @@ export class RoomMemberContextService { } /** - * Retrieves the current room member token. + * Sets the participant's display name and optionally stores it in localStorage. * - * @returns The room member token, or undefined if not set + * @param participantName - The display name of the participant + * @param fromUrl - Whether the name came from a URL parameter */ - getRoomMemberToken(): string | undefined { - return this.roomMemberToken; + setParticipantName(participantName: string, fromUrl = false) { + this._participantName.set(participantName); + this._isParticipantNameFromUrl.set(fromUrl); } /** - * Sets the participant's display name and stores it in localStorage. + * Saves the participant name to localStorage * - * @param participantName - The display name of the participant + * @param participantName - The display name of the participant to save */ - setParticipantName(participantName: string): void { - this.participantName = participantName; + saveParticipantNameToStorage(participantName: string) { localStorage.setItem(this.PARTICIPANT_NAME_KEY, participantName); } /** - * Retrieves the participant's display name from memory or localStorage. - * - * @returns The display name of the participant, or undefined if not set + * Loads the participant name from localStorage */ - getParticipantName(): string | undefined { - return this.participantName || localStorage.getItem(this.PARTICIPANT_NAME_KEY) || undefined; - } - - /** - * Retrieves the participant's identity. - * - * @returns The identity of the participant, or undefined if not set - */ - getParticipantIdentity(): string | undefined { - return this.participantIdentity; + loadParticipantNameFromStorage() { + const storedName = localStorage.getItem(this.PARTICIPANT_NAME_KEY); + if (storedName) { + this._participantName.set(storedName); + this._isParticipantNameFromUrl.set(false); + } } /** @@ -77,7 +92,7 @@ export class RoomMemberContextService { * @returns True if the member has the permission, false otherwise */ hasPermission(permission: keyof MeetRoomMemberPermissions): boolean { - return this.permissions?.[permission] ?? false; + return this._permissions()?.[permission] ?? false; } /** @@ -97,7 +112,7 @@ export class RoomMemberContextService { } const { token } = await this.roomMemberService.generateRoomMemberToken(roomId, tokenOptions); - this.roomMemberToken = token; + this._roomMemberToken.set(token); await this.updateContextFromToken(token); return token; } @@ -115,15 +130,25 @@ export class RoomMemberContextService { if (decodedToken.sub && decodedToken.name) { const decryptedName = await this.e2eeService.decrypt(decodedToken.name); - this.setParticipantName(decryptedName); - this.participantIdentity = decodedToken.sub; + this._participantName.set(decryptedName); + this._participantIdentity.set(decodedToken.sub); } - this.permissions = metadata.effectivePermissions; + this._permissions.set(metadata.effectivePermissions); + + // If token contains memberId, fetch and store member info + if (metadata.memberId) { + try { + const member = await this.roomMemberService.getRoomMember(metadata.roomId, metadata.memberId); + this._member.set(member); + } catch (error) { + this.log.w('Could not fetch member info:', error); + } + } // Update feature configuration this.roomFeatureService.setRoomMemberRole(metadata.baseRole); - this.roomFeatureService.setRoomMemberPermissions(this.permissions); + this.roomFeatureService.setRoomMemberPermissions(metadata.effectivePermissions); } catch (error) { this.log.e('Error decoding room member token:', error); throw new Error('Invalid room member token'); @@ -131,12 +156,14 @@ export class RoomMemberContextService { } /** - * Clears the room member context, including token, participant info, role, and permissions. + * Clears the room member context, including token, participant info, member, role, and permissions. */ clearContext(): void { - this.roomMemberToken = undefined; - this.participantName = undefined; - this.participantIdentity = undefined; - this.permissions = undefined; + this._roomMemberToken.set(undefined); + this._participantName.set(undefined); + this._isParticipantNameFromUrl.set(false); + this._participantIdentity.set(undefined); + this._permissions.set(undefined); + this._member.set(undefined); } }