Skip to content

Commit

Permalink
[PM-6827] Browser Extension Refresh - Tabs Routing (#9004)
Browse files Browse the repository at this point in the history
* [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
  • Loading branch information
shane-melton committed May 6, 2024
1 parent 09ff12f commit ff30211
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
Expand Down
10 changes: 6 additions & 4 deletions apps/browser/src/popup/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -47,13 +47,15 @@ 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";
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 = {
Expand Down Expand Up @@ -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: [
{
Expand All @@ -336,6 +337,7 @@ const routes: Routes = [
path: "current",
component: CurrentTabComponent,
canActivate: [AuthGuard],
canMatch: [extensionRefreshRedirect("/tabs/vault")],
data: { state: "tabs_current" },
runGuardsAndResolvers: "always",
},
Expand Down Expand Up @@ -364,7 +366,7 @@ const routes: Routes = [
data: { state: "tabs_send" },
},
],
},
}),
{
path: "account-switcher",
component: AccountSwitcherComponent,
Expand Down
2 changes: 2 additions & 0 deletions apps/browser/src/popup/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -160,6 +161,7 @@ import "../platform/popup/locales";
SsoComponent,
SyncComponent,
TabsComponent,
TabsV2Component,
TwoFactorComponent,
TwoFactorOptionsComponent,
UpdateTempPasswordComponent,
Expand Down
45 changes: 45 additions & 0 deletions apps/browser/src/popup/extension-refresh-route-utils.ts
Original file line number Diff line number Diff line change
@@ -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;
}
};
}
11 changes: 11 additions & 0 deletions apps/browser/src/popup/tabs-v2.component.ts
Original file line number Diff line number Diff line change
@@ -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 {}
55 changes: 55 additions & 0 deletions libs/angular/src/utils/component-route-swap.ts
Original file line number Diff line number Diff line change
@@ -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];
}
2 changes: 2 additions & 0 deletions libs/common/src/enums/feature-flag.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down

0 comments on commit ff30211

Please sign in to comment.