mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-17 20:31:50 +01:00
[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 = [
|
navButtons = [
|
||||||
{
|
{
|
||||||
label: "Vault",
|
label: "Vault",
|
||||||
page: "/vault",
|
page: "/tabs/vault",
|
||||||
iconKey: "lock",
|
iconKey: "lock",
|
||||||
iconKeyActive: "lock-f",
|
iconKeyActive: "lock-f",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Generator",
|
label: "Generator",
|
||||||
page: "/generator",
|
page: "/tabs/generator",
|
||||||
iconKey: "generate",
|
iconKey: "generate",
|
||||||
iconKeyActive: "generate-f",
|
iconKeyActive: "generate-f",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Send",
|
label: "Send",
|
||||||
page: "/send",
|
page: "/tabs/send",
|
||||||
iconKey: "send",
|
iconKey: "send",
|
||||||
iconKeyActive: "send-f",
|
iconKeyActive: "send-f",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Settings",
|
label: "Settings",
|
||||||
page: "/settings",
|
page: "/tabs/settings",
|
||||||
iconKey: "cog",
|
iconKey: "cog",
|
||||||
iconKeyActive: "cog-f",
|
iconKeyActive: "cog-f",
|
||||||
},
|
},
|
||||||
|
@ -2,9 +2,9 @@ import { Injectable, NgModule } from "@angular/core";
|
|||||||
import { ActivatedRouteSnapshot, RouteReuseStrategy, RouterModule, Routes } from "@angular/router";
|
import { ActivatedRouteSnapshot, RouteReuseStrategy, RouterModule, Routes } from "@angular/router";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
redirectGuard,
|
|
||||||
AuthGuard,
|
AuthGuard,
|
||||||
lockGuard,
|
lockGuard,
|
||||||
|
redirectGuard,
|
||||||
tdeDecryptionRequiredGuard,
|
tdeDecryptionRequiredGuard,
|
||||||
unauthGuardFn,
|
unauthGuardFn,
|
||||||
} from "@bitwarden/angular/auth/guards";
|
} 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 { 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 { extensionRefreshRedirect, extensionRefreshSwap } from "./extension-refresh-route-utils";
|
||||||
import { debounceNavigationGuard } from "./services/debounce-navigation.service";
|
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";
|
||||||
@ -54,6 +55,7 @@ import { HelpAndFeedbackComponent } from "./settings/help-and-feedback.component
|
|||||||
import { OptionsComponent } from "./settings/options.component";
|
import { OptionsComponent } from "./settings/options.component";
|
||||||
import { SettingsComponent } from "./settings/settings.component";
|
import { SettingsComponent } from "./settings/settings.component";
|
||||||
import { SyncComponent } from "./settings/sync.component";
|
import { SyncComponent } from "./settings/sync.component";
|
||||||
|
import { TabsV2Component } from "./tabs-v2.component";
|
||||||
import { TabsComponent } from "./tabs.component";
|
import { TabsComponent } from "./tabs.component";
|
||||||
|
|
||||||
const unauthRouteOverrides = {
|
const unauthRouteOverrides = {
|
||||||
@ -322,9 +324,8 @@ const routes: Routes = [
|
|||||||
canActivate: [AuthGuard],
|
canActivate: [AuthGuard],
|
||||||
data: { state: "help-and-feedback" },
|
data: { state: "help-and-feedback" },
|
||||||
},
|
},
|
||||||
{
|
...extensionRefreshSwap(TabsComponent, TabsV2Component, {
|
||||||
path: "tabs",
|
path: "tabs",
|
||||||
component: TabsComponent,
|
|
||||||
data: { state: "tabs" },
|
data: { state: "tabs" },
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@ -336,6 +337,7 @@ const routes: Routes = [
|
|||||||
path: "current",
|
path: "current",
|
||||||
component: CurrentTabComponent,
|
component: CurrentTabComponent,
|
||||||
canActivate: [AuthGuard],
|
canActivate: [AuthGuard],
|
||||||
|
canMatch: [extensionRefreshRedirect("/tabs/vault")],
|
||||||
data: { state: "tabs_current" },
|
data: { state: "tabs_current" },
|
||||||
runGuardsAndResolvers: "always",
|
runGuardsAndResolvers: "always",
|
||||||
},
|
},
|
||||||
@ -364,7 +366,7 @@ const routes: Routes = [
|
|||||||
data: { state: "tabs_send" },
|
data: { state: "tabs_send" },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
}),
|
||||||
{
|
{
|
||||||
path: "account-switcher",
|
path: "account-switcher",
|
||||||
component: AccountSwitcherComponent,
|
component: AccountSwitcherComponent,
|
||||||
|
@ -80,6 +80,7 @@ import { OptionsComponent } from "./settings/options.component";
|
|||||||
import { SettingsComponent } from "./settings/settings.component";
|
import { SettingsComponent } from "./settings/settings.component";
|
||||||
import { SyncComponent } from "./settings/sync.component";
|
import { SyncComponent } from "./settings/sync.component";
|
||||||
import { VaultTimeoutInputComponent } from "./settings/vault-timeout-input.component";
|
import { VaultTimeoutInputComponent } from "./settings/vault-timeout-input.component";
|
||||||
|
import { TabsV2Component } from "./tabs-v2.component";
|
||||||
import { TabsComponent } from "./tabs.component";
|
import { TabsComponent } from "./tabs.component";
|
||||||
|
|
||||||
// Register the locales for the application
|
// Register the locales for the application
|
||||||
@ -160,6 +161,7 @@ import "../platform/popup/locales";
|
|||||||
SsoComponent,
|
SsoComponent,
|
||||||
SyncComponent,
|
SyncComponent,
|
||||||
TabsComponent,
|
TabsComponent,
|
||||||
|
TabsV2Component,
|
||||||
TwoFactorComponent,
|
TwoFactorComponent,
|
||||||
TwoFactorOptionsComponent,
|
TwoFactorOptionsComponent,
|
||||||
UpdateTempPasswordComponent,
|
UpdateTempPasswordComponent,
|
||||||
|
45
apps/browser/src/popup/extension-refresh-route-utils.ts
Normal file
45
apps/browser/src/popup/extension-refresh-route-utils.ts
Normal file
@ -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
apps/browser/src/popup/tabs-v2.component.ts
Normal file
11
apps/browser/src/popup/tabs-v2.component.ts
Normal file
@ -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
libs/angular/src/utils/component-route-swap.ts
Normal file
55
libs/angular/src/utils/component-route-swap.ts
Normal file
@ -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",
|
AC1795_UpdatedSubscriptionStatusSection = "AC-1795_updated-subscription-status-section",
|
||||||
UnassignedItemsBanner = "unassigned-items-banner",
|
UnassignedItemsBanner = "unassigned-items-banner",
|
||||||
EnableDeleteProvider = "AC-1218-delete-provider",
|
EnableDeleteProvider = "AC-1218-delete-provider",
|
||||||
|
ExtensionRefresh = "extension-refresh",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AllowedFeatureFlagTypes = boolean | number | string;
|
export type AllowedFeatureFlagTypes = boolean | number | string;
|
||||||
@ -42,6 +43,7 @@ export const DefaultFeatureFlagValue = {
|
|||||||
[FeatureFlag.AC1795_UpdatedSubscriptionStatusSection]: FALSE,
|
[FeatureFlag.AC1795_UpdatedSubscriptionStatusSection]: FALSE,
|
||||||
[FeatureFlag.UnassignedItemsBanner]: FALSE,
|
[FeatureFlag.UnassignedItemsBanner]: FALSE,
|
||||||
[FeatureFlag.EnableDeleteProvider]: FALSE,
|
[FeatureFlag.EnableDeleteProvider]: FALSE,
|
||||||
|
[FeatureFlag.ExtensionRefresh]: FALSE,
|
||||||
} satisfies Record<FeatureFlag, AllowedFeatureFlagTypes>;
|
} satisfies Record<FeatureFlag, AllowedFeatureFlagTypes>;
|
||||||
|
|
||||||
export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue;
|
export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue;
|
||||||
|
Loading…
Reference in New Issue
Block a user