@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.
- package/dist/chunk-CLF77PSE.js +755 -0
- package/dist/chunk-CLF77PSE.js.map +1 -0
- package/dist/{chunk-IVSBLEMY.cjs → chunk-DUTHZ5HA.cjs} +201 -1027
- package/dist/chunk-DUTHZ5HA.cjs.map +1 -0
- package/dist/chunk-PSJYCISB.cjs +765 -0
- package/dist/chunk-PSJYCISB.cjs.map +1 -0
- package/dist/{chunk-CPMMHIQM.js → chunk-W7PHCOE6.js} +190 -1022
- package/dist/chunk-W7PHCOE6.js.map +1 -0
- package/dist/client/index.cjs +71 -140
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.js +70 -140
- package/dist/client/index.js.map +1 -1
- package/dist/client/upload.d.ts +9 -4
- package/dist/client/upload.d.ts.map +1 -1
- package/dist/server/actions/drive.d.ts.map +1 -1
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/controllers/drive.d.ts.map +1 -1
- package/dist/server/express.cjs +15 -14
- package/dist/server/express.cjs.map +1 -1
- package/dist/server/express.js +4 -2
- package/dist/server/express.js.map +1 -1
- package/dist/server/hono.cjs +15 -14
- package/dist/server/hono.cjs.map +1 -1
- package/dist/server/hono.js +4 -2
- package/dist/server/hono.js.map +1 -1
- package/dist/server/index.cjs +28 -27
- package/dist/server/index.js +2 -1
- package/dist/server/tus.d.ts +17 -0
- package/dist/server/tus.d.ts.map +1 -0
- package/dist/server/zod/schemas.d.ts +0 -53
- package/dist/server/zod/schemas.d.ts.map +1 -1
- package/dist/tus-JZQ3PKBS.cjs +16 -0
- package/dist/tus-JZQ3PKBS.cjs.map +1 -0
- package/dist/tus-MNPA66VL.js +3 -0
- package/dist/tus-MNPA66VL.js.map +1 -0
- package/dist/types/server/api.d.ts +1 -1
- package/dist/types/server/api.d.ts.map +1 -1
- package/package.json +4 -1
- package/dist/chunk-CPMMHIQM.js.map +0 -1
- package/dist/chunk-IVSBLEMY.cjs.map +0 -1
package/dist/client/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
-
|
|
468
|
-
|
|
403
|
+
},
|
|
404
|
+
onSuccess() {
|
|
405
|
+
log("success", "Upload completed successfully");
|
|
469
406
|
if (completedItem !== void 0) onComplete?.(completedItem);
|
|
470
|
-
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
-
|
|
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;
|