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:
parent
b2a7a29842
commit
7446f1c680
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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 }}
|
||||
|
@ -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, {});
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user