1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-04-07 18:57:06 +02:00

[PM-12034] Remove usage of ActiveUserState from vault-banners.service (#11543)

* Migrated banner service from using active user state

* Fixed unit tests for the vault banner service

* Updated component to pass user id required by the banner service

* Updated component tests

* Added comments

* Fixed unit tests

* Updated vault banner service to use lastSync$ version and removed polling

* Updated to use UserDecryptionOptions

* Updated to use getKdfConfig$

* Updated shouldShowVerifyEmailBanner to use account observable

* Added takewhile operator to only make calls when userId is present

* Simplified to use sing userId

* Simplified to use sing userId
This commit is contained in:
SmithThe4th 2025-01-09 13:12:08 -05:00 committed by GitHub
parent 06ca00f3c1
commit 14568f11dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 153 additions and 143 deletions

View File

@ -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<boolean>(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<Date | null>(null);
const userDecryptionOptions$ = new BehaviorSubject<UserDecryptionOptions>({
hasMasterPassword: true,
});
const kdfConfig$ = new BehaviorSubject({ kdfType: KdfType.PBKDF2_SHA256, iterations: 600000 });
const accounts$ = new BehaviorSubject<Record<UserId, AccountInfo>>({
[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);
});
});
});

View File

@ -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<SessionBanners[]
@Injectable()
export class VaultBannersService {
shouldShowPremiumBanner$: Observable<boolean>;
private premiumBannerState: ActiveUserState<PremiumBannerReprompt>;
private sessionBannerState: ActiveUserState<SessionBanners[]>;
/**
* 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<void>();
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<boolean> {
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<boolean> {
async shouldShowUpdateBrowserBanner(userId: UserId): Promise<boolean> {
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<boolean> {
const needsVerification = !(await this.tokenService.getEmailVerified());
async shouldShowVerifyEmailBanner(userId: UserId): Promise<boolean> {
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<boolean> {
const hasLowKDF = (await this.userVerificationService.hasMasterPassword())
? await this.isLowKdfIteration()
async shouldShowLowKDFBanner(userId: UserId): Promise<boolean> {
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<void> {
async dismissBanner(userId: UserId, banner: SessionBanners): Promise<void> {
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<PremiumBannerReprompt> {
return this.stateProvider.getUser(userId, PREMIUM_BANNER_REPROMPT_KEY);
}
/**
*
* @returns a SingleUserState for the session banners dismissed state
*/
private sessionBannerState(userId: UserId): SingleUserState<SessionBanners[]> {
return this.stateProvider.getUser(userId, BANNERS_DISMISSED_DISK_KEY);
}
/** Returns banners that have already been dismissed */
private async getBannerDismissedState(): Promise<SessionBanners[]> {
private async getBannerDismissedState(userId: UserId): Promise<SessionBanners[]> {
// `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<void> {
await this.premiumBannerState.update((current) => {
private async dismissPremiumBanner(userId: UserId): Promise<void> {
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);
}
}

View File

@ -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<VaultBannersComponent>;
const premiumBanner$ = new BehaviorSubject<boolean>(false);
const mockUserId = Utils.newGuid() as UserId;
const bannerService = mock<VaultBannersService>({
shouldShowPremiumBanner$: premiumBanner$,
shouldShowPremiumBanner$: jest.fn((userId$: Observable<UserId>) => 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<TokenService>(),
},
{
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([]);
});

View File

@ -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<void> {
@ -39,7 +45,8 @@ export class VaultBannersComponent implements OnInit {
}
async dismissBanner(banner: VisibleVaultBanner): Promise<void> {
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<void> {
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,