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