@muhgholy/next-drive 4.23.24 → 4.23.26

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 (40) hide show
  1. package/dist/chunk-CLF77PSE.js +755 -0
  2. package/dist/chunk-CLF77PSE.js.map +1 -0
  3. package/dist/{chunk-IVSBLEMY.cjs → chunk-DUTHZ5HA.cjs} +201 -1027
  4. package/dist/chunk-DUTHZ5HA.cjs.map +1 -0
  5. package/dist/chunk-PSJYCISB.cjs +765 -0
  6. package/dist/chunk-PSJYCISB.cjs.map +1 -0
  7. package/dist/{chunk-CPMMHIQM.js → chunk-W7PHCOE6.js} +190 -1022
  8. package/dist/chunk-W7PHCOE6.js.map +1 -0
  9. package/dist/client/index.cjs +71 -140
  10. package/dist/client/index.cjs.map +1 -1
  11. package/dist/client/index.js +70 -140
  12. package/dist/client/index.js.map +1 -1
  13. package/dist/client/upload.d.ts +9 -4
  14. package/dist/client/upload.d.ts.map +1 -1
  15. package/dist/server/actions/drive.d.ts.map +1 -1
  16. package/dist/server/config.d.ts.map +1 -1
  17. package/dist/server/controllers/drive.d.ts.map +1 -1
  18. package/dist/server/express.cjs +15 -14
  19. package/dist/server/express.cjs.map +1 -1
  20. package/dist/server/express.js +4 -2
  21. package/dist/server/express.js.map +1 -1
  22. package/dist/server/hono.cjs +15 -14
  23. package/dist/server/hono.cjs.map +1 -1
  24. package/dist/server/hono.js +4 -2
  25. package/dist/server/hono.js.map +1 -1
  26. package/dist/server/index.cjs +28 -27
  27. package/dist/server/index.js +2 -1
  28. package/dist/server/tus.d.ts +17 -0
  29. package/dist/server/tus.d.ts.map +1 -0
  30. package/dist/server/zod/schemas.d.ts +0 -53
  31. package/dist/server/zod/schemas.d.ts.map +1 -1
  32. package/dist/tus-JZQ3PKBS.cjs +16 -0
  33. package/dist/tus-JZQ3PKBS.cjs.map +1 -0
  34. package/dist/tus-MNPA66VL.js +3 -0
  35. package/dist/tus-MNPA66VL.js.map +1 -0
  36. package/dist/types/server/api.d.ts +1 -1
  37. package/dist/types/server/api.d.ts.map +1 -1
  38. package/package.json +4 -1
  39. package/dist/chunk-CPMMHIQM.js.map +0 -1
  40. package/dist/chunk-IVSBLEMY.cjs.map +0 -1
@@ -4,6 +4,7 @@ var chunkHQTC3554_cjs = require('../chunk-HQTC3554.cjs');
4
4
  var chunkA65ZAA2Z_cjs = require('../chunk-A65ZAA2Z.cjs');
5
5
  var React3 = require('react');
6
6
  var jsxRuntime = require('react/jsx-runtime');
7
+ var tus = require('tus-js-client');
7
8
  var reactSlot = require('@radix-ui/react-slot');
8
9
  var classVarianceAuthority = require('class-variance-authority');
9
10
  var ProgressPrimitive = require('@radix-ui/react-progress');
@@ -37,6 +38,7 @@ function _interopNamespace(e) {
37
38
  }
38
39
 
39
40
  var React3__namespace = /*#__PURE__*/_interopNamespace(React3);
41
+ var tus__namespace = /*#__PURE__*/_interopNamespace(tus);
40
42
  var ProgressPrimitive__namespace = /*#__PURE__*/_interopNamespace(ProgressPrimitive);
41
43
  var SheetPrimitive__namespace = /*#__PURE__*/_interopNamespace(SheetPrimitive);
42
44
  var DropdownMenuPrimitive__namespace = /*#__PURE__*/_interopNamespace(DropdownMenuPrimitive);
@@ -332,8 +334,6 @@ var useDrive = () => {
332
334
  if (!context) throw new Error("useDrive must be used within a DriveProvider");
333
335
  return context;
334
336
  };
335
-
336
- // src/client/upload.ts
337
337
  var MAX_CONCURRENT_UPLOADS = 2;
338
338
  var getChunkSize = (fileSize) => {
339
339
  if (fileSize < 50 * 1024 * 1024) return 2 * 1024 * 1024;
@@ -367,161 +367,92 @@ var uploadFile = (options) => {
367
367
  onComplete
368
368
  } = options;
369
369
  const id = options.id ?? generateId();
370
- const endpoint = apiEndpoint.replace(/\/$/, "");
371
- const uploadUrl = `${endpoint}?action=upload`;
372
- const controller = new AbortController();
373
- let activeXhr = null;
374
- let driveId;
375
- let completedItem;
376
- let cancelled = false;
377
- const totalBytes = file.size;
370
+ const endpoint = `${apiEndpoint.replace(/\/$/, "")}?action=upload`;
378
371
  const chunkSize = getChunkSize(file.size);
379
- const totalChunks = Math.ceil(file.size / chunkSize);
372
+ const totalChunks = Math.max(1, Math.ceil(file.size / chunkSize));
380
373
  const log = (type, message) => onLog?.({ id, type, message, timestamp: Date.now() });
381
- const emitProgress = (currentChunk, uploadedBytes) => {
382
- const percent = totalBytes === 0 ? 100 : Math.min(100, Math.round(uploadedBytes / totalBytes * 100));
383
- onProgress?.({ id, percent, uploadedBytes, totalBytes, currentChunk, totalChunks });
374
+ let settle = () => {
384
375
  };
385
- const sendChunk = (chunkIndex, formData, chunkStart) => new Promise((resolve) => {
386
- const xhr = new XMLHttpRequest();
387
- activeXhr = xhr;
388
- log("info", `Sending chunk to ${uploadUrl}`);
389
- xhr.upload.onprogress = (e) => {
390
- if (e.lengthComputable) emitProgress(chunkIndex, chunkStart + e.loaded);
391
- };
392
- xhr.onload = () => {
393
- activeXhr = null;
394
- const status = xhr.status;
395
- const text = xhr.responseText;
396
- if (status < 200 || status >= 300) {
397
- log("error", `HTTP ${status}: ${xhr.statusText}`);
398
- try {
399
- const json = JSON.parse(text);
400
- const msg = json.message || json.error?.message || `Status ${status}`;
401
- log("error", `Server response: ${msg}`);
402
- const canRetry = [408, 429, 502, 503, 504].includes(status);
403
- if (canRetry) log("warning", `Error is retryable (status ${status})`);
404
- resolve([false, msg, canRetry]);
405
- } catch {
406
- log("error", `Failed to parse error response: ${text.slice(0, 100)}`);
407
- const isRetryable = status >= 500 || status === 429;
408
- resolve([false, `Server error ${status}: ${text.slice(0, 100)}`, isRetryable]);
409
- }
410
- return;
376
+ const promise = new Promise((resolve) => {
377
+ settle = resolve;
378
+ });
379
+ let completedItem;
380
+ let cancelled = false;
381
+ const metadata = {
382
+ filename: file.name,
383
+ filetype: file.type || "application/octet-stream",
384
+ folderId: folderId || "root"
385
+ };
386
+ if (conflictAction) metadata.conflictAction = conflictAction;
387
+ const upload = new tus__namespace.Upload(file, {
388
+ endpoint,
389
+ chunkSize,
390
+ // ** Retry with backoff. On a dropped connection tus first re-checks the server offset (HEAD)
391
+ // ** and resumes from there this is what prevents the "session expired" failures at ~100%.
392
+ retryDelays: [0, 1e3, 3e3, 5e3, 1e4],
393
+ removeFingerprintOnSuccess: true,
394
+ metadata,
395
+ headers: accountId ? { "x-drive-account": accountId } : {},
396
+ onBeforeRequest(req) {
397
+ if (withCredentials) {
398
+ const xhr = req.getUnderlyingObject();
399
+ if (xhr && "withCredentials" in xhr) xhr.withCredentials = true;
411
400
  }
412
- try {
413
- const data = JSON.parse(text);
414
- if (data.status !== 200) {
415
- log("error", `Upload API error: ${data.message || "Unknown error"}`);
416
- resolve([false, data.message || "Upload failed", false]);
417
- return;
418
- }
419
- log("success", `Chunk uploaded successfully`);
420
- resolve([true, data.data, false]);
421
- } catch {
422
- log("error", `Failed to parse response: ${text.slice(0, 100)}`);
423
- resolve([false, "Invalid server response", true]);
401
+ },
402
+ onShouldRetry(err) {
403
+ const status = err?.originalResponse?.getStatus?.() ?? 0;
404
+ if ([400, 401, 403, 413].includes(status)) {
405
+ log("error", `Non-retryable error (status ${status})`);
406
+ return false;
424
407
  }
425
- };
426
- xhr.onerror = () => {
427
- activeXhr = null;
428
- log("error", `Network/Fetch error: request failed`);
429
- resolve([false, `Upload connection failed`, true]);
430
- };
431
- xhr.onabort = () => {
432
- activeXhr = null;
433
- resolve([false, "Cancelled", false]);
434
- };
435
- xhr.open("POST", uploadUrl);
436
- if (accountId) xhr.setRequestHeader("x-drive-account", accountId);
437
- xhr.withCredentials = withCredentials;
438
- xhr.send(formData);
439
- });
440
- const run = async () => {
441
- log("info", `Starting upload: ${file.name} (${file.size} bytes)`);
442
- log("info", `File split into ${totalChunks} chunks of ${chunkSize} bytes each`);
443
- emitProgress(0, 0);
444
- try {
445
- for (let i = 0; i < totalChunks; i++) {
446
- if (controller.signal.aborted) throw new Error("Cancelled");
447
- log("info", `Processing chunk ${i + 1}/${totalChunks}`);
448
- const start = i * chunkSize;
449
- const end = Math.min(start + chunkSize, file.size);
450
- const chunk = file.slice(start, end);
451
- const formData = new FormData();
452
- formData.append("chunk", chunk);
453
- formData.append("chunkIndex", String(i));
454
- formData.append("totalChunks", String(totalChunks));
455
- formData.append("fileName", file.name);
456
- formData.append("fileSize", String(file.size));
457
- formData.append("fileType", file.type);
458
- formData.append("folderId", folderId || "root");
459
- if (conflictAction) formData.append("conflictAction", conflictAction);
460
- if (driveId) formData.append("driveId", driveId);
461
- let attempts = 0;
462
- let success = false;
463
- while (!success && attempts < 3 && !controller.signal.aborted) {
464
- const [ok, result, canRetry] = await sendChunk(i, formData, start);
465
- if (ok) {
466
- success = true;
467
- emitProgress(i + 1, end);
468
- if (result.type === "UPLOAD_STARTED" && result.driveId) {
469
- driveId = result.driveId;
470
- log("success", `Upload session started with ID: ${driveId}`);
471
- } else if (result.type === "UPLOAD_COMPLETE") {
472
- if (result.driveId) driveId = result.driveId;
473
- log("success", `Upload completed successfully`);
474
- completedItem = result.item;
475
- }
476
- } else {
477
- if (result === "Cancelled") throw new Error("Cancelled");
478
- if (!canRetry) {
479
- log("error", `Non-retryable error: ${result}`);
480
- throw new Error(result);
481
- }
482
- attempts++;
483
- log("warning", `Retry attempt ${attempts}/3 after error`);
484
- if (attempts === 3) {
485
- log("error", `Max retry attempts reached: ${result}`);
486
- throw new Error(result);
487
- }
488
- await new Promise((r) => setTimeout(r, 1e3 * attempts));
489
- }
408
+ log("warning", `Upload interrupted (status ${status}), retrying`);
409
+ return true;
410
+ },
411
+ onProgress(bytesUploaded, bytesTotal) {
412
+ const percent = bytesTotal === 0 ? 100 : Math.min(100, Math.round(bytesUploaded / bytesTotal * 100));
413
+ onProgress?.({
414
+ id,
415
+ percent,
416
+ uploadedBytes: bytesUploaded,
417
+ totalBytes: bytesTotal,
418
+ currentChunk: Math.min(totalChunks, Math.floor(bytesUploaded / chunkSize)),
419
+ totalChunks
420
+ });
421
+ },
422
+ onAfterResponse(_req, res) {
423
+ const header = res.getHeader("X-Drive-Item");
424
+ if (header) {
425
+ try {
426
+ completedItem = JSON.parse(decodeURIComponent(header));
427
+ } catch {
490
428
  }
491
- if (!success && controller.signal.aborted) throw new Error("Cancelled");
492
429
  }
493
- log("success", `All ${totalChunks} chunks uploaded successfully`);
494
- emitProgress(totalChunks, totalBytes);
430
+ },
431
+ onSuccess() {
432
+ log("success", "Upload completed successfully");
495
433
  if (completedItem !== void 0) onComplete?.(completedItem);
496
- return { id, status: "complete", driveId: driveId ?? "", file: toDriveFile(completedItem), item: completedItem };
497
- } catch (error) {
498
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
499
- if (errorMessage === "Cancelled") {
500
- log("warning", "Upload cancelled by user");
501
- return { id, status: "cancelled", driveId: driveId ?? null };
502
- }
503
- log("error", `Upload failed: ${errorMessage}`);
504
- return { id, status: "error", driveId: driveId ?? null, error: errorMessage };
434
+ settle({ id, status: "complete", driveId: completedItem?.id ?? "", file: toDriveFile(completedItem), item: completedItem });
435
+ },
436
+ onError(error) {
437
+ const message = error instanceof Error ? error.message : "Upload failed";
438
+ log("error", `Upload failed: ${message}`);
439
+ settle({ id, status: "error", driveId: null, error: message });
505
440
  }
506
- };
441
+ });
507
442
  const cancel = () => {
508
443
  if (cancelled) return;
509
444
  cancelled = true;
510
- controller.abort();
511
- activeXhr?.abort();
512
- if (driveId) {
513
- fetch(`${endpoint}?action=cancel&id=${driveId}`, {
514
- method: "POST",
515
- credentials: withCredentials ? "include" : "same-origin"
516
- }).catch(() => {
517
- });
518
- }
445
+ upload.abort(true).catch(() => {
446
+ });
447
+ settle({ id, status: "cancelled", driveId: null });
519
448
  };
520
449
  if (signal) {
521
450
  if (signal.aborted) cancel();
522
451
  else signal.addEventListener("abort", cancel, { once: true });
523
452
  }
524
- return { id, promise: run(), cancel };
453
+ log("info", `Starting upload: ${file.name} (${file.size} bytes)`);
454
+ upload.start();
455
+ return { id, promise, cancel };
525
456
  };
526
457
  var uploadFiles = (files, options) => {
527
458
  const { concurrency = MAX_CONCURRENT_UPLOADS, onFileComplete, ...base } = options;