mirror of
https://github.com/bitwarden/browser
synced 2026-01-06 18:43:25 +00:00
Merge branch 'main' into autofill/pm-8518-autofill-scripts-do-not-inject-into-sub-frames-on-install
This commit is contained in:
@@ -77,7 +77,9 @@ type OverlayBackgroundExtensionMessageHandlers = {
|
||||
updateFocusedFieldData: ({ message, sender }: BackgroundOnMessageHandlerParams) => void;
|
||||
collectPageDetailsResponse: ({ message, sender }: BackgroundOnMessageHandlerParams) => void;
|
||||
unlockCompleted: ({ message }: BackgroundMessageParam) => void;
|
||||
addedCipher: () => void;
|
||||
addEditCipherSubmitted: () => void;
|
||||
editedCipher: () => void;
|
||||
deletedCipher: () => void;
|
||||
};
|
||||
|
||||
|
||||
@@ -1000,29 +1000,23 @@ describe("OverlayBackground", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("addEditCipherSubmitted message handler", () => {
|
||||
it("updates the overlay ciphers", () => {
|
||||
const message = {
|
||||
command: "addEditCipherSubmitted",
|
||||
};
|
||||
jest.spyOn(overlayBackground as any, "updateOverlayCiphers").mockImplementation();
|
||||
describe("extension messages that trigger an update of the inline menu ciphers", () => {
|
||||
const extensionMessages = [
|
||||
"addedCipher",
|
||||
"addEditCipherSubmitted",
|
||||
"editedCipher",
|
||||
"deletedCipher",
|
||||
];
|
||||
|
||||
sendMockExtensionMessage(message);
|
||||
|
||||
expect(overlayBackground["updateOverlayCiphers"]).toHaveBeenCalled();
|
||||
beforeEach(() => {
|
||||
jest.spyOn(overlayBackground, "updateOverlayCiphers").mockImplementation();
|
||||
});
|
||||
});
|
||||
|
||||
describe("deletedCipher message handler", () => {
|
||||
it("updates the overlay ciphers", () => {
|
||||
const message = {
|
||||
command: "deletedCipher",
|
||||
};
|
||||
jest.spyOn(overlayBackground as any, "updateOverlayCiphers").mockImplementation();
|
||||
|
||||
sendMockExtensionMessage(message);
|
||||
|
||||
expect(overlayBackground["updateOverlayCiphers"]).toHaveBeenCalled();
|
||||
extensionMessages.forEach((message) => {
|
||||
it(`triggers an update of the overlay ciphers when the ${message} message is received`, () => {
|
||||
sendMockExtensionMessage({ command: message });
|
||||
expect(overlayBackground.updateOverlayCiphers).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -72,7 +72,9 @@ class OverlayBackground implements OverlayBackgroundInterface {
|
||||
updateFocusedFieldData: ({ message, sender }) => this.setFocusedFieldData(message, sender),
|
||||
collectPageDetailsResponse: ({ message, sender }) => this.storePageDetails(message, sender),
|
||||
unlockCompleted: ({ message }) => this.unlockCompleted(message),
|
||||
addedCipher: () => this.updateOverlayCiphers(),
|
||||
addEditCipherSubmitted: () => this.updateOverlayCiphers(),
|
||||
editedCipher: () => this.updateOverlayCiphers(),
|
||||
deletedCipher: () => this.updateOverlayCiphers(),
|
||||
};
|
||||
private readonly overlayButtonPortMessageHandlers: OverlayButtonPortMessageHandlers = {
|
||||
|
||||
@@ -136,6 +136,9 @@ import { DefaultStateProvider } from "@bitwarden/common/platform/state/implement
|
||||
import { InlineDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/inline-derived-state";
|
||||
import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service";
|
||||
/* eslint-enable import/no-restricted-paths */
|
||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||
// eslint-disable-next-line no-restricted-imports -- Needed for service creation
|
||||
import { DefaultSyncService } from "@bitwarden/common/platform/sync/internal";
|
||||
import { DefaultThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||
import { ApiService } from "@bitwarden/common/services/api.service";
|
||||
import { AuditService } from "@bitwarden/common/services/audit.service";
|
||||
@@ -166,8 +169,6 @@ import { CollectionService as CollectionServiceAbstraction } from "@bitwarden/co
|
||||
import { CipherFileUploadService as CipherFileUploadServiceAbstraction } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service";
|
||||
import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction";
|
||||
import { InternalFolderService as InternalFolderServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { SyncNotifierService as SyncNotifierServiceAbstraction } from "@bitwarden/common/vault/abstractions/sync/sync-notifier.service.abstraction";
|
||||
import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service";
|
||||
import { VaultSettingsService as VaultSettingsServiceAbstraction } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
@@ -176,8 +177,6 @@ import { CollectionService } from "@bitwarden/common/vault/services/collection.s
|
||||
import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service";
|
||||
import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service";
|
||||
import { SyncNotifierService } from "@bitwarden/common/vault/services/sync/sync-notifier.service";
|
||||
import { SyncService } from "@bitwarden/common/vault/services/sync/sync.service";
|
||||
import { TotpService } from "@bitwarden/common/vault/services/totp.service";
|
||||
import { VaultSettingsService } from "@bitwarden/common/vault/services/vault-settings/vault-settings.service";
|
||||
import {
|
||||
@@ -268,7 +267,7 @@ export default class MainBackground {
|
||||
collectionService: CollectionServiceAbstraction;
|
||||
vaultTimeoutService: VaultTimeoutService;
|
||||
vaultTimeoutSettingsService: VaultTimeoutSettingsServiceAbstraction;
|
||||
syncService: SyncServiceAbstraction;
|
||||
syncService: SyncService;
|
||||
passwordGenerationService: PasswordGenerationServiceAbstraction;
|
||||
passwordStrengthService: PasswordStrengthServiceAbstraction;
|
||||
totpService: TotpServiceAbstraction;
|
||||
@@ -306,7 +305,6 @@ export default class MainBackground {
|
||||
policyApiService: PolicyApiServiceAbstraction;
|
||||
sendApiService: SendApiServiceAbstraction;
|
||||
userVerificationApiService: UserVerificationApiServiceAbstraction;
|
||||
syncNotifierService: SyncNotifierServiceAbstraction;
|
||||
fido2UserInterfaceService: Fido2UserInterfaceServiceAbstraction;
|
||||
fido2AuthenticatorService: Fido2AuthenticatorServiceAbstraction;
|
||||
fido2ClientService: Fido2ClientServiceAbstraction;
|
||||
@@ -638,7 +636,6 @@ export default class MainBackground {
|
||||
this.i18nService,
|
||||
this.stateProvider,
|
||||
);
|
||||
this.syncNotifierService = new SyncNotifierService();
|
||||
|
||||
this.autofillSettingsService = new AutofillSettingsService(
|
||||
this.stateProvider,
|
||||
@@ -827,7 +824,7 @@ export default class MainBackground {
|
||||
messageListener,
|
||||
);
|
||||
} else {
|
||||
this.syncService = new SyncService(
|
||||
this.syncService = new DefaultSyncService(
|
||||
this.masterPasswordService,
|
||||
this.accountService,
|
||||
this.apiService,
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<div class="tw-flex tw-justify-between tw-items-end tw-gap-1 tw-px-1 tw-pb-1">
|
||||
<div class="tw-flex tw-items-center tw-gap-1">
|
||||
<h2 bitTypography="h6" noMargin class="tw-mb-0 tw-text-headers">
|
||||
{{ title }}
|
||||
</h2>
|
||||
<ng-content select="[slot=title-suffix]"></ng-content>
|
||||
</div>
|
||||
<div class="tw-text-muted has-[button]:-tw-mb-1">
|
||||
<ng-content select="[slot=end]"></ng-content>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Component, Input } from "@angular/core";
|
||||
|
||||
import { TypographyModule } from "@bitwarden/components";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "popup-section-header",
|
||||
templateUrl: "./popup-section-header.component.html",
|
||||
imports: [TypographyModule],
|
||||
})
|
||||
export class PopupSectionHeaderComponent {
|
||||
@Input() title: string;
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
|
||||
|
||||
import {
|
||||
CardComponent,
|
||||
IconButtonModule,
|
||||
SectionComponent,
|
||||
TypographyModule,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
import { PopupSectionHeaderComponent } from "./popup-section-header.component";
|
||||
|
||||
export default {
|
||||
title: "Browser/Popup Section Header",
|
||||
component: PopupSectionHeaderComponent,
|
||||
args: {
|
||||
title: "Title",
|
||||
},
|
||||
decorators: [
|
||||
moduleMetadata({
|
||||
imports: [SectionComponent, CardComponent, TypographyModule, IconButtonModule],
|
||||
}),
|
||||
],
|
||||
} as Meta<PopupSectionHeaderComponent>;
|
||||
|
||||
type Story = StoryObj<PopupSectionHeaderComponent>;
|
||||
|
||||
export const OnlyTitle: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<popup-section-header [title]="title"></popup-section-header>
|
||||
`,
|
||||
}),
|
||||
args: {
|
||||
title: "Only Title",
|
||||
},
|
||||
};
|
||||
|
||||
export const TrailingText: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<popup-section-header [title]="title">
|
||||
<span bitTypography="body2" slot="end">13</span>
|
||||
</popup-section-header>
|
||||
`,
|
||||
}),
|
||||
args: {
|
||||
title: "Trailing Text",
|
||||
},
|
||||
};
|
||||
|
||||
export const TailingIcon: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<popup-section-header [title]="title">
|
||||
<button bitIconButton="bwi-star" size="small" slot="end"></button>
|
||||
</popup-section-header>
|
||||
`,
|
||||
}),
|
||||
args: {
|
||||
title: "Trailing Icon",
|
||||
},
|
||||
};
|
||||
|
||||
export const TitleSuffix: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: `
|
||||
<popup-section-header [title]="title">
|
||||
<button bitIconButton="bwi-refresh" size="small" slot="title-suffix"></button>
|
||||
</popup-section-header>
|
||||
`,
|
||||
}),
|
||||
args: {
|
||||
title: "Title Suffix",
|
||||
},
|
||||
};
|
||||
|
||||
export const WithSections: Story = {
|
||||
render: () => ({
|
||||
template: `
|
||||
<div class="tw-bg-background-alt tw-p-2">
|
||||
<bit-section>
|
||||
<popup-section-header title="Section 1">
|
||||
<button bitIconButton="bwi-star" size="small" slot="end"></button>
|
||||
</popup-section-header>
|
||||
<bit-card>
|
||||
<h3 bitTypography="h3">Card 1 Content</h3>
|
||||
</bit-card>
|
||||
</bit-section>
|
||||
<bit-section>
|
||||
<popup-section-header title="Section 2">
|
||||
<button bitIconButton="bwi-star" size="small" slot="end"></button>
|
||||
</popup-section-header>
|
||||
<bit-card>
|
||||
<h3 bitTypography="h3">Card 2 Content</h3>
|
||||
</bit-card>
|
||||
</bit-section>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
};
|
||||
@@ -46,7 +46,6 @@ import { PopupFooterComponent } from "../platform/popup/layout/popup-footer.comp
|
||||
import { PopupHeaderComponent } from "../platform/popup/layout/popup-header.component";
|
||||
import { PopupPageComponent } from "../platform/popup/layout/popup-page.component";
|
||||
import { PopupTabNavigationComponent } from "../platform/popup/layout/popup-tab-navigation.component";
|
||||
import { PopupSectionHeaderComponent } from "../platform/popup/popup-section-header/popup-section-header.component";
|
||||
import { FilePopoutCalloutComponent } from "../tools/popup/components/file-popout-callout.component";
|
||||
import { GeneratorComponent } from "../tools/popup/generator/generator.component";
|
||||
import { PasswordGeneratorHistoryComponent } from "../tools/popup/generator/password-generator-history.component";
|
||||
@@ -119,7 +118,6 @@ import "../platform/popup/locales";
|
||||
PopupFooterComponent,
|
||||
PopupHeaderComponent,
|
||||
UserVerificationDialogComponent,
|
||||
PopupSectionHeaderComponent,
|
||||
CurrentAccountComponent,
|
||||
],
|
||||
declarations: [
|
||||
|
||||
@@ -80,11 +80,11 @@ import {
|
||||
} from "@bitwarden/common/platform/state";
|
||||
// eslint-disable-next-line import/no-restricted-paths -- Used for dependency injection
|
||||
import { InlineDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/inline-derived-state";
|
||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||
import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
||||
import { FolderService as FolderServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service";
|
||||
import { TotpService } from "@bitwarden/common/vault/services/totp.service";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
|
||||
@@ -8,17 +8,19 @@
|
||||
></app-vault-list-items-container>
|
||||
<ng-container *ngIf="showEmptyAutofillTip$ | async">
|
||||
<bit-section>
|
||||
<popup-section-header [title]="'autofillSuggestions' | i18n">
|
||||
<bit-section-header>
|
||||
<h2 bitTypography="h6">
|
||||
{{ "autofillSuggestions" | i18n }}
|
||||
</h2>
|
||||
<button
|
||||
*ngIf="showRefresh"
|
||||
bitIconButton="bwi-refresh"
|
||||
size="small"
|
||||
slot="title-suffix"
|
||||
type="button"
|
||||
[appA11yTitle]="'refresh' | i18n"
|
||||
(click)="refreshCurrentTab()"
|
||||
></button>
|
||||
</popup-section-header>
|
||||
</bit-section-header>
|
||||
<span class="tw-text-muted tw-px-1" bitTypography="body2">{{
|
||||
"autofillSuggestionsTip" | i18n
|
||||
}}</span>
|
||||
|
||||
@@ -3,10 +3,14 @@ import { Component } from "@angular/core";
|
||||
import { combineLatest, map, Observable } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { IconButtonModule, SectionComponent, TypographyModule } from "@bitwarden/components";
|
||||
import {
|
||||
IconButtonModule,
|
||||
SectionComponent,
|
||||
SectionHeaderComponent,
|
||||
TypographyModule,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils";
|
||||
import { PopupSectionHeaderComponent } from "../../../../../platform/popup/popup-section-header/popup-section-header.component";
|
||||
import { VaultPopupItemsService } from "../../../services/vault-popup-items.service";
|
||||
import { PopupCipherView } from "../../../views/popup-cipher.view";
|
||||
import { VaultListItemsContainerComponent } from "../vault-list-items-container/vault-list-items-container.component";
|
||||
@@ -19,7 +23,7 @@ import { VaultListItemsContainerComponent } from "../vault-list-items-container/
|
||||
TypographyModule,
|
||||
VaultListItemsContainerComponent,
|
||||
JslibModule,
|
||||
PopupSectionHeaderComponent,
|
||||
SectionHeaderComponent,
|
||||
IconButtonModule,
|
||||
],
|
||||
selector: "app-autofill-vault-list-items",
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
<bit-section *ngIf="ciphers?.length > 0">
|
||||
<popup-section-header [title]="title">
|
||||
<span bitTypography="body2" slot="end">{{ ciphers.length }}</span>
|
||||
<bit-section-header>
|
||||
<h2 bitTypography="h6">
|
||||
{{ title }}
|
||||
</h2>
|
||||
<button
|
||||
*ngIf="showRefresh"
|
||||
bitIconButton="bwi-refresh"
|
||||
type="button"
|
||||
size="small"
|
||||
slot="title-suffix"
|
||||
(click)="onRefresh.emit()"
|
||||
[appA11yTitle]="'refresh' | i18n"
|
||||
></button>
|
||||
</popup-section-header>
|
||||
<span bitTypography="body2" slot="end">{{ ciphers.length }}</span>
|
||||
</bit-section-header>
|
||||
<bit-item-group>
|
||||
<bit-item *ngFor="let cipher of ciphers">
|
||||
<a
|
||||
|
||||
@@ -10,10 +10,10 @@ import {
|
||||
IconButtonModule,
|
||||
ItemModule,
|
||||
SectionComponent,
|
||||
SectionHeaderComponent,
|
||||
TypographyModule,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
import { PopupSectionHeaderComponent } from "../../../../../platform/popup/popup-section-header/popup-section-header.component";
|
||||
import { PopupCipherView } from "../../../views/popup-cipher.view";
|
||||
import { ItemCopyActionsComponent } from "../item-copy-action/item-copy-actions.component";
|
||||
import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options.component";
|
||||
@@ -28,7 +28,7 @@ import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options
|
||||
SectionComponent,
|
||||
TypographyModule,
|
||||
JslibModule,
|
||||
PopupSectionHeaderComponent,
|
||||
SectionHeaderComponent,
|
||||
RouterLink,
|
||||
ItemCopyActionsComponent,
|
||||
ItemMoreOptionsComponent,
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, Output, EventEmitter } from "@angular/core";
|
||||
import { Component } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { FormsModule } from "@angular/forms";
|
||||
import { Subject, debounceTime } from "rxjs";
|
||||
import { Subject, Subscription, debounceTime, filter } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { SearchModule } from "@bitwarden/components";
|
||||
|
||||
import { VaultPopupItemsService } from "../../../services/vault-popup-items.service";
|
||||
|
||||
const SearchTextDebounceInterval = 200;
|
||||
|
||||
@Component({
|
||||
@@ -17,19 +19,34 @@ const SearchTextDebounceInterval = 200;
|
||||
})
|
||||
export class VaultV2SearchComponent {
|
||||
searchText: string;
|
||||
@Output() searchTextChanged = new EventEmitter<string>();
|
||||
|
||||
private searchText$ = new Subject<string>();
|
||||
|
||||
constructor() {
|
||||
this.searchText$
|
||||
.pipe(debounceTime(SearchTextDebounceInterval), takeUntilDestroyed())
|
||||
.subscribe((data) => {
|
||||
this.searchTextChanged.emit(data);
|
||||
});
|
||||
constructor(private vaultPopupItemsService: VaultPopupItemsService) {
|
||||
this.subscribeToLatestSearchText();
|
||||
this.subscribeToApplyFilter();
|
||||
}
|
||||
|
||||
onSearchTextChanged() {
|
||||
this.searchText$.next(this.searchText);
|
||||
}
|
||||
|
||||
subscribeToLatestSearchText(): Subscription {
|
||||
return this.vaultPopupItemsService.latestSearchText$
|
||||
.pipe(
|
||||
takeUntilDestroyed(),
|
||||
filter((data) => !!data),
|
||||
)
|
||||
.subscribe((text) => {
|
||||
this.searchText = text;
|
||||
});
|
||||
}
|
||||
|
||||
subscribeToApplyFilter(): Subscription {
|
||||
return this.searchText$
|
||||
.pipe(debounceTime(SearchTextDebounceInterval), takeUntilDestroyed())
|
||||
.subscribe((data) => {
|
||||
this.vaultPopupItemsService.applyFilter(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
||||
|
||||
@@ -9,8 +9,8 @@ import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
|
||||
@@ -22,18 +22,15 @@
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="!(showEmptyState$ | async)">
|
||||
<div class="tw-fixed">
|
||||
<app-vault-v2-search (searchTextChanged)="handleSearchTextChange($event)">
|
||||
</app-vault-v2-search>
|
||||
<app-vault-v2-search> </app-vault-v2-search>
|
||||
|
||||
<app-vault-list-filters></app-vault-list-filters>
|
||||
</div>
|
||||
<app-vault-list-filters></app-vault-list-filters>
|
||||
|
||||
<div
|
||||
*ngIf="(showNoResultsState$ | async) && !(showDeactivatedOrg$ | async)"
|
||||
class="tw-flex tw-flex-col tw-h-full tw-justify-center"
|
||||
class="tw-flex tw-flex-col tw-justify-center tw-h-auto tw-pt-12"
|
||||
>
|
||||
<bit-no-items>
|
||||
<bit-no-items [icon]="noResultsIcon">
|
||||
<ng-container slot="title">{{ "noItemsMatchSearch" | i18n }}</ng-container>
|
||||
<ng-container slot="description">{{ "clearFiltersOrTryAnother" | i18n }}</ng-container>
|
||||
</bit-no-items>
|
||||
@@ -41,7 +38,7 @@
|
||||
|
||||
<div
|
||||
*ngIf="showDeactivatedOrg$ | async"
|
||||
class="tw-flex tw-flex-col tw-h-full tw-justify-center"
|
||||
class="tw-flex tw-flex-col tw-justify-center tw-h-auto tw-pt-12"
|
||||
>
|
||||
<bit-no-items [icon]="deactivatedIcon">
|
||||
<ng-container slot="title">{{ "organizationIsDeactivated" | i18n }}</ng-container>
|
||||
|
||||
@@ -44,6 +44,7 @@ export class VaultV2Component implements OnInit, OnDestroy {
|
||||
|
||||
protected vaultIcon = Icons.Vault;
|
||||
protected deactivatedIcon = Icons.DeactivatedOrg;
|
||||
protected noResultsIcon = Icons.NoResults;
|
||||
|
||||
constructor(
|
||||
private vaultPopupItemsService: VaultPopupItemsService,
|
||||
@@ -54,10 +55,6 @@ export class VaultV2Component implements OnInit, OnDestroy {
|
||||
|
||||
ngOnDestroy(): void {}
|
||||
|
||||
handleSearchTextChange(searchText: string) {
|
||||
this.vaultPopupItemsService.applyFilter(searchText);
|
||||
}
|
||||
|
||||
addCipher() {
|
||||
// TODO: Add currently filtered organization to query params if available
|
||||
void this.router.navigate(["/add-cipher"], {});
|
||||
|
||||
@@ -6,6 +6,7 @@ import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { ProductType } from "@bitwarden/common/enums";
|
||||
import { ObservableTracker } from "@bitwarden/common/spec";
|
||||
import { CipherId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
||||
@@ -50,7 +51,8 @@ describe("VaultPopupItemsService", () => {
|
||||
cipherList[3].favorite = true;
|
||||
|
||||
cipherServiceMock.getAllDecrypted.mockResolvedValue(cipherList);
|
||||
cipherServiceMock.ciphers$ = new BehaviorSubject(null).asObservable();
|
||||
cipherServiceMock.ciphers$ = new BehaviorSubject(null);
|
||||
cipherServiceMock.localData$ = new BehaviorSubject(null);
|
||||
searchService.searchCiphers.mockImplementation(async (_, __, ciphers) => ciphers);
|
||||
cipherServiceMock.filterCiphersForUrl.mockImplementation(async (ciphers) =>
|
||||
ciphers.filter((c) => ["0", "1"].includes(c.id)),
|
||||
@@ -123,6 +125,34 @@ describe("VaultPopupItemsService", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should update cipher list when cipherService.ciphers$ emits", async () => {
|
||||
const tracker = new ObservableTracker(service.autoFillCiphers$);
|
||||
|
||||
await tracker.expectEmission();
|
||||
|
||||
(cipherServiceMock.ciphers$ as BehaviorSubject<any>).next(null);
|
||||
|
||||
await tracker.expectEmission();
|
||||
|
||||
// Should only emit twice
|
||||
expect(tracker.emissions.length).toBe(2);
|
||||
await expect(tracker.pauseUntilReceived(3)).rejects.toThrow("Timeout exceeded");
|
||||
});
|
||||
|
||||
it("should update cipher list when cipherService.localData$ emits", async () => {
|
||||
const tracker = new ObservableTracker(service.autoFillCiphers$);
|
||||
|
||||
await tracker.expectEmission();
|
||||
|
||||
(cipherServiceMock.localData$ as BehaviorSubject<any>).next(null);
|
||||
|
||||
await tracker.expectEmission();
|
||||
|
||||
// Should only emit twice
|
||||
expect(tracker.emissions.length).toBe(2);
|
||||
await expect(tracker.pauseUntilReceived(3)).rejects.toThrow("Timeout exceeded");
|
||||
});
|
||||
|
||||
describe("autoFillCiphers$", () => {
|
||||
it("should return empty array if there is no current tab", (done) => {
|
||||
jest.spyOn(BrowserApi, "getTabFromCurrentWindow").mockResolvedValue(null);
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
distinctUntilKeyChanged,
|
||||
from,
|
||||
map,
|
||||
merge,
|
||||
Observable,
|
||||
of,
|
||||
shareReplay,
|
||||
@@ -38,7 +39,8 @@ import { MY_VAULT_ID, VaultPopupListFiltersService } from "./vault-popup-list-fi
|
||||
})
|
||||
export class VaultPopupItemsService {
|
||||
private _refreshCurrentTab$ = new Subject<void>();
|
||||
private searchText$ = new BehaviorSubject<string>("");
|
||||
private _searchText$ = new BehaviorSubject<string>("");
|
||||
latestSearchText$: Observable<string> = this._searchText$.asObservable();
|
||||
|
||||
/**
|
||||
* Observable that contains the list of other cipher types that should be shown
|
||||
@@ -77,10 +79,12 @@ export class VaultPopupItemsService {
|
||||
* Observable that contains the list of all decrypted ciphers.
|
||||
* @private
|
||||
*/
|
||||
private _cipherList$: Observable<PopupCipherView[]> = this.cipherService.ciphers$.pipe(
|
||||
private _cipherList$: Observable<PopupCipherView[]> = merge(
|
||||
this.cipherService.ciphers$,
|
||||
this.cipherService.localData$,
|
||||
).pipe(
|
||||
runInsideAngular(inject(NgZone)), // Workaround to ensure cipher$ state provider emissions are run inside Angular
|
||||
switchMap(() => Utils.asyncToObservable(() => this.cipherService.getAllDecrypted())),
|
||||
map((ciphers) => Object.values(ciphers)),
|
||||
switchMap((ciphers) =>
|
||||
combineLatest([
|
||||
this.organizationService.organizations$,
|
||||
@@ -105,7 +109,7 @@ export class VaultPopupItemsService {
|
||||
|
||||
private _filteredCipherList$: Observable<PopupCipherView[]> = combineLatest([
|
||||
this._cipherList$,
|
||||
this.searchText$,
|
||||
this._searchText$,
|
||||
this.vaultPopupListFiltersService.filterFunction$,
|
||||
]).pipe(
|
||||
map(([ciphers, searchText, filterFunction]): [CipherView[], string] => [
|
||||
@@ -179,7 +183,7 @@ export class VaultPopupItemsService {
|
||||
* Observable that indicates whether a filter is currently applied to the ciphers.
|
||||
*/
|
||||
hasFilterApplied$ = combineLatest([
|
||||
this.searchText$,
|
||||
this._searchText$,
|
||||
this.vaultPopupListFiltersService.filters$,
|
||||
]).pipe(
|
||||
switchMap(([searchText, filters]) => {
|
||||
@@ -242,7 +246,7 @@ export class VaultPopupItemsService {
|
||||
}
|
||||
|
||||
applyFilter(newSearchText: string) {
|
||||
this.searchText$.next(newSearchText);
|
||||
this._searchText$.next(newSearchText);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Component, OnInit } from "@angular/core";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||
|
||||
@Component({
|
||||
selector: "app-sync",
|
||||
|
||||
Reference in New Issue
Block a user