Browse Source

Task/improve type safety in client services (#7175)

Improve type safety
pull/7159/head
Kenrick Tandrian 2 weeks ago
committed by GitHub
parent
commit
87ff19f48a
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      apps/client/src/app/components/header/header.component.ts
  2. 2
      apps/client/src/app/pages/admin/admin-page.component.ts
  3. 2
      apps/client/src/app/pages/demo/demo-page.component.ts
  4. 4
      apps/client/src/app/services/impersonation-storage.service.ts
  5. 2
      apps/client/src/app/services/settings-storage.service.ts
  6. 4
      apps/client/src/app/services/token-storage.service.ts
  7. 44
      apps/client/src/app/services/user/user.service.ts
  8. 20
      apps/client/src/app/services/web-authn.service.ts

2
apps/client/src/app/components/header/header.component.ts

@ -101,7 +101,7 @@ export class GfHeaderComponent implements OnChanges {
protected hasPermissionToAccessAssistant: boolean; protected hasPermissionToAccessAssistant: boolean;
protected hasPermissionToAccessFearAndGreedIndex: boolean; protected hasPermissionToAccessFearAndGreedIndex: boolean;
protected hasPermissionToCreateUser: boolean; protected hasPermissionToCreateUser: boolean;
protected impersonationId: string; protected impersonationId: string | null;
protected readonly internalRoutes = internalRoutes; protected readonly internalRoutes = internalRoutes;
protected isMenuOpen: boolean; protected isMenuOpen: boolean;
protected readonly routeAbout = publicRoutes.about.path; protected readonly routeAbout = publicRoutes.about.path;

2
apps/client/src/app/pages/admin/admin-page.component.ts

@ -103,7 +103,7 @@ export class AdminPageComponent {
const token = this.tokenStorageService.getToken(); const token = this.tokenStorageService.getToken();
document.cookie = [ document.cookie = [
`${BULL_BOARD_COOKIE_NAME}=${encodeURIComponent(token)}`, `${BULL_BOARD_COOKIE_NAME}=${encodeURIComponent(token ?? '')}`,
'path=/', 'path=/',
'SameSite=Strict' 'SameSite=Strict'
].join('; '); ].join('; ');

2
apps/client/src/app/pages/demo/demo-page.component.ts

@ -25,7 +25,7 @@ export class GfDemoPageComponent {
} }
public ngOnInit() { public ngOnInit() {
const hasToken = this.tokenStorageService.getToken()?.length > 0; const hasToken = !!this.tokenStorageService.getToken();
if (hasToken) { if (hasToken) {
this.notificationService.alert({ this.notificationService.alert({

4
apps/client/src/app/services/impersonation-storage.service.ts

@ -7,11 +7,11 @@ export const IMPERSONATION_KEY = 'impersonationId';
providedIn: 'root' providedIn: 'root'
}) })
export class ImpersonationStorageService { export class ImpersonationStorageService {
private hasImpersonationChangeSubject = new BehaviorSubject<string>( private hasImpersonationChangeSubject = new BehaviorSubject<string | null>(
this.getId() this.getId()
); );
public getId(): string { public getId(): string | null {
return window.localStorage.getItem(IMPERSONATION_KEY); return window.localStorage.getItem(IMPERSONATION_KEY);
} }

2
apps/client/src/app/services/settings-storage.service.ts

@ -8,7 +8,7 @@ export const KEY_TOKEN = 'auth-token';
providedIn: 'root' providedIn: 'root'
}) })
export class SettingsStorageService { export class SettingsStorageService {
public getSetting(aKey: string): string { public getSetting(aKey: string): string | null {
return window.localStorage.getItem(aKey); return window.localStorage.getItem(aKey);
} }

4
apps/client/src/app/services/token-storage.service.ts

@ -6,9 +6,9 @@ import { KEY_TOKEN } from './settings-storage.service';
providedIn: 'root' providedIn: 'root'
}) })
export class TokenStorageService { export class TokenStorageService {
public getToken(): string { public getToken(): string | null {
return ( return (
window.sessionStorage.getItem(KEY_TOKEN) || window.sessionStorage.getItem(KEY_TOKEN) ??
window.localStorage.getItem(KEY_TOKEN) window.localStorage.getItem(KEY_TOKEN)
); );
} }

44
apps/client/src/app/services/user/user.service.ts

@ -3,7 +3,7 @@ import { Filter, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { DestroyRef, Injectable } from '@angular/core'; import { computed, DestroyRef, inject, Injectable } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { ObservableStore } from '@codewithdan/observable-store'; import { ObservableStore } from '@codewithdan/observable-store';
@ -22,20 +22,20 @@ import { UserStoreState } from './user-store.state';
providedIn: 'root' providedIn: 'root'
}) })
export class UserService extends ObservableStore<UserStoreState> { export class UserService extends ObservableStore<UserStoreState> {
private deviceType: string; private readonly deviceType = computed(
() => this.deviceDetectorService.deviceInfo().deviceType
public constructor( );
private destroyRef: DestroyRef,
private deviceDetectorService: DeviceDetectorService, private readonly destroyRef = inject(DestroyRef);
private dialog: MatDialog, private readonly deviceDetectorService = inject(DeviceDetectorService);
private http: HttpClient, private readonly dialog = inject(MatDialog);
private webAuthnService: WebAuthnService private readonly http = inject(HttpClient);
) { private readonly webAuthnService = inject(WebAuthnService);
public constructor() {
super({ trackStateHistory: true }); super({ trackStateHistory: true });
this.setState({ user: undefined }, UserStoreActions.Initialize); this.setState({ user: undefined }, UserStoreActions.Initialize);
this.deviceType = this.deviceDetectorService.getDeviceInfo().deviceType;
} }
public get(force = false) { public get(force = false) {
@ -97,7 +97,7 @@ export class UserService extends ObservableStore<UserStoreState> {
} }
public reset() { public reset() {
this.setState({ user: null }, UserStoreActions.RemoveUser); this.setState({ user: undefined }, UserStoreActions.RemoveUser);
} }
public signOut() { public signOut() {
@ -127,7 +127,19 @@ export class UserService extends ObservableStore<UserStoreState> {
const cookies = await cookieStore.getAll(); const cookies = await cookieStore.getAll();
await Promise.all(cookies.map(({ name }) => cookieStore.delete(name))); const cookieNames = cookies
.map(({ name }) => {
return name;
})
.filter((name): name is string => {
return !!name;
});
await Promise.all(
cookieNames.map((name) => {
return cookieStore.delete(name);
})
);
} }
private fetchUser(): Observable<User> { private fetchUser(): Observable<User> {
@ -158,8 +170,8 @@ export class UserService extends ObservableStore<UserStoreState> {
user user
}, },
disableClose: true, disableClose: true,
height: this.deviceType === 'mobile' ? '98vh' : '80vh', height: this.deviceType() === 'mobile' ? '98vh' : '80vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType() === 'mobile' ? '100vw' : '50rem'
}); });
dialogRef dialogRef

20
apps/client/src/app/services/web-authn.service.ts

@ -6,7 +6,7 @@ import {
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { import {
startAuthentication, startAuthentication,
startRegistration startRegistration
@ -20,10 +20,8 @@ import { catchError, switchMap, tap } from 'rxjs/operators';
export class WebAuthnService { export class WebAuthnService {
private static readonly WEB_AUTH_N_DEVICE_ID = 'WEB_AUTH_N_DEVICE_ID'; private static readonly WEB_AUTH_N_DEVICE_ID = 'WEB_AUTH_N_DEVICE_ID';
public constructor( private readonly http = inject(HttpClient);
private http: HttpClient, private readonly settingsStorageService = inject(SettingsStorageService);
private settingsStorageService: SettingsStorageService
) {}
public isSupported() { public isSupported() {
return typeof PublicKeyCredential !== 'undefined'; return typeof PublicKeyCredential !== 'undefined';
@ -44,8 +42,12 @@ export class WebAuthnService {
console.warn('Could not register device', error); console.warn('Could not register device', error);
return of(null); return of(null);
}), }),
switchMap((attOps) => { switchMap((registrationOptions) => {
return startRegistration({ optionsJSON: attOps }); if (!registrationOptions) {
throw new Error('Could not generate registration options');
}
return startRegistration({ optionsJSON: registrationOptions });
}), }),
switchMap((credential) => { switchMap((credential) => {
return this.http.post<AuthDeviceDto>( return this.http.post<AuthDeviceDto>(
@ -53,10 +55,10 @@ export class WebAuthnService {
{ credential } { credential }
); );
}), }),
tap((authDevice) => tap(({ id }) =>
this.settingsStorageService.setSetting( this.settingsStorageService.setSetting(
WebAuthnService.WEB_AUTH_N_DEVICE_ID, WebAuthnService.WEB_AUTH_N_DEVICE_ID,
authDevice.id id
) )
) )
); );

Loading…
Cancel
Save