@muhgholy/next-drive 1.7.0 → 2.0.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.
- package/dist/{chunk-D6GHXEOY.js → chunk-LJCUIGLV.js} +95 -45
- package/dist/chunk-LJCUIGLV.js.map +1 -0
- package/dist/client/components/drive/sidebar.d.ts.map +1 -1
- package/dist/client/index.js +14 -3
- package/dist/client/index.js.map +1 -1
- package/dist/server/express.js +2 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/zod/schemas.d.ts +1 -1
- package/dist/server/zod/schemas.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-D6GHXEOY.js.map +0 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { __require } from './chunk-DGUM43GV.js';
|
|
2
2
|
import formidable from 'formidable';
|
|
3
3
|
import path3 from 'path';
|
|
4
|
-
import
|
|
4
|
+
import fs5 from 'fs';
|
|
5
|
+
import os from 'os';
|
|
5
6
|
import mongoose2, { Schema, isValidObjectId } from 'mongoose';
|
|
6
7
|
import crypto2 from 'crypto';
|
|
7
8
|
import sharp from 'sharp';
|
|
@@ -138,7 +139,7 @@ var validateMimeType = (mime, allowedTypes) => {
|
|
|
138
139
|
};
|
|
139
140
|
var computeFileHash = (filePath) => new Promise((resolve, reject) => {
|
|
140
141
|
const hash = crypto2.createHash("sha256");
|
|
141
|
-
const stream =
|
|
142
|
+
const stream = fs5.createReadStream(filePath);
|
|
142
143
|
stream.on("data", (data) => hash.update(data));
|
|
143
144
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
144
145
|
stream.on("error", reject);
|
|
@@ -224,8 +225,8 @@ var searchQuerySchema = z.object({
|
|
|
224
225
|
z.object({
|
|
225
226
|
id: objectIdSchema
|
|
226
227
|
});
|
|
227
|
-
z.object({
|
|
228
|
-
id:
|
|
228
|
+
var cancelQuerySchema = z.object({
|
|
229
|
+
id: z.string().uuid()
|
|
229
230
|
});
|
|
230
231
|
z.object({
|
|
231
232
|
days: z.number().int().min(1).max(365).optional()
|
|
@@ -258,11 +259,11 @@ var LocalStorageProvider = {
|
|
|
258
259
|
openStream: async (item, accountId) => {
|
|
259
260
|
if (item.information.type !== "FILE") throw new Error("Cannot stream folder");
|
|
260
261
|
const filePath = path3.join(getDriveConfig().storage.path, item.information.path);
|
|
261
|
-
if (!
|
|
262
|
+
if (!fs5.existsSync(filePath)) {
|
|
262
263
|
throw new Error("File not found on disk");
|
|
263
264
|
}
|
|
264
|
-
const stat =
|
|
265
|
-
const stream =
|
|
265
|
+
const stat = fs5.statSync(filePath);
|
|
266
|
+
const stream = fs5.createReadStream(filePath);
|
|
266
267
|
return {
|
|
267
268
|
stream,
|
|
268
269
|
mime: item.information.mime,
|
|
@@ -274,11 +275,11 @@ var LocalStorageProvider = {
|
|
|
274
275
|
const storagePath = getDriveConfig().storage.path;
|
|
275
276
|
const originalPath = path3.join(storagePath, item.information.path);
|
|
276
277
|
const thumbPath = path3.join(storagePath, "cache", "thumbnails", `${item._id.toString()}.webp`);
|
|
277
|
-
if (!
|
|
278
|
-
if (
|
|
279
|
-
return
|
|
278
|
+
if (!fs5.existsSync(originalPath)) throw new Error("Original file not found");
|
|
279
|
+
if (fs5.existsSync(thumbPath)) {
|
|
280
|
+
return fs5.createReadStream(thumbPath);
|
|
280
281
|
}
|
|
281
|
-
if (!
|
|
282
|
+
if (!fs5.existsSync(path3.dirname(thumbPath))) fs5.mkdirSync(path3.dirname(thumbPath), { recursive: true });
|
|
282
283
|
if (item.information.mime.startsWith("image/")) {
|
|
283
284
|
await sharp(originalPath).resize(300, 300, { fit: "inside" }).toFormat("webp", { quality: 80 }).toFile(thumbPath);
|
|
284
285
|
} else if (item.information.mime.startsWith("video/")) {
|
|
@@ -293,7 +294,7 @@ var LocalStorageProvider = {
|
|
|
293
294
|
} else {
|
|
294
295
|
throw new Error("Unsupported mime type for thumbnail");
|
|
295
296
|
}
|
|
296
|
-
return
|
|
297
|
+
return fs5.createReadStream(thumbPath);
|
|
297
298
|
},
|
|
298
299
|
createFolder: async (name, parentId, owner, accountId) => {
|
|
299
300
|
const getNextOrderValue = async (owner2) => {
|
|
@@ -316,8 +317,8 @@ var LocalStorageProvider = {
|
|
|
316
317
|
if (drive.information.type !== "FILE") throw new Error("Invalid drive type");
|
|
317
318
|
const destPath = path3.join(getDriveConfig().storage.path, drive.information.path);
|
|
318
319
|
const dirPath = path3.dirname(destPath);
|
|
319
|
-
if (!
|
|
320
|
-
|
|
320
|
+
if (!fs5.existsSync(dirPath)) fs5.mkdirSync(dirPath, { recursive: true });
|
|
321
|
+
fs5.renameSync(filePath, destPath);
|
|
321
322
|
drive.status = "READY";
|
|
322
323
|
drive.information.hash = await computeFileHash(destPath);
|
|
323
324
|
if (drive.information.mime.startsWith("image/")) {
|
|
@@ -346,8 +347,8 @@ var LocalStorageProvider = {
|
|
|
346
347
|
if (item.information.type === "FILE" && item.information.path) {
|
|
347
348
|
const fullPath = path3.join(getDriveConfig().storage.path, item.information.path);
|
|
348
349
|
const dirPath = path3.dirname(fullPath);
|
|
349
|
-
if (
|
|
350
|
-
|
|
350
|
+
if (fs5.existsSync(dirPath)) {
|
|
351
|
+
fs5.rmSync(dirPath, { recursive: true, force: true });
|
|
351
352
|
}
|
|
352
353
|
}
|
|
353
354
|
}
|
|
@@ -651,7 +652,7 @@ var GoogleDriveProvider = {
|
|
|
651
652
|
},
|
|
652
653
|
media: {
|
|
653
654
|
mimeType: drive.information.mime,
|
|
654
|
-
body:
|
|
655
|
+
body: fs5.createReadStream(filePath)
|
|
655
656
|
},
|
|
656
657
|
fields: "id, name, mimeType, webViewLink, iconLink, thumbnailLink, size"
|
|
657
658
|
});
|
|
@@ -841,7 +842,7 @@ var driveFilePath = async (file) => {
|
|
|
841
842
|
const providerType = drive.provider?.type || "LOCAL";
|
|
842
843
|
if (providerType === "LOCAL") {
|
|
843
844
|
const filePath = path3.join(STORAGE_PATH, drive.information.path);
|
|
844
|
-
if (!
|
|
845
|
+
if (!fs5.existsSync(filePath)) {
|
|
845
846
|
throw new Error(`Local file not found on disk: ${filePath}`);
|
|
846
847
|
}
|
|
847
848
|
return Object.freeze({
|
|
@@ -856,8 +857,8 @@ var driveFilePath = async (file) => {
|
|
|
856
857
|
const libraryDir = path3.join(STORAGE_PATH, "library", "google");
|
|
857
858
|
const fileName = `${drive._id}${path3.extname(drive.name)}`;
|
|
858
859
|
const cachedFilePath = path3.join(libraryDir, fileName);
|
|
859
|
-
if (
|
|
860
|
-
const stats =
|
|
860
|
+
if (fs5.existsSync(cachedFilePath)) {
|
|
861
|
+
const stats = fs5.statSync(cachedFilePath);
|
|
861
862
|
if (stats.size === drive.information.sizeInBytes) {
|
|
862
863
|
return Object.freeze({
|
|
863
864
|
path: cachedFilePath,
|
|
@@ -867,22 +868,22 @@ var driveFilePath = async (file) => {
|
|
|
867
868
|
provider: "GOOGLE"
|
|
868
869
|
});
|
|
869
870
|
}
|
|
870
|
-
|
|
871
|
+
fs5.unlinkSync(cachedFilePath);
|
|
871
872
|
}
|
|
872
873
|
const accountId = drive.storageAccountId?.toString();
|
|
873
874
|
const { stream } = await GoogleDriveProvider.openStream(drive, accountId);
|
|
874
|
-
if (!
|
|
875
|
-
|
|
875
|
+
if (!fs5.existsSync(libraryDir)) {
|
|
876
|
+
fs5.mkdirSync(libraryDir, { recursive: true });
|
|
876
877
|
}
|
|
877
878
|
const tempPath = `${cachedFilePath}.tmp`;
|
|
878
|
-
const writeStream =
|
|
879
|
+
const writeStream = fs5.createWriteStream(tempPath);
|
|
879
880
|
await new Promise((resolve, reject) => {
|
|
880
881
|
stream.pipe(writeStream);
|
|
881
882
|
writeStream.on("finish", resolve);
|
|
882
883
|
writeStream.on("error", reject);
|
|
883
884
|
stream.on("error", reject);
|
|
884
885
|
});
|
|
885
|
-
|
|
886
|
+
fs5.renameSync(tempPath, cachedFilePath);
|
|
886
887
|
return Object.freeze({
|
|
887
888
|
path: cachedFilePath,
|
|
888
889
|
name: drive.name,
|
|
@@ -978,9 +979,11 @@ var driveAPIHandler = async (req, res) => {
|
|
|
978
979
|
const { provider: provider2 } = req.query;
|
|
979
980
|
if (provider2 === "GOOGLE") {
|
|
980
981
|
const { clientId, clientSecret, redirectUri } = config.storage?.google || {};
|
|
981
|
-
if (!clientId || !clientSecret) return res.status(500).json({ status: 500, message: "Google not configured" });
|
|
982
|
+
if (!clientId || !clientSecret || !redirectUri) return res.status(500).json({ status: 500, message: "Google not configured" });
|
|
982
983
|
const { google: google2 } = __require("googleapis");
|
|
983
|
-
const
|
|
984
|
+
const callbackUri = new URL(redirectUri);
|
|
985
|
+
callbackUri.searchParams.set("action", "callback");
|
|
986
|
+
const oAuth2Client = new google2.auth.OAuth2(clientId, clientSecret, callbackUri.toString());
|
|
984
987
|
const state = Buffer.from(JSON.stringify({ owner })).toString("base64");
|
|
985
988
|
const url = oAuth2Client.generateAuthUrl({
|
|
986
989
|
access_type: "offline",
|
|
@@ -997,8 +1000,11 @@ var driveAPIHandler = async (req, res) => {
|
|
|
997
1000
|
const { code, state } = req.query;
|
|
998
1001
|
if (!code) return res.status(400).json({ status: 400, message: "Missing code" });
|
|
999
1002
|
const { clientId, clientSecret, redirectUri } = config.storage?.google || {};
|
|
1003
|
+
if (!clientId || !clientSecret || !redirectUri) return res.status(500).json({ status: 500, message: "Google not configured" });
|
|
1000
1004
|
const { google: google2 } = __require("googleapis");
|
|
1001
|
-
const
|
|
1005
|
+
const callbackUri = new URL(redirectUri);
|
|
1006
|
+
callbackUri.searchParams.set("action", "callback");
|
|
1007
|
+
const oAuth2Client = new google2.auth.OAuth2(clientId, clientSecret, callbackUri.toString());
|
|
1002
1008
|
const { tokens } = await oAuth2Client.getToken(code);
|
|
1003
1009
|
oAuth2Client.setCredentials(tokens);
|
|
1004
1010
|
const oauth2 = google2.oauth2({ version: "v2", auth: oAuth2Client });
|
|
@@ -1022,7 +1028,35 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1022
1028
|
});
|
|
1023
1029
|
}
|
|
1024
1030
|
res.setHeader("Content-Type", "text/html");
|
|
1025
|
-
return res.send(
|
|
1031
|
+
return res.send(`<!DOCTYPE html>
|
|
1032
|
+
<html>
|
|
1033
|
+
<head><title>Authentication Complete</title></head>
|
|
1034
|
+
<body>
|
|
1035
|
+
<p>Authentication successful! This window will close automatically.</p>
|
|
1036
|
+
<script>
|
|
1037
|
+
(function() {
|
|
1038
|
+
// Method 1: postMessage for popup windows
|
|
1039
|
+
if (window.opener) {
|
|
1040
|
+
try {
|
|
1041
|
+
window.opener.postMessage('oauth-success', '*');
|
|
1042
|
+
} catch (e) {}
|
|
1043
|
+
}
|
|
1044
|
+
// Method 2: localStorage event for new tabs (macOS fullscreen mode)
|
|
1045
|
+
try {
|
|
1046
|
+
localStorage.setItem('next-drive-oauth-success', Date.now().toString());
|
|
1047
|
+
localStorage.removeItem('next-drive-oauth-success');
|
|
1048
|
+
} catch (e) {}
|
|
1049
|
+
// Close the window/tab
|
|
1050
|
+
window.close();
|
|
1051
|
+
// Fallback: If window.close() doesn't work (some browsers block it),
|
|
1052
|
+
// show a message to manually close
|
|
1053
|
+
setTimeout(function() {
|
|
1054
|
+
document.body.innerHTML = '<p style="font-family: system-ui; text-align: center; margin-top: 50px;">Authentication successful!<br>You can close this tab now.</p>';
|
|
1055
|
+
}, 500);
|
|
1056
|
+
})();
|
|
1057
|
+
</script>
|
|
1058
|
+
</body>
|
|
1059
|
+
</html>`);
|
|
1026
1060
|
}
|
|
1027
1061
|
case "listAccounts": {
|
|
1028
1062
|
const accounts = await account_default.find({ owner });
|
|
@@ -1108,13 +1142,14 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1108
1142
|
// ** 3. UPLOAD **
|
|
1109
1143
|
case "upload": {
|
|
1110
1144
|
if (req.method !== "POST") return res.status(405).json({ status: 405, message: "Only POST allowed" });
|
|
1145
|
+
const systemTmpDir = path3.join(os.tmpdir(), "next-drive-uploads");
|
|
1146
|
+
if (!fs5.existsSync(systemTmpDir)) fs5.mkdirSync(systemTmpDir, { recursive: true });
|
|
1111
1147
|
const form = formidable({
|
|
1112
1148
|
multiples: false,
|
|
1113
1149
|
maxFileSize: config.security.maxUploadSizeInBytes * 2,
|
|
1114
|
-
uploadDir:
|
|
1150
|
+
uploadDir: systemTmpDir,
|
|
1115
1151
|
keepExtensions: true
|
|
1116
1152
|
});
|
|
1117
|
-
if (!fs2.existsSync(path3.join(STORAGE_PATH, "temp"))) fs2.mkdirSync(path3.join(STORAGE_PATH, "temp"), { recursive: true });
|
|
1118
1153
|
const [fields, files] = await new Promise((resolve, reject) => {
|
|
1119
1154
|
form.parse(req, (err, fields2, files2) => {
|
|
1120
1155
|
if (err) reject(err);
|
|
@@ -1123,7 +1158,7 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1123
1158
|
});
|
|
1124
1159
|
const cleanupTempFiles = (files2) => {
|
|
1125
1160
|
Object.values(files2).flat().forEach((file) => {
|
|
1126
|
-
if (file &&
|
|
1161
|
+
if (file && fs5.existsSync(file.filepath)) fs5.rmSync(file.filepath, { force: true });
|
|
1127
1162
|
});
|
|
1128
1163
|
};
|
|
1129
1164
|
const getString = (f) => Array.isArray(f) ? f[0] : f || "";
|
|
@@ -1143,7 +1178,7 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1143
1178
|
}
|
|
1144
1179
|
const { chunkIndex, totalChunks, driveId, fileName, fileSize: fileSizeInBytes, fileType, folderId } = uploadData.data;
|
|
1145
1180
|
let currentUploadId = driveId;
|
|
1146
|
-
const tempBaseDir = path3.join(
|
|
1181
|
+
const tempBaseDir = path3.join(os.tmpdir(), "next-drive-uploads");
|
|
1147
1182
|
if (!currentUploadId) {
|
|
1148
1183
|
if (chunkIndex !== 0) return res.status(400).json({ message: "Missing upload ID for non-zero chunk" });
|
|
1149
1184
|
if (fileType && !validateMimeType(fileType, config.security.allowedMimeTypes)) {
|
|
@@ -1157,7 +1192,7 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1157
1192
|
}
|
|
1158
1193
|
currentUploadId = crypto.randomUUID();
|
|
1159
1194
|
const uploadDir = path3.join(tempBaseDir, currentUploadId);
|
|
1160
|
-
|
|
1195
|
+
fs5.mkdirSync(uploadDir, { recursive: true });
|
|
1161
1196
|
const metadata = {
|
|
1162
1197
|
owner,
|
|
1163
1198
|
accountId,
|
|
@@ -1168,11 +1203,11 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1168
1203
|
mimeType: fileType,
|
|
1169
1204
|
totalChunks
|
|
1170
1205
|
};
|
|
1171
|
-
|
|
1206
|
+
fs5.writeFileSync(path3.join(uploadDir, "metadata.json"), JSON.stringify(metadata));
|
|
1172
1207
|
}
|
|
1173
1208
|
if (currentUploadId) {
|
|
1174
1209
|
const uploadDir = path3.join(tempBaseDir, currentUploadId);
|
|
1175
|
-
if (!
|
|
1210
|
+
if (!fs5.existsSync(uploadDir)) {
|
|
1176
1211
|
cleanupTempFiles(files);
|
|
1177
1212
|
return res.status(404).json({ status: 404, message: "Upload session not found or expired" });
|
|
1178
1213
|
}
|
|
@@ -1180,16 +1215,16 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1180
1215
|
const chunkFile = Array.isArray(files.chunk) ? files.chunk[0] : files.chunk;
|
|
1181
1216
|
if (!chunkFile) throw new Error("No chunk file received");
|
|
1182
1217
|
const partPath = path3.join(uploadDir, `part_${chunkIndex}`);
|
|
1183
|
-
|
|
1184
|
-
const uploadedParts =
|
|
1218
|
+
fs5.renameSync(chunkFile.filepath, partPath);
|
|
1219
|
+
const uploadedParts = fs5.readdirSync(uploadDir).filter((f) => f.startsWith("part_"));
|
|
1185
1220
|
if (uploadedParts.length === totalChunks) {
|
|
1186
1221
|
const metaPath = path3.join(uploadDir, "metadata.json");
|
|
1187
|
-
const meta = JSON.parse(
|
|
1222
|
+
const meta = JSON.parse(fs5.readFileSync(metaPath, "utf-8"));
|
|
1188
1223
|
const finalTempPath = path3.join(uploadDir, "final.bin");
|
|
1189
|
-
const writeStream =
|
|
1224
|
+
const writeStream = fs5.createWriteStream(finalTempPath);
|
|
1190
1225
|
for (let i = 0; i < totalChunks; i++) {
|
|
1191
1226
|
const pPath = path3.join(uploadDir, `part_${i}`);
|
|
1192
|
-
const data =
|
|
1227
|
+
const data = fs5.readFileSync(pPath);
|
|
1193
1228
|
writeStream.write(data);
|
|
1194
1229
|
}
|
|
1195
1230
|
writeStream.end();
|
|
@@ -1215,7 +1250,7 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1215
1250
|
await drive.save();
|
|
1216
1251
|
try {
|
|
1217
1252
|
const item = await provider.uploadFile(drive, finalTempPath, meta.accountId);
|
|
1218
|
-
|
|
1253
|
+
fs5.rmSync(uploadDir, { recursive: true, force: true });
|
|
1219
1254
|
const newQuota = await provider.getQuota(meta.owner, meta.accountId, information.storage.quotaInBytes);
|
|
1220
1255
|
res.status(200).json({ status: 200, message: "Upload complete", data: { type: "UPLOAD_COMPLETE", driveId: String(drive._id), item }, statistic: { storage: newQuota } });
|
|
1221
1256
|
} catch (err) {
|
|
@@ -1239,7 +1274,22 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1239
1274
|
cleanupTempFiles(files);
|
|
1240
1275
|
return res.status(400).json({ status: 400, message: "Invalid upload request" });
|
|
1241
1276
|
}
|
|
1242
|
-
// ** 4.
|
|
1277
|
+
// ** 4. CANCEL UPLOAD **
|
|
1278
|
+
case "cancel": {
|
|
1279
|
+
const cancelData = cancelQuerySchema.safeParse(req.query);
|
|
1280
|
+
if (!cancelData.success) return res.status(400).json({ status: 400, message: "Invalid ID" });
|
|
1281
|
+
const { id } = cancelData.data;
|
|
1282
|
+
const tempUploadDir = path3.join(os.tmpdir(), "next-drive-uploads", id);
|
|
1283
|
+
if (fs5.existsSync(tempUploadDir)) {
|
|
1284
|
+
try {
|
|
1285
|
+
fs5.rmSync(tempUploadDir, { recursive: true, force: true });
|
|
1286
|
+
} catch (e) {
|
|
1287
|
+
console.error("Failed to cleanup temp upload:", e);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
return res.status(200).json({ status: 200, message: "Upload cancelled", data: null });
|
|
1291
|
+
}
|
|
1292
|
+
// ** 5. CREATE FOLDER **
|
|
1243
1293
|
case "createFolder": {
|
|
1244
1294
|
const folderData = createFolderBodySchema.safeParse(req.body);
|
|
1245
1295
|
if (!folderData.success) return res.status(400).json({ status: 400, message: folderData.error.errors[0].message });
|
|
@@ -1411,5 +1461,5 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1411
1461
|
};
|
|
1412
1462
|
|
|
1413
1463
|
export { driveAPIHandler, driveConfiguration, driveFilePath, driveFileSchemaZod, driveGetUrl, driveReadFile, getDriveConfig, getDriveInformation };
|
|
1414
|
-
//# sourceMappingURL=chunk-
|
|
1415
|
-
//# sourceMappingURL=chunk-
|
|
1464
|
+
//# sourceMappingURL=chunk-LJCUIGLV.js.map
|
|
1465
|
+
//# sourceMappingURL=chunk-LJCUIGLV.js.map
|