Skip to content

Commit d82b7d5

Browse files
committed
fix: re-enable streaming zip on Safari to prevent iOS OOM crash
The buffered zip path (createZipFromUploadFiles) loads all file data into memory simultaneously: chunks array + Uint8Array copy + JSZip buffer + output blob = ~3-4x the input size in peak memory. For a 430MB upload, this reaches ~1.3-1.7GB and triggers iOS Safari's jetsam OOM killer, causing a silent page refresh with no error. Re-enable streaming zip for large multi-file uploads on all browsers. The streaming zip uses constant memory (processes chunks incrementally). Its size estimates may differ from actual bytes on Safari, but the upload pipeline handles this gracefully via empty-chunk filtering, the pre-completion consistency check, and R2 accepting fewer parts than originally allocated.
1 parent 599b66b commit d82b7d5

1 file changed

Lines changed: 7 additions & 5 deletions

File tree

apps/frontend/src/lib/api.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -675,10 +675,12 @@ export async function uploadFiles(
675675
// Determine upload strategy for multi-file uploads
676676
const isMultiFile = files.length > 1;
677677
const totalInputSize = files.reduce((sum, f) => sum + f.size, 0);
678-
// On Safari/WebKit, always use buffered zip — streaming zip produces estimated
679-
// sizes that can mismatch actual bytes due to HEIC/HEVC lazy transcoding, and
680-
// WebKit's ReadableStream can emit empty chunks during transcoding pauses.
681-
const useStreamingZip = isMultiFile && totalInputSize >= STREAMING_ZIP_THRESHOLD && !isWebKit;
678+
// Streaming zip is used for large multi-file uploads to avoid loading everything
679+
// into memory. On Safari, the streaming zip's size estimate may differ from actual
680+
// bytes, but the upload pipeline handles this gracefully (fewer/more parts are OK).
681+
// The alternative (buffered zip) would OOM-crash iOS Safari for files >200-300MB
682+
// since it loads all data + zip output into memory simultaneously.
683+
const useStreamingZip = isMultiFile && totalInputSize >= STREAMING_ZIP_THRESHOLD;
682684

683685
// For multiple files, create a zip (buffered for small, streaming for large)
684686
let uploadBlob: Blob | null = null;
@@ -698,7 +700,7 @@ export async function uploadFiles(
698700
zipFilename = streamingResult.filename;
699701
estimatedZipSize = streamingResult.estimatedSize;
700702
} else {
701-
// Small files (or Safari): use buffered zip for exact sizing
703+
// Small files: use buffered zip for compression benefits and exact sizing
702704
const zipResult = await createZipFromUploadFiles(files, onZipProgress);
703705
uploadBlob = zipResult.blob;
704706
zipFilename = zipResult.filename;

0 commit comments

Comments
 (0)