molex-ftp-client 1.0.0 → 1.2.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/CHANGELOG.md +52 -0
- package/README.md +47 -0
- package/index.js +11 -469
- package/lib/FTPClient.js +226 -0
- package/lib/commands.js +323 -0
- package/lib/connection.js +176 -0
- package/lib/utils.js +84 -0
- package/package.json +4 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [1.2.0] - 2026-02-02
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- **Major refactoring**: Improved separation of concerns
|
|
7
|
+
- `index.js` now serves as simple entry point
|
|
8
|
+
- Implementation moved to organized `lib/` structure:
|
|
9
|
+
- `lib/FTPClient.js` - Main class definition
|
|
10
|
+
- `lib/connection.js` - Connection and authentication logic
|
|
11
|
+
- `lib/commands.js` - All FTP command implementations
|
|
12
|
+
- `lib/utils.js` - Helper functions
|
|
13
|
+
- Better code maintainability and readability
|
|
14
|
+
- No breaking changes - API remains identical
|
|
15
|
+
|
|
16
|
+
## [1.1.0] - 2026-02-02
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- `ensureDir(dirPath, recursive)` - Ensure directory exists, creating parent directories if needed
|
|
20
|
+
- `ensureParentDir(filePath)` - Ensure parent directory exists for a file path
|
|
21
|
+
- `uploadFile(data, remotePath, ensureDir)` - Upload with automatic directory creation
|
|
22
|
+
- Utility library (`lib/utils.js`) for better code organization
|
|
23
|
+
- Helper functions for FTP command parsing and path manipulation
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- Refactored internal code structure for better maintainability
|
|
27
|
+
- Improved path normalization across all directory operations
|
|
28
|
+
- Better error handling for directory creation
|
|
29
|
+
|
|
30
|
+
### Improved
|
|
31
|
+
- Cleaner API for common operations
|
|
32
|
+
- Reduced boilerplate code needed for directory handling
|
|
33
|
+
- More consistent error messages
|
|
34
|
+
|
|
35
|
+
## [1.0.1] - 2026-02-02
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
- Updated repository URLs to correct GitHub location
|
|
39
|
+
|
|
40
|
+
## [1.0.0] - 2026-02-02
|
|
41
|
+
|
|
42
|
+
### Initial Release
|
|
43
|
+
- Zero dependencies FTP client using native Node.js TCP sockets
|
|
44
|
+
- Promise-based API with async/await support
|
|
45
|
+
- Passive mode (PASV) for data transfers
|
|
46
|
+
- Debug logging with configurable options
|
|
47
|
+
- Connection keep-alive and timeout configuration
|
|
48
|
+
- Upload/download files with Buffer support
|
|
49
|
+
- Directory operations (list, cd, mkdir, pwd)
|
|
50
|
+
- File operations (delete, rename, size, exists, modifiedTime)
|
|
51
|
+
- Connection statistics tracking
|
|
52
|
+
- Event-based architecture
|
package/README.md
CHANGED
|
@@ -177,6 +177,24 @@ console.log(`Last modified: ${date.toISOString()}`);
|
|
|
177
177
|
|
|
178
178
|
Returns: `Promise<Date>`
|
|
179
179
|
|
|
180
|
+
#### `uploadFile(data, remotePath, ensureDir)`
|
|
181
|
+
|
|
182
|
+
Upload file and optionally ensure parent directory exists.
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
// Upload with automatic directory creation
|
|
186
|
+
await client.uploadFile('data', '/deep/nested/path/file.txt', true);
|
|
187
|
+
|
|
188
|
+
// Upload without directory creation (default behavior)
|
|
189
|
+
await client.uploadFile('data', '/file.txt');
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
- `data` (string|Buffer): File content
|
|
193
|
+
- `remotePath` (string): Remote file path
|
|
194
|
+
- `ensureDir` (boolean): Create parent directories if needed (default: false)
|
|
195
|
+
|
|
196
|
+
Returns: `Promise<void>`
|
|
197
|
+
|
|
180
198
|
### Directory Operations
|
|
181
199
|
|
|
182
200
|
#### `list(path)`
|
|
@@ -221,6 +239,35 @@ await client.mkdir('/remote/newdir');
|
|
|
221
239
|
|
|
222
240
|
Returns: `Promise<void>`
|
|
223
241
|
|
|
242
|
+
#### `ensureDir(dirPath, recursive)`
|
|
243
|
+
|
|
244
|
+
Ensure directory exists, creating it (and parent directories) if necessary.
|
|
245
|
+
|
|
246
|
+
```javascript
|
|
247
|
+
// Create nested directories recursively
|
|
248
|
+
await client.ensureDir('/deep/nested/path');
|
|
249
|
+
|
|
250
|
+
// Create single directory (no parent creation)
|
|
251
|
+
await client.ensureDir('/newdir', false);
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
- `dirPath` (string): Directory path to ensure exists
|
|
255
|
+
- `recursive` (boolean): Create parent directories if needed (default: true)
|
|
256
|
+
|
|
257
|
+
Returns: `Promise<void>`
|
|
258
|
+
|
|
259
|
+
#### `ensureParentDir(filePath)`
|
|
260
|
+
|
|
261
|
+
Ensure the parent directory exists for a given file path.
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
// Ensures /path/to exists before uploading
|
|
265
|
+
await client.ensureParentDir('/path/to/file.txt');
|
|
266
|
+
await client.upload('data', '/path/to/file.txt');
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Returns: `Promise<void>`
|
|
270
|
+
|
|
224
271
|
### Utilities
|
|
225
272
|
|
|
226
273
|
#### `getStats()`
|
package/index.js
CHANGED
|
@@ -1,475 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*
|
|
1
|
+
/*
|
|
2
|
+
* File: c:\Users\tonyw\Desktop\PRIVS\molex-ftp-client\index.js
|
|
3
|
+
* Project: c:\Users\tonyw\Desktop\PRIVS\molex-ftp-client
|
|
4
|
+
* Created Date: Monday February 2nd 2026
|
|
5
|
+
* Author: Tony Wiedman
|
|
6
|
+
* -----
|
|
7
|
+
* Last Modified: Mon February 2nd 2026 1:23:44
|
|
8
|
+
* Modified By: Tony Wiedman
|
|
9
|
+
* -----
|
|
10
|
+
* Copyright (c) 2026 MolexWorks
|
|
6
11
|
*/
|
|
7
|
-
class FTPClient extends EventEmitter {
|
|
8
|
-
constructor(options = {}) {
|
|
9
|
-
super();
|
|
10
|
-
this.socket = null;
|
|
11
|
-
this.dataSocket = null;
|
|
12
|
-
this.buffer = '';
|
|
13
|
-
this.connected = false;
|
|
14
|
-
this.authenticated = false;
|
|
15
|
-
this.debug = options.debug || false;
|
|
16
|
-
this.timeout = options.timeout || 30000;
|
|
17
|
-
this.keepAlive = options.keepAlive !== false;
|
|
18
|
-
this._log = options.logger || console.log;
|
|
19
|
-
this._commandCount = 0;
|
|
20
|
-
this._lastCommand = null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Log message if debug is enabled
|
|
25
|
-
* @private
|
|
26
|
-
*/
|
|
27
|
-
_debug(...args) {
|
|
28
|
-
if (this.debug && this._log) {
|
|
29
|
-
this._log('[FTP Debug]', ...args);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Connect to FTP server
|
|
35
|
-
* @param {Object} options - Connection options
|
|
36
|
-
* @param {string} options.host - FTP server host
|
|
37
|
-
* @param {number} [options.port=21] - FTP server port
|
|
38
|
-
* @param {string} [options.user='anonymous'] - Username
|
|
39
|
-
* @param {string} [options.password='anonymous@'] - Password
|
|
40
|
-
* @returns {Promise<void>}
|
|
41
|
-
*/
|
|
42
|
-
async connect({ host, port = 21, user = 'anonymous', password = 'anonymous@' }) {
|
|
43
|
-
this._debug(`Connecting to ${host}:${port} as ${user}`);
|
|
44
|
-
return new Promise((resolve, reject) => {
|
|
45
|
-
this.socket = net.createConnection({ host, port }, () => {
|
|
46
|
-
this.connected = true;
|
|
47
|
-
this._debug('TCP connection established');
|
|
48
|
-
if (this.keepAlive) {
|
|
49
|
-
this.socket.setKeepAlive(true, 10000);
|
|
50
|
-
}
|
|
51
|
-
this.emit('connected');
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
this.socket.setEncoding('utf8');
|
|
55
|
-
this.socket.on('data', async (data) => {
|
|
56
|
-
this.buffer += data;
|
|
57
|
-
const lines = this.buffer.split('\r\n');
|
|
58
|
-
this.buffer = lines.pop();
|
|
59
|
-
|
|
60
|
-
for (const line of lines) {
|
|
61
|
-
if (line) {
|
|
62
|
-
this._debug('<<<', line);
|
|
63
|
-
this.emit('response', line);
|
|
64
|
-
const code = parseInt(line.substring(0, 3));
|
|
65
|
-
|
|
66
|
-
// Handle initial connection
|
|
67
|
-
if (code === 220 && !this.authenticated) {
|
|
68
|
-
try {
|
|
69
|
-
this._debug('Authenticating...');
|
|
70
|
-
await this._sendCommand(`USER ${user}`);
|
|
71
|
-
await this._sendCommand(`PASS ${password}`);
|
|
72
|
-
this.authenticated = true;
|
|
73
|
-
this._debug('Authentication successful');
|
|
74
|
-
resolve();
|
|
75
|
-
} catch (err) {
|
|
76
|
-
reject(err);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
this.socket.on('error', (err) => {
|
|
84
|
-
this.emit('error', err);
|
|
85
|
-
reject(err);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
this.socket.on('close', () => {
|
|
89
|
-
this.connected = false;
|
|
90
|
-
this.authenticated = false;
|
|
91
|
-
this.emit('close');
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
setTimeout(() => reject(new Error('Connection timeout')), 10000);
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Send FTP command and wait for response
|
|
100
|
-
* @param {string} command - FTP command
|
|
101
|
-
* @param {boolean} allowPreliminary - Allow 1xx preliminary responses
|
|
102
|
-
* @returns {Promise<Object>}
|
|
103
|
-
*/
|
|
104
|
-
_sendCommand(command, allowPreliminary = false) {
|
|
105
|
-
return new Promise((resolve, reject) => {
|
|
106
|
-
if (!this.connected) {
|
|
107
|
-
return reject(new Error('Not connected'));
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
this._commandCount++;
|
|
111
|
-
this._lastCommand = command;
|
|
112
|
-
const cmdToLog = command.startsWith('PASS ') ? 'PASS ********' : command;
|
|
113
|
-
this._debug('>>>', cmdToLog);
|
|
114
|
-
|
|
115
|
-
const timeoutId = setTimeout(() => {
|
|
116
|
-
this.removeListener('response', responseHandler);
|
|
117
|
-
reject(new Error(`Command timeout: ${cmdToLog}`));
|
|
118
|
-
}, this.timeout);
|
|
119
|
-
|
|
120
|
-
const responseHandler = (line) => {
|
|
121
|
-
clearTimeout(timeoutId);
|
|
122
|
-
const code = parseInt(line.substring(0, 3));
|
|
123
|
-
const message = line.substring(4);
|
|
124
|
-
|
|
125
|
-
// Check if this is a complete response (not a multi-line response in progress)
|
|
126
|
-
if (line.charAt(3) === ' ') {
|
|
127
|
-
// 1xx = Preliminary positive reply (command okay, another command expected)
|
|
128
|
-
// 2xx = Positive completion reply
|
|
129
|
-
// 3xx = Positive intermediate reply (command okay, awaiting more info)
|
|
130
|
-
// 4xx/5xx = Negative replies (errors)
|
|
131
|
-
|
|
132
|
-
if (code >= 100 && code < 200 && allowPreliminary) {
|
|
133
|
-
// Don't remove listener, wait for final response
|
|
134
|
-
this._debug('Preliminary response, waiting for completion...');
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
clearTimeout(timeoutId);
|
|
139
|
-
this.removeListener('response', responseHandler);
|
|
140
|
-
|
|
141
|
-
if (code >= 200 && code < 400) {
|
|
142
|
-
resolve({ code, message, raw: line });
|
|
143
|
-
} else {
|
|
144
|
-
this._debug(`Error response: ${code}`);
|
|
145
|
-
reject(new Error(`FTP Error ${code}: ${message}`));
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
this.on('response', responseHandler);
|
|
151
|
-
this.socket.write(command + '\r\n');
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Enter passive mode and get data connection info
|
|
157
|
-
* @returns {Promise<Object>}
|
|
158
|
-
*/
|
|
159
|
-
async _enterPassiveMode() {
|
|
160
|
-
const response = await this._sendCommand('PASV');
|
|
161
|
-
const match = response.message.match(/\((\d+),(\d+),(\d+),(\d+),(\d+),(\d+)\)/);
|
|
162
|
-
|
|
163
|
-
if (!match) {
|
|
164
|
-
throw new Error('Failed to parse PASV response');
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const host = `${match[1]}.${match[2]}.${match[3]}.${match[4]}`;
|
|
168
|
-
const port = parseInt(match[5]) * 256 + parseInt(match[6]);
|
|
169
|
-
|
|
170
|
-
return { host, port };
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Upload file to FTP server
|
|
175
|
-
* @param {string|Buffer} data - File data
|
|
176
|
-
* @param {string} remotePath - Remote file path
|
|
177
|
-
* @returns {Promise<void>}
|
|
178
|
-
*/
|
|
179
|
-
async upload(data, remotePath) {
|
|
180
|
-
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data, 'utf8');
|
|
181
|
-
this._debug(`Uploading ${buffer.length} bytes to ${remotePath}`);
|
|
182
|
-
const { host, port } = await this._enterPassiveMode();
|
|
183
|
-
|
|
184
|
-
return new Promise((resolve, reject) => {
|
|
185
|
-
let commandSent = false;
|
|
186
|
-
|
|
187
|
-
this.dataSocket = net.createConnection({ host, port }, () => {
|
|
188
|
-
// Send STOR command to start upload (expects 150, then 226)
|
|
189
|
-
if (!commandSent) {
|
|
190
|
-
commandSent = true;
|
|
191
|
-
this._debug(`Data connection established for upload`);
|
|
192
|
-
this._sendCommand(`STOR ${remotePath}`, true).catch(reject);
|
|
193
|
-
|
|
194
|
-
// Write data to data socket
|
|
195
|
-
this.dataSocket.write(buffer);
|
|
196
|
-
this.dataSocket.end();
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
this.dataSocket.on('error', reject);
|
|
201
|
-
|
|
202
|
-
this.dataSocket.on('close', () => {
|
|
203
|
-
// Wait for final response from control socket
|
|
204
|
-
const finalHandler = (line) => {
|
|
205
|
-
const code = parseInt(line.substring(0, 3));
|
|
206
|
-
if (code === 226 || code === 250) {
|
|
207
|
-
this.removeListener('response', finalHandler);
|
|
208
|
-
this._debug(`Upload completed successfully`);
|
|
209
|
-
resolve();
|
|
210
|
-
} else if (code >= 400) {
|
|
211
|
-
this.removeListener('response', finalHandler);
|
|
212
|
-
reject(new Error(`FTP Error ${code}: ${line.substring(4)}`));
|
|
213
|
-
}
|
|
214
|
-
};
|
|
215
|
-
this.on('response', finalHandler);
|
|
216
|
-
|
|
217
|
-
// Timeout if no response
|
|
218
|
-
setTimeout(() => {
|
|
219
|
-
this.removeListener('response', finalHandler);
|
|
220
|
-
resolve();
|
|
221
|
-
}, 5000);
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Download file from FTP server
|
|
228
|
-
* @param {string} remotePath - Remote file path
|
|
229
|
-
* @returns {Promise<Buffer>}
|
|
230
|
-
*/
|
|
231
|
-
async download(remotePath) {
|
|
232
|
-
this._debug(`Downloading ${remotePath}`);
|
|
233
|
-
const { host, port } = await this._enterPassiveMode();
|
|
234
|
-
|
|
235
|
-
return new Promise((resolve, reject) => {
|
|
236
|
-
const chunks = [];
|
|
237
|
-
let commandSent = false;
|
|
238
|
-
|
|
239
|
-
this.dataSocket = net.createConnection({ host, port }, () => {
|
|
240
|
-
// Send RETR command to start download (expects 150, then 226)
|
|
241
|
-
if (!commandSent) {
|
|
242
|
-
commandSent = true;
|
|
243
|
-
this._debug(`Data connection established for download`);
|
|
244
|
-
this._sendCommand(`RETR ${remotePath}`, true).catch(reject);
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
this.dataSocket.on('data', (chunk) => {
|
|
249
|
-
chunks.push(chunk);
|
|
250
|
-
this._debug(`Received ${chunk.length} bytes`);
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
this.dataSocket.on('error', reject);
|
|
254
|
-
|
|
255
|
-
this.dataSocket.on('close', () => {
|
|
256
|
-
// Wait for final 226 response
|
|
257
|
-
const finalHandler = (line) => {
|
|
258
|
-
const code = parseInt(line.substring(0, 3));
|
|
259
|
-
if (code === 226 || code === 250) {
|
|
260
|
-
this.removeListener('response', finalHandler);
|
|
261
|
-
const result = Buffer.concat(chunks);
|
|
262
|
-
this._debug(`Download completed: ${result.length} bytes`);
|
|
263
|
-
resolve(result);
|
|
264
|
-
} else if (code >= 400) {
|
|
265
|
-
this.removeListener('response', finalHandler);
|
|
266
|
-
reject(new Error(`FTP Error ${code}: ${line.substring(4)}`));
|
|
267
|
-
}
|
|
268
|
-
};
|
|
269
|
-
this.on('response', finalHandler);
|
|
270
|
-
|
|
271
|
-
// Timeout if no response
|
|
272
|
-
setTimeout(() => {
|
|
273
|
-
this.removeListener('response', finalHandler);
|
|
274
|
-
if (chunks.length > 0) {
|
|
275
|
-
resolve(Buffer.concat(chunks));
|
|
276
|
-
}
|
|
277
|
-
}, 5000);
|
|
278
|
-
});
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* List directory contents
|
|
284
|
-
* @param {string} [path='.'] - Directory path
|
|
285
|
-
* @returns {Promise<string>}
|
|
286
|
-
*/
|
|
287
|
-
async list(path = '.') {
|
|
288
|
-
this._debug(`Listing directory: ${path}`);
|
|
289
|
-
const { host, port } = await this._enterPassiveMode();
|
|
290
|
-
|
|
291
|
-
return new Promise((resolve, reject) => {
|
|
292
|
-
const chunks = [];
|
|
293
|
-
let commandSent = false;
|
|
294
|
-
|
|
295
|
-
this.dataSocket = net.createConnection({ host, port }, () => {
|
|
296
|
-
if (!commandSent) {
|
|
297
|
-
commandSent = true;
|
|
298
|
-
this._sendCommand(`LIST ${path}`, true).catch(reject);
|
|
299
|
-
}
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
this.dataSocket.on('data', (chunk) => {
|
|
303
|
-
chunks.push(chunk);
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
this.dataSocket.on('error', reject);
|
|
307
|
-
|
|
308
|
-
this.dataSocket.on('close', () => {
|
|
309
|
-
// Wait for final 226 response
|
|
310
|
-
const finalHandler = (line) => {
|
|
311
|
-
const code = parseInt(line.substring(0, 3));
|
|
312
|
-
if (code === 226 || code === 250) {
|
|
313
|
-
this.removeListener('response', finalHandler);
|
|
314
|
-
resolve(Buffer.concat(chunks).toString('utf8'));
|
|
315
|
-
}
|
|
316
|
-
};
|
|
317
|
-
this.on('response', finalHandler);
|
|
318
|
-
|
|
319
|
-
// Timeout fallback
|
|
320
|
-
setTimeout(() => {
|
|
321
|
-
this.removeListener('response', finalHandler);
|
|
322
|
-
resolve(Buffer.concat(chunks).toString('utf8'));
|
|
323
|
-
}, 3000);
|
|
324
|
-
});
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* Change working directory
|
|
330
|
-
* @param {string} path - Directory path
|
|
331
|
-
* @returns {Promise<void>}
|
|
332
|
-
*/
|
|
333
|
-
async cd(path) {
|
|
334
|
-
await this._sendCommand(`CWD ${path}`);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Get current working directory
|
|
339
|
-
* @returns {Promise<string>}
|
|
340
|
-
*/
|
|
341
|
-
async pwd() {
|
|
342
|
-
const response = await this._sendCommand('PWD');
|
|
343
|
-
const match = response.message.match(/"(.+)"/);
|
|
344
|
-
return match ? match[1] : '/';
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* Create directory
|
|
349
|
-
* @param {string} path - Directory path
|
|
350
|
-
* @returns {Promise<void>}
|
|
351
|
-
*/
|
|
352
|
-
async mkdir(path) {
|
|
353
|
-
await this._sendCommand(`MKD ${path}`);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* Delete file
|
|
358
|
-
* @param {string} path - File path
|
|
359
|
-
* @returns {Promise<void>}
|
|
360
|
-
*/
|
|
361
|
-
async delete(path) {
|
|
362
|
-
await this._sendCommand(`DELE ${path}`);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Rename file
|
|
367
|
-
* @param {string} from - Current name
|
|
368
|
-
* @param {string} to - New name
|
|
369
|
-
* @returns {Promise<void>}
|
|
370
|
-
*/
|
|
371
|
-
async rename(from, to) {
|
|
372
|
-
await this._sendCommand(`RNFR ${from}`);
|
|
373
|
-
await this._sendCommand(`RNTO ${to}`);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* Get file size
|
|
378
|
-
* @param {string} path - File path
|
|
379
|
-
* @returns {Promise<number>}
|
|
380
|
-
*/
|
|
381
|
-
async size(path) {
|
|
382
|
-
this._debug(`Getting size of ${path}`)
|
|
383
|
-
const response = await this._sendCommand(`SIZE ${path}`);
|
|
384
|
-
return parseInt(response.message);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Check if file or directory exists
|
|
389
|
-
* @param {string} path - File or directory path
|
|
390
|
-
* @returns {Promise<boolean>}
|
|
391
|
-
*/
|
|
392
|
-
async exists(path) {
|
|
393
|
-
try {
|
|
394
|
-
await this.size(path);
|
|
395
|
-
return true;
|
|
396
|
-
} catch (err) {
|
|
397
|
-
return false;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
/**
|
|
402
|
-
* Get file modification time
|
|
403
|
-
* @param {string} path - File path
|
|
404
|
-
* @returns {Promise<Date>}
|
|
405
|
-
*/
|
|
406
|
-
async modifiedTime(path) {
|
|
407
|
-
this._debug(`Getting modification time of ${path}`);
|
|
408
|
-
const response = await this._sendCommand(`MDTM ${path}`);
|
|
409
|
-
// Parse MDTM response: YYYYMMDDhhmmss
|
|
410
|
-
const match = response.message.match(/(\d{14})/);
|
|
411
|
-
if (match) {
|
|
412
|
-
const str = match[1];
|
|
413
|
-
const year = parseInt(str.substring(0, 4));
|
|
414
|
-
const month = parseInt(str.substring(4, 6)) - 1;
|
|
415
|
-
const day = parseInt(str.substring(6, 8));
|
|
416
|
-
const hour = parseInt(str.substring(8, 10));
|
|
417
|
-
const minute = parseInt(str.substring(10, 12));
|
|
418
|
-
const second = parseInt(str.substring(12, 14));
|
|
419
|
-
return new Date(Date.UTC(year, month, day, hour, minute, second));
|
|
420
|
-
}
|
|
421
|
-
throw new Error('Failed to parse MDTM response');
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
/**
|
|
425
|
-
* Get connection statistics
|
|
426
|
-
* @returns {Object}
|
|
427
|
-
*/
|
|
428
|
-
getStats() {
|
|
429
|
-
return {
|
|
430
|
-
connected: this.connected,
|
|
431
|
-
authenticated: this.authenticated,
|
|
432
|
-
commandCount: this._commandCount,
|
|
433
|
-
lastCommand: this._lastCommand
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
/**
|
|
438
|
-
* Enable or disable debug mode
|
|
439
|
-
* @param {boolean} enabled - Enable debug mode
|
|
440
|
-
*/
|
|
441
|
-
setDebug(enabled) {
|
|
442
|
-
this.debug = enabled;
|
|
443
|
-
this._debug(`Debug mode ${enabled ? 'enabled' : 'disabled'}`);
|
|
444
|
-
}
|
|
445
12
|
|
|
446
|
-
/**
|
|
447
|
-
* Close connection
|
|
448
|
-
* @returns {Promise<void>}
|
|
449
|
-
*/
|
|
450
|
-
async close() {
|
|
451
|
-
if (this.connected) {
|
|
452
|
-
this._debug('Closing connection...');
|
|
453
|
-
try {
|
|
454
|
-
await this._sendCommand('QUIT');
|
|
455
|
-
} catch (err) {
|
|
456
|
-
this._debug('Error during QUIT:', err.message);
|
|
457
|
-
}
|
|
458
|
-
this.socket.end();
|
|
459
|
-
this.connected = false;
|
|
460
|
-
this.authenticated = false;
|
|
461
|
-
this._debug('Connection closed');
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
13
|
|
|
465
|
-
|
|
466
|
-
* Disconnect (alias for close)
|
|
467
|
-
* @returns {Promise<void>}
|
|
468
|
-
*/
|
|
469
|
-
async disconnect() {
|
|
470
|
-
return this.close();
|
|
471
|
-
}
|
|
472
|
-
}
|
|
14
|
+
const FTPClient = require('./lib/FTPClient');
|
|
473
15
|
|
|
474
16
|
module.exports = FTPClient;
|
|
475
17
|
module.exports.FTPClient = FTPClient;
|