[PM-6827] Browser Extension Refresh - Tabs Routing (#9004)
* [PM-6827] Add componentRouteSwap util function * [PM-6827] Add extension-refresh feature flag * [PM-6827] Add extension-refresh route swap utils * [PM-6827] Add the TabsV2 component * [PM-6827] Add the TabsV2 to routing module * [PM-6827] Fix route prefixes in popup-tab-navigation component
This commit is contained in:
parent
09ff12fc02
commit
ff3021129e
|
@ -17,25 +17,25 @@ export class PopupTabNavigationComponent {
|
|||
navButtons = [
|
||||
{
|
||||
label: "Vault",
|
||||
page: "/vault",
|
||||
page: "/tabs/vault",
|
||||
iconKey: "lock",
|
||||
iconKeyActive: "lock-f",
|
||||
},
|
||||
{
|
||||
label: "Generator",
|
||||
page: "/generator",
|
||||
page: "/tabs/generator",
|
||||
iconKey: "generate",
|
||||
iconKeyActive: "generate-f",
|
||||
},
|
||||
{
|
||||
label: "Send",
|
||||
page: "/send",
|
||||
page: "/tabs/send",
|
||||
iconKey: "send",
|
||||
iconKeyActive: "send-f",
|
||||
},
|
||||
{
|
||||
label: "Settings",
|
||||
page: "/settings",
|
||||
page: "/tabs/settings",
|
||||
iconKey: "cog",
|
||||
iconKeyActive: "cog-f",
|
||||
},
|
||||
|
|
|
@ -2,9 +2,9 @@ import { Injectable, NgModule } from "@angular/core";
|
|||
import { ActivatedRouteSnapshot, RouteReuseStrategy, RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import {
|
||||
redirectGuard,
|
||||
AuthGuard,
|
||||
lockGuard,
|
||||
redirectGuard,
|
||||
tdeDecryptionRequiredGuard,
|
||||
unauthGuardFn,
|
||||
} from "@bitwarden/angular/auth/guards";
|
||||
|
@ -47,6 +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 { extensionRefreshRedirect, extensionRefreshSwap } from "./extension-refresh-route-utils";
|
||||
import { debounceNavigationGuard } from "./services/debounce-navigation.service";
|
||||
import { ExcludedDomainsComponent } from "./settings/excluded-domains.component";
|
||||
import { FoldersComponent } from "./settings/folders.component";
|
||||
|
@ -54,6 +55,7 @@ import { HelpAndFeedbackComponent } from "./settings/help-and-feedback.component
|
|||
import { OptionsComponent } from "./settings/options.component";
|
||||
import { SettingsComponent } from "./settings/settings.component";
|
||||
import { SyncComponent } from "./settings/sync.component";
|
||||
import { TabsV2Component } from "./tabs-v2.component";
|
||||
import { TabsComponent } from "./tabs.component";
|
||||
|
||||
const unauthRouteOverrides = {
|
||||
|
@ -322,9 +324,8 @@ const routes: Routes = [
|
|||
canActivate: [AuthGuard],
|
||||
data: { state: "help-and-feedback" },
|
||||
},
|
||||
{
|
||||
...extensionRefreshSwap(TabsComponent, TabsV2Component, {
|
||||
path: "tabs",
|
||||
component: TabsComponent,
|
||||
data: { state: "tabs" },
|
||||
children: [
|
||||
{
|
||||
|
@ -336,6 +337,7 @@ const routes: Routes = [
|
|||
path: "current",
|
||||
component: CurrentTabComponent,
|
||||
canActivate: [AuthGuard],
|
||||
canMatch: [extensionRefreshRedirect("/tabs/vault")],
|
||||
data: { state: "tabs_current" },
|
||||
runGuardsAndResolvers: "always",
|
||||
},
|
||||
|
@ -364,7 +366,7 @@ const routes: Routes = [
|
|||
data: { state: "tabs_send" },
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
{
|
||||
path: "account-switcher",
|
||||
component: AccountSwitcherComponent,
|
||||
|
|
|
@ -80,6 +80,7 @@ import { OptionsComponent } from "./settings/options.component";
|
|||
import { SettingsComponent } from "./settings/settings.component";
|
||||
import { SyncComponent } from "./settings/sync.component";
|
||||
import { VaultTimeoutInputComponent } from "./settings/vault-timeout-input.component";
|
||||
import { TabsV2Component } from "./tabs-v2.component";
|
||||
import { TabsComponent } from "./tabs.component";
|
||||
|
||||
// Register the locales for the application
|
||||
|
@ -160,6 +161,7 @@ import "../platform/popup/locales";
|
|||
SsoComponent,
|
||||
SyncComponent,
|
||||
TabsComponent,
|
||||
TabsV2Component,
|
||||
TwoFactorComponent,
|
||||
TwoFactorOptionsComponent,
|
||||
UpdateTempPasswordComponent,
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import { inject, Type } from "@angular/core";
|
||||
import { Route, Router, Routes, UrlTree } from "@angular/router";
|
||||
|
||||
import { componentRouteSwap } from "@bitwarden/angular/utils/component-route-swap";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
|
||||
/**
|
||||
* Helper function to swap between two components based on the ExtensionRefresh feature flag.
|
||||
* @param defaultComponent - The current non-refreshed component to render.
|
||||
* @param refreshedComponent - The new refreshed component to render.
|
||||
* @param options - The shared route options to apply to both components.
|
||||
*/
|
||||
export function extensionRefreshSwap(
|
||||
defaultComponent: Type<any>,
|
||||
refreshedComponent: Type<any>,
|
||||
options: Route,
|
||||
): Routes {
|
||||
return componentRouteSwap(
|
||||
defaultComponent,
|
||||
refreshedComponent,
|
||||
async () => {
|
||||
const configService = inject(ConfigService);
|
||||
return configService.getFeatureFlag(FeatureFlag.ExtensionRefresh);
|
||||
},
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to redirect to a new URL based on the ExtensionRefresh feature flag.
|
||||
* @param redirectUrl - The URL to redirect to if the ExtensionRefresh flag is enabled.
|
||||
*/
|
||||
export function extensionRefreshRedirect(redirectUrl: string): () => Promise<boolean | UrlTree> {
|
||||
return async () => {
|
||||
const configService = inject(ConfigService);
|
||||
const router = inject(Router);
|
||||
const shouldRedirect = await configService.getFeatureFlag(FeatureFlag.ExtensionRefresh);
|
||||
if (shouldRedirect) {
|
||||
return router.parseUrl(redirectUrl);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import { Component } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "app-tabs-v2",
|
||||
template: `
|
||||
<popup-tab-navigation>
|
||||
<router-outlet></router-outlet>
|
||||
</popup-tab-navigation>
|
||||
`,
|
||||
})
|
||||
export class TabsV2Component {}
|
|
@ -0,0 +1,55 @@
|
|||
import { Type } from "@angular/core";
|
||||
import { Route, Routes } from "@angular/router";
|
||||
|
||||
/**
|
||||
* Helper function to swap between two components based on an async condition. The async condition is evaluated
|
||||
* as an `CanMatchFn` and supports Angular dependency injection via `inject()`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const routes = [
|
||||
* ...componentRouteSwap(
|
||||
* defaultComponent,
|
||||
* altComponent,
|
||||
* async () => {
|
||||
* const configService = inject(ConfigService);
|
||||
* return configService.getFeatureFlag(FeatureFlag.SomeFlag);
|
||||
* },
|
||||
* {
|
||||
* path: 'some-path'
|
||||
* }
|
||||
* ),
|
||||
* // Other routes...
|
||||
* ];
|
||||
* ```
|
||||
*
|
||||
* @param defaultComponent - The default component to render.
|
||||
* @param altComponent - The alternate component to render when the condition is met.
|
||||
* @param shouldSwapFn - The async function to determine if the alternate component should be rendered.
|
||||
* @param options - The shared route options to apply to both components.
|
||||
*/
|
||||
export function componentRouteSwap(
|
||||
defaultComponent: Type<any>,
|
||||
altComponent: Type<any>,
|
||||
shouldSwapFn: () => Promise<boolean>,
|
||||
options: Route,
|
||||
): Routes {
|
||||
const defaultRoute = {
|
||||
...options,
|
||||
component: defaultComponent,
|
||||
};
|
||||
|
||||
const altRoute: Route = {
|
||||
...options,
|
||||
component: altComponent,
|
||||
canMatch: [
|
||||
async () => {
|
||||
return await shouldSwapFn();
|
||||
},
|
||||
...(options.canMatch ?? []),
|
||||
],
|
||||
};
|
||||
|
||||
// Return the alternate route first, so it is evaluated first.
|
||||
return [altRoute, defaultRoute];
|
||||
}
|
|
@ -16,6 +16,7 @@ export enum FeatureFlag {
|
|||
AC1795_UpdatedSubscriptionStatusSection = "AC-1795_updated-subscription-status-section",
|
||||
UnassignedItemsBanner = "unassigned-items-banner",
|
||||
EnableDeleteProvider = "AC-1218-delete-provider",
|
||||
ExtensionRefresh = "extension-refresh",
|
||||
}
|
||||
|
||||
export type AllowedFeatureFlagTypes = boolean | number | string;
|
||||
|
@ -42,6 +43,7 @@ export const DefaultFeatureFlagValue = {
|
|||
[FeatureFlag.AC1795_UpdatedSubscriptionStatusSection]: FALSE,
|
||||
[FeatureFlag.UnassignedItemsBanner]: FALSE,
|
||||
[FeatureFlag.EnableDeleteProvider]: FALSE,
|
||||
[FeatureFlag.ExtensionRefresh]: FALSE,
|
||||
} satisfies Record<FeatureFlag, AllowedFeatureFlagTypes>;
|
||||
|
||||
export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue;
|
||||
|
|
Loading…
Reference in New Issue