frontend: enhance participant token management and update related services
- Renamed `setParticipantToken` to `setParticipantTokenAndUpdateContext` in ContextService to clarify its functionality. - Introduced ParticipantTokenService to encapsulate token generation logic and manage role/permissions extraction. - Updated VideoRoomComponent to utilize the new ParticipantTokenService for generating participant tokens. - Refactored access room method to improve form validation and participant name initialization. - Added unit tests for ParticipantTokenService to ensure proper functionality. - Updated sidenav model comments for clarity.
This commit is contained in:
parent
1cd58c19b9
commit
b2f1e2194a
@ -53,7 +53,7 @@ export const httpInterceptor: HttpInterceptorFn = (req: HttpRequest<unknown>, ne
|
|||||||
return from(httpService.refreshParticipantToken({ roomId, participantName, secret })).pipe(
|
return from(httpService.refreshParticipantToken({ roomId, participantName, secret })).pipe(
|
||||||
switchMap((data) => {
|
switchMap((data) => {
|
||||||
console.log('Participant token refreshed');
|
console.log('Participant token refreshed');
|
||||||
contextService.setParticipantToken(data.token);
|
contextService.setParticipantTokenAndUpdateContext(data.token);
|
||||||
return next(req);
|
return next(req);
|
||||||
}),
|
}),
|
||||||
catchError((error: HttpErrorResponse) => {
|
catchError((error: HttpErrorResponse) => {
|
||||||
@ -127,7 +127,7 @@ export const httpInterceptor: HttpInterceptorFn = (req: HttpRequest<unknown>, ne
|
|||||||
// refresh the participant token
|
// refresh the participant token
|
||||||
return refreshParticipantToken(error);
|
return refreshParticipantToken(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expired access token
|
// Expired access token
|
||||||
if (!pageUrl.startsWith('/console/login') && !pageUrl.startsWith('/login')) {
|
if (!pageUrl.startsWith('/console/login') && !pageUrl.startsWith('/login')) {
|
||||||
// If the error occurred in a page that is not the login page, refresh the access token
|
// If the error occurred in a page that is not the login page, refresh the access token
|
||||||
|
|||||||
@ -0,0 +1,7 @@
|
|||||||
|
import { OpenViduMeetPermissions, ParticipantRole } from 'shared-meet-components';
|
||||||
|
|
||||||
|
export interface TokenGenerationResult {
|
||||||
|
token: string; // The generated participant token
|
||||||
|
role: ParticipantRole; // Role of the participant (e.g., 'moderator', 'publisher')
|
||||||
|
permissions: OpenViduMeetPermissions; // List of permissions granted to the participant
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
export interface ConsoleNavLink {
|
export interface ConsoleNavLink {
|
||||||
label: string; // Nombre del enlace
|
label: string; // Link name
|
||||||
icon?: string; // Icono opcional
|
icon?: string; // Optional icon
|
||||||
route?: string; // Ruta para la navegación (opcional)
|
route?: string; // Route for navigation (optional)
|
||||||
clickHandler?: () => void; // Función para manejar clics (opcional)
|
clickHandler?: () => void; // Function to handle clicks (optional)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
<h2 class="form-title">
|
<h2 class="form-title">
|
||||||
Access room <strong>{{ roomId }}</strong>
|
Access room <strong>{{ roomId }}</strong>
|
||||||
</h2>
|
</h2>
|
||||||
<form [formGroup]="participantForm" (ngSubmit)="accessRoom()">
|
<form [formGroup]="participantForm" (ngSubmit)="submitAccessRoom()">
|
||||||
<mat-form-field appearance="outline" class="full-width">
|
<mat-form-field appearance="outline" class="full-width">
|
||||||
<mat-label>Name</mat-label>
|
<mat-label>Name</mat-label>
|
||||||
<input
|
<input
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import {
|
|||||||
SessionStorageService,
|
SessionStorageService,
|
||||||
WebComponentManagerService
|
WebComponentManagerService
|
||||||
} from '../../services';
|
} from '../../services';
|
||||||
|
import { ParticipantTokenService } from '@lib/services/participant-token/participant-token.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-video-room',
|
selector: 'app-video-room',
|
||||||
@ -83,6 +84,7 @@ export class VideoRoomComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected httpService: HttpService,
|
protected httpService: HttpService,
|
||||||
|
protected participantTokenService: ParticipantTokenService,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
protected route: ActivatedRoute,
|
protected route: ActivatedRoute,
|
||||||
protected location: Location,
|
protected location: Location,
|
||||||
@ -99,32 +101,28 @@ export class VideoRoomComponent implements OnInit, OnDestroy {
|
|||||||
const storageSecret = this.sessionStorageService.getModeratorSecret(this.roomId);
|
const storageSecret = this.sessionStorageService.getModeratorSecret(this.roomId);
|
||||||
this.roomSecret = storageSecret || secret;
|
this.roomSecret = storageSecret || secret;
|
||||||
|
|
||||||
// Apply participant name from context if set, otherwise use authenticated username
|
await this.initializeParticipantName();
|
||||||
const contextParticipantName = this.ctxService.getParticipantName();
|
|
||||||
const username = await this.authService.getUsername();
|
|
||||||
const participantName = contextParticipantName || username;
|
|
||||||
|
|
||||||
if (participantName) {
|
|
||||||
this.participantForm.get('name')?.setValue(participantName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.wcManagerService.stopCommandsListener();
|
this.wcManagerService.stopCommandsListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
async accessRoom() {
|
async submitAccessRoom() {
|
||||||
if (!this.participantForm.valid) {
|
const { valid, value } = this.participantForm;
|
||||||
|
if (!valid || !value.name?.trim()) {
|
||||||
|
// If the form is invalid, do not proceed
|
||||||
|
console.warn('Participant form is invalid. Cannot access room.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.participantName = this.participantForm.value.name!;
|
this.participantName = value.name.trim();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.generateParticipantToken();
|
await this.generateParticipantToken();
|
||||||
await this.replaceUrlQueryParams();
|
await this.replaceUrlQueryParams();
|
||||||
// await this.loadRoomPreferences();
|
await this.loadRoomPreferences();
|
||||||
this.applyParticipantPermissions();
|
this.updateFeatureConfiguration();
|
||||||
this.showRoom = true;
|
this.showRoom = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error accessing room:', error);
|
console.error('Error accessing room:', error);
|
||||||
@ -136,14 +134,44 @@ export class VideoRoomComponent implements OnInit, OnDestroy {
|
|||||||
this.participantToken = this.ctxService.getParticipantToken();
|
this.participantToken = this.ctxService.getParticipantToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the participant name in the form control.
|
||||||
|
*
|
||||||
|
* Retrieves the participant name from the context service first, and if not available,
|
||||||
|
* falls back to the authenticated username. Sets the retrieved name value in the
|
||||||
|
* participant form's 'name' control if a valid name is found.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @async
|
||||||
|
* @returns {Promise<void>} A promise that resolves when the participant name has been initialized
|
||||||
|
*/
|
||||||
|
private async initializeParticipantName() {
|
||||||
|
// Apply participant name from context if set, otherwise use authenticated username
|
||||||
|
const contextParticipantName = this.ctxService.getParticipantName();
|
||||||
|
const username = await this.authService.getUsername();
|
||||||
|
const participantName = contextParticipantName || username;
|
||||||
|
|
||||||
|
if (participantName) {
|
||||||
|
this.participantForm.get('name')?.setValue(participantName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a participant token for joining a video room.
|
||||||
|
*
|
||||||
|
* @throws {Error} When participant already exists in the room (status 409)
|
||||||
|
* @returns {Promise<void>} Promise that resolves when token is generated and set, or rejects on participant conflict
|
||||||
|
*/
|
||||||
private async generateParticipantToken() {
|
private async generateParticipantToken() {
|
||||||
try {
|
try {
|
||||||
const response = await this.httpService.generateParticipantToken({
|
const { token, role, permissions } = await this.participantTokenService.generateToken(
|
||||||
roomId: this.roomId,
|
this.roomId,
|
||||||
participantName: this.participantName,
|
this.participantName,
|
||||||
secret: this.roomSecret
|
this.roomSecret
|
||||||
});
|
);
|
||||||
this.setParticipantToken(response.token);
|
this.participantToken = token;
|
||||||
|
this.participantRole = role;
|
||||||
|
this.participantPermissions = permissions;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error generating participant token:', error);
|
console.error('Error generating participant token:', error);
|
||||||
switch (error.status) {
|
switch (error.status) {
|
||||||
@ -166,16 +194,6 @@ export class VideoRoomComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private setParticipantToken(token: string): void {
|
|
||||||
try {
|
|
||||||
this.ctxService.setParticipantToken(token);
|
|
||||||
this.participantRole = this.ctxService.getParticipantRole();
|
|
||||||
this.participantPermissions = this.ctxService.getParticipantPermissions();
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error('Error setting token in context', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async replaceUrlQueryParams() {
|
private async replaceUrlQueryParams() {
|
||||||
let secretQueryParam = this.roomSecret;
|
let secretQueryParam = this.roomSecret;
|
||||||
|
|
||||||
|
|||||||
@ -119,15 +119,19 @@ export class ContextService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the token for the current session.
|
* Sets the participant token in the context and updates feature configuration.
|
||||||
* @param token - A string representing the token.
|
* @param token - The JWT token to set.
|
||||||
|
* @throws Error if the token is invalid or expired.
|
||||||
*/
|
*/
|
||||||
setParticipantToken(token: string): void {
|
setParticipantTokenAndUpdateContext(token: string): void {
|
||||||
try {
|
try {
|
||||||
const decodedToken = this.getValidDecodedToken(token);
|
const decodedToken = this.getValidDecodedToken(token);
|
||||||
this.context.participantToken = token;
|
this.context.participantToken = token;
|
||||||
this.context.participantPermissions = decodedToken.metadata.permissions;
|
this.context.participantPermissions = decodedToken.metadata.permissions;
|
||||||
this.context.participantRole = decodedToken.metadata.role;
|
this.context.participantRole = decodedToken.metadata.role;
|
||||||
|
|
||||||
|
// Update feature configuration based on the new token
|
||||||
|
this.updateFeatureConfiguration();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
this.log.e('Error setting token in context', error);
|
this.log.e('Error setting token in context', error);
|
||||||
throw new Error('Error setting token', error);
|
throw new Error('Error setting token', error);
|
||||||
|
|||||||
@ -6,3 +6,4 @@ export * from './room/room.service';
|
|||||||
export * from './notification/notification.service';
|
export * from './notification/notification.service';
|
||||||
export * from './webcomponent-manager/webcomponent-manager.service';
|
export * from './webcomponent-manager/webcomponent-manager.service';
|
||||||
export * from './session-storage/session-storage.service';
|
export * from './session-storage/session-storage.service';
|
||||||
|
export * from './participant-token/participant-token.service';
|
||||||
|
|||||||
@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ParticipantTokenService } from './participant-token.service';
|
||||||
|
|
||||||
|
describe('ParticipantTokenService', () => {
|
||||||
|
let service: ParticipantTokenService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(ParticipantTokenService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { TokenGenerationResult } from '@lib/models/auth.model';
|
||||||
|
import { HttpService, ContextService, SessionStorageService } from 'shared-meet-components';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ParticipantTokenService {
|
||||||
|
constructor(
|
||||||
|
private httpService: HttpService,
|
||||||
|
private ctxService: ContextService,
|
||||||
|
private sessionStorageService: SessionStorageService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a participant token and extracts role/permissions
|
||||||
|
*/
|
||||||
|
async generateToken(roomId: string, participantName: string, secret: string): Promise<TokenGenerationResult> {
|
||||||
|
const response = await this.httpService.generateParticipantToken({
|
||||||
|
roomId,
|
||||||
|
participantName,
|
||||||
|
secret
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ctxService.setParticipantTokenAndUpdateContext(response.token);
|
||||||
|
|
||||||
|
return {
|
||||||
|
token: response.token,
|
||||||
|
role: this.ctxService.getParticipantRole(),
|
||||||
|
permissions: this.ctxService.getParticipantPermissions()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user