diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index 924bc2dd30f..25d5bcff7d0 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -86,7 +86,7 @@ const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours - +
@@ -112,6 +112,7 @@ export class AppComponent implements OnInit, OnDestroy { @ViewChild("loginApproval", { read: ViewContainerRef, static: true }) loginApprovalModalRef: ViewContainerRef; + showHeader$ = this.accountService.showHeader$; loading = false; private lastActivity: Date = null; diff --git a/apps/desktop/src/modal/passkeys/create/fido2-create.component.html b/apps/desktop/src/modal/passkeys/create/fido2-create.component.html index e3423d6d7f8..8fefae29fd0 100644 --- a/apps/desktop/src/modal/passkeys/create/fido2-create.component.html +++ b/apps/desktop/src/modal/passkeys/create/fido2-create.component.html @@ -3,7 +3,7 @@ disableMargin class="tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300" > - +
@@ -16,7 +16,7 @@ type="button" bitIconButton="bwi-close" slot="end" - class="tw-mb-4 tw-mr-2" + class="passkey-header-close tw-mb-4 tw-mr-2" (click)="closeModal()" > Close diff --git a/apps/desktop/src/modal/passkeys/create/fido2-create.component.ts b/apps/desktop/src/modal/passkeys/create/fido2-create.component.ts index 5e166e770b4..5881c244a2c 100644 --- a/apps/desktop/src/modal/passkeys/create/fido2-create.component.ts +++ b/apps/desktop/src/modal/passkeys/create/fido2-create.component.ts @@ -67,6 +67,7 @@ export class Fido2CreateComponent implements OnInit { ) {} async ngOnInit() { + await this.accountService.setShowHeader(false); this.session = this.fido2UserInterfaceService.getCurrentSession(); const rpid = await this.session.getRpId(); const equivalentDomains = await firstValueFrom( @@ -94,6 +95,10 @@ export class Fido2CreateComponent implements OnInit { .catch((error) => this.logService.error(error)); } + async ngOnDestroy() { + await this.accountService.setShowHeader(true); + } + async addPasskeyToCipher(cipher: CipherView) { this.session.notifyConfirmCreateCredential(true, cipher); } diff --git a/apps/desktop/src/modal/passkeys/fido2-vault.component.html b/apps/desktop/src/modal/passkeys/fido2-vault.component.html index 5e7fadf8ec3..5191dcb1b6e 100644 --- a/apps/desktop/src/modal/passkeys/fido2-vault.component.html +++ b/apps/desktop/src/modal/passkeys/fido2-vault.component.html @@ -3,7 +3,7 @@ disableMargin class="tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300" > - +
@@ -13,7 +13,7 @@ type="button" bitIconButton="bwi-close" slot="end" - class="tw-mb-4 tw-mr-2" + class="passkey-header-close tw-mb-4 tw-mr-2" (click)="closeModal()" > Close diff --git a/apps/desktop/src/modal/passkeys/fido2-vault.component.ts b/apps/desktop/src/modal/passkeys/fido2-vault.component.ts index 0c5061befa7..423d646992a 100644 --- a/apps/desktop/src/modal/passkeys/fido2-vault.component.ts +++ b/apps/desktop/src/modal/passkeys/fido2-vault.component.ts @@ -1,8 +1,7 @@ import { CommonModule } from "@angular/common"; import { Component, OnInit, OnDestroy } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { RouterModule, Router } from "@angular/router"; -import { firstValueFrom, map, BehaviorSubject, Observable } from "rxjs"; +import { firstValueFrom, map, BehaviorSubject, Observable, Subject, takeUntil } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { BitwardenShield } from "@bitwarden/auth/angular"; @@ -50,6 +49,7 @@ import { DesktopSettingsService } from "../../platform/services/desktop-settings }) export class Fido2VaultComponent implements OnInit, OnDestroy { session?: DesktopFido2UserInterfaceSession = null; + private destroy$ = new Subject(); private ciphersSubject = new BehaviorSubject([]); ciphers$: Observable = this.ciphersSubject.asObservable(); private cipherIdsSubject = new BehaviorSubject([]); @@ -67,6 +67,7 @@ export class Fido2VaultComponent implements OnInit, OnDestroy { ) {} async ngOnInit() { + await this.accountService.setShowHeader(false); const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); @@ -74,7 +75,7 @@ export class Fido2VaultComponent implements OnInit, OnDestroy { this.session = this.fido2UserInterfaceService.getCurrentSession(); this.cipherIds$ = this.session?.availableCipherIds$; - this.cipherIds$.pipe(takeUntilDestroyed()).subscribe((cipherIds) => { + this.cipherIds$.pipe(takeUntil(this.destroy$)).subscribe((cipherIds) => { this.cipherService .getAllDecryptedForIds(activeUserId, cipherIds || []) .then((ciphers) => { @@ -84,7 +85,8 @@ export class Fido2VaultComponent implements OnInit, OnDestroy { }); } - ngOnDestroy() { + async ngOnDestroy() { + await this.accountService.setShowHeader(true); this.cipherIdsSubject.complete(); // Clean up the BehaviorSubject } diff --git a/apps/desktop/src/scss/header.scss b/apps/desktop/src/scss/header.scss index cdd579a6554..da8d9449b84 100644 --- a/apps/desktop/src/scss/header.scss +++ b/apps/desktop/src/scss/header.scss @@ -232,3 +232,11 @@ font-size: $font-size-small; } } + +.passkey-header { + -webkit-app-region: drag; +} + +.passkey-header-close { + -webkit-app-region: no-drag; +} diff --git a/libs/common/spec/fake-account-service.ts b/libs/common/spec/fake-account-service.ts index ba48181faa2..389975dc2e1 100644 --- a/libs/common/spec/fake-account-service.ts +++ b/libs/common/spec/fake-account-service.ts @@ -37,6 +37,8 @@ export class FakeAccountService implements AccountService { accountActivitySubject = new ReplaySubject>(1); // eslint-disable-next-line rxjs/no-exposed-subjects -- test class accountVerifyDevicesSubject = new ReplaySubject(1); + // eslint-disable-next-line rxjs/no-exposed-subjects -- test class + showHeaderSubject = new ReplaySubject(1); private _activeUserId: UserId; get activeUserId() { return this._activeUserId; @@ -55,6 +57,7 @@ export class FakeAccountService implements AccountService { }), ); } + showHeader$ = this.showHeaderSubject.asObservable(); get nextUpAccount$(): Observable { return combineLatest([this.accounts$, this.activeAccount$, this.sortedUserIds$]).pipe( map(([accounts, activeAccount, sortedUserIds]) => { @@ -114,6 +117,10 @@ export class FakeAccountService implements AccountService { this.accountsSubject.next(updated); await this.mock.clean(userId); } + + async setShowHeader(value: boolean): Promise { + this.showHeaderSubject.next(value); + } } const loggedOutInfo: AccountInfo = { diff --git a/libs/common/src/auth/abstractions/account.service.ts b/libs/common/src/auth/abstractions/account.service.ts index 1686eefda06..bedd490cd0c 100644 --- a/libs/common/src/auth/abstractions/account.service.ts +++ b/libs/common/src/auth/abstractions/account.service.ts @@ -49,6 +49,7 @@ export abstract class AccountService { sortedUserIds$: Observable; /** Next account that is not the current active account */ nextUpAccount$: Observable; + showHeader$: Observable; /** * Updates the `accounts$` observable with the new account data. * @@ -102,6 +103,11 @@ export abstract class AccountService { * @param lastActivity */ abstract setAccountActivity(userId: UserId, lastActivity: Date): Promise; + /** + * Show the account switcher. + * @param value + */ + abstract setShowHeader(visible: boolean): Promise; } export abstract class InternalAccountService extends AccountService { diff --git a/libs/common/src/auth/services/account.service.spec.ts b/libs/common/src/auth/services/account.service.spec.ts index 3fc47002083..3a6018c9b89 100644 --- a/libs/common/src/auth/services/account.service.spec.ts +++ b/libs/common/src/auth/services/account.service.spec.ts @@ -429,6 +429,24 @@ describe("accountService", () => { }, ); }); + + describe("setShowHeader", () => { + it("should update _showHeader$ when setShowHeader is called", async () => { + expect(sut["_showHeader$"].value).toBe(true); + + await sut.setShowHeader(false); + expect(sut["_showHeader$"].value).toBe(false); + }); + + it("should emit values correctly", (done) => { + sut.showHeader$.subscribe((value) => { + expect(value).toBe(false); + done(); + }); + + await sut.setShowHeader(false); + }); + }); }); }); diff --git a/libs/common/src/auth/services/account.service.ts b/libs/common/src/auth/services/account.service.ts index 50ba2455d78..828fad31ab8 100644 --- a/libs/common/src/auth/services/account.service.ts +++ b/libs/common/src/auth/services/account.service.ts @@ -6,6 +6,7 @@ import { distinctUntilChanged, shareReplay, combineLatest, + BehaviorSubject, Observable, switchMap, filter, @@ -84,6 +85,7 @@ export const getOptionalUserId = map( export class AccountServiceImplementation implements InternalAccountService { private accountsState: GlobalState>; private activeAccountIdState: GlobalState; + private _showHeader$ = new BehaviorSubject(true); accounts$: Observable>; activeAccount$: Observable; @@ -91,6 +93,7 @@ export class AccountServiceImplementation implements InternalAccountService { accountVerifyNewDeviceLogin$: Observable; sortedUserIds$: Observable; nextUpAccount$: Observable; + showHeader$ = this._showHeader$.asObservable(); constructor( private messagingService: MessagingService, @@ -260,6 +263,10 @@ export class AccountServiceImplementation implements InternalAccountService { } } + async setShowHeader(visible: boolean): Promise { + this._showHeader$.next(visible); + } + private async setAccountInfo(userId: UserId, update: Partial): Promise { function newAccountInfo(oldAccountInfo: AccountInfo): AccountInfo { return { ...oldAccountInfo, ...update };