Skip to content

Commit 2d586a8

Browse files
committed
refactor: Move file compression to backend and support larger uploads
- Switched file hashing to chunked streaming to prevent frontend memory exhaustion on `>2GB` uploads - Added backend-aware upload headers (omit x-ms-blob-type for S3 URLs) - Added support for archive encoding for multiple file uploads Signed-off-by: Omar <omar.brbutovic@secomind.com>
1 parent cf218e5 commit 2d586a8

11 files changed

Lines changed: 550 additions & 216 deletions

File tree

frontend/package-lock.json

Lines changed: 26 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"@fortawesome/react-fontawesome": "^3.3.0",
2121
"@hookform/resolvers": "^5.2.2",
2222
"@monaco-editor/react": "^4.7.0",
23+
"@noble/hashes": "^2.0.1",
2324
"@tanstack/react-table": "^8.21.3",
2425
"@types/js-cookie": "^3.0.6",
2526
"@types/leaflet": "^1.9.21",
@@ -35,7 +36,6 @@
3536
"class-variance-authority": "^0.7.1",
3637
"clsx": "^2.1.1",
3738
"dayjs": "^1.11.20",
38-
"fflate": "^0.8.2",
3939
"graphql": "^16.13.2",
4040
"history": "^5.3.0",
4141
"js-cookie": "^3.0.5",

frontend/src/components/DeviceTabs/FilesUploadTab.tsx

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ import { v7 as uuidv7 } from "uuid";
4242

4343
import type { FilesUploadTab_PaginationQuery } from "@/api/__generated__/FilesUploadTab_PaginationQuery.graphql";
4444
import type { FilesUploadTab_createFileDownloadRequestPresignedUrl_Mutation } from "@/api/__generated__/FilesUploadTab_createFileDownloadRequestPresignedUrl_Mutation.graphql";
45-
import type { FilesUploadTab_createManualFileDownloadRequest_Mutation } from "@/api/__generated__/FilesUploadTab_createManualFileDownloadRequest_Mutation.graphql";
4645
import type { FilesUploadTab_createManagedFileDownloadRequest_Mutation } from "@/api/__generated__/FilesUploadTab_createManagedFileDownloadRequest_Mutation.graphql";
46+
import type { FilesUploadTab_createManualFileDownloadRequest_Mutation } from "@/api/__generated__/FilesUploadTab_createManualFileDownloadRequest_Mutation.graphql";
4747
import type { FilesUploadTab_fileDownloadRequests$key } from "@/api/__generated__/FilesUploadTab_fileDownloadRequests.graphql";
4848
import type { FilesUploadTab_getRepositories_Query } from "@/api/__generated__/FilesUploadTab_getRepositories_Query.graphql";
4949

@@ -60,7 +60,11 @@ import type {
6060
FileDestinationType,
6161
ManualFileDownloadRequestFromRepositoryData,
6262
} from "@/forms/validation";
63-
import { computeDigest, createTarGzArchive } from "@/lib/files";
63+
import {
64+
computeDigest,
65+
createTarArchive,
66+
getUploadRequestHeaders,
67+
} from "@/lib/files";
6468

6569
// We use graphql fields below in columns configuration
6670
/* eslint-disable relay/unused-fields */
@@ -217,13 +221,17 @@ const formatRelayErrors = (
217221
type ManualFileDownloadRequestFormWrapperProps = {
218222
setErrorFeedback: (feedback: React.ReactNode) => void;
219223
deviceId: string;
224+
supportedEncodings: string[];
225+
allowArchiveUpload: boolean;
220226
showAdvancedOptions: boolean;
221227
destinationTypeOptions: DestinationTypeOption[];
222228
};
223229

224230
const ManualFileDownloadRequestFormWrapper = ({
225231
setErrorFeedback,
226232
deviceId,
233+
supportedEncodings,
234+
allowArchiveUpload,
227235
showAdvancedOptions,
228236
destinationTypeOptions,
229237
}: ManualFileDownloadRequestFormWrapperProps) => {
@@ -264,6 +272,7 @@ const ManualFileDownloadRequestFormWrapper = ({
264272
const {
265273
files,
266274
archiveName,
275+
encoding,
267276
destinationType,
268277
destination,
269278
ttlSeconds,
@@ -276,7 +285,6 @@ const ManualFileDownloadRequestFormWrapper = ({
276285
let uploadBlob: Blob;
277286
let fileName: string;
278287
let uncompressedSize: number;
279-
let encoding: string | null = null;
280288

281289
// Files from folder selection have webkitRelativePath set.
282290
// These need archiving even if there's only one file, to preserve
@@ -285,27 +293,18 @@ const ManualFileDownloadRequestFormWrapper = ({
285293
const needsArchive = files.length > 1 || hasRelativePaths;
286294

287295
if (needsArchive) {
288-
// Multiple files or folder contents: create tar.gz archive
289-
uploadBlob = await createTarGzArchive(files);
290-
const baseName = archiveName?.trim() || "files-archive";
291-
fileName = baseName.endsWith(".tar.gz")
292-
? baseName
293-
: `${baseName}.tar.gz`;
296+
// Multiple files or folder content are uploaded as an archive
297+
uploadBlob = await createTarArchive(files);
298+
fileName = archiveName?.trim() || "files-archive";
294299
uncompressedSize = files.reduce((sum, f) => sum + f.size, 0);
295-
encoding = "tar.gz";
296300
} else {
297301
uploadBlob = files[0];
298302
fileName = files[0].name;
299303
uncompressedSize = files[0].size;
300304
}
301305

302-
if (files.length === 1 && /\.(tar\.gz|tgz)$/i.test(files[0].name)) {
303-
encoding = "tar.gz";
304-
}
305-
306-
const archiveData = new Uint8Array(await uploadBlob.arrayBuffer());
307306
const fileDownloadRequestId = uuidv7();
308-
const digest = await computeDigest(archiveData);
307+
const digest = await computeDigest(uploadBlob);
309308

310309
// Get presigned URL from the backend
311310
const presignedUrls = await new Promise<{
@@ -361,7 +360,7 @@ const ManualFileDownloadRequestFormWrapper = ({
361360
// Upload the file to the presigned PUT URL
362361
const uploadResponse = await fetch(presignedUrls.put_url, {
363362
method: "PUT",
364-
headers: { "x-ms-blob-type": "BlockBlob" },
363+
headers: getUploadRequestHeaders(presignedUrls.put_url),
365364
body: uploadBlob,
366365
});
367366

@@ -459,6 +458,7 @@ const ManualFileDownloadRequestFormWrapper = ({
459458
deviceId,
460459
getPresignedUrl,
461460
createFileDownloadRequest,
461+
allowArchiveUpload,
462462
intl,
463463
setErrorFeedback,
464464
],
@@ -468,6 +468,8 @@ const ManualFileDownloadRequestFormWrapper = ({
468468
<ManualFileDownloadRequestForm
469469
isLoading={isUploading}
470470
onFileSubmit={handleFileUpload}
471+
supportedEncodings={supportedEncodings}
472+
allowArchiveUpload={allowArchiveUpload}
471473
showAdvancedOptions={showAdvancedOptions}
472474
destinationTypeOptions={destinationTypeOptions}
473475
/>
@@ -519,12 +521,6 @@ const ManualFileDownloadRequestFromRepositoryFormWrapper = ({
519521
groupId,
520522
} = values;
521523

522-
let encoding: string | null = null;
523-
524-
if (/\.(tar\.gz|tgz)$/i.test(file.name)) {
525-
encoding = "tar.gz";
526-
}
527-
528524
const fileDownloadRequestId = uuidv7();
529525

530526
// Create the file download request with all metadata
@@ -535,7 +531,6 @@ const ManualFileDownloadRequestFromRepositoryFormWrapper = ({
535531
deviceId,
536532
fileDownloadRequestId,
537533
fileId: file.id,
538-
encoding,
539534
fileMode,
540535
userId,
541536
groupId,
@@ -702,6 +697,33 @@ const FilesUploadTab = ({
702697
[data.fileTransferCapabilities?.targets, intl],
703698
);
704699

700+
const supportedEncodings = useMemo(() => {
701+
const uniqueEncodings = new Set<string>();
702+
703+
data.fileTransferCapabilities?.encodings?.forEach((encoding) => {
704+
const value = encoding?.trim();
705+
706+
if (value) {
707+
uniqueEncodings.add(value);
708+
}
709+
});
710+
711+
return Array.from(uniqueEncodings);
712+
}, [data.fileTransferCapabilities?.encodings]);
713+
714+
const allowArchiveUpload = useMemo(
715+
() =>
716+
supportedEncodings.some((encoding) => {
717+
const normalizedEncoding = encoding.trim().toLowerCase();
718+
return (
719+
normalizedEncoding === "tar" ||
720+
normalizedEncoding === "tar.gz" ||
721+
normalizedEncoding === "tar.lz4"
722+
);
723+
}),
724+
[supportedEncodings],
725+
);
726+
705727
if (destinationTypeOptions?.length === 0) {
706728
return null;
707729
}
@@ -766,6 +788,8 @@ const FilesUploadTab = ({
766788
<ManualFileDownloadRequestFormWrapper
767789
setErrorFeedback={setErrorFeedback}
768790
deviceId={deviceId}
791+
supportedEncodings={supportedEncodings}
792+
allowArchiveUpload={allowArchiveUpload}
769793
showAdvancedOptions={showAdvancedOptions}
770794
destinationTypeOptions={destinationTypeOptions}
771795
/>

0 commit comments

Comments
 (0)