basic-ftp
Advanced tools
Comparing version 2.13.2 to 2.14.0
@@ -8,3 +8,3 @@ { | ||
"parserOptions": { | ||
"ecmaVersion": 8 | ||
"ecmaVersion": 9 | ||
}, | ||
@@ -11,0 +11,0 @@ "rules": { |
# Changelog | ||
## 2.14.0 | ||
- Improved error handling and reporting. | ||
## 2.13.2 | ||
@@ -4,0 +8,0 @@ |
@@ -44,3 +44,3 @@ "use strict"; | ||
this.ftp = new FTPContext(timeout); | ||
this.prepareTransfer = prepareTransferAutoDetect; | ||
this.prepareTransfer = enterFirstCompatibleMode(enterPassiveModeIPv6, enterPassiveModeIPv4); | ||
this.parseList = parseListAutoDetect; | ||
@@ -494,3 +494,6 @@ this._progressTracker = new ProgressTracker(); | ||
TransferResolver | ||
} | ||
}, | ||
// enterFirstCompatibleMode, | ||
// enterPassiveModeIPv4, | ||
// enterPassiveModeIPv6, | ||
}; | ||
@@ -556,18 +559,20 @@ | ||
*/ | ||
async function prepareTransferAutoDetect(client) { | ||
client.ftp.log("Trying to find optimal transfer strategy..."); | ||
for (const strategy of [ enterPassiveModeIPv6, enterPassiveModeIPv4 ]) { | ||
try { | ||
const res = await strategy(client); | ||
client.ftp.log("Optimal transfer strategy found."); | ||
client.prepareTransfer = strategy; // First strategy that works will be used from now on. | ||
return res; | ||
} | ||
catch(err) { | ||
if (!err.code) { // Don't log out FTP response error again. | ||
client.ftp.log(err.toString()); | ||
function enterFirstCompatibleMode(...strategies) { | ||
return async function autoDetect(client) { | ||
client.ftp.log("Trying to find optimal transfer strategy..."); | ||
for (const strategy of strategies) { | ||
try { | ||
const res = await strategy(client); | ||
client.ftp.log("Optimal transfer strategy found."); | ||
client.prepareTransfer = strategy; // First strategy that works will be used from now on. | ||
return res; | ||
} | ||
catch(err) { | ||
if (!err.code) { // Don't log out FTP response error again. | ||
client.ftp.log(err.toString()); | ||
} | ||
} | ||
} | ||
} | ||
throw new Error("None of the available transfer strategies work."); | ||
throw new Error("None of the available transfer strategies work."); | ||
}; | ||
} | ||
@@ -574,0 +579,0 @@ |
@@ -59,6 +59,3 @@ "use strict"; | ||
if (this._socket) { | ||
// socket.removeAllListeners() without name doesn't work: https://github.com/nodejs/node/issues/20923 | ||
this._socket.removeAllListeners("timeout"); | ||
this._socket.removeAllListeners("data"); | ||
this._socket.removeAllListeners("error"); | ||
this._removeSocketListeners(this._socket); | ||
} | ||
@@ -69,4 +66,3 @@ if (socket) { | ||
socket.on("data", data => this._onControlSocketData(data)); | ||
socket.once("error", error => this._closeWithError(error)); | ||
socket.once("timeout", () => this._closeWithError("Timeout control connection")); | ||
this._setupErrorHandlers(socket, "control"); | ||
} | ||
@@ -93,4 +89,3 @@ else { | ||
socket.setTimeout(this._timeout); | ||
socket.once("error", error => this._closeWithError(error)); | ||
socket.once("timeout", () => this._closeWithError("Timeout data connection")); | ||
this._setupErrorHandlers(socket, "data"); | ||
} | ||
@@ -212,2 +207,18 @@ this._dataSocket = socket; | ||
/** | ||
* Setup all error handlers for a socket. | ||
* | ||
* @param {Socket} socket | ||
* @param {string} identifier | ||
*/ | ||
_setupErrorHandlers(socket, identifier) { | ||
socket.once("error", error => this._closeWithError({ ...error, ftpSocket: identifier })); | ||
socket.once("timeout", () => this._closeWithError({ info: "socket timeout", ftpSocket: identifier })); | ||
socket.once("close", hadError => { | ||
if (hadError) { | ||
this._closeWithError({ info: "socket closed due to transmission error", ftpSocket: identifier}); | ||
} | ||
}); | ||
} | ||
/** | ||
* Send an error to the current handler and close all connections. | ||
@@ -231,8 +242,19 @@ * | ||
socket.destroy(); | ||
// socket.removeAllListeners() without name doesn't work: https://github.com/nodejs/node/issues/20923 | ||
socket.removeAllListeners("timeout"); | ||
socket.removeAllListeners("data"); | ||
socket.removeAllListeners("error"); | ||
this._removeSocketListeners(socket); | ||
} | ||
} | ||
/** | ||
* Remove all default listeners for socket. | ||
* | ||
* socket.removeAllListeners() without name doesn't work: https://github.com/nodejs/node/issues/20923 | ||
* | ||
* @param {Socket} socket | ||
*/ | ||
_removeSocketListeners(socket) { | ||
socket.removeAllListeners("timeout"); | ||
socket.removeAllListeners("data"); | ||
socket.removeAllListeners("error"); | ||
socket.removeAllListeners("close"); | ||
} | ||
}; |
{ | ||
"name": "basic-ftp", | ||
"version": "2.13.2", | ||
"version": "2.14.0", | ||
"description": "FTP client for Node.js with support for explicit FTPS over TLS.", | ||
@@ -30,5 +30,5 @@ "main": "./lib/ftp", | ||
"devDependencies": { | ||
"eslint": "5.0.1", | ||
"eslint": "5.6.0", | ||
"mocha": "5.2.0" | ||
} | ||
} |
@@ -27,2 +27,3 @@ # Basic FTP | ||
const client = new ftp.Client() | ||
client.ftp.verbose = true | ||
try { | ||
@@ -185,26 +186,42 @@ await client.access({ | ||
Here are examples for the 3 different types of error messages you'll receive: | ||
Here are examples for the different types of error messages you'll receive: | ||
### FTP response | ||
### FTP error response | ||
``` | ||
FTP response messages that have been interpreted as errors will throw an exception that holds an object describing the response message and the FTP response code. | ||
```js | ||
{ | ||
code: [FTP error code], | ||
message: [Complete FTP response including code] | ||
code: 530, // FTP response code | ||
message: '530 Login failed.' // Complete FTP response message including code | ||
} | ||
``` | ||
### Timeout | ||
### Timeouts | ||
``` | ||
The following format will be encountered for timeouts or closed connections due to transmission error. | ||
```js | ||
{ | ||
error: "Timeout control socket" | ||
error: { | ||
info: 'socket timeout', // Error type | ||
ftpSocket: 'data' // Responsible socket, 'data' or 'control'. | ||
} | ||
} | ||
``` | ||
### Connection error or else | ||
### General connection or other errors | ||
``` | ||
Error objects from Node.js also have an additional field `ftpSocket` that says which socket was responsible for the error. There are two identifiers: `control` for control connections and `data` for data connections. | ||
```js | ||
{ | ||
error: [Error object by Node] | ||
error: { | ||
errno: 'ENETUNREACH', // Typical Node.js error object… | ||
code: 'ENETUNREACH', | ||
syscall: 'connect', | ||
address: '192.168.1.123', | ||
port: 21 | ||
ftpSocket: 'control' // Responsible socket | ||
} | ||
} | ||
@@ -211,0 +228,0 @@ ``` |
@@ -99,3 +99,3 @@ const assert = require("assert"); | ||
reply: undefined, | ||
result: new MockError({ error: { info: "SocketError" } }) | ||
result: new MockError({ error: { info: "SocketError", ftpSocket: "control" } }) | ||
}, | ||
@@ -102,0 +102,0 @@ { |
@@ -36,3 +36,3 @@ const assert = require("assert"); | ||
ftp.handle(undefined, (res, task) => { | ||
assert.deepEqual(res, { error: "Timeout control connection" }); | ||
assert.deepEqual(res, { error: { info: "socket timeout", ftpSocket: "control" }}); | ||
done(); | ||
@@ -45,3 +45,3 @@ }); | ||
ftp.handle(undefined, (res, task) => { | ||
assert.deepEqual(res, { error: { foo: "bar" } }); | ||
assert.deepEqual(res, { error: { foo: "bar", ftpSocket: "control" } }); | ||
done(); | ||
@@ -54,3 +54,3 @@ }); | ||
ftp.handle(undefined, (res, task) => { | ||
assert.deepEqual(res, { error: "Timeout data connection" }); | ||
assert.deepEqual(res, { error: { info: "socket timeout", ftpSocket: "data" }}); | ||
done(); | ||
@@ -63,3 +63,3 @@ }); | ||
ftp.handle(undefined, (res, task) => { | ||
assert.deepEqual(res, { error: { foo: "bar" } }); | ||
assert.deepEqual(res, { error: { foo: "bar", ftpSocket: "data" } }); | ||
done(); | ||
@@ -66,0 +66,0 @@ }); |
109675
31
2369
302