molex-ftp-client 2.0.0 → 2.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 +34 -3
- package/README.md +138 -397
- package/lib/FTPClient.js +54 -5
- package/lib/commands.js +183 -111
- package/lib/connection.js +2 -7
- package/lib/performance.js +16 -67
- package/package.json +1 -1
- package/test-comprehensive.js +235 -0
- package/benchmark.js +0 -86
package/README.md
CHANGED
|
@@ -1,28 +1,22 @@
|
|
|
1
1
|
# molex-ftp-client
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/molex-ftp-client)
|
|
4
|
+
[](https://www.npmjs.com/package/molex-ftp-client)
|
|
5
|
+
[](https://opensource.org/licenses/ISC)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
[](package.json)
|
|
8
|
+
|
|
9
|
+
> **Lightweight FTP client built with native Node.js TCP sockets. Zero dependencies, optimized for performance.**
|
|
4
10
|
|
|
5
11
|
## Features
|
|
6
12
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
- ✅ **Upload/download** files with Buffer support
|
|
15
|
-
- ✅ **Smart directory management** - Auto-create nested directories
|
|
16
|
-
- ✅ **Directory operations** (list, cd, mkdir, pwd, ensureDir)
|
|
17
|
-
- ✅ **File operations** (delete, rename, size, exists, modifiedTime)
|
|
18
|
-
- ✅ **Robust exists()** - Properly detects both files and directories
|
|
19
|
-
- ✅ **Connection validation** - Automatic state checking before operations
|
|
20
|
-
- ✅ **Enhanced error messages** - Include context for better debugging
|
|
21
|
-
- ✅ **High-performance TCP** - Optimized socket settings (TCP_NODELAY, buffer tuning)
|
|
22
|
-
- ✅ **Performance presets** - LOW_LATENCY, HIGH_THROUGHPUT, BALANCED modes
|
|
23
|
-
- ✅ **Streaming downloads** - Memory-efficient for large files
|
|
24
|
-
- ✅ **Connection statistics** - Track command count and status
|
|
25
|
-
- ✅ **Clean architecture** - Modular structure with separation of concerns
|
|
13
|
+
- **Zero dependencies** - Uses only native Node.js modules
|
|
14
|
+
- **Promise-based API** - Modern async/await support
|
|
15
|
+
- **TCP optimizations** - TCP_NODELAY and keep-alive applied by default (~2.5 MB/s transfer speeds)
|
|
16
|
+
- **Auto-create directories** - Upload files to nested paths automatically
|
|
17
|
+
- **Streaming support** - Memory-efficient downloads for large files
|
|
18
|
+
- **Full FTP support** - Upload, download, list, delete, rename, chmod, stat, and more
|
|
19
|
+
- **Debug mode** - See all FTP commands and responses in real-time
|
|
26
20
|
|
|
27
21
|
## Installation
|
|
28
22
|
|
|
@@ -35,384 +29,206 @@ npm install molex-ftp-client
|
|
|
35
29
|
```javascript
|
|
36
30
|
const FTPClient = require('molex-ftp-client');
|
|
37
31
|
|
|
38
|
-
const client = new FTPClient(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
32
|
+
const client = new FTPClient();
|
|
33
|
+
|
|
34
|
+
await client.connect({
|
|
35
|
+
host: 'ftp.example.com',
|
|
36
|
+
user: 'username',
|
|
37
|
+
password: 'password'
|
|
42
38
|
});
|
|
43
39
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// Upload file
|
|
54
|
-
await client.upload('Hello World!', '/remote/path/file.txt');
|
|
55
|
-
|
|
56
|
-
// Download file
|
|
57
|
-
const data = await client.download('/remote/path/file.txt');
|
|
58
|
-
console.log(data.toString());
|
|
59
|
-
|
|
60
|
-
// Close connection
|
|
61
|
-
await client.close();
|
|
62
|
-
} catch (err) {
|
|
63
|
-
console.error('FTP Error:', err);
|
|
64
|
-
}
|
|
40
|
+
// Upload with auto-directory creation
|
|
41
|
+
await client.upload('Hello World!', '/path/to/file.txt', true);
|
|
42
|
+
|
|
43
|
+
// Download
|
|
44
|
+
const data = await client.download('/path/to/file.txt');
|
|
45
|
+
console.log(data.toString());
|
|
46
|
+
|
|
47
|
+
await client.close();
|
|
65
48
|
```
|
|
66
49
|
|
|
67
50
|
## Constructor Options
|
|
68
51
|
|
|
69
52
|
```javascript
|
|
70
53
|
const client = new FTPClient({
|
|
71
|
-
debug: false,
|
|
72
|
-
timeout: 30000,
|
|
73
|
-
|
|
74
|
-
logger: console.log, // Custom logger function
|
|
75
|
-
performancePreset: 'BALANCED', // Performance preset: LOW_LATENCY, HIGH_THROUGHPUT, BALANCED
|
|
76
|
-
performance: { // Custom performance tuning (overrides preset)
|
|
77
|
-
noDelay: true, // TCP_NODELAY - disable Nagle's algorithm
|
|
78
|
-
sendBufferSize: 65536, // SO_SNDBUF in bytes
|
|
79
|
-
receiveBufferSize: 65536, // SO_RCVBUF in bytes
|
|
80
|
-
keepAlive: true, // TCP keep-alive
|
|
81
|
-
keepAliveDelay: 10000 // Keep-alive delay in ms
|
|
82
|
-
}
|
|
54
|
+
debug: false, // Enable debug logging
|
|
55
|
+
timeout: 30000, // Command timeout in milliseconds (default: 30000)
|
|
56
|
+
logger: console.log // Custom logger function
|
|
83
57
|
});
|
|
84
58
|
```
|
|
85
59
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
Choose a preset based on your use case:
|
|
89
|
-
|
|
90
|
-
- **`LOW_LATENCY`** - Optimized for small files and interactive operations (fastest response)
|
|
91
|
-
- **`HIGH_THROUGHPUT`** - Optimized for large file transfers (maximum bandwidth)
|
|
92
|
-
- **`BALANCED`** - Good default for most use cases (recommended)
|
|
93
|
-
|
|
94
|
-
```javascript
|
|
95
|
-
// For small files like privileges.xml
|
|
96
|
-
const client = new FTPClient({ performancePreset: 'LOW_LATENCY' });
|
|
97
|
-
|
|
98
|
-
// For large backups or video files
|
|
99
|
-
const client = new FTPClient({ performancePreset: 'HIGH_THROUGHPUT' });
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
## API
|
|
60
|
+
## API Reference
|
|
103
61
|
|
|
104
|
-
### Connection
|
|
62
|
+
### Connection Methods
|
|
105
63
|
|
|
106
64
|
#### `connect(options)`
|
|
107
|
-
|
|
108
|
-
Connect to FTP server.
|
|
109
|
-
|
|
110
65
|
```javascript
|
|
111
66
|
await client.connect({
|
|
112
|
-
host: 'ftp.example.com',
|
|
113
|
-
port: 21,
|
|
114
|
-
user: 'username',
|
|
115
|
-
password: 'password'
|
|
67
|
+
host: 'ftp.example.com', // Required
|
|
68
|
+
port: 21, // Default: 21
|
|
69
|
+
user: 'username', // Default: 'anonymous'
|
|
70
|
+
password: 'password' // Default: 'anonymous@'
|
|
116
71
|
});
|
|
117
72
|
```
|
|
118
73
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
#### `close()` / `disconnect()`
|
|
122
|
-
|
|
123
|
-
Close connection to FTP server.
|
|
124
|
-
|
|
74
|
+
#### `close()`
|
|
125
75
|
```javascript
|
|
126
76
|
await client.close();
|
|
127
77
|
```
|
|
128
78
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
### File Operations
|
|
79
|
+
### File Methods
|
|
132
80
|
|
|
133
81
|
#### `upload(data, remotePath, ensureDir)`
|
|
134
|
-
|
|
135
|
-
Upload file to server.
|
|
136
|
-
|
|
137
82
|
```javascript
|
|
138
|
-
//
|
|
139
|
-
await client.upload(
|
|
140
|
-
|
|
141
|
-
// Upload Buffer
|
|
142
|
-
const buffer = Buffer.from('data');
|
|
143
|
-
await client.upload(buffer, '/path/file.bin');
|
|
144
|
-
|
|
145
|
-
// Upload with automatic directory creation (recommended for nested paths)
|
|
146
|
-
await client.upload('data', '/deep/nested/path/file.txt', true);
|
|
83
|
+
await client.upload('content', '/path/file.txt'); // Basic upload
|
|
84
|
+
await client.upload(buffer, '/path/file.bin'); // Upload Buffer
|
|
85
|
+
await client.upload('content', '/deep/path/file.txt', true); // Auto-create dirs
|
|
147
86
|
```
|
|
148
87
|
|
|
149
|
-
|
|
150
|
-
- `data` (string|Buffer): File content
|
|
151
|
-
- `remotePath` (string): Remote file path
|
|
152
|
-
- `ensureDir` (boolean): Create parent directories if needed (default: false)
|
|
153
|
-
|
|
154
|
-
Returns: `Promise<void>`
|
|
155
|
-
|
|
156
|
-
#### `download(remotePath)`
|
|
157
|
-
|
|
158
|
-
Download file from server.
|
|
159
|
-
|
|
88
|
+
#### `download(remotePath)` → `Buffer`
|
|
160
89
|
```javascript
|
|
161
90
|
const data = await client.download('/path/file.txt');
|
|
162
|
-
console.log(data.toString()); // Convert Buffer to string
|
|
163
91
|
```
|
|
164
92
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
#### `downloadStream(remotePath, writeStream)`
|
|
168
|
-
|
|
169
|
-
Download file as a stream (memory efficient for large files).
|
|
170
|
-
|
|
93
|
+
#### `downloadStream(remotePath, writeStream)` → `number`
|
|
94
|
+
Stream download directly to a writable stream (for saving to disk or processing chunks).
|
|
171
95
|
```javascript
|
|
172
96
|
const fs = require('fs');
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const bytesTransferred = await client.downloadStream('/large-file.bin', writeStream);
|
|
177
|
-
console.log(`Downloaded ${bytesTransferred} bytes`);
|
|
178
|
-
|
|
179
|
-
// Stream to any writable stream
|
|
180
|
-
const { PassThrough } = require('stream');
|
|
181
|
-
const stream = new PassThrough();
|
|
182
|
-
stream.on('data', (chunk) => {
|
|
183
|
-
console.log(`Received ${chunk.length} bytes`);
|
|
184
|
-
});
|
|
185
|
-
await client.downloadStream('/file.txt', stream);
|
|
97
|
+
const fileStream = fs.createWriteStream('./local-file.bin');
|
|
98
|
+
const bytes = await client.downloadStream('/remote.bin', fileStream);
|
|
99
|
+
console.log(`Saved ${bytes} bytes to disk`);
|
|
186
100
|
```
|
|
187
101
|
|
|
188
|
-
**Use this for:** Large files where you don't want to buffer everything in memory.
|
|
189
|
-
|
|
190
|
-
Returns: `Promise<number>` - Total bytes transferred
|
|
191
|
-
|
|
192
102
|
#### `delete(path)`
|
|
193
|
-
|
|
194
|
-
Delete file.
|
|
195
|
-
|
|
196
103
|
```javascript
|
|
197
104
|
await client.delete('/path/file.txt');
|
|
198
105
|
```
|
|
199
106
|
|
|
200
|
-
|
|
107
|
+
#### `removeDir(path, recursive)`
|
|
108
|
+
Remove directory, optionally with all contents.
|
|
109
|
+
```javascript
|
|
110
|
+
await client.removeDir('/path/emptydir'); // Remove empty directory
|
|
111
|
+
await client.removeDir('/path/dir', true); // Delete recursively with contents
|
|
112
|
+
```
|
|
201
113
|
|
|
202
114
|
#### `rename(from, to)`
|
|
203
|
-
|
|
204
|
-
Rename or move file.
|
|
205
|
-
|
|
206
115
|
```javascript
|
|
207
|
-
await client.rename('/old
|
|
116
|
+
await client.rename('/old.txt', '/new.txt');
|
|
208
117
|
```
|
|
209
118
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
#### `size(path)`
|
|
213
|
-
|
|
214
|
-
Get file size in bytes.
|
|
215
|
-
|
|
119
|
+
#### `exists(path)` → `boolean`
|
|
216
120
|
```javascript
|
|
217
|
-
const
|
|
218
|
-
console.log(`File size: ${bytes} bytes`);
|
|
121
|
+
const exists = await client.exists('/path/file.txt');
|
|
219
122
|
```
|
|
220
123
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
#### `exists(path)`
|
|
224
|
-
|
|
225
|
-
Check if file or directory exists.
|
|
226
|
-
|
|
124
|
+
#### `stat(path)` → `Object`
|
|
125
|
+
Get detailed file/directory information.
|
|
227
126
|
```javascript
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const dirExists = await client.exists('/path/to/directory');
|
|
232
|
-
console.log(dirExists ? 'Directory exists' : 'Directory not found');
|
|
127
|
+
const info = await client.stat('/path/file.txt');
|
|
128
|
+
// { exists: true, size: 1024, isFile: true, isDirectory: false }
|
|
233
129
|
```
|
|
234
130
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
Get file modification time.
|
|
131
|
+
#### `size(path)` → `number`
|
|
132
|
+
```javascript
|
|
133
|
+
const bytes = await client.size('/path/file.txt');
|
|
134
|
+
```
|
|
240
135
|
|
|
136
|
+
#### `modifiedTime(path)` → `Date`
|
|
241
137
|
```javascript
|
|
242
138
|
const date = await client.modifiedTime('/path/file.txt');
|
|
243
|
-
console.log(`Last modified: ${date.toISOString()}`);
|
|
244
139
|
```
|
|
245
140
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
### Directory Operations
|
|
249
|
-
|
|
250
|
-
#### `list(path)`
|
|
251
|
-
|
|
252
|
-
List directory contents.
|
|
253
|
-
|
|
141
|
+
#### `chmod(path, mode)`
|
|
142
|
+
Change file permissions (Unix/Linux servers only).
|
|
254
143
|
```javascript
|
|
255
|
-
|
|
256
|
-
|
|
144
|
+
await client.chmod('/path/file.txt', '755'); // String format
|
|
145
|
+
await client.chmod('/path/script.sh', 0755); // Octal format
|
|
257
146
|
```
|
|
258
147
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
#### `cd(path)`
|
|
262
|
-
|
|
263
|
-
Change working directory.
|
|
148
|
+
### Directory Methods
|
|
264
149
|
|
|
150
|
+
#### `list(path)` → `string`
|
|
151
|
+
Raw directory listing.
|
|
265
152
|
```javascript
|
|
266
|
-
await client.
|
|
153
|
+
const listing = await client.list('/path');
|
|
267
154
|
```
|
|
268
155
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
#### `pwd()`
|
|
272
|
-
|
|
273
|
-
Get current working directory.
|
|
274
|
-
|
|
156
|
+
#### `listDetailed(path)` → `Array`
|
|
157
|
+
Parsed directory listing with permissions, owner, size, etc.
|
|
275
158
|
```javascript
|
|
276
|
-
const
|
|
277
|
-
|
|
159
|
+
const files = await client.listDetailed('/path');
|
|
160
|
+
// [
|
|
161
|
+
// { name: 'file.txt', type: 'file', permissions: '-rw-r--r--',
|
|
162
|
+
// owner: 'user', group: 'group', size: 1024, date: 'Jan 15 10:30' },
|
|
163
|
+
// { name: 'subdir', type: 'directory', permissions: 'drwxr-xr-x', ... }
|
|
164
|
+
// ]
|
|
278
165
|
```
|
|
279
166
|
|
|
280
|
-
Returns: `Promise<string>`
|
|
281
|
-
|
|
282
167
|
#### `mkdir(path)`
|
|
283
|
-
|
|
284
|
-
Create directory.
|
|
285
|
-
|
|
286
168
|
```javascript
|
|
287
|
-
await client.mkdir('/
|
|
169
|
+
await client.mkdir('/path/newdir');
|
|
288
170
|
```
|
|
289
171
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
#### `ensureDir(dirPath, recursive, isFilePath)`
|
|
293
|
-
|
|
294
|
-
Ensure directory exists, creating it (and parent directories) if necessary. Idempotent - safe to call multiple times.
|
|
295
|
-
|
|
172
|
+
#### `cd(path)`
|
|
296
173
|
```javascript
|
|
297
|
-
|
|
298
|
-
await client.ensureDir('/deep/nested/path');
|
|
299
|
-
|
|
300
|
-
// Create single directory only (will fail if parent doesn't exist)
|
|
301
|
-
await client.ensureDir('/newdir', false);
|
|
302
|
-
|
|
303
|
-
// Ensure parent directory for a file path
|
|
304
|
-
await client.ensureDir('/path/to/file.txt', true, true);
|
|
305
|
-
|
|
306
|
-
// Idempotent - no error if directory already exists
|
|
307
|
-
await client.ensureDir('/existing/path'); // No error
|
|
174
|
+
await client.cd('/path/to/directory');
|
|
308
175
|
```
|
|
309
176
|
|
|
310
|
-
|
|
311
|
-
- `dirPath` (string): Directory path or file path to ensure exists
|
|
312
|
-
- `recursive` (boolean): Create parent directories if needed (default: true)
|
|
313
|
-
- `isFilePath` (boolean): If true, ensures parent directory of file path (default: false)
|
|
314
|
-
|
|
315
|
-
**Use case:** Preparing directory structure before multiple file uploads. For single file uploads, use `upload()` with `ensureDir=true`.
|
|
316
|
-
|
|
317
|
-
Returns: `Promise<void>`
|
|
318
|
-
|
|
319
|
-
### Utilities
|
|
320
|
-
|
|
321
|
-
#### `isConnected()`
|
|
322
|
-
|
|
323
|
-
Check if client is connected and authenticated.
|
|
324
|
-
|
|
177
|
+
#### `pwd()` → `string`
|
|
325
178
|
```javascript
|
|
326
|
-
|
|
327
|
-
console.log('Ready to execute commands');
|
|
328
|
-
} else {
|
|
329
|
-
console.log('Not connected');
|
|
330
|
-
}
|
|
179
|
+
const currentDir = await client.pwd();
|
|
331
180
|
```
|
|
332
181
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
182
|
+
#### `ensureDir(dirPath, recursive, isFilePath)`
|
|
183
|
+
Create directory if it doesn't exist, optionally creating parent directories.
|
|
184
|
+
```javascript
|
|
185
|
+
await client.ensureDir('/deep/nested/path'); // Create full path
|
|
186
|
+
await client.ensureDir('/path/file.txt', true, true); // Ensure parent dir for file
|
|
187
|
+
```
|
|
336
188
|
|
|
337
|
-
|
|
189
|
+
### Utility Methods
|
|
338
190
|
|
|
191
|
+
#### `getState()` → `Object`
|
|
192
|
+
Get current client state for debugging.
|
|
339
193
|
```javascript
|
|
340
|
-
const
|
|
341
|
-
console.log(stats);
|
|
194
|
+
const state = client.getState();
|
|
342
195
|
// {
|
|
343
196
|
// connected: true,
|
|
344
197
|
// authenticated: true,
|
|
345
|
-
//
|
|
346
|
-
//
|
|
198
|
+
// host: 'ftp.example.com',
|
|
199
|
+
// ...
|
|
347
200
|
// }
|
|
348
201
|
```
|
|
349
202
|
|
|
350
|
-
Returns: `Object`
|
|
351
|
-
|
|
352
203
|
#### `setDebug(enabled)`
|
|
353
|
-
|
|
354
|
-
Enable or disable debug mode at runtime.
|
|
355
|
-
|
|
204
|
+
Toggle debug mode at runtime.
|
|
356
205
|
```javascript
|
|
357
|
-
client.setDebug(true);
|
|
358
|
-
client.setDebug(false); // Disable debug logging
|
|
206
|
+
client.setDebug(true);
|
|
359
207
|
```
|
|
360
208
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
The client extends EventEmitter and emits the following events:
|
|
364
|
-
|
|
365
|
-
### `connected`
|
|
366
|
-
|
|
367
|
-
Fired when TCP connection is established.
|
|
368
|
-
|
|
209
|
+
#### `site(command)` → `Object`
|
|
210
|
+
Execute server-specific SITE commands.
|
|
369
211
|
```javascript
|
|
370
|
-
client.
|
|
371
|
-
|
|
372
|
-
});
|
|
212
|
+
await client.site('CHMOD 755 /path/file.txt'); // Alternative chmod
|
|
213
|
+
const response = await client.site('HELP'); // Get server help
|
|
373
214
|
```
|
|
374
215
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
Fired for each FTP response (useful for debugging).
|
|
378
|
-
|
|
379
|
-
```javascript
|
|
380
|
-
client.on('response', (line) => {
|
|
381
|
-
console.log('FTP:', line);
|
|
382
|
-
});
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
### `error`
|
|
386
|
-
|
|
387
|
-
Fired on connection errors.
|
|
216
|
+
## Events
|
|
388
217
|
|
|
389
218
|
```javascript
|
|
390
|
-
client.on('
|
|
391
|
-
|
|
392
|
-
|
|
219
|
+
client.on('connected', () => console.log('TCP connection established'));
|
|
220
|
+
client.on('response', (line) => console.log('FTP:', line));
|
|
221
|
+
client.on('error', (err) => console.error('Error:', err));
|
|
222
|
+
client.on('close', () => console.log('Connection closed'));
|
|
393
223
|
```
|
|
394
224
|
|
|
395
|
-
|
|
225
|
+
## Debugging
|
|
396
226
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
```javascript
|
|
400
|
-
client.on('close', () => {
|
|
401
|
-
console.log('Connection closed');
|
|
402
|
-
});
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
## Debug Mode
|
|
406
|
-
|
|
407
|
-
Enable debug logging to troubleshoot FTP issues:
|
|
227
|
+
Enable debug mode to see all FTP commands and responses:
|
|
408
228
|
|
|
409
229
|
```javascript
|
|
410
230
|
const client = new FTPClient({ debug: true });
|
|
411
231
|
|
|
412
|
-
client.on('response', (line) => {
|
|
413
|
-
console.log('FTP Response:', line);
|
|
414
|
-
});
|
|
415
|
-
|
|
416
232
|
await client.connect({ host: 'ftp.example.com', user: 'user', password: 'pass' });
|
|
417
233
|
// [FTP Debug] Connecting to ftp.example.com:21 as user
|
|
418
234
|
// [FTP Debug] TCP connection established
|
|
@@ -421,147 +237,72 @@ await client.connect({ host: 'ftp.example.com', user: 'user', password: 'pass' }
|
|
|
421
237
|
// [FTP Debug] <<< 331 Password required
|
|
422
238
|
// [FTP Debug] >>> PASS ********
|
|
423
239
|
// [FTP Debug] <<< 230 Login successful
|
|
424
|
-
// [FTP Debug] Authentication successful
|
|
425
240
|
```
|
|
426
241
|
|
|
427
|
-
## Performance
|
|
428
|
-
|
|
429
|
-
This library implements TCP-level optimizations:
|
|
430
|
-
|
|
431
|
-
### TCP_NODELAY (Nagle's Algorithm)
|
|
432
|
-
|
|
433
|
-
By default, the client uses `TCP_NODELAY` which disables Nagle's algorithm. This reduces latency by sending packets immediately instead of buffering them.
|
|
434
|
-
|
|
435
|
-
**When to use:**
|
|
436
|
-
- Small file transfers
|
|
437
|
-
- Interactive operations
|
|
438
|
-
- When latency matters more than bandwidth
|
|
439
|
-
|
|
440
|
-
**Trade-off:** Slightly more network overhead, but much faster response times.
|
|
242
|
+
## Performance
|
|
441
243
|
|
|
442
|
-
|
|
244
|
+
TCP optimizations are automatically applied:
|
|
245
|
+
- **TCP_NODELAY** - Disables Nagle's algorithm for lower latency
|
|
246
|
+
- **Keep-alive** - Detects dead connections (10s interval)
|
|
443
247
|
|
|
444
|
-
|
|
248
|
+
**Typical transfer speeds:** ~2.5 MB/s for 1MB files over standard internet connections.
|
|
445
249
|
|
|
446
|
-
|
|
447
|
-
// Low latency (32KB buffers) - fast for small files
|
|
448
|
-
const client = new FTPClient({ performancePreset: 'LOW_LATENCY' });
|
|
449
|
-
|
|
450
|
-
// High throughput (128KB buffers) - better for large files
|
|
451
|
-
const client = new FTPClient({ performancePreset: 'HIGH_THROUGHPUT' });
|
|
452
|
-
```
|
|
453
|
-
|
|
454
|
-
### Streaming for Large Files
|
|
455
|
-
|
|
456
|
-
Use `downloadStream()` for memory-efficient transfers:
|
|
250
|
+
For large files, use `downloadStream()` to save directly to disk without buffering in memory:
|
|
457
251
|
|
|
458
252
|
```javascript
|
|
459
253
|
const fs = require('fs');
|
|
460
|
-
const
|
|
461
|
-
await client.downloadStream('/backup.zip',
|
|
254
|
+
const fileStream = fs.createWriteStream('./large-backup.zip');
|
|
255
|
+
const bytes = await client.downloadStream('/backup.zip', fileStream);
|
|
256
|
+
console.log(`Saved ${bytes} bytes to disk`);
|
|
462
257
|
```
|
|
463
258
|
|
|
464
|
-
**Benefits:**
|
|
465
|
-
- Constant memory usage (no buffering entire file)
|
|
466
|
-
- Start processing data before download completes
|
|
467
|
-
- Better for files > 10MB
|
|
468
|
-
|
|
469
|
-
### Performance Comparison
|
|
470
|
-
|
|
471
|
-
For a typical privileges.xml (< 100KB):
|
|
472
|
-
- **Without optimization:** ~150-200ms
|
|
473
|
-
- **With LOW_LATENCY preset:** ~80-120ms
|
|
474
|
-
- **40-50% faster** response time
|
|
475
|
-
|
|
476
259
|
## Error Handling
|
|
477
260
|
|
|
478
|
-
All methods return promises and will reject on errors:
|
|
479
|
-
|
|
480
261
|
```javascript
|
|
481
262
|
try {
|
|
482
263
|
await client.upload('data', '/readonly/file.txt');
|
|
483
264
|
} catch (err) {
|
|
484
265
|
if (err.message.includes('FTP Error 550')) {
|
|
485
266
|
console.error('Permission denied');
|
|
486
|
-
} else {
|
|
487
|
-
console.error('Upload failed:', err.message);
|
|
488
267
|
}
|
|
489
268
|
}
|
|
490
269
|
```
|
|
491
270
|
|
|
492
|
-
##
|
|
271
|
+
## Example
|
|
493
272
|
|
|
494
273
|
```javascript
|
|
495
274
|
const FTPClient = require('molex-ftp-client');
|
|
496
275
|
|
|
497
|
-
async function
|
|
498
|
-
const client = new FTPClient({
|
|
499
|
-
debug: true,
|
|
500
|
-
timeout: 60000
|
|
501
|
-
});
|
|
276
|
+
async function main() {
|
|
277
|
+
const client = new FTPClient({ debug: true });
|
|
502
278
|
|
|
503
279
|
try {
|
|
504
|
-
// Connect
|
|
505
280
|
await client.connect({
|
|
506
|
-
host: 'ftp.
|
|
507
|
-
port: 21,
|
|
281
|
+
host: 'ftp.example.com',
|
|
508
282
|
user: 'admin',
|
|
509
|
-
password: '
|
|
283
|
+
password: 'secret'
|
|
510
284
|
});
|
|
511
285
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
const oldData = await client.download('/backup/data.json');
|
|
519
|
-
console.log('Old backup size:', oldData.length, 'bytes');
|
|
520
|
-
|
|
521
|
-
// Get modification time
|
|
522
|
-
const modTime = await client.modifiedTime('/backup/data.json');
|
|
523
|
-
console.log('Last modified:', modTime.toISOString());
|
|
524
|
-
|
|
525
|
-
// Rename old backup
|
|
526
|
-
await client.rename('/backup/data.json', '/backup/data.old.json');
|
|
286
|
+
// Check file info
|
|
287
|
+
const info = await client.stat('/backup/data.json');
|
|
288
|
+
if (info.exists) {
|
|
289
|
+
console.log(`File size: ${info.size} bytes`);
|
|
290
|
+
const data = await client.download('/backup/data.json');
|
|
291
|
+
console.log('Downloaded:', data.toString());
|
|
527
292
|
}
|
|
528
293
|
|
|
529
|
-
// Upload new
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
console.log('Backup uploaded successfully');
|
|
533
|
-
|
|
534
|
-
// Verify
|
|
535
|
-
const size = await client.size('/backup/data.json');
|
|
536
|
-
console.log('New backup size:', size, 'bytes');
|
|
537
|
-
|
|
538
|
-
// Get stats
|
|
539
|
-
const stats = client.getStats();
|
|
540
|
-
console.log('Commands executed:', stats.commandCount);
|
|
541
|
-
|
|
542
|
-
// Close connection
|
|
294
|
+
// Upload new file
|
|
295
|
+
await client.upload('new data', '/backup/updated.json', true);
|
|
296
|
+
|
|
543
297
|
await client.close();
|
|
544
298
|
} catch (err) {
|
|
545
|
-
console.error('
|
|
546
|
-
await client.close();
|
|
299
|
+
console.error('FTP Error:', err.message);
|
|
547
300
|
}
|
|
548
301
|
}
|
|
549
302
|
|
|
550
|
-
|
|
303
|
+
main();
|
|
551
304
|
```
|
|
552
305
|
|
|
553
|
-
## Architecture
|
|
554
|
-
|
|
555
|
-
```
|
|
556
|
-
molex-ftp-client/
|
|
557
|
-
├── index.js # Entry point - exports FTPClient
|
|
558
|
-
├── lib/
|
|
559
|
-
│ ├── FTPClient.js # Main class definition & public API
|
|
560
|
-
│ ├── connection.js # Connection & authentication logic
|
|
561
|
-
│ ├── commands.js # FTP command implementations
|
|
562
|
-
│ └── utils.js # Helper functions (parsing, paths)
|
|
563
|
-
```
|
|
564
306
|
## License
|
|
565
307
|
|
|
566
|
-
ISC
|
|
567
|
-
|
|
308
|
+
ISC License
|