From 344879c0c9223a08f8b1fc9c8ebfc190ea1e42e6 Mon Sep 17 00:00:00 2001 From: SkyZeroZx <73321943+SkyZeroZx@users.noreply.github.com> Date: Thu, 17 Oct 2024 18:09:55 -0500 Subject: [PATCH] feat(webauthn-angular): Add Global Handler Error with Dialog --- .../src/app/app.component.spec.ts | 26 ++-------- apps/webauthn-angular/src/app/app.config.ts | 4 +- .../core/constants/error/error.constant.ts | 2 + .../components/error-dialog.component.html | 13 +++++ .../components/error-dialog.component.scss | 16 ++++++ .../components/error-dialog.component.spec.ts | 21 ++++++++ .../components/error-dialog.component.ts | 24 +++++++++ .../errors/global-error-handler.service.ts | 50 +++++++++++++++++++ .../src/app/core/errors/index.ts | 9 ++++ 9 files changed, 143 insertions(+), 22 deletions(-) create mode 100644 apps/webauthn-angular/src/app/core/errors/components/error-dialog.component.html create mode 100644 apps/webauthn-angular/src/app/core/errors/components/error-dialog.component.scss create mode 100644 apps/webauthn-angular/src/app/core/errors/components/error-dialog.component.spec.ts create mode 100644 apps/webauthn-angular/src/app/core/errors/components/error-dialog.component.ts create mode 100644 apps/webauthn-angular/src/app/core/errors/global-error-handler.service.ts create mode 100644 apps/webauthn-angular/src/app/core/errors/index.ts diff --git a/apps/webauthn-angular/src/app/app.component.spec.ts b/apps/webauthn-angular/src/app/app.component.spec.ts index d81a8be..07264f8 100644 --- a/apps/webauthn-angular/src/app/app.component.spec.ts +++ b/apps/webauthn-angular/src/app/app.component.spec.ts @@ -1,27 +1,11 @@ import { TestBed } from '@angular/core/testing'; import { AppComponent } from './app.component'; -import { NxWelcomeComponent } from './nx-welcome.component'; import { RouterModule } from '@angular/router'; describe('AppComponent', () => { - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [AppComponent, NxWelcomeComponent, RouterModule.forRoot([])], - }).compileComponents(); - }); - - it('should render title', () => { - const fixture = TestBed.createComponent(AppComponent); - fixture.detectChanges(); - const compiled = fixture.nativeElement as HTMLElement; - expect(compiled.querySelector('h1')?.textContent).toContain( - 'Welcome webauthn-angular' - ); - }); - - it(`should have as title 'webauthn-angular'`, () => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.componentInstance; - expect(app.title).toEqual('webauthn-angular'); - }); + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AppComponent, RouterModule.forRoot([])] + }).compileComponents(); + }); }); diff --git a/apps/webauthn-angular/src/app/app.config.ts b/apps/webauthn-angular/src/app/app.config.ts index 1ab301f..76a4825 100644 --- a/apps/webauthn-angular/src/app/app.config.ts +++ b/apps/webauthn-angular/src/app/app.config.ts @@ -1,6 +1,7 @@ import { provideMarkdown } from 'ngx-markdown'; import { MAT_SNACK_BAR_CONFIG } from '@/core/config'; +import { GLOBAL_ERROR_HANDLER } from '@/core/errors'; import { errorInterceptor, progressInterceptor, tokenInterceptor } from '@/core/interceptors'; import { provideHttpClient, withFetch, withInterceptors } from '@angular/common/http'; import { ApplicationConfig, importProvidersFrom, provideZoneChangeDetection } from '@angular/core'; @@ -22,6 +23,7 @@ export const appConfig: ApplicationConfig = { ), provideAnimationsAsync(), provideMarkdown(), - MAT_SNACK_BAR_CONFIG + MAT_SNACK_BAR_CONFIG, + GLOBAL_ERROR_HANDLER ] }; diff --git a/apps/webauthn-angular/src/app/core/constants/error/error.constant.ts b/apps/webauthn-angular/src/app/core/constants/error/error.constant.ts index 30360fd..2eb7185 100644 --- a/apps/webauthn-angular/src/app/core/constants/error/error.constant.ts +++ b/apps/webauthn-angular/src/app/core/constants/error/error.constant.ts @@ -1,3 +1,5 @@ export const DEFAULT_ERROR = 'Error unknown'; export const DUPLICATE_CONSTRAINT = 'duplicate key value violates unique constraint'; + +export const PREVIOUS_REGISTER_AUTHENTICATOR = 'the authenticator was previously registered'; \ No newline at end of file diff --git a/apps/webauthn-angular/src/app/core/errors/components/error-dialog.component.html b/apps/webauthn-angular/src/app/core/errors/components/error-dialog.component.html new file mode 100644 index 0000000..154c649 --- /dev/null +++ b/apps/webauthn-angular/src/app/core/errors/components/error-dialog.component.html @@ -0,0 +1,13 @@ +
+

{{ data.title }}

+ +
+ +
+ +

{{ data.message }}

+ +
+ +
+
diff --git a/apps/webauthn-angular/src/app/core/errors/components/error-dialog.component.scss b/apps/webauthn-angular/src/app/core/errors/components/error-dialog.component.scss new file mode 100644 index 0000000..8ad126c --- /dev/null +++ b/apps/webauthn-angular/src/app/core/errors/components/error-dialog.component.scss @@ -0,0 +1,16 @@ +.error-dialog { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + padding: 1rem; + gap: 1.5rem; + height: 100%; +} + +mat-icon { + font-size: 50px; + height: 50px; + width: 50px; + color: #f8bb86; +} diff --git a/apps/webauthn-angular/src/app/core/errors/components/error-dialog.component.spec.ts b/apps/webauthn-angular/src/app/core/errors/components/error-dialog.component.spec.ts new file mode 100644 index 0000000..24ae567 --- /dev/null +++ b/apps/webauthn-angular/src/app/core/errors/components/error-dialog.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ErrorDialogComponent } from './error-dialog.component'; + +describe('ErrorDialogComponent', () => { + let component: ErrorDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ErrorDialogComponent] + }).compileComponents(); + + fixture = TestBed.createComponent(ErrorDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/webauthn-angular/src/app/core/errors/components/error-dialog.component.ts b/apps/webauthn-angular/src/app/core/errors/components/error-dialog.component.ts new file mode 100644 index 0000000..12dcadd --- /dev/null +++ b/apps/webauthn-angular/src/app/core/errors/components/error-dialog.component.ts @@ -0,0 +1,24 @@ +import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, inject } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { MatIconModule } from '@angular/material/icon'; + +import { ErrorMessage } from '@/core/interface'; + +@Component({ + selector: 'app-error-dialog', + standalone: true, + imports: [MatButtonModule, MatIconModule], + templateUrl: './error-dialog.component.html', + styleUrl: './error-dialog.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + schemas: [CUSTOM_ELEMENTS_SCHEMA] +}) +export class ErrorDialogComponent { + data = inject(MAT_DIALOG_DATA); + dialogRef = inject(MatDialogRef); + + close() { + this.dialogRef.close(); + } +} diff --git a/apps/webauthn-angular/src/app/core/errors/global-error-handler.service.ts b/apps/webauthn-angular/src/app/core/errors/global-error-handler.service.ts new file mode 100644 index 0000000..12dee3d --- /dev/null +++ b/apps/webauthn-angular/src/app/core/errors/global-error-handler.service.ts @@ -0,0 +1,50 @@ +import { PREVIOUS_REGISTER_AUTHENTICATOR } from '@/core/constants'; +import { ErrorMapper } from '@/core/interface'; +import { ErrorHandler, inject, Injectable } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; + +import { ErrorDialogComponent } from './components/error-dialog.component'; + +@Injectable() +export class GlobalErrorHandler implements ErrorHandler { + private readonly matDialog = inject(MatDialog); + + private readonly errorMappings: ErrorMapper[] = [ + { + title: 'Previous registration', + toCompare: PREVIOUS_REGISTER_AUTHENTICATOR, + icon: 'error', + message: 'Already registered authentication' + } + ]; + + handleError(error: unknown) { + console.error(GlobalErrorHandler.name, error); + + const message = this.getMessage(error); + + const matchedError = this.errorMappings.find((mapping) => + message.includes(mapping.toCompare.toLowerCase()) + ); + + if (matchedError) { + console.log(`Handling error: ${matchedError.title}`); + + this.matDialog.open(ErrorDialogComponent, { + data: matchedError, + height: '400px', + width: '600px' + }); + } + } + + getMessage(error: unknown) { + const message = error?.['message']; + + if (!message) { + return null; + } + + return message?.toLowerCase() as string; + } +} diff --git a/apps/webauthn-angular/src/app/core/errors/index.ts b/apps/webauthn-angular/src/app/core/errors/index.ts new file mode 100644 index 0000000..bc29314 --- /dev/null +++ b/apps/webauthn-angular/src/app/core/errors/index.ts @@ -0,0 +1,9 @@ +import { ErrorHandler } from '@angular/core'; +import { GlobalErrorHandler } from './global-error-handler.service'; +import { MatDialog, MatDialogModule } from '@angular/material/dialog'; + +export const GLOBAL_ERROR_HANDLER = { + provide: ErrorHandler, + useClass: GlobalErrorHandler, + deps: [MatDialog, MatDialogModule] +}; \ No newline at end of file