@uploadbox/react 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/generate-components.d.ts +5 -0
  2. package/dist/generate-components.d.ts.map +1 -0
  3. package/dist/generate-components.js +9 -0
  4. package/dist/generate-components.js.map +1 -0
  5. package/dist/index.d.ts +11 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +9 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/multipart.d.ts +25 -0
  10. package/dist/multipart.d.ts.map +1 -0
  11. package/dist/multipart.js +130 -0
  12. package/dist/multipart.js.map +1 -0
  13. package/dist/progress-tracker.d.ts +13 -0
  14. package/dist/progress-tracker.d.ts.map +1 -0
  15. package/dist/progress-tracker.js +93 -0
  16. package/dist/progress-tracker.js.map +1 -0
  17. package/dist/provider.d.ts +14 -0
  18. package/dist/provider.d.ts.map +1 -0
  19. package/dist/provider.js +12 -0
  20. package/dist/provider.js.map +1 -0
  21. package/dist/retry.d.ts +5 -0
  22. package/dist/retry.d.ts.map +1 -0
  23. package/dist/retry.js +47 -0
  24. package/dist/retry.js.map +1 -0
  25. package/dist/types.d.ts +86 -0
  26. package/dist/types.d.ts.map +1 -0
  27. package/dist/types.js +2 -0
  28. package/dist/types.js.map +1 -0
  29. package/dist/upload-button.d.ts +4 -0
  30. package/dist/upload-button.d.ts.map +1 -0
  31. package/dist/upload-button.js +25 -0
  32. package/dist/upload-button.js.map +1 -0
  33. package/dist/upload-dropzone.d.ts +4 -0
  34. package/dist/upload-dropzone.d.ts.map +1 -0
  35. package/dist/upload-dropzone.js +47 -0
  36. package/dist/upload-dropzone.js.map +1 -0
  37. package/dist/use-uploadbox.d.ts +4 -0
  38. package/dist/use-uploadbox.d.ts.map +1 -0
  39. package/dist/use-uploadbox.js +246 -0
  40. package/dist/use-uploadbox.js.map +1 -0
  41. package/package.json +56 -0
  42. package/src/generate-components.ts +20 -0
  43. package/src/index.ts +22 -0
  44. package/src/multipart.ts +189 -0
  45. package/src/progress-tracker.ts +107 -0
  46. package/src/provider.tsx +34 -0
  47. package/src/retry.ts +62 -0
  48. package/src/styles.css +126 -0
  49. package/src/types.ts +96 -0
  50. package/src/upload-button.tsx +76 -0
  51. package/src/upload-dropzone.tsx +138 -0
  52. package/src/use-uploadbox.ts +333 -0
@@ -0,0 +1,47 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState, useCallback } from "react";
4
+ import { useDropzone } from "react-dropzone";
5
+ import { useUploadbox } from "./use-uploadbox.js";
6
+ export function UploadDropzone(props) {
7
+ const { endpoint, onClientUploadComplete, onUploadError, onUploadProgress, onBeforeUploadBegin, className, disabled, content, } = props;
8
+ const [state, setState] = useState("idle");
9
+ const [selectedFiles, setSelectedFiles] = useState([]);
10
+ const { startUpload, isUploading, progress } = useUploadbox(endpoint, {
11
+ onClientUploadComplete: (files) => {
12
+ setState("complete");
13
+ setSelectedFiles([]);
14
+ onClientUploadComplete?.(files);
15
+ setTimeout(() => setState("idle"), 2000);
16
+ },
17
+ onUploadError: (error) => {
18
+ setState("error");
19
+ onUploadError?.(error);
20
+ setTimeout(() => setState("idle"), 3000);
21
+ },
22
+ onUploadProgress,
23
+ onBeforeUploadBegin,
24
+ });
25
+ const onDrop = useCallback((acceptedFiles) => {
26
+ setSelectedFiles(acceptedFiles);
27
+ setState("idle");
28
+ }, []);
29
+ const { getRootProps, getInputProps, isDragActive } = useDropzone({
30
+ onDrop,
31
+ disabled: disabled || isUploading,
32
+ });
33
+ const handleUpload = useCallback(async () => {
34
+ if (selectedFiles.length === 0)
35
+ return;
36
+ setState("uploading");
37
+ await startUpload(selectedFiles);
38
+ }, [selectedFiles, startUpload]);
39
+ const stateClass = isDragActive ? "dragover" : state;
40
+ return (_jsxs("div", { ...getRootProps(), className: `uploadbox-dropzone uploadbox-dropzone--${stateClass} ${className ?? ""}`, children: [_jsx("input", { ...getInputProps() }), state === "complete" ? (_jsx("div", { className: "uploadbox-dropzone-content", children: _jsx("p", { className: "uploadbox-dropzone-label", children: "Upload complete!" }) })) : state === "error" ? (_jsx("div", { className: "uploadbox-dropzone-content", children: _jsx("p", { className: "uploadbox-dropzone-label", children: "Upload failed. Try again." }) })) : (_jsxs("div", { className: "uploadbox-dropzone-content", children: [_jsxs("svg", { className: "uploadbox-dropzone-icon", xmlns: "http://www.w3.org/2000/svg", width: "40", height: "40", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }), _jsx("polyline", { points: "17 8 12 3 7 8" }), _jsx("line", { x1: "12", y1: "3", x2: "12", y2: "15" })] }), _jsx("p", { className: "uploadbox-dropzone-label", children: isDragActive
41
+ ? "Drop files here"
42
+ : content?.label ?? "Drag & drop files here, or click to browse" }), content?.allowedContent && (_jsx("p", { className: "uploadbox-dropzone-allowed", children: content.allowedContent })), selectedFiles.length > 0 && !isUploading && (_jsxs("div", { className: "uploadbox-dropzone-selected", children: [_jsxs("p", { children: [selectedFiles.length, " file(s) selected"] }), _jsx("button", { type: "button", className: "uploadbox-button", onClick: (e) => {
43
+ e.stopPropagation();
44
+ handleUpload();
45
+ }, children: content?.button ?? "Upload" })] })), isUploading && (_jsx("div", { className: "uploadbox-progress-bar", children: _jsx("div", { className: "uploadbox-progress-bar-fill", style: { width: `${progress}%` } }) }))] }))] }));
46
+ }
47
+ //# sourceMappingURL=upload-dropzone.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload-dropzone.js","sourceRoot":"","sources":["../src/upload-dropzone.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { FileRouter } from "@uploadbox/core";
2
+ import type { UseUploadboxOpts, UseUploadboxReturn } from "./types.js";
3
+ export declare function useUploadbox<TRouter extends FileRouter, TEndpoint extends keyof TRouter & string>(endpoint: TEndpoint, opts?: Omit<UseUploadboxOpts<TRouter, TEndpoint>, "endpoint">): UseUploadboxReturn;
4
+ //# sourceMappingURL=use-uploadbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-uploadbox.d.ts","sourceRoot":"","sources":["../src/use-uploadbox.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAgB,MAAM,iBAAiB,CAAC;AAChE,OAAO,KAAK,EACV,gBAAgB,EAChB,kBAAkB,EAInB,MAAM,YAAY,CAAC;AAmGpB,wBAAgB,YAAY,CAC1B,OAAO,SAAS,UAAU,EAC1B,SAAS,SAAS,MAAM,OAAO,GAAG,MAAM,EAExC,QAAQ,EAAE,SAAS,EACnB,IAAI,CAAC,EAAE,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE,UAAU,CAAC,GAC5D,kBAAkB,CAyNpB"}
@@ -0,0 +1,246 @@
1
+ "use client";
2
+ import { useState, useEffect, useCallback, useMemo, useRef } from "react";
3
+ import { ProgressTracker } from "./progress-tracker.js";
4
+ import { withRetry, DEFAULT_RETRY_CONFIG } from "./retry.js";
5
+ import { shouldUseMultipart, uploadFileMultipart } from "./multipart.js";
6
+ import { useUploadboxConfig } from "./provider.js";
7
+ const DEFAULT_API_URL = "/api/uploadbox";
8
+ function buildHeaders(providerHeaders, providerApiKey, optHeaders) {
9
+ const merged = {};
10
+ if (providerHeaders)
11
+ Object.assign(merged, providerHeaders);
12
+ if (providerApiKey)
13
+ merged["Authorization"] = `Bearer ${providerApiKey}`;
14
+ if (optHeaders)
15
+ Object.assign(merged, optHeaders);
16
+ return merged;
17
+ }
18
+ async function fetchRouterConfig(apiUrl, headers) {
19
+ const res = await fetch(apiUrl, { headers });
20
+ if (!res.ok)
21
+ throw new Error("Failed to fetch router config");
22
+ return res.json();
23
+ }
24
+ async function requestPresignedUrls(apiUrl, routeKey, files, headers) {
25
+ const res = await fetch(apiUrl, {
26
+ method: "POST",
27
+ headers: { "Content-Type": "application/json", ...headers },
28
+ body: JSON.stringify({ action: "upload", routeKey, files }),
29
+ });
30
+ if (!res.ok) {
31
+ const err = await res.json();
32
+ throw new Error(err.message || "Failed to get upload URLs");
33
+ }
34
+ return res.json();
35
+ }
36
+ async function confirmUploads(apiUrl, routeKey, keys, headers) {
37
+ const res = await fetch(apiUrl, {
38
+ method: "POST",
39
+ headers: { "Content-Type": "application/json", ...headers },
40
+ body: JSON.stringify({ action: "complete", routeKey, keys }),
41
+ });
42
+ if (!res.ok) {
43
+ const err = await res.json();
44
+ throw new Error(err.message || "Failed to confirm uploads");
45
+ }
46
+ return res.json();
47
+ }
48
+ function uploadFileWithProgress(url, file, contentType, onProgress, signal) {
49
+ return new Promise((resolve, reject) => {
50
+ const xhr = new XMLHttpRequest();
51
+ xhr.open("PUT", url);
52
+ xhr.setRequestHeader("Content-Type", contentType);
53
+ if (signal) {
54
+ signal.addEventListener("abort", () => xhr.abort(), { once: true });
55
+ }
56
+ xhr.upload.addEventListener("progress", (event) => {
57
+ if (event.lengthComputable) {
58
+ onProgress?.(event.loaded);
59
+ }
60
+ });
61
+ xhr.addEventListener("load", () => {
62
+ if (xhr.status >= 200 && xhr.status < 300) {
63
+ resolve();
64
+ }
65
+ else {
66
+ reject(new Error(`Upload failed with status ${xhr.status}`));
67
+ }
68
+ });
69
+ xhr.addEventListener("error", () => reject(new Error("Upload failed")));
70
+ xhr.addEventListener("abort", () => reject(new Error("Upload aborted")));
71
+ xhr.send(file);
72
+ });
73
+ }
74
+ export function useUploadbox(endpoint, opts) {
75
+ const providerConfig = useUploadboxConfig();
76
+ const apiUrl = providerConfig.apiUrl ?? DEFAULT_API_URL;
77
+ const mergedHeaders = useMemo(() => buildHeaders(providerConfig.headers, providerConfig.apiKey, opts?.headers), [providerConfig.headers, providerConfig.apiKey, opts?.headers]);
78
+ const [isUploading, setIsUploading] = useState(false);
79
+ const [progress, setProgress] = useState(0);
80
+ const [fileProgress, setFileProgress] = useState([]);
81
+ const [routeConfig, setRouteConfig] = useState();
82
+ const trackerRef = useRef(new ProgressTracker());
83
+ const abortControllerRef = useRef(null);
84
+ useEffect(() => {
85
+ fetchRouterConfig(apiUrl, mergedHeaders).then((config) => {
86
+ setRouteConfig(config[endpoint]);
87
+ }).catch(console.error);
88
+ }, [endpoint, apiUrl, mergedHeaders]);
89
+ const abort = useCallback(() => {
90
+ abortControllerRef.current?.abort();
91
+ }, []);
92
+ const startUpload = useCallback(async (inputFiles) => {
93
+ const abortController = new AbortController();
94
+ abortControllerRef.current = abortController;
95
+ const tracker = trackerRef.current;
96
+ tracker.reset();
97
+ try {
98
+ setIsUploading(true);
99
+ setProgress(0);
100
+ let filesToUpload = inputFiles;
101
+ if (opts?.onBeforeUploadBegin) {
102
+ filesToUpload = opts.onBeforeUploadBegin(filesToUpload);
103
+ }
104
+ // Initialize progress tracker
105
+ filesToUpload.forEach((f, i) => {
106
+ tracker.init(String(i), f.name, f.size, f.type || "application/octet-stream");
107
+ });
108
+ // 1. Build file infos with metadata
109
+ const fileInfos = filesToUpload.map((f) => ({
110
+ name: f.name,
111
+ size: f.size,
112
+ type: f.type || "application/octet-stream",
113
+ ...(opts?.getFileMetadata ? { customMetadata: opts.getFileMetadata(f) } : {}),
114
+ ...(opts?.ttlSeconds != null ? { ttlSeconds: opts.ttlSeconds } : {}),
115
+ }));
116
+ // Split into small files (single-part) and large files (multipart)
117
+ const smallFileIndices = [];
118
+ const largeFileIndices = [];
119
+ filesToUpload.forEach((f, i) => {
120
+ if (shouldUseMultipart(f.size)) {
121
+ largeFileIndices.push(i);
122
+ }
123
+ else {
124
+ smallFileIndices.push(i);
125
+ }
126
+ });
127
+ // 2. Request presigned URLs for small files
128
+ const smallFileInfos = smallFileIndices.map((i) => fileInfos[i]);
129
+ let presignedResults = [];
130
+ if (smallFileInfos.length > 0) {
131
+ presignedResults = await requestPresignedUrls(apiUrl, endpoint, smallFileInfos, mergedHeaders);
132
+ }
133
+ const retryConfig = opts?.retry === false ? undefined : (opts?.retry ?? DEFAULT_RETRY_CONFIG);
134
+ // Helper to emit progress
135
+ const emitProgress = () => {
136
+ const snapshot = tracker.getSnapshot();
137
+ setProgress(snapshot.percent);
138
+ setFileProgress(snapshot.fileProgress);
139
+ opts?.onUploadProgress?.(snapshot);
140
+ };
141
+ // 3. Upload small files with retry
142
+ const smallUploadPromises = presignedResults.map((result, idx) => {
143
+ const fileIndex = smallFileIndices[idx];
144
+ const file = filesToUpload[fileIndex];
145
+ const fileId = String(fileIndex);
146
+ tracker.setKey(fileId, result.key);
147
+ tracker.setStatus(fileId, "uploading");
148
+ const doUpload = async () => {
149
+ await uploadFileWithProgress(result.url, file, file.type || "application/octet-stream", (loaded) => {
150
+ tracker.updateProgress(fileId, loaded);
151
+ emitProgress();
152
+ }, abortController.signal);
153
+ };
154
+ if (retryConfig) {
155
+ return withRetry(() => doUpload(), retryConfig, (attempt) => {
156
+ tracker.incrementRetry(fileId);
157
+ emitProgress();
158
+ }, abortController.signal).then(() => {
159
+ tracker.setStatus(fileId, "complete");
160
+ emitProgress();
161
+ });
162
+ }
163
+ return doUpload().then(() => {
164
+ tracker.setStatus(fileId, "complete");
165
+ emitProgress();
166
+ });
167
+ });
168
+ // 4. Upload large files via multipart
169
+ const largeUploadPromises = largeFileIndices.map(async (fileIndex) => {
170
+ const file = filesToUpload[fileIndex];
171
+ const fileId = String(fileIndex);
172
+ const info = fileInfos[fileIndex];
173
+ tracker.setStatus(fileId, "uploading");
174
+ const result = await uploadFileMultipart({
175
+ file,
176
+ routeKey: endpoint,
177
+ fileInfo: info,
178
+ apiUrl,
179
+ headers: mergedHeaders,
180
+ retryConfig: retryConfig ?? undefined,
181
+ signal: abortController.signal,
182
+ onProgress: (loaded) => {
183
+ tracker.updateProgress(fileId, loaded);
184
+ emitProgress();
185
+ },
186
+ onRetry: () => {
187
+ tracker.incrementRetry(fileId);
188
+ emitProgress();
189
+ },
190
+ });
191
+ tracker.setKey(fileId, result.key);
192
+ tracker.setStatus(fileId, "complete");
193
+ emitProgress();
194
+ return result;
195
+ });
196
+ // Wait for all uploads
197
+ await Promise.all([...smallUploadPromises, ...largeUploadPromises]);
198
+ // 5. Confirm small file uploads
199
+ const smallKeys = presignedResults.map((r) => r.key);
200
+ let allResults = [];
201
+ if (smallKeys.length > 0) {
202
+ const smallConfirm = await confirmUploads(apiUrl, endpoint, smallKeys, mergedHeaders);
203
+ allResults.push(...smallConfirm);
204
+ }
205
+ // Large file results are already confirmed server-side during multipart complete
206
+ // Add their results too
207
+ for (const idx of largeFileIndices) {
208
+ const fileId = String(idx);
209
+ const fp = tracker.getSnapshot().fileProgress.find((f) => f.fileId === fileId);
210
+ if (fp?.key) {
211
+ allResults.push({
212
+ file: {
213
+ key: fp.key,
214
+ name: fp.name,
215
+ size: fp.size,
216
+ type: fp.type,
217
+ url: "", // URL will come from confirm
218
+ },
219
+ });
220
+ }
221
+ }
222
+ const uploadedFiles = allResults.map((r) => ({
223
+ key: r.file.key,
224
+ name: r.file.name,
225
+ size: r.file.size,
226
+ type: r.file.type,
227
+ url: r.file.url,
228
+ customMetadata: r.file.customMetadata,
229
+ }));
230
+ setProgress(100);
231
+ opts?.onClientUploadComplete?.(uploadedFiles);
232
+ return uploadedFiles;
233
+ }
234
+ catch (err) {
235
+ const error = err instanceof Error ? err : new Error("Upload failed");
236
+ opts?.onUploadError?.(error);
237
+ return undefined;
238
+ }
239
+ finally {
240
+ setIsUploading(false);
241
+ abortControllerRef.current = null;
242
+ }
243
+ }, [apiUrl, endpoint, mergedHeaders, opts]);
244
+ return { startUpload, isUploading, progress, routeConfig, fileProgress, abort };
245
+ }
246
+ //# sourceMappingURL=use-uploadbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-uploadbox.js","sourceRoot":"","sources":["../src/use-uploadbox.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@uploadbox/react",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./styles.css": "./src/styles.css"
14
+ },
15
+ "dependencies": {
16
+ "react-dropzone": "^14.3.0",
17
+ "@uploadbox/core": "0.1.0"
18
+ },
19
+ "peerDependencies": {
20
+ "react": "^18.0.0 || ^19.0.0",
21
+ "react-dom": "^18.0.0 || ^19.0.0"
22
+ },
23
+ "devDependencies": {
24
+ "typescript": "^5.7.0",
25
+ "@types/react": "^19.0.0",
26
+ "@types/react-dom": "^19.0.0"
27
+ },
28
+ "description": "React components and hooks for Uploadbox — UploadButton, UploadDropzone, useUploadbox",
29
+ "keywords": [
30
+ "uploadbox",
31
+ "react",
32
+ "upload",
33
+ "file-upload",
34
+ "upload-button",
35
+ "dropzone"
36
+ ],
37
+ "license": "MIT",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "https://github.com/federicostarace/uploadbox",
41
+ "directory": "packages/react"
42
+ },
43
+ "files": [
44
+ "dist",
45
+ "src"
46
+ ],
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
50
+ "scripts": {
51
+ "build": "tsc",
52
+ "dev": "tsc --watch",
53
+ "typecheck": "tsc --noEmit",
54
+ "clean": "rm -rf dist"
55
+ }
56
+ }
@@ -0,0 +1,20 @@
1
+ import type { FileRouter } from "@uploadbox/core";
2
+ import type { UploadButtonProps, UploadDropzoneProps } from "./types.js";
3
+ import { UploadButton } from "./upload-button.js";
4
+ import { UploadDropzone } from "./upload-dropzone.js";
5
+
6
+ export function generateUploadButton<
7
+ TRouter extends FileRouter
8
+ >() {
9
+ return UploadButton as <TEndpoint extends keyof TRouter & string>(
10
+ props: UploadButtonProps<TRouter, TEndpoint>
11
+ ) => React.JSX.Element;
12
+ }
13
+
14
+ export function generateUploadDropzone<
15
+ TRouter extends FileRouter
16
+ >() {
17
+ return UploadDropzone as <TEndpoint extends keyof TRouter & string>(
18
+ props: UploadDropzoneProps<TRouter, TEndpoint>
19
+ ) => React.JSX.Element;
20
+ }
package/src/index.ts ADDED
@@ -0,0 +1,22 @@
1
+ export { UploadButton } from "./upload-button.js";
2
+ export { UploadDropzone } from "./upload-dropzone.js";
3
+ export { useUploadbox } from "./use-uploadbox.js";
4
+ export { generateUploadButton, generateUploadDropzone } from "./generate-components.js";
5
+ export { UploadboxProvider, useUploadboxConfig } from "./provider.js";
6
+ export type { UploadboxContextValue } from "./provider.js";
7
+ export { withRetry, DEFAULT_RETRY_CONFIG, isRetryableError } from "./retry.js";
8
+ export { ProgressTracker } from "./progress-tracker.js";
9
+ export { shouldUseMultipart, uploadFileMultipart } from "./multipart.js";
10
+ export type {
11
+ UploadButtonProps,
12
+ UploadDropzoneProps,
13
+ UseUploadboxOpts,
14
+ UseUploadboxReturn,
15
+ UploadedFile,
16
+ UploadProgressEvent,
17
+ EndpointHelper,
18
+ FileUploadStatus,
19
+ FileProgress,
20
+ EnhancedUploadProgressEvent,
21
+ RetryConfig,
22
+ } from "./types.js";
@@ -0,0 +1,189 @@
1
+ import type { RetryConfig } from "./types.js";
2
+ import { withRetry, DEFAULT_RETRY_CONFIG } from "./retry.js";
3
+
4
+ const DEFAULT_API_URL = "/api/uploadbox";
5
+ const MULTIPART_THRESHOLD = 10 * 1024 * 1024; // 10MB
6
+ const DEFAULT_PART_SIZE = 10 * 1024 * 1024; // 10MB
7
+ const MAX_CONCURRENT_PARTS = 4;
8
+
9
+ export function shouldUseMultipart(fileSize: number): boolean {
10
+ return fileSize >= MULTIPART_THRESHOLD;
11
+ }
12
+
13
+ interface MultipartUploadOptions {
14
+ file: File;
15
+ routeKey: string;
16
+ fileInfo: { name: string; size: number; type: string; customMetadata?: Record<string, string>; ttlSeconds?: number };
17
+ apiUrl?: string;
18
+ headers?: Record<string, string>;
19
+ retryConfig?: RetryConfig;
20
+ signal?: AbortSignal;
21
+ onProgress?: (loaded: number) => void;
22
+ onRetry?: () => void;
23
+ }
24
+
25
+ interface MultipartInitResponse {
26
+ fileKey: string;
27
+ uploadId: string;
28
+ parts: { partNumber: number; url: string }[];
29
+ partSize: number;
30
+ totalParts: number;
31
+ }
32
+
33
+ function uploadPartWithXhr(
34
+ url: string,
35
+ blob: Blob,
36
+ onProgress?: (loaded: number) => void,
37
+ signal?: AbortSignal
38
+ ): Promise<string> {
39
+ return new Promise((resolve, reject) => {
40
+ const xhr = new XMLHttpRequest();
41
+ xhr.open("PUT", url);
42
+
43
+ if (signal) {
44
+ signal.addEventListener("abort", () => xhr.abort(), { once: true });
45
+ }
46
+
47
+ xhr.upload.addEventListener("progress", (event) => {
48
+ if (event.lengthComputable) {
49
+ onProgress?.(event.loaded);
50
+ }
51
+ });
52
+
53
+ xhr.addEventListener("load", () => {
54
+ if (xhr.status >= 200 && xhr.status < 300) {
55
+ const etag = xhr.getResponseHeader("ETag");
56
+ if (!etag) {
57
+ reject(new Error("Missing ETag in upload response — check S3 CORS exposeHeaders"));
58
+ return;
59
+ }
60
+ resolve(etag);
61
+ } else {
62
+ reject(new Error(`Part upload failed with status ${xhr.status}`));
63
+ }
64
+ });
65
+
66
+ xhr.addEventListener("error", () => reject(new Error("Part upload failed")));
67
+ xhr.addEventListener("abort", () => reject(new Error("Upload aborted")));
68
+
69
+ xhr.send(blob);
70
+ });
71
+ }
72
+
73
+ export async function uploadFileMultipart(
74
+ options: MultipartUploadOptions
75
+ ): Promise<{ key: string; uploadId: string }> {
76
+ const { file, routeKey, fileInfo, apiUrl = DEFAULT_API_URL, headers, retryConfig, signal, onProgress, onRetry } = options;
77
+
78
+ // 1. Create multipart upload on server
79
+ const initRes = await fetch(apiUrl, {
80
+ method: "POST",
81
+ headers: { "Content-Type": "application/json", ...headers },
82
+ body: JSON.stringify({
83
+ action: "create-multipart",
84
+ routeKey,
85
+ file: fileInfo,
86
+ }),
87
+ signal,
88
+ });
89
+
90
+ if (!initRes.ok) {
91
+ const err = await initRes.json();
92
+ throw new Error(err.message || "Failed to create multipart upload");
93
+ }
94
+
95
+ const initData: MultipartInitResponse = await initRes.json();
96
+ const { fileKey, uploadId, parts, partSize } = initData;
97
+
98
+ // 2. Upload parts with concurrency
99
+ const completedParts: { partNumber: number; etag: string }[] = [];
100
+ const partLoaded = new Map<number, number>();
101
+ let aborted = false;
102
+
103
+ const uploadPart = async (part: { partNumber: number; url: string }) => {
104
+ if (signal?.aborted) throw new Error("Upload aborted");
105
+
106
+ const start = (part.partNumber - 1) * partSize;
107
+ const end = Math.min(start + partSize, file.size);
108
+ const blob = file.slice(start, end);
109
+
110
+ const retry = retryConfig ?? DEFAULT_RETRY_CONFIG;
111
+
112
+ const etag = await withRetry(
113
+ async () => {
114
+ return uploadPartWithXhr(
115
+ part.url,
116
+ blob,
117
+ (loaded) => {
118
+ partLoaded.set(part.partNumber, loaded);
119
+ const totalLoaded = Array.from(partLoaded.values()).reduce((a, b) => a + b, 0);
120
+ onProgress?.(totalLoaded);
121
+ },
122
+ signal
123
+ );
124
+ },
125
+ retry,
126
+ () => {
127
+ partLoaded.set(part.partNumber, 0);
128
+ onRetry?.();
129
+ },
130
+ signal
131
+ );
132
+
133
+ completedParts.push({ partNumber: part.partNumber, etag });
134
+ };
135
+
136
+ // Process parts with concurrency limit
137
+ const queue = [...parts];
138
+ const workers: Promise<void>[] = [];
139
+
140
+ for (let i = 0; i < Math.min(MAX_CONCURRENT_PARTS, queue.length); i++) {
141
+ workers.push(
142
+ (async () => {
143
+ while (queue.length > 0 && !aborted) {
144
+ const part = queue.shift();
145
+ if (!part) break;
146
+ await uploadPart(part);
147
+ }
148
+ })()
149
+ );
150
+ }
151
+
152
+ try {
153
+ await Promise.all(workers);
154
+ } catch (err) {
155
+ aborted = true;
156
+ // Abort the multipart upload on failure
157
+ await fetch(apiUrl, {
158
+ method: "POST",
159
+ headers: { "Content-Type": "application/json", ...headers },
160
+ body: JSON.stringify({
161
+ action: "abort-multipart",
162
+ fileKey,
163
+ uploadId,
164
+ }),
165
+ }).catch(() => {});
166
+ throw err;
167
+ }
168
+
169
+ // 3. Complete multipart upload on server
170
+ const completeRes = await fetch(apiUrl, {
171
+ method: "POST",
172
+ headers: { "Content-Type": "application/json", ...headers },
173
+ body: JSON.stringify({
174
+ action: "complete-multipart",
175
+ routeKey,
176
+ fileKey,
177
+ uploadId,
178
+ parts: completedParts,
179
+ }),
180
+ signal,
181
+ });
182
+
183
+ if (!completeRes.ok) {
184
+ const err = await completeRes.json();
185
+ throw new Error(err.message || "Failed to complete multipart upload");
186
+ }
187
+
188
+ return { key: fileKey, uploadId };
189
+ }