@internxt/cli 1.6.6 → 1.6.8

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 (85) hide show
  1. package/README.md +36 -231
  2. package/dist/commands/create-folder.js +2 -6
  3. package/dist/commands/delete-permanently-file.js +1 -1
  4. package/dist/commands/delete-permanently-folder.js +1 -1
  5. package/dist/commands/download-file.js +3 -3
  6. package/dist/commands/list.d.ts +2 -0
  7. package/dist/commands/list.js +6 -4
  8. package/dist/commands/login.d.ts +0 -1
  9. package/dist/commands/login.js +1 -11
  10. package/dist/commands/move-file.d.ts +1 -0
  11. package/dist/commands/move-file.js +1 -1
  12. package/dist/commands/move-folder.js +1 -1
  13. package/dist/commands/rename-file.js +1 -1
  14. package/dist/commands/rename-folder.js +1 -1
  15. package/dist/commands/trash-file.js +1 -1
  16. package/dist/commands/trash-folder.js +1 -1
  17. package/dist/commands/trash-list.d.ts +2 -0
  18. package/dist/commands/trash-list.js +5 -3
  19. package/dist/commands/trash-restore-file.d.ts +1 -0
  20. package/dist/commands/trash-restore-file.js +1 -1
  21. package/dist/commands/trash-restore-folder.js +1 -1
  22. package/dist/commands/upload-file.d.ts +1 -0
  23. package/dist/commands/upload-file.js +22 -13
  24. package/dist/commands/whoami.js +6 -3
  25. package/dist/hooks/prerun/auth_check.js +2 -2
  26. package/dist/services/auth.service.d.ts +3 -3
  27. package/dist/services/auth.service.js +35 -44
  28. package/dist/services/crypto.service.d.ts +0 -4
  29. package/dist/services/crypto.service.js +1 -32
  30. package/dist/services/database/drive-item/drive-item.model.js +1 -0
  31. package/dist/services/database/drive-item/drive-item.repository.js +12 -2
  32. package/dist/services/drive/drive-file.service.d.ts +2 -0
  33. package/dist/services/drive/drive-file.service.js +48 -18
  34. package/dist/services/drive/drive-folder.service.d.ts +2 -0
  35. package/dist/services/drive/drive-folder.service.js +14 -46
  36. package/dist/services/drive/trash.service.d.ts +2 -0
  37. package/dist/services/drive/trash.service.js +14 -41
  38. package/dist/services/network/upload/upload-file.service.js +5 -9
  39. package/dist/services/network/upload/upload.types.d.ts +2 -1
  40. package/dist/services/sdk-manager.service.d.ts +0 -2
  41. package/dist/services/sdk-manager.service.js +0 -7
  42. package/dist/services/thumbnail.service.d.ts +4 -13
  43. package/dist/services/thumbnail.service.js +7 -21
  44. package/dist/services/universal-link.service.d.ts +1 -1
  45. package/dist/services/universal-link.service.js +2 -2
  46. package/dist/services/validation.service.d.ts +3 -13
  47. package/dist/services/validation.service.js +4 -28
  48. package/dist/types/command.types.d.ts +1 -29
  49. package/dist/types/command.types.js +1 -15
  50. package/dist/types/network.types.d.ts +3 -11
  51. package/dist/types/webdav.types.d.ts +0 -3
  52. package/dist/utils/cli.utils.js +9 -22
  53. package/dist/utils/drive.utils.d.ts +1 -0
  54. package/dist/utils/drive.utils.js +5 -1
  55. package/dist/utils/format.utils.js +6 -2
  56. package/dist/utils/logger.utils.js +9 -2
  57. package/dist/utils/network.utils.js +2 -2
  58. package/dist/utils/stream.utils.d.ts +2 -2
  59. package/dist/utils/stream.utils.js +7 -6
  60. package/dist/utils/thumbnail.utils.d.ts +1 -3
  61. package/dist/utils/thumbnail.utils.js +3 -13
  62. package/dist/utils/upload.utils.d.ts +1 -1
  63. package/dist/utils/upload.utils.js +2 -2
  64. package/dist/utils/webdav.utils.d.ts +2 -0
  65. package/dist/utils/webdav.utils.js +16 -0
  66. package/dist/webdav/handlers/GET.handler.js +4 -2
  67. package/dist/webdav/handlers/HEAD.handler.js +2 -0
  68. package/dist/webdav/handlers/MKCOL.handler.js +3 -3
  69. package/dist/webdav/handlers/PROPFIND.handler.js +8 -4
  70. package/dist/webdav/handlers/PUT.handler.js +24 -10
  71. package/dist/webdav/index.js +2 -2
  72. package/dist/webdav/middewares/auth.middleware.js +2 -1
  73. package/dist/webdav/middewares/errors.middleware.js +29 -5
  74. package/dist/webdav/middewares/webdav-auth.middleware.js +1 -0
  75. package/dist/webdav/webdav-server.js +2 -2
  76. package/oclif.manifest.json +9 -198
  77. package/package.json +23 -22
  78. package/dist/commands/workspaces-list.d.ts +0 -20
  79. package/dist/commands/workspaces-list.js +0 -68
  80. package/dist/commands/workspaces-unset.d.ts +0 -23
  81. package/dist/commands/workspaces-unset.js +0 -46
  82. package/dist/commands/workspaces-use.d.ts +0 -27
  83. package/dist/commands/workspaces-use.js +0 -114
  84. package/dist/services/drive/workspace.service.d.ts +0 -7
  85. package/dist/services/drive/workspace.service.js +0 -30
@@ -118,7 +118,7 @@ class CLIUtils {
118
118
  options: { type: 'input' },
119
119
  },
120
120
  }, {
121
- validate: validation_service_1.ValidationService.instance.validateUUIDv4,
121
+ validate: validation_service_1.ValidationService.instance.validateUUID,
122
122
  error: new command_types_1.NotValidFolderUuidError(),
123
123
  canBeEmpty: true,
124
124
  }, reporter);
@@ -255,27 +255,14 @@ class CLIUtils {
255
255
  }
256
256
  };
257
257
  static getNetworkCreds = async (userCredentials) => {
258
- const currentWorkspace = await auth_service_1.AuthService.instance.getCurrentWorkspace();
259
- if (currentWorkspace) {
260
- return {
261
- bucket: currentWorkspace.workspaceCredentials.bucket,
262
- credentials: {
263
- user: currentWorkspace.workspaceCredentials.credentials.user,
264
- pass: currentWorkspace.workspaceCredentials.credentials.pass,
265
- },
266
- mnemonic: currentWorkspace.workspaceData.workspaceUser.key,
267
- };
268
- }
269
- else {
270
- return {
271
- bucket: userCredentials.bucket,
272
- credentials: {
273
- user: userCredentials.bridgeUser,
274
- pass: userCredentials.userId,
275
- },
276
- mnemonic: userCredentials.mnemonic,
277
- };
278
- }
258
+ return {
259
+ bucket: userCredentials.bucket,
260
+ credentials: {
261
+ user: userCredentials.bridgeUser,
262
+ pass: userCredentials.userId,
263
+ },
264
+ mnemonic: userCredentials.mnemonic,
265
+ };
279
266
  };
280
267
  }
281
268
  exports.CLIUtils = CLIUtils;
@@ -1,6 +1,7 @@
1
1
  import { FileMeta, FolderMeta, CreateFolderResponse } from '@internxt/sdk/dist/drive/storage/types';
2
2
  import { DriveFileItem, DriveFolderItem } from '../types/drive.types';
3
3
  export declare class DriveUtils {
4
+ static parseFileSize(size: string | number): number;
4
5
  static driveFileMetaToItem(fileMeta: FileMeta): DriveFileItem;
5
6
  static driveFolderMetaToItem(folderMeta: FolderMeta): DriveFolderItem;
6
7
  static createFolderResponseToItem(folderResponse: CreateFolderResponse): DriveFolderItem;
@@ -3,13 +3,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DriveUtils = void 0;
4
4
  const types_1 = require("@internxt/sdk/dist/drive/storage/types");
5
5
  class DriveUtils {
6
+ static parseFileSize(size) {
7
+ const parsed = Number(size);
8
+ return Number.isFinite(parsed) ? parsed : 0;
9
+ }
6
10
  static driveFileMetaToItem(fileMeta) {
7
11
  return {
8
12
  itemType: 'file',
9
13
  uuid: fileMeta.uuid ?? '',
10
14
  status: fileMeta.status,
11
15
  folderUuid: fileMeta.folderUuid,
12
- size: Number(fileMeta.size),
16
+ size: DriveUtils.parseFileSize(fileMeta.size),
13
17
  name: fileMeta.plainName ?? fileMeta.name,
14
18
  bucket: fileMeta.bucket,
15
19
  createdAt: new Date(fileMeta.createdAt),
@@ -15,10 +15,14 @@ class FormatUtils {
15
15
  return Number((size / Math.pow(1024, unitIndex)).toFixed(2)) + ' ' + units[unitIndex];
16
16
  };
17
17
  static formatDate = (date) => {
18
- return (0, dayjs_1.default)(date).format('D MMMM, YYYY [at] HH:mm');
18
+ const d = (0, dayjs_1.default)(date);
19
+ return d.isValid() ? d.format('D MMMM, YYYY [at] HH:mm') : '-';
19
20
  };
20
21
  static formatDateForWebDav = (date) => {
21
- return dayjs_1.default.utc(date).format('ddd, DD MMM YYYY HH:mm:ss [GMT]');
22
+ const d = dayjs_1.default.utc(date);
23
+ return d.isValid()
24
+ ? d.format('ddd, DD MMM YYYY HH:mm:ss [GMT]')
25
+ : dayjs_1.default.utc().format('ddd, DD MMM YYYY HH:mm:ss [GMT]');
22
26
  };
23
27
  static formatLimit = (limit) => {
24
28
  let result = '...';
@@ -4,14 +4,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.webdavLogger = exports.logger = void 0;
7
+ const node_os_1 = __importDefault(require("node:os"));
7
8
  const winston_1 = __importDefault(require("winston"));
8
9
  const configs_1 = require("../constants/configs");
10
+ const package_json_1 = __importDefault(require("../../package.json"));
9
11
  const maxLogSize = 40 * 1024 * 1024;
10
12
  const maxLogsFiles = 5;
13
+ const environmentMeta = {
14
+ version: package_json_1.default.version,
15
+ os: `${node_os_1.default.platform()} ${node_os_1.default.release()} (${node_os_1.default.arch()})`,
16
+ node: process.version,
17
+ };
11
18
  exports.logger = winston_1.default.createLogger({
12
19
  level: 'info',
13
20
  format: winston_1.default.format.combine(winston_1.default.format.timestamp(), winston_1.default.format.json()),
14
- defaultMeta: { service: 'internxt-cli' },
21
+ defaultMeta: { service: 'internxt-cli', ...environmentMeta },
15
22
  transports: [
16
23
  new winston_1.default.transports.File({
17
24
  filename: 'internxt-cli-error.log',
@@ -33,7 +40,7 @@ exports.logger = winston_1.default.createLogger({
33
40
  exports.webdavLogger = winston_1.default.createLogger({
34
41
  level: 'info',
35
42
  format: winston_1.default.format.combine(winston_1.default.format.timestamp(), winston_1.default.format.json()),
36
- defaultMeta: { service: 'internxt-webdav' },
43
+ defaultMeta: { service: 'internxt-webdav', ...environmentMeta },
37
44
  transports: [
38
45
  new winston_1.default.transports.File({
39
46
  filename: 'internxt-webdav-error.log',
@@ -100,10 +100,10 @@ class NetworkUtils {
100
100
  }
101
101
  }
102
102
  else if (parsed === -1) {
103
- throw new Error(`Malformed Range-Request. ${JSON.stringify(rangeOptions)}`);
103
+ throw new Error(`Unsatisfiable Range-Request. ${JSON.stringify(rangeOptions)}`);
104
104
  }
105
105
  else if (parsed === -2) {
106
- throw new Error(`Unsatisfiable Range-Request. ${JSON.stringify(rangeOptions)}`);
106
+ throw new Error(`Malformed Range-Request. ${JSON.stringify(rangeOptions)}`);
107
107
  }
108
108
  else {
109
109
  throw new Error(`Unknown error from Range-Request. ${JSON.stringify(rangeOptions)}`);
@@ -6,10 +6,10 @@ export declare class StreamUtils {
6
6
  static joinReadableBinaryStreams(streams: ReadableStream<Uint8Array>[]): ReadableStream<Uint8Array>;
7
7
  }
8
8
  export declare class BufferStream extends Transform {
9
- buffer: Buffer | null;
9
+ private chunks;
10
10
  constructor(opts?: TransformOptions);
11
11
  _transform(chunk: Buffer, _: BufferEncoding, callback: TransformCallback): void;
12
12
  _flush(callback: TransformCallback): void;
13
13
  reset(): void;
14
- getBuffer(): Buffer | null;
14
+ getBuffer(): Buffer | undefined;
15
15
  }
@@ -56,24 +56,25 @@ class StreamUtils {
56
56
  }
57
57
  exports.StreamUtils = StreamUtils;
58
58
  class BufferStream extends node_stream_1.Transform {
59
- buffer;
59
+ chunks;
60
60
  constructor(opts) {
61
61
  super(opts);
62
- this.buffer = null;
62
+ this.chunks = [];
63
63
  }
64
64
  _transform(chunk, _, callback) {
65
- const currentBuffer = this.buffer ?? Buffer.alloc(0);
66
- this.buffer = Buffer.concat([currentBuffer, chunk]);
65
+ this.chunks.push(chunk);
67
66
  callback(null, chunk);
68
67
  }
69
68
  _flush(callback) {
70
69
  callback();
71
70
  }
72
71
  reset() {
73
- this.buffer = null;
72
+ this.chunks = [];
74
73
  }
75
74
  getBuffer() {
76
- return this.buffer;
75
+ if (this.chunks.length === 0)
76
+ return undefined;
77
+ return Buffer.concat(this.chunks);
77
78
  }
78
79
  }
79
80
  exports.BufferStream = BufferStream;
@@ -5,8 +5,6 @@ export declare const ThumbnailConfig: {
5
5
  readonly Type: "png";
6
6
  };
7
7
  export declare class ThumbnailUtils {
8
- static readonly MAX_IMAGE_THUMBNAILABLE_SIZE_IN_MB: number;
9
- static readonly isFileThumbnailable: (fileType: string) => boolean;
10
- static readonly isPDFThumbnailable: (fileType: string) => boolean;
8
+ static readonly MAX_IMAGE_THUMBNAILABLE_SIZE_IN_BYTES: number;
11
9
  static readonly isImageThumbnailable: (fileType: string, size: number) => boolean;
12
10
  }
@@ -18,9 +18,6 @@ const imageExtensions = {
18
18
  raw: ['raw', 'cr2', 'nef', 'orf', 'sr2'],
19
19
  webp: ['webp'],
20
20
  };
21
- const pdfExtensions = {
22
- pdf: ['pdf'],
23
- };
24
21
  const thumbnailableImageExtension = new Set([
25
22
  ...imageExtensions['jpg'],
26
23
  ...imageExtensions['png'],
@@ -28,19 +25,12 @@ const thumbnailableImageExtension = new Set([
28
25
  ...imageExtensions['gif'],
29
26
  ...imageExtensions['tiff'],
30
27
  ]);
31
- const thumbnailablePdfExtension = new Set(pdfExtensions['pdf']);
32
- const thumbnailableExtension = new Set(thumbnailableImageExtension);
33
28
  class ThumbnailUtils {
34
- static MAX_IMAGE_THUMBNAILABLE_SIZE_IN_MB = 500 * 1024 * 1024;
35
- static isFileThumbnailable = (fileType) => {
36
- return fileType.trim().length > 0 && thumbnailableExtension.has(fileType.trim().toLowerCase());
37
- };
38
- static isPDFThumbnailable = (fileType) => {
39
- return fileType.trim().length > 0 && thumbnailablePdfExtension.has(fileType.trim().toLowerCase());
40
- };
29
+ static MAX_IMAGE_THUMBNAILABLE_SIZE_IN_BYTES = 128 * 1024 * 1024;
41
30
  static isImageThumbnailable = (fileType, size) => {
42
- if (size > ThumbnailUtils.MAX_IMAGE_THUMBNAILABLE_SIZE_IN_MB)
31
+ if (size <= 0 || size > ThumbnailUtils.MAX_IMAGE_THUMBNAILABLE_SIZE_IN_BYTES) {
43
32
  return false;
33
+ }
44
34
  return fileType.trim().length > 0 && thumbnailableImageExtension.has(fileType.trim().toLowerCase());
45
35
  };
46
36
  }
@@ -2,7 +2,7 @@ import { Readable } from 'node:stream';
2
2
  import { BufferStream } from './stream.utils';
3
3
  export declare class UploadUtils {
4
4
  static readonly checkUploadSizeLimits: (size: number) => Promise<void>;
5
- static readonly prepareUploadStreams: (readable: Readable, fileType: string) => {
5
+ static readonly prepareUploadStreams: (readable: Readable, fileType: string, size: number) => {
6
6
  fileStream: Readable;
7
7
  thumbnailStream: BufferStream | undefined;
8
8
  isThumbnailable: boolean;
@@ -19,8 +19,8 @@ class UploadUtils {
19
19
  throw new Error('File is too big (more than 100 GB)');
20
20
  }
21
21
  };
22
- static prepareUploadStreams = (readable, fileType) => {
23
- const isThumbnailable = thumbnail_utils_1.ThumbnailUtils.isFileThumbnailable(fileType);
22
+ static prepareUploadStreams = (readable, fileType, size) => {
23
+ const isThumbnailable = thumbnail_utils_1.ThumbnailUtils.isImageThumbnailable(fileType, size);
24
24
  if (!isThumbnailable) {
25
25
  return { fileStream: readable, thumbnailStream: undefined, isThumbnailable };
26
26
  }
@@ -13,4 +13,6 @@ export declare class WebDavUtils {
13
13
  itemType: 'file' | 'folder';
14
14
  uuid: string;
15
15
  }>(driveItem: T): Promise<void>;
16
+ static generateETag(parts: Array<string | number | null | undefined>): string;
17
+ static getItemETag(driveItem: DriveFileItem | DriveFolderItem): string;
16
18
  }
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.WebDavUtils = void 0;
7
7
  const node_path_1 = __importDefault(require("node:path"));
8
+ const node_crypto_1 = require("node:crypto");
8
9
  const drive_item_service_1 = require("../services/drive/drive-item.service");
9
10
  const logger_utils_1 = require("./logger.utils");
10
11
  const config_service_1 = require("../services/config.service");
@@ -100,5 +101,20 @@ class WebDavUtils {
100
101
  }
101
102
  await drive_item_repository_1.DriveItemRepository.instance.delete([driveItem.uuid]);
102
103
  }
104
+ static generateETag(parts) {
105
+ const normalized = parts.map((part) => part ?? '-');
106
+ const hash = (0, node_crypto_1.createHash)('sha256').update(normalized.join('|')).digest('hex');
107
+ return `"${hash}"`;
108
+ }
109
+ static getItemETag(driveItem) {
110
+ return this.generateETag([
111
+ driveItem.uuid,
112
+ driveItem.itemType === 'file' ? driveItem.size : undefined,
113
+ driveItem.createdAt.getTime(),
114
+ driveItem.updatedAt.getTime(),
115
+ driveItem.creationTime.getTime(),
116
+ driveItem.modificationTime.getTime(),
117
+ ]);
118
+ }
103
119
  }
104
120
  exports.WebDavUtils = WebDavUtils;
@@ -20,6 +20,8 @@ class GETRequestHandler {
20
20
  const { user } = await auth_service_1.AuthService.instance.getAuthDetails();
21
21
  logger_utils_1.webdavLogger.info(`[GET] [${driveFile.uuid}] Network ready for download`);
22
22
  res.header('Content-Type', 'application/octet-stream');
23
+ res.header('Accept-Ranges', 'bytes');
24
+ res.header('ETag', webdav_utils_1.WebDavUtils.getItemETag(driveFile));
23
25
  const fileSize = driveFile.size ?? 0;
24
26
  if (fileSize > 0) {
25
27
  const range = req.headers['range'];
@@ -44,8 +46,8 @@ class GETRequestHandler {
44
46
  if (!driveFile.fileId) {
45
47
  throw new command_types_1.NotValidFileIdError();
46
48
  }
47
- const { networkFacade, bucket, mnemonic } = await cli_utils_1.CLIUtils.prepareNetwork(user);
48
- const [executeDownload] = await networkFacade.downloadToStream(bucket, mnemonic, driveFile.fileId, contentLength, writable, rangeOptions);
49
+ const { networkFacade, mnemonic } = await cli_utils_1.CLIUtils.prepareNetwork(user);
50
+ const [executeDownload] = await networkFacade.downloadToStream(driveFile.bucket, mnemonic, driveFile.fileId, contentLength, writable, rangeOptions);
49
51
  logger_utils_1.webdavLogger.info(`[GET] [${driveFile.uuid}] Download prepared, executing...`);
50
52
  res.status(200);
51
53
  await executeDownload;
@@ -14,6 +14,7 @@ class HEADRequestHandler {
14
14
  throw new errors_utils_1.NotFoundError(`Resource not found on Internxt Drive at ${resource.url}`);
15
15
  }
16
16
  logger_utils_1.webdavLogger.info(`[HEAD] [${driveItem.uuid}] Found Drive item`);
17
+ res.header('ETag', webdav_utils_1.WebDavUtils.getItemETag(driveItem));
17
18
  if (driveItem.itemType === 'file') {
18
19
  const range = req.headers['range'];
19
20
  const rangeOptions = network_utils_1.NetworkUtils.parseRangeHeader({
@@ -26,6 +27,7 @@ class HEADRequestHandler {
26
27
  contentLength = rangeOptions.rangeSize;
27
28
  }
28
29
  res.header('Content-Type', 'application/octet-stream');
30
+ res.header('Accept-Ranges', 'bytes');
29
31
  res.header('Content-length', contentLength.toString());
30
32
  }
31
33
  res.status(200).send();
@@ -7,6 +7,7 @@ const logger_utils_1 = require("../../utils/logger.utils");
7
7
  const xml_utils_1 = require("../../utils/xml.utils");
8
8
  const webdav_folder_service_1 = require("../../services/webdav/webdav-folder.service");
9
9
  const async_utils_1 = require("../../utils/async.utils");
10
+ const errors_utils_1 = require("../../utils/errors.utils");
10
11
  class MKCOLRequestHandler {
11
12
  handle = async (req, res) => {
12
13
  const resource = await webdav_utils_1.WebDavUtils.getRequestedResource(req.url);
@@ -16,9 +17,8 @@ class MKCOLRequestHandler {
16
17
  const driveFolderItem = await webdav_utils_1.WebDavUtils.getDriveFolderFromResource(resource.url);
17
18
  const folderAlreadyExists = !!driveFolderItem;
18
19
  if (folderAlreadyExists) {
19
- logger_utils_1.webdavLogger.info(`[MKCOL] Folder '${resource.url}' already exists, ignoring the creation request`);
20
- res.status(200).send(xml_utils_1.XMLUtils.toWebDavXML({}, {}));
21
- return;
20
+ logger_utils_1.webdavLogger.info(`[MKCOL] Folder '${resource.url}' already exists, throwing 405 error`);
21
+ throw new errors_utils_1.MethodNotAllowed('Folder already exists');
22
22
  }
23
23
  const newFolder = await webdav_folder_service_1.WebDavFolderService.instance.createFolder({
24
24
  folderName: resource.path.base,
@@ -8,8 +8,8 @@ const xml_utils_1 = require("../../utils/xml.utils");
8
8
  const drive_item_domain_1 = require("../../services/database/drive-item/drive-item.domain");
9
9
  const drive_item_repository_1 = require("../../services/database/drive-item/drive-item.repository");
10
10
  const drive_folder_service_1 = require("../../services/drive/drive-folder.service");
11
+ const drive_utils_1 = require("../../utils/drive.utils");
11
12
  const format_utils_1 = require("../../utils/format.utils");
12
- const node_crypto_1 = require("node:crypto");
13
13
  const mime_types_1 = __importDefault(require("mime-types"));
14
14
  const webdav_utils_1 = require("../../utils/webdav.utils");
15
15
  const logger_utils_1 = require("../../utils/logger.utils");
@@ -23,6 +23,7 @@ class PROPFINDRequestHandler {
23
23
  res.status(404).send();
24
24
  return;
25
25
  }
26
+ res.set('Content-Type', 'application/xml; charset="utf-8"');
26
27
  switch (driveItem.itemType) {
27
28
  case 'file': {
28
29
  const fileMetaXML = await this.getFileMetaXML(resource, driveItem);
@@ -113,7 +114,7 @@ class PROPFINDRequestHandler {
113
114
  type: file.type,
114
115
  status: file.status,
115
116
  folderUuid: file.folderUuid,
116
- size: Number(file.size),
117
+ size: drive_utils_1.DriveUtils.parseFileSize(file.size),
117
118
  creationTime: new Date(file.creationTime),
118
119
  modificationTime: new Date(file.modificationTime),
119
120
  createdAt: new Date(file.createdAt),
@@ -140,7 +141,8 @@ class PROPFINDRequestHandler {
140
141
  [xml_utils_1.XMLUtils.addDefaultNamespace('propstat')]: {
141
142
  [xml_utils_1.XMLUtils.addDefaultNamespace('status')]: 'HTTP/1.1 200 OK',
142
143
  [xml_utils_1.XMLUtils.addDefaultNamespace('prop')]: {
143
- [xml_utils_1.XMLUtils.addDefaultNamespace('getcontenttype')]: 'application/octet-stream',
144
+ [xml_utils_1.XMLUtils.addDefaultNamespace('getcontenttype')]: 'httpd/unix-directory',
145
+ [xml_utils_1.XMLUtils.addDefaultNamespace('getetag')]: webdav_utils_1.WebDavUtils.getItemETag(driveFolderItem),
144
146
  'x1:lastmodified': {
145
147
  '#text': format_utils_1.FormatUtils.formatDateForWebDav(driveFolderItem.updatedAt),
146
148
  '@_xmlns:x1': 'SAR:',
@@ -173,6 +175,8 @@ class PROPFINDRequestHandler {
173
175
  [xml_utils_1.XMLUtils.addDefaultNamespace('displayname')]: displayName,
174
176
  [xml_utils_1.XMLUtils.addDefaultNamespace('getlastmodified')]: format_utils_1.FormatUtils.formatDateForWebDav(driveFolderItem.updatedAt),
175
177
  [xml_utils_1.XMLUtils.addDefaultNamespace('getcontentlength')]: 0,
178
+ [xml_utils_1.XMLUtils.addDefaultNamespace('getcontenttype')]: 'httpd/unix-directory',
179
+ [xml_utils_1.XMLUtils.addDefaultNamespace('getetag')]: webdav_utils_1.WebDavUtils.getItemETag(driveFolderItem),
176
180
  [xml_utils_1.XMLUtils.addDefaultNamespace('resourcetype')]: {
177
181
  [xml_utils_1.XMLUtils.addDefaultNamespace('collection')]: '',
178
182
  },
@@ -190,7 +194,7 @@ class PROPFINDRequestHandler {
190
194
  [xml_utils_1.XMLUtils.addDefaultNamespace('status')]: 'HTTP/1.1 200 OK',
191
195
  [xml_utils_1.XMLUtils.addDefaultNamespace('prop')]: {
192
196
  [xml_utils_1.XMLUtils.addDefaultNamespace('resourcetype')]: '',
193
- [xml_utils_1.XMLUtils.addDefaultNamespace('getetag')]: '"' + (0, node_crypto_1.randomUUID)().replaceAll('-', '') + '"',
197
+ [xml_utils_1.XMLUtils.addDefaultNamespace('getetag')]: webdav_utils_1.WebDavUtils.getItemETag(driveFileItem),
194
198
  [xml_utils_1.XMLUtils.addDefaultNamespace('displayname')]: displayName,
195
199
  [xml_utils_1.XMLUtils.addDefaultNamespace('getcontenttype')]: mime_types_1.default.lookup(displayName) || 'application/octet-stream',
196
200
  [xml_utils_1.XMLUtils.addDefaultNamespace('getlastmodified')]: lastModified,
@@ -39,16 +39,10 @@ class PUTRequestHandler {
39
39
  }
40
40
  isReplacement = true;
41
41
  logger_utils_1.webdavLogger.info(`[PUT] File '${resource.name}' already exists in '${resource.path.dir}', it will be replaced...`);
42
- try {
43
- await webdav_utils_1.WebDavUtils.deleteOrTrashItem(driveFileItem);
44
- await drive_item_repository_1.DriveItemRepository.instance.delete([driveFileItem.uuid]);
45
- }
46
- catch {
47
- }
48
42
  }
49
43
  const { user } = await auth_service_1.AuthService.instance.getAuthDetails();
50
44
  const fileType = resource.path.ext.replace('.', '');
51
- const { fileStream, thumbnailStream } = upload_utils_1.UploadUtils.prepareUploadStreams(req, fileType);
45
+ const { fileStream, thumbnailStream } = upload_utils_1.UploadUtils.prepareUploadStreams(req, fileType, contentLength);
52
46
  const { networkFacade, bucket } = await cli_utils_1.CLIUtils.prepareNetwork(user);
53
47
  let fileId;
54
48
  if (contentLength > 0) {
@@ -79,7 +73,7 @@ class PUTRequestHandler {
79
73
  logger_utils_1.webdavLogger.info('[PUT] ✅ File uploaded to network');
80
74
  }
81
75
  const driveTimer = cli_utils_1.CLIUtils.timer();
82
- const file = await drive_file_service_1.DriveFileService.instance.createFile({
76
+ const filePayload = {
83
77
  plainName: resource.path.name,
84
78
  type: fileType,
85
79
  size: contentLength,
@@ -87,7 +81,26 @@ class PUTRequestHandler {
87
81
  fileId,
88
82
  bucket,
89
83
  encryptVersion: types_1.EncryptionVersion.Aes03,
90
- });
84
+ };
85
+ let file;
86
+ if (driveFileItem?.itemType === 'file' && contentLength > 0) {
87
+ try {
88
+ file = await drive_file_service_1.DriveFileService.instance.replaceFile(driveFileItem.uuid, filePayload);
89
+ }
90
+ catch (error) {
91
+ logger_utils_1.webdavLogger.warn(`[PUT] File replace failed for '${resource.url}', falling back to delete and create: ${error instanceof Error ? error.message : String(error)}`);
92
+ await webdav_utils_1.WebDavUtils.deleteOrTrashItem(driveFileItem);
93
+ await drive_item_repository_1.DriveItemRepository.instance.delete([driveFileItem.uuid]);
94
+ file = await drive_file_service_1.DriveFileService.instance.createFile(filePayload);
95
+ }
96
+ }
97
+ else {
98
+ if (driveFileItem?.itemType === 'file') {
99
+ await webdav_utils_1.WebDavUtils.deleteOrTrashItem(driveFileItem);
100
+ await drive_item_repository_1.DriveItemRepository.instance.delete([driveFileItem.uuid]);
101
+ }
102
+ file = await drive_file_service_1.DriveFileService.instance.createFile(filePayload);
103
+ }
91
104
  timings.driveUpload = driveTimer.stop();
92
105
  await drive_item_repository_1.DriveItemRepository.instance.createOrUpdate([
93
106
  {
@@ -101,7 +114,7 @@ class PUTRequestHandler {
101
114
  const thumbnailTimer = cli_utils_1.CLIUtils.timer();
102
115
  await thumbnail_service_1.ThumbnailService.instance.tryUploadThumbnail({
103
116
  fileUuid: file.uuid,
104
- bufferStream: thumbnailStream,
117
+ input: thumbnailStream?.getBuffer(),
105
118
  fileType,
106
119
  bucket,
107
120
  networkFacade,
@@ -115,6 +128,7 @@ class PUTRequestHandler {
115
128
  const statusCode = isReplacement ? 204 : 201;
116
129
  logger_utils_1.webdavLogger.info(`[PUT] [RESPONSE-${statusCode}] ${resource.url} - Returning ${statusCode} ` +
117
130
  `after ${cli_utils_1.CLIUtils.formatDuration(totalTime)}`);
131
+ res.header('ETag', webdav_utils_1.WebDavUtils.getItemETag(file));
118
132
  res.status(statusCode).send();
119
133
  };
120
134
  }
@@ -18,8 +18,8 @@ const init = async () => {
18
18
  await config_service_1.ConfigService.instance.ensureInternxtLogsDirExists();
19
19
  await database_service_1.DatabaseService.instance.initialize();
20
20
  await database_service_1.DatabaseService.instance.clear();
21
- const { token, workspace } = await auth_service_1.AuthService.instance.getAuthDetails();
22
- sdk_manager_service_1.SdkManager.init({ token, workspaceToken: workspace?.workspaceCredentials.token });
21
+ const { token } = await auth_service_1.AuthService.instance.getAuthDetails();
22
+ sdk_manager_service_1.SdkManager.init({ token });
23
23
  new webdav_server_1.WebDavServer((0, express_1.default)())
24
24
  .start()
25
25
  .then()
@@ -13,7 +13,7 @@ const AuthMiddleware = () => {
13
13
  try {
14
14
  const cached = cache_service_1.CacheService.instance.get(cache_service_1.CacheService.AUTH_CACHE_KEY);
15
15
  if (cached) {
16
- sdk_manager_service_1.SdkManager.init({ token: cached.token, workspaceToken: cached.workspace?.workspaceCredentials?.token });
16
+ sdk_manager_service_1.SdkManager.init({ token: cached.token });
17
17
  next();
18
18
  return;
19
19
  }
@@ -35,6 +35,7 @@ const AuthMiddleware = () => {
35
35
  const errorBodyXML = xml_utils_1.XMLUtils.toWebDavXML({
36
36
  [xml_utils_1.XMLUtils.addDefaultNamespace('responsedescription')]: message,
37
37
  }, {}, 'error');
38
+ res.set('WWW-Authenticate', 'Basic realm="Internxt WebDAV"');
38
39
  res.status(401).send(errorBodyXML);
39
40
  }
40
41
  })();
@@ -4,8 +4,35 @@ exports.ErrorHandlingMiddleware = void 0;
4
4
  const logger_utils_1 = require("../../utils/logger.utils");
5
5
  const xml_utils_1 = require("../../utils/xml.utils");
6
6
  const errors_utils_1 = require("../../utils/errors.utils");
7
+ const getErrorDetail = (err) => {
8
+ if (typeof err !== 'object' || err === null || !('data' in err))
9
+ return undefined;
10
+ const data = err.data;
11
+ if (typeof data !== 'object' || data === null || !('message' in data))
12
+ return undefined;
13
+ const message = data.message;
14
+ if (typeof message === 'string' && message.trim().length > 0)
15
+ return message;
16
+ if (Array.isArray(message) && message.length > 0)
17
+ return message.join(', ');
18
+ return undefined;
19
+ };
20
+ const getErrorStatusCode = (err) => {
21
+ if (typeof err !== 'object' || err === null)
22
+ return undefined;
23
+ const { statusCode, status } = err;
24
+ if (typeof statusCode === 'number' && !Number.isNaN(statusCode))
25
+ return statusCode;
26
+ if (typeof status === 'number' && !Number.isNaN(status))
27
+ return status;
28
+ return undefined;
29
+ };
7
30
  const ErrorHandlingMiddleware = (err, req, res, _) => {
8
- const message = errors_utils_1.ErrorUtils.isError(err) ? err.message : 'Something went wrong';
31
+ let message = errors_utils_1.ErrorUtils.isError(err) ? err.message : 'Something went wrong';
32
+ const detail = getErrorDetail(err);
33
+ if (detail) {
34
+ message += ` [${detail}]`;
35
+ }
9
36
  if (errors_utils_1.ErrorUtils.isError(err) && err.stack) {
10
37
  logger_utils_1.webdavLogger.error(`[ERROR MIDDLEWARE] [${req.method.toUpperCase()} - ${req.url}] ${message}\nStack: ${err.stack}`);
11
38
  }
@@ -15,10 +42,7 @@ const ErrorHandlingMiddleware = (err, req, res, _) => {
15
42
  const errorBodyXML = xml_utils_1.XMLUtils.toWebDavXML({
16
43
  [xml_utils_1.XMLUtils.addDefaultNamespace('responsedescription')]: message,
17
44
  }, {}, 'error');
18
- let statusCode = 500;
19
- if ('statusCode' in err && !Number.isNaN(err.statusCode)) {
20
- statusCode = err.statusCode;
21
- }
45
+ const statusCode = getErrorStatusCode(err) ?? 500;
22
46
  res.set('Content-Type', 'application/xml; charset="utf-8"');
23
47
  res.status(statusCode).send(errorBodyXML);
24
48
  req.destroy();
@@ -37,5 +37,6 @@ const sendUnauthorizedError = (res, message) => {
37
37
  const errorBodyXML = xml_utils_1.XMLUtils.toWebDavXML({
38
38
  [xml_utils_1.XMLUtils.addDefaultNamespace('responsedescription')]: message,
39
39
  }, {}, 'error');
40
+ res.set('WWW-Authenticate', 'Basic realm="Internxt WebDAV"');
40
41
  res.status(401).send(errorBodyXML);
41
42
  };
@@ -35,11 +35,11 @@ class WebDavServer {
35
35
  this.app = app;
36
36
  }
37
37
  registerStartMiddlewares = (configs) => {
38
- this.app.use((0, webdav_auth_middleware_1.WebDAVAuthMiddleware)(configs));
39
- this.app.use((0, auth_middleware_1.AuthMiddleware)());
40
38
  this.app.use((0, request_logger_middleware_1.RequestLoggerMiddleware)({
41
39
  enable: true,
42
40
  }));
41
+ this.app.use((0, webdav_auth_middleware_1.WebDAVAuthMiddleware)(configs));
42
+ this.app.use((0, auth_middleware_1.AuthMiddleware)());
43
43
  this.app.use(body_parser_1.default.text({ type: ['application/xml', 'text/xml'] }));
44
44
  this.app.use(mkcol_middleware_1.MkcolMiddleware);
45
45
  };