@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.
@@ -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 fs2 from 'fs';
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 = fs2.createReadStream(filePath);
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: objectIdSchema
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 (!fs2.existsSync(filePath)) {
262
+ if (!fs5.existsSync(filePath)) {
262
263
  throw new Error("File not found on disk");
263
264
  }
264
- const stat = fs2.statSync(filePath);
265
- const stream = fs2.createReadStream(filePath);
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 (!fs2.existsSync(originalPath)) throw new Error("Original file not found");
278
- if (fs2.existsSync(thumbPath)) {
279
- return fs2.createReadStream(thumbPath);
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 (!fs2.existsSync(path3.dirname(thumbPath))) fs2.mkdirSync(path3.dirname(thumbPath), { recursive: true });
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 fs2.createReadStream(thumbPath);
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 (!fs2.existsSync(dirPath)) fs2.mkdirSync(dirPath, { recursive: true });
320
- fs2.renameSync(filePath, destPath);
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 (fs2.existsSync(dirPath)) {
350
- fs2.rmSync(dirPath, { recursive: true, force: true });
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: fs2.createReadStream(filePath)
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 (!fs2.existsSync(filePath)) {
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 (fs2.existsSync(cachedFilePath)) {
860
- const stats = fs2.statSync(cachedFilePath);
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
- fs2.unlinkSync(cachedFilePath);
871
+ fs5.unlinkSync(cachedFilePath);
871
872
  }
872
873
  const accountId = drive.storageAccountId?.toString();
873
874
  const { stream } = await GoogleDriveProvider.openStream(drive, accountId);
874
- if (!fs2.existsSync(libraryDir)) {
875
- fs2.mkdirSync(libraryDir, { recursive: true });
875
+ if (!fs5.existsSync(libraryDir)) {
876
+ fs5.mkdirSync(libraryDir, { recursive: true });
876
877
  }
877
878
  const tempPath = `${cachedFilePath}.tmp`;
878
- const writeStream = fs2.createWriteStream(tempPath);
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
- fs2.renameSync(tempPath, cachedFilePath);
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 oAuth2Client = new google2.auth.OAuth2(clientId, clientSecret, redirectUri);
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 oAuth2Client = new google2.auth.OAuth2(clientId, clientSecret, redirectUri);
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('<script>window.opener.postMessage("oauth-success", "*"); window.close();</script>');
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: path3.join(STORAGE_PATH, "temp"),
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 && fs2.existsSync(file.filepath)) fs2.rmSync(file.filepath, { force: true });
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(STORAGE_PATH, "temp", "uploads");
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
- fs2.mkdirSync(uploadDir, { recursive: true });
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
- fs2.writeFileSync(path3.join(uploadDir, "metadata.json"), JSON.stringify(metadata));
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 (!fs2.existsSync(uploadDir)) {
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
- fs2.renameSync(chunkFile.filepath, partPath);
1184
- const uploadedParts = fs2.readdirSync(uploadDir).filter((f) => f.startsWith("part_"));
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(fs2.readFileSync(metaPath, "utf-8"));
1222
+ const meta = JSON.parse(fs5.readFileSync(metaPath, "utf-8"));
1188
1223
  const finalTempPath = path3.join(uploadDir, "final.bin");
1189
- const writeStream = fs2.createWriteStream(finalTempPath);
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 = fs2.readFileSync(pPath);
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
- fs2.rmSync(uploadDir, { recursive: true, force: true });
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. CREATE FOLDER **
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-D6GHXEOY.js.map
1415
- //# sourceMappingURL=chunk-D6GHXEOY.js.map
1464
+ //# sourceMappingURL=chunk-LJCUIGLV.js.map
1465
+ //# sourceMappingURL=chunk-LJCUIGLV.js.map