1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

[PM-18802] - Autofill Settings Nudges and Settings Badge (#14439)

* autofill nudge

* remove undismiss logic

* revert change to popup view cache service

* move browser autofill logic to platform. cleanup

* fix test

* adjustments to autofill nudges

* add missing provider

* updates to autofill nudges

* fix date logic

* change autofillBrowserSettingsService isBrowserAutofillSettingOverridden to function

* fix up browser autofill overridden settings logic

* remove check for privacy in isBrowserAutofillSettingOverridden
This commit is contained in:
Jordan Aasen
2025-05-09 08:52:54 -07:00
committed by GitHub
parent 1edca39fa2
commit a7efd2158e
15 changed files with 240 additions and 62 deletions

View File

@@ -1589,6 +1589,24 @@
"autofillSuggestionsSectionTitle": { "autofillSuggestionsSectionTitle": {
"message": "Autofill suggestions" "message": "Autofill suggestions"
}, },
"autofillSpotlightTitle": {
"message": "Easily find autofill suggestions"
},
"autofillSpotlightDesc": {
"message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden."
},
"turnOffBrowserAutofill": {
"message": "Turn off $BROWSER$ autofill",
"placeholders": {
"browser": {
"content": "$1",
"example": "Chrome"
}
}
},
"turnOffAutofill": {
"message": "Turn off autofill"
},
"showInlineMenuLabel": { "showInlineMenuLabel": {
"message": "Show autofill suggestions on form fields" "message": "Show autofill suggestions on form fields"
}, },

View File

@@ -6,6 +6,16 @@
</popup-header> </popup-header>
<div class="tw-bg-background-alt"> <div class="tw-bg-background-alt">
<div *ngIf="!defaultBrowserAutofillDisabled && (showSpotlightNudge$ | async)" class="tw-mb-6">
<bit-spotlight
[title]="'autofillSpotlightTitle' | i18n"
[subtitle]="'autofillSpotlightDesc' | i18n"
[buttonText]="spotlightButtonText"
(onDismiss)="dismissSpotlight()"
(onButtonClick)="openURI($event, disablePasswordManagerURI)"
[buttonIcon]="spotlightButtonIcon"
></bit-spotlight>
</div>
<bit-section> <bit-section>
<bit-section-header> <bit-section-header>
<h2 bitTypography="h6">{{ "autofillSuggestionsSectionTitle" | i18n }}</h2> <h2 bitTypography="h6">{{ "autofillSuggestionsSectionTitle" | i18n }}</h2>

View File

@@ -11,9 +11,11 @@ import {
FormControl, FormControl,
} from "@angular/forms"; } from "@angular/forms";
import { RouterModule } from "@angular/router"; import { RouterModule } from "@angular/router";
import { firstValueFrom } from "rxjs"; import { Observable, filter, firstValueFrom, map, switchMap } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { import {
AutofillOverlayVisibility, AutofillOverlayVisibility,
BrowserClientVendors, BrowserClientVendors,
@@ -53,7 +55,9 @@ import {
SelectModule, SelectModule,
TypographyModule, TypographyModule,
} from "@bitwarden/components"; } from "@bitwarden/components";
import { SpotlightComponent, VaultNudgesService, VaultNudgeType } from "@bitwarden/vault";
import { AutofillBrowserSettingsService } from "../../../autofill/services/autofill-browser-settings.service";
import { BrowserApi } from "../../../platform/browser/browser-api"; import { BrowserApi } from "../../../platform/browser/browser-api";
import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component";
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
@@ -81,6 +85,7 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co
SelectModule, SelectModule,
TypographyModule, TypographyModule,
ReactiveFormsModule, ReactiveFormsModule,
SpotlightComponent,
], ],
}) })
export class AutofillComponent implements OnInit { export class AutofillComponent implements OnInit {
@@ -100,6 +105,14 @@ export class AutofillComponent implements OnInit {
protected browserClientIsUnknown: boolean; protected browserClientIsUnknown: boolean;
protected autofillOnPageLoadFromPolicy$ = protected autofillOnPageLoadFromPolicy$ =
this.autofillSettingsService.activateAutofillOnPageLoadFromPolicy$; this.autofillSettingsService.activateAutofillOnPageLoadFromPolicy$;
protected showSpotlightNudge$: Observable<boolean> = this.accountService.activeAccount$.pipe(
filter((account): account is Account => account !== null),
switchMap((account) =>
this.vaultNudgesService
.showNudge$(VaultNudgeType.AutofillNudge, account.id)
.pipe(map((nudgeStatus) => !nudgeStatus.hasSpotlightDismissed)),
),
);
protected autofillOnPageLoadForm = new FormGroup({ protected autofillOnPageLoadForm = new FormGroup({
autofillOnPageLoad: new FormControl(), autofillOnPageLoad: new FormControl(),
@@ -142,6 +155,9 @@ export class AutofillComponent implements OnInit {
private configService: ConfigService, private configService: ConfigService,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private destroyRef: DestroyRef, private destroyRef: DestroyRef,
private vaultNudgesService: VaultNudgesService,
private accountService: AccountService,
private autofillBrowserSettingsService: AutofillBrowserSettingsService,
) { ) {
this.autofillOnPageLoadOptions = [ this.autofillOnPageLoadOptions = [
{ name: this.i18nService.t("autoFillOnPageLoadYes"), value: true }, { name: this.i18nService.t("autoFillOnPageLoadYes"), value: true },
@@ -165,7 +181,7 @@ export class AutofillComponent implements OnInit {
{ name: i18nService.t("never"), value: UriMatchStrategy.Never }, { name: i18nService.t("never"), value: UriMatchStrategy.Never },
]; ];
this.browserClientVendor = this.getBrowserClientVendor(); this.browserClientVendor = BrowserApi.getBrowserClientVendor(window);
this.disablePasswordManagerURI = DisablePasswordManagerUris[this.browserClientVendor]; this.disablePasswordManagerURI = DisablePasswordManagerUris[this.browserClientVendor];
this.browserShortcutsURI = BrowserShortcutsUris[this.browserClientVendor]; this.browserShortcutsURI = BrowserShortcutsUris[this.browserClientVendor];
this.browserClientIsUnknown = this.browserClientVendor === BrowserClientVendors.Unknown; this.browserClientIsUnknown = this.browserClientVendor === BrowserClientVendors.Unknown;
@@ -173,7 +189,11 @@ export class AutofillComponent implements OnInit {
async ngOnInit() { async ngOnInit() {
this.canOverrideBrowserAutofillSetting = !this.browserClientIsUnknown; this.canOverrideBrowserAutofillSetting = !this.browserClientIsUnknown;
this.defaultBrowserAutofillDisabled = await this.browserAutofillSettingCurrentlyOverridden();
this.defaultBrowserAutofillDisabled =
await this.autofillBrowserSettingsService.isBrowserAutofillSettingOverridden(
this.browserClientVendor,
);
this.inlineMenuVisibility = await firstValueFrom( this.inlineMenuVisibility = await firstValueFrom(
this.autofillSettingsService.inlineMenuVisibility$, this.autofillSettingsService.inlineMenuVisibility$,
@@ -308,6 +328,27 @@ export class AutofillComponent implements OnInit {
); );
} }
get spotlightButtonIcon() {
if (this.browserClientVendor === BrowserClientVendors.Unknown) {
return "bwi-external-link";
}
return null;
}
get spotlightButtonText() {
if (this.browserClientVendor === BrowserClientVendors.Unknown) {
return this.i18nService.t("turnOffAutofill");
}
return this.i18nService.t("turnOffBrowserAutofill", this.browserClientVendor);
}
async dismissSpotlight() {
await this.vaultNudgesService.dismissNudge(
VaultNudgeType.AutofillNudge,
await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)),
);
}
async updateInlineMenuVisibility() { async updateInlineMenuVisibility() {
if (!this.enableInlineMenu) { if (!this.enableInlineMenu) {
this.enableInlineMenuOnIconSelect = false; this.enableInlineMenuOnIconSelect = false;
@@ -346,26 +387,6 @@ export class AutofillComponent implements OnInit {
} }
} }
private getBrowserClientVendor(): BrowserClientVendor {
if (this.platformUtilsService.isChrome()) {
return BrowserClientVendors.Chrome;
}
if (this.platformUtilsService.isOpera()) {
return BrowserClientVendors.Opera;
}
if (this.platformUtilsService.isEdge()) {
return BrowserClientVendors.Edge;
}
if (this.platformUtilsService.isVivaldi()) {
return BrowserClientVendors.Vivaldi;
}
return BrowserClientVendors.Unknown;
}
protected async openURI(event: Event, uri: BrowserShortcutsUri | DisablePasswordManagerUri) { protected async openURI(event: Event, uri: BrowserShortcutsUri | DisablePasswordManagerUri) {
event.preventDefault(); event.preventDefault();
@@ -422,7 +443,7 @@ export class AutofillComponent implements OnInit {
if ( if (
this.inlineMenuVisibility === AutofillOverlayVisibility.Off || this.inlineMenuVisibility === AutofillOverlayVisibility.Off ||
!this.canOverrideBrowserAutofillSetting || !this.canOverrideBrowserAutofillSetting ||
(await this.browserAutofillSettingCurrentlyOverridden()) this.defaultBrowserAutofillDisabled
) { ) {
return; return;
} }
@@ -460,6 +481,9 @@ export class AutofillComponent implements OnInit {
} }
await BrowserApi.updateDefaultBrowserAutofillSettings(!this.defaultBrowserAutofillDisabled); await BrowserApi.updateDefaultBrowserAutofillSettings(!this.defaultBrowserAutofillDisabled);
this.autofillBrowserSettingsService.setDefaultBrowserAutofillDisabled(
this.defaultBrowserAutofillDisabled,
);
} }
private handleOverrideDialogAccept = async () => { private handleOverrideDialogAccept = async () => {
@@ -467,18 +491,6 @@ export class AutofillComponent implements OnInit {
await this.updateDefaultBrowserAutofillDisabled(); await this.updateDefaultBrowserAutofillDisabled();
}; };
async browserAutofillSettingCurrentlyOverridden() {
if (!this.canOverrideBrowserAutofillSetting) {
return false;
}
if (!(await this.privacyPermissionGranted())) {
return false;
}
return await BrowserApi.browserAutofillSettingsOverridden();
}
async privacyPermissionGranted(): Promise<boolean> { async privacyPermissionGranted(): Promise<boolean> {
return await BrowserApi.permissionsGranted(["privacy"]); return await BrowserApi.permissionsGranted(["privacy"]);
} }

View File

@@ -0,0 +1,31 @@
import { Injectable } from "@angular/core";
import { BehaviorSubject, Observable } from "rxjs";
import { BrowserClientVendors } from "@bitwarden/common/autofill/constants";
import { BrowserClientVendor } from "@bitwarden/common/autofill/types";
import { BrowserApi } from "../../platform/browser/browser-api";
/**
* Service class for various Autofill-related browser API operations.
*/
@Injectable({
providedIn: "root",
})
export class AutofillBrowserSettingsService {
async isBrowserAutofillSettingOverridden(browserClient: BrowserClientVendor) {
return (
browserClient !== BrowserClientVendors.Unknown &&
(await BrowserApi.browserAutofillSettingsOverridden())
);
}
private _defaultBrowserAutofillDisabled$ = new BehaviorSubject<boolean>(false);
defaultBrowserAutofillDisabled$: Observable<boolean> =
this._defaultBrowserAutofillDisabled$.asObservable();
setDefaultBrowserAutofillDisabled(value: boolean) {
this._defaultBrowserAutofillDisabled$.next(value);
}
}

View File

@@ -2,6 +2,8 @@
// @ts-strict-ignore // @ts-strict-ignore
import { Observable } from "rxjs"; import { Observable } from "rxjs";
import { BrowserClientVendors } from "@bitwarden/common/autofill/constants";
import { BrowserClientVendor } from "@bitwarden/common/autofill/types";
import { DeviceType } from "@bitwarden/common/enums"; import { DeviceType } from "@bitwarden/common/enums";
import { isBrowserSafariApi } from "@bitwarden/platform"; import { isBrowserSafariApi } from "@bitwarden/platform";
@@ -131,6 +133,27 @@ export class BrowserApi {
}); });
} }
static getBrowserClientVendor(clientWindow: Window): BrowserClientVendor {
const device = BrowserPlatformUtilsService.getDevice(clientWindow);
switch (device) {
case DeviceType.ChromeExtension:
case DeviceType.ChromeBrowser:
return BrowserClientVendors.Chrome;
case DeviceType.OperaExtension:
case DeviceType.OperaBrowser:
return BrowserClientVendors.Opera;
case DeviceType.EdgeExtension:
case DeviceType.EdgeBrowser:
return BrowserClientVendors.Edge;
case DeviceType.VivaldiExtension:
case DeviceType.VivaldiBrowser:
return BrowserClientVendors.Vivaldi;
default:
return BrowserClientVendors.Unknown;
}
}
/** /**
* Gets the tab with the given id. * Gets the tab with the given id.
* *

View File

@@ -82,7 +82,7 @@ export class PopupViewCacheService implements ViewCacheService {
initialValue, initialValue,
persistNavigation, persistNavigation,
} = options; } = options;
const cachedValue = this.cache[key] const cachedValue = this.cache[key]?.value
? deserializer(JSON.parse(this.cache[key].value)) ? deserializer(JSON.parse(this.cache[key].value))
: initialValue; : initialValue;
const _signal = signal(cachedValue); const _signal = signal(cachedValue);

View File

@@ -17,7 +17,15 @@
<bit-item> <bit-item>
<a bit-item-content routerLink="/autofill"> <a bit-item-content routerLink="/autofill">
<i slot="start" class="bwi bwi-check-circle" aria-hidden="true"></i> <i slot="start" class="bwi bwi-check-circle" aria-hidden="true"></i>
{{ "autofill" | i18n }} <div class="tw-flex tw-items-center tw-justify-center">
<p class="tw-pr-2">{{ "autofill" | i18n }}</p>
<span
*ngIf="!isBrowserAutofillSettingOverridden && (showAutofillBadge$ | async)"
bitBadge
variant="notification"
>1</span
>
</div>
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i> <i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
</a> </a>
</bit-item> </bit-item>

View File

@@ -1,7 +1,15 @@
import { CommonModule } from "@angular/common"; import { CommonModule } from "@angular/common";
import { Component } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { RouterModule } from "@angular/router"; import { RouterModule } from "@angular/router";
import { filter, firstValueFrom, Observable, shareReplay, switchMap } from "rxjs"; import {
combineLatest,
filter,
firstValueFrom,
map,
Observable,
shareReplay,
switchMap,
} from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
@@ -12,6 +20,8 @@ import { BadgeComponent, ItemModule } from "@bitwarden/components";
import { NudgeStatus, VaultNudgesService, VaultNudgeType } from "@bitwarden/vault"; import { NudgeStatus, VaultNudgesService, VaultNudgeType } from "@bitwarden/vault";
import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component"; import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component";
import { AutofillBrowserSettingsService } from "../../../autofill/services/autofill-browser-settings.service";
import { BrowserApi } from "../../../platform/browser/browser-api";
import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component";
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component";
@@ -31,8 +41,10 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co
BadgeComponent, BadgeComponent,
], ],
}) })
export class SettingsV2Component { export class SettingsV2Component implements OnInit {
VaultNudgeType = VaultNudgeType; VaultNudgeType = VaultNudgeType;
activeUserId: UserId | null = null;
protected isBrowserAutofillSettingOverridden = false;
private authenticatedAccount$: Observable<Account> = this.accountService.activeAccount$.pipe( private authenticatedAccount$: Observable<Account> = this.accountService.activeAccount$.pipe(
filter((account): account is Account => account !== null), filter((account): account is Account => account !== null),
@@ -51,6 +63,19 @@ export class SettingsV2Component {
), ),
); );
showAutofillBadge$: Observable<boolean> = combineLatest([
this.autofillBrowserSettingsService.defaultBrowserAutofillDisabled$,
this.authenticatedAccount$,
]).pipe(
switchMap(([defaultBrowserAutofillDisabled, account]) =>
this.vaultNudgesService.showNudge$(VaultNudgeType.AutofillNudge, account.id).pipe(
map((nudgeStatus) => {
return !defaultBrowserAutofillDisabled && nudgeStatus.hasBadgeDismissed === false;
}),
),
),
);
protected isNudgeFeatureEnabled$ = this.configService.getFeatureFlag$( protected isNudgeFeatureEnabled$ = this.configService.getFeatureFlag$(
FeatureFlag.PM8851_BrowserOnboardingNudge, FeatureFlag.PM8851_BrowserOnboardingNudge,
); );
@@ -58,9 +83,17 @@ export class SettingsV2Component {
constructor( constructor(
private readonly vaultNudgesService: VaultNudgesService, private readonly vaultNudgesService: VaultNudgesService,
private readonly accountService: AccountService, private readonly accountService: AccountService,
private readonly autofillBrowserSettingsService: AutofillBrowserSettingsService,
private readonly configService: ConfigService, private readonly configService: ConfigService,
) {} ) {}
async ngOnInit() {
this.isBrowserAutofillSettingOverridden =
await this.autofillBrowserSettingsService.isBrowserAutofillSettingOverridden(
BrowserApi.getBrowserClientVendor(window),
);
}
async dismissBadge(type: VaultNudgeType) { async dismissBadge(type: VaultNudgeType) {
if (!(await firstValueFrom(this.showVaultBadge$)).hasBadgeDismissed) { if (!(await firstValueFrom(this.showVaultBadge$)).hasBadgeDismissed) {
const account = await firstValueFrom(this.authenticatedAccount$); const account = await firstValueFrom(this.authenticatedAccount$);

View File

@@ -23,10 +23,9 @@
type="button" type="button"
buttonType="primary" buttonType="primary"
*ngIf="buttonText" *ngIf="buttonText"
(click)="handleButtonClick()" (click)="handleButtonClick($event)"
> >
{{ buttonText }} {{ buttonText }}
<i *ngIf="buttonIcon" [ngClass]="buttonIcon" class="bwi tw-ml-1" aria-hidden="true"></i>
</button> </button>
<ng-content></ng-content>
</div> </div>

View File

@@ -19,11 +19,13 @@ export class SpotlightComponent {
@Input() buttonText?: string; @Input() buttonText?: string;
// Wheter the component can be dismissed, if true, the component will not show a close button // Wheter the component can be dismissed, if true, the component will not show a close button
@Input() persistent = false; @Input() persistent = false;
// Optional icon to display on the button
@Input() buttonIcon: string | null = null;
@Output() onDismiss = new EventEmitter<void>(); @Output() onDismiss = new EventEmitter<void>();
@Output() onButtonClick = new EventEmitter<void>(); @Output() onButtonClick = new EventEmitter();
handleButtonClick(): void { handleButtonClick(event: MouseEvent): void {
this.onButtonClick.emit(); this.onButtonClick.emit(event);
} }
handleDismiss(): void { handleDismiss(): void {

View File

@@ -52,9 +52,9 @@ export const Persistent: Story = {
}, },
}; };
export const WithCustomButton: Story = { export const WithButtonIcon: Story = {
args: { args: {
buttonText: "Custom Button", buttonIcon: "bwi bwi-external-link",
}, },
render: (args) => ({ render: (args) => ({
props: args, props: args,
@@ -62,19 +62,9 @@ export const WithCustomButton: Story = {
<bit-spotlight <bit-spotlight
[title]="title" [title]="title"
[subtitle]="subtitle" [subtitle]="subtitle"
> buttonText="External Link"
<button buttonIcon="bwi-external-link"
class="tw-w-full" ></bit-spotlight>
bit-item-content
bitButton
type="button"
buttonType="primary"
(click)="handleButtonClick()"
>
External Link
<i slot="end" class="bwi bwi-external-link ml-2" aria-hidden="true"></i>
</button>
</bit-spotlight>
`, `,
}), }),
}; };

View File

@@ -0,0 +1,47 @@
import { Injectable, inject } from "@angular/core";
import { Observable, combineLatest, from, map, of } from "rxjs";
import { catchError } from "rxjs/operators";
import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { UserId } from "@bitwarden/common/types/guid";
import { DefaultSingleNudgeService } from "../default-single-nudge.service";
import { NudgeStatus, VaultNudgeType } from "../vault-nudges.service";
const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
/**
* Custom Nudge Service to use for the Autofill Nudge in the Vault
*/
@Injectable({
providedIn: "root",
})
export class AutofillNudgeService extends DefaultSingleNudgeService {
vaultProfileService = inject(VaultProfileService);
logService = inject(LogService);
nudgeStatus$(_: VaultNudgeType, userId: UserId): Observable<NudgeStatus> {
const profileDate$ = from(this.vaultProfileService.getProfileCreationDate(userId)).pipe(
catchError(() => {
this.logService.error("Error getting profile creation date");
// Default to today to ensure we show the nudge
return of(new Date());
}),
);
return combineLatest([
profileDate$,
this.getNudgeStatus$(VaultNudgeType.AutofillNudge, userId),
of(Date.now() - THIRTY_DAYS_MS),
]).pipe(
map(([profileCreationDate, status, profileCutoff]) => {
const profileOlderThanCutoff = profileCreationDate.getTime() < profileCutoff;
return {
hasBadgeDismissed: status.hasBadgeDismissed || profileOlderThanCutoff,
hasSpotlightDismissed: status.hasSpotlightDismissed || profileOlderThanCutoff,
};
}),
);
}
}

View File

@@ -1,3 +1,4 @@
export * from "./autofill-nudge.service";
export * from "./has-items-nudge.service"; export * from "./has-items-nudge.service";
export * from "./download-bitwarden-nudge.service"; export * from "./download-bitwarden-nudge.service";
export * from "./empty-vault-nudge.service"; export * from "./empty-vault-nudge.service";

View File

@@ -55,6 +55,7 @@ describe("Vault Nudges Service", () => {
useValue: mock<ApiService>(), useValue: mock<ApiService>(),
}, },
{ provide: CipherService, useValue: mock<CipherService>() }, { provide: CipherService, useValue: mock<CipherService>() },
{ provide: LogService, useValue: mock<LogService>() },
{ {
provide: AccountService, provide: AccountService,
useValue: mock<AccountService>(), useValue: mock<AccountService>(),

View File

@@ -9,6 +9,7 @@ import { UserId } from "@bitwarden/common/types/guid";
import { import {
HasItemsNudgeService, HasItemsNudgeService,
EmptyVaultNudgeService, EmptyVaultNudgeService,
AutofillNudgeService,
DownloadBitwardenNudgeService, DownloadBitwardenNudgeService,
NewItemNudgeService, NewItemNudgeService,
} from "./custom-nudges-services"; } from "./custom-nudges-services";
@@ -28,6 +29,7 @@ export enum VaultNudgeType {
*/ */
EmptyVaultNudge = "empty-vault-nudge", EmptyVaultNudge = "empty-vault-nudge",
HasVaultItems = "has-vault-items", HasVaultItems = "has-vault-items",
AutofillNudge = "autofill-nudge",
DownloadBitwarden = "download-bitwarden", DownloadBitwarden = "download-bitwarden",
newLoginItemStatus = "new-login-item-status", newLoginItemStatus = "new-login-item-status",
newCardItemStatus = "new-card-item-status", newCardItemStatus = "new-card-item-status",
@@ -57,6 +59,7 @@ export class VaultNudgesService {
private customNudgeServices: Partial<Record<VaultNudgeType, SingleNudgeService>> = { private customNudgeServices: Partial<Record<VaultNudgeType, SingleNudgeService>> = {
[VaultNudgeType.HasVaultItems]: inject(HasItemsNudgeService), [VaultNudgeType.HasVaultItems]: inject(HasItemsNudgeService),
[VaultNudgeType.EmptyVaultNudge]: inject(EmptyVaultNudgeService), [VaultNudgeType.EmptyVaultNudge]: inject(EmptyVaultNudgeService),
[VaultNudgeType.AutofillNudge]: inject(AutofillNudgeService),
[VaultNudgeType.DownloadBitwarden]: inject(DownloadBitwardenNudgeService), [VaultNudgeType.DownloadBitwarden]: inject(DownloadBitwardenNudgeService),
[VaultNudgeType.newLoginItemStatus]: this.newItemNudgeService, [VaultNudgeType.newLoginItemStatus]: this.newItemNudgeService,
[VaultNudgeType.newCardItemStatus]: this.newItemNudgeService, [VaultNudgeType.newCardItemStatus]: this.newItemNudgeService,