Skip to content

Commit

Permalink
feat(webauthn-angular): Add Global Handler Error with Dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
SkyZeroZx committed Oct 17, 2024
1 parent f026590 commit 344879c
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 22 deletions.
26 changes: 5 additions & 21 deletions apps/webauthn-angular/src/app/app.component.spec.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
4 changes: 3 additions & 1 deletion apps/webauthn-angular/src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -22,6 +23,7 @@ export const appConfig: ApplicationConfig = {
),
provideAnimationsAsync(),
provideMarkdown(),
MAT_SNACK_BAR_CONFIG
MAT_SNACK_BAR_CONFIG,
GLOBAL_ERROR_HANDLER
]
};
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<div class="error-dialog">
<h4>{{ data.title }}</h4>

<div>
<mat-icon class="icon-xl" [fontIcon]="data.icon" />
</div>

<p>{{ data.message }}</p>

<div>
<button mat-button (click)="close()">OK</button>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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<ErrorDialogComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ErrorDialogComponent]
}).compileComponents();

fixture = TestBed.createComponent(ErrorDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -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<ErrorMessage>(MAT_DIALOG_DATA);
dialogRef = inject(MatDialogRef<ErrorDialogComponent>);

close() {
this.dialogRef.close();
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
9 changes: 9 additions & 0 deletions apps/webauthn-angular/src/app/core/errors/index.ts
Original file line number Diff line number Diff line change
@@ -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]
};

0 comments on commit 344879c

Please sign in to comment.