1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-01-17 20:31:50 +01:00

[PS-1078] Refactor FolderService to use Observables ()

This commit is contained in:
Oscar Hinton 2022-07-12 20:25:18 +02:00 committed by GitHub
parent a43aa9612c
commit 23253b3882
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 421 additions and 180 deletions

View File

@ -44,6 +44,7 @@ import { ApiService } from "@bitwarden/common/services/api.service";
import { AppIdService } from "@bitwarden/common/services/appId.service"; import { AppIdService } from "@bitwarden/common/services/appId.service";
import { AuditService } from "@bitwarden/common/services/audit.service"; import { AuditService } from "@bitwarden/common/services/audit.service";
import { AuthService } from "@bitwarden/common/services/auth.service"; import { AuthService } from "@bitwarden/common/services/auth.service";
import { BroadcasterService } from "@bitwarden/common/services/broadcaster.service";
import { CipherService } from "@bitwarden/common/services/cipher.service"; import { CipherService } from "@bitwarden/common/services/cipher.service";
import { CollectionService } from "@bitwarden/common/services/collection.service"; import { CollectionService } from "@bitwarden/common/services/collection.service";
import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service"; import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service";
@ -149,6 +150,7 @@ export default class MainBackground {
vaultFilterService: VaultFilterService; vaultFilterService: VaultFilterService;
usernameGenerationService: UsernameGenerationServiceAbstraction; usernameGenerationService: UsernameGenerationServiceAbstraction;
encryptService: EncryptService; encryptService: EncryptService;
broadcasterService: BroadcasterService;
folderApiService: FolderApiServiceAbstraction; folderApiService: FolderApiServiceAbstraction;
onUpdatedRan: boolean; onUpdatedRan: boolean;
@ -267,11 +269,13 @@ export default class MainBackground {
this.logService, this.logService,
this.stateService this.stateService
); );
this.broadcasterService = new BroadcasterService();
this.folderService = new FolderService( this.folderService = new FolderService(
this.cryptoService, this.cryptoService,
this.i18nService, this.i18nService,
this.cipherService, this.cipherService,
this.stateService this.stateService,
this.broadcasterService
); );
this.folderApiService = new FolderApiService(this.folderService, this.apiService); this.folderApiService = new FolderApiService(this.folderService, this.apiService);
this.collectionService = new CollectionService( this.collectionService = new CollectionService(

View File

@ -1,3 +1,5 @@
import { firstValueFrom } from "rxjs";
import { AuthService } from "@bitwarden/common/abstractions/auth.service"; import { AuthService } from "@bitwarden/common/abstractions/auth.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction"; import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
@ -385,7 +387,7 @@ export default class NotificationBackground {
model.login = loginModel; model.login = loginModel;
if (!Utils.isNullOrWhitespace(folderId)) { if (!Utils.isNullOrWhitespace(folderId)) {
const folders = await this.folderService.getAllDecrypted(); const folders = await firstValueFrom(this.folderService.folderViews$);
if (folders.some((x) => x.id === folderId)) { if (folders.some((x) => x.id === folderId)) {
model.folderId = folderId; model.folderId = folderId;
} }
@ -437,7 +439,7 @@ export default class NotificationBackground {
private async getDataForTab(tab: chrome.tabs.Tab, responseCommand: string) { private async getDataForTab(tab: chrome.tabs.Tab, responseCommand: string) {
const responseData: any = {}; const responseData: any = {};
if (responseCommand === "notificationBarGetFoldersList") { if (responseCommand === "notificationBarGetFoldersList") {
responseData.folders = await this.folderService.getAllDecrypted(); responseData.folders = await firstValueFrom(this.folderService.folderViews$);
} }
await BrowserApi.tabSendMessageData(tab, responseCommand, responseData); await BrowserApi.tabSendMessageData(tab, responseCommand, responseData);

View File

@ -11,6 +11,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AppIdService } from "@bitwarden/common/abstractions/appId.service"; import { AppIdService } from "@bitwarden/common/abstractions/appId.service";
import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/abstractions/auth.service"; import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/abstractions/auth.service";
import { BroadcasterService as BroadcasterServiceAbstraction } from "@bitwarden/common/abstractions/broadcaster.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service"; import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
@ -111,6 +112,10 @@ function getBgService<T>(service: keyof MainBackground) {
: new BrowserMessagingService(); : new BrowserMessagingService();
}, },
}, },
{
provide: BroadcasterServiceAbstraction,
useFactory: getBgService<BroadcasterServiceAbstraction>("broadcasterService"),
},
{ {
provide: TwoFactorService, provide: TwoFactorService,
useFactory: getBgService<TwoFactorService>("twoFactorService"), useFactory: getBgService<TwoFactorService>("twoFactorService"),

View File

@ -20,20 +20,20 @@
</div> </div>
</header> </header>
<main tabindex="-1"> <main tabindex="-1">
<div class="box list full-list" *ngIf="folders && folders.length"> <div class="box list full-list" *ngIf="(folders$ | async)?.length">
<div class="box-content"> <div class="box-content">
<button <button
type="button" type="button"
appStopClick appStopClick
(click)="folderSelected(f)" (click)="folderSelected(f)"
class="box-content-row padded" class="box-content-row padded"
*ngFor="let f of folders" *ngFor="let f of folders$ | async"
> >
{{ f.name }} {{ f.name }}
</button> </button>
</div> </div>
</div> </div>
<div class="no-items" *ngIf="!folders || !folders.length"> <div class="no-items" *ngIf="!(folders$ | async)?.length">
<p>{{ "noFolders" | i18n }}</p> <p>{{ "noFolders" | i18n }}</p>
</div> </div>
</main> </main>

View File

@ -1,5 +1,6 @@
import { Component, OnInit } from "@angular/core"; import { Component } from "@angular/core";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { map, Observable } from "rxjs";
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction"; import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
import { FolderView } from "@bitwarden/common/models/view/folderView"; import { FolderView } from "@bitwarden/common/models/view/folderView";
@ -8,17 +9,19 @@ import { FolderView } from "@bitwarden/common/models/view/folderView";
selector: "app-folders", selector: "app-folders",
templateUrl: "folders.component.html", templateUrl: "folders.component.html",
}) })
export class FoldersComponent implements OnInit { export class FoldersComponent {
folders: FolderView[]; folders$: Observable<FolderView[]>;
constructor(private folderService: FolderService, private router: Router) {} constructor(private folderService: FolderService, private router: Router) {
this.folders$ = this.folderService.folderViews$.pipe(
async ngOnInit() { map((folders) => {
this.folders = await this.folderService.getAllDecrypted(); if (folders.length > 0) {
// Remove "No Folder" folders = folders.slice(0, folders.length - 1);
if (this.folders.length > 0) {
this.folders = this.folders.slice(0, this.folders.length - 1);
} }
return folders;
})
);
} }
folderSelected(folder: FolderView) { folderSelected(folder: FolderView) {

View File

@ -515,7 +515,7 @@
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="folder">{{ "folder" | i18n }}</label> <label for="folder">{{ "folder" | i18n }}</label>
<select id="folder" name="FolderId" [(ngModel)]="cipher.folderId"> <select id="folder" name="FolderId" [(ngModel)]="cipher.folderId">
<option *ngFor="let f of folders" [ngValue]="f.id">{{ f.name }}</option> <option *ngFor="let f of folders$ | async" [ngValue]="f.id">{{ f.name }}</option>
</select> </select>
</div> </div>
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>

View File

@ -120,7 +120,7 @@ export class CiphersComponent extends BaseCiphersComponent implements OnInit, On
this.searchPlaceholder = this.i18nService.t("searchFolder"); this.searchPlaceholder = this.i18nService.t("searchFolder");
if (this.folderId != null) { if (this.folderId != null) {
this.showOrganizations = false; this.showOrganizations = false;
const folderNode = await this.folderService.getNested(this.folderId); const folderNode = await this.vaultFilterService.getFolderNested(this.folderId);
if (folderNode != null && folderNode.node != null) { if (folderNode != null && folderNode.node != null) {
this.groupingTitle = folderNode.node.name; this.groupingTitle = folderNode.node.name;
this.nestedFolders = this.nestedFolders =

View File

@ -1,6 +1,7 @@
import { Location } from "@angular/common"; import { Location } from "@angular/common";
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core"; import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
import { first } from "rxjs/operators"; import { first } from "rxjs/operators";
import { VaultFilter } from "@bitwarden/angular/modules/vault-filter/models/vault-filter.model"; import { VaultFilter } from "@bitwarden/angular/modules/vault-filter/models/vault-filter.model";
@ -182,9 +183,11 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
} }
async loadFolders() { async loadFolders() {
const allFolders = await this.vaultFilterService.buildFolders(this.selectedOrganization); const allFolders = await firstValueFrom(
this.vaultFilterService.buildNestedFolders(this.selectedOrganization)
);
this.folders = allFolders.fullList; this.folders = allFolders.fullList;
this.nestedFolders = await allFolders.nestedList; this.nestedFolders = allFolders.nestedList;
} }
async search(timeout: number = null) { async search(timeout: number = null) {

View File

@ -14,6 +14,7 @@ import { GlobalState } from "@bitwarden/common/models/domain/globalState";
import { AppIdService } from "@bitwarden/common/services/appId.service"; import { AppIdService } from "@bitwarden/common/services/appId.service";
import { AuditService } from "@bitwarden/common/services/audit.service"; import { AuditService } from "@bitwarden/common/services/audit.service";
import { AuthService } from "@bitwarden/common/services/auth.service"; import { AuthService } from "@bitwarden/common/services/auth.service";
import { BroadcasterService } from "@bitwarden/common/services/broadcaster.service";
import { CipherService } from "@bitwarden/common/services/cipher.service"; import { CipherService } from "@bitwarden/common/services/cipher.service";
import { CollectionService } from "@bitwarden/common/services/collection.service"; import { CollectionService } from "@bitwarden/common/services/collection.service";
import { ContainerService } from "@bitwarden/common/services/container.service"; import { ContainerService } from "@bitwarden/common/services/container.service";
@ -103,6 +104,7 @@ export class Main {
organizationService: OrganizationService; organizationService: OrganizationService;
providerService: ProviderService; providerService: ProviderService;
twoFactorService: TwoFactorService; twoFactorService: TwoFactorService;
broadcasterService: BroadcasterService;
folderApiService: FolderApiService; folderApiService: FolderApiService;
constructor() { constructor() {
@ -198,11 +200,14 @@ export class Main {
this.stateService this.stateService
); );
this.broadcasterService = new BroadcasterService();
this.folderService = new FolderService( this.folderService = new FolderService(
this.cryptoService, this.cryptoService,
this.i18nService, this.i18nService,
this.cipherService, this.cipherService,
this.stateService this.stateService,
this.broadcasterService
); );
this.folderApiService = new FolderApiService(this.folderService, this.apiService); this.folderApiService = new FolderApiService(this.folderService, this.apiService);

View File

@ -1,3 +1,5 @@
import { firstValueFrom } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
@ -358,7 +360,7 @@ export class GetCommand extends DownloadCommand {
decFolder = await folder.decrypt(); decFolder = await folder.decrypt();
} }
} else if (id.trim() !== "") { } else if (id.trim() !== "") {
let folders = await this.folderService.getAllDecrypted(); let folders = await firstValueFrom(this.folderService.folderViews$);
folders = CliUtils.searchFolders(folders, id); folders = CliUtils.searchFolders(folders, id);
if (folders.length > 1) { if (folders.length > 1) {
return Response.multipleResults(folders.map((f) => f.id)); return Response.multipleResults(folders.map((f) => f.id));

View File

@ -1,3 +1,5 @@
import { firstValueFrom } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service"; import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
@ -126,7 +128,7 @@ export class ListCommand {
} }
private async listFolders(options: Options) { private async listFolders(options: Options) {
let folders = await this.folderService.getAllDecrypted(); let folders = await firstValueFrom(this.folderService.folderViews$);
if (options.search != null && options.search.trim() !== "") { if (options.search != null && options.search.trim() !== "") {
folders = CliUtils.searchFolders(folders, options.search); folders = CliUtils.searchFolders(folders, options.search);

View File

@ -32,7 +32,7 @@
[hide]="hideFolders" [hide]="hideFolders"
[activeFilter]="activeFilter" [activeFilter]="activeFilter"
[collapsedFilterNodes]="collapsedFilterNodes" [collapsedFilterNodes]="collapsedFilterNodes"
[folderNodes]="folders" [folderNodes]="folders$ | async"
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)" (onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
(onFilterChange)="applyFilter($event)" (onFilterChange)="applyFilter($event)"
(onAddFolder)="addFolder()" (onAddFolder)="addFolder()"

View File

@ -455,7 +455,7 @@
<div class="box-content-row" appBoxRow> <div class="box-content-row" appBoxRow>
<label for="folder">{{ "folder" | i18n }}</label> <label for="folder">{{ "folder" | i18n }}</label>
<select id="folder" name="FolderId" [(ngModel)]="cipher.folderId"> <select id="folder" name="FolderId" [(ngModel)]="cipher.folderId">
<option *ngFor="let f of folders" [ngValue]="f.id">{{ f.name }}</option> <option *ngFor="let f of folders$ | async" [ngValue]="f.id">{{ f.name }}</option>
</select> </select>
</div> </div>
<div class="box-content-row box-content-row-checkbox" appBoxRow> <div class="box-content-row box-content-row-checkbox" appBoxRow>

View File

@ -57,7 +57,7 @@
[hide]="hideFolders" [hide]="hideFolders"
[activeFilter]="activeFilter" [activeFilter]="activeFilter"
[collapsedFilterNodes]="collapsedFilterNodes" [collapsedFilterNodes]="collapsedFilterNodes"
[folderNodes]="folders" [folderNodes]="folders$ | async"
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)" (onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
(onFilterChange)="applyFilter($event)" (onFilterChange)="applyFilter($event)"
(onAddFolder)="addFolder()" (onAddFolder)="addFolder()"

View File

@ -1,5 +1,6 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
import { ChangePasswordComponent as BaseChangePasswordComponent } from "@bitwarden/angular/components/change-password.component"; import { ChangePasswordComponent as BaseChangePasswordComponent } from "@bitwarden/angular/components/change-password.component";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
@ -192,7 +193,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
request.key = encKey[1].encryptedString; request.key = encKey[1].encryptedString;
request.masterPasswordHash = masterPasswordHash; request.masterPasswordHash = masterPasswordHash;
const folders = await this.folderService.getAllDecrypted(); const folders = await firstValueFrom(this.folderService.folderViews$);
for (let i = 0; i < folders.length; i++) { for (let i = 0; i < folders.length; i++) {
if (folders[i].id == null) { if (folders[i].id == null) {
continue; continue;

View File

@ -1,4 +1,5 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { firstValueFrom } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
@ -81,7 +82,7 @@ export class UpdateKeyComponent {
await this.syncService.fullSync(true); await this.syncService.fullSync(true);
const folders = await this.folderService.getAllDecrypted(); const folders = await firstValueFrom(this.folderService.folderViews$);
for (let i = 0; i < folders.length; i++) { for (let i = 0; i < folders.length; i++) {
if (folders[i].id == null) { if (folders[i].id == null) {
continue; continue;

View File

@ -60,7 +60,7 @@
class="form-control" class="form-control"
[disabled]="cipher.isDeleted || viewOnly" [disabled]="cipher.isDeleted || viewOnly"
> >
<option *ngFor="let f of folders" [ngValue]="f.id">{{ f.name }}</option> <option *ngFor="let f of folders$ | async" [ngValue]="f.id">{{ f.name }}</option>
</select> </select>
</div> </div>
</div> </div>

View File

@ -19,7 +19,7 @@
<div class="form-group"> <div class="form-group">
<label for="folder">{{ "folder" | i18n }}</label> <label for="folder">{{ "folder" | i18n }}</label>
<select id="folder" name="FolderId" [(ngModel)]="folderId" class="form-control"> <select id="folder" name="FolderId" [(ngModel)]="folderId" class="form-control">
<option *ngFor="let f of folders" [ngValue]="f.id">{{ f.name }}</option> <option *ngFor="let f of folders$ | async" [ngValue]="f.id">{{ f.name }}</option>
</select> </select>
</div> </div>
</div> </div>

View File

@ -1,4 +1,5 @@
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { firstValueFrom, Observable } from "rxjs";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction"; import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction";
@ -15,7 +16,7 @@ export class BulkMoveComponent implements OnInit {
@Output() onMoved = new EventEmitter(); @Output() onMoved = new EventEmitter();
folderId: string = null; folderId: string = null;
folders: FolderView[] = []; folders$: Observable<FolderView[]>;
formPromise: Promise<any>; formPromise: Promise<any>;
constructor( constructor(
@ -26,8 +27,8 @@ export class BulkMoveComponent implements OnInit {
) {} ) {}
async ngOnInit() { async ngOnInit() {
this.folders = await this.folderService.getAllDecrypted(); this.folders$ = this.folderService.folderViews$;
this.folderId = this.folders[0].id; this.folderId = (await firstValueFrom(this.folders$))[0].id;
} }
async submit() { async submit() {

View File

@ -1,4 +1,5 @@
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { Observable } from "rxjs";
import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
@ -51,7 +52,7 @@ export class AddEditComponent implements OnInit {
editMode = false; editMode = false;
cipher: CipherView; cipher: CipherView;
folders: FolderView[]; folders$: Observable<FolderView[]>;
collections: CollectionView[] = []; collections: CollectionView[] = [];
title: string; title: string;
formPromise: Promise<any>; formPromise: Promise<any>;
@ -243,7 +244,7 @@ export class AddEditComponent implements OnInit {
} }
} }
this.folders = await this.folderService.getAllDecrypted(); this.folders$ = this.folderService.folderViews$;
if (this.editMode && this.previousCipherId !== this.cipherId) { if (this.editMode && this.previousCipherId !== this.cipherId) {
this.eventService.collect(EventType.Cipher_ClientViewed, this.cipherId); this.eventService.collect(EventType.Cipher_ClientViewed, this.cipherId);

View File

@ -1,4 +1,5 @@
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { firstValueFrom, Observable } from "rxjs";
import { Organization } from "@bitwarden/common/models/domain/organization"; import { Organization } from "@bitwarden/common/models/domain/organization";
import { ITreeNodeObject } from "@bitwarden/common/models/domain/treeNode"; import { ITreeNodeObject } from "@bitwarden/common/models/domain/treeNode";
@ -28,7 +29,7 @@ export class VaultFilterComponent implements OnInit {
activePersonalOwnershipPolicy: boolean; activePersonalOwnershipPolicy: boolean;
activeSingleOrganizationPolicy: boolean; activeSingleOrganizationPolicy: boolean;
collections: DynamicTreeNode<CollectionView>; collections: DynamicTreeNode<CollectionView>;
folders: DynamicTreeNode<FolderView>; folders$: Observable<DynamicTreeNode<FolderView>>;
constructor(protected vaultFilterService: VaultFilterService) {} constructor(protected vaultFilterService: VaultFilterService) {}
@ -45,7 +46,7 @@ export class VaultFilterComponent implements OnInit {
this.activeSingleOrganizationPolicy = this.activeSingleOrganizationPolicy =
await this.vaultFilterService.checkForSingleOrganizationPolicy(); await this.vaultFilterService.checkForSingleOrganizationPolicy();
} }
this.folders = await this.vaultFilterService.buildFolders(); this.folders$ = await this.vaultFilterService.buildNestedFolders();
this.collections = await this.initCollections(); this.collections = await this.initCollections();
this.isLoaded = true; this.isLoaded = true;
} }
@ -67,13 +68,13 @@ export class VaultFilterComponent implements OnInit {
async applyFilter(filter: VaultFilter) { async applyFilter(filter: VaultFilter) {
if (filter.refreshCollectionsAndFolders) { if (filter.refreshCollectionsAndFolders) {
await this.reloadCollectionsAndFolders(filter); await this.reloadCollectionsAndFolders(filter);
filter = this.pruneInvalidatedFilterSelections(filter); filter = await this.pruneInvalidatedFilterSelections(filter);
} }
this.onFilterChange.emit(filter); this.onFilterChange.emit(filter);
} }
async reloadCollectionsAndFolders(filter: VaultFilter) { async reloadCollectionsAndFolders(filter: VaultFilter) {
this.folders = await this.vaultFilterService.buildFolders(filter.selectedOrganizationId); this.folders$ = await this.vaultFilterService.buildNestedFolders(filter.selectedOrganizationId);
this.collections = filter.myVaultOnly this.collections = filter.myVaultOnly
? null ? null
: await this.vaultFilterService.buildCollections(filter.selectedOrganizationId); : await this.vaultFilterService.buildCollections(filter.selectedOrganizationId);
@ -95,14 +96,17 @@ export class VaultFilterComponent implements OnInit {
this.onEditFolder.emit(folder); this.onEditFolder.emit(folder);
} }
protected pruneInvalidatedFilterSelections(filter: VaultFilter): VaultFilter { protected async pruneInvalidatedFilterSelections(filter: VaultFilter): Promise<VaultFilter> {
filter = this.pruneInvalidFolderSelection(filter); filter = await this.pruneInvalidFolderSelection(filter);
filter = this.pruneInvalidCollectionSelection(filter); filter = this.pruneInvalidCollectionSelection(filter);
return filter; return filter;
} }
protected pruneInvalidFolderSelection(filter: VaultFilter): VaultFilter { protected async pruneInvalidFolderSelection(filter: VaultFilter): Promise<VaultFilter> {
if (filter.selectedFolder && !this.folders?.hasId(filter.selectedFolderId)) { if (
filter.selectedFolder &&
!(await firstValueFrom(this.folders$))?.hasId(filter.selectedFolderId)
) {
filter.selectedFolder = false; filter.selectedFolder = false;
filter.selectedFolderId = null; filter.selectedFolderId = null;
} }

View File

@ -1,4 +1,5 @@
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { from, mergeMap, Observable } from "rxjs";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/abstractions/collection.service"; import { CollectionService } from "@bitwarden/common/abstractions/collection.service";
@ -7,12 +8,16 @@ import { OrganizationService } from "@bitwarden/common/abstractions/organization
import { PolicyService } from "@bitwarden/common/abstractions/policy.service"; import { PolicyService } from "@bitwarden/common/abstractions/policy.service";
import { StateService } from "@bitwarden/common/abstractions/state.service"; import { StateService } from "@bitwarden/common/abstractions/state.service";
import { PolicyType } from "@bitwarden/common/enums/policyType"; import { PolicyType } from "@bitwarden/common/enums/policyType";
import { ServiceUtils } from "@bitwarden/common/misc/serviceUtils";
import { Organization } from "@bitwarden/common/models/domain/organization"; import { Organization } from "@bitwarden/common/models/domain/organization";
import { TreeNode } from "@bitwarden/common/models/domain/treeNode";
import { CollectionView } from "@bitwarden/common/models/view/collectionView"; import { CollectionView } from "@bitwarden/common/models/view/collectionView";
import { FolderView } from "@bitwarden/common/models/view/folderView"; import { FolderView } from "@bitwarden/common/models/view/folderView";
import { DynamicTreeNode } from "./models/dynamic-tree-node.model"; import { DynamicTreeNode } from "./models/dynamic-tree-node.model";
const NestingDelimiter = "/";
@Injectable() @Injectable()
export class VaultFilterService { export class VaultFilterService {
constructor( constructor(
@ -36,8 +41,8 @@ export class VaultFilterService {
return await this.organizationService.getAll(); return await this.organizationService.getAll();
} }
async buildFolders(organizationId?: string): Promise<DynamicTreeNode<FolderView>> { buildNestedFolders(organizationId?: string): Observable<DynamicTreeNode<FolderView>> {
const storedFolders = await this.folderService.getAllDecrypted(); const transformation = async (storedFolders: FolderView[]) => {
let folders: FolderView[]; let folders: FolderView[];
if (organizationId != null) { if (organizationId != null) {
const ciphers = await this.cipherService.getAllDecrypted(); const ciphers = await this.cipherService.getAllDecrypted();
@ -50,11 +55,16 @@ export class VaultFilterService {
} else { } else {
folders = storedFolders; folders = storedFolders;
} }
const nestedFolders = await this.folderService.getAllNested(folders); const nestedFolders = await this.getAllFoldersNested(folders);
return new DynamicTreeNode<FolderView>({ return new DynamicTreeNode<FolderView>({
fullList: folders, fullList: folders,
nestedList: nestedFolders, nestedList: nestedFolders,
}); });
};
return this.folderService.folderViews$.pipe(
mergeMap((folders) => from(transformation(folders)))
);
} }
async buildCollections(organizationId?: string): Promise<DynamicTreeNode<CollectionView>> { async buildCollections(organizationId?: string): Promise<DynamicTreeNode<CollectionView>> {
@ -79,4 +89,21 @@ export class VaultFilterService {
async checkForPersonalOwnershipPolicy(): Promise<boolean> { async checkForPersonalOwnershipPolicy(): Promise<boolean> {
return await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership); return await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership);
} }
protected async getAllFoldersNested(folders?: FolderView[]): Promise<TreeNode<FolderView>[]> {
const nodes: TreeNode<FolderView>[] = [];
folders.forEach((f) => {
const folderCopy = new FolderView();
folderCopy.id = f.id;
folderCopy.revisionDate = f.revisionDate;
const parts = f.name != null ? f.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter) : [];
ServiceUtils.nestedTraverse(nodes, 0, parts, folderCopy, null, NestingDelimiter);
});
return nodes;
}
async getFolderNested(id: string): Promise<TreeNode<FolderView>> {
const folders = await this.getAllFoldersNested();
return ServiceUtils.getTreeNodeObject(folders, id) as TreeNode<FolderView>;
}
} }

View File

@ -223,6 +223,7 @@ export const LOG_MAC_FAILURES = new InjectionToken<string>("LOG_MAC_FAILURES");
I18nServiceAbstraction, I18nServiceAbstraction,
CipherServiceAbstraction, CipherServiceAbstraction,
StateServiceAbstraction, StateServiceAbstraction,
BroadcasterServiceAbstraction,
], ],
}, },
{ {

View File

@ -1,4 +1,5 @@
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
import { BehaviorSubject } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
@ -97,8 +98,8 @@ describe("ExportService", () => {
folderService = Substitute.for<FolderService>(); folderService = Substitute.for<FolderService>();
cryptoService = Substitute.for<CryptoService>(); cryptoService = Substitute.for<CryptoService>();
folderService.getAllDecrypted().resolves([]); folderService.folderViews$.returns(new BehaviorSubject([]));
folderService.getAll().resolves([]); folderService.folders$.returns(new BehaviorSubject([]));
exportService = new ExportService( exportService = new ExportService(
folderService, folderService,

View File

@ -0,0 +1,195 @@
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
import { BehaviorSubject, firstValueFrom } from "rxjs";
import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { FolderData } from "@bitwarden/common/models/data/folderData";
import { EncString } from "@bitwarden/common/models/domain/encString";
import { FolderView } from "@bitwarden/common/models/view/folderView";
import { ContainerService } from "@bitwarden/common/services/container.service";
import { FolderService } from "@bitwarden/common/services/folder/folder.service";
import { StateService } from "@bitwarden/common/services/state.service";
describe("Folder Service", () => {
let folderService: FolderService;
let cryptoService: SubstituteOf<CryptoService>;
let i18nService: SubstituteOf<I18nService>;
let cipherService: SubstituteOf<CipherService>;
let stateService: SubstituteOf<StateService>;
let broadcasterService: SubstituteOf<BroadcasterService>;
let activeAccount: BehaviorSubject<string>;
beforeEach(() => {
cryptoService = Substitute.for();
i18nService = Substitute.for();
cipherService = Substitute.for();
stateService = Substitute.for();
broadcasterService = Substitute.for();
activeAccount = new BehaviorSubject("123");
stateService.getEncryptedFolders().resolves({
"1": folderData("1", "test"),
});
stateService.activeAccount.returns(activeAccount);
(window as any).bitwardenContainerService = new ContainerService(cryptoService);
folderService = new FolderService(
cryptoService,
i18nService,
cipherService,
stateService,
broadcasterService
);
});
it("encrypt", async () => {
const model = new FolderView();
model.id = "2";
model.name = "Test Folder";
cryptoService.encrypt(Arg.any()).resolves(new EncString("ENC"));
cryptoService.decryptToUtf8(Arg.any()).resolves("DEC");
const result = await folderService.encrypt(model);
expect(result).toEqual({
id: "2",
name: {
encryptedString: "ENC",
encryptionType: 0,
},
});
});
describe("get", () => {
it("exists", async () => {
const result = await folderService.get("1");
expect(result).toEqual({
id: "1",
name: {
decryptedValue: [],
encryptedString: "test",
encryptionType: 0,
},
revisionDate: null,
});
});
it("not exists", async () => {
const result = await folderService.get("2");
expect(result).toBe(undefined);
});
});
it("upsert", async () => {
await folderService.upsert(folderData("2", "test 2"));
expect(await firstValueFrom(folderService.folders$)).toEqual([
{
id: "1",
name: {
decryptedValue: [],
encryptedString: "test",
encryptionType: 0,
},
revisionDate: null,
},
{
id: "2",
name: {
decryptedValue: [],
encryptedString: "test 2",
encryptionType: 0,
},
revisionDate: null,
},
]);
expect(await firstValueFrom(folderService.folderViews$)).toEqual([
{ id: "1", name: [], revisionDate: null },
{ id: "2", name: [], revisionDate: null },
{ id: null, name: [], revisionDate: null },
]);
});
it("replace", async () => {
await folderService.replace({ "2": folderData("2", "test 2") });
expect(await firstValueFrom(folderService.folders$)).toEqual([
{
id: "2",
name: {
decryptedValue: [],
encryptedString: "test 2",
encryptionType: 0,
},
revisionDate: null,
},
]);
expect(await firstValueFrom(folderService.folderViews$)).toEqual([
{ id: "2", name: [], revisionDate: null },
{ id: null, name: [], revisionDate: null },
]);
});
it("delete", async () => {
await folderService.delete("1");
expect((await firstValueFrom(folderService.folders$)).length).toBe(0);
expect(await firstValueFrom(folderService.folderViews$)).toEqual([
{ id: null, name: [], revisionDate: null },
]);
});
it("clearCache", async () => {
await folderService.clearCache();
expect((await firstValueFrom(folderService.folders$)).length).toBe(1);
expect((await firstValueFrom(folderService.folderViews$)).length).toBe(0);
});
describe("clear", () => {
it("null userId", async () => {
await folderService.clear();
stateService.received(1).setEncryptedFolders(Arg.any(), Arg.any());
expect((await firstValueFrom(folderService.folders$)).length).toBe(0);
expect((await firstValueFrom(folderService.folderViews$)).length).toBe(0);
});
it("matching userId", async () => {
stateService.getUserId().resolves("1");
await folderService.clear("1");
stateService.received(1).setEncryptedFolders(Arg.any(), Arg.any());
expect((await firstValueFrom(folderService.folders$)).length).toBe(0);
expect((await firstValueFrom(folderService.folderViews$)).length).toBe(0);
});
it("missmatching userId", async () => {
await folderService.clear("12");
stateService.received(1).setEncryptedFolders(Arg.any(), Arg.any());
expect((await firstValueFrom(folderService.folders$)).length).toBe(1);
expect((await firstValueFrom(folderService.folderViews$)).length).toBe(2);
});
});
function folderData(id: string, name: string) {
const data = new FolderData({} as any);
data.id = id;
data.name = name;
return data;
}
});

View File

@ -1,22 +1,22 @@
import { Observable } from "rxjs";
import { FolderData } from "../../models/data/folderData"; import { FolderData } from "../../models/data/folderData";
import { Folder } from "../../models/domain/folder"; import { Folder } from "../../models/domain/folder";
import { SymmetricCryptoKey } from "../../models/domain/symmetricCryptoKey"; import { SymmetricCryptoKey } from "../../models/domain/symmetricCryptoKey";
import { TreeNode } from "../../models/domain/treeNode";
import { FolderView } from "../../models/view/folderView"; import { FolderView } from "../../models/view/folderView";
export abstract class FolderService { export abstract class FolderService {
clearCache: (userId?: string) => Promise<void>; folders$: Observable<Folder[]>;
folderViews$: Observable<FolderView[]>;
clearCache: () => Promise<void>;
encrypt: (model: FolderView, key?: SymmetricCryptoKey) => Promise<Folder>; encrypt: (model: FolderView, key?: SymmetricCryptoKey) => Promise<Folder>;
get: (id: string) => Promise<Folder>; get: (id: string) => Promise<Folder>;
getAll: () => Promise<Folder[]>;
getAllDecrypted: () => Promise<FolderView[]>;
getAllNested: (folders?: FolderView[]) => Promise<TreeNode<FolderView>[]>;
getNested: (id: string) => Promise<TreeNode<FolderView>>;
} }
export abstract class InternalFolderService extends FolderService { export abstract class InternalFolderService extends FolderService {
upsert: (folder: FolderData | FolderData[]) => Promise<any>; upsert: (folder: FolderData | FolderData[]) => Promise<void>;
replace: (folders: { [id: string]: FolderData }) => Promise<any>; replace: (folders: { [id: string]: FolderData }) => Promise<void>;
clear: (userId: string) => Promise<any>; clear: (userId: string) => Promise<any>;
delete: (id: string | string[]) => Promise<any>; delete: (id: string | string[]) => Promise<any>;
} }

View File

@ -21,7 +21,6 @@ import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
import { WindowState } from "../models/domain/windowState"; import { WindowState } from "../models/domain/windowState";
import { CipherView } from "../models/view/cipherView"; import { CipherView } from "../models/view/cipherView";
import { CollectionView } from "../models/view/collectionView"; import { CollectionView } from "../models/view/collectionView";
import { FolderView } from "../models/view/folderView";
import { SendView } from "../models/view/sendView"; import { SendView } from "../models/view/sendView";
export abstract class StateService<T extends Account = Account> { export abstract class StateService<T extends Account = Account> {
@ -88,8 +87,6 @@ export abstract class StateService<T extends Account = Account> {
value: SymmetricCryptoKey, value: SymmetricCryptoKey,
options?: StorageOptions options?: StorageOptions
) => Promise<void>; ) => Promise<void>;
getDecryptedFolders: (options?: StorageOptions) => Promise<FolderView[]>;
setDecryptedFolders: (value: FolderView[], options?: StorageOptions) => Promise<void>;
getDecryptedOrganizationKeys: ( getDecryptedOrganizationKeys: (
options?: StorageOptions options?: StorageOptions
) => Promise<Map<string, SymmetricCryptoKey>>; ) => Promise<Map<string, SymmetricCryptoKey>>;
@ -183,7 +180,13 @@ export abstract class StateService<T extends Account = Account> {
) => Promise<void>; ) => Promise<void>;
getEncryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise<string>; getEncryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise<string>;
setEncryptedCryptoSymmetricKey: (value: string, options?: StorageOptions) => Promise<void>; setEncryptedCryptoSymmetricKey: (value: string, options?: StorageOptions) => Promise<void>;
/**
* @deprecated Do not call this directly, use FolderService
*/
getEncryptedFolders: (options?: StorageOptions) => Promise<{ [id: string]: FolderData }>; getEncryptedFolders: (options?: StorageOptions) => Promise<{ [id: string]: FolderData }>;
/**
* @deprecated Do not call this directly, use FolderService
*/
setEncryptedFolders: ( setEncryptedFolders: (
value: { [id: string]: FolderData }, value: { [id: string]: FolderData },
options?: StorageOptions options?: StorageOptions

View File

@ -11,7 +11,6 @@ import { ProviderData } from "../data/providerData";
import { SendData } from "../data/sendData"; import { SendData } from "../data/sendData";
import { CipherView } from "../view/cipherView"; import { CipherView } from "../view/cipherView";
import { CollectionView } from "../view/collectionView"; import { CollectionView } from "../view/collectionView";
import { FolderView } from "../view/folderView";
import { SendView } from "../view/sendView"; import { SendView } from "../view/sendView";
import { EncString } from "./encString"; import { EncString } from "./encString";
@ -31,15 +30,19 @@ export class DataEncryptionPair<TEncrypted, TDecrypted> {
decrypted?: TDecrypted[]; decrypted?: TDecrypted[];
} }
// This is a temporary structure to handle migrated `DataEncryptionPair` to
// avoid needing a data migration at this stage. It should be replaced with
// proper data migrations when `DataEncryptionPair` is deprecated.
export class TemporaryDataEncryption<TEncrypted> {
encrypted?: { [id: string]: TEncrypted };
}
export class AccountData { export class AccountData {
ciphers?: DataEncryptionPair<CipherData, CipherView> = new DataEncryptionPair< ciphers?: DataEncryptionPair<CipherData, CipherView> = new DataEncryptionPair<
CipherData, CipherData,
CipherView CipherView
>(); >();
folders?: DataEncryptionPair<FolderData, FolderView> = new DataEncryptionPair< folders? = new TemporaryDataEncryption<FolderData>();
FolderData,
FolderView
>();
localData?: any; localData?: any;
sends?: DataEncryptionPair<SendData, SendView> = new DataEncryptionPair<SendData, SendView>(); sends?: DataEncryptionPair<SendData, SendView> = new DataEncryptionPair<SendData, SendView>();
collections?: DataEncryptionPair<CollectionData, CollectionView> = new DataEncryptionPair< collections?: DataEncryptionPair<CollectionData, CollectionView> = new DataEncryptionPair<

View File

@ -1,4 +1,5 @@
import * as papa from "papaparse"; import * as papa from "papaparse";
import { firstValueFrom } from "rxjs";
import { ApiService } from "../abstractions/api.service"; import { ApiService } from "../abstractions/api.service";
import { CipherService } from "../abstractions/cipher.service"; import { CipherService } from "../abstractions/cipher.service";
@ -115,7 +116,7 @@ export class ExportService implements ExportServiceAbstraction {
const promises = []; const promises = [];
promises.push( promises.push(
this.folderService.getAllDecrypted().then((folders) => { firstValueFrom(this.folderService.folderViews$).then((folders) => {
decFolders = folders; decFolders = folders;
}) })
); );
@ -191,7 +192,7 @@ export class ExportService implements ExportServiceAbstraction {
const promises = []; const promises = [];
promises.push( promises.push(
this.folderService.getAll().then((f) => { firstValueFrom(this.folderService.folders$).then((f) => {
folders = f; folders = f;
}) })
); );

View File

@ -1,31 +1,70 @@
import { BehaviorSubject } from "rxjs";
import { BroadcasterService } from "../../abstractions/broadcaster.service";
import { CipherService } from "../../abstractions/cipher.service"; import { CipherService } from "../../abstractions/cipher.service";
import { CryptoService } from "../../abstractions/crypto.service"; import { CryptoService } from "../../abstractions/crypto.service";
import { FolderService as FolderServiceAbstraction } from "../../abstractions/folder/folder.service.abstraction"; import { FolderService as FolderServiceAbstraction } from "../../abstractions/folder/folder.service.abstraction";
import { I18nService } from "../../abstractions/i18n.service"; import { I18nService } from "../../abstractions/i18n.service";
import { StateService } from "../../abstractions/state.service"; import { StateService } from "../../abstractions/state.service";
import { ServiceUtils } from "../../misc/serviceUtils";
import { Utils } from "../../misc/utils"; import { Utils } from "../../misc/utils";
import { CipherData } from "../../models/data/cipherData"; import { CipherData } from "../../models/data/cipherData";
import { FolderData } from "../../models/data/folderData"; import { FolderData } from "../../models/data/folderData";
import { Folder } from "../../models/domain/folder"; import { Folder } from "../../models/domain/folder";
import { SymmetricCryptoKey } from "../../models/domain/symmetricCryptoKey"; import { SymmetricCryptoKey } from "../../models/domain/symmetricCryptoKey";
import { TreeNode } from "../../models/domain/treeNode";
import { FolderView } from "../../models/view/folderView"; import { FolderView } from "../../models/view/folderView";
const NestingDelimiter = "/"; const BroadcasterSubscriptionId = "FolderService";
export class FolderService implements FolderServiceAbstraction { export class FolderService implements FolderServiceAbstraction {
private _folders: BehaviorSubject<Folder[]> = new BehaviorSubject([]);
private _folderViews: BehaviorSubject<FolderView[]> = new BehaviorSubject([]);
folders$ = this._folders.asObservable();
folderViews$ = this._folderViews.asObservable();
constructor( constructor(
private cryptoService: CryptoService, private cryptoService: CryptoService,
private i18nService: I18nService, private i18nService: I18nService,
private cipherService: CipherService, private cipherService: CipherService,
private stateService: StateService private stateService: StateService,
) {} private broadcasterService: BroadcasterService
) {
async clearCache(userId?: string): Promise<void> { this.stateService.activeAccount.subscribe(async (activeAccount) => {
await this.stateService.setDecryptedFolders(null, { userId: userId }); if ((Utils.global as any).bitwardenContainerService == null) {
return;
} }
if (activeAccount == null) {
this._folders.next([]);
this._folderViews.next([]);
return;
}
const data = await this.stateService.getEncryptedFolders();
await this.updateObservables(data);
});
// TODO: Broadcasterservice should be removed or replaced with observables
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
switch (message.command) {
case "unlocked": {
const data = await this.stateService.getEncryptedFolders();
await this.updateObservables(data);
break;
}
default:
break;
}
});
}
async clearCache(): Promise<void> {
this._folderViews.next([]);
}
// TODO: This should be moved to EncryptService or something
async encrypt(model: FolderView, key?: SymmetricCryptoKey): Promise<Folder> { async encrypt(model: FolderView, key?: SymmetricCryptoKey): Promise<Folder> {
const folder = new Folder(); const folder = new Folder();
folder.id = model.id; folder.id = model.id;
@ -34,75 +73,12 @@ export class FolderService implements FolderServiceAbstraction {
} }
async get(id: string): Promise<Folder> { async get(id: string): Promise<Folder> {
const folders = await this.stateService.getEncryptedFolders(); const folders = this._folders.getValue();
// eslint-disable-next-line
if (folders == null || !folders.hasOwnProperty(id)) { return folders.find((folder) => folder.id === id);
return null;
} }
return new Folder(folders[id]); async upsert(folder: FolderData | FolderData[]): Promise<void> {
}
async getAll(): Promise<Folder[]> {
const folders = await this.stateService.getEncryptedFolders();
const response: Folder[] = [];
for (const id in folders) {
// eslint-disable-next-line
if (folders.hasOwnProperty(id)) {
response.push(new Folder(folders[id]));
}
}
return response;
}
async getAllDecrypted(): Promise<FolderView[]> {
const decryptedFolders = await this.stateService.getDecryptedFolders();
if (decryptedFolders != null) {
return decryptedFolders;
}
const hasKey = await this.cryptoService.hasKey();
if (!hasKey) {
throw new Error("No key.");
}
const decFolders: FolderView[] = [];
const promises: Promise<any>[] = [];
const folders = await this.getAll();
folders.forEach((folder) => {
promises.push(folder.decrypt().then((f) => decFolders.push(f)));
});
await Promise.all(promises);
decFolders.sort(Utils.getSortFunction(this.i18nService, "name"));
const noneFolder = new FolderView();
noneFolder.name = this.i18nService.t("noneFolder");
decFolders.push(noneFolder);
await this.stateService.setDecryptedFolders(decFolders);
return decFolders;
}
async getAllNested(folders?: FolderView[]): Promise<TreeNode<FolderView>[]> {
folders = folders ?? (await this.getAllDecrypted());
const nodes: TreeNode<FolderView>[] = [];
folders.forEach((f) => {
const folderCopy = new FolderView();
folderCopy.id = f.id;
folderCopy.revisionDate = f.revisionDate;
const parts = f.name != null ? f.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter) : [];
ServiceUtils.nestedTraverse(nodes, 0, parts, folderCopy, null, NestingDelimiter);
});
return nodes;
}
async getNested(id: string): Promise<TreeNode<FolderView>> {
const folders = await this.getAllNested();
return ServiceUtils.getTreeNodeObject(folders, id) as TreeNode<FolderView>;
}
async upsert(folder: FolderData | FolderData[]): Promise<any> {
let folders = await this.stateService.getEncryptedFolders(); let folders = await this.stateService.getEncryptedFolders();
if (folders == null) { if (folders == null) {
folders = {}; folders = {};
@ -117,17 +93,20 @@ export class FolderService implements FolderServiceAbstraction {
}); });
} }
await this.stateService.setDecryptedFolders(null); await this.updateObservables(folders);
await this.stateService.setEncryptedFolders(folders); await this.stateService.setEncryptedFolders(folders);
} }
async replace(folders: { [id: string]: FolderData }): Promise<any> { async replace(folders: { [id: string]: FolderData }): Promise<void> {
await this.stateService.setDecryptedFolders(null); await this.updateObservables(folders);
await this.stateService.setEncryptedFolders(folders); await this.stateService.setEncryptedFolders(folders);
} }
async clear(userId?: string): Promise<any> { async clear(userId?: string): Promise<any> {
await this.stateService.setDecryptedFolders(null, { userId: userId }); if (userId == null || userId == (await this.stateService.getUserId())) {
this._folders.next([]);
this._folderViews.next([]);
}
await this.stateService.setEncryptedFolders(null, { userId: userId }); await this.stateService.setEncryptedFolders(null, { userId: userId });
} }
@ -148,7 +127,7 @@ export class FolderService implements FolderServiceAbstraction {
}); });
} }
await this.stateService.setDecryptedFolders(null); await this.updateObservables(folders);
await this.stateService.setEncryptedFolders(folders); await this.stateService.setEncryptedFolders(folders);
// Items in a deleted folder are re-assigned to "No Folder" // Items in a deleted folder are re-assigned to "No Folder"
@ -166,4 +145,20 @@ export class FolderService implements FolderServiceAbstraction {
} }
} }
} }
private async updateObservables(foldersMap: { [id: string]: FolderData }) {
const folders = Object.values(foldersMap || {}).map((f) => new Folder(f));
const decryptFolderPromises = folders.map((f) => f.decrypt());
const decryptedFolders = await Promise.all(decryptFolderPromises);
decryptedFolders.sort(Utils.getSortFunction(this.i18nService, "name"));
const noneFolder = new FolderView();
noneFolder.name = this.i18nService.t("noneFolder");
decryptedFolders.push(noneFolder);
this._folders.next(folders);
this._folderViews.next(decryptedFolders);
}
} }

View File

@ -31,7 +31,6 @@ import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
import { WindowState } from "../models/domain/windowState"; import { WindowState } from "../models/domain/windowState";
import { CipherView } from "../models/view/cipherView"; import { CipherView } from "../models/view/cipherView";
import { CollectionView } from "../models/view/collectionView"; import { CollectionView } from "../models/view/collectionView";
import { FolderView } from "../models/view/folderView";
import { SendView } from "../models/view/sendView"; import { SendView } from "../models/view/sendView";
const keys = { const keys = {
@ -658,24 +657,6 @@ export class StateService<
); );
} }
@withPrototypeForArrayMembers(FolderView)
async getDecryptedFolders(options?: StorageOptions): Promise<FolderView[]> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
)?.data?.folders?.decrypted;
}
async setDecryptedFolders(value: FolderView[], options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultInMemoryOptions())
);
account.data.folders.decrypted = value;
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultInMemoryOptions())
);
}
@withPrototypeForMap(SymmetricCryptoKey, SymmetricCryptoKey.initFromJson) @withPrototypeForMap(SymmetricCryptoKey, SymmetricCryptoKey.initFromJson)
async getDecryptedOrganizationKeys( async getDecryptedOrganizationKeys(
options?: StorageOptions options?: StorageOptions

View File

@ -80,6 +80,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
if (userId == null || userId === (await this.stateService.getUserId())) { if (userId == null || userId === (await this.stateService.getUserId())) {
this.searchService.clearIndex(); this.searchService.clearIndex();
await this.folderService.clearCache();
} }
await this.stateService.setEverBeenUnlocked(true, { userId: userId }); await this.stateService.setEverBeenUnlocked(true, { userId: userId });
@ -91,7 +92,6 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
await this.cryptoService.clearKeyPair(true, userId); await this.cryptoService.clearKeyPair(true, userId);
await this.cryptoService.clearEncKey(true, userId); await this.cryptoService.clearEncKey(true, userId);
await this.folderService.clearCache(userId);
await this.cipherService.clearCache(userId); await this.cipherService.clearCache(userId);
await this.collectionService.clearCache(userId); await this.collectionService.clearCache(userId);