diff --git a/.github/whitelist-capital-letters.txt b/.github/whitelist-capital-letters.txt index 9637f95431..ef2ff29ed8 100644 --- a/.github/whitelist-capital-letters.txt +++ b/.github/whitelist-capital-letters.txt @@ -40,7 +40,6 @@ ./apps/browser/README.md ./apps/browser/store/windows/AppxManifest.xml ./apps/browser/src/background/nativeMessaging.background.ts -./apps/browser/src/popup/services/debounceNavigationService.ts ./apps/browser/src/models/browserComponentState.ts ./apps/browser/src/models/browserSendComponentState.ts ./apps/browser/src/models/browserGroupingsComponentState.ts diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 6dd41320c7..f012dbe9c9 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -47,7 +47,7 @@ import { VaultItemsComponent } from "../vault/popup/components/vault/vault-items import { ViewComponent } from "../vault/popup/components/vault/view.component"; import { FolderAddEditComponent } from "../vault/popup/settings/folder-add-edit.component"; -import { DebounceNavigationService } from "./services/debounceNavigationService"; +import { debounceNavigationGuard } from "./services/debounce-navigation.service"; import { ExcludedDomainsComponent } from "./settings/excluded-domains.component"; import { FoldersComponent } from "./settings/folders.component"; import { HelpAndFeedbackComponent } from "./settings/help-and-feedback.component"; @@ -183,14 +183,14 @@ const routes: Routes = [ { path: "add-cipher", component: AddEditComponent, - canActivate: [AuthGuard, DebounceNavigationService], + canActivate: [AuthGuard, debounceNavigationGuard()], data: { state: "add-cipher" }, runGuardsAndResolvers: "always", }, { path: "edit-cipher", component: AddEditComponent, - canActivate: [AuthGuard, DebounceNavigationService], + canActivate: [AuthGuard, debounceNavigationGuard()], data: { state: "edit-cipher" }, runGuardsAndResolvers: "always", }, diff --git a/apps/browser/src/popup/services/debounceNavigationService.ts b/apps/browser/src/popup/services/debounce-navigation.service.ts similarity index 63% rename from apps/browser/src/popup/services/debounceNavigationService.ts rename to apps/browser/src/popup/services/debounce-navigation.service.ts index 091f817be9..b40738f0a8 100644 --- a/apps/browser/src/popup/services/debounceNavigationService.ts +++ b/apps/browser/src/popup/services/debounce-navigation.service.ts @@ -1,10 +1,24 @@ -import { Injectable, OnDestroy } from "@angular/core"; -import { CanActivate, NavigationEnd, NavigationStart, Router } from "@angular/router"; +import { inject, Injectable, OnDestroy } from "@angular/core"; +import { CanActivateFn, NavigationEnd, NavigationStart, Router } from "@angular/router"; import { Subscription } from "rxjs"; import { filter, pairwise } from "rxjs/operators"; +/** + * CanActivate guard that cancels duplicate navigation events, which can otherwise reinitialize some components + * unexpectedly. + * Specifically, this is used to avoid data loss when navigating from the password generator component back to the + * add/edit cipher component in browser. + * For more information, see https://github.com/bitwarden/clients/pull/1935 + */ +export function debounceNavigationGuard(): CanActivateFn { + return async () => { + const debounceNavigationService = inject(DebounceNavigationService); + return debounceNavigationService.canActivate(); + }; +} + @Injectable() -export class DebounceNavigationService implements CanActivate, OnDestroy { +export class DebounceNavigationService implements OnDestroy { navigationStartSub: Subscription; navigationSuccessSub: Subscription; diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 46b94f6a8c..dc22348828 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -116,7 +116,7 @@ import { FilePopoutUtilsService } from "../../tools/popup/services/file-popout-u import { BrowserFolderService } from "../../vault/services/browser-folder.service"; import { VaultFilterService } from "../../vault/services/vault-filter.service"; -import { DebounceNavigationService } from "./debounceNavigationService"; +import { DebounceNavigationService } from "./debounce-navigation.service"; import { InitService } from "./init.service"; import { PopupCloseWarningService } from "./popup-close-warning.service"; import { PopupSearchService } from "./popup-search.service"; diff --git a/apps/web/src/app/core/guards/has-premium.guard.ts b/apps/web/src/app/core/guards/has-premium.guard.ts index 9b123c3615..bb4d07f1d1 100644 --- a/apps/web/src/app/core/guards/has-premium.guard.ts +++ b/apps/web/src/app/core/guards/has-premium.guard.ts @@ -1,31 +1,35 @@ -import { Injectable } from "@angular/core"; -import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from "@angular/router"; +import { inject } from "@angular/core"; +import { + ActivatedRouteSnapshot, + RouterStateSnapshot, + Router, + CanActivateFn, +} from "@angular/router"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -@Injectable({ - providedIn: "root", -}) -export class HasPremiumGuard implements CanActivate { - constructor( - private router: Router, - private stateService: StateService, - private messagingService: MessagingService, - ) {} +/** + * CanActivate guard that checks if the user has premium and otherwise triggers the "premiumRequired" + * message and blocks navigation. + */ +export function hasPremiumGuard(): CanActivateFn { + return async (_route: ActivatedRouteSnapshot, _state: RouterStateSnapshot) => { + const router = inject(Router); + const stateService = inject(StateService); + const messagingService = inject(MessagingService); - async canActivate(route: ActivatedRouteSnapshot, routerState: RouterStateSnapshot) { - const userHasPremium = await this.stateService.getCanAccessPremium(); + const userHasPremium = await stateService.getCanAccessPremium(); if (!userHasPremium) { - this.messagingService.send("premiumRequired"); + messagingService.send("premiumRequired"); } // Prevent trapping the user on the login page, since that's an awful UX flow - if (!userHasPremium && this.router.url === "/login") { - return this.router.createUrlTree(["/"]); + if (!userHasPremium && router.url === "/login") { + return router.createUrlTree(["/"]); } return userHasPremium; - } + }; } diff --git a/apps/web/src/app/reports/reports-routing.module.ts b/apps/web/src/app/reports/reports-routing.module.ts index 6bbba15c9b..6bd2683776 100644 --- a/apps/web/src/app/reports/reports-routing.module.ts +++ b/apps/web/src/app/reports/reports-routing.module.ts @@ -3,7 +3,7 @@ import { RouterModule, Routes } from "@angular/router"; import { AuthGuard } from "@bitwarden/angular/auth/guards"; -import { HasPremiumGuard } from "../core/guards/has-premium.guard"; +import { hasPremiumGuard } from "../core/guards/has-premium.guard"; import { BreachReportComponent } from "./pages/breach-report.component"; import { ExposedPasswordsReportComponent } from "./pages/exposed-passwords-report.component"; @@ -30,31 +30,31 @@ const routes: Routes = [ path: "reused-passwords-report", component: ReusedPasswordsReportComponent, data: { titleId: "reusedPasswordsReport" }, - canActivate: [HasPremiumGuard], + canActivate: [hasPremiumGuard()], }, { path: "unsecured-websites-report", component: UnsecuredWebsitesReportComponent, data: { titleId: "unsecuredWebsitesReport" }, - canActivate: [HasPremiumGuard], + canActivate: [hasPremiumGuard()], }, { path: "weak-passwords-report", component: WeakPasswordsReportComponent, data: { titleId: "weakPasswordsReport" }, - canActivate: [HasPremiumGuard], + canActivate: [hasPremiumGuard()], }, { path: "exposed-passwords-report", component: ExposedPasswordsReportComponent, data: { titleId: "exposedPasswordsReport" }, - canActivate: [HasPremiumGuard], + canActivate: [hasPremiumGuard()], }, { path: "inactive-two-factor-report", component: InactiveTwoFactorReportComponent, data: { titleId: "inactive2faReport" }, - canActivate: [HasPremiumGuard], + canActivate: [hasPremiumGuard()], }, ], },