Add safe archive preview and extraction workflows

This commit is contained in:
2026-07-31 02:48:56 +02:00
parent 159a012833
commit b752dea610
20 changed files with 1961 additions and 182 deletions
+87 -2
View File
@@ -328,6 +328,25 @@ export type ManagedFileSnapshotResponse = {files: ManagedFile[];folders: FileFol
export type FileSpacesResponse = {spaces: FileSpace[];};
export type FileUploadResponse = {files: ManagedFile[];};
export type FileUploadProgress = {loaded: number;total?: number;percentage: number | null;};
export type ArchivePreviewEntry = {
path: string;
kind: "file" | "directory";
size_bytes: number;
compressed_size_bytes?: number | null;
encrypted: boolean;
};
export type ArchivePreviewResponse = {
preview_token: string;
archive_format: string;
entries: ArchivePreviewEntry[];
file_count: number;
directory_count: number;
expanded_size_bytes: number;
compressed_size_bytes: number;
requires_password: boolean;
password_verified: boolean;
expires_at: string;
};
export type FileConnectorFilePayload = {
library_id: string;
path: string;
@@ -556,14 +575,80 @@ options: {
return apiFetch<FileUploadResponse>(settings, "/api/v1/files/upload", { method: "POST", body: form });
}
export function previewArchiveUpload(
settings: ApiSettings,
file: File,
options: {
owner_type: "user" | "group";
owner_id: string;
path?: string;
campaign_id?: string;
password?: string;
})
: Promise<ArchivePreviewResponse> {
const form = new FormData();
form.append("file", file);
form.append("owner_type", options.owner_type);
form.append("owner_id", options.owner_id);
form.append("path", options.path ?? "");
if (options.campaign_id) form.append("campaign_id", options.campaign_id);
if (options.password) form.append("password", options.password);
return apiFetch<ArchivePreviewResponse>(settings, "/api/v1/files/archive-preview", { method: "POST", body: form });
}
export function confirmArchiveUpload(
settings: ApiSettings,
file: File,
options: {
preview_token: string;
selected_paths: string[];
owner_type: "user" | "group";
owner_id: string;
path?: string;
campaign_id?: string;
password?: string;
conflict_strategy?: ConflictStrategy;
conflict_resolutions?: ConflictResolution[];
source_provenance?: FileSourceProvenance;
source_revision?: string;
connector_policy_sources?: FileConnectorPolicySource[];
onProgress?: (progress: FileUploadProgress) => void;
})
: Promise<FileUploadResponse> {
const form = new FormData();
form.append("file", file);
form.append("preview_token", options.preview_token);
form.append("selected_paths_json", JSON.stringify(options.selected_paths));
form.append("owner_type", options.owner_type);
form.append("owner_id", options.owner_id);
form.append("path", options.path ?? "");
if (options.campaign_id) form.append("campaign_id", options.campaign_id);
if (options.password) form.append("password", options.password);
if (options.conflict_strategy) form.append("conflict_strategy", options.conflict_strategy);
if (options.conflict_resolutions?.length) form.append("conflict_resolutions_json", JSON.stringify(options.conflict_resolutions));
if (options.source_provenance) form.append("source_provenance_json", JSON.stringify(options.source_provenance));
if (options.source_revision) form.append("source_revision", options.source_revision);
if (options.connector_policy_sources?.length) form.append("connector_policy_json", JSON.stringify({ sources: options.connector_policy_sources }));
if (options.onProgress) {
return uploadFilesWithProgress(
settings,
form,
options.onProgress,
"/api/v1/files/archive-confirm"
);
}
return apiFetch<FileUploadResponse>(settings, "/api/v1/files/archive-confirm", { method: "POST", body: form });
}
function uploadFilesWithProgress(
settings: ApiSettings,
form: FormData,
onProgress: (progress: FileUploadProgress) => void)
onProgress: (progress: FileUploadProgress) => void,
endpoint = "/api/v1/files/upload")
: Promise<FileUploadResponse> {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("POST", apiUrl(settings, "/api/v1/files/upload"));
xhr.open("POST", apiUrl(settings, endpoint));
xhr.withCredentials = true;
for (const [key, value] of authHeaders(settings)) xhr.setRequestHeader(key, value);
const csrf = csrfToken();