diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/auth/interceptor-handlers/auth-error-handler.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/auth/interceptor-handlers/auth-error-handler.service.ts index 23a922b8..f5ce1eea 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/auth/interceptor-handlers/auth-error-handler.service.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/auth/interceptor-handlers/auth-error-handler.service.ts @@ -2,7 +2,11 @@ import { HttpErrorResponse, HttpEvent } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; import { Router } from '@angular/router'; import { Observable, catchError, from, switchMap } from 'rxjs'; -import { HttpErrorContext, HttpErrorHandler, HttpErrorNotifierService } from '../../../shared/services/http-error-notifier.service'; +import { + HttpErrorContext, + HttpErrorHandler, + HttpErrorNotifierService +} from '../../../shared/services/http-error-notifier.service'; import { TokenStorageService } from '../../../shared/services/token-storage.service'; import { AuthService } from '../services/auth.service'; diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/auth/pages/login/login.component.html b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/auth/pages/login/login.component.html index c5fd5d12..944f6b81 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/auth/pages/login/login.component.html +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/auth/pages/login/login.component.html @@ -13,8 +13,13 @@
- Username - + User ID + person diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/auth/pages/login/login.component.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/auth/pages/login/login.component.ts index 1fee7e8a..0a660b20 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/auth/pages/login/login.component.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/auth/pages/login/login.component.ts @@ -12,24 +12,24 @@ import { NavigationService } from '../../../../shared/services/navigation.servic import { AuthService } from '../../services/auth.service'; @Component({ - selector: 'ov-login', - imports: [ - MatFormFieldModule, - ReactiveFormsModule, - MatInputModule, - MatButtonModule, - FormsModule, - MatCardModule, - MatIconModule, - MatTooltipModule, - RouterModule - ], - templateUrl: './login.component.html', - styleUrl: './login.component.scss' + selector: 'ov-login', + imports: [ + MatFormFieldModule, + ReactiveFormsModule, + MatInputModule, + MatButtonModule, + FormsModule, + MatCardModule, + MatIconModule, + MatTooltipModule, + RouterModule + ], + templateUrl: './login.component.html', + styleUrl: './login.component.scss' }) export class LoginComponent implements OnInit { loginForm = new FormGroup({ - username: new FormControl('', [Validators.required]), + userId: new FormControl('', [Validators.required]), password: new FormControl('', [Validators.required]) }); @@ -54,16 +54,16 @@ export class LoginComponent implements OnInit { async login() { this.loginErrorMessage = undefined; - const { username, password } = this.loginForm.value; + const { userId, password } = this.loginForm.value; try { - await this.authService.login(username!, password!); + await this.authService.login(userId!, password!); await this.navigationService.redirectTo(this.redirectTo); } catch (error) { if ((error as HttpErrorResponse).status === 429) { this.loginErrorMessage = 'Too many login attempts. Please try again later'; } else { - this.loginErrorMessage = 'Invalid username or password'; + this.loginErrorMessage = 'Invalid user ID or password'; } } } diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/auth/services/auth.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/auth/services/auth.service.ts index b8b398cf..2efa07f3 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/auth/services/auth.service.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/auth/services/auth.service.ts @@ -4,34 +4,54 @@ import { MeetUserDTO, MeetUserRole } from '@openvidu-meet/typings'; import { HttpService } from '../../../shared/services/http.service'; import { NavigationService } from '../../../shared/services/navigation.service'; import { TokenStorageService } from '../../../shared/services/token-storage.service'; +import { UserService } from '../../users/services/user.service'; @Injectable({ providedIn: 'root' }) export class AuthService { protected readonly AUTH_API = `${HttpService.INTERNAL_API_PATH_PREFIX}/auth`; - protected readonly USERS_API = `${HttpService.INTERNAL_API_PATH_PREFIX}/users`; + protected hasCheckAuth = false; protected user: MeetUserDTO | null = null; constructor( protected httpService: HttpService, + protected userService: UserService, protected tokenStorageService: TokenStorageService, protected navigationService: NavigationService ) {} - async login(username: string, password: string) { + /** + * Logs in a user with the provided credentials. + * + * @param userId - The unique identifier of the user + * @param password - The user's password + * @returns A promise that resolves when the login is successful + */ + async login(userId: string, password: string) { try { const path = `${this.AUTH_API}/login`; - const body = { username, password }; - const response = await this.httpService.postRequest(path, body); + const body = { userId, password }; + const response = await this.httpService.postRequest<{ + message: string; + accessToken: string; + refreshToken?: string; + mustChangePassword?: boolean; + }>(path, body); - // Check if we got tokens in the response (header mode) - if (response.accessToken && response.refreshToken) { - this.tokenStorageService.setAccessToken(response.accessToken); + // Save tokens in localStorage + this.tokenStorageService.setAccessToken(response.accessToken); + if (response.refreshToken) { this.tokenStorageService.setRefreshToken(response.refreshToken); } + // TODO: Redirect user to profile page in order to change password on first login if required by backend + // if (response.mustChangePassword) { + // this.navigationService.redirectToProfilePage(); + // return; + // } + await this.getAuthenticatedUser(true); } catch (err) { const error = err as HttpErrorResponse; @@ -40,6 +60,11 @@ export class AuthService { } } + /** + * Refreshes the access token using the refresh token. + * + * @returns A promise that resolves to the response from the refresh endpoint + */ async refreshToken() { const path = `${this.AUTH_API}/refresh`; const refreshToken = this.tokenStorageService.getRefreshToken(); @@ -61,6 +86,13 @@ export class AuthService { return response; } + /** + * Logs out the currently authenticated user and clears authentication tokens. + * Redirects to the login page after logout, optionally with a query parameter to redirect back after login. + * + * @param redirectToAfterLogin - Optional path to redirect to after login + * @returns A promise that resolves when the logout is successful + */ async logout(redirectToAfterLogin?: string) { try { const path = `${this.AUTH_API}/logout`; @@ -78,31 +110,67 @@ export class AuthService { } } + /** + * Checks if the user is authenticated by attempting to retrieve the authenticated user's information. + * + * @return A promise that resolves to true if the user is authenticated, false otherwise + */ async isUserAuthenticated(): Promise { await this.getAuthenticatedUser(); return !!this.user; } - async getUsername(): Promise { + /** + * Retrieves the authenticated user's ID. + * + * @return A promise that resolves to the user's ID if authenticated, undefined otherwise + */ + async getUserId(): Promise { await this.getAuthenticatedUser(); - return this.user?.username; + return this.user?.userId; } - async getUserRoles(): Promise { + /** + * Retrieves the authenticated user's name. + * + * @return A promise that resolves to the user's name if authenticated, undefined otherwise + */ + async getUserName(): Promise { await this.getAuthenticatedUser(); - return this.user?.roles; + return this.user?.name; } + /** + * Retrieves the authenticated user's role. + * + * @return A promise that resolves to the user's role if authenticated, undefined otherwise + */ + async getUserRole(): Promise { + await this.getAuthenticatedUser(); + return this.user?.role; + } + + /** + * Checks if the authenticated user has an admin role. + * + * @return A promise that resolves to true if the user is an admin, false otherwise + */ async isAdmin(): Promise { - const roles = await this.getUserRoles(); - return roles ? roles.includes(MeetUserRole.ADMIN) : false; + const role = await this.getUserRole(); + return role === MeetUserRole.ADMIN; } + /** + * Retrieves the authenticated user's information and caches it in the service. + * If the user information is already cached and force is not true, it returns immediately. + * If force is true, it will attempt to fetch the user information again from the server. + * + * @param force - If true, forces a refresh of the user information from the server + */ private async getAuthenticatedUser(force = false) { if (force || (!this.user && !this.hasCheckAuth)) { try { - const path = `${this.USERS_API}/profile`; - const user = await this.httpService.getRequest(path); + const user = await this.userService.getMe(); this.user = user; } catch (error) { this.user = null; @@ -111,9 +179,4 @@ export class AuthService { this.hasCheckAuth = true; } } - - async changePassword(currentPassword: string, newPassword: string): Promise { - const path = `${this.USERS_API}/change-password`; - return this.httpService.postRequest(path, { currentPassword, newPassword }); - } } diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/components/index.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/components/index.ts index 6b8190aa..f1df2478 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/components/index.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/components/index.ts @@ -1 +1,2 @@ export * from './console-nav/console-nav.component'; +export * from './logo-selector/logo-selector.component'; diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/components/logo-selector/logo-selector.component.scss b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/components/logo-selector/logo-selector.component.scss index b1d74af2..4b091400 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/components/logo-selector/logo-selector.component.scss +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/components/logo-selector/logo-selector.component.scss @@ -1,4 +1,4 @@ -@use '../../../../../../src/assets/styles/design-tokens'; +@use '../../../../../../../../src/assets/styles/design-tokens'; // Form styling .form-section { diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/index.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/index.ts index e3be5653..9d412aac 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/index.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/index.ts @@ -1,3 +1,2 @@ export * from './components'; export * from './pages'; - diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/overview/overview.component.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/overview/overview.component.ts index d47c8c20..73b896be 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/overview/overview.component.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/console/pages/overview/overview.component.ts @@ -53,8 +53,4 @@ export class OverviewComponent implements OnInit { console.error(`Error navigating to ${section}:`, error); } } - - async refreshData() { - await this.loadStats(); - } } diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/components/index.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/components/index.ts index 0630ad23..e091a8ce 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/components/index.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/components/index.ts @@ -2,4 +2,3 @@ export * from './hidden-participants-indicator/hidden-participants-indicator.com export * from './meeting-lobby/meeting-lobby.component'; export * from './meeting-share-link-overlay/meeting-share-link-overlay.component'; export * from './share-meeting-link/share-meeting-link.component'; - diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/index.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/index.ts index a3962816..9f8a0c90 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/index.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/index.ts @@ -4,4 +4,3 @@ export * from './models'; export * from './pages'; export * from './providers'; export * from './services'; - diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/models/custom-participant.model.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/models/custom-participant.model.ts index ed62edd5..818efbc9 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/models/custom-participant.model.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/models/custom-participant.model.ts @@ -58,5 +58,5 @@ const extractParticipantRole = (metadata: any): MeetRoomMemberRole => { if (!parsedMetadata || typeof parsedMetadata !== 'object') { return MeetRoomMemberRole.SPEAKER; } - return parsedMetadata.role || MeetRoomMemberRole.SPEAKER; + return parsedMetadata.baseRole || MeetRoomMemberRole.SPEAKER; }; 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 7959167a..c179358b 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 @@ -2,4 +2,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/pages/index.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/pages/index.ts index 6225345c..473aefe7 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/pages/index.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/pages/index.ts @@ -1,4 +1,2 @@ export * from './end-meeting/end-meeting.component'; export * from './meeting/meeting.component'; - - diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/index.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/index.ts index 73494d1b..d0893829 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/index.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/meeting/services/index.ts @@ -5,4 +5,3 @@ export * from './meeting-layout.service'; export * from './meeting-lobby.service'; export * from './meeting-webcomponent-manager.service'; export * from './meeting.service'; - 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 ace01d5a..3bcbaed1 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 @@ -271,7 +271,7 @@ export class MeetingEventHandlerService { // Refresh participant token with new role await this.roomMemberService.generateToken(roomId, { secret, - grantJoinMeetingPermission: true, + joinMeeting: true, participantName, participantIdentity }); 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 ed20ecd7..f1d372fa 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 @@ -330,7 +330,7 @@ export class MeetingLobbyService { protected async initializeParticipantName(): Promise { // Apply participant name from RoomMemberService if set, otherwise use authenticated username const currentParticipantName = this.roomMemberService.getParticipantName(); - const username = await this.authService.getUsername(); + const username = await this.authService.getUserName(); const participantName = currentParticipantName || username; if (participantName) { @@ -351,7 +351,7 @@ export class MeetingLobbyService { roomId!, { secret: roomSecret!, - grantJoinMeetingPermission: true, + joinMeeting: true, participantName: this.participantName() }, this.e2eeKeyValue() diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/components/index.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/components/index.ts index 311a30a9..2f470705 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/components/index.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/components/index.ts @@ -1,4 +1,3 @@ export * from './recording-lists/recording-lists.component'; export * from './recording-share-dialog/recording-share-dialog.component'; export * from './recording-video-player/recording-video-player.component'; - diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/index.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/index.ts index 4ed759bc..bcd37985 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/index.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/recordings/index.ts @@ -3,4 +3,3 @@ export * from './guards'; export * from './models'; export * from './pages'; export * from './services'; - 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 b6320212..b4c60ace 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 @@ -76,33 +76,18 @@ export class RecordingService { let path = this.RECORDINGS_API; if (filters) { - const params = new URLSearchParams(); - if (filters.roomId) { - params.append('roomId', filters.roomId); - } - if (filters.roomName) { - params.append('roomName', filters.roomName); - } - if (filters.status) { - params.append('status', filters.status); - } - if (filters.fields) { - params.append('fields', filters.fields); - } - if (filters.maxItems) { - params.append('maxItems', filters.maxItems.toString()); - } - if (filters.nextPageToken) { - params.append('nextPageToken', filters.nextPageToken); - } - if (filters.sortField) { - params.append('sortField', filters.sortField); - } - if (filters.sortOrder) { - params.append('sortOrder', filters.sortOrder); - } + const queryParams = new URLSearchParams(); - path += `?${params.toString()}`; + Object.entries(filters).forEach(([key, value]) => { + if (value) { + queryParams.set(key, value.toString()); + } + }); + + const queryString = queryParams.toString(); + if (queryString) { + path += `?${queryString}`; + } } return this.httpService.getRequest(path); diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/components/index.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/components/index.ts index 432e9404..25453d7b 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/components/index.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/components/index.ts @@ -2,4 +2,3 @@ export * from './delete-room-dialog/delete-room-dialog.component'; export * from './rooms-lists/rooms-lists.component'; export * from './step-indicator/step-indicator.component'; export * from './wizard-nav/wizard-nav.component'; - diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/guards/room-validate-access.guard.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/guards/room-validate-access.guard.ts index 7ce77e9b..31289093 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/guards/room-validate-access.guard.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/guards/room-validate-access.guard.ts @@ -51,7 +51,7 @@ const validateRoomAccessInternal = async (pageUrl: string, validateRecordingPerm try { await roomMemberService.generateToken(roomId, { secret, - grantJoinMeetingPermission: false + joinMeeting: false }); // Perform recording validation if requested @@ -81,4 +81,3 @@ const validateRoomAccessInternal = async (pageUrl: string, validateRecordingPerm } } }; - diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/index.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/index.ts index 07f676b5..8d6630d2 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/index.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/index.ts @@ -5,4 +5,3 @@ export * from './models'; export * from './pages'; export * from './providers'; export * from './services'; - diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/interceptor-handlers/room-member-error-handler.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/interceptor-handlers/room-member-error-handler.service.ts index a275b858..c1d59465 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/interceptor-handlers/room-member-error-handler.service.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/interceptor-handlers/room-member-error-handler.service.ts @@ -1,7 +1,11 @@ import { HttpErrorResponse, HttpEvent } from '@angular/common/http'; import { Injectable, inject } from '@angular/core'; import { Observable, catchError, from, switchMap, throwError } from 'rxjs'; -import { HttpErrorContext, HttpErrorHandler, HttpErrorNotifierService } from '../../../shared/services/http-error-notifier.service'; +import { + HttpErrorContext, + HttpErrorHandler, + HttpErrorNotifierService +} from '../../../shared/services/http-error-notifier.service'; import { TokenStorageService } from '../../../shared/services/token-storage.service'; import { MeetingContextService } from '../../meeting/services/meeting-context.service'; import { RoomMemberService } from '../services/room-member.service'; @@ -75,12 +79,12 @@ export class RoomMemberInterceptorErrorHandlerService implements HttpErrorHandle const participantName = this.roomMemberService.getParticipantName(); const participantIdentity = this.roomMemberService.getParticipantIdentity(); - const grantJoinMeetingPermission = !!participantIdentity; // Grant join permission if identity is set + const joinMeeting = !!participantIdentity; // Grant join permission if identity is set return from( this.roomMemberService.generateToken(roomId, { secret, - grantJoinMeetingPermission, + joinMeeting, participantName, participantIdentity }) diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/services/room-member.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/services/room-member.service.ts index df8fe48a..8e21411b 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/services/room-member.service.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/services/room-member.service.ts @@ -15,6 +15,7 @@ import { getValidDecodedToken } from '../../../shared/utils/token.utils'; providedIn: 'root' }) export class RoomMemberService { + protected readonly ROOM_MEMBERS_API = `${HttpService.INTERNAL_API_PATH_PREFIX}/rooms`; protected readonly PARTICIPANT_NAME_KEY = 'ovMeet-participantName'; protected participantName?: string; @@ -34,6 +35,10 @@ export class RoomMemberService { this.log = this.loggerService.get('OpenVidu Meet - ParticipantTokenService'); } + protected getRoomMemberApiPath(roomId: string): string { + return `${this.ROOM_MEMBERS_API}/${roomId}/members`; + } + setParticipantName(participantName: string): void { this.participantName = participantName; localStorage.setItem(this.PARTICIPANT_NAME_KEY, participantName); @@ -65,7 +70,7 @@ export class RoomMemberService { tokenOptions.participantName = encryptedName; } - const path = `${HttpService.INTERNAL_API_PATH_PREFIX}/rooms/${roomId}/token`; + const path = `${this.getRoomMemberApiPath(roomId)}/token`; const { token } = await this.httpService.postRequest<{ token: string }>(path, tokenOptions); this.tokenStorageService.setRoomMemberToken(token); @@ -90,11 +95,8 @@ export class RoomMemberService { this.participantIdentity = decodedToken.sub; } - this.role = metadata.role; - this.permissions = { - livekit: decodedToken.video, - meet: metadata.permissions - }; + this.role = metadata.baseRole; + this.permissions = metadata.effectivePermissions; // Update feature configuration this.featureConfService.setRoomMemberRole(this.role); @@ -105,17 +107,8 @@ export class RoomMemberService { } } - setRoomMemberRole(role: MeetRoomMemberRole): void { - this.role = role; - this.featureConfService.setRoomMemberRole(this.role); - } - - getRoomMemberRole(): MeetRoomMemberRole { - return this.role; - } - isModerator(): boolean { - return this.getRoomMemberRole() === MeetRoomMemberRole.MODERATOR; + return this.role === MeetRoomMemberRole.MODERATOR; } getRoomMemberPermissions(): MeetRoomMemberPermissions | undefined { @@ -123,10 +116,10 @@ export class RoomMemberService { } canRetrieveRecordings(): boolean { - return this.permissions?.meet.canRetrieveRecordings ?? false; + return this.permissions?.canRetrieveRecordings ?? false; } canDeleteRecordings(): boolean { - return this.permissions?.meet.canDeleteRecordings ?? false; + return this.permissions?.canDeleteRecordings ?? false; } } diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/services/room.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/services/room.service.ts index 66925456..0625d944 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/services/room.service.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/domains/rooms/services/room.service.ts @@ -1,40 +1,33 @@ import { inject, Injectable } from '@angular/core'; import { MeetRoom, + MeetRoomAnonymousConfig, MeetRoomConfig, MeetRoomDeletionPolicyWithMeeting, MeetRoomDeletionPolicyWithRecordings, MeetRoomDeletionSuccessCode, MeetRoomFilters, - MeetRoomMemberRoleAndPermissions, MeetRoomOptions, + MeetRoomRolesConfig, MeetRoomStatus } from '@openvidu-meet/typings'; import { ILogger, LoggerService } from 'openvidu-components-angular'; import { FeatureConfigurationService } from '../../../shared/services/feature-configuration.service'; import { HttpService } from '../../../shared/services/http.service'; -/** - * RoomService - Persistence Layer for Room Data - * - * This service acts as a PERSISTENCE LAYER for room-related data and CRUD operations. - * - * Responsibilities: - * - Persist room data (roomId, roomSecret) in SessionStorage for page refresh/reload - * - Automatically sync persisted data to MeetingContextService (Single Source of Truth) - * - Provide HTTP API methods for room CRUD operations - * - Load and update room configuration - */ @Injectable({ providedIn: 'root' }) export class RoomService { protected readonly ROOMS_API = `${HttpService.API_PATH_PREFIX}/rooms`; protected readonly INTERNAL_ROOMS_API = `${HttpService.INTERNAL_API_PATH_PREFIX}/rooms`; + protected httpService: HttpService = inject(HttpService); protected loggerService: LoggerService = inject(LoggerService); protected featureConfService: FeatureConfigurationService = inject(FeatureConfigurationService); + protected log: ILogger = this.loggerService.get('OpenVidu Meet - RoomService'); + constructor() {} /** @@ -48,9 +41,9 @@ export class RoomService { } /** - * Lists rooms with optional filters for pagination and fields. + * Lists rooms with optional filters. * - * @param filters - Optional filters for pagination and fields + * @param filters - Optional filters * @return A promise that resolves to an object containing rooms and pagination info */ async listRooms(filters?: MeetRoomFilters): Promise<{ @@ -65,29 +58,17 @@ export class RoomService { if (filters) { const queryParams = new URLSearchParams(); - if (filters.roomName) { - queryParams.set('roomName', filters.roomName); - } - if (filters.status) { - queryParams.set('status', filters.status); - } - if (filters.fields) { - queryParams.set('fields', filters.fields); - } - if (filters.maxItems) { - queryParams.set('maxItems', filters.maxItems.toString()); - } - if (filters.nextPageToken) { - queryParams.set('nextPageToken', filters.nextPageToken); - } - if (filters.sortField) { - queryParams.set('sortField', filters.sortField); - } - if (filters.sortOrder) { - queryParams.set('sortOrder', filters.sortOrder); - } - path += `?${queryParams.toString()}`; + Object.entries(filters).forEach(([key, value]) => { + if (value) { + queryParams.set(key, value.toString()); + } + }); + + const queryString = queryParams.toString(); + if (queryString) { + path += `?${queryString}`; + } } return this.httpService.getRequest(path); @@ -104,6 +85,48 @@ export class RoomService { return this.httpService.getRequest(path); } + /** + * Retrieves the config for a specific room. + * + * @param roomId - The unique identifier of the room + * @return A promise that resolves to the MeetRoomConfig object + */ + async getRoomConfig(roomId: string): Promise { + const path = `${this.ROOMS_API}/${roomId}/config`; + return this.httpService.getRequest(path); + } + + /** + * Loads the room config and updates the feature configuration service. + * + * @param roomId - The unique identifier of the room + */ + async loadRoomConfig(roomId: string): Promise { + this.log.d('Fetching room config for roomId:', roomId); + + try { + const config = await this.getRoomConfig(roomId); + this.featureConfService.setRoomConfig(config); + this.log.d('Room config loaded:', config); + } catch (error) { + this.log.e('Error loading room config', error); + throw new Error('Failed to load room config'); + } + } + + /** + * Saves new room config. + * + * @param roomId - The unique identifier of the room + * @param config - The room config to be saved. + * @returns A promise that resolves when the config have been saved. + */ + async updateRoomConfig(roomId: string, config: Partial): Promise { + this.log.d('Saving room config', config); + const path = `${this.ROOMS_API}/${roomId}/config`; + await this.httpService.putRequest(path, { config }); + } + /** * Updates the status of a room. * @@ -116,6 +139,30 @@ export class RoomService { return this.httpService.putRequest(path, { status }); } + /** + * Updates the roles permissions of a room. + * + * @param roomId - The unique identifier of the room + * @param rolesConfig - The new roles configuration to be set + * @returns A promise that resolves when the roles configuration has been updated + */ + async updateRoomRoles(roomId: string, rolesConfig: MeetRoomRolesConfig): Promise { + const path = `${this.ROOMS_API}/${roomId}/roles`; + return this.httpService.putRequest(path, { roles: rolesConfig }); + } + + /** + * Updates the anonymous access configuration of a room. + * + * @param roomId - The unique identifier of the room + * @param anonymousConfig - The new anonymous access configuration to be set + * @returns A promise that resolves when the anonymous access configuration has been updated + */ + async updateRoomAnonymous(roomId: string, anonymousConfig: MeetRoomAnonymousConfig): Promise { + const path = `${this.ROOMS_API}/${roomId}/anonymous`; + return this.httpService.putRequest(path, { anonymous: anonymousConfig }); + } + /** * Deletes a room by its ID. * @@ -165,64 +212,4 @@ export class RoomService { const path = `${this.ROOMS_API}?${queryParams.toString()}`; return this.httpService.deleteRequest(path); } - - /** - * Retrieves the config for a specific room. - * - * @param roomId - The unique identifier of the room - * @return A promise that resolves to the MeetRoomConfig object - */ - async getRoomConfig(roomId: string): Promise { - this.log.d('Fetching room config for roomId:', roomId); - - try { - const path = `${this.ROOMS_API}/${roomId}/config`; - const config = await this.httpService.getRequest(path); - return config; - } catch (error) { - this.log.e('Error fetching room config', error); - throw new Error(`Failed to fetch room config for roomId: ${roomId}`); - } - } - - /** - * Loads the room config and updates the feature configuration service. - * - * @param roomId - The unique identifier of the room - */ - async loadRoomConfig(roomId: string): Promise { - try { - const config = await this.getRoomConfig(roomId); - this.featureConfService.setRoomConfig(config); - this.log.d('Room config loaded:', config); - } catch (error) { - this.log.e('Error loading room config', error); - throw new Error('Failed to load room config'); - } - } - - /** - * Saves new room config. - * - * @param roomId - The unique identifier of the room - * @param config - The room config to be saved. - * @returns A promise that resolves when the config have been saved. - */ - async updateRoomConfig(roomId: string, config: Partial): Promise { - this.log.d('Saving room config', config); - const path = `${this.ROOMS_API}/${roomId}/config`; - await this.httpService.putRequest(path, { config }); - } - - /** - * Retrieves the role and permissions for a specified room and secret. - * - * @param roomId - The unique identifier of the room - * @param secret - The secret parameter for the room - * @returns A promise that resolves to an object containing the role and permissions - */ - async getRoomMemberRoleAndPermissions(roomId: string, secret: string): Promise { - const path = `${this.INTERNAL_ROOMS_API}/${roomId}/roles/${secret}`; - return this.httpService.getRequest(path); - } } diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/feature-configuration.service.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/feature-configuration.service.ts index 45797a15..104d10e2 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/feature-configuration.service.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/services/feature-configuration.service.ts @@ -181,10 +181,10 @@ export class FeatureConfigurationService { * DISABLED_WITH_WARNING: room config enabled BUT global config disabled */ protected computeCaptionsStatus( - roomCaptionsConfig: MeetRoomCaptionsConfig | undefined, + roomCaptionsConfig: MeetRoomCaptionsConfig, globalEnabled: boolean ): CaptionsStatus { - if (!roomCaptionsConfig?.enabled) { + if (!roomCaptionsConfig.enabled) { return 'HIDDEN'; } return globalEnabled ? 'ENABLED' : 'DISABLED_WITH_WARNING'; 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 b092e616..b9f14401 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,5 +1,5 @@ import { inject, Injectable } from '@angular/core'; -import { AuthMode, MeetAppearanceConfig, SecurityConfig, WebhookConfig } from '@openvidu-meet/typings'; +import { MeetAppearanceConfig, SecurityConfig, WebhookConfig } from '@openvidu-meet/typings'; import { ILogger, LoggerService } from 'openvidu-components-angular'; import { FeatureConfigurationService } from './feature-configuration.service'; import { HttpService } from './http.service'; @@ -9,37 +9,23 @@ import { HttpService } from './http.service'; }) export class GlobalConfigService { protected readonly GLOBAL_CONFIG_API = `${HttpService.INTERNAL_API_PATH_PREFIX}/config`; - protected securityConfig?: SecurityConfig; + protected loggerService: LoggerService = inject(LoggerService); protected httpService: HttpService = inject(HttpService); protected featureConfService: FeatureConfigurationService = inject(FeatureConfigurationService); + protected log: ILogger = this.loggerService.get('OpenVidu Meet - GlobalConfigService'); + constructor() {} - async getSecurityConfig(forceRefresh = false): Promise { - if (this.securityConfig && !forceRefresh) { - return this.securityConfig; - } - - try { - const path = `${this.GLOBAL_CONFIG_API}/security`; - this.securityConfig = await this.httpService.getRequest(path); - return this.securityConfig; - } catch (error) { - this.log.e('Error fetching security config:', error); - throw error; - } - } - - async getAuthModeToAccessRoom(): Promise { - await this.getSecurityConfig(); - return this.securityConfig!.authentication.authModeToAccessRoom; + async getSecurityConfig(): Promise { + const path = `${this.GLOBAL_CONFIG_API}/security`; + return await this.httpService.getRequest(path); } async saveSecurityConfig(config: SecurityConfig) { const path = `${this.GLOBAL_CONFIG_API}/security`; await this.httpService.putRequest(path, config); - this.securityConfig = config; } async getWebhookConfig(): Promise { diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/utils/array.utils.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/utils/array.utils.ts index 48e18d21..3715b90e 100644 --- a/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/utils/array.utils.ts +++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/shared/utils/array.utils.ts @@ -5,6 +5,7 @@ export const setsAreEqual = (setA: Set, setB: Set): boolean => { } return true; }; + export const arraysAreEqual = (arrayA: T[], arrayB: T[]): boolean => { if (arrayA.length !== arrayB.length) return false; for (let i = 0; i < arrayA.length; i++) {