diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts index 7f7e0f075b7..88fae02275f 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts @@ -1,11 +1,14 @@ import { TestBed } from "@angular/core/testing"; -import { BehaviorSubject, firstValueFrom } from "rxjs"; +import { BehaviorSubject, firstValueFrom, take, timeout } from "rxjs"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; +import { + UserDecryptionOptions, + UserDecryptionOptionsServiceAbstraction, +} from "@bitwarden/auth/common"; +import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; import { StateProvider } from "@bitwarden/common/platform/state"; import { FakeStateProvider, mockAccountServiceWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; @@ -22,18 +25,20 @@ describe("VaultBannersService", () => { let service: VaultBannersService; const isSelfHost = jest.fn().mockReturnValue(false); const hasPremiumFromAnySource$ = new BehaviorSubject(false); - const userId = "user-id" as UserId; + const userId = Utils.newGuid() as UserId; const fakeStateProvider = new FakeStateProvider(mockAccountServiceWith(userId)); const getEmailVerified = jest.fn().mockResolvedValue(true); - const hasMasterPassword = jest.fn().mockResolvedValue(true); - const getKdfConfig = jest - .fn() - .mockResolvedValue({ kdfType: KdfType.PBKDF2_SHA256, iterations: 600000 }); - const getLastSync = jest.fn().mockResolvedValue(null); + const lastSync$ = new BehaviorSubject(null); + const userDecryptionOptions$ = new BehaviorSubject({ + hasMasterPassword: true, + }); + const kdfConfig$ = new BehaviorSubject({ kdfType: KdfType.PBKDF2_SHA256, iterations: 600000 }); + const accounts$ = new BehaviorSubject>({ + [userId]: { email: "test@bitwarden.com", emailVerified: true, name: "name" } as AccountInfo, + }); beforeEach(() => { - jest.useFakeTimers(); - getLastSync.mockClear().mockResolvedValue(new Date("2024-05-14")); + lastSync$.next(new Date("2024-05-14")); isSelfHost.mockClear(); getEmailVerified.mockClear().mockResolvedValue(true); @@ -52,25 +57,27 @@ describe("VaultBannersService", () => { provide: StateProvider, useValue: fakeStateProvider, }, + { + provide: PlatformUtilsService, + useValue: { isSelfHost }, + }, { provide: AccountService, - useValue: mockAccountServiceWith(userId), - }, - { - provide: TokenService, - useValue: { getEmailVerified }, - }, - { - provide: UserVerificationService, - useValue: { hasMasterPassword }, + useValue: { accounts$ }, }, { provide: KdfConfigService, - useValue: { getKdfConfig }, + useValue: { getKdfConfig$: () => kdfConfig$ }, }, { provide: SyncService, - useValue: { getLastSync }, + useValue: { lastSync$: () => lastSync$ }, + }, + { + provide: UserDecryptionOptionsServiceAbstraction, + useValue: { + userDecryptionOptionsById$: () => userDecryptionOptions$, + }, }, ], }); @@ -82,39 +89,38 @@ describe("VaultBannersService", () => { describe("Premium", () => { it("waits until sync is completed before showing premium banner", async () => { - getLastSync.mockResolvedValue(new Date("2024-05-14")); hasPremiumFromAnySource$.next(false); isSelfHost.mockReturnValue(false); + lastSync$.next(null); service = TestBed.inject(VaultBannersService); - jest.advanceTimersByTime(201); + const premiumBanner$ = service.shouldShowPremiumBanner$(userId); - expect(await firstValueFrom(service.shouldShowPremiumBanner$)).toBe(true); + // Should not emit when sync is null + await expect(firstValueFrom(premiumBanner$.pipe(take(1), timeout(100)))).rejects.toThrow(); + + // Should emit when sync is completed + lastSync$.next(new Date("2024-05-14")); + expect(await firstValueFrom(premiumBanner$)).toBe(true); }); it("does not show a premium banner for self-hosted users", async () => { - getLastSync.mockResolvedValue(new Date("2024-05-14")); hasPremiumFromAnySource$.next(false); isSelfHost.mockReturnValue(true); service = TestBed.inject(VaultBannersService); - jest.advanceTimersByTime(201); - - expect(await firstValueFrom(service.shouldShowPremiumBanner$)).toBe(false); + expect(await firstValueFrom(service.shouldShowPremiumBanner$(userId))).toBe(false); }); it("does not show a premium banner when they have access to premium", async () => { - getLastSync.mockResolvedValue(new Date("2024-05-14")); hasPremiumFromAnySource$.next(true); isSelfHost.mockReturnValue(false); service = TestBed.inject(VaultBannersService); - jest.advanceTimersByTime(201); - - expect(await firstValueFrom(service.shouldShowPremiumBanner$)).toBe(false); + expect(await firstValueFrom(service.shouldShowPremiumBanner$(userId))).toBe(false); }); describe("dismissing", () => { @@ -125,7 +131,7 @@ describe("VaultBannersService", () => { jest.setSystemTime(date.getTime()); service = TestBed.inject(VaultBannersService); - await service.dismissBanner(VisibleVaultBanner.Premium); + await service.dismissBanner(userId, VisibleVaultBanner.Premium); }); afterEach(() => { @@ -134,7 +140,7 @@ describe("VaultBannersService", () => { it("updates state on first dismiss", async () => { const state = await firstValueFrom( - fakeStateProvider.getActive(PREMIUM_BANNER_REPROMPT_KEY).state$, + fakeStateProvider.getUser(userId, PREMIUM_BANNER_REPROMPT_KEY).state$, ); const oneWeekLater = new Date("2023-06-15"); @@ -148,7 +154,7 @@ describe("VaultBannersService", () => { it("updates state on second dismiss", async () => { const state = await firstValueFrom( - fakeStateProvider.getActive(PREMIUM_BANNER_REPROMPT_KEY).state$, + fakeStateProvider.getUser(userId, PREMIUM_BANNER_REPROMPT_KEY).state$, ); const oneMonthLater = new Date("2023-07-08"); @@ -162,7 +168,7 @@ describe("VaultBannersService", () => { it("updates state on third dismiss", async () => { const state = await firstValueFrom( - fakeStateProvider.getActive(PREMIUM_BANNER_REPROMPT_KEY).state$, + fakeStateProvider.getUser(userId, PREMIUM_BANNER_REPROMPT_KEY).state$, ); const oneYearLater = new Date("2024-06-08"); @@ -178,40 +184,40 @@ describe("VaultBannersService", () => { describe("KDFSettings", () => { beforeEach(async () => { - hasMasterPassword.mockResolvedValue(true); - getKdfConfig.mockResolvedValue({ kdfType: KdfType.PBKDF2_SHA256, iterations: 599999 }); + userDecryptionOptions$.next({ hasMasterPassword: true }); + kdfConfig$.next({ kdfType: KdfType.PBKDF2_SHA256, iterations: 599999 }); }); it("shows low KDF iteration banner", async () => { service = TestBed.inject(VaultBannersService); - expect(await service.shouldShowLowKDFBanner()).toBe(true); + expect(await service.shouldShowLowKDFBanner(userId)).toBe(true); }); it("does not show low KDF iteration banner if KDF type is not PBKDF2_SHA256", async () => { - getKdfConfig.mockResolvedValue({ kdfType: KdfType.Argon2id, iterations: 600001 }); + kdfConfig$.next({ kdfType: KdfType.Argon2id, iterations: 600001 }); service = TestBed.inject(VaultBannersService); - expect(await service.shouldShowLowKDFBanner()).toBe(false); + expect(await service.shouldShowLowKDFBanner(userId)).toBe(false); }); it("does not show low KDF for iterations about 600,000", async () => { - getKdfConfig.mockResolvedValue({ kdfType: KdfType.PBKDF2_SHA256, iterations: 600001 }); + kdfConfig$.next({ kdfType: KdfType.PBKDF2_SHA256, iterations: 600001 }); service = TestBed.inject(VaultBannersService); - expect(await service.shouldShowLowKDFBanner()).toBe(false); + expect(await service.shouldShowLowKDFBanner(userId)).toBe(false); }); it("dismisses low KDF iteration banner", async () => { service = TestBed.inject(VaultBannersService); - expect(await service.shouldShowLowKDFBanner()).toBe(true); + expect(await service.shouldShowLowKDFBanner(userId)).toBe(true); - await service.dismissBanner(VisibleVaultBanner.KDFSettings); + await service.dismissBanner(userId, VisibleVaultBanner.KDFSettings); - expect(await service.shouldShowLowKDFBanner()).toBe(false); + expect(await service.shouldShowLowKDFBanner(userId)).toBe(false); }); }); @@ -228,39 +234,44 @@ describe("VaultBannersService", () => { it("shows outdated browser banner", async () => { service = TestBed.inject(VaultBannersService); - expect(await service.shouldShowUpdateBrowserBanner()).toBe(true); + expect(await service.shouldShowUpdateBrowserBanner(userId)).toBe(true); }); it("dismisses outdated browser banner", async () => { service = TestBed.inject(VaultBannersService); - expect(await service.shouldShowUpdateBrowserBanner()).toBe(true); + expect(await service.shouldShowUpdateBrowserBanner(userId)).toBe(true); - await service.dismissBanner(VisibleVaultBanner.OutdatedBrowser); + await service.dismissBanner(userId, VisibleVaultBanner.OutdatedBrowser); - expect(await service.shouldShowUpdateBrowserBanner()).toBe(false); + expect(await service.shouldShowUpdateBrowserBanner(userId)).toBe(false); }); }); describe("VerifyEmail", () => { beforeEach(async () => { - getEmailVerified.mockResolvedValue(false); + accounts$.next({ + [userId]: { + ...accounts$.value[userId], + emailVerified: false, + }, + }); }); it("shows verify email banner", async () => { service = TestBed.inject(VaultBannersService); - expect(await service.shouldShowVerifyEmailBanner()).toBe(true); + expect(await service.shouldShowVerifyEmailBanner(userId)).toBe(true); }); it("dismisses verify email banner", async () => { service = TestBed.inject(VaultBannersService); - expect(await service.shouldShowVerifyEmailBanner()).toBe(true); + expect(await service.shouldShowVerifyEmailBanner(userId)).toBe(true); - await service.dismissBanner(VisibleVaultBanner.VerifyEmail); + await service.dismissBanner(userId, VisibleVaultBanner.VerifyEmail); - expect(await service.shouldShowVerifyEmailBanner()).toBe(false); + expect(await service.shouldShowVerifyEmailBanner(userId)).toBe(false); }); }); }); diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts index c18b046e35e..390b95fa2b1 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts @@ -1,28 +1,18 @@ import { Injectable } from "@angular/core"; -import { - Subject, - Observable, - combineLatest, - firstValueFrom, - map, - mergeMap, - take, - switchMap, - of, -} from "rxjs"; +import { Observable, combineLatest, firstValueFrom, map, filter, mergeMap, take } from "rxjs"; +import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateProvider, - ActiveUserState, PREMIUM_BANNER_DISK_LOCAL, BANNERS_DISMISSED_DISK, UserKeyDefinition, + SingleUserState, } from "@bitwarden/common/platform/state"; +import { UserId } from "@bitwarden/common/types/guid"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { PBKDF2KdfConfig, KdfConfigService, KdfType } from "@bitwarden/key-management"; @@ -62,47 +52,25 @@ export const BANNERS_DISMISSED_DISK_KEY = new UserKeyDefinition; - - private premiumBannerState: ActiveUserState; - private sessionBannerState: ActiveUserState; - - /** - * Emits when the sync service has completed a sync - * - * This is needed because `hasPremiumFromAnySource$` will emit false until the sync is completed - * resulting in the premium banner being shown briefly on startup when the user has access to - * premium features. - */ - private syncCompleted$ = new Subject(); - constructor( - private tokenService: TokenService, - private userVerificationService: UserVerificationService, + private accountService: AccountService, private stateProvider: StateProvider, private billingAccountProfileStateService: BillingAccountProfileStateService, private platformUtilsService: PlatformUtilsService, private kdfConfigService: KdfConfigService, private syncService: SyncService, - private accountService: AccountService, - ) { - this.pollUntilSynced(); - this.premiumBannerState = this.stateProvider.getActive(PREMIUM_BANNER_REPROMPT_KEY); - this.sessionBannerState = this.stateProvider.getActive(BANNERS_DISMISSED_DISK_KEY); + private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, + ) {} - const premiumSources$ = this.accountService.activeAccount$.pipe( - take(1), - switchMap((account) => { - return combineLatest([ - account - ? this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id) - : of(false), - this.premiumBannerState.state$, - ]); - }), - ); + shouldShowPremiumBanner$(userId: UserId): Observable { + const premiumBannerState = this.premiumBannerState(userId); + const premiumSources$ = combineLatest([ + this.billingAccountProfileStateService.hasPremiumFromAnySource$(userId), + premiumBannerState.state$, + ]); - this.shouldShowPremiumBanner$ = this.syncCompleted$.pipe( + return this.syncService.lastSync$(userId).pipe( + filter((lastSync) => lastSync !== null), take(1), // Wait until the first sync is complete before considering the premium status mergeMap(() => premiumSources$), map(([canAccessPremium, dismissedState]) => { @@ -122,9 +90,9 @@ export class VaultBannersService { } /** Returns true when the update browser banner should be shown */ - async shouldShowUpdateBrowserBanner(): Promise { + async shouldShowUpdateBrowserBanner(userId: UserId): Promise { const outdatedBrowser = window.navigator.userAgent.indexOf("MSIE") !== -1; - const alreadyDismissed = (await this.getBannerDismissedState()).includes( + const alreadyDismissed = (await this.getBannerDismissedState(userId)).includes( VisibleVaultBanner.OutdatedBrowser, ); @@ -132,10 +100,12 @@ export class VaultBannersService { } /** Returns true when the verify email banner should be shown */ - async shouldShowVerifyEmailBanner(): Promise { - const needsVerification = !(await this.tokenService.getEmailVerified()); + async shouldShowVerifyEmailBanner(userId: UserId): Promise { + const needsVerification = !( + await firstValueFrom(this.accountService.accounts$.pipe(map((accounts) => accounts[userId]))) + )?.emailVerified; - const alreadyDismissed = (await this.getBannerDismissedState()).includes( + const alreadyDismissed = (await this.getBannerDismissedState(userId)).includes( VisibleVaultBanner.VerifyEmail, ); @@ -143,12 +113,14 @@ export class VaultBannersService { } /** Returns true when the low KDF iteration banner should be shown */ - async shouldShowLowKDFBanner(): Promise { - const hasLowKDF = (await this.userVerificationService.hasMasterPassword()) - ? await this.isLowKdfIteration() + async shouldShowLowKDFBanner(userId: UserId): Promise { + const hasLowKDF = ( + await firstValueFrom(this.userDecryptionOptionsService.userDecryptionOptionsById$(userId)) + )?.hasMasterPassword + ? await this.isLowKdfIteration(userId) : false; - const alreadyDismissed = (await this.getBannerDismissedState()).includes( + const alreadyDismissed = (await this.getBannerDismissedState(userId)).includes( VisibleVaultBanner.KDFSettings, ); @@ -156,11 +128,11 @@ export class VaultBannersService { } /** Dismiss the given banner and perform any respective side effects */ - async dismissBanner(banner: SessionBanners): Promise { + async dismissBanner(userId: UserId, banner: SessionBanners): Promise { if (banner === VisibleVaultBanner.Premium) { - await this.dismissPremiumBanner(); + await this.dismissPremiumBanner(userId); } else { - await this.sessionBannerState.update((current) => { + await this.sessionBannerState(userId).update((current) => { const bannersDismissed = current ?? []; return [...bannersDismissed, banner]; @@ -168,16 +140,32 @@ export class VaultBannersService { } } + /** + * + * @returns a SingleUserState for the premium banner reprompt state + */ + private premiumBannerState(userId: UserId): SingleUserState { + return this.stateProvider.getUser(userId, PREMIUM_BANNER_REPROMPT_KEY); + } + + /** + * + * @returns a SingleUserState for the session banners dismissed state + */ + private sessionBannerState(userId: UserId): SingleUserState { + return this.stateProvider.getUser(userId, BANNERS_DISMISSED_DISK_KEY); + } + /** Returns banners that have already been dismissed */ - private async getBannerDismissedState(): Promise { + private async getBannerDismissedState(userId: UserId): Promise { // `state$` can emit null when a value has not been set yet, // use nullish coalescing to default to an empty array - return (await firstValueFrom(this.sessionBannerState.state$)) ?? []; + return (await firstValueFrom(this.sessionBannerState(userId).state$)) ?? []; } /** Increment dismissal state of the premium banner */ - private async dismissPremiumBanner(): Promise { - await this.premiumBannerState.update((current) => { + private async dismissPremiumBanner(userId: UserId): Promise { + await this.premiumBannerState(userId).update((current) => { const numberOfDismissals = current?.numberOfDismissals ?? 0; const now = new Date(); @@ -213,22 +201,11 @@ export class VaultBannersService { }); } - private async isLowKdfIteration() { - const kdfConfig = await this.kdfConfigService.getKdfConfig(); + private async isLowKdfIteration(userId: UserId) { + const kdfConfig = await firstValueFrom(this.kdfConfigService.getKdfConfig$(userId)); return ( kdfConfig.kdfType === KdfType.PBKDF2_SHA256 && kdfConfig.iterations < PBKDF2KdfConfig.ITERATIONS.defaultValue ); } - - /** Poll the `syncService` until a sync is completed */ - private pollUntilSynced() { - const interval = setInterval(async () => { - const lastSync = await this.syncService.getLastSync(); - if (lastSync !== null) { - clearInterval(interval); - this.syncCompleted$.next(); - } - }, 200); - } } diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.spec.ts b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.spec.ts index 5fdac63e932..f35a93f8b9c 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.spec.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.spec.ts @@ -2,13 +2,17 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { By } from "@angular/platform-browser"; import { RouterTestingModule } from "@angular/router/testing"; import { mock } from "jest-mock-extended"; -import { BehaviorSubject } from "rxjs"; +import { BehaviorSubject, Observable } from "rxjs"; import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { BannerComponent, BannerModule } from "@bitwarden/components"; import { VerifyEmailComponent } from "../../../auth/settings/verify-email.component"; @@ -21,21 +25,25 @@ describe("VaultBannersComponent", () => { let component: VaultBannersComponent; let fixture: ComponentFixture; const premiumBanner$ = new BehaviorSubject(false); + const mockUserId = Utils.newGuid() as UserId; const bannerService = mock({ - shouldShowPremiumBanner$: premiumBanner$, + shouldShowPremiumBanner$: jest.fn((userId$: Observable) => premiumBanner$), shouldShowUpdateBrowserBanner: jest.fn(), shouldShowVerifyEmailBanner: jest.fn(), shouldShowLowKDFBanner: jest.fn(), dismissBanner: jest.fn(), }); + const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); + beforeEach(async () => { - bannerService.shouldShowPremiumBanner$ = premiumBanner$; bannerService.shouldShowUpdateBrowserBanner.mockResolvedValue(false); bannerService.shouldShowVerifyEmailBanner.mockResolvedValue(false); bannerService.shouldShowLowKDFBanner.mockResolvedValue(false); + premiumBanner$.next(false); + await TestBed.configureTestingModule({ imports: [ BannerModule, @@ -62,6 +70,10 @@ describe("VaultBannersComponent", () => { provide: TokenService, useValue: mock(), }, + { + provide: AccountService, + useValue: accountService, + }, ], }) .overrideProvider(VaultBannersService, { useValue: bannerService }) @@ -135,7 +147,7 @@ describe("VaultBannersComponent", () => { dismissButton.dispatchEvent(new Event("click")); - expect(bannerService.dismissBanner).toHaveBeenCalledWith(banner); + expect(bannerService.dismissBanner).toHaveBeenCalledWith(mockUserId, banner); expect(component.visibleBanners).toEqual([]); }); diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts index 933b4899c94..161b2ccb7ef 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts @@ -2,8 +2,9 @@ // @ts-strict-ignore import { Component, Input, OnInit } from "@angular/core"; import { Router } from "@angular/router"; -import { Observable } from "rxjs"; +import { firstValueFrom, map, Observable, switchMap } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { BannerModule } from "@bitwarden/components"; @@ -26,12 +27,17 @@ export class VaultBannersComponent implements OnInit { VisibleVaultBanner = VisibleVaultBanner; @Input() organizationsPaymentStatus: FreeTrial[] = []; + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + constructor( private vaultBannerService: VaultBannersService, private router: Router, private i18nService: I18nService, + private accountService: AccountService, ) { - this.premiumBannerVisible$ = this.vaultBannerService.shouldShowPremiumBanner$; + this.premiumBannerVisible$ = this.activeUserId$.pipe( + switchMap((userId) => this.vaultBannerService.shouldShowPremiumBanner$(userId)), + ); } async ngOnInit(): Promise { @@ -39,7 +45,8 @@ export class VaultBannersComponent implements OnInit { } async dismissBanner(banner: VisibleVaultBanner): Promise { - await this.vaultBannerService.dismissBanner(banner); + const activeUserId = await firstValueFrom(this.activeUserId$); + await this.vaultBannerService.dismissBanner(activeUserId, banner); await this.determineVisibleBanners(); } @@ -57,9 +64,12 @@ export class VaultBannersComponent implements OnInit { /** Determine which banners should be present */ private async determineVisibleBanners(): Promise { - const showBrowserOutdated = await this.vaultBannerService.shouldShowUpdateBrowserBanner(); - const showVerifyEmail = await this.vaultBannerService.shouldShowVerifyEmailBanner(); - const showLowKdf = await this.vaultBannerService.shouldShowLowKDFBanner(); + const activeUserId = await firstValueFrom(this.activeUserId$); + + const showBrowserOutdated = + await this.vaultBannerService.shouldShowUpdateBrowserBanner(activeUserId); + const showVerifyEmail = await this.vaultBannerService.shouldShowVerifyEmailBanner(activeUserId); + const showLowKdf = await this.vaultBannerService.shouldShowLowKDFBanner(activeUserId); this.visibleBanners = [ showBrowserOutdated ? VisibleVaultBanner.OutdatedBrowser : null,