mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 22:03:36 +00:00
Preserve export type across export source selections (#16922)
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
import { UserId, OrganizationId } from "@bitwarden/common/types/guid";
|
import { UserId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||||
|
|
||||||
import { ExportedVault } from "../types";
|
import { ExportedVault } from "../types";
|
||||||
@@ -5,6 +7,24 @@ import { ExportedVault } from "../types";
|
|||||||
export const EXPORT_FORMATS = ["csv", "json", "encrypted_json", "zip"] as const;
|
export const EXPORT_FORMATS = ["csv", "json", "encrypted_json", "zip"] as const;
|
||||||
export type ExportFormat = (typeof EXPORT_FORMATS)[number];
|
export type ExportFormat = (typeof EXPORT_FORMATS)[number];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options that determine which export formats are available
|
||||||
|
*/
|
||||||
|
export type FormatOptions = {
|
||||||
|
/** Whether the export is for the user's personal vault */
|
||||||
|
isMyVault: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata describing an available export format
|
||||||
|
*/
|
||||||
|
export type ExportFormatMetadata = {
|
||||||
|
/** Display name for the format (e.g., ".json", ".csv") */
|
||||||
|
name: string;
|
||||||
|
/** The export format identifier */
|
||||||
|
format: ExportFormat;
|
||||||
|
};
|
||||||
|
|
||||||
export abstract class VaultExportServiceAbstraction {
|
export abstract class VaultExportServiceAbstraction {
|
||||||
abstract getExport: (
|
abstract getExport: (
|
||||||
userId: UserId,
|
userId: UserId,
|
||||||
@@ -18,4 +38,11 @@ export abstract class VaultExportServiceAbstraction {
|
|||||||
password: string,
|
password: string,
|
||||||
onlyManagedCollections?: boolean,
|
onlyManagedCollections?: boolean,
|
||||||
) => Promise<ExportedVault>;
|
) => Promise<ExportedVault>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get available export formats based on vault context
|
||||||
|
* @param options Options determining which formats are available
|
||||||
|
* @returns Observable stream of available export formats
|
||||||
|
*/
|
||||||
|
abstract formats$(options: FormatOptions): Observable<ExportFormatMetadata[]>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { firstValueFrom } from "rxjs";
|
import { firstValueFrom, Observable, of } from "rxjs";
|
||||||
|
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
@@ -9,7 +9,12 @@ import { ExportedVault } from "../types";
|
|||||||
|
|
||||||
import { IndividualVaultExportServiceAbstraction } from "./individual-vault-export.service.abstraction";
|
import { IndividualVaultExportServiceAbstraction } from "./individual-vault-export.service.abstraction";
|
||||||
import { OrganizationVaultExportServiceAbstraction } from "./org-vault-export.service.abstraction";
|
import { OrganizationVaultExportServiceAbstraction } from "./org-vault-export.service.abstraction";
|
||||||
import { ExportFormat, VaultExportServiceAbstraction } from "./vault-export.service.abstraction";
|
import {
|
||||||
|
ExportFormat,
|
||||||
|
ExportFormatMetadata,
|
||||||
|
FormatOptions,
|
||||||
|
VaultExportServiceAbstraction,
|
||||||
|
} from "./vault-export.service.abstraction";
|
||||||
|
|
||||||
export class VaultExportService implements VaultExportServiceAbstraction {
|
export class VaultExportService implements VaultExportServiceAbstraction {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -85,6 +90,26 @@ export class VaultExportService implements VaultExportServiceAbstraction {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get available export formats based on vault context
|
||||||
|
* @param options Options determining which formats are available
|
||||||
|
* @returns Observable stream of available export formats
|
||||||
|
*/
|
||||||
|
formats$(options: FormatOptions): Observable<ExportFormatMetadata[]> {
|
||||||
|
const baseFormats: ExportFormatMetadata[] = [
|
||||||
|
{ name: ".json", format: "json" },
|
||||||
|
{ name: ".csv", format: "csv" },
|
||||||
|
{ name: ".json (Encrypted)", format: "encrypted_json" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ZIP format with attachments is only available for individual vault exports
|
||||||
|
if (options.isMyVault) {
|
||||||
|
return of([...baseFormats, { name: ".zip (with attachments)", format: "zip" }]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return of(baseFormats);
|
||||||
|
}
|
||||||
|
|
||||||
/** Checks if the provided userId matches the currently authenticated user
|
/** Checks if the provided userId matches the currently authenticated user
|
||||||
* @param userId The userId to check
|
* @param userId The userId to check
|
||||||
* @throws Error if the userId does not match the currently authenticated user
|
* @throws Error if the userId does not match the currently authenticated user
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
<bit-form-field>
|
<bit-form-field>
|
||||||
<bit-label>{{ "fileFormat" | i18n }}</bit-label>
|
<bit-label>{{ "fileFormat" | i18n }}</bit-label>
|
||||||
<bit-select formControlName="format">
|
<bit-select formControlName="format">
|
||||||
<bit-option *ngFor="let f of formatOptions" [value]="f.value" [label]="f.name" />
|
<bit-option *ngFor="let f of formatOptions$ | async" [value]="f.format" [label]="f.name" />
|
||||||
</bit-select>
|
</bit-select>
|
||||||
</bit-form-field>
|
</bit-form-field>
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,11 @@ import {
|
|||||||
} from "@bitwarden/components";
|
} from "@bitwarden/components";
|
||||||
import { GeneratorServicesModule } from "@bitwarden/generator-components";
|
import { GeneratorServicesModule } from "@bitwarden/generator-components";
|
||||||
import { CredentialGeneratorService, GenerateRequest, Type } from "@bitwarden/generator-core";
|
import { CredentialGeneratorService, GenerateRequest, Type } from "@bitwarden/generator-core";
|
||||||
import { ExportedVault, VaultExportServiceAbstraction } from "@bitwarden/vault-export-core";
|
import {
|
||||||
|
ExportedVault,
|
||||||
|
ExportFormatMetadata,
|
||||||
|
VaultExportServiceAbstraction,
|
||||||
|
} from "@bitwarden/vault-export-core";
|
||||||
|
|
||||||
import { EncryptedExportType } from "../enums/encrypted-export-type.enum";
|
import { EncryptedExportType } from "../enums/encrypted-export-type.enum";
|
||||||
|
|
||||||
@@ -231,11 +235,11 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||||||
fileEncryptionType: [EncryptedExportType.AccountEncrypted],
|
fileEncryptionType: [EncryptedExportType.AccountEncrypted],
|
||||||
});
|
});
|
||||||
|
|
||||||
formatOptions = [
|
/**
|
||||||
{ name: ".json", value: "json" },
|
* Observable stream of available export format options
|
||||||
{ name: ".csv", value: "csv" },
|
* Dynamically updates based on vault selection (My Vault vs Organization)
|
||||||
{ name: ".json (Encrypted)", value: "encrypted_json" },
|
*/
|
||||||
];
|
formatOptions$: Observable<ExportFormatMetadata[]>;
|
||||||
|
|
||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
private onlyManagedCollections = true;
|
private onlyManagedCollections = true;
|
||||||
@@ -338,17 +342,28 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private observeFormSelections(): void {
|
private observeFormSelections(): void {
|
||||||
this.exportForm.controls.vaultSelector.valueChanges
|
// Set up dynamic format options based on vault selection
|
||||||
.pipe(takeUntil(this.destroy$))
|
this.formatOptions$ = this.exportForm.controls.vaultSelector.valueChanges.pipe(
|
||||||
.subscribe((value) => {
|
startWith(this.exportForm.controls.vaultSelector.value),
|
||||||
this.organizationId = value !== "myVault" ? value : undefined;
|
map((vaultSelection) => {
|
||||||
|
const isMyVault = vaultSelection === "myVault";
|
||||||
|
// Update organizationId based on vault selection
|
||||||
|
this.organizationId = isMyVault ? undefined : vaultSelection;
|
||||||
|
return { isMyVault };
|
||||||
|
}),
|
||||||
|
switchMap((options) => this.exportService.formats$(options)),
|
||||||
|
tap((formats) => {
|
||||||
|
// Preserve the current format selection if it's still available in the new format list
|
||||||
|
const currentFormat = this.exportForm.get("format").value;
|
||||||
|
const isFormatAvailable = formats.some((f) => f.format === currentFormat);
|
||||||
|
|
||||||
this.formatOptions = this.formatOptions.filter((option) => option.value !== "zip");
|
// Only reset to json if the current format is no longer available
|
||||||
|
if (!isFormatAvailable) {
|
||||||
this.exportForm.get("format").setValue("json");
|
this.exportForm.get("format").setValue("json");
|
||||||
if (value === "myVault") {
|
|
||||||
this.formatOptions.push({ name: ".zip (with attachments)", value: "zip" });
|
|
||||||
}
|
}
|
||||||
});
|
}),
|
||||||
|
shareReplay({ bufferSize: 1, refCount: true }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user