frontend: project restructuring by domain

- centralize HTTP interceptor error handling via registry and handlers
This commit is contained in:
Carlos Santos 2026-01-12 10:25:39 +01:00
parent c42a3ce1cf
commit dac64bb1a9
229 changed files with 943 additions and 583 deletions

View File

@ -1,25 +0,0 @@
export * from './console-nav/console-nav.component';
export * from './dialogs/basic-dialog/dialog.component';
export * from './dialogs/delete-room-dialog/delete-room-dialog.component';
export * from './dialogs/share-recording-dialog/share-recording-dialog.component';
export * from './logo-selector/logo-selector.component';
export * from './pro-feature-badge/pro-feature-badge.component';
export * from './recording-lists/recording-lists.component';
export * from './recording-video-player/recording-video-player.component';
export * from './rooms-lists/rooms-lists.component';
export * from './selectable-card/selectable-card.component';
export * from './share-meeting-link/share-meeting-link.component';
export * from './spinner/spinner.component';
export * from './step-indicator/step-indicator.component';
export * from './wizard-nav/wizard-nav.component';
// Meeting modular components
export * from './meeting-lobby/meeting-lobby.component';
export * from './meeting-share-link-overlay/meeting-share-link-overlay.component';
// Meeting components
export * from './hidden-participants-indicator/hidden-participants-indicator.component';
export * from './meeting-lobby/meeting-lobby.component';
export * from './meeting-share-link-overlay/meeting-share-link-overlay.component';

View File

@ -1 +0,0 @@
export * from './components/index';

View File

@ -0,0 +1,4 @@
export * from './interceptor-handlers';
export * from './pages';
export * from './services';

View File

@ -0,0 +1,46 @@
# Interceptor Handlers - Auth Domain
Este directorio contiene los handlers que gestionan la lógica de dominio relacionada con errores HTTP de autenticación.
## Propósito
Separar la lógica de negocio del interceptor HTTP principal, siguiendo el principio de responsabilidad única y la arquitectura de dominios.
## Componentes
### `AuthErrorHandlerService`
Servicio responsable de manejar errores 401 relacionados con tokens de acceso expirados.
**Responsabilidades:**
- Refrescar el access token cuando expira
- Reintrentar la petición original con el nuevo token
- Manejar errores de refresh token (logout si es necesario)
**NO es responsable de:**
- Decidir cuándo debe ejecutarse (eso lo hace el interceptor)
- Conocer sobre tokens de room members (eso es del dominio rooms)
- Agregar headers a las peticiones (eso lo hace el interceptor)
## Arquitectura
```
HTTP Interceptor (detecta error 401)
Notifica → HttpErrorNotifierService
Decide qué handler ejecutar según contexto
AuthErrorHandlerService.createRetryStrategy()
Ejecuta lógica de dominio (refresh token)
Retorna Observable para reintentar la petición
```
## Uso
El servicio se inyecta automáticamente en el interceptor HTTP y se invoca cuando:
- Se recibe un error 401
- No es un error de token de room member
- No estamos en la página de login O existe un refresh token disponible

View File

@ -0,0 +1,108 @@
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 { TokenStorageService } from '../../../shared/services/token-storage.service';
import { AuthService } from '../services/auth.service';
/**
* Handler for authentication-related HTTP errors.
* Registers itself with HttpErrorNotifierService to autonomously handle access token refresh.
* The interceptor doesn't know about this service - it discovers itself via registration.
*/
@Injectable({
providedIn: 'root'
})
export class AuthInterceptorErrorHandlerService implements HttpErrorHandler {
private readonly authService = inject(AuthService);
private readonly tokenStorageService = inject(TokenStorageService);
private readonly router = inject(Router);
private readonly httpErrorNotifier = inject(HttpErrorNotifierService);
/**
* Registers this handler with the error notifier service
*/
init(): void {
this.httpErrorNotifier.register(this);
}
/**
* Determines if this handler can handle the given error context
*/
canHandle(context: HttpErrorContext): boolean {
const { error, pageUrl } = context;
// Only handle 401 errors
if (error.status !== 401) {
return false;
}
// Don't handle if it's already a token refresh endpoint error (avoid infinite loop)
if (error.url?.includes('/auth/refresh')) {
return false;
}
// Special case: room member token generation failed, need to refresh access token first
if (error.url?.includes('/token')) {
return true;
}
// Handle if not on login page OR if there's a refresh token available
return !pageUrl.startsWith('/login') || !!this.tokenStorageService.getRefreshToken();
}
/**
* Handles the error and returns a recovery Observable
*/
handle(context: HttpErrorContext): Observable<HttpEvent<unknown>> {
const { error } = context;
// Special case: room member token generation failed
if (error.url?.includes('/token')) {
console.log('Generating room member token failed. Refreshing access token first...');
}
return this.refreshAccessToken(context);
}
/**
* Refreshes the access token and retries the original request
*/
private refreshAccessToken(context: HttpErrorContext): Observable<HttpEvent<unknown>> {
const { request: originalRequest, error: originalError, pageUrl, next } = context;
console.log('Refreshing access token...');
return from(this.authService.refreshToken()).pipe(
switchMap(() => {
console.log('Access token refreshed');
// Update the request with the new token
const newToken = this.tokenStorageService.getAccessToken();
const updatedRequest = newToken
? originalRequest.clone({
setHeaders: {
authorization: `Bearer ${newToken}`
}
})
: originalRequest;
return next(updatedRequest);
}),
catchError(async (error: HttpErrorResponse) => {
if (error.url?.includes('/auth/refresh')) {
console.error('Error refreshing access token');
// If the original request was not to the profile endpoint, logout and redirect to the login page
if (!originalRequest.url.includes('/profile')) {
console.log('Logging out...');
await this.authService.logout(pageUrl);
}
throw originalError;
}
throw error;
})
);
}
}

View File

@ -0,0 +1 @@
export * from './auth-error-handler.service';

View File

@ -0,0 +1 @@
export * from './login/login.component';

View File

@ -1,4 +1,4 @@
@use '../../../../../../src/assets/styles/design-tokens';
@use '../../../../../../../../src/assets/styles/design-tokens';
// Login Page Container - Full height centered layout
.ov-page-container {

View File

@ -8,7 +8,8 @@ import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatTooltipModule } from '@angular/material/tooltip';
import { ActivatedRoute, RouterModule } from '@angular/router';
import { AuthService, NavigationService } from '../../services';
import { NavigationService } from '../../../../shared';
import { AuthService } from '../../services/auth.service';
@Component({
selector: 'ov-login',

View File

@ -1,7 +1,7 @@
import { HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { MeetUserDTO, MeetUserRole } from '@openvidu-meet/typings';
import { HttpService, NavigationService, TokenStorageService } from '../services';
import { HttpService, NavigationService, TokenStorageService } from '../../../shared/services';
@Injectable({
providedIn: 'root'
@ -9,7 +9,6 @@ import { HttpService, NavigationService, TokenStorageService } from '../services
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;

View File

@ -0,0 +1 @@
export * from './auth.service';

View File

@ -1,4 +1,4 @@
@use '../../../../../../src/assets/styles/design-tokens';
@use '../../../../../../../../src/assets/styles/design-tokens';
#dashboard-container,
mat-sidenav-container {

View File

@ -7,8 +7,7 @@ import { MatSidenav, MatSidenavModule } from '@angular/material/sidenav';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTooltipModule } from '@angular/material/tooltip';
import { RouterModule } from '@angular/router';
import { ConsoleNavLink } from '../../models';
import { AppDataService, ThemeService } from '../../services';
import { AppDataService, ConsoleNavLink, ThemeService } from '../../../../shared';
@Component({
selector: 'ov-console-nav',
@ -41,7 +40,7 @@ export class ConsoleNavComponent {
private appDataService: AppDataService,
private themeService: ThemeService
) {
this.version = `v${this.appDataService.getVersion()} (${this.appDataService.getEdition()})`;
this.version = `v${this.appDataService.version()} (${this.appDataService.edition()})`;
this.isDarkMode = this.themeService.isDark;
}

View File

@ -0,0 +1 @@
export * from './console-nav/console-nav.component';

View File

@ -1,7 +1,7 @@
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { ProFeatureBadgeComponent } from '../../components';
import { ProFeatureBadgeComponent } from '../../../../shared';
@Component({
selector: 'ov-logo-selector',

View File

@ -0,0 +1,3 @@
export * from './components';
export * from './pages';

View File

@ -1,4 +1,4 @@
@use '../../../../../../../src/assets/styles/design-tokens';
@use '../../../../../../../../src/assets/styles/design-tokens';
.ov-page-container {
button {

View File

@ -12,8 +12,8 @@ import { MatSlideToggleChange, MatSlideToggleModule } from '@angular/material/sl
import { MatTooltipModule } from '@angular/material/tooltip';
import { MeetAppearanceConfig, MeetRoomTheme, MeetRoomThemeMode } from '@openvidu-meet/typings';
import { OPENVIDU_COMPONENTS_DARK_THEME, OPENVIDU_COMPONENTS_LIGHT_THEME } from 'openvidu-components-angular';
import { ColorField, ThemeColors } from '../../../models';
import { GlobalConfigService, NotificationService } from '../../../services';
import { ColorField, ThemeColors } from '../../../../shared/models';
import { GlobalConfigService, NotificationService } from '../../../../shared/services';
@Component({
selector: 'ov-config',

View File

@ -1,5 +1,5 @@
@use '../../../../../../src/assets/styles/design-tokens';
@use '../../../../../../../../src/assets/styles/design-tokens';
::ng-deep {
mat-slide-toggle {

View File

@ -1,7 +1,7 @@
import { Component } from '@angular/core';
import { ConsoleNavLink } from '../../../../shared';
import { AuthService } from '../../../auth/services/auth.service';
import { ConsoleNavComponent } from '../../components';
import { ConsoleNavLink } from '../../models';
import { AuthService } from '../../services';
@Component({
selector: 'ov-console',

View File

@ -1,4 +1,4 @@
@use '../../../../../../../src/assets/styles/design-tokens';
@use '../../../../../../../../src/assets/styles/design-tokens';
.ov-page-container {
button {

View File

@ -9,8 +9,8 @@ import { MatInputModule } from '@angular/material/input';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatTooltipModule } from '@angular/material/tooltip';
import { ApiKeyService, GlobalConfigService, NotificationService } from '../../../services';
import { MeetApiKey } from '@openvidu-meet/typings';
import { ApiKeyService, GlobalConfigService, NotificationService } from '../../../../shared';
@Component({
selector: 'ov-embedded',

View File

@ -1,4 +1,4 @@
@use '../../../../../../src/assets/styles/design-tokens';
@use '../../../../../../../../src/assets/styles/design-tokens';
.error-page {
@include design-tokens.ov-theme-transition;

View File

@ -3,8 +3,10 @@ import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
import { ActivatedRoute } from '@angular/router';
import { ErrorReason } from '../../models';
import { AppDataService, AuthService, NavigationService, WebComponentManagerService } from '../../services';
import { AppDataService, NavigationService, } from '../../../../shared';
import { NavigationErrorReason } from '../../../../shared/models/navigation.model';
import { AuthService } from '../../../auth/services/auth.service';
import { MeetingWebComponentManagerService } from '../../../meeting/services';
@Component({
selector: 'ov-error',
@ -24,7 +26,7 @@ export class ErrorComponent implements OnInit {
protected authService: AuthService,
protected navService: NavigationService,
protected appDataService: AppDataService,
protected wcManagerService: WebComponentManagerService
protected wcManagerService: MeetingWebComponentManagerService
) {}
ngOnInit() {
@ -48,51 +50,51 @@ export class ErrorComponent implements OnInit {
* Maps technical error reasons to user-friendly names and messages
*/
private mapReasonToNameAndMessage(reason: string): { title: string; message: string } {
const reasonMap: { [key in ErrorReason]: { title: string; message: string } } = {
[ErrorReason.CLOSED_ROOM]: {
const reasonMap: { [key in NavigationErrorReason]: { title: string; message: string } } = {
[NavigationErrorReason.CLOSED_ROOM]: {
title: 'Closed room',
message: 'The room you are trying to access is closed'
},
[ErrorReason.MISSING_ROOM_SECRET]: {
[NavigationErrorReason.MISSING_ROOM_SECRET]: {
title: 'Invalid link',
message:
'The link you used to access this room is not valid. Please ask the moderator to share the correct link using the share buttons available in the room. Note: Sharing the URL from the browser address bar is not valid'
},
[ErrorReason.MISSING_RECORDING_SECRET]: {
[NavigationErrorReason.MISSING_RECORDING_SECRET]: {
title: 'Invalid link',
message: 'The link you used to access this recording is not valid'
},
[ErrorReason.INVALID_ROOM_SECRET]: {
[NavigationErrorReason.INVALID_ROOM_SECRET]: {
title: 'Invalid link',
message:
'The link you used to access this room is not valid. Please ask the moderator to share the correct link using the share buttons available in the room. Note: Sharing the URL from the browser address bar is not valid'
},
[ErrorReason.INVALID_RECORDING_SECRET]: {
[NavigationErrorReason.INVALID_RECORDING_SECRET]: {
title: 'Invalid link',
message: 'The link you used to access this recording is not valid'
},
[ErrorReason.INVALID_ROOM]: {
[NavigationErrorReason.INVALID_ROOM]: {
title: 'Invalid room',
message: 'The room you are trying to access does not exist or has been deleted'
},
[ErrorReason.INVALID_RECORDING]: {
[NavigationErrorReason.INVALID_RECORDING]: {
title: 'Invalid recording',
message: 'The recording you are trying to access does not exist or has been deleted'
},
[ErrorReason.UNAUTHORIZED_RECORDING_ACCESS]: {
[NavigationErrorReason.UNAUTHORIZED_RECORDING_ACCESS]: {
title: 'Unauthorized recording access',
message: 'You are not authorized to access the recordings in this room'
},
[ErrorReason.INTERNAL_ERROR]: {
[NavigationErrorReason.INTERNAL_ERROR]: {
title: 'Internal error',
message: 'An unexpected error occurred, please try again later'
}
};
const normalizedReason = Object.values(ErrorReason).find((enumValue) => enumValue === reason) as
| ErrorReason
const normalizedReason = Object.values(NavigationErrorReason).find((enumValue) => enumValue === reason) as
| NavigationErrorReason
| undefined;
return reasonMap[normalizedReason ?? ErrorReason.INTERNAL_ERROR];
return reasonMap[normalizedReason ?? NavigationErrorReason.INTERNAL_ERROR];
}
/**

View File

@ -0,0 +1,7 @@
export * from './about/about.component';
export * from './config/config.component';
export * from './console/console.component';
export * from './embedded/embedded.component';
export * from './overview/overview.component';
export * from './users-permissions/users-permissions.component';

View File

@ -1,4 +1,4 @@
@use '../../../../../../../src/assets/styles/design-tokens';
@use '../../../../../../../../src/assets/styles/design-tokens';
// Welcome State Styles
.welcome-content {

View File

@ -4,7 +4,7 @@ import { MatCardModule } from '@angular/material/card';
import { MatGridListModule } from '@angular/material/grid-list';
import { MatIconModule } from '@angular/material/icon';
import { MeetAnalytics } from '@openvidu-meet/typings';
import { AnalyticsService, NavigationService } from '../../../services';
import { AnalyticsService, NavigationService } from '../../../../shared';
@Component({
selector: 'ov-overview',

View File

@ -1,4 +1,4 @@
@use '../../../../../../../src/assets/styles/design-tokens';
@use '../../../../../../../../src/assets/styles/design-tokens';
.form-field-header {
position: relative;

View File

@ -18,8 +18,9 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { MatTooltipModule } from '@angular/material/tooltip';
// import { ProFeatureBadgeComponent } from '../components';
import { AuthService, GlobalConfigService, NotificationService } from '../../../services';
import { AuthMode } from '@openvidu-meet/typings';
import { GlobalConfigService, NotificationService } from '../../../../shared';
import { AuthService } from '../../../auth/services/auth.service';
@Component({
selector: 'ov-users-permissions',

View File

@ -0,0 +1,5 @@
export * from './hidden-participants-indicator/hidden-participants-indicator.component';
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';

View File

@ -1,4 +1,4 @@
@use '../../../../../../src/assets/styles/design-tokens';
@use '../../../../../../../../src/assets/styles/design-tokens';
// Room Access Container - Main layout using design tokens
.room-access-container {

View File

@ -7,8 +7,8 @@ import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { ShareMeetingLinkComponent } from '../../components';
import { MeetingLobbyService } from '../../services/meeting/meeting-lobby.service';
import { MeetingService } from '../../services/meeting/meeting.service';
import { MeetingLobbyService } from '../../services/meeting-lobby.service';
import { MeetingService } from '../../services/meeting.service';
/**
* Reusable component for the meeting lobby page.

View File

@ -1,4 +1,4 @@
@use '../../../../../../src/assets/styles/design-tokens';
@use '../../../../../../../../src/assets/styles/design-tokens';
.main-share-meeting-link-container {
background-color: var(--ov-surface-color); // Use ov-components variable

View File

@ -1,4 +1,4 @@
@use '../../../../../../src/assets/styles/design-tokens';
@use '../../../../../../../../src/assets/styles/design-tokens';
.meeting-url-badge {
margin: var(--ov-meet-spacing-sm) auto;

View File

@ -1,7 +1,8 @@
export * from './meeting-toolbar-leave-button/meeting-toolbar-leave-button.component';
export * from './meeting-toolbar-extra-buttons/meeting-toolbar-extra-buttons.component';
export * from './meeting-custom-layout/meeting-custom-layout.component';
export * from './meeting-invite-panel/meeting-invite-panel.component';
export * from './meeting-participant-item/meeting-participant-item.component';
export * from './meeting-custom-layout/meeting-custom-layout.component';
export * from './meeting-settings-extensions/meeting-settings-extensions.component';
export * from './meeting-toolbar-extra-buttons/meeting-toolbar-extra-buttons.component';
export * from './meeting-toolbar-leave-button/meeting-toolbar-leave-button.component';
export * from './meeting-toolbar-more-options-menu/meeting-toolbar-more-options-menu.component';

View File

@ -1,4 +1,4 @@
@use '../../../../../../../src/assets/styles/design-tokens';
@use '../../../../../../../../src/assets/styles/design-tokens';
.remote-participant {
height: -webkit-fill-available;

View File

@ -1,20 +1,20 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Subject } from 'rxjs';
import { Participant } from 'livekit-client';
import {
ParticipantModel,
LoggerService,
ParticipantService,
OpenViduService
OpenViduService,
ParticipantModel,
ParticipantService
} from 'openvidu-components-angular';
import { Subject } from 'rxjs';
import { MeetLayoutMode } from '../../models/layout.model';
import { MeetingLayoutService } from '../../services/meeting-layout.service';
import { MeetingCustomLayoutComponent } from './meeting-custom-layout.component';
import { MeetLayoutService } from '../../../services/layout.service';
import { MeetLayoutMode } from '../../../models/layout.model';
describe('MeetingLayoutComponent', () => {
let component: MeetingCustomLayoutComponent;
let fixture: ComponentFixture<MeetingCustomLayoutComponent>;
let mockLayoutService: jasmine.SpyObj<MeetLayoutService>;
let mockLayoutService: jasmine.SpyObj<MeetingLayoutService>;
let mockParticipantService: jasmine.SpyObj<ParticipantService>;
let mockOpenViduService: jasmine.SpyObj<OpenViduService>;
let mockLoggerService: jasmine.SpyObj<LoggerService>;
@ -58,7 +58,7 @@ describe('MeetingLayoutComponent', () => {
await TestBed.configureTestingModule({
imports: [MeetingCustomLayoutComponent],
providers: [
{ provide: MeetLayoutService, useValue: mockLayoutService },
{ provide: MeetingLayoutService, useValue: mockLayoutService },
{ provide: ParticipantService, useValue: mockParticipantService },
{ provide: OpenViduService, useValue: mockOpenViduService },
{ provide: LoggerService, useValue: mockLoggerService }

View File

@ -1,9 +1,9 @@
import { CommonModule } from '@angular/common';
import { Component, computed, effect, inject, signal, untracked } from '@angular/core';
import { ILogger, LoggerService, OpenViduComponentsUiModule, PanelService, PanelType, ParticipantModel } from 'openvidu-components-angular';
import { HiddenParticipantsIndicatorComponent, ShareMeetingLinkComponent } from '../../../components';
import { CustomParticipantModel } from '../../../models';
import { MeetingContextService, MeetingService, MeetLayoutService } from '../../../services';
import { HiddenParticipantsIndicatorComponent, ShareMeetingLinkComponent } from '../../components';
import { CustomParticipantModel } from '../../models';
import { MeetingContextService, MeetingLayoutService, MeetingService } from '../../services';
@Component({
selector: 'ov-meeting-custom-layout',
@ -18,7 +18,7 @@ import { MeetingContextService, MeetingService, MeetLayoutService } from '../../
})
export class MeetingCustomLayoutComponent {
private readonly logger: ILogger = inject(LoggerService).get('MeetingCustomLayoutComponent');
protected readonly layoutService = inject(MeetLayoutService);
protected readonly layoutService = inject(MeetingLayoutService);
protected readonly meetingContextService = inject(MeetingContextService);
protected readonly meetingService = inject(MeetingService);
protected readonly panelService = inject(PanelService);

View File

@ -1,9 +1,9 @@
import { CommonModule } from '@angular/common';
import { Component, computed, inject } from '@angular/core';
import { ShareMeetingLinkComponent } from '../../../components/share-meeting-link/share-meeting-link.component';
import { MeetingContextService } from '../../../services/meeting/meeting-context.service';
import { MeetingService } from '../../../services/meeting/meeting.service';
import { LoggerService } from 'openvidu-components-angular';
import { ShareMeetingLinkComponent } from '../../components/share-meeting-link/share-meeting-link.component';
import { MeetingContextService } from '../../services/meeting-context.service';
import { MeetingService } from '../../services/meeting.service';
/**
* Reusable component for displaying the share meeting link panel

View File

@ -3,10 +3,10 @@ import { Component, TemplateRef, ViewChild, inject } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatTooltipModule } from '@angular/material/tooltip';
import { LoggerService, OpenViduComponentsUiModule } from 'openvidu-components-angular';
import { CustomParticipantModel } from '../../../models';
import { MeetingService } from '../../../services/meeting/meeting.service';
import { MeetRoomMemberRole } from '@openvidu-meet/typings';
import { LoggerService, OpenViduComponentsUiModule } from 'openvidu-components-angular';
import { CustomParticipantModel } from '../../models';
import { MeetingService } from '../../services/meeting.service';
/**
* Interface for computed participant display properties

View File

@ -1,15 +1,15 @@
import { Component, computed, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Component, computed, inject } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatListModule } from '@angular/material/list';
import { MatRadioModule } from '@angular/material/radio';
import { MatSliderModule } from '@angular/material/slider';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSelectModule } from '@angular/material/select';
import { FormsModule } from '@angular/forms';
import { MeetLayoutMode } from '../../../models/layout.model';
import { MeetLayoutService } from '../../../services/layout.service';
import { MeetingContextService } from '../../../services/meeting/meeting-context.service';
import { MatSliderModule } from '@angular/material/slider';
import { MeetLayoutMode } from '../../models/layout.model';
import { MeetingContextService } from '../../services/meeting-context.service';
import { MeetingLayoutService } from '../../services/meeting-layout.service';
/**
* Component for additional settings in the Settings Panel.
@ -30,7 +30,7 @@ import { MeetingContextService } from '../../../services/meeting/meeting-context
styleUrl: './meeting-settings-extensions.component.scss'
})
export class MeetingSettingsExtensionsComponent {
private readonly layoutService = inject(MeetLayoutService);
private readonly layoutService = inject(MeetingLayoutService);
protected readonly meetingContextService = inject(MeetingContextService);
/**

View File

@ -1,12 +1,12 @@
import { Component, inject, computed } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatMenuModule } from '@angular/material/menu';
import { Component, computed, inject } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MeetingContextService } from '../../../services/meeting/meeting-context.service';
import { MeetingService } from '../../../services/meeting/meeting.service';
import { LoggerService } from 'openvidu-components-angular';
import { MeetingContextService } from '../../services/meeting-context.service';
import { MeetingService } from '../../services/meeting.service';
/**
* Component for extra toolbar buttons (like copy meeting link).

View File

@ -1,13 +1,13 @@
import { Component, inject, computed } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Component, computed, inject } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatDividerModule } from '@angular/material/divider';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MeetingContextService } from '../../../services/meeting/meeting-context.service';
import { MeetingService } from '../../../services/meeting/meeting.service';
import { LoggerService, OpenViduService } from 'openvidu-components-angular';
import { MeetingContextService } from '../../services/meeting-context.service';
import { MeetingService } from '../../services/meeting.service';
/**
* Reusable component for meeting toolbar Leave button.

View File

@ -1,11 +1,11 @@
import { Component, computed, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatIconModule } from '@angular/material/icon';
import { Component, computed, inject } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatTooltipModule } from '@angular/material/tooltip';
import { PanelService, ViewportService, PanelType } from 'openvidu-components-angular';
import { MeetingContextService } from '../../../services/meeting/meeting-context.service';
import { PanelService, PanelType, ViewportService } from 'openvidu-components-angular';
import { MeetingContextService } from '../../services/meeting-context.service';
/**
* Component for additional menu items in the toolbar's "More Options" menu.

View File

@ -0,0 +1,5 @@
export * from './components';
export * from './customization';
export * from './models';
export * from './pages';
export * from './services';

View File

@ -0,0 +1,3 @@
export * from './custom-participant.model';
export * from './layout.model';
export * from './lobby.model';

View File

@ -1,4 +1,4 @@
@use '../../../../../../../src/assets/styles/design-tokens';
@use '../../../../../../../../src/assets/styles/design-tokens';
.disconnected-container {
@include design-tokens.ov-theme-transition;

View File

@ -4,8 +4,10 @@ import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
import { ActivatedRoute } from '@angular/router';
import { AppDataService, AuthService, NavigationService, WebComponentManagerService } from '../../../services';
import { LeftEventReason } from '@openvidu-meet/typings';
import { AppDataService, NavigationService, } from '../../../../shared';
import { AuthService } from '../../../auth/services/auth.service';
import { MeetingWebComponentManagerService } from '../../services';
@Component({
selector: 'ov-end-meeting',
@ -25,7 +27,7 @@ export class EndMeetingComponent implements OnInit {
protected authService: AuthService,
protected navService: NavigationService,
protected appDataService: AppDataService,
protected wcManagerService: WebComponentManagerService
protected wcManagerService: MeetingWebComponentManagerService
) {}
ngOnInit() {

View File

@ -0,0 +1,4 @@
export * from './end-meeting/end-meeting.component';
export * from './meeting/meeting.component';

View File

@ -1,4 +1,4 @@
@use '../../../../../../src/assets/styles/design-tokens';
@use '../../../../../../../../src/assets/styles/design-tokens';
.prejoin-loading-container,
.prejoin-error-container {

View File

@ -13,20 +13,16 @@ import {
ViewportService
} from 'openvidu-components-angular';
import { Subject } from 'rxjs';
import { MeetingLobbyComponent } from '../../components/meeting-lobby/meeting-lobby.component';
import { MeetingParticipantItemComponent } from '../../customization';
import { ApplicationFeatures } from '../../models/app.model';
import {
ApplicationFeatures,
FeatureConfigurationService,
GlobalConfigService,
MeetingContextService,
MeetingEventHandlerService,
MeetingLobbyService,
MeetingService,
NotificationService,
RoomMemberService,
WebComponentManagerService
} from '../../services';
NotificationService
} from '../../../../shared';
import { RoomMemberService, } from '../../../rooms/services';
import { MeetingLobbyComponent } from '../../components/meeting-lobby/meeting-lobby.component';
import { MeetingParticipantItemComponent } from '../../customization';
import { MeetingContextService, MeetingEventHandlerService, MeetingLobbyService, MeetingService, MeetingWebComponentManagerService } from '../../services';
@Component({
selector: 'ov-meeting',
@ -64,7 +60,7 @@ export class MeetingComponent implements OnInit {
protected meetingService = inject(MeetingService);
protected participantService = inject(RoomMemberService);
protected featureConfService = inject(FeatureConfigurationService);
protected wcManagerService = inject(WebComponentManagerService);
protected wcManagerService = inject(MeetingWebComponentManagerService);
protected openviduService = inject(OpenViduService);
protected viewportService = inject(ViewportService);
protected ovThemeService = inject(OpenViduThemeService);

View File

@ -0,0 +1,7 @@
export * from './meeting-context.service';
export * from './meeting-event-handler.service';
export * from './meeting-layout.service';
export * from './meeting-lobby.service';
export * from './meeting-webcomponent-manager.service';
export * from './meeting.service';

View File

@ -2,9 +2,9 @@ import { computed, DestroyRef, effect, inject, Injectable, signal } from '@angul
import { MeetRoom } from 'node_modules/@openvidu-meet/typings/dist/room';
import { ParticipantService, Room, ViewportService } from 'openvidu-components-angular';
import { CustomParticipantModel } from '../../models';
import { FeatureConfigurationService } from '../feature-configuration.service';
import { SessionStorageService } from '../session-storage.service';
import { FeatureConfigurationService } from '../../../shared/services/feature-configuration.service';
import { SessionStorageService } from '../../../shared/services/session-storage.service';
import { CustomParticipantModel } from '../models';
/**
* Central service for managing meeting context and state during the MEETING PHASE.

View File

@ -18,17 +18,15 @@ import {
Room,
RoomEvent
} from 'openvidu-components-angular';
import { CustomParticipantModel } from '../../models';
import { MeetingContextService, MeetingWebComponentManagerService } from '.';
import {
FeatureConfigurationService,
MeetingContextService,
NavigationService,
RecordingService,
RoomMemberService,
SessionStorageService,
TokenStorageService,
WebComponentManagerService
} from '../../services';
TokenStorageService
} from '../../../shared';
import { RecordingService } from '../../recordings/services';
import { RoomMemberService } from '../../rooms/services';
/**
* Service that handles all LiveKit/OpenVidu room events.
@ -44,7 +42,7 @@ export class MeetingEventHandlerService {
protected roomMemberService = inject(RoomMemberService);
protected sessionStorageService = inject(SessionStorageService);
protected tokenStorageService = inject(TokenStorageService);
protected wcManagerService = inject(WebComponentManagerService);
protected wcManagerService = inject(MeetingWebComponentManagerService);
protected navigationService = inject(NavigationService);
// ============================================

View File

@ -1,11 +1,11 @@
import { computed, DestroyRef, effect, inject, Injectable, signal } from '@angular/core';
import { Participant, Room } from 'livekit-client';
import { LayoutService, LoggerService, ViewportService } from 'openvidu-components-angular';
import { MeetStorageService } from '.';
import { MeetStorageService } from '../../../shared';
import { MeetLayoutMode } from '../models';
@Injectable({ providedIn: 'root' })
export class MeetLayoutService extends LayoutService {
export class MeetingLayoutService extends LayoutService {
private readonly destroyRef = inject(DestroyRef);
private readonly INITIAL_SPEAKERS_COUNT = 4;
readonly MIN_REMOTE_SPEAKERS = 1;

View File

@ -4,18 +4,20 @@ import { ActivatedRoute } from '@angular/router';
import { MeetRoomStatus } from '@openvidu-meet/typings';
import { LoggerService } from 'openvidu-components-angular';
import {
AppDataService,
AuthService,
MeetingContextService,
MeetingService,
NavigationService,
RecordingService,
MeetingWebComponentManagerService
} from '.';
import { AppDataService, NavigationService } from '../../../shared';
import { NavigationErrorReason } from '../../../shared/models/navigation.model';
import { AuthService } from '../../auth/services/auth.service';
import { RecordingService } from '../../recordings/services';
import {
RoomMemberService,
RoomService,
WebComponentManagerService
} from '..';
import { ErrorReason } from '../../models';
import { LobbyState } from '../../models/lobby.model';
} from '../../rooms/services';
import { LobbyState } from '../models/lobby.model';
/**
* Service that manages the meeting lobby phase state and operations.
@ -51,7 +53,7 @@ export class MeetingLobbyService {
protected roomMemberService: RoomMemberService = inject(RoomMemberService);
protected navigationService: NavigationService = inject(NavigationService);
protected appDataService: AppDataService = inject(AppDataService);
protected wcManagerService: WebComponentManagerService = inject(WebComponentManagerService);
protected wcManagerService: MeetingWebComponentManagerService = inject(MeetingWebComponentManagerService);
protected loggerService = inject(LoggerService);
protected log = this.loggerService.get('OpenVidu Meet - MeetingLobbyService');
protected route: ActivatedRoute = inject(ActivatedRoute);
@ -366,18 +368,18 @@ export class MeetingLobbyService {
switch (error.status) {
case 400:
// Invalid secret
await this.navigationService.redirectToErrorPage(ErrorReason.INVALID_ROOM_SECRET, true);
await this.navigationService.redirectToErrorPage(NavigationErrorReason.INVALID_ROOM_SECRET, true);
break;
case 404:
// Room not found
await this.navigationService.redirectToErrorPage(ErrorReason.INVALID_ROOM, true);
await this.navigationService.redirectToErrorPage(NavigationErrorReason.INVALID_ROOM, true);
break;
case 409:
// Room is closed
await this.navigationService.redirectToErrorPage(ErrorReason.CLOSED_ROOM, true);
await this.navigationService.redirectToErrorPage(NavigationErrorReason.CLOSED_ROOM, true);
break;
default:
await this.navigationService.redirectToErrorPage(ErrorReason.INTERNAL_ERROR, true);
await this.navigationService.redirectToErrorPage(NavigationErrorReason.INTERNAL_ERROR, true);
}
throw new Error('Error generating room member token');

View File

@ -1,4 +1,5 @@
import { inject, Injectable } from '@angular/core';
import { effect, inject, Injectable } from '@angular/core';
import { AppDataService } from '@openvidu-meet/shared-components';
import {
WebComponentCommand,
WebComponentEvent,
@ -6,7 +7,8 @@ import {
WebComponentOutboundEventMessage
} from '@openvidu-meet/typings';
import { LoggerService, OpenViduService } from 'openvidu-components-angular';
import { MeetingContextService, MeetingService, RoomMemberService } from '../services';
import { MeetingContextService, MeetingService } from '.';
import { RoomMemberService } from '../../rooms/services';
/**
* Service to manage the commands from OpenVidu Meet WebComponent/Iframe.
@ -16,9 +18,8 @@ import { MeetingContextService, MeetingService, RoomMemberService } from '../ser
@Injectable({
providedIn: 'root'
})
export class WebComponentManagerService {
export class MeetingWebComponentManagerService {
protected isInitialized = false;
protected parentDomain: string = '';
protected boundHandleMessage: (event: MessageEvent) => Promise<void>;
@ -28,10 +29,16 @@ export class WebComponentManagerService {
protected readonly openviduService = inject(OpenViduService);
protected readonly meetingService = inject(MeetingService);
protected readonly loggerService = inject(LoggerService);
protected readonly appDataService = inject(AppDataService);
constructor() {
this.log = this.loggerService.get('OpenVidu Meet - WebComponentManagerService');
this.boundHandleMessage = this.handleMessage.bind(this);
effect(() => {
if (this.appDataService.isEmbeddedMode()) {
this.initialize();
}
});
}
initialize() {

View File

@ -1,9 +1,9 @@
import { inject, Injectable } from '@angular/core';
import { Clipboard } from '@angular/cdk/clipboard';
import { LoggerService } from 'openvidu-components-angular';
import { HttpService } from '../http.service';
import { inject, Injectable } from '@angular/core';
import { MeetRoom } from 'node_modules/@openvidu-meet/typings/dist/room';
import { NotificationService } from '../notification.service';
import { LoggerService } from 'openvidu-components-angular';
import { HttpService } from '../../../shared/services/http.service';
import { NotificationService } from '../../../shared/services/notification.service';
@Injectable({
providedIn: 'root'

View File

@ -0,0 +1,4 @@
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';

Some files were not shown because too many files have changed in this diff Show More