frontend: include ParticipantNameForm component inside VideoRoom component and remove unused guards
This commit is contained in:
parent
dbd38f3c28
commit
ec37389d1c
@ -1,12 +1,5 @@
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
CanActivateFn,
|
||||
RedirectCommand,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
UrlTree
|
||||
} from '@angular/router';
|
||||
import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
|
||||
import { AuthMode, ParticipantRole } from '@lib/typings/ce';
|
||||
import { AuthService, ContextService, HttpService, SessionStorageService } from '../services';
|
||||
|
||||
@ -79,12 +72,9 @@ export const checkParticipantRoleAndAuthGuard: CanActivateFn = async (
|
||||
const isAuthenticated = await authService.isUserAuthenticated();
|
||||
if (!isAuthenticated) {
|
||||
// Redirect to the login page with query param to redirect back to the room
|
||||
const loginRoute = router.createUrlTree(['login'], {
|
||||
return router.createUrlTree(['login'], {
|
||||
queryParams: { redirectTo: state.url }
|
||||
});
|
||||
return new RedirectCommand(loginRoute, {
|
||||
skipLocationChange: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,5 @@ export * from './application-mode.guard';
|
||||
export * from './auth.guard';
|
||||
export * from './extract-query-params.guard';
|
||||
export * from './moderator-secret.guard';
|
||||
export * from './participant-name.guard';
|
||||
export * from './run-serially.guard';
|
||||
export * from './validate-room-access.guard';
|
||||
export * from './validate-recording-access.guard';
|
||||
|
||||
@ -5,54 +5,6 @@ import { Router } from '@angular/router';
|
||||
import { ContextService, HttpService, SessionStorageService } from '../services';
|
||||
import { filter, take } from 'rxjs';
|
||||
|
||||
/**
|
||||
* Guard that replaces the moderator secret in the URL with the publisher secret.
|
||||
*
|
||||
* This guard checks if the current participant is a moderator. If so, it retrieves the moderator and publisher secrets
|
||||
* for the current room and updates the session storage with the moderator secret. It then replaces the secret in the URL
|
||||
* with the publisher secret.
|
||||
*
|
||||
* @param route - The activated route snapshot.
|
||||
* @param state - The router state snapshot.
|
||||
* @returns A promise that resolves to `true` if the operation is successful, otherwise `false`.
|
||||
*
|
||||
* @throws Will log an error and return `false` if an error occurs during the process.
|
||||
*/
|
||||
export const replaceModeratorSecretGuard: CanActivateFn = (route, _state) => {
|
||||
const httpService = inject(HttpService);
|
||||
const contextService = inject(ContextService);
|
||||
const router = inject(Router);
|
||||
const location: Location = inject(Location);
|
||||
const sessionStorageService = inject(SessionStorageService);
|
||||
|
||||
try {
|
||||
router.events
|
||||
.pipe(
|
||||
filter((event) => event instanceof NavigationEnd),
|
||||
take(1)
|
||||
)
|
||||
.subscribe(async () => {
|
||||
if (contextService.isModeratorParticipant()) {
|
||||
const roomId = contextService.getRoomId();
|
||||
const { moderatorSecret, publisherSecret } = await getUrlSecret(httpService, roomId);
|
||||
|
||||
sessionStorageService.setModeratorSecret(roomId, moderatorSecret);
|
||||
// Replace secret in URL by the publisher secret
|
||||
const queryParams = { ...route.queryParams, secret: publisherSecret };
|
||||
const urlTree = router.createUrlTree([], { queryParams, queryParamsHandling: 'merge' });
|
||||
const newUrl = router.serializeUrl(urlTree);
|
||||
|
||||
location.replaceState(newUrl);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('error', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Guard that intercepts navigation to remove the 'secret' query parameter from the URL
|
||||
* when a moderator participant is detected. The secret is stored in session storage
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
import { inject } from '@angular/core';
|
||||
import { CanActivateFn, RedirectCommand } from '@angular/router';
|
||||
import { Router } from '@angular/router';
|
||||
import { ContextService } from '../services';
|
||||
|
||||
export const checkParticipantNameGuard: CanActivateFn = (_route, state) => {
|
||||
const router = inject(Router);
|
||||
const contextService = inject(ContextService);
|
||||
const roomId = contextService.getRoomId();
|
||||
const hasParticipantName = !!contextService.getParticipantName();
|
||||
|
||||
// Check if participant name exists in the service
|
||||
if (!hasParticipantName) {
|
||||
// Redirect to a page where the participant can input their participant name
|
||||
const participantNameRoute = router.createUrlTree([`room/${roomId}/participant-name`], {
|
||||
queryParams: { originUrl: state.url, t: Date.now() }
|
||||
});
|
||||
return new RedirectCommand(participantNameRoute, {
|
||||
skipLocationChange: true
|
||||
});
|
||||
}
|
||||
|
||||
// Proceed if the name exists
|
||||
return true;
|
||||
};
|
||||
@ -1,64 +0,0 @@
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
CanActivateFn,
|
||||
UrlTree,
|
||||
RedirectCommand
|
||||
} from '@angular/router';
|
||||
import { ContextService, HttpService, SessionStorageService } from '../services';
|
||||
|
||||
/**
|
||||
* Guard to validate the access to a room.
|
||||
*/
|
||||
export const validateRoomAccessGuard: CanActivateFn = async (
|
||||
_route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot
|
||||
) => {
|
||||
const httpService = inject(HttpService);
|
||||
const contextService = inject(ContextService);
|
||||
const router = inject(Router);
|
||||
const sessionStorageService = inject(SessionStorageService);
|
||||
|
||||
const roomId = contextService.getRoomId();
|
||||
const participantName = contextService.getParticipantName();
|
||||
const secret = contextService.getSecret();
|
||||
const storageSecret = sessionStorageService.getModeratorSecret(roomId);
|
||||
|
||||
try {
|
||||
// Generate a participant token
|
||||
const response = await httpService.generateParticipantToken({
|
||||
roomId,
|
||||
participantName,
|
||||
secret: storageSecret || secret
|
||||
});
|
||||
contextService.setParticipantToken(response.token);
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
console.error('Error generating participant token:', error);
|
||||
switch (error.status) {
|
||||
case 400:
|
||||
// Invalid secret
|
||||
return redirectToErrorPage(router, 'invalid-secret');
|
||||
case 404:
|
||||
// Room not found
|
||||
return redirectToErrorPage(router, 'invalid-room');
|
||||
case 409:
|
||||
// Participant already exists.
|
||||
// Send a timestamp to force update the query params and show the error message in participant name input form
|
||||
const participantNameRoute = router.createUrlTree([`room/${roomId}/participant-name`], {
|
||||
queryParams: { originUrl: state.url, accessError: 'participant-exists', t: Date.now() }
|
||||
});
|
||||
return new RedirectCommand(participantNameRoute, {
|
||||
skipLocationChange: true
|
||||
});
|
||||
default:
|
||||
return redirectToErrorPage(router, 'internal-error');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const redirectToErrorPage = (router: Router, reason: string): UrlTree => {
|
||||
return router.createUrlTree(['error'], { queryParams: { reason } });
|
||||
};
|
||||
@ -10,6 +10,5 @@ export * from './console/security-preferences/security-preferences.component';
|
||||
export * from './disconnected/disconnected.component';
|
||||
export * from './error/error.component';
|
||||
export * from './login/login.component';
|
||||
export * from './participant-name-form/participant-name-form.component';
|
||||
export * from './room-recordings/room-recordings.component';
|
||||
export * from './video-room/video-room.component';
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
<div class="form-container">
|
||||
<mat-card class="form-card">
|
||||
<h2 class="form-title">What is your name?</h2>
|
||||
<form [formGroup]="participantForm" (ngSubmit)="onSubmit()">
|
||||
<mat-form-field appearance="outline" class="full-width">
|
||||
<mat-label>Name</mat-label>
|
||||
<input id="participant-name-input" matInput placeholder="Enter your name" formControlName="name" required />
|
||||
@if (name?.hasError('minlength')) {
|
||||
<mat-error> The name must be at least <strong>4 characters</strong> </mat-error>
|
||||
}
|
||||
@if (name?.hasError('required')) {
|
||||
<mat-error> The name is <strong>required</strong> </mat-error>
|
||||
}
|
||||
@if (name?.hasError('participantExists')) {
|
||||
<mat-error> The name is already taken. <strong> Please choose another name </strong> </mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
<button mat-raised-button color="primary" id="participant-name-submit" class="full-width" [disabled]="participantForm.invalid">
|
||||
Continue
|
||||
</button>
|
||||
</form>
|
||||
</mat-card>
|
||||
</div>
|
||||
@ -1,36 +0,0 @@
|
||||
.form-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background-color: var(--ov-background-color);
|
||||
}
|
||||
|
||||
.form-card {
|
||||
width: 400px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||
border-radius: var(--ov-surface-radius);
|
||||
background-color: var(--ov-surface-color);
|
||||
}
|
||||
|
||||
.form-title {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
color: var(--ov-text-surface-color);
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
button {
|
||||
height: 56px;
|
||||
border-radius: var(--ov-surface-radius);
|
||||
}
|
||||
|
||||
button:not([disabled]) {
|
||||
background-color: var(--ov-accent-action-color);
|
||||
color: var(--ov-text-primary-color);
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ParticipantNameFormComponent } from './participant-name-form.component';
|
||||
|
||||
describe('ParticipantNameFormComponent', () => {
|
||||
let component: ParticipantNameFormComponent;
|
||||
let fixture: ComponentFixture<ParticipantNameFormComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ParticipantNameFormComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ParticipantNameFormComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -1,74 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormGroup, Validators, FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { AuthService } from 'shared-meet-components';
|
||||
|
||||
@Component({
|
||||
selector: 'ov-participant-name-form',
|
||||
standalone: true,
|
||||
imports: [
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatCardModule,
|
||||
MatButtonModule
|
||||
// MatIconModule
|
||||
],
|
||||
templateUrl: './participant-name-form.component.html',
|
||||
styleUrl: './participant-name-form.component.scss'
|
||||
})
|
||||
export class ParticipantNameFormComponent implements OnInit {
|
||||
participantForm = new FormGroup({
|
||||
name: new FormControl('', [Validators.required, Validators.minLength(4)])
|
||||
});
|
||||
protected originUrl: string = '';
|
||||
protected error = '';
|
||||
|
||||
constructor(
|
||||
protected router: Router,
|
||||
protected route: ActivatedRoute,
|
||||
protected authService: AuthService
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.route.queryParams.subscribe((params) => {
|
||||
if (params['originUrl']) {
|
||||
this.originUrl = params['originUrl'];
|
||||
this.error = params['accessError'];
|
||||
this.applyErrorToForm();
|
||||
}
|
||||
});
|
||||
|
||||
// Set the username if authenticated as default value
|
||||
const username = await this.authService.getUsername();
|
||||
if (username) {
|
||||
this.participantForm.get('name')?.setValue(username);
|
||||
}
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.participantForm.get('name');
|
||||
}
|
||||
|
||||
async onSubmit() {
|
||||
if (this.participantForm.valid) {
|
||||
const participantName = this.participantForm.value.name;
|
||||
|
||||
let urlTree = this.router.parseUrl(this.originUrl);
|
||||
urlTree.queryParams = { ...urlTree.queryParams, 'participant-name': participantName };
|
||||
|
||||
await this.router.navigateByUrl(urlTree);
|
||||
}
|
||||
}
|
||||
|
||||
private applyErrorToForm() {
|
||||
if (this.error === 'participant-exists') {
|
||||
this.participantForm.get('name')?.setErrors({ participantExists: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,45 @@
|
||||
@if (!serverError && !loading) {
|
||||
@if (!showRoom) {
|
||||
<div class="form-container">
|
||||
<mat-card class="form-card">
|
||||
<h2 class="form-title">What is your name?</h2>
|
||||
<form [formGroup]="participantForm" (ngSubmit)="accessRoom()">
|
||||
<mat-form-field appearance="outline" class="full-width">
|
||||
<mat-label>Name</mat-label>
|
||||
<input
|
||||
id="participant-name-input"
|
||||
matInput
|
||||
placeholder="Enter your name"
|
||||
formControlName="name"
|
||||
required
|
||||
/>
|
||||
@if (participantForm.get('name')?.hasError('minlength')) {
|
||||
<mat-error> The name must be at least <strong>4 characters</strong> </mat-error>
|
||||
}
|
||||
@if (participantForm.get('name')?.hasError('required')) {
|
||||
<mat-error> The name is <strong>required</strong> </mat-error>
|
||||
}
|
||||
@if (participantForm.get('name')?.hasError('participantExists')) {
|
||||
<mat-error>
|
||||
The name is already taken. <strong> Please choose another name </strong>
|
||||
</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
id="participant-name-submit"
|
||||
class="full-width"
|
||||
[disabled]="participantForm.invalid"
|
||||
>
|
||||
Continue
|
||||
</button>
|
||||
</form>
|
||||
</mat-card>
|
||||
</div>
|
||||
} @else {
|
||||
<ov-videoconference
|
||||
[token]="token"
|
||||
[tokenError]="serverError"
|
||||
[token]="participantToken"
|
||||
[participantName]="participantName"
|
||||
[prejoin]="featureFlags.showPrejoin"
|
||||
[prejoinDisplayParticipantName]="false"
|
||||
@ -17,21 +55,11 @@
|
||||
[toolbarActivitiesPanelButton]="true"
|
||||
[activitiesPanelRecordingActivity]="true"
|
||||
[activitiesPanelBroadcastingActivity]="false"
|
||||
(onTokenRequested)="onTokenRequested($event)"
|
||||
(onTokenRequested)="onTokenRequested()"
|
||||
(onParticipantConnected)="onParticipantConnected($event)"
|
||||
(onParticipantLeft)="onParticipantLeft($event)"
|
||||
(onRecordingStartRequested)="onRecordingStartRequested($event)"
|
||||
(onRecordingStopRequested)="onRecordingStopRequested($event)"
|
||||
(onRecordingDeleteRequested)="onRecordingDeleteRequested($event)"
|
||||
>
|
||||
</ov-videoconference>
|
||||
}
|
||||
|
||||
@if (!loading && serverError) {
|
||||
<div class="error">
|
||||
<mat-icon>error</mat-icon>
|
||||
<span>
|
||||
{{ serverError }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@ -1,9 +1,36 @@
|
||||
.error {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
mat-icon {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
.form-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background-color: var(--ov-background-color);
|
||||
}
|
||||
|
||||
.form-card {
|
||||
width: 400px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||
border-radius: var(--ov-surface-radius);
|
||||
background-color: var(--ov-surface-color);
|
||||
}
|
||||
|
||||
.form-title {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
color: var(--ov-text-surface-color);
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
button {
|
||||
height: 56px;
|
||||
border-radius: var(--ov-surface-radius);
|
||||
}
|
||||
|
||||
button:not([disabled]) {
|
||||
background-color: var(--ov-accent-action-color);
|
||||
color: var(--ov-text-primary-color);
|
||||
}
|
||||
|
||||
@ -1,53 +1,74 @@
|
||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { Location } from '@angular/common';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { MeetRecordingAccess, MeetRoomPreferences, OpenViduMeetPermissions, ParticipantRole } from '@lib/typings/ce';
|
||||
import {
|
||||
RecordingDeleteRequestedEvent,
|
||||
RecordingStartRequestedEvent,
|
||||
RecordingStopRequestedEvent,
|
||||
ApiDirectiveModule,
|
||||
ParticipantLeftEvent,
|
||||
ParticipantModel,
|
||||
OpenViduComponentsUiModule,
|
||||
ParticipantLeftReason
|
||||
ParticipantLeftEvent,
|
||||
ParticipantLeftReason,
|
||||
ParticipantModel,
|
||||
RecordingStartRequestedEvent,
|
||||
RecordingStopRequestedEvent
|
||||
} from 'openvidu-components-angular';
|
||||
|
||||
import {
|
||||
MeetChatPreferences,
|
||||
MeetRecordingAccess,
|
||||
MeetRecordingPreferences,
|
||||
MeetVirtualBackgroundPreferences
|
||||
} from '@lib/typings/ce';
|
||||
|
||||
import {
|
||||
HttpService,
|
||||
WebComponentManagerService,
|
||||
ContextService,
|
||||
RoomService,
|
||||
SessionStorageService
|
||||
} from '../../services';
|
||||
import { OutboundEventMessage } from 'webcomponent/src/models/message.type';
|
||||
import { WebComponentEvent } from 'webcomponent/src/models/event.model';
|
||||
import { OutboundEventMessage } from 'webcomponent/src/models/message.type';
|
||||
import {
|
||||
AuthService,
|
||||
ContextService,
|
||||
HttpService,
|
||||
RoomService,
|
||||
SessionStorageService,
|
||||
WebComponentManagerService
|
||||
} from '../../services';
|
||||
|
||||
@Component({
|
||||
selector: 'app-video-room',
|
||||
templateUrl: './video-room.component.html',
|
||||
styleUrls: ['./video-room.component.scss'],
|
||||
standalone: true,
|
||||
imports: [OpenViduComponentsUiModule, ApiDirectiveModule, MatIcon]
|
||||
imports: [
|
||||
OpenViduComponentsUiModule,
|
||||
ApiDirectiveModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatCardModule,
|
||||
MatButtonModule
|
||||
]
|
||||
})
|
||||
export class VideoRoomComponent implements OnInit, OnDestroy {
|
||||
participantForm = new FormGroup({
|
||||
name: new FormControl('', [Validators.required, Validators.minLength(4)])
|
||||
});
|
||||
showRoom = false;
|
||||
|
||||
roomId = '';
|
||||
roomSecret = '';
|
||||
participantName = '';
|
||||
token = '';
|
||||
serverError = '';
|
||||
loading = true;
|
||||
chatPreferences: MeetChatPreferences = { enabled: true };
|
||||
recordingPreferences: MeetRecordingPreferences = {
|
||||
enabled: true,
|
||||
allowAccessTo: MeetRecordingAccess.ADMIN_MODERATOR_PUBLISHER
|
||||
participantToken = '';
|
||||
participantRole: ParticipantRole = ParticipantRole.PUBLISHER;
|
||||
participantPermissions: OpenViduMeetPermissions = {
|
||||
canRecord: false,
|
||||
canChat: false,
|
||||
canChangeVirtualBackground: false,
|
||||
canPublishScreen: false
|
||||
};
|
||||
|
||||
roomPreferences: MeetRoomPreferences = {
|
||||
recordingPreferences: {
|
||||
enabled: true,
|
||||
allowAccessTo: MeetRecordingAccess.ADMIN_MODERATOR_PUBLISHER
|
||||
},
|
||||
chatPreferences: { enabled: true },
|
||||
virtualBackgroundPreferences: { enabled: true }
|
||||
};
|
||||
virtualBackgroundPreferences: MeetVirtualBackgroundPreferences = { enabled: true };
|
||||
featureFlags = {
|
||||
videoEnabled: true,
|
||||
audioEnabled: true,
|
||||
@ -63,56 +84,132 @@ export class VideoRoomComponent implements OnInit, OnDestroy {
|
||||
constructor(
|
||||
protected httpService: HttpService,
|
||||
protected router: Router,
|
||||
protected route: ActivatedRoute,
|
||||
protected location: Location,
|
||||
protected authService: AuthService,
|
||||
protected ctxService: ContextService,
|
||||
protected roomService: RoomService,
|
||||
protected wcManagerService: WebComponentManagerService,
|
||||
protected sessionStorageService: SessionStorageService,
|
||||
protected cdr: ChangeDetectorRef
|
||||
protected sessionStorageService: SessionStorageService
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
try {
|
||||
this.roomId = this.ctxService.getRoomId();
|
||||
this.participantName = this.ctxService.getParticipantName();
|
||||
this.roomId = this.ctxService.getRoomId();
|
||||
const secret = this.ctxService.getSecret();
|
||||
const storageSecret = this.sessionStorageService.getModeratorSecret(this.roomId);
|
||||
this.roomSecret = storageSecret || secret;
|
||||
|
||||
if (this.ctxService.isEmbeddedMode()) {
|
||||
this.featureFlags.showPrejoin = false;
|
||||
}
|
||||
// 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;
|
||||
|
||||
// TODO: Apply room preferences from saved room using context service
|
||||
// await this.loadRoomPreferences();
|
||||
|
||||
// TODO: Extract permissions from token and apply them to the component
|
||||
this.applyParticipantPermissions();
|
||||
} catch (error: any) {
|
||||
console.error('Error fetching room preferences', error);
|
||||
this.serverError = error.error.message || error.message || error.error;
|
||||
if (participantName) {
|
||||
this.participantForm.get('name')?.setValue(participantName);
|
||||
}
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
// Clean up the context service
|
||||
// this.contextService.clearContext();
|
||||
this.wcManagerService.stopCommandsListener();
|
||||
}
|
||||
|
||||
async onTokenRequested(participantName: string) {
|
||||
try {
|
||||
if (this.ctxService.isStandaloneMode()) {
|
||||
// As token is not provided, we need to set the participant name from
|
||||
// ov-videoconference event
|
||||
this.ctxService.setParticipantName(participantName);
|
||||
}
|
||||
|
||||
this.token = this.ctxService.getParticipantToken();
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
this.serverError = error.error;
|
||||
async accessRoom() {
|
||||
if (!this.participantForm.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
this.cdr.detectChanges();
|
||||
this.participantName = this.participantForm.value.name!;
|
||||
|
||||
try {
|
||||
await this.generateParticipantToken();
|
||||
await this.replaceUrlQueryParams();
|
||||
// await this.loadRoomPreferences();
|
||||
this.applyParticipantPermissions();
|
||||
this.showRoom = true;
|
||||
} catch (error) {
|
||||
console.error('Error accessing room:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async onTokenRequested() {
|
||||
// Participant token must be set only when requested
|
||||
this.participantToken = this.ctxService.getParticipantToken();
|
||||
}
|
||||
|
||||
private async generateParticipantToken() {
|
||||
try {
|
||||
const response = await this.httpService.generateParticipantToken({
|
||||
roomId: this.roomId,
|
||||
participantName: this.participantName,
|
||||
secret: this.roomSecret
|
||||
});
|
||||
this.setParticipantToken(response.token);
|
||||
} catch (error: any) {
|
||||
console.error('Error generating participant token:', error);
|
||||
switch (error.status) {
|
||||
case 400:
|
||||
// Invalid secret
|
||||
this.redirectToErrorPage('invalid-secret');
|
||||
break;
|
||||
case 404:
|
||||
// Room not found
|
||||
this.redirectToErrorPage('invalid-room');
|
||||
break;
|
||||
case 409:
|
||||
// Participant already exists.
|
||||
// Show the error message in participant name input form
|
||||
this.participantForm.get('name')?.setErrors({ participantExists: true });
|
||||
throw new Error('Participant already exists in the room');
|
||||
default:
|
||||
this.redirectToErrorPage('internal-error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
let secretQueryParam = this.roomSecret;
|
||||
|
||||
// If participant is moderator, store the moderator secret in session storage
|
||||
// and replace the secret in the URL with the publisher secret
|
||||
if (this.participantRole === ParticipantRole.MODERATOR) {
|
||||
try {
|
||||
const { moderatorSecret, publisherSecret } = await this.getRoomSecrets();
|
||||
this.sessionStorageService.setModeratorSecret(this.roomId, moderatorSecret);
|
||||
secretQueryParam = publisherSecret;
|
||||
} catch (error) {
|
||||
console.error('error', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Replace secret and participant name in the URL query parameters
|
||||
const queryParams = {
|
||||
...this.route.snapshot.queryParams,
|
||||
secret: secretQueryParam,
|
||||
'participant-name': this.participantName
|
||||
};
|
||||
const urlTree = this.router.createUrlTree([], { queryParams, queryParamsHandling: 'merge' });
|
||||
const newUrl = this.router.serializeUrl(urlTree);
|
||||
this.location.replaceState(newUrl);
|
||||
}
|
||||
|
||||
private async getRoomSecrets(): Promise<{ moderatorSecret: string; publisherSecret: string }> {
|
||||
const { moderatorRoomUrl, publisherRoomUrl } = await this.httpService.getRoom(this.roomId);
|
||||
|
||||
const publisherUrl = new URL(publisherRoomUrl);
|
||||
const publisherSecret = publisherUrl.searchParams.get('secret') || '';
|
||||
const moderatorUrl = new URL(moderatorRoomUrl);
|
||||
const moderatorSecret = moderatorUrl.searchParams.get('secret') || '';
|
||||
return { publisherSecret, moderatorSecret };
|
||||
}
|
||||
|
||||
onParticipantConnected(event: ParticipantModel) {
|
||||
@ -158,11 +255,7 @@ export class VideoRoomComponent implements OnInit, OnDestroy {
|
||||
this.sessionStorageService.removeModeratorSecret(event.roomName);
|
||||
}
|
||||
|
||||
//if (this.contextService.isEmbeddedMode()) this.sendMessageToParent(event);
|
||||
this.redirectTo(redirectURL, isExternalURL);
|
||||
|
||||
// Stop listening to commands from the parent
|
||||
this.wcManagerService.stopCommandsListener();
|
||||
}
|
||||
|
||||
async onRecordingStartRequested(event: RecordingStartRequestedEvent) {
|
||||
@ -186,18 +279,6 @@ export class VideoRoomComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
async onRecordingDeleteRequested(event: RecordingDeleteRequestedEvent) {
|
||||
try {
|
||||
const { recordingId } = event;
|
||||
|
||||
if (!recordingId) throw new Error('Recording ID not found when deleting recording');
|
||||
|
||||
await this.httpService.deleteRecording(recordingId);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the room preferences from the global preferences service and assigns them to the component.
|
||||
*
|
||||
@ -208,26 +289,26 @@ export class VideoRoomComponent implements OnInit, OnDestroy {
|
||||
* @returns {Promise<void>} A promise that resolves when the room preferences have been loaded and applied.
|
||||
*/
|
||||
private async loadRoomPreferences() {
|
||||
const preferences = await this.roomService.getRoomPreferences();
|
||||
// Assign the preferences to the component
|
||||
Object.assign(this, preferences);
|
||||
try {
|
||||
this.roomPreferences = await this.roomService.getRoomPreferences();
|
||||
} catch (error) {
|
||||
console.error('Error loading room preferences:', error);
|
||||
}
|
||||
|
||||
this.featureFlags.showChat = this.chatPreferences.enabled;
|
||||
this.featureFlags.showRecording = this.recordingPreferences.enabled;
|
||||
this.featureFlags.showBackgrounds = this.virtualBackgroundPreferences.enabled;
|
||||
this.featureFlags.showChat = this.roomPreferences.chatPreferences.enabled;
|
||||
this.featureFlags.showRecording = this.roomPreferences.recordingPreferences.enabled;
|
||||
this.featureFlags.showBackgrounds = this.roomPreferences.virtualBackgroundPreferences.enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the feature flags based on the token permissions.
|
||||
*
|
||||
* This method checks the token permissions and sets the feature flags accordingly.
|
||||
* Configures the feature flags based on participant permissions.
|
||||
*/
|
||||
private applyParticipantPermissions() {
|
||||
if (this.featureFlags.showChat) {
|
||||
this.featureFlags.showChat = this.ctxService.canChat();
|
||||
this.featureFlags.showChat = this.participantPermissions.canChat;
|
||||
}
|
||||
if (this.featureFlags.showRecording) {
|
||||
this.featureFlags.showRecording = this.ctxService.canRecord();
|
||||
this.featureFlags.showRecording = this.participantPermissions.canRecord;
|
||||
}
|
||||
}
|
||||
|
||||
@ -240,4 +321,8 @@ export class VideoRoomComponent implements OnInit, OnDestroy {
|
||||
this.router.navigate([url], { replaceUrl: true });
|
||||
}
|
||||
}
|
||||
|
||||
private redirectToErrorPage(reason: string) {
|
||||
this.router.navigate(['error'], { queryParams: { reason } });
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +1,14 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import {
|
||||
applicationModeGuard,
|
||||
checkParticipantNameGuard,
|
||||
checkParticipantRoleAndAuthGuard,
|
||||
checkUserAuthenticatedGuard,
|
||||
checkUserNotAuthenticatedGuard,
|
||||
extractRecordingQueryParamsGuard,
|
||||
extractRoomQueryParamsGuard,
|
||||
removeModeratorSecretGuard,
|
||||
replaceModeratorSecretGuard,
|
||||
runGuardsSerially,
|
||||
validateRecordingAccessGuard,
|
||||
validateRoomAccessGuard
|
||||
validateRecordingAccessGuard
|
||||
} from '../guards';
|
||||
import {
|
||||
ConsoleComponent,
|
||||
@ -19,7 +16,6 @@ import {
|
||||
ErrorComponent,
|
||||
LoginComponent,
|
||||
OverviewComponent,
|
||||
ParticipantNameFormComponent,
|
||||
RecordingsComponent,
|
||||
RoomFormComponent,
|
||||
RoomRecordingsComponent,
|
||||
@ -83,20 +79,9 @@ export const baseRoutes: Routes = [
|
||||
path: 'room/:room-id',
|
||||
component: VideoRoomComponent,
|
||||
canActivate: [
|
||||
runGuardsSerially(
|
||||
applicationModeGuard,
|
||||
extractRoomQueryParamsGuard,
|
||||
checkParticipantRoleAndAuthGuard,
|
||||
checkParticipantNameGuard,
|
||||
validateRoomAccessGuard,
|
||||
replaceModeratorSecretGuard
|
||||
)
|
||||
runGuardsSerially(applicationModeGuard, extractRoomQueryParamsGuard, checkParticipantRoleAndAuthGuard)
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'room/:room-id/participant-name',
|
||||
component: ParticipantNameFormComponent
|
||||
},
|
||||
{
|
||||
path: 'room/:room-id/recordings',
|
||||
component: RoomRecordingsComponent,
|
||||
|
||||
@ -150,12 +150,8 @@ export class ContextService {
|
||||
return this.context.participantRole === ParticipantRole.MODERATOR;
|
||||
}
|
||||
|
||||
canRecord(): boolean {
|
||||
return this.context.participantPermissions.canRecord;
|
||||
}
|
||||
|
||||
canChat(): boolean {
|
||||
return this.context.participantPermissions.canChat;
|
||||
getParticipantPermissions() {
|
||||
return this.context.participantPermissions;
|
||||
}
|
||||
|
||||
setRecordingPermissionsFromToken(token: string): void {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user