diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index e05d3fcd16..8d784a9678 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -736,6 +736,10 @@ "newUri": { "message": "New URI" }, + "addDomain": { + "message": "Add domain", + "description": "'Domain' here refers to an internet domain name (e.g. 'bitwarden.com') and the message in whole described the act of putting a domain value into the context." + }, "addedItem": { "message": "Item added" }, @@ -776,6 +780,9 @@ "enableAddLoginNotification": { "message": "Ask to add login" }, + "vaultSaveOptionsTitle": { + "message": "Save to vault options" + }, "addLoginNotificationDesc": { "message": "Ask to add an item if one isn't found in your vault." }, @@ -1967,6 +1974,10 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "domainsTitle": { + "message": "Domains", + "description": "A category title describing the concept of web domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -1976,6 +1987,15 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "websiteItemLabel": { + "message": "Website $number$ (URI)", + "placeholders": { + "number": { + "content": "$1", + "example": "3" + } + } + }, "excludedDomainsInvalidDomain": { "message": "$DOMAIN$ is not a valid domain", "placeholders": { @@ -1985,6 +2005,9 @@ } } }, + "excludedDomainsSavedSuccess": { + "message": "Excluded domain changes saved" + }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." diff --git a/apps/browser/src/autofill/popup/settings/excluded-domains-v1.component.html b/apps/browser/src/autofill/popup/settings/excluded-domains-v1.component.html new file mode 100644 index 0000000000..8f78ac1640 --- /dev/null +++ b/apps/browser/src/autofill/popup/settings/excluded-domains-v1.component.html @@ -0,0 +1,91 @@ +
+
+
+ +
+

+ {{ "excludedDomains" | i18n }} +

+
+ +
+
+
+
+
+ + +
+ +
+ + + + +
+
+ +
+
+
+ +
+
+
+
diff --git a/apps/browser/src/autofill/popup/settings/excluded-domains-v1.component.ts b/apps/browser/src/autofill/popup/settings/excluded-domains-v1.component.ts new file mode 100644 index 0000000000..362ac4896c --- /dev/null +++ b/apps/browser/src/autofill/popup/settings/excluded-domains-v1.component.ts @@ -0,0 +1,141 @@ +import { Component, NgZone, OnDestroy, OnInit } from "@angular/core"; +import { Router } from "@angular/router"; +import { firstValueFrom } from "rxjs"; + +import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.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 { BrowserApi } from "../../../platform/browser/browser-api"; +import { enableAccountSwitching } from "../../../platform/flags"; + +interface ExcludedDomain { + uri: string; + showCurrentUris: boolean; +} + +const BroadcasterSubscriptionId = "excludedDomains"; + +@Component({ + selector: "app-excluded-domains-v1", + templateUrl: "excluded-domains-v1.component.html", +}) +export class ExcludedDomainsV1Component implements OnInit, OnDestroy { + excludedDomains: ExcludedDomain[] = []; + existingExcludedDomains: ExcludedDomain[] = []; + currentUris: string[]; + loadCurrentUrisTimeout: number; + accountSwitcherEnabled = false; + + constructor( + private domainSettingsService: DomainSettingsService, + private i18nService: I18nService, + private router: Router, + private broadcasterService: BroadcasterService, + private ngZone: NgZone, + private platformUtilsService: PlatformUtilsService, + ) { + this.accountSwitcherEnabled = enableAccountSwitching(); + } + + async ngOnInit() { + const savedDomains = await firstValueFrom(this.domainSettingsService.neverDomains$); + if (savedDomains) { + for (const uri of Object.keys(savedDomains)) { + this.excludedDomains.push({ uri: uri, showCurrentUris: false }); + this.existingExcludedDomains.push({ uri: uri, showCurrentUris: false }); + } + } + + await this.loadCurrentUris(); + + this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { + // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.ngZone.run(async () => { + switch (message.command) { + case "tabChanged": + case "windowChanged": + if (this.loadCurrentUrisTimeout != null) { + window.clearTimeout(this.loadCurrentUrisTimeout); + } + this.loadCurrentUrisTimeout = window.setTimeout( + async () => await this.loadCurrentUris(), + 500, + ); + break; + default: + break; + } + }); + }); + } + + ngOnDestroy() { + this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); + } + + async addUri() { + this.excludedDomains.push({ uri: "", showCurrentUris: false }); + } + + async removeUri(i: number) { + this.excludedDomains.splice(i, 1); + } + + async submit() { + const savedDomains: { [name: string]: null } = {}; + const newExcludedDomains = this.getNewlyAddedDomains(this.excludedDomains); + for (const domain of this.excludedDomains) { + const resp = newExcludedDomains.filter((e) => e.uri === domain.uri); + if (resp.length === 0) { + savedDomains[domain.uri] = null; + } else { + if (domain.uri && domain.uri !== "") { + const validDomain = Utils.getHostname(domain.uri); + if (!validDomain) { + this.platformUtilsService.showToast( + "error", + null, + this.i18nService.t("excludedDomainsInvalidDomain", domain.uri), + ); + return; + } + savedDomains[validDomain] = null; + } + } + } + + await this.domainSettingsService.setNeverDomains(savedDomains); + // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.router.navigate(["/tabs/settings"]); + } + + trackByFunction(index: number, item: any) { + return index; + } + + getNewlyAddedDomains(domain: ExcludedDomain[]): ExcludedDomain[] { + const result = this.excludedDomains.filter( + (newDomain) => + !this.existingExcludedDomains.some((oldDomain) => newDomain.uri === oldDomain.uri), + ); + return result; + } + + toggleUriInput(domain: ExcludedDomain) { + domain.showCurrentUris = !domain.showCurrentUris; + } + + async loadCurrentUris() { + const tabs = await BrowserApi.tabsQuery({ windowType: "normal" }); + if (tabs) { + const uriSet = new Set(tabs.map((tab) => Utils.getHostname(tab.url))); + uriSet.delete(null); + this.currentUris = Array.from(uriSet); + } + } +} diff --git a/apps/browser/src/autofill/popup/settings/excluded-domains.component.html b/apps/browser/src/autofill/popup/settings/excluded-domains.component.html index 8f78ac1640..8f909a336b 100644 --- a/apps/browser/src/autofill/popup/settings/excluded-domains.component.html +++ b/apps/browser/src/autofill/popup/settings/excluded-domains.component.html @@ -1,91 +1,63 @@ -
-
-
- -
-

- {{ "excludedDomains" | i18n }} -

-
- -
-
-
-
-
- - -
- -
- - - - -
-
- -
-
-
- -
-
-
-
+ + {{ + "websiteItemLabel" | i18n: i + 1 + }} + +
{{ domain }}
+
+ + + + + + + + + + diff --git a/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts b/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts index 5dad991dfa..76e913a383 100644 --- a/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts +++ b/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts @@ -1,141 +1,166 @@ -import { Component, NgZone, OnDestroy, OnInit } from "@angular/core"; -import { Router } from "@angular/router"; +import { CommonModule } from "@angular/common"; +import { Component, OnDestroy, OnInit } from "@angular/core"; +import { FormsModule } from "@angular/forms"; +import { Router, RouterModule } from "@angular/router"; import { firstValueFrom } from "rxjs"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { NeverDomains } from "@bitwarden/common/models/domain/domain-service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.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 { + ButtonModule, + CardComponent, + FormFieldModule, + IconButtonModule, + ItemModule, + LinkModule, + SectionComponent, + SectionHeaderComponent, + TypographyModule, +} from "@bitwarden/components"; -import { BrowserApi } from "../../../platform/browser/browser-api"; import { enableAccountSwitching } from "../../../platform/flags"; +import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; +import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component"; +import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; +import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; -interface ExcludedDomain { - uri: string; - showCurrentUris: boolean; -} - -const BroadcasterSubscriptionId = "excludedDomains"; +const BroadcasterSubscriptionId = "excludedDomainsState"; @Component({ selector: "app-excluded-domains", templateUrl: "excluded-domains.component.html", + standalone: true, + imports: [ + ButtonModule, + CardComponent, + CommonModule, + FormFieldModule, + FormsModule, + IconButtonModule, + ItemModule, + JslibModule, + LinkModule, + PopOutComponent, + PopupFooterComponent, + PopupHeaderComponent, + PopupPageComponent, + RouterModule, + SectionComponent, + SectionHeaderComponent, + TypographyModule, + ], }) export class ExcludedDomainsComponent implements OnInit, OnDestroy { - excludedDomains: ExcludedDomain[] = []; - existingExcludedDomains: ExcludedDomain[] = []; - currentUris: string[]; - loadCurrentUrisTimeout: number; accountSwitcherEnabled = false; + dataIsPristine = true; + excludedDomainsState: string[] = []; + storedExcludedDomains: string[] = []; + // How many fields should be non-editable before editable fields + fieldsEditThreshold: number = 0; constructor( private domainSettingsService: DomainSettingsService, private i18nService: I18nService, private router: Router, private broadcasterService: BroadcasterService, - private ngZone: NgZone, private platformUtilsService: PlatformUtilsService, ) { this.accountSwitcherEnabled = enableAccountSwitching(); } async ngOnInit() { - const savedDomains = await firstValueFrom(this.domainSettingsService.neverDomains$); - if (savedDomains) { - for (const uri of Object.keys(savedDomains)) { - this.excludedDomains.push({ uri: uri, showCurrentUris: false }); - this.existingExcludedDomains.push({ uri: uri, showCurrentUris: false }); - } + const neverDomains = await firstValueFrom(this.domainSettingsService.neverDomains$); + + if (neverDomains) { + this.storedExcludedDomains = Object.keys(neverDomains); } - await this.loadCurrentUris(); + this.excludedDomainsState = [...this.storedExcludedDomains]; - this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.ngZone.run(async () => { - switch (message.command) { - case "tabChanged": - case "windowChanged": - if (this.loadCurrentUrisTimeout != null) { - window.clearTimeout(this.loadCurrentUrisTimeout); - } - this.loadCurrentUrisTimeout = window.setTimeout( - async () => await this.loadCurrentUris(), - 500, - ); - break; - default: - break; - } - }); - }); + // Do not allow the first x (pre-existing) fields to be edited + this.fieldsEditThreshold = this.storedExcludedDomains.length; } ngOnDestroy() { this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); } - async addUri() { - this.excludedDomains.push({ uri: "", showCurrentUris: false }); + async addNewDomain() { + // add empty field to the Domains list interface + this.excludedDomainsState.push(""); + + await this.fieldChange(); } - async removeUri(i: number) { - this.excludedDomains.splice(i, 1); + async removeDomain(i: number) { + this.excludedDomainsState.splice(i, 1); + + // if a pre-existing field was dropped, lower the edit threshold + if (i < this.fieldsEditThreshold) { + this.fieldsEditThreshold--; + } + + await this.fieldChange(); } - async submit() { - const savedDomains: { [name: string]: null } = {}; - const newExcludedDomains = this.getNewlyAddedDomains(this.excludedDomains); - for (const domain of this.excludedDomains) { - const resp = newExcludedDomains.filter((e) => e.uri === domain.uri); - if (resp.length === 0) { - savedDomains[domain.uri] = null; - } else { - if (domain.uri && domain.uri !== "") { - const validDomain = Utils.getHostname(domain.uri); - if (!validDomain) { - this.platformUtilsService.showToast( - "error", - null, - this.i18nService.t("excludedDomainsInvalidDomain", domain.uri), - ); - return; - } - savedDomains[validDomain] = null; + async fieldChange() { + if (this.dataIsPristine) { + this.dataIsPristine = false; + } + } + + async saveChanges() { + if (this.dataIsPristine) { + await this.router.navigate(["/notifications"]); + + return; + } + + const newExcludedDomainsSaveState: NeverDomains = {}; + const uniqueExcludedDomains = new Set(this.excludedDomainsState); + + for (const uri of uniqueExcludedDomains) { + if (uri && uri !== "") { + const validatedHost = Utils.getHostname(uri); + + if (!validatedHost) { + this.platformUtilsService.showToast( + "error", + null, + this.i18nService.t("excludedDomainsInvalidDomain", uri), + ); + + return; } + + newExcludedDomainsSaveState[validatedHost] = null; } } - await this.domainSettingsService.setNeverDomains(savedDomains); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["/tabs/settings"]); + try { + await this.domainSettingsService.setNeverDomains(newExcludedDomainsSaveState); + + this.platformUtilsService.showToast( + "success", + null, + this.i18nService.t("excludedDomainsSavedSuccess"), + ); + } catch { + this.platformUtilsService.showToast("error", null, this.i18nService.t("unexpectedError")); + + // Do not navigate on error + return; + } + + await this.router.navigate(["/notifications"]); } - trackByFunction(index: number, item: any) { + trackByFunction(index: number, _: string) { return index; } - - getNewlyAddedDomains(domain: ExcludedDomain[]): ExcludedDomain[] { - const result = this.excludedDomains.filter( - (newDomain) => - !this.existingExcludedDomains.some((oldDomain) => newDomain.uri === oldDomain.uri), - ); - return result; - } - - toggleUriInput(domain: ExcludedDomain) { - domain.showCurrentUris = !domain.showCurrentUris; - } - - async loadCurrentUris() { - const tabs = await BrowserApi.tabsQuery({ windowType: "normal" }); - if (tabs) { - const uriSet = new Set(tabs.map((tab) => Utils.getHostname(tab.url))); - uriSet.delete(null); - this.currentUris = Array.from(uriSet); - } - } } diff --git a/apps/browser/src/autofill/popup/settings/notifications-v1.component.html b/apps/browser/src/autofill/popup/settings/notifications-v1.component.html new file mode 100644 index 0000000000..89d83c9e48 --- /dev/null +++ b/apps/browser/src/autofill/popup/settings/notifications-v1.component.html @@ -0,0 +1,89 @@ +
+
+ +
+

+ {{ "notifications" | i18n }} +

+
+ +
+
+
+
+
+
+ + +
+
+ +
+
+
+
+ + +
+
+ +
+
+
+
+ + +
+
+ +
+
+
+ +
+
+
diff --git a/apps/browser/src/autofill/popup/settings/notifications-v1.component.ts b/apps/browser/src/autofill/popup/settings/notifications-v1.component.ts new file mode 100644 index 0000000000..442e91e55e --- /dev/null +++ b/apps/browser/src/autofill/popup/settings/notifications-v1.component.ts @@ -0,0 +1,53 @@ +import { Component, OnInit } from "@angular/core"; +import { firstValueFrom } from "rxjs"; + +import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service"; +import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; + +import { enableAccountSwitching } from "../../../platform/flags"; + +@Component({ + selector: "autofill-notification-v1-settings", + templateUrl: "notifications-v1.component.html", +}) +export class NotificationsSettingsV1Component implements OnInit { + enableAddLoginNotification = false; + enableChangedPasswordNotification = false; + enablePasskeys = true; + accountSwitcherEnabled = false; + + constructor( + private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction, + private vaultSettingsService: VaultSettingsService, + ) { + this.accountSwitcherEnabled = enableAccountSwitching(); + } + + async ngOnInit() { + this.enableAddLoginNotification = await firstValueFrom( + this.userNotificationSettingsService.enableAddedLoginPrompt$, + ); + + this.enableChangedPasswordNotification = await firstValueFrom( + this.userNotificationSettingsService.enableChangedPasswordPrompt$, + ); + + this.enablePasskeys = await firstValueFrom(this.vaultSettingsService.enablePasskeys$); + } + + async updateAddLoginNotification() { + await this.userNotificationSettingsService.setEnableAddedLoginPrompt( + this.enableAddLoginNotification, + ); + } + + async updateChangedPasswordNotification() { + await this.userNotificationSettingsService.setEnableChangedPasswordPrompt( + this.enableChangedPasswordNotification, + ); + } + + async updateEnablePasskeys() { + await this.vaultSettingsService.setEnablePasskeys(this.enablePasskeys); + } +} diff --git a/apps/browser/src/autofill/popup/settings/notifications.component.html b/apps/browser/src/autofill/popup/settings/notifications.component.html index 89d83c9e48..d39b61ce10 100644 --- a/apps/browser/src/autofill/popup/settings/notifications.component.html +++ b/apps/browser/src/autofill/popup/settings/notifications.component.html @@ -1,89 +1,56 @@ -
-
- + + + + + + + +
+ + +

{{ "vaultSaveOptionsTitle" | i18n }}

+
+ +
+ + +
+
+ + +
+
+ + +
+
+
+ + + {{ "excludedDomains" | i18n }} + + +
-

- {{ "notifications" | i18n }} -

-
- -
-
-
-
-
-
- - -
-
- -
-
-
-
- - -
-
- -
-
-
-
- - -
-
- -
-
-
- -
-
-
+ diff --git a/apps/browser/src/autofill/popup/settings/notifications.component.ts b/apps/browser/src/autofill/popup/settings/notifications.component.ts index f4a7773916..be447e3f88 100644 --- a/apps/browser/src/autofill/popup/settings/notifications.component.ts +++ b/apps/browser/src/autofill/popup/settings/notifications.component.ts @@ -1,27 +1,57 @@ +import { CommonModule } from "@angular/common"; import { Component, OnInit } from "@angular/core"; +import { FormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; import { firstValueFrom } from "rxjs"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service"; import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; +import { + ItemModule, + CardComponent, + SectionComponent, + SectionHeaderComponent, + CheckboxModule, + TypographyModule, + FormFieldModule, +} from "@bitwarden/components"; -import { enableAccountSwitching } from "../../../platform/flags"; +import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; +import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component"; +import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; +import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; @Component({ - selector: "autofill-notification-settings", templateUrl: "notifications.component.html", + standalone: true, + imports: [ + CommonModule, + JslibModule, + RouterModule, + PopupPageComponent, + PopupHeaderComponent, + PopupFooterComponent, + PopOutComponent, + ItemModule, + CardComponent, + SectionComponent, + SectionHeaderComponent, + CheckboxModule, + TypographyModule, + FormFieldModule, + FormsModule, + ], }) export class NotificationsSettingsComponent implements OnInit { enableAddLoginNotification = false; enableChangedPasswordNotification = false; enablePasskeys = true; - accountSwitcherEnabled = false; constructor( private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction, private vaultSettingsService: VaultSettingsService, - ) { - this.accountSwitcherEnabled = enableAccountSwitching(); - } + ) {} async ngOnInit() { this.enableAddLoginNotification = await firstValueFrom( diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 53ab778c31..11475362ba 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -39,7 +39,9 @@ import { TwoFactorOptionsComponent } from "../auth/popup/two-factor-options.comp import { TwoFactorComponent } from "../auth/popup/two-factor.component"; import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component"; import { AutofillComponent } from "../autofill/popup/settings/autofill.component"; +import { ExcludedDomainsV1Component } from "../autofill/popup/settings/excluded-domains-v1.component"; import { ExcludedDomainsComponent } from "../autofill/popup/settings/excluded-domains.component"; +import { NotificationsSettingsV1Component } from "../autofill/popup/settings/notifications-v1.component"; import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component"; import { PremiumComponent } from "../billing/popup/settings/premium.component"; import BrowserPopupUtils from "../platform/popup/browser-popup-utils"; @@ -288,12 +290,12 @@ const routes: Routes = [ canActivate: [AuthGuard], data: { state: "account-security" }, }, - { + ...extensionRefreshSwap(NotificationsSettingsV1Component, NotificationsSettingsComponent, { path: "notifications", - component: NotificationsSettingsComponent, + component: NotificationsSettingsV1Component, canActivate: [AuthGuard], data: { state: "notifications" }, - }, + }), ...extensionRefreshSwap(VaultSettingsComponent, VaultSettingsV2Component, { path: "vault-settings", canActivate: [AuthGuard], @@ -323,12 +325,12 @@ const routes: Routes = [ canActivate: [AuthGuard], data: { state: "sync" }, }, - { + ...extensionRefreshSwap(ExcludedDomainsV1Component, ExcludedDomainsComponent, { path: "excluded-domains", - component: ExcludedDomainsComponent, + component: ExcludedDomainsV1Component, canActivate: [AuthGuard], data: { state: "excluded-domains" }, - }, + }), { path: "premium", component: PremiumComponent, diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index 3c7f45e55f..d43e680e9b 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -37,7 +37,9 @@ import { TwoFactorOptionsComponent } from "../auth/popup/two-factor-options.comp import { TwoFactorComponent } from "../auth/popup/two-factor.component"; import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component"; import { AutofillComponent } from "../autofill/popup/settings/autofill.component"; +import { ExcludedDomainsV1Component } from "../autofill/popup/settings/excluded-domains-v1.component"; import { ExcludedDomainsComponent } from "../autofill/popup/settings/excluded-domains.component"; +import { NotificationsSettingsV1Component } from "../autofill/popup/settings/notifications-v1.component"; import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component"; import { PremiumComponent } from "../billing/popup/settings/premium.component"; import { PopOutComponent } from "../platform/popup/components/pop-out.component"; @@ -108,10 +110,12 @@ import "../platform/popup/locales"; ScrollingModule, ServicesModule, DialogModule, + ExcludedDomainsComponent, FilePopoutCalloutComponent, AvatarModule, AccountComponent, ButtonModule, + NotificationsSettingsComponent, PopOutComponent, PopupPageComponent, PopupTabNavigationComponent, @@ -133,7 +137,7 @@ import "../platform/popup/locales"; ColorPasswordCountPipe, CurrentTabComponent, EnvironmentComponent, - ExcludedDomainsComponent, + ExcludedDomainsV1Component, Fido2CipherRowComponent, Fido2UseBrowserLinkComponent, FolderAddEditComponent, @@ -146,7 +150,7 @@ import "../platform/popup/locales"; LoginComponent, LoginViaAuthRequestComponent, LoginDecryptionOptionsComponent, - NotificationsSettingsComponent, + NotificationsSettingsV1Component, AppearanceComponent, GeneratorComponent, PasswordGeneratorHistoryComponent, diff --git a/apps/browser/src/vault/popup/components/fido2/fido2-use-browser-link.component.ts b/apps/browser/src/vault/popup/components/fido2/fido2-use-browser-link.component.ts index 7dd0c50003..9c69d76228 100644 --- a/apps/browser/src/vault/popup/components/fido2/fido2-use-browser-link.component.ts +++ b/apps/browser/src/vault/popup/components/fido2/fido2-use-browser-link.component.ts @@ -4,6 +4,7 @@ import { Component } from "@angular/core"; import { firstValueFrom } from "rxjs"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { NeverDomains } from "@bitwarden/common/models/domain/domain-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"; @@ -92,7 +93,7 @@ export class Fido2UseBrowserLinkComponent { const exisitingDomains = await firstValueFrom(this.domainSettingsService.neverDomains$); const validDomain = Utils.getHostname(uri); - const savedDomains: { [name: string]: unknown } = { + const savedDomains: NeverDomains = { ...exisitingDomains, }; savedDomains[validDomain] = null; diff --git a/libs/common/src/models/domain/domain-service.ts b/libs/common/src/models/domain/domain-service.ts index d5247765fc..9ff53cc878 100644 --- a/libs/common/src/models/domain/domain-service.ts +++ b/libs/common/src/models/domain/domain-service.ts @@ -20,5 +20,6 @@ export const UriMatchStrategy = { export type UriMatchStrategySetting = (typeof UriMatchStrategy)[keyof typeof UriMatchStrategy]; -export type NeverDomains = { [id: string]: unknown }; +// using uniqueness properties of object shape over Set for ease of state storability +export type NeverDomains = { [id: string]: null }; export type EquivalentDomains = string[][];