mirror of
https://github.com/bitwarden/browser
synced 2025-12-13 23:03:32 +00:00
[PM-4349] Migrate hasPremium and debounceNavigation to be functional (#6591)
Class based router guards are deprecated as of Angular 15.2, per angular.io/guide/deprecations#router-class-and-injection-token-guards. To simplify future angular upgrades we need to resolve these deprecations. This PR migrates the HasPremium and DebounceNavigationService guards to use the new functional pattern.
This commit is contained in:
1
.github/whitelist-capital-letters.txt
vendored
1
.github/whitelist-capital-letters.txt
vendored
@@ -40,7 +40,6 @@
|
|||||||
./apps/browser/README.md
|
./apps/browser/README.md
|
||||||
./apps/browser/store/windows/AppxManifest.xml
|
./apps/browser/store/windows/AppxManifest.xml
|
||||||
./apps/browser/src/background/nativeMessaging.background.ts
|
./apps/browser/src/background/nativeMessaging.background.ts
|
||||||
./apps/browser/src/popup/services/debounceNavigationService.ts
|
|
||||||
./apps/browser/src/models/browserComponentState.ts
|
./apps/browser/src/models/browserComponentState.ts
|
||||||
./apps/browser/src/models/browserSendComponentState.ts
|
./apps/browser/src/models/browserSendComponentState.ts
|
||||||
./apps/browser/src/models/browserGroupingsComponentState.ts
|
./apps/browser/src/models/browserGroupingsComponentState.ts
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ import { VaultItemsComponent } from "../vault/popup/components/vault/vault-items
|
|||||||
import { ViewComponent } from "../vault/popup/components/vault/view.component";
|
import { ViewComponent } from "../vault/popup/components/vault/view.component";
|
||||||
import { FolderAddEditComponent } from "../vault/popup/settings/folder-add-edit.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 { ExcludedDomainsComponent } from "./settings/excluded-domains.component";
|
||||||
import { FoldersComponent } from "./settings/folders.component";
|
import { FoldersComponent } from "./settings/folders.component";
|
||||||
import { HelpAndFeedbackComponent } from "./settings/help-and-feedback.component";
|
import { HelpAndFeedbackComponent } from "./settings/help-and-feedback.component";
|
||||||
@@ -183,14 +183,14 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: "add-cipher",
|
path: "add-cipher",
|
||||||
component: AddEditComponent,
|
component: AddEditComponent,
|
||||||
canActivate: [AuthGuard, DebounceNavigationService],
|
canActivate: [AuthGuard, debounceNavigationGuard()],
|
||||||
data: { state: "add-cipher" },
|
data: { state: "add-cipher" },
|
||||||
runGuardsAndResolvers: "always",
|
runGuardsAndResolvers: "always",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "edit-cipher",
|
path: "edit-cipher",
|
||||||
component: AddEditComponent,
|
component: AddEditComponent,
|
||||||
canActivate: [AuthGuard, DebounceNavigationService],
|
canActivate: [AuthGuard, debounceNavigationGuard()],
|
||||||
data: { state: "edit-cipher" },
|
data: { state: "edit-cipher" },
|
||||||
runGuardsAndResolvers: "always",
|
runGuardsAndResolvers: "always",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,10 +1,24 @@
|
|||||||
import { Injectable, OnDestroy } from "@angular/core";
|
import { inject, Injectable, OnDestroy } from "@angular/core";
|
||||||
import { CanActivate, NavigationEnd, NavigationStart, Router } from "@angular/router";
|
import { CanActivateFn, NavigationEnd, NavigationStart, Router } from "@angular/router";
|
||||||
import { Subscription } from "rxjs";
|
import { Subscription } from "rxjs";
|
||||||
import { filter, pairwise } from "rxjs/operators";
|
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()
|
@Injectable()
|
||||||
export class DebounceNavigationService implements CanActivate, OnDestroy {
|
export class DebounceNavigationService implements OnDestroy {
|
||||||
navigationStartSub: Subscription;
|
navigationStartSub: Subscription;
|
||||||
navigationSuccessSub: Subscription;
|
navigationSuccessSub: Subscription;
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ import { FilePopoutUtilsService } from "../../tools/popup/services/file-popout-u
|
|||||||
import { BrowserFolderService } from "../../vault/services/browser-folder.service";
|
import { BrowserFolderService } from "../../vault/services/browser-folder.service";
|
||||||
import { VaultFilterService } from "../../vault/services/vault-filter.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 { InitService } from "./init.service";
|
||||||
import { PopupCloseWarningService } from "./popup-close-warning.service";
|
import { PopupCloseWarningService } from "./popup-close-warning.service";
|
||||||
import { PopupSearchService } from "./popup-search.service";
|
import { PopupSearchService } from "./popup-search.service";
|
||||||
|
|||||||
@@ -1,31 +1,35 @@
|
|||||||
import { Injectable } from "@angular/core";
|
import { inject } from "@angular/core";
|
||||||
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from "@angular/router";
|
import {
|
||||||
|
ActivatedRouteSnapshot,
|
||||||
|
RouterStateSnapshot,
|
||||||
|
Router,
|
||||||
|
CanActivateFn,
|
||||||
|
} from "@angular/router";
|
||||||
|
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
|
|
||||||
@Injectable({
|
/**
|
||||||
providedIn: "root",
|
* CanActivate guard that checks if the user has premium and otherwise triggers the "premiumRequired"
|
||||||
})
|
* message and blocks navigation.
|
||||||
export class HasPremiumGuard implements CanActivate {
|
*/
|
||||||
constructor(
|
export function hasPremiumGuard(): CanActivateFn {
|
||||||
private router: Router,
|
return async (_route: ActivatedRouteSnapshot, _state: RouterStateSnapshot) => {
|
||||||
private stateService: StateService,
|
const router = inject(Router);
|
||||||
private messagingService: MessagingService,
|
const stateService = inject(StateService);
|
||||||
) {}
|
const messagingService = inject(MessagingService);
|
||||||
|
|
||||||
async canActivate(route: ActivatedRouteSnapshot, routerState: RouterStateSnapshot) {
|
const userHasPremium = await stateService.getCanAccessPremium();
|
||||||
const userHasPremium = await this.stateService.getCanAccessPremium();
|
|
||||||
|
|
||||||
if (!userHasPremium) {
|
if (!userHasPremium) {
|
||||||
this.messagingService.send("premiumRequired");
|
messagingService.send("premiumRequired");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent trapping the user on the login page, since that's an awful UX flow
|
// Prevent trapping the user on the login page, since that's an awful UX flow
|
||||||
if (!userHasPremium && this.router.url === "/login") {
|
if (!userHasPremium && router.url === "/login") {
|
||||||
return this.router.createUrlTree(["/"]);
|
return router.createUrlTree(["/"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return userHasPremium;
|
return userHasPremium;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { RouterModule, Routes } from "@angular/router";
|
|||||||
|
|
||||||
import { AuthGuard } from "@bitwarden/angular/auth/guards";
|
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 { BreachReportComponent } from "./pages/breach-report.component";
|
||||||
import { ExposedPasswordsReportComponent } from "./pages/exposed-passwords-report.component";
|
import { ExposedPasswordsReportComponent } from "./pages/exposed-passwords-report.component";
|
||||||
@@ -30,31 +30,31 @@ const routes: Routes = [
|
|||||||
path: "reused-passwords-report",
|
path: "reused-passwords-report",
|
||||||
component: ReusedPasswordsReportComponent,
|
component: ReusedPasswordsReportComponent,
|
||||||
data: { titleId: "reusedPasswordsReport" },
|
data: { titleId: "reusedPasswordsReport" },
|
||||||
canActivate: [HasPremiumGuard],
|
canActivate: [hasPremiumGuard()],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "unsecured-websites-report",
|
path: "unsecured-websites-report",
|
||||||
component: UnsecuredWebsitesReportComponent,
|
component: UnsecuredWebsitesReportComponent,
|
||||||
data: { titleId: "unsecuredWebsitesReport" },
|
data: { titleId: "unsecuredWebsitesReport" },
|
||||||
canActivate: [HasPremiumGuard],
|
canActivate: [hasPremiumGuard()],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "weak-passwords-report",
|
path: "weak-passwords-report",
|
||||||
component: WeakPasswordsReportComponent,
|
component: WeakPasswordsReportComponent,
|
||||||
data: { titleId: "weakPasswordsReport" },
|
data: { titleId: "weakPasswordsReport" },
|
||||||
canActivate: [HasPremiumGuard],
|
canActivate: [hasPremiumGuard()],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "exposed-passwords-report",
|
path: "exposed-passwords-report",
|
||||||
component: ExposedPasswordsReportComponent,
|
component: ExposedPasswordsReportComponent,
|
||||||
data: { titleId: "exposedPasswordsReport" },
|
data: { titleId: "exposedPasswordsReport" },
|
||||||
canActivate: [HasPremiumGuard],
|
canActivate: [hasPremiumGuard()],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "inactive-two-factor-report",
|
path: "inactive-two-factor-report",
|
||||||
component: InactiveTwoFactorReportComponent,
|
component: InactiveTwoFactorReportComponent,
|
||||||
data: { titleId: "inactive2faReport" },
|
data: { titleId: "inactive2faReport" },
|
||||||
canActivate: [HasPremiumGuard],
|
canActivate: [hasPremiumGuard()],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user