1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-02-11 00:31:45 +01:00

[PM-2014] feat: improve async data loading

This commit is contained in:
Andreas Coroiu 2023-05-10 10:48:16 +02:00
parent b2a7a29842
commit 7446f1c680
No known key found for this signature in database
GPG Key ID: E70B5FFC81DFEC1A
3 changed files with 48 additions and 21 deletions

View File

@ -1,5 +1,5 @@
import { Injectable, Optional } from "@angular/core";
import { from, map, Observable } from "rxjs";
import { BehaviorSubject, from, map, Observable, shareReplay, switchMap, tap } from "rxjs";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
@ -17,17 +17,26 @@ import { WebauthnApiService } from "./webauthn-api.service";
@Injectable({ providedIn: CoreAuthModule })
export class WebauthnService {
private credentials: CredentialsContainer;
private navigatorCredentials: CredentialsContainer;
private _refresh$ = new BehaviorSubject<void>(undefined);
private _loading$ = new BehaviorSubject<boolean>(true);
readonly credentials$ = this._refresh$.pipe(
switchMap(() => this.getCredentials$()),
tap(() => this._loading$.next(false)),
shareReplay({ bufferSize: 1, refCount: true })
);
readonly loading$ = this._loading$.asObservable();
constructor(
private apiService: WebauthnApiService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
@Optional() credentials?: CredentialsContainer,
@Optional() navigatorCredentials?: CredentialsContainer,
@Optional() private logService?: LogService
) {
// Default parameters don't work when used with Angular DI
this.credentials = credentials ?? navigator.credentials;
this.navigatorCredentials = navigatorCredentials ?? navigator.credentials;
}
async getCredentialCreateOptions(
@ -59,7 +68,7 @@ export class WebauthnService {
};
try {
const response = await this.credentials.create(nativeOptions);
const response = await this.navigatorCredentials.create(nativeOptions);
if (!(response instanceof PublicKeyCredential)) {
return undefined;
}
@ -81,6 +90,7 @@ export class WebauthnService {
request.token = credentialOptions.token;
request.name = name;
await this.apiService.saveCredential(request);
this.refresh();
return true;
} catch (error) {
this.logService?.error(error);
@ -89,7 +99,12 @@ export class WebauthnService {
}
}
getCredentials$(): Observable<WebauthnCredentialView[]> {
private getCredentials$(): Observable<WebauthnCredentialView[]> {
return from(this.apiService.getCredentials()).pipe(map((response) => response.data));
}
private refresh() {
this._loading$.next(true);
this._refresh$.next();
}
}

View File

@ -1,5 +1,6 @@
<h2 bitTypography="h2">
{{ "loginWithPasskey" | i18n }} <span bitBadge badgeType="secondary">Not implemented</span>
<i *ngIf="loading" class="bwi bwi-spinner bwi-spin tw-ml-1" aria-hidden="true"></i>
</h2>
<p bitTypography="body1">
{{ "loginWithPasskeyInfo" | i18n }}

View File

@ -1,39 +1,50 @@
import { Component, OnInit } from "@angular/core";
import { firstValueFrom, Observable } from "rxjs";
import { Component, HostBinding, OnDestroy, OnInit } from "@angular/core";
import { Observable, Subject, takeUntil } from "rxjs";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { WebauthnService } from "../../core";
import { WebauthnCredentialView } from "../../core/views/webauth-credential.view";
import {
CreateCredentialDialogResult,
openCreateCredentialDialog,
} from "./create-credential-dialog/create-credential-dialog.component";
import { openCreateCredentialDialog } from "./create-credential-dialog/create-credential-dialog.component";
@Component({
selector: "app-fido2-login-settings",
templateUrl: "fido2-login-settings.component.html",
host: {
"aria-live": "polite",
},
})
export class Fido2LoginSettingsComponent implements OnInit {
export class Fido2LoginSettingsComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
protected credentials$: Observable<WebauthnCredentialView[]>;
protected loading = true;
constructor(
private webauthnService: WebauthnService,
private dialogService: DialogServiceAbstraction
) {}
@HostBinding("attr.aria-busy")
get ariaBusy() {
return this.loading ? "true" : "false";
}
ngOnInit(): void {
this.credentials$ = this.webauthnService.getCredentials$();
this.credentials$ = this.webauthnService.credentials$;
this.webauthnService.loading$
.pipe(takeUntil(this.destroy$))
.subscribe((loading) => (this.loading = loading));
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
protected async createCredential() {
const dialogRef = openCreateCredentialDialog(this.dialogService, {});
const result = await firstValueFrom(dialogRef.closed);
if (result === CreateCredentialDialogResult.Success) {
/** Refresh */
}
openCreateCredentialDialog(this.dialogService, {});
}
}