1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-09 12:03:33 +00:00

Merge branch 'main' into autofill/pm-8027-inline-menu-appears-within-input-fields-that-do-not-relate-to-user-login

This commit is contained in:
Cesar Gonzalez
2024-06-05 13:24:33 -05:00
committed by GitHub
54 changed files with 677 additions and 622 deletions

View File

@@ -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,

View File

@@ -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";

View File

@@ -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);
});
}
}

View File

@@ -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";

View File

@@ -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";

View File

@@ -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>

View File

@@ -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"], {});

View File

@@ -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);

View File

@@ -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);
}
/**

View File

@@ -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",