@muhgholy/next-drive 3.6.0 → 3.7.1
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/README.md +8 -6
- package/dist/{chunk-UUMAVUTE.cjs → chunk-LMM5IU5U.cjs} +96 -68
- package/dist/chunk-LMM5IU5U.cjs.map +1 -0
- package/dist/{chunk-YYSAE5S2.js → chunk-RQ3YRDNR.js} +95 -67
- package/dist/chunk-RQ3YRDNR.js.map +1 -0
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/server/controllers/drive.d.ts +11 -2
- package/dist/server/controllers/drive.d.ts.map +1 -1
- package/dist/server/express.cjs +11 -11
- package/dist/server/express.js +2 -2
- package/dist/server/index.cjs +13 -13
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-UUMAVUTE.cjs.map +0 -1
- package/dist/chunk-YYSAE5S2.js.map +0 -1
package/README.md
CHANGED
|
@@ -279,18 +279,20 @@ const file = await driveUpload(
|
|
|
279
279
|
{ userId: "123" },
|
|
280
280
|
{
|
|
281
281
|
name: "document.txt",
|
|
282
|
+
mime: "text/plain", // Optional: specify MIME type
|
|
282
283
|
}
|
|
283
284
|
);
|
|
284
285
|
```
|
|
285
286
|
|
|
286
287
|
**Options:**
|
|
287
288
|
|
|
288
|
-
| Option | Type | Required | Description
|
|
289
|
-
| ----------- | ---------------- | -------- |
|
|
290
|
-
| `name` | `string` | Yes | File name with extension
|
|
291
|
-
| `parentId` | `string \| null` | No | Parent folder ID (null or 'root' for root)
|
|
292
|
-
| `accountId` | `string` | No | Storage account ID ('LOCAL' for local storage)
|
|
293
|
-
| `
|
|
289
|
+
| Option | Type | Required | Description |
|
|
290
|
+
| ----------- | ---------------- | -------- | -------------------------------------------------------- |
|
|
291
|
+
| `name` | `string` | Yes | File name with extension |
|
|
292
|
+
| `parentId` | `string \| null` | No | Parent folder ID (null or 'root' for root) |
|
|
293
|
+
| `accountId` | `string` | No | Storage account ID ('LOCAL' for local storage) |
|
|
294
|
+
| `mime` | `string` | No | MIME type (auto-detected from extension if not provided) |
|
|
295
|
+
| `enforce` | `boolean` | No | Bypass quota check (default: false) |
|
|
294
296
|
|
|
295
297
|
### Get Signed URL
|
|
296
298
|
|
|
@@ -5,8 +5,8 @@ var formidable = require('formidable');
|
|
|
5
5
|
var path3 = require('path');
|
|
6
6
|
var fs4 = require('fs');
|
|
7
7
|
var os2 = require('os');
|
|
8
|
+
var crypto2 = require('crypto');
|
|
8
9
|
var mongoose2 = require('mongoose');
|
|
9
|
-
var crypto3 = require('crypto');
|
|
10
10
|
var sharp = require('sharp');
|
|
11
11
|
var zod = require('zod');
|
|
12
12
|
var ffmpeg = require('fluent-ffmpeg');
|
|
@@ -18,8 +18,8 @@ var formidable__default = /*#__PURE__*/_interopDefault(formidable);
|
|
|
18
18
|
var path3__default = /*#__PURE__*/_interopDefault(path3);
|
|
19
19
|
var fs4__default = /*#__PURE__*/_interopDefault(fs4);
|
|
20
20
|
var os2__default = /*#__PURE__*/_interopDefault(os2);
|
|
21
|
+
var crypto2__default = /*#__PURE__*/_interopDefault(crypto2);
|
|
21
22
|
var mongoose2__default = /*#__PURE__*/_interopDefault(mongoose2);
|
|
22
|
-
var crypto3__default = /*#__PURE__*/_interopDefault(crypto3);
|
|
23
23
|
var sharp__default = /*#__PURE__*/_interopDefault(sharp);
|
|
24
24
|
var ffmpeg__default = /*#__PURE__*/_interopDefault(ffmpeg);
|
|
25
25
|
|
|
@@ -151,7 +151,7 @@ var validateMimeType = (mime, allowedTypes) => {
|
|
|
151
151
|
});
|
|
152
152
|
};
|
|
153
153
|
var computeFileHash = (filePath) => new Promise((resolve, reject) => {
|
|
154
|
-
const hash =
|
|
154
|
+
const hash = crypto2__default.default.createHash("sha256");
|
|
155
155
|
const stream = fs4__default.default.createReadStream(filePath);
|
|
156
156
|
stream.on("data", (data) => hash.update(data));
|
|
157
157
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
@@ -194,13 +194,13 @@ var listQuerySchema = zod.z.object({
|
|
|
194
194
|
}),
|
|
195
195
|
afterId: objectIdSchema.optional()
|
|
196
196
|
});
|
|
197
|
-
|
|
197
|
+
zod.z.object({
|
|
198
198
|
id: objectIdSchema,
|
|
199
199
|
token: zod.z.string().optional(),
|
|
200
200
|
q: zod.z.enum(["ultralow", "low", "medium", "high", "normal"]).optional(),
|
|
201
201
|
format: zod.z.enum(["webp", "jpeg", "png"]).optional()
|
|
202
202
|
});
|
|
203
|
-
|
|
203
|
+
zod.z.object({
|
|
204
204
|
id: objectIdSchema,
|
|
205
205
|
size: zod.z.enum(["small", "medium", "large"]).optional().default("medium"),
|
|
206
206
|
token: zod.z.string().optional()
|
|
@@ -857,7 +857,7 @@ var driveGetUrl = (fileId, options) => {
|
|
|
857
857
|
} else {
|
|
858
858
|
expiryTimestamp = Math.floor(Date.now() / 1e3) + expiresIn;
|
|
859
859
|
}
|
|
860
|
-
const signature =
|
|
860
|
+
const signature = crypto2__default.default.createHmac("sha256", secret).update(`${fileId}:${expiryTimestamp}`).digest("hex");
|
|
861
861
|
const token = Buffer.from(`${expiryTimestamp}:${signature}`).toString("base64url");
|
|
862
862
|
return `/api/drive?action=serve&id=${fileId}&token=${token}`;
|
|
863
863
|
};
|
|
@@ -1084,7 +1084,7 @@ var driveUpload = async (source, key, options) => {
|
|
|
1084
1084
|
if (!fs4__default.default.existsSync(tempDir)) {
|
|
1085
1085
|
fs4__default.default.mkdirSync(tempDir, { recursive: true });
|
|
1086
1086
|
}
|
|
1087
|
-
tempFilePath = path3__default.default.join(tempDir, `upload-${
|
|
1087
|
+
tempFilePath = path3__default.default.join(tempDir, `upload-${crypto2__default.default.randomUUID()}.tmp`);
|
|
1088
1088
|
fs4__default.default.writeFileSync(tempFilePath, source);
|
|
1089
1089
|
sourceFilePath = tempFilePath;
|
|
1090
1090
|
fileSize = source.length;
|
|
@@ -1093,7 +1093,7 @@ var driveUpload = async (source, key, options) => {
|
|
|
1093
1093
|
if (!fs4__default.default.existsSync(tempDir)) {
|
|
1094
1094
|
fs4__default.default.mkdirSync(tempDir, { recursive: true });
|
|
1095
1095
|
}
|
|
1096
|
-
tempFilePath = path3__default.default.join(tempDir, `upload-${
|
|
1096
|
+
tempFilePath = path3__default.default.join(tempDir, `upload-${crypto2__default.default.randomUUID()}.tmp`);
|
|
1097
1097
|
const writeStream = fs4__default.default.createWriteStream(tempFilePath);
|
|
1098
1098
|
await new Promise((resolve, reject) => {
|
|
1099
1099
|
source.pipe(writeStream);
|
|
@@ -1106,27 +1106,32 @@ var driveUpload = async (source, key, options) => {
|
|
|
1106
1106
|
fileSize = stats.size;
|
|
1107
1107
|
}
|
|
1108
1108
|
try {
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1109
|
+
let mimeType;
|
|
1110
|
+
if (options.mime) {
|
|
1111
|
+
mimeType = options.mime;
|
|
1112
|
+
} else {
|
|
1113
|
+
const ext = path3__default.default.extname(options.name).toLowerCase();
|
|
1114
|
+
const mimeTypes = {
|
|
1115
|
+
".jpg": "image/jpeg",
|
|
1116
|
+
".jpeg": "image/jpeg",
|
|
1117
|
+
".png": "image/png",
|
|
1118
|
+
".gif": "image/gif",
|
|
1119
|
+
".webp": "image/webp",
|
|
1120
|
+
".svg": "image/svg+xml",
|
|
1121
|
+
".mp4": "video/mp4",
|
|
1122
|
+
".mov": "video/quicktime",
|
|
1123
|
+
".avi": "video/x-msvideo",
|
|
1124
|
+
".pdf": "application/pdf",
|
|
1125
|
+
".doc": "application/msword",
|
|
1126
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
1127
|
+
".xls": "application/vnd.ms-excel",
|
|
1128
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
1129
|
+
".txt": "text/plain",
|
|
1130
|
+
".json": "application/json",
|
|
1131
|
+
".zip": "application/zip"
|
|
1132
|
+
};
|
|
1133
|
+
mimeType = mimeTypes[ext] || "application/octet-stream";
|
|
1134
|
+
}
|
|
1130
1135
|
if (!validateMimeType(mimeType, config.security.allowedMimeTypes)) {
|
|
1131
1136
|
throw new Error(`File type ${mimeType} not allowed`);
|
|
1132
1137
|
}
|
|
@@ -1251,6 +1256,65 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1251
1256
|
res.status(400).json({ status: 400, message: "Missing action query parameter" });
|
|
1252
1257
|
return;
|
|
1253
1258
|
}
|
|
1259
|
+
if (action === "serve" || action === "thumbnail") {
|
|
1260
|
+
try {
|
|
1261
|
+
const { id, token } = req.query;
|
|
1262
|
+
if (!id || typeof id !== "string") {
|
|
1263
|
+
return res.status(400).json({ status: 400, message: "Missing or invalid file ID" });
|
|
1264
|
+
}
|
|
1265
|
+
const drive = await drive_default.findById(id);
|
|
1266
|
+
if (!drive) return res.status(404).json({ status: 404, message: "File not found" });
|
|
1267
|
+
if (config.security.signedUrls?.enabled) {
|
|
1268
|
+
if (!token || typeof token !== "string") {
|
|
1269
|
+
return res.status(401).json({ status: 401, message: "Missing or invalid token" });
|
|
1270
|
+
}
|
|
1271
|
+
try {
|
|
1272
|
+
const decoded = Buffer.from(token, "base64url").toString();
|
|
1273
|
+
const [expiryStr, signature] = decoded.split(":");
|
|
1274
|
+
const expiry = parseInt(expiryStr, 10);
|
|
1275
|
+
if (Date.now() / 1e3 > expiry) {
|
|
1276
|
+
return res.status(401).json({ status: 401, message: "Token expired" });
|
|
1277
|
+
}
|
|
1278
|
+
const { secret } = config.security.signedUrls;
|
|
1279
|
+
const expectedSignature = crypto2__default.default.createHmac("sha256", secret).update(`${id}:${expiry}`).digest("hex");
|
|
1280
|
+
if (signature !== expectedSignature) {
|
|
1281
|
+
return res.status(401).json({ status: 401, message: "Invalid token" });
|
|
1282
|
+
}
|
|
1283
|
+
} catch (err) {
|
|
1284
|
+
return res.status(401).json({ status: 401, message: "Invalid token format" });
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
const itemProvider = drive.provider?.type === "GOOGLE" ? GoogleDriveProvider : LocalStorageProvider;
|
|
1288
|
+
const itemAccountId = drive.storageAccountId ? drive.storageAccountId.toString() : void 0;
|
|
1289
|
+
if (action === "thumbnail") {
|
|
1290
|
+
const stream = await itemProvider.getThumbnail(drive, itemAccountId);
|
|
1291
|
+
res.setHeader("Content-Type", "image/webp");
|
|
1292
|
+
if (config.cors?.enabled) {
|
|
1293
|
+
res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
|
|
1294
|
+
}
|
|
1295
|
+
stream.pipe(res);
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
if (action === "serve") {
|
|
1299
|
+
const { stream, mime, size } = await itemProvider.openStream(drive, itemAccountId);
|
|
1300
|
+
const safeFilename = sanitizeContentDispositionFilename(drive.name);
|
|
1301
|
+
res.setHeader("Content-Disposition", `inline; filename="${safeFilename}"`);
|
|
1302
|
+
res.setHeader("Content-Type", mime);
|
|
1303
|
+
if (config.cors?.enabled) {
|
|
1304
|
+
res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
|
|
1305
|
+
}
|
|
1306
|
+
if (size) res.setHeader("Content-Length", size);
|
|
1307
|
+
stream.pipe(res);
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
} catch (error) {
|
|
1311
|
+
console.error(`[next-drive] Error in ${action}:`, error);
|
|
1312
|
+
return res.status(500).json({
|
|
1313
|
+
status: 500,
|
|
1314
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1254
1318
|
try {
|
|
1255
1319
|
const information = await getDriveInformation(req);
|
|
1256
1320
|
const { key: owner } = information;
|
|
@@ -1485,7 +1549,7 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1485
1549
|
cleanupTempFiles(files);
|
|
1486
1550
|
return res.status(413).json({ status: 413, message: "Storage quota exceeded" });
|
|
1487
1551
|
}
|
|
1488
|
-
currentUploadId =
|
|
1552
|
+
currentUploadId = crypto2__default.default.randomUUID();
|
|
1489
1553
|
const uploadDir = path3__default.default.join(tempBaseDir, currentUploadId);
|
|
1490
1554
|
fs4__default.default.mkdirSync(uploadDir, { recursive: true });
|
|
1491
1555
|
const metadata = {
|
|
@@ -1738,42 +1802,6 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1738
1802
|
return res.status(200).json({ status: 200, message: "Renamed", data: { item } });
|
|
1739
1803
|
}
|
|
1740
1804
|
// ** 9. THUMBNAIL **
|
|
1741
|
-
case "thumbnail": {
|
|
1742
|
-
const thumbQuery = thumbnailQuerySchema.safeParse(req.query);
|
|
1743
|
-
if (!thumbQuery.success) return res.status(400).json({ status: 400, message: "Invalid params" });
|
|
1744
|
-
const { id } = thumbQuery.data;
|
|
1745
|
-
const drive = await drive_default.findById(id);
|
|
1746
|
-
if (!drive) return res.status(404).json({ status: 404, message: "Not found" });
|
|
1747
|
-
const itemProvider = drive.provider?.type === "GOOGLE" ? GoogleDriveProvider : LocalStorageProvider;
|
|
1748
|
-
const itemAccountId = drive.storageAccountId ? drive.storageAccountId.toString() : void 0;
|
|
1749
|
-
const stream = await itemProvider.getThumbnail(drive, itemAccountId);
|
|
1750
|
-
res.setHeader("Content-Type", "image/webp");
|
|
1751
|
-
if (config.cors?.enabled) {
|
|
1752
|
-
res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
|
|
1753
|
-
}
|
|
1754
|
-
stream.pipe(res);
|
|
1755
|
-
return;
|
|
1756
|
-
}
|
|
1757
|
-
// ** 10. SERVE / DOWNLOAD **
|
|
1758
|
-
case "serve": {
|
|
1759
|
-
const serveQuery = serveQuerySchema.safeParse(req.query);
|
|
1760
|
-
if (!serveQuery.success) return res.status(400).json({ status: 400, message: "Invalid params" });
|
|
1761
|
-
const { id } = serveQuery.data;
|
|
1762
|
-
const drive = await drive_default.findById(id);
|
|
1763
|
-
if (!drive) return res.status(404).json({ status: 404, message: "Not found" });
|
|
1764
|
-
const itemProvider = drive.provider?.type === "GOOGLE" ? GoogleDriveProvider : LocalStorageProvider;
|
|
1765
|
-
const itemAccountId = drive.storageAccountId ? drive.storageAccountId.toString() : void 0;
|
|
1766
|
-
const { stream, mime, size } = await itemProvider.openStream(drive, itemAccountId);
|
|
1767
|
-
const safeFilename = sanitizeContentDispositionFilename(drive.name);
|
|
1768
|
-
res.setHeader("Content-Disposition", `inline; filename="${safeFilename}"`);
|
|
1769
|
-
res.setHeader("Content-Type", mime);
|
|
1770
|
-
if (config.cors?.enabled) {
|
|
1771
|
-
res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
|
|
1772
|
-
}
|
|
1773
|
-
if (size) res.setHeader("Content-Length", size);
|
|
1774
|
-
stream.pipe(res);
|
|
1775
|
-
return;
|
|
1776
|
-
}
|
|
1777
1805
|
default:
|
|
1778
1806
|
res.status(400).json({ status: 400, message: `Unknown action: ${action}` });
|
|
1779
1807
|
}
|
|
@@ -1795,5 +1823,5 @@ exports.driveReadFile = driveReadFile;
|
|
|
1795
1823
|
exports.driveUpload = driveUpload;
|
|
1796
1824
|
exports.getDriveConfig = getDriveConfig;
|
|
1797
1825
|
exports.getDriveInformation = getDriveInformation;
|
|
1798
|
-
//# sourceMappingURL=chunk-
|
|
1799
|
-
//# sourceMappingURL=chunk-
|
|
1826
|
+
//# sourceMappingURL=chunk-LMM5IU5U.cjs.map
|
|
1827
|
+
//# sourceMappingURL=chunk-LMM5IU5U.cjs.map
|