mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 13:53:34 +00:00
[PM-13811] Remove conditional code for extension refresh on web (#13145)
* Enable UI refresh on web by default Removing all conditional code around the `ExtensionRefresh`-feature-flag on the web-UI * Remove no longer needed extensRefresh helpers --------- Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
7e2e604439
commit
9ddaf96020
@@ -22,7 +22,7 @@
|
|||||||
[routerLink]="[]"
|
[routerLink]="[]"
|
||||||
[queryParams]="{ itemId: cipher.id, action: clickAction }"
|
[queryParams]="{ itemId: cipher.id, action: clickAction }"
|
||||||
queryParamsHandling="merge"
|
queryParamsHandling="merge"
|
||||||
[replaceUrl]="extensionRefreshEnabled"
|
[replaceUrl]="true"
|
||||||
title="{{ 'editItemWithName' | i18n: cipher.name }}"
|
title="{{ 'editItemWithName' | i18n: cipher.name }}"
|
||||||
type="button"
|
type="button"
|
||||||
appStopProp
|
appStopProp
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||||
import { firstValueFrom } from "rxjs";
|
|
||||||
|
|
||||||
import { CollectionView } from "@bitwarden/admin-console/common";
|
import { CollectionView } from "@bitwarden/admin-console/common";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
@@ -25,11 +22,6 @@ import { RowHeightClass } from "./vault-items.component";
|
|||||||
export class VaultCipherRowComponent implements OnInit {
|
export class VaultCipherRowComponent implements OnInit {
|
||||||
protected RowHeightClass = RowHeightClass;
|
protected RowHeightClass = RowHeightClass;
|
||||||
|
|
||||||
/**
|
|
||||||
* Flag to determine if the extension refresh feature flag is enabled.
|
|
||||||
*/
|
|
||||||
protected extensionRefreshEnabled = false;
|
|
||||||
|
|
||||||
@Input() disabled: boolean;
|
@Input() disabled: boolean;
|
||||||
@Input() cipher: CipherView;
|
@Input() cipher: CipherView;
|
||||||
@Input() showOwner: boolean;
|
@Input() showOwner: boolean;
|
||||||
@@ -61,19 +53,12 @@ export class VaultCipherRowComponent implements OnInit {
|
|||||||
];
|
];
|
||||||
protected organization?: Organization;
|
protected organization?: Organization;
|
||||||
|
|
||||||
constructor(
|
constructor(private i18nService: I18nService) {}
|
||||||
private configService: ConfigService,
|
|
||||||
private i18nService: I18nService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lifecycle hook for component initialization.
|
* Lifecycle hook for component initialization.
|
||||||
* Checks if the extension refresh feature flag is enabled to provide to template.
|
|
||||||
*/
|
*/
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
this.extensionRefreshEnabled = await firstValueFrom(
|
|
||||||
this.configService.getFeatureFlag$(FeatureFlag.ExtensionRefresh),
|
|
||||||
);
|
|
||||||
if (this.cipher.organizationId != null) {
|
if (this.cipher.organizationId != null) {
|
||||||
this.organization = this.organizations.find((o) => o.id === this.cipher.organizationId);
|
this.organization = this.organizations.find((o) => o.id === this.cipher.organizationId);
|
||||||
}
|
}
|
||||||
@@ -83,7 +68,7 @@ export class VaultCipherRowComponent implements OnInit {
|
|||||||
if (this.cipher.decryptionFailure) {
|
if (this.cipher.decryptionFailure) {
|
||||||
return "showFailedToDecrypt";
|
return "showFailedToDecrypt";
|
||||||
}
|
}
|
||||||
return this.extensionRefreshEnabled ? "view" : null;
|
return "view";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected get showTotpCopyButton() {
|
protected get showTotpCopyButton() {
|
||||||
|
|||||||
@@ -143,11 +143,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
const extensionRefreshEnabled = await firstValueFrom(
|
this.cardIsExpired = isCardExpired(this.cipher.card);
|
||||||
this.configService.getFeatureFlag$(FeatureFlag.ExtensionRefresh),
|
|
||||||
);
|
|
||||||
|
|
||||||
this.cardIsExpired = extensionRefreshEnabled && isCardExpired(this.cipher.card);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
|
|||||||
@@ -69,88 +69,48 @@
|
|||||||
|
|
||||||
<div *ngIf="filter.type !== 'trash'" class="tw-shrink-0">
|
<div *ngIf="filter.type !== 'trash'" class="tw-shrink-0">
|
||||||
<div appListDropdown>
|
<div appListDropdown>
|
||||||
<ng-container [ngSwitch]="extensionRefreshEnabled">
|
<button
|
||||||
<ng-container *ngSwitchCase="true">
|
bitButton
|
||||||
<button
|
buttonType="primary"
|
||||||
bitButton
|
type="button"
|
||||||
buttonType="primary"
|
[bitMenuTriggerFor]="addOptions"
|
||||||
type="button"
|
id="newItemDropdown"
|
||||||
[bitMenuTriggerFor]="addOptions"
|
appA11yTitle="{{ 'new' | i18n }}"
|
||||||
id="newItemDropdown"
|
>
|
||||||
appA11yTitle="{{ 'new' | i18n }}"
|
<i class="bwi bwi-plus-f" aria-hidden="true"></i>
|
||||||
>
|
{{ "new" | i18n }}<i class="bwi tw-ml-2" aria-hidden="true"></i>
|
||||||
<i class="bwi bwi-plus-f" aria-hidden="true"></i>
|
</button>
|
||||||
{{ "new" | i18n }}<i class="bwi tw-ml-2" aria-hidden="true"></i>
|
<bit-menu #addOptions aria-labelledby="newItemDropdown">
|
||||||
</button>
|
<button type="button" bitMenuItem (click)="addCipher(CipherType.Login)">
|
||||||
<bit-menu #addOptions aria-labelledby="newItemDropdown">
|
<i class="bwi bwi-globe" slot="start" aria-hidden="true"></i>
|
||||||
<button type="button" bitMenuItem (click)="addCipher(CipherType.Login)">
|
{{ "typeLogin" | i18n }}
|
||||||
<i class="bwi bwi-globe" slot="start" aria-hidden="true"></i>
|
</button>
|
||||||
{{ "typeLogin" | i18n }}
|
<button type="button" bitMenuItem (click)="addCipher(CipherType.Card)">
|
||||||
</button>
|
<i class="bwi bwi-credit-card" slot="start" aria-hidden="true"></i>
|
||||||
<button type="button" bitMenuItem (click)="addCipher(CipherType.Card)">
|
{{ "typeCard" | i18n }}
|
||||||
<i class="bwi bwi-credit-card" slot="start" aria-hidden="true"></i>
|
</button>
|
||||||
{{ "typeCard" | i18n }}
|
<button type="button" bitMenuItem (click)="addCipher(CipherType.Identity)">
|
||||||
</button>
|
<i class="bwi bwi-id-card" slot="start" aria-hidden="true"></i>
|
||||||
<button type="button" bitMenuItem (click)="addCipher(CipherType.Identity)">
|
{{ "typeIdentity" | i18n }}
|
||||||
<i class="bwi bwi-id-card" slot="start" aria-hidden="true"></i>
|
</button>
|
||||||
{{ "typeIdentity" | i18n }}
|
<button type="button" bitMenuItem (click)="addCipher(CipherType.SecureNote)">
|
||||||
</button>
|
<i class="bwi bwi-sticky-note" slot="start" aria-hidden="true"></i>
|
||||||
<button type="button" bitMenuItem (click)="addCipher(CipherType.SecureNote)">
|
{{ "note" | i18n }}
|
||||||
<i class="bwi bwi-sticky-note" slot="start" aria-hidden="true"></i>
|
</button>
|
||||||
{{ "note" | i18n }}
|
<button type="button" bitMenuItem (click)="addCipher(CipherType.SshKey)">
|
||||||
</button>
|
<i class="bwi bwi-key" slot="start" aria-hidden="true"></i>
|
||||||
<button type="button" bitMenuItem (click)="addCipher(CipherType.SshKey)">
|
{{ "typeSshKey" | i18n }}
|
||||||
<i class="bwi bwi-key" slot="start" aria-hidden="true"></i>
|
</button>
|
||||||
{{ "typeSshKey" | i18n }}
|
<bit-menu-divider />
|
||||||
</button>
|
<button type="button" bitMenuItem (click)="addFolder()">
|
||||||
<bit-menu-divider />
|
<i class="bwi bwi-fw bwi-folder" aria-hidden="true"></i>
|
||||||
<button type="button" bitMenuItem (click)="addFolder()">
|
{{ "folder" | i18n }}
|
||||||
<i class="bwi bwi-fw bwi-folder" aria-hidden="true"></i>
|
</button>
|
||||||
{{ "folder" | i18n }}
|
<button *ngIf="canCreateCollections" type="button" bitMenuItem (click)="addCollection()">
|
||||||
</button>
|
<i class="bwi bwi-fw bwi-collection" aria-hidden="true"></i>
|
||||||
<button
|
{{ "collection" | i18n }}
|
||||||
*ngIf="canCreateCollections"
|
</button>
|
||||||
type="button"
|
</bit-menu>
|
||||||
bitMenuItem
|
|
||||||
(click)="addCollection()"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-fw bwi-collection" aria-hidden="true"></i>
|
|
||||||
{{ "collection" | i18n }}
|
|
||||||
</button>
|
|
||||||
</bit-menu>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngSwitchCase="false">
|
|
||||||
<button
|
|
||||||
bitButton
|
|
||||||
buttonType="primary"
|
|
||||||
type="button"
|
|
||||||
[bitMenuTriggerFor]="addOptions"
|
|
||||||
id="newItemDropdown"
|
|
||||||
appA11yTitle="{{ 'new' | i18n }}"
|
|
||||||
>
|
|
||||||
{{ "new" | i18n }}<i class="bwi bwi-angle-down tw-ml-2" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
<bit-menu #addOptions aria-labelledby="newItemDropdown">
|
|
||||||
<button type="button" bitMenuItem (click)="addCipher()">
|
|
||||||
<i class="bwi bwi-fw bwi-globe" aria-hidden="true"></i>
|
|
||||||
{{ "item" | i18n }}
|
|
||||||
</button>
|
|
||||||
<button type="button" bitMenuItem (click)="addFolder()">
|
|
||||||
<i class="bwi bwi-fw bwi-folder" aria-hidden="true"></i>
|
|
||||||
{{ "folder" | i18n }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
*ngIf="canCreateCollections"
|
|
||||||
type="button"
|
|
||||||
bitMenuItem
|
|
||||||
(click)="addCollection()"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-fw bwi-collection" aria-hidden="true"></i>
|
|
||||||
{{ "collection" | i18n }}
|
|
||||||
</button>
|
|
||||||
</bit-menu>
|
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</app-header>
|
</app-header>
|
||||||
|
|||||||
@@ -9,13 +9,10 @@ import {
|
|||||||
OnInit,
|
OnInit,
|
||||||
Output,
|
Output,
|
||||||
} from "@angular/core";
|
} from "@angular/core";
|
||||||
import { firstValueFrom } from "rxjs";
|
|
||||||
|
|
||||||
import { Unassigned, CollectionView } from "@bitwarden/admin-console/common";
|
import { Unassigned, CollectionView } from "@bitwarden/admin-console/common";
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||||
@@ -50,7 +47,6 @@ export class VaultHeaderComponent implements OnInit {
|
|||||||
protected All = All;
|
protected All = All;
|
||||||
protected CollectionDialogTabType = CollectionDialogTabType;
|
protected CollectionDialogTabType = CollectionDialogTabType;
|
||||||
protected CipherType = CipherType;
|
protected CipherType = CipherType;
|
||||||
protected extensionRefreshEnabled: boolean;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Boolean to determine the loading state of the header.
|
* Boolean to determine the loading state of the header.
|
||||||
@@ -85,16 +81,9 @@ export class VaultHeaderComponent implements OnInit {
|
|||||||
/** Emits an event when the delete collection button is clicked in the header */
|
/** Emits an event when the delete collection button is clicked in the header */
|
||||||
@Output() onDeleteCollection = new EventEmitter<void>();
|
@Output() onDeleteCollection = new EventEmitter<void>();
|
||||||
|
|
||||||
constructor(
|
constructor(private i18nService: I18nService) {}
|
||||||
private i18nService: I18nService,
|
|
||||||
private configService: ConfigService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {}
|
||||||
this.extensionRefreshEnabled = await firstValueFrom(
|
|
||||||
this.configService.getFeatureFlag$(FeatureFlag.ExtensionRefresh),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The id of the organization that is currently being filtered on.
|
* The id of the organization that is currently being filtered on.
|
||||||
|
|||||||
@@ -22,12 +22,7 @@
|
|||||||
<p class="tw-pl-1">
|
<p class="tw-pl-1">
|
||||||
{{ "onboardingImportDataDetailsPartOne" | i18n }}
|
{{ "onboardingImportDataDetailsPartOne" | i18n }}
|
||||||
<button type="button" bitLink (click)="emitToAddCipher()">
|
<button type="button" bitLink (click)="emitToAddCipher()">
|
||||||
{{
|
{{ "onboardingImportDataDetailsLoginLink" | i18n }}
|
||||||
(extensionRefreshEnabled
|
|
||||||
? "onboardingImportDataDetailsLoginLink"
|
|
||||||
: "onboardingImportDataDetailsLink"
|
|
||||||
) | i18n
|
|
||||||
}}
|
|
||||||
</button>
|
</button>
|
||||||
<span>
|
<span>
|
||||||
{{ "onboardingImportDataDetailsPartTwoNoOrgs" | i18n }}
|
{{ "onboardingImportDataDetailsPartTwoNoOrgs" | i18n }}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { Subject, of } from "rxjs";
|
|||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||||
@@ -28,7 +27,6 @@ describe("VaultOnboardingComponent", () => {
|
|||||||
let mockStateProvider: Partial<StateProvider>;
|
let mockStateProvider: Partial<StateProvider>;
|
||||||
let setInstallExtLinkSpy: any;
|
let setInstallExtLinkSpy: any;
|
||||||
let individualVaultPolicyCheckSpy: any;
|
let individualVaultPolicyCheckSpy: any;
|
||||||
let mockConfigService: MockProxy<ConfigService>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockPolicyService = mock<PolicyService>();
|
mockPolicyService = mock<PolicyService>();
|
||||||
@@ -47,7 +45,6 @@ describe("VaultOnboardingComponent", () => {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
mockConfigService = mock<ConfigService>();
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@@ -60,7 +57,6 @@ describe("VaultOnboardingComponent", () => {
|
|||||||
{ provide: I18nService, useValue: mockI18nService },
|
{ provide: I18nService, useValue: mockI18nService },
|
||||||
{ provide: ApiService, useValue: mockApiService },
|
{ provide: ApiService, useValue: mockApiService },
|
||||||
{ provide: StateProvider, useValue: mockStateProvider },
|
{ provide: StateProvider, useValue: mockStateProvider },
|
||||||
{ provide: ConfigService, useValue: mockConfigService },
|
|
||||||
],
|
],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
fixture = TestBed.createComponent(VaultOnboardingComponent);
|
fixture = TestBed.createComponent(VaultOnboardingComponent);
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
|||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
||||||
import { VaultOnboardingMessages } from "@bitwarden/common/vault/enums/vault-onboarding.enum";
|
import { VaultOnboardingMessages } from "@bitwarden/common/vault/enums/vault-onboarding.enum";
|
||||||
@@ -58,14 +56,12 @@ export class VaultOnboardingComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
|
|
||||||
protected onboardingTasks$: Observable<VaultOnboardingTasks>;
|
protected onboardingTasks$: Observable<VaultOnboardingTasks>;
|
||||||
protected showOnboarding = false;
|
protected showOnboarding = false;
|
||||||
protected extensionRefreshEnabled = false;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected platformUtilsService: PlatformUtilsService,
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
protected policyService: PolicyService,
|
protected policyService: PolicyService,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private vaultOnboardingService: VaultOnboardingServiceAbstraction,
|
private vaultOnboardingService: VaultOnboardingServiceAbstraction,
|
||||||
private configService: ConfigService,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
@@ -74,9 +70,6 @@ export class VaultOnboardingComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
this.setInstallExtLink();
|
this.setInstallExtLink();
|
||||||
this.individualVaultPolicyCheck();
|
this.individualVaultPolicyCheck();
|
||||||
this.checkForBrowserExtension();
|
this.checkForBrowserExtension();
|
||||||
this.extensionRefreshEnabled = await this.configService.getFeatureFlag(
|
|
||||||
FeatureFlag.ExtensionRefresh,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnChanges(changes: SimpleChanges) {
|
async ngOnChanges(changes: SimpleChanges) {
|
||||||
|
|||||||
@@ -1,15 +1,7 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { DialogRef } from "@angular/cdk/dialog";
|
import { DialogRef } from "@angular/cdk/dialog";
|
||||||
import {
|
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core";
|
||||||
ChangeDetectorRef,
|
|
||||||
Component,
|
|
||||||
NgZone,
|
|
||||||
OnDestroy,
|
|
||||||
OnInit,
|
|
||||||
ViewChild,
|
|
||||||
ViewContainerRef,
|
|
||||||
} from "@angular/core";
|
|
||||||
import { ActivatedRoute, Params, Router } from "@angular/router";
|
import { ActivatedRoute, Params, Router } from "@angular/router";
|
||||||
import {
|
import {
|
||||||
BehaviorSubject,
|
BehaviorSubject,
|
||||||
@@ -42,7 +34,6 @@ import {
|
|||||||
Unassigned,
|
Unassigned,
|
||||||
} from "@bitwarden/admin-console/common";
|
} from "@bitwarden/admin-console/common";
|
||||||
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
|
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||||
@@ -57,9 +48,7 @@ import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing
|
|||||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
|
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
|
||||||
import { EventType } from "@bitwarden/common/enums";
|
import { EventType } from "@bitwarden/common/enums";
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
|
||||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
@@ -71,7 +60,6 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi
|
|||||||
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
|
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
||||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
|
||||||
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
import { ServiceUtils } from "@bitwarden/common/vault/service-utils";
|
import { ServiceUtils } from "@bitwarden/common/vault/service-utils";
|
||||||
@@ -105,13 +93,11 @@ import { VaultItemEvent } from "../components/vault-items/vault-item-event";
|
|||||||
import { VaultItemsModule } from "../components/vault-items/vault-items.module";
|
import { VaultItemsModule } from "../components/vault-items/vault-items.module";
|
||||||
import { getNestedCollectionTree } from "../utils/collection-utils";
|
import { getNestedCollectionTree } from "../utils/collection-utils";
|
||||||
|
|
||||||
import { AddEditComponent } from "./add-edit.component";
|
|
||||||
import {
|
import {
|
||||||
AttachmentDialogCloseResult,
|
AttachmentDialogCloseResult,
|
||||||
AttachmentDialogResult,
|
AttachmentDialogResult,
|
||||||
AttachmentsV2Component,
|
AttachmentsV2Component,
|
||||||
} from "./attachments-v2.component";
|
} from "./attachments-v2.component";
|
||||||
import { AttachmentsComponent } from "./attachments.component";
|
|
||||||
import {
|
import {
|
||||||
BulkDeleteDialogResult,
|
BulkDeleteDialogResult,
|
||||||
openBulkDeleteDialog,
|
openBulkDeleteDialog,
|
||||||
@@ -160,15 +146,6 @@ const SearchTextDebounceInterval = 200;
|
|||||||
})
|
})
|
||||||
export class VaultComponent implements OnInit, OnDestroy {
|
export class VaultComponent implements OnInit, OnDestroy {
|
||||||
@ViewChild("vaultFilter", { static: true }) filterComponent: VaultFilterComponent;
|
@ViewChild("vaultFilter", { static: true }) filterComponent: VaultFilterComponent;
|
||||||
@ViewChild("attachments", { read: ViewContainerRef, static: true })
|
|
||||||
attachmentsModalRef: ViewContainerRef;
|
|
||||||
@ViewChild("folderAddEdit", { read: ViewContainerRef, static: true })
|
|
||||||
folderAddEditModalRef: ViewContainerRef;
|
|
||||||
@ViewChild("cipherAddEdit", { read: ViewContainerRef, static: true })
|
|
||||||
cipherAddEditModalRef: ViewContainerRef;
|
|
||||||
@ViewChild("share", { read: ViewContainerRef, static: true }) shareModalRef: ViewContainerRef;
|
|
||||||
@ViewChild("collectionsModal", { read: ViewContainerRef, static: true })
|
|
||||||
collectionsModalRef: ViewContainerRef;
|
|
||||||
|
|
||||||
trashCleanupWarning: string = null;
|
trashCleanupWarning: string = null;
|
||||||
kdfIterations: number;
|
kdfIterations: number;
|
||||||
@@ -193,7 +170,6 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
private searchText$ = new Subject<string>();
|
private searchText$ = new Subject<string>();
|
||||||
private refresh$ = new BehaviorSubject<void>(null);
|
private refresh$ = new BehaviorSubject<void>(null);
|
||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
private extensionRefreshEnabled: boolean;
|
|
||||||
private hasSubscription$ = new BehaviorSubject<boolean>(false);
|
private hasSubscription$ = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
private vaultItemDialogRef?: DialogRef<VaultItemDialogResult> | undefined;
|
private vaultItemDialogRef?: DialogRef<VaultItemDialogResult> | undefined;
|
||||||
@@ -260,7 +236,6 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
private router: Router,
|
private router: Router,
|
||||||
private changeDetectorRef: ChangeDetectorRef,
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private modalService: ModalService,
|
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
private messagingService: MessagingService,
|
private messagingService: MessagingService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
@@ -278,7 +253,6 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
private eventCollectionService: EventCollectionService,
|
private eventCollectionService: EventCollectionService,
|
||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
private searchPipe: SearchPipe,
|
private searchPipe: SearchPipe,
|
||||||
private configService: ConfigService,
|
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private billingAccountProfileStateService: BillingAccountProfileStateService,
|
private billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
@@ -437,15 +411,15 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
firstSetup$
|
firstSetup$
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap(() => this.route.queryParams),
|
switchMap(() => this.route.queryParams),
|
||||||
// Only process the queryParams if the dialog is not open (only when extension refresh is enabled)
|
// Only process the queryParams if the dialog is not open
|
||||||
filter(() => this.vaultItemDialogRef == undefined || !this.extensionRefreshEnabled),
|
filter(() => this.vaultItemDialogRef == undefined),
|
||||||
switchMap(async (params) => {
|
switchMap(async (params) => {
|
||||||
const cipherId = getCipherIdFromParams(params);
|
const cipherId = getCipherIdFromParams(params);
|
||||||
if (cipherId) {
|
if (cipherId) {
|
||||||
if (await this.cipherService.get(cipherId)) {
|
if (await this.cipherService.get(cipherId)) {
|
||||||
let action = params.action;
|
let action = params.action;
|
||||||
// Default to "view" if extension refresh is enabled
|
// Default to "view"
|
||||||
if (action == null && this.extensionRefreshEnabled) {
|
if (action == null) {
|
||||||
action = "view";
|
action = "view";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,11 +518,6 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
this.refreshing = false;
|
this.refreshing = false;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check if the extension refresh feature flag is enabled
|
|
||||||
this.extensionRefreshEnabled = await this.configService.getFeatureFlag(
|
|
||||||
FeatureFlag.ExtensionRefresh,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
@@ -642,8 +611,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
* Handles opening the attachments dialog for a cipher.
|
* Handles opening the attachments dialog for a cipher.
|
||||||
* Runs several checks to ensure that the user has the correct permissions
|
* Runs several checks to ensure that the user has the correct permissions
|
||||||
* and then opens the attachments dialog.
|
* and then opens the attachments dialog.
|
||||||
* Uses the new AttachmentsV2Component if the extensionRefresh feature flag is enabled.
|
* Uses the new AttachmentsV2Component
|
||||||
*
|
|
||||||
* @param cipher
|
* @param cipher
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
@@ -668,51 +636,20 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const canEditAttachments = await this.canEditAttachments(cipher);
|
const dialogRef = AttachmentsV2Component.open(this.dialogService, {
|
||||||
|
cipherId: cipher.id as CipherId,
|
||||||
|
});
|
||||||
|
|
||||||
let madeAttachmentChanges = false;
|
const result: AttachmentDialogCloseResult = await lastValueFrom(dialogRef.closed);
|
||||||
|
|
||||||
if (this.extensionRefreshEnabled) {
|
if (
|
||||||
const dialogRef = AttachmentsV2Component.open(this.dialogService, {
|
result.action === AttachmentDialogResult.Uploaded ||
|
||||||
cipherId: cipher.id as CipherId,
|
result.action === AttachmentDialogResult.Removed
|
||||||
});
|
) {
|
||||||
|
this.refresh();
|
||||||
const result: AttachmentDialogCloseResult = await lastValueFrom(dialogRef.closed);
|
|
||||||
|
|
||||||
if (
|
|
||||||
result.action === AttachmentDialogResult.Uploaded ||
|
|
||||||
result.action === AttachmentDialogResult.Removed
|
|
||||||
) {
|
|
||||||
this.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const [modal] = await this.modalService.openViewRef(
|
return;
|
||||||
AttachmentsComponent,
|
|
||||||
this.attachmentsModalRef,
|
|
||||||
(comp) => {
|
|
||||||
comp.cipherId = cipher.id;
|
|
||||||
comp.viewOnly = !canEditAttachments;
|
|
||||||
comp.onUploadedAttachment
|
|
||||||
.pipe(takeUntil(this.destroy$))
|
|
||||||
.subscribe(() => (madeAttachmentChanges = true));
|
|
||||||
comp.onDeletedAttachment
|
|
||||||
.pipe(takeUntil(this.destroy$))
|
|
||||||
.subscribe(() => (madeAttachmentChanges = true));
|
|
||||||
comp.onReuploadedAttachment
|
|
||||||
.pipe(takeUntil(this.destroy$))
|
|
||||||
.subscribe(() => (madeAttachmentChanges = true));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
modal.onClosed.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
||||||
if (madeAttachmentChanges) {
|
|
||||||
this.refresh();
|
|
||||||
}
|
|
||||||
madeAttachmentChanges = false;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -751,48 +688,13 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
await this.go({ cipherId: null, itemId: null, action: null });
|
await this.go({ cipherId: null, itemId: null, action: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
async addCipher(cipherType?: CipherType) {
|
|
||||||
const type = cipherType ?? this.activeFilter.cipherType;
|
|
||||||
|
|
||||||
if (this.extensionRefreshEnabled) {
|
|
||||||
return this.addCipherV2(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
const component = (await this.editCipher(null)) as AddEditComponent;
|
|
||||||
component.type = type;
|
|
||||||
if (
|
|
||||||
this.activeFilter.organizationId !== "MyVault" &&
|
|
||||||
this.activeFilter.organizationId != null
|
|
||||||
) {
|
|
||||||
component.organizationId = this.activeFilter.organizationId;
|
|
||||||
component.collections = (
|
|
||||||
await firstValueFrom(this.vaultFilterService.filteredCollections$)
|
|
||||||
).filter((c) => !c.readOnly && c.id != null);
|
|
||||||
}
|
|
||||||
const selectedColId = this.activeFilter.collectionId;
|
|
||||||
if (selectedColId !== "AllCollections" && selectedColId != null) {
|
|
||||||
const selectedCollection = (
|
|
||||||
await firstValueFrom(this.vaultFilterService.filteredCollections$)
|
|
||||||
).find((c) => c.id === selectedColId);
|
|
||||||
component.organizationId = selectedCollection?.organizationId;
|
|
||||||
if (!selectedCollection.readOnly) {
|
|
||||||
component.collectionIds = [selectedColId];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
component.folderId = this.activeFilter.folderId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the add cipher dialog.
|
* Opens the add cipher dialog.
|
||||||
* @param cipherType The type of cipher to add.
|
* @param cipherType The type of cipher to add.
|
||||||
* @returns The dialog reference.
|
|
||||||
*/
|
*/
|
||||||
async addCipherV2(cipherType?: CipherType) {
|
async addCipher(cipherType?: CipherType) {
|
||||||
const cipherFormConfig = await this.cipherFormConfigService.buildConfig(
|
const type = cipherType ?? this.activeFilter.cipherType;
|
||||||
"add",
|
const cipherFormConfig = await this.cipherFormConfigService.buildConfig("add", null, type);
|
||||||
null,
|
|
||||||
cipherType,
|
|
||||||
);
|
|
||||||
const collectionId =
|
const collectionId =
|
||||||
this.activeFilter.collectionId !== "AllCollections" && this.activeFilter.collectionId != null
|
this.activeFilter.collectionId !== "AllCollections" && this.activeFilter.collectionId != null
|
||||||
? this.activeFilter.collectionId
|
? this.activeFilter.collectionId
|
||||||
@@ -823,6 +725,12 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
return this.editCipherId(cipher?.id, cloneMode);
|
return this.editCipherId(cipher?.id, cloneMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit a cipher using the new VaultItemDialog.
|
||||||
|
* @param id
|
||||||
|
* @param cloneMode
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
async editCipherId(id: string, cloneMode?: boolean) {
|
async editCipherId(id: string, cloneMode?: boolean) {
|
||||||
const cipher = await this.cipherService.get(id);
|
const cipher = await this.cipherService.get(id);
|
||||||
|
|
||||||
@@ -836,49 +744,6 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.extensionRefreshEnabled) {
|
|
||||||
await this.editCipherIdV2(cipher, cloneMode);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [modal, childComponent] = await this.modalService.openViewRef(
|
|
||||||
AddEditComponent,
|
|
||||||
this.cipherAddEditModalRef,
|
|
||||||
(comp) => {
|
|
||||||
comp.cipherId = id;
|
|
||||||
comp.collectionId = this.selectedCollection?.node.id;
|
|
||||||
|
|
||||||
comp.onSavedCipher.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
||||||
modal.close();
|
|
||||||
this.refresh();
|
|
||||||
});
|
|
||||||
comp.onDeletedCipher.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
||||||
modal.close();
|
|
||||||
this.refresh();
|
|
||||||
});
|
|
||||||
comp.onRestoredCipher.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
||||||
modal.close();
|
|
||||||
this.refresh();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
modal.onClosedPromise().then(() => {
|
|
||||||
void this.go({ cipherId: null, itemId: null, action: null });
|
|
||||||
});
|
|
||||||
|
|
||||||
return childComponent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Edit a cipher using the new VaultItemDialog.
|
|
||||||
*
|
|
||||||
* @param cipher
|
|
||||||
* @param cloneMode
|
|
||||||
*/
|
|
||||||
private async editCipherIdV2(cipher: Cipher, cloneMode?: boolean) {
|
|
||||||
const cipherFormConfig = await this.cipherFormConfigService.buildConfig(
|
const cipherFormConfig = await this.cipherFormConfigService.buildConfig(
|
||||||
cloneMode ? "clone" : "edit",
|
cloneMode ? "clone" : "edit",
|
||||||
cipher.id as CipherId,
|
cipher.id as CipherId,
|
||||||
@@ -1076,11 +941,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const component = await this.editCipher(cipher, true);
|
await this.editCipher(cipher, true);
|
||||||
|
|
||||||
if (component != null) {
|
|
||||||
component.cloneMode = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
restore = async (c: CipherView): Promise<boolean> => {
|
restore = async (c: CipherView): Promise<boolean> => {
|
||||||
@@ -1331,15 +1192,6 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
this.refresh$.next();
|
this.refresh$.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async canEditAttachments(cipher: CipherView) {
|
|
||||||
if (cipher.organizationId == null || cipher.edit) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const organization = this.allOrganizations.find((o) => o.id === cipher.organizationId);
|
|
||||||
return organization.canEditAllCiphers;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async go(queryParams: any = null) {
|
private async go(queryParams: any = null) {
|
||||||
if (queryParams == null) {
|
if (queryParams == null) {
|
||||||
queryParams = {
|
queryParams = {
|
||||||
|
|||||||
@@ -104,8 +104,8 @@
|
|||||||
*ngIf="filter.type !== 'trash' && filter.collectionId !== Unassigned && organization"
|
*ngIf="filter.type !== 'trash' && filter.collectionId !== Unassigned && organization"
|
||||||
class="tw-shrink-0"
|
class="tw-shrink-0"
|
||||||
>
|
>
|
||||||
<!-- "New" menu is always shown for Extension Refresh unless the user cannot create a cipher -->
|
<!-- "New" menu is always shown unless the user cannot create a cipher -->
|
||||||
<ng-container *ngIf="extensionRefreshEnabled && canCreateCipher; else nonRefresh">
|
<ng-container *ngIf="canCreateCipher">
|
||||||
<div appListDropdown>
|
<div appListDropdown>
|
||||||
<button
|
<button
|
||||||
bitButton
|
bitButton
|
||||||
@@ -145,56 +145,5 @@
|
|||||||
</bit-menu>
|
</bit-menu>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-template #nonRefresh>
|
|
||||||
<!-- Show a menu when the user can create a cipher and collection -->
|
|
||||||
<div *ngIf="canCreateCipher && canCreateCollection" appListDropdown>
|
|
||||||
<button
|
|
||||||
bitButton
|
|
||||||
buttonType="primary"
|
|
||||||
type="button"
|
|
||||||
[bitMenuTriggerFor]="addOptions"
|
|
||||||
id="newItemDropdown"
|
|
||||||
appA11yTitle="{{ 'new' | i18n }}"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-plus-f" aria-hidden="true"></i>
|
|
||||||
{{ "new" | i18n }}<i class="bwi tw-ml-2" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
<bit-menu #addOptions aria-labelledby="newItemDropdown">
|
|
||||||
<button type="button" bitMenuItem (click)="addCipher()">
|
|
||||||
<i class="bwi bwi-fw bwi-globe" aria-hidden="true"></i>
|
|
||||||
{{ "item" | i18n }}
|
|
||||||
</button>
|
|
||||||
<button type="button" bitMenuItem (click)="addCollection()">
|
|
||||||
<i class="bwi bwi-fw bwi-collection" aria-hidden="true"></i>
|
|
||||||
{{ "collection" | i18n }}
|
|
||||||
</button>
|
|
||||||
</bit-menu>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Show a single button when the user can only create a cipher -->
|
|
||||||
<button
|
|
||||||
*ngIf="canCreateCipher && !canCreateCollection"
|
|
||||||
type="button"
|
|
||||||
bitButton
|
|
||||||
buttonType="primary"
|
|
||||||
(click)="addCipher()"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
|
||||||
{{ "newItem" | i18n }}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Show a single button when the user can only create a collection -->
|
|
||||||
<button
|
|
||||||
*ngIf="canCreateCollection && !canCreateCipher"
|
|
||||||
type="button"
|
|
||||||
bitButton
|
|
||||||
buttonType="primary"
|
|
||||||
(click)="addCollection()"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
|
||||||
{{ "newCollection" | i18n }}
|
|
||||||
</button>
|
|
||||||
</ng-template>
|
|
||||||
</div>
|
</div>
|
||||||
</app-header>
|
</app-header>
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import {
|
|||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { ProductTierType } from "@bitwarden/common/billing/enums";
|
import { ProductTierType } from "@bitwarden/common/billing/enums";
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
@@ -90,11 +89,6 @@ export class VaultHeaderComponent implements OnInit {
|
|||||||
|
|
||||||
protected CollectionDialogTabType = CollectionDialogTabType;
|
protected CollectionDialogTabType = CollectionDialogTabType;
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the extension refresh feature flag is enabled.
|
|
||||||
*/
|
|
||||||
protected extensionRefreshEnabled = false;
|
|
||||||
|
|
||||||
/** The cipher type enum. */
|
/** The cipher type enum. */
|
||||||
protected CipherType = CipherType;
|
protected CipherType = CipherType;
|
||||||
|
|
||||||
@@ -106,11 +100,7 @@ export class VaultHeaderComponent implements OnInit {
|
|||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {}
|
||||||
this.extensionRefreshEnabled = await this.configService.getFeatureFlag(
|
|
||||||
FeatureFlag.ExtensionRefresh,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
const headerType = this.i18nService.t("collections").toLowerCase();
|
const headerType = this.i18nService.t("collections").toLowerCase();
|
||||||
|
|||||||
@@ -1,15 +1,7 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { DialogRef } from "@angular/cdk/dialog";
|
import { DialogRef } from "@angular/cdk/dialog";
|
||||||
import {
|
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||||
ChangeDetectorRef,
|
|
||||||
Component,
|
|
||||||
NgZone,
|
|
||||||
OnDestroy,
|
|
||||||
OnInit,
|
|
||||||
ViewChild,
|
|
||||||
ViewContainerRef,
|
|
||||||
} from "@angular/core";
|
|
||||||
import { ActivatedRoute, Params, Router } from "@angular/router";
|
import { ActivatedRoute, Params, Router } from "@angular/router";
|
||||||
import {
|
import {
|
||||||
BehaviorSubject,
|
BehaviorSubject,
|
||||||
@@ -37,12 +29,10 @@ import {
|
|||||||
import {
|
import {
|
||||||
CollectionAdminService,
|
CollectionAdminService,
|
||||||
CollectionAdminView,
|
CollectionAdminView,
|
||||||
CollectionService,
|
|
||||||
CollectionView,
|
CollectionView,
|
||||||
Unassigned,
|
Unassigned,
|
||||||
} from "@bitwarden/admin-console/common";
|
} from "@bitwarden/admin-console/common";
|
||||||
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
|
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||||
@@ -127,7 +117,6 @@ import {
|
|||||||
import { VaultHeaderComponent } from "../org-vault/vault-header/vault-header.component";
|
import { VaultHeaderComponent } from "../org-vault/vault-header/vault-header.component";
|
||||||
import { getNestedCollectionTree } from "../utils/collection-utils";
|
import { getNestedCollectionTree } from "../utils/collection-utils";
|
||||||
|
|
||||||
import { AddEditComponent } from "./add-edit.component";
|
|
||||||
import {
|
import {
|
||||||
BulkCollectionsDialogComponent,
|
BulkCollectionsDialogComponent,
|
||||||
BulkCollectionsDialogResult,
|
BulkCollectionsDialogResult,
|
||||||
@@ -166,13 +155,6 @@ enum AddAccessStatusType {
|
|||||||
export class VaultComponent implements OnInit, OnDestroy {
|
export class VaultComponent implements OnInit, OnDestroy {
|
||||||
protected Unassigned = Unassigned;
|
protected Unassigned = Unassigned;
|
||||||
|
|
||||||
@ViewChild("attachments", { read: ViewContainerRef, static: true })
|
|
||||||
attachmentsModalRef: ViewContainerRef;
|
|
||||||
@ViewChild("cipherAddEdit", { read: ViewContainerRef, static: true })
|
|
||||||
cipherAddEditModalRef: ViewContainerRef;
|
|
||||||
@ViewChild("collectionsModal", { read: ViewContainerRef, static: true })
|
|
||||||
collectionsModalRef: ViewContainerRef;
|
|
||||||
|
|
||||||
trashCleanupWarning: string = null;
|
trashCleanupWarning: string = null;
|
||||||
activeFilter: VaultFilter = new VaultFilter();
|
activeFilter: VaultFilter = new VaultFilter();
|
||||||
|
|
||||||
@@ -210,7 +192,6 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
private refresh$ = new BehaviorSubject<void>(null);
|
private refresh$ = new BehaviorSubject<void>(null);
|
||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
protected addAccessStatus$ = new BehaviorSubject<AddAccessStatusType>(0);
|
protected addAccessStatus$ = new BehaviorSubject<AddAccessStatusType>(0);
|
||||||
private extensionRefreshEnabled: boolean;
|
|
||||||
private resellerManagedOrgAlert: boolean;
|
private resellerManagedOrgAlert: boolean;
|
||||||
private vaultItemDialogRef?: DialogRef<VaultItemDialogResult> | undefined;
|
private vaultItemDialogRef?: DialogRef<VaultItemDialogResult> | undefined;
|
||||||
|
|
||||||
@@ -249,7 +230,6 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
private changeDetectorRef: ChangeDetectorRef,
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
private syncService: SyncService,
|
private syncService: SyncService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private modalService: ModalService,
|
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
private messagingService: MessagingService,
|
private messagingService: MessagingService,
|
||||||
private broadcasterService: BroadcasterService,
|
private broadcasterService: BroadcasterService,
|
||||||
@@ -265,7 +245,6 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
private eventCollectionService: EventCollectionService,
|
private eventCollectionService: EventCollectionService,
|
||||||
private totpService: TotpService,
|
private totpService: TotpService,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private collectionService: CollectionService,
|
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
private cipherFormConfigService: CipherFormConfigService,
|
private cipherFormConfigService: CipherFormConfigService,
|
||||||
@@ -278,10 +257,6 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.extensionRefreshEnabled = await this.configService.getFeatureFlag(
|
|
||||||
FeatureFlag.ExtensionRefresh,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.resellerManagedOrgAlert = await this.configService.getFeatureFlag(
|
this.resellerManagedOrgAlert = await this.configService.getFeatureFlag(
|
||||||
FeatureFlag.ResellerManagedOrgAlert,
|
FeatureFlag.ResellerManagedOrgAlert,
|
||||||
);
|
);
|
||||||
@@ -555,7 +530,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
firstSetup$
|
firstSetup$
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap(() => combineLatest([this.route.queryParams, allCipherMap$])),
|
switchMap(() => combineLatest([this.route.queryParams, allCipherMap$])),
|
||||||
filter(() => this.vaultItemDialogRef == undefined || !this.extensionRefreshEnabled),
|
filter(() => this.vaultItemDialogRef == undefined),
|
||||||
switchMap(async ([qParams, allCiphersMap]) => {
|
switchMap(async ([qParams, allCiphersMap]) => {
|
||||||
const cipherId = getCipherIdFromParams(qParams);
|
const cipherId = getCipherIdFromParams(qParams);
|
||||||
|
|
||||||
@@ -586,15 +561,15 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default to "view" if extension refresh is enabled
|
// Default to "view"
|
||||||
if (action == null && this.extensionRefreshEnabled) {
|
if (action == null) {
|
||||||
action = "view";
|
action = "view";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action === "view") {
|
if (action === "view") {
|
||||||
await this.viewCipherById(cipher);
|
await this.viewCipherById(cipher);
|
||||||
} else {
|
} else {
|
||||||
await this.editCipherId(cipher, false);
|
await this.editCipher(cipher, false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
@@ -836,27 +811,8 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Opens the Add/Edit Dialog */
|
||||||
async addCipher(cipherType?: CipherType) {
|
async addCipher(cipherType?: CipherType) {
|
||||||
if (this.extensionRefreshEnabled) {
|
|
||||||
return this.addCipherV2(cipherType);
|
|
||||||
}
|
|
||||||
|
|
||||||
let collections: CollectionView[] = [];
|
|
||||||
|
|
||||||
// Admins limited to only adding items to collections they have access to.
|
|
||||||
collections = await firstValueFrom(this.editableCollections$);
|
|
||||||
|
|
||||||
await this.editCipher(null, false, (comp) => {
|
|
||||||
comp.type = cipherType || this.activeFilter.cipherType;
|
|
||||||
comp.collections = collections;
|
|
||||||
if (this.activeFilter.collectionId) {
|
|
||||||
comp.collectionIds = [this.activeFilter.collectionId];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Opens the Add/Edit Dialog. Only to be used when the BrowserExtension feature flag is active */
|
|
||||||
async addCipherV2(cipherType?: CipherType) {
|
|
||||||
const cipherFormConfig = await this.cipherFormConfigService.buildConfig(
|
const cipherFormConfig = await this.cipherFormConfigService.buildConfig(
|
||||||
"add",
|
"add",
|
||||||
null,
|
null,
|
||||||
@@ -877,24 +833,8 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
* Edit the given cipher or add a new cipher
|
* Edit the given cipher or add a new cipher
|
||||||
* @param cipherView - When set, the cipher to be edited
|
* @param cipherView - When set, the cipher to be edited
|
||||||
* @param cloneCipher - `true` when the cipher should be cloned.
|
* @param cloneCipher - `true` when the cipher should be cloned.
|
||||||
* Used in place of the `additionalComponentParameters`, as
|
|
||||||
* the `editCipherIdV2` method has a differing implementation.
|
|
||||||
* @param defaultComponentParameters - A method that takes in an instance of
|
|
||||||
* the `AddEditComponent` to edit methods directly.
|
|
||||||
*/
|
*/
|
||||||
async editCipher(
|
async editCipher(cipher: CipherView | null, cloneCipher: boolean) {
|
||||||
cipher: CipherView | null,
|
|
||||||
cloneCipher: boolean,
|
|
||||||
additionalComponentParameters?: (comp: AddEditComponent) => void,
|
|
||||||
) {
|
|
||||||
return this.editCipherId(cipher, cloneCipher, additionalComponentParameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
async editCipherId(
|
|
||||||
cipher: CipherView | null,
|
|
||||||
cloneCipher: boolean,
|
|
||||||
additionalComponentParameters?: (comp: AddEditComponent) => void,
|
|
||||||
) {
|
|
||||||
if (
|
if (
|
||||||
cipher &&
|
cipher &&
|
||||||
cipher.reprompt !== 0 &&
|
cipher.reprompt !== 0 &&
|
||||||
@@ -905,55 +845,6 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.extensionRefreshEnabled) {
|
|
||||||
await this.editCipherIdV2(cipher, cloneCipher);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultComponentParameters = (comp: AddEditComponent) => {
|
|
||||||
comp.organization = this.organization;
|
|
||||||
comp.organizationId = this.organization.id;
|
|
||||||
comp.cipherId = cipher?.id;
|
|
||||||
comp.collectionId = this.activeFilter.collectionId;
|
|
||||||
comp.onSavedCipher.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
||||||
modal.close();
|
|
||||||
this.refresh();
|
|
||||||
});
|
|
||||||
comp.onDeletedCipher.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
||||||
modal.close();
|
|
||||||
this.refresh();
|
|
||||||
});
|
|
||||||
comp.onRestoredCipher.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
||||||
modal.close();
|
|
||||||
this.refresh();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const [modal, childComponent] = await this.modalService.openViewRef(
|
|
||||||
AddEditComponent,
|
|
||||||
this.cipherAddEditModalRef,
|
|
||||||
additionalComponentParameters == null
|
|
||||||
? defaultComponentParameters
|
|
||||||
: (comp) => {
|
|
||||||
defaultComponentParameters(comp);
|
|
||||||
additionalComponentParameters(comp);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
modal.onClosedPromise().then(() => {
|
|
||||||
this.go({ cipherId: null, itemId: null, action: null });
|
|
||||||
});
|
|
||||||
|
|
||||||
return childComponent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Edit a cipher using the new AddEditCipherDialogV2 component.
|
|
||||||
* Only to be used behind the ExtensionRefresh feature flag.
|
|
||||||
*/
|
|
||||||
private async editCipherIdV2(cipher: CipherView | null, cloneCipher: boolean) {
|
|
||||||
const cipherFormConfig = await this.cipherFormConfigService.buildConfig(
|
const cipherFormConfig = await this.cipherFormConfigService.buildConfig(
|
||||||
cloneCipher ? "clone" : "edit",
|
cloneCipher ? "clone" : "edit",
|
||||||
cipher?.id as CipherId | null,
|
cipher?.id as CipherId | null,
|
||||||
@@ -1038,16 +929,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let collections: CollectionView[] = [];
|
await this.editCipher(cipher, true);
|
||||||
|
|
||||||
// Admins limited to only adding items to collections they have access to.
|
|
||||||
collections = await firstValueFrom(this.editableCollections$);
|
|
||||||
|
|
||||||
await this.editCipher(cipher, true, (comp) => {
|
|
||||||
comp.cloneMode = true;
|
|
||||||
comp.collections = collections;
|
|
||||||
comp.collectionIds = cipher.collectionIds;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
restore = async (c: CipherView): Promise<boolean> => {
|
restore = async (c: CipherView): Promise<boolean> => {
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
import { TestBed } from "@angular/core/testing";
|
|
||||||
import { Navigation, Router, UrlTree } from "@angular/router";
|
|
||||||
import { mock, MockProxy } from "jest-mock-extended";
|
|
||||||
|
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
|
||||||
|
|
||||||
import { extensionRefreshRedirect } from "./extension-refresh-redirect";
|
|
||||||
|
|
||||||
describe("extensionRefreshRedirect", () => {
|
|
||||||
let configService: MockProxy<ConfigService>;
|
|
||||||
let router: MockProxy<Router>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
configService = mock<ConfigService>();
|
|
||||||
router = mock<Router>();
|
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
providers: [
|
|
||||||
{ provide: ConfigService, useValue: configService },
|
|
||||||
{ provide: Router, useValue: router },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns true when ExtensionRefresh flag is disabled", async () => {
|
|
||||||
configService.getFeatureFlag.mockResolvedValue(false);
|
|
||||||
|
|
||||||
const result = await TestBed.runInInjectionContext(() =>
|
|
||||||
extensionRefreshRedirect("/redirect")(),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result).toBe(true);
|
|
||||||
expect(configService.getFeatureFlag).toHaveBeenCalledWith(FeatureFlag.ExtensionRefresh);
|
|
||||||
expect(router.parseUrl).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns UrlTree when ExtensionRefresh flag is enabled and preserves query params", async () => {
|
|
||||||
configService.getFeatureFlag.mockResolvedValue(true);
|
|
||||||
|
|
||||||
const urlTree = new UrlTree();
|
|
||||||
urlTree.queryParams = { test: "test" };
|
|
||||||
|
|
||||||
const navigation: Navigation = {
|
|
||||||
extras: {},
|
|
||||||
id: 0,
|
|
||||||
initialUrl: new UrlTree(),
|
|
||||||
extractedUrl: urlTree,
|
|
||||||
trigger: "imperative",
|
|
||||||
previousNavigation: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
router.getCurrentNavigation.mockReturnValue(navigation);
|
|
||||||
|
|
||||||
await TestBed.runInInjectionContext(() => extensionRefreshRedirect("/redirect")());
|
|
||||||
|
|
||||||
expect(configService.getFeatureFlag).toHaveBeenCalledWith(FeatureFlag.ExtensionRefresh);
|
|
||||||
expect(router.createUrlTree).toHaveBeenCalledWith(["/redirect"], {
|
|
||||||
queryParams: urlTree.queryParams,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import { inject } from "@angular/core";
|
|
||||||
import { UrlTree, Router } from "@angular/router";
|
|
||||||
|
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) {
|
|
||||||
const currentNavigation = router.getCurrentNavigation();
|
|
||||||
const queryParams = currentNavigation?.extractedUrl?.queryParams || {};
|
|
||||||
|
|
||||||
// Preserve query params when redirecting as it is likely that the refreshed component
|
|
||||||
// will be consuming the same query params.
|
|
||||||
return router.createUrlTree([redirectUrl], { queryParams });
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import { Type, inject } from "@angular/core";
|
|
||||||
import { Route, Routes } from "@angular/router";
|
|
||||||
|
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
|
||||||
|
|
||||||
import { componentRouteSwap } from "./component-route-swap";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 the default component, and to the alt component if altOptions is not provided.
|
|
||||||
* @param altOptions - The alt route options to apply to the alt component.
|
|
||||||
*/
|
|
||||||
export function extensionRefreshSwap(
|
|
||||||
defaultComponent: Type<any>,
|
|
||||||
refreshedComponent: Type<any>,
|
|
||||||
options: Route,
|
|
||||||
altOptions?: Route,
|
|
||||||
): Routes {
|
|
||||||
return componentRouteSwap(
|
|
||||||
defaultComponent,
|
|
||||||
refreshedComponent,
|
|
||||||
async () => {
|
|
||||||
const configService = inject(ConfigService);
|
|
||||||
return configService.getFeatureFlag(FeatureFlag.ExtensionRefresh);
|
|
||||||
},
|
|
||||||
options,
|
|
||||||
altOptions,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user