diff --git a/apps/browser/src/autofill/popup/settings/blocked-domains.component.ts b/apps/browser/src/autofill/popup/settings/blocked-domains.component.ts index de5707f5c6f..461f62da6dc 100644 --- a/apps/browser/src/autofill/popup/settings/blocked-domains.component.ts +++ b/apps/browser/src/autofill/popup/settings/blocked-domains.component.ts @@ -15,7 +15,6 @@ 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 { 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, @@ -26,6 +25,7 @@ import { LinkModule, SectionComponent, SectionHeaderComponent, + ToastService, TypographyModule, } from "@bitwarden/components"; @@ -59,7 +59,8 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co ], }) export class BlockedDomainsComponent implements AfterViewInit, OnDestroy { - @ViewChildren("uriInput") uriInputElements: QueryList>; + @ViewChildren("uriInput") uriInputElements: QueryList> = + new QueryList(); dataIsPristine = true; isLoading = false; @@ -73,7 +74,7 @@ export class BlockedDomainsComponent implements AfterViewInit, OnDestroy { constructor( private domainSettingsService: DomainSettingsService, private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, + private toastService: ToastService, ) {} async ngAfterViewInit() { @@ -150,11 +151,11 @@ export class BlockedDomainsComponent implements AfterViewInit, OnDestroy { const validatedHost = Utils.getHostname(uri); if (!validatedHost) { - this.platformUtilsService.showToast( - "error", - null, - this.i18nService.t("excludedDomainsInvalidDomain", uri), - ); + this.toastService.showToast({ + message: this.i18nService.t("excludedDomainsInvalidDomain", uri), + title: "", + variant: "error", + }); // Don't reset via `handleStateUpdate` to allow existing input value correction this.isLoading = false; @@ -176,7 +177,7 @@ export class BlockedDomainsComponent implements AfterViewInit, OnDestroy { if (stateIsUnchanged) { // Reset UI state directly const constructedNeverDomainsState = this.storedBlockedDomains.reduce( - (neverDomains, uri) => ({ ...neverDomains, [uri]: null }), + (neverDomains: NeverDomains, uri: string) => ({ ...neverDomains, [uri]: null }), {}, ); this.handleStateUpdate(constructedNeverDomainsState); @@ -184,13 +185,17 @@ export class BlockedDomainsComponent implements AfterViewInit, OnDestroy { await this.domainSettingsService.setBlockedInteractionsUris(newBlockedDomainsSaveState); } - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("blockedDomainsSavedSuccess"), - ); + this.toastService.showToast({ + message: this.i18nService.t("blockedDomainsSavedSuccess"), + title: "", + variant: "success", + }); } catch { - this.platformUtilsService.showToast("error", null, this.i18nService.t("unexpectedError")); + this.toastService.showToast({ + message: this.i18nService.t("unexpectedError"), + title: "", + variant: "error", + }); // Don't reset via `handleStateUpdate` to preserve input values this.isLoading = false; 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 1391ad516fb..7d429bfe4f0 100644 --- a/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts +++ b/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { QueryList, @@ -17,7 +15,6 @@ 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 { 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, @@ -28,6 +25,7 @@ import { LinkModule, SectionComponent, SectionHeaderComponent, + ToastService, TypographyModule, } from "@bitwarden/components"; @@ -62,7 +60,8 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co ], }) export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy { - @ViewChildren("uriInput") uriInputElements: QueryList>; + @ViewChildren("uriInput") uriInputElements: QueryList> = + new QueryList(); accountSwitcherEnabled = false; dataIsPristine = true; @@ -77,7 +76,7 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy { constructor( private domainSettingsService: DomainSettingsService, private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, + private toastService: ToastService, ) { this.accountSwitcherEnabled = enableAccountSwitching(); } @@ -156,11 +155,11 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy { const validatedHost = Utils.getHostname(uri); if (!validatedHost) { - this.platformUtilsService.showToast( - "error", - null, - this.i18nService.t("excludedDomainsInvalidDomain", uri), - ); + this.toastService.showToast({ + message: this.i18nService.t("excludedDomainsInvalidDomain", uri), + title: "", + variant: "error", + }); // Don't reset via `handleStateUpdate` to allow existing input value correction this.isLoading = false; @@ -182,7 +181,7 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy { if (stateIsUnchanged) { // Reset UI state directly const constructedNeverDomainsState = this.storedExcludedDomains.reduce( - (neverDomains, uri) => ({ ...neverDomains, [uri]: null }), + (neverDomains: NeverDomains, uri: string) => ({ ...neverDomains, [uri]: null }), {}, ); this.handleStateUpdate(constructedNeverDomainsState); @@ -190,13 +189,17 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy { await this.domainSettingsService.setNeverDomains(newExcludedDomainsSaveState); } - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("excludedDomainsSavedSuccess"), - ); + this.toastService.showToast({ + message: this.i18nService.t("excludedDomainsSavedSuccess"), + title: "", + variant: "success", + }); } catch { - this.platformUtilsService.showToast("error", null, this.i18nService.t("unexpectedError")); + this.toastService.showToast({ + message: this.i18nService.t("unexpectedError"), + title: "", + variant: "error", + }); // Don't reset via `handleStateUpdate` to preserve input values this.isLoading = false; diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts index deda630342a..1704493974f 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts @@ -41,7 +41,7 @@ export class AutofillVaultListItemsComponent { /** * Indicators for the section. */ - @Input() sectionIndicators: string[]; + @Input() sectionIndicators: string[] = []; /** * Observable that determines whether the empty autofill tip should be shown. diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts index 09cb5f1c47b..e7b67d2756c 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts @@ -2,7 +2,15 @@ // @ts-strict-ignore import { ScrollingModule } from "@angular/cdk/scrolling"; import { CommonModule } from "@angular/common"; -import { booleanAttribute, Component, EventEmitter, inject, Input, Output, OnInit } from "@angular/core"; +import { + booleanAttribute, + Component, + EventEmitter, + inject, + Input, + Output, + OnInit, +} from "@angular/core"; import { Router } from "@angular/router"; import { map } from "rxjs"; @@ -93,7 +101,7 @@ export class VaultListItemsContainerComponent implements OnInit { /** * Indicators for the section. */ - @Input() sectionIndicators: string[]; + @Input() sectionIndicators: string[] = []; /** * Optional description for the vault list item section. Will be shown below the title even when diff --git a/apps/browser/src/vault/popup/components/vault/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault/vault-v2.component.ts index cfbcc1b6d1f..e0894be9594 100644 --- a/apps/browser/src/vault/popup/components/vault/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault/vault-v2.component.ts @@ -62,7 +62,7 @@ export class VaultV2Component implements OnInit, OnDestroy { protected loading$ = this.vaultPopupItemsService.loading$; protected scriptInjectionIsBlocked = false; protected showScriptInjectionIsBlockedBanner = false; - protected autofillTabHostname: string = null; + protected autofillTabHostname: string | null = null; protected sectionIndicators: string[] = []; protected newItemItemValues$: Observable = @@ -151,20 +151,24 @@ export class VaultV2Component implements OnInit, OnDestroy { ngOnDestroy(): void {} handleScriptInjectionIsBlockedBannerDismiss() { - firstValueFrom(this.domainSettingsService.blockedInteractionsUris$) - .then((blockedURIs) => { - this.showScriptInjectionIsBlockedBanner = false; - this.domainSettingsService - .setBlockedInteractionsUris({ + if (!this.autofillTabHostname) { + return; + } + + try { + void firstValueFrom(this.domainSettingsService.blockedInteractionsUris$).then( + (blockedURIs) => { + this.showScriptInjectionIsBlockedBanner = false; + void this.domainSettingsService.setBlockedInteractionsUris({ ...blockedURIs, - [this.autofillTabHostname]: { bannerIsDismissed: true }, - }) - .catch(() => { - /* no-op */ + [this.autofillTabHostname as string]: { bannerIsDismissed: true }, }); - }) - .catch(() => { - /* no-op */ - }); + }, + ); + } catch (e) { + throw new Error( + "There was a problem dismissing the blocked interaction URI notification banner", + ); + } } } diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index 91b75a14ff6..edd0ac4c20c 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -483,7 +483,29 @@ export class ServiceContainer { this.containerService = new ContainerService(this.keyService, this.encryptService); - this.domainSettingsService = new DefaultDomainSettingsService(this.stateProvider); + this.configApiService = new ConfigApiService(this.apiService, this.tokenService); + + this.authService = new AuthService( + this.accountService, + this.messagingService, + this.keyService, + this.apiService, + this.stateService, + this.tokenService, + ); + + this.configService = new DefaultConfigService( + this.configApiService, + this.environmentService, + this.logService, + this.stateProvider, + this.authService, + ); + + this.domainSettingsService = new DefaultDomainSettingsService( + this.stateProvider, + this.configService, + ); this.fileUploadService = new FileUploadService(this.logService, this.apiService); @@ -580,25 +602,6 @@ export class ServiceContainer { this.taskSchedulerService = new DefaultTaskSchedulerService(this.logService); - this.authService = new AuthService( - this.accountService, - this.messagingService, - this.keyService, - this.apiService, - this.stateService, - this.tokenService, - ); - - this.configApiService = new ConfigApiService(this.apiService, this.tokenService); - - this.configService = new DefaultConfigService( - this.configApiService, - this.environmentService, - this.logService, - this.stateProvider, - this.authService, - ); - this.devicesApiService = new DevicesApiServiceImplementation(this.apiService); this.deviceTrustService = new DeviceTrustService( this.keyGenerationService, diff --git a/libs/common/src/autofill/services/domain-settings.service.spec.ts b/libs/common/src/autofill/services/domain-settings.service.spec.ts index 24e3763eb45..1f27ddcde5b 100644 --- a/libs/common/src/autofill/services/domain-settings.service.spec.ts +++ b/libs/common/src/autofill/services/domain-settings.service.spec.ts @@ -1,5 +1,8 @@ +import { MockProxy, mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; + import { FakeStateProvider, FakeAccountService, mockAccountServiceWith } from "../../../spec"; import { Utils } from "../../platform/misc/utils"; import { UserId } from "../../types/guid"; @@ -8,8 +11,10 @@ import { DefaultDomainSettingsService, DomainSettingsService } from "./domain-se describe("DefaultDomainSettingsService", () => { let domainSettingsService: DomainSettingsService; + const configServiceMock = mock(); const mockUserId = Utils.newGuid() as UserId; const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); + let mockConfigService: MockProxy; const fakeStateProvider: FakeStateProvider = new FakeStateProvider(accountService); const mockEquivalentDomains = [ @@ -19,10 +24,12 @@ describe("DefaultDomainSettingsService", () => { ]; beforeEach(() => { - domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider); + domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider, mockConfigService); + jest.spyOn(configServiceMock, "getFeatureFlag$").mockReturnValue(of(false)); jest.spyOn(domainSettingsService, "getUrlEquivalentDomains"); domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains); + domainSettingsService.blockedInteractionsUris$ = of(null); }); describe("getUrlEquivalentDomains", () => { diff --git a/libs/common/src/autofill/services/domain-settings.service.ts b/libs/common/src/autofill/services/domain-settings.service.ts index c49cfff6b02..b61fd2bb997 100644 --- a/libs/common/src/autofill/services/domain-settings.service.ts +++ b/libs/common/src/autofill/services/domain-settings.service.ts @@ -3,7 +3,6 @@ import { combineLatest, map, Observable } from "rxjs"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { NeverDomains, @@ -11,6 +10,7 @@ import { UriMatchStrategySetting, UriMatchStrategy, } from "../../models/domain/domain-service"; +import { ConfigService } from "../../platform/abstractions/config/config.service"; import { Utils } from "../../platform/misc/utils"; import { DOMAIN_SETTINGS_DISK, @@ -99,7 +99,7 @@ export class DefaultDomainSettingsService implements DomainSettingsService { this.blockedInteractionsUris$ = combineLatest([ this.blockedInteractionsUrisState.state$, - this.configService.getFeatureFlag$(FeatureFlag.BlockBrowserInjectionsByDomain), + this.configService?.getFeatureFlag$(FeatureFlag.BlockBrowserInjectionsByDomain), ]).pipe( map(([blockedUris, blockBrowserInjectionsByDomainEnabled]) => { if (!blockBrowserInjectionsByDomainEnabled) {