@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
@@ -3,6 +3,7 @@ export { driveFileSchemaZod } from '../chunk-FJRGKB3U.js';
3
3
  import * as React3 from 'react';
4
4
  import React3__default, { createContext, useContext, useState, useMemo, useCallback, useRef, useEffect } from 'react';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
+ import * as tus from 'tus-js-client';
6
7
  import { Slot } from '@radix-ui/react-slot';
7
8
  import { cva } from 'class-variance-authority';
8
9
  import * as ProgressPrimitive from '@radix-ui/react-progress';
@@ -306,8 +307,6 @@ var useDrive = () => {
306
307
  if (!context) throw new Error("useDrive must be used within a DriveProvider");
307
308
  return context;
308
309
  };
309
-
310
- // src/client/upload.ts
311
310
  var MAX_CONCURRENT_UPLOADS = 2;
312
311
  var getChunkSize = (fileSize) => {
313
312
  if (fileSize < 50 * 1024 * 1024) return 2 * 1024 * 1024;
@@ -341,161 +340,92 @@ var uploadFile = (options) => {
341
340
  onComplete
342
341
  } = options;
343
342
  const id = options.id ?? generateId();
344
- const endpoint = apiEndpoint.replace(/\/$/, "");
345
- const uploadUrl = `${endpoint}?action=upload`;
346
- const controller = new AbortController();
347
- let activeXhr = null;
348
- let driveId;
349
- let completedItem;
350
- let cancelled = false;
351
- const totalBytes = file.size;
343
+ const endpoint = `${apiEndpoint.replace(/\/$/, "")}?action=upload`;
352
344
  const chunkSize = getChunkSize(file.size);
353
- const totalChunks = Math.ceil(file.size / chunkSize);
345
+ const totalChunks = Math.max(1, Math.ceil(file.size / chunkSize));
354
346
  const log = (type, message) => onLog?.({ id, type, message, timestamp: Date.now() });
355
- const emitProgress = (currentChunk, uploadedBytes) => {
356
- const percent = totalBytes === 0 ? 100 : Math.min(100, Math.round(uploadedBytes / totalBytes * 100));
357
- onProgress?.({ id, percent, uploadedBytes, totalBytes, currentChunk, totalChunks });
347
+ let settle = () => {
358
348
  };
359
- const sendChunk = (chunkIndex, formData, chunkStart) => new Promise((resolve) => {
360
- const xhr = new XMLHttpRequest();
361
- activeXhr = xhr;
362
- log("info", `Sending chunk to ${uploadUrl}`);
363
- xhr.upload.onprogress = (e) => {
364
- if (e.lengthComputable) emitProgress(chunkIndex, chunkStart + e.loaded);
365
- };
366
- xhr.onload = () => {
367
- activeXhr = null;
368
- const status = xhr.status;
369
- const text = xhr.responseText;
370
- if (status < 200 || status >= 300) {
371
- log("error", `HTTP ${status}: ${xhr.statusText}`);
372
- try {
373
- const json = JSON.parse(text);
374
- const msg = json.message || json.error?.message || `Status ${status}`;
375
- log("error", `Server response: ${msg}`);
376
- const canRetry = [408, 429, 502, 503, 504].includes(status);
377
- if (canRetry) log("warning", `Error is retryable (status ${status})`);
378
- resolve([false, msg, canRetry]);
379
- } catch {
380
- log("error", `Failed to parse error response: ${text.slice(0, 100)}`);
381
- const isRetryable = status >= 500 || status === 429;
382
- resolve([false, `Server error ${status}: ${text.slice(0, 100)}`, isRetryable]);
383
- }
384
- return;
349
+ const promise = new Promise((resolve) => {
350
+ settle = resolve;
351
+ });
352
+ let completedItem;
353
+ let cancelled = false;
354
+ const metadata = {
355
+ filename: file.name,
356
+ filetype: file.type || "application/octet-stream",
357
+ folderId: folderId || "root"
358
+ };
359
+ if (conflictAction) metadata.conflictAction = conflictAction;
360
+ const upload = new tus.Upload(file, {
361
+ endpoint,
362
+ chunkSize,
363
+ // ** Retry with backoff. On a dropped connection tus first re-checks the server offset (HEAD)
364
+ // ** and resumes from there this is what prevents the "session expired" failures at ~100%.
365
+ retryDelays: [0, 1e3, 3e3, 5e3, 1e4],
366
+ removeFingerprintOnSuccess: true,
367
+ metadata,
368
+ headers: accountId ? { "x-drive-account": accountId } : {},
369
+ onBeforeRequest(req) {
370
+ if (withCredentials) {
371
+ const xhr = req.getUnderlyingObject();
372
+ if (xhr && "withCredentials" in xhr) xhr.withCredentials = true;
385
373
  }
386
- try {
387
- const data = JSON.parse(text);
388
- if (data.status !== 200) {
389
- log("error", `Upload API error: ${data.message || "Unknown error"}`);
390
- resolve([false, data.message || "Upload failed", false]);
391
- return;
392
- }
393
- log("success", `Chunk uploaded successfully`);
394
- resolve([true, data.data, false]);
395
- } catch {
396
- log("error", `Failed to parse response: ${text.slice(0, 100)}`);
397
- resolve([false, "Invalid server response", true]);
374
+ },
375
+ onShouldRetry(err) {
376
+ const status = err?.originalResponse?.getStatus?.() ?? 0;
377
+ if ([400, 401, 403, 413].includes(status)) {
378
+ log("error", `Non-retryable error (status ${status})`);
379
+ return false;
398
380
  }
399
- };
400
- xhr.onerror = () => {
401
- activeXhr = null;
402
- log("error", `Network/Fetch error: request failed`);
403
- resolve([false, `Upload connection failed`, true]);
404
- };
405
- xhr.onabort = () => {
406
- activeXhr = null;
407
- resolve([false, "Cancelled", false]);
408
- };
409
- xhr.open("POST", uploadUrl);
410
- if (accountId) xhr.setRequestHeader("x-drive-account", accountId);
411
- xhr.withCredentials = withCredentials;
412
- xhr.send(formData);
413
- });
414
- const run = async () => {
415
- log("info", `Starting upload: ${file.name} (${file.size} bytes)`);
416
- log("info", `File split into ${totalChunks} chunks of ${chunkSize} bytes each`);
417
- emitProgress(0, 0);
418
- try {
419
- for (let i = 0; i < totalChunks; i++) {
420
- if (controller.signal.aborted) throw new Error("Cancelled");
421
- log("info", `Processing chunk ${i + 1}/${totalChunks}`);
422
- const start = i * chunkSize;
423
- const end = Math.min(start + chunkSize, file.size);
424
- const chunk = file.slice(start, end);
425
- const formData = new FormData();
426
- formData.append("chunk", chunk);
427
- formData.append("chunkIndex", String(i));
428
- formData.append("totalChunks", String(totalChunks));
429
- formData.append("fileName", file.name);
430
- formData.append("fileSize", String(file.size));
431
- formData.append("fileType", file.type);
432
- formData.append("folderId", folderId || "root");
433
- if (conflictAction) formData.append("conflictAction", conflictAction);
434
- if (driveId) formData.append("driveId", driveId);
435
- let attempts = 0;
436
- let success = false;
437
- while (!success && attempts < 3 && !controller.signal.aborted) {
438
- const [ok, result, canRetry] = await sendChunk(i, formData, start);
439
- if (ok) {
440
- success = true;
441
- emitProgress(i + 1, end);
442
- if (result.type === "UPLOAD_STARTED" && result.driveId) {
443
- driveId = result.driveId;
444
- log("success", `Upload session started with ID: ${driveId}`);
445
- } else if (result.type === "UPLOAD_COMPLETE") {
446
- if (result.driveId) driveId = result.driveId;
447
- log("success", `Upload completed successfully`);
448
- completedItem = result.item;
449
- }
450
- } else {
451
- if (result === "Cancelled") throw new Error("Cancelled");
452
- if (!canRetry) {
453
- log("error", `Non-retryable error: ${result}`);
454
- throw new Error(result);
455
- }
456
- attempts++;
457
- log("warning", `Retry attempt ${attempts}/3 after error`);
458
- if (attempts === 3) {
459
- log("error", `Max retry attempts reached: ${result}`);
460
- throw new Error(result);
461
- }
462
- await new Promise((r) => setTimeout(r, 1e3 * attempts));
463
- }
381
+ log("warning", `Upload interrupted (status ${status}), retrying`);
382
+ return true;
383
+ },
384
+ onProgress(bytesUploaded, bytesTotal) {
385
+ const percent = bytesTotal === 0 ? 100 : Math.min(100, Math.round(bytesUploaded / bytesTotal * 100));
386
+ onProgress?.({
387
+ id,
388
+ percent,
389
+ uploadedBytes: bytesUploaded,
390
+ totalBytes: bytesTotal,
391
+ currentChunk: Math.min(totalChunks, Math.floor(bytesUploaded / chunkSize)),
392
+ totalChunks
393
+ });
394
+ },
395
+ onAfterResponse(_req, res) {
396
+ const header = res.getHeader("X-Drive-Item");
397
+ if (header) {
398
+ try {
399
+ completedItem = JSON.parse(decodeURIComponent(header));
400
+ } catch {
464
401
  }
465
- if (!success && controller.signal.aborted) throw new Error("Cancelled");
466
402
  }
467
- log("success", `All ${totalChunks} chunks uploaded successfully`);
468
- emitProgress(totalChunks, totalBytes);
403
+ },
404
+ onSuccess() {
405
+ log("success", "Upload completed successfully");
469
406
  if (completedItem !== void 0) onComplete?.(completedItem);
470
- return { id, status: "complete", driveId: driveId ?? "", file: toDriveFile(completedItem), item: completedItem };
471
- } catch (error) {
472
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
473
- if (errorMessage === "Cancelled") {
474
- log("warning", "Upload cancelled by user");
475
- return { id, status: "cancelled", driveId: driveId ?? null };
476
- }
477
- log("error", `Upload failed: ${errorMessage}`);
478
- return { id, status: "error", driveId: driveId ?? null, error: errorMessage };
407
+ settle({ id, status: "complete", driveId: completedItem?.id ?? "", file: toDriveFile(completedItem), item: completedItem });
408
+ },
409
+ onError(error) {
410
+ const message = error instanceof Error ? error.message : "Upload failed";
411
+ log("error", `Upload failed: ${message}`);
412
+ settle({ id, status: "error", driveId: null, error: message });
479
413
  }
480
- };
414
+ });
481
415
  const cancel = () => {
482
416
  if (cancelled) return;
483
417
  cancelled = true;
484
- controller.abort();
485
- activeXhr?.abort();
486
- if (driveId) {
487
- fetch(`${endpoint}?action=cancel&id=${driveId}`, {
488
- method: "POST",
489
- credentials: withCredentials ? "include" : "same-origin"
490
- }).catch(() => {
491
- });
492
- }
418
+ upload.abort(true).catch(() => {
419
+ });
420
+ settle({ id, status: "cancelled", driveId: null });
493
421
  };
494
422
  if (signal) {
495
423
  if (signal.aborted) cancel();
496
424
  else signal.addEventListener("abort", cancel, { once: true });
497
425
  }
498
- return { id, promise: run(), cancel };
426
+ log("info", `Starting upload: ${file.name} (${file.size} bytes)`);
427
+ upload.start();
428
+ return { id, promise, cancel };
499
429
  };
500
430
  var uploadFiles = (files, options) => {
501
431
  const { concurrency = MAX_CONCURRENT_UPLOADS, onFileComplete, ...base } = options;