frontend: refactor room member context and related services for improved state management

This commit is contained in:
juancarmore 2026-02-12 18:37:27 +01:00
parent 599a744302
commit ab907bb0e8
6 changed files with 79 additions and 47 deletions

View File

@ -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

View File

@ -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) {

View File

@ -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);
}

View File

@ -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;

View File

@ -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<string, string> | null {
const roomMemberToken = this.roomMemberContextService.getRoomMemberToken();
const roomMemberToken = this.roomMemberContextService.roomMemberToken();
if (!roomMemberToken) {
return null;
}

View File

@ -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<string | undefined>(undefined);
private readonly _participantName = signal<string | undefined>(undefined);
private readonly _isParticipantNameFromUrl = signal<boolean>(false);
private readonly _participantIdentity = signal<string | undefined>(undefined);
private readonly _permissions = signal<MeetRoomMemberPermissions | undefined>(undefined);
private readonly _member = signal<MeetRoomMember | undefined>(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);
}
}