Browse Source

Task/improve type safety in HTTP interceptor (#6957)

* feat(client): resolve errors

* feat(client): replace constructor based DI with inject function

* feat(client): enforce encapsulation and immutability

* feat(client): remove unused tap operator

* feat(client): implement generic type parameter

* feat(client): replace constructor based DI with inject functions
pull/4198/merge
Kenrick Tandrian 4 days ago
committed by GitHub
parent
commit
fea5ee33bb
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 12
      apps/client/src/app/core/auth.guard.ts
  2. 16
      apps/client/src/app/core/auth.interceptor.ts
  3. 45
      apps/client/src/app/core/http-response.interceptor.ts

12
apps/client/src/app/core/auth.guard.ts

@ -3,7 +3,7 @@ import { UserService } from '@ghostfolio/client/services/user/user.service';
import { internalRoutes, publicRoutes } from '@ghostfolio/common/routes/routes';
import { DataService } from '@ghostfolio/ui/services';
import { Injectable } from '@angular/core';
import { inject, Injectable } from '@angular/core';
import {
ActivatedRouteSnapshot,
Router,
@ -14,12 +14,10 @@ import { catchError } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class AuthGuard {
public constructor(
private dataService: DataService,
private router: Router,
private settingsStorageService: SettingsStorageService,
private userService: UserService
) {}
private readonly dataService = inject(DataService);
private readonly router = inject(Router);
private readonly settingsStorageService = inject(SettingsStorageService);
private readonly userService = inject(UserService);
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const utmSource = route.queryParams?.utm_source;

16
apps/client/src/app/core/auth.interceptor.ts

@ -13,20 +13,20 @@ import {
HttpInterceptor,
HttpRequest
} from '@angular/common/http';
import { Injectable } from '@angular/core';
import { inject, Injectable } from '@angular/core';
import { Observable } from 'rxjs';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
public constructor(
private impersonationStorageService: ImpersonationStorageService,
private tokenStorageService: TokenStorageService
) {}
private readonly impersonationStorageService = inject(
ImpersonationStorageService
);
private readonly tokenStorageService = inject(TokenStorageService);
public intercept(
req: HttpRequest<any>,
public intercept<T>(
req: HttpRequest<T>,
next: HttpHandler
): Observable<HttpEvent<any>> {
): Observable<HttpEvent<T>> {
let request = req;
if (request.headers.has(HEADER_KEY_SKIP_INTERCEPTOR)) {

45
apps/client/src/app/core/http-response.interceptor.ts

@ -12,7 +12,7 @@ import {
HttpInterceptor,
HttpRequest
} from '@angular/common/http';
import { Injectable } from '@angular/core';
import { inject, Injectable } from '@angular/core';
import {
MatSnackBar,
MatSnackBarRef,
@ -22,31 +22,28 @@ import { Router } from '@angular/router';
import { StatusCodes } from 'http-status-codes';
import ms from 'ms';
import { Observable, throwError } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { catchError } from 'rxjs/operators';
@Injectable()
export class HttpResponseInterceptor implements HttpInterceptor {
public info: InfoItem;
public snackBarRef: MatSnackBarRef<TextOnlySnackBar>;
private readonly info: InfoItem;
private snackBarRef: MatSnackBarRef<TextOnlySnackBar> | undefined;
public constructor(
private dataService: DataService,
private router: Router,
private snackBar: MatSnackBar,
private userService: UserService,
private webAuthnService: WebAuthnService
) {
private readonly dataService = inject(DataService);
private readonly router = inject(Router);
private readonly snackBar = inject(MatSnackBar);
private readonly userService = inject(UserService);
private readonly webAuthnService = inject(WebAuthnService);
public constructor() {
this.info = this.dataService.fetchInfo();
}
public intercept(
request: HttpRequest<any>,
public intercept<T>(
request: HttpRequest<T>,
next: HttpHandler
): Observable<HttpEvent<any>> {
): Observable<HttpEvent<T>> {
return next.handle(request).pipe(
tap((event: HttpEvent<any>) => {
return event;
}),
catchError((error: HttpErrorResponse) => {
if (error.status === StatusCodes.FORBIDDEN) {
if (!this.snackBarRef) {
@ -61,7 +58,7 @@ export class HttpResponseInterceptor implements HttpInterceptor {
}
);
} else if (
!error.url.includes(internalRoutes.auth.routerLink.join(''))
!error.url?.includes(internalRoutes.auth.routerLink.join(''))
) {
this.snackBarRef = this.snackBar.open(
$localize`This action is not allowed.`,
@ -72,11 +69,11 @@ export class HttpResponseInterceptor implements HttpInterceptor {
);
}
this.snackBarRef.afterDismissed().subscribe(() => {
this.snackBarRef?.afterDismissed().subscribe(() => {
this.snackBarRef = undefined;
});
this.snackBarRef.onAction().subscribe(() => {
this.snackBarRef?.onAction().subscribe(() => {
this.router.navigate(publicRoutes.pricing.routerLink);
});
}
@ -92,11 +89,11 @@ export class HttpResponseInterceptor implements HttpInterceptor {
}
);
this.snackBarRef.afterDismissed().subscribe(() => {
this.snackBarRef?.afterDismissed().subscribe(() => {
this.snackBarRef = undefined;
});
this.snackBarRef.onAction().subscribe(() => {
this.snackBarRef?.onAction().subscribe(() => {
window.location.reload();
});
}
@ -106,12 +103,12 @@ export class HttpResponseInterceptor implements HttpInterceptor {
$localize`Oops! It looks like you’re making too many requests. Please slow down a bit.`
);
this.snackBarRef.afterDismissed().subscribe(() => {
this.snackBarRef?.afterDismissed().subscribe(() => {
this.snackBarRef = undefined;
});
}
} else if (error.status === StatusCodes.UNAUTHORIZED) {
if (!error.url.includes('/data-providers/ghostfolio/status')) {
if (!error.url?.includes('/data-providers/ghostfolio/status')) {
if (this.webAuthnService.isEnabled()) {
this.router.navigate(internalRoutes.webauthn.routerLink);
} else {

Loading…
Cancel
Save