ssh2-sftp-client
Advanced tools
Comparing version 4.1.0 to 4.2.0
{ | ||
"name": "ssh2-sftp-client", | ||
"version": "4.1.0", | ||
"version": "4.2.0", | ||
"description": "ssh2 sftp client for node", | ||
@@ -22,3 +22,3 @@ "main": "src/index.js", | ||
"name": "见见 (original author)", | ||
"email": "jyu213@gmail.com" | ||
"email": "jyu213@gmail.com" | ||
} | ||
@@ -25,0 +25,0 @@ ], |
309
README.md
# Table of Contents | ||
1. [SSH2 SFTP Client](#org43ca01c) | ||
2. [Installation](#org85ec107) | ||
3. [Basic Usage](#org7ca5e28) | ||
4. [Breaking Changes in Version 4.x](#org11186bf) | ||
5. [Enhancements in Version 4.1.x](#orge5f7732) | ||
6. [Documentation](#org8b8cd18) | ||
1. [Methods](#org820f1d6) | ||
1. [connect(config) ===> SFTPstream](#org83fbd25) | ||
2. [list(path, pattern) ==> Array[object]](#org2871c47) | ||
3. [exists(path) ==> boolean](#org1f19090) | ||
4. [stat(path) ==> object](#org625195b) | ||
5. [get(path, dst, options) ==> String|Stream|Buffer](#org2c1f4a0) | ||
6. [fastGet(remotePath, localPath, options) ===> string](#orgdc896c4) | ||
7. [put(src, remotePath, options) ==> string](#orgb196a2e) | ||
8. [fastPut(localPath, remotePath, options) ==> string](#org243adae) | ||
9. [append(input, remotePath, options) ==> string](#org7d9d07f) | ||
10. [mkdir(path, recursive) ==> string](#orgb972a31) | ||
11. [rmdir(path, recursive) ==> string](#org5db8003) | ||
12. [delete(path) ==> string](#org6301aa9) | ||
13. [rename(fromPath, toPath) ==> string](#orgae0f051) | ||
14. [chmod(path, mode) ==> string](#org462d46f) | ||
15. [realPath(path) ===> string](#orgde5aa73) | ||
16. [cwd() ==> string](#org705f820) | ||
17. [end() ==> boolean](#org40dd1ae) | ||
18. [Add and Remove Listeners](#org169bbdb) | ||
7. [FAQ](#org0dd8098) | ||
1. [How can you pass writable stream as dst for get method?](#org93da735) | ||
2. [How can I upload files without having to specify a password?](#orgde3dfae) | ||
3. [How can I connect through a Socks Proxy](#org97b11c7) | ||
8. [Change Log](#orgcd88ff2) | ||
1. [v4.1.0 (Prod Version)](#org4624fd1) | ||
2. [v4.0.4](#org99482d5) | ||
3. [v4.0.3](#orga917421) | ||
4. [v4.0.2](#orgc8b9bd3) | ||
5. [v4.0.0](#org56a1855) | ||
6. [v2.5.2](#org7c011ac) | ||
7. [v2.5.1](#orgfb00d0f) | ||
8. [v2.5.0](#org175b195) | ||
9. [v2.4.3](#org5c185aa) | ||
10. [v2.4.2](#org70e34eb) | ||
11. [v2.4.1](#orgcc4683d) | ||
12. [v2.4.0](#org4413262) | ||
13. [v2.3.0](#orgd6201fe) | ||
14. [v3.0.0 – deprecate this version](#org24ca85f) | ||
15. [v2.1.1](#org6410067) | ||
16. [v2.0.1](#orgd0190f0) | ||
17. [v1.1.0](#org2007ebd) | ||
18. [v1.0.5:](#org6eb1f80) | ||
9. [Logging Issues](#orgec61686) | ||
10. [Pull Requests](#orgb91eb96) | ||
11. [Contributors](#orgdd815db) | ||
1. [SSH2 SFTP Client](#org2d4beb0) | ||
2. [Installation](#org1b2885a) | ||
3. [Basic Usage](#org225126f) | ||
4. [Breaking Changes in Version 4.x](#org2cf43bd) | ||
5. [Enhancements in Version 4.2.x](#org30c96ac) | ||
6. [Enhancements in Version 4.1.x](#org5777d99) | ||
7. [Documentation](#org8c25b6e) | ||
1. [Methods](#org8f330c9) | ||
1. [new SftpClient(name) ===> SFTP client object](#orgb660d8f) | ||
2. [connect(config) ===> SFTPstream](#org1a60cfb) | ||
3. [list(path, pattern) ==> Array[object]](#org8a1dff4) | ||
4. [exists(path) ==> boolean](#orgaa778db) | ||
5. [stat(path) ==> object](#org5184595) | ||
6. [get(path, dst, options) ==> String|Stream|Buffer](#orga0dfcd5) | ||
7. [fastGet(remotePath, localPath, options) ===> string](#org1f07b2e) | ||
8. [put(src, remotePath, options) ==> string](#org372e9a1) | ||
9. [fastPut(localPath, remotePath, options) ==> string](#org6c596b7) | ||
10. [append(input, remotePath, options) ==> string](#orgfdb9a8f) | ||
11. [mkdir(path, recursive) ==> string](#orgd57aded) | ||
12. [rmdir(path, recursive) ==> string](#orgdb74ea9) | ||
13. [delete(path) ==> string](#orga755e94) | ||
14. [rename(fromPath, toPath) ==> string](#org74fb8ca) | ||
15. [chmod(path, mode) ==> string](#orgf44a17a) | ||
16. [realPath(path) ===> string](#orge8bc11c) | ||
17. [cwd() ==> string](#orgc9d3058) | ||
18. [end() ==> boolean](#org72b3f6f) | ||
19. [Add and Remove Listeners](#orge7b5b8a) | ||
8. [FAQ](#org06ab353) | ||
1. [Remote server drops connections with only an end event](#org5bac919) | ||
2. [How can you pass writable stream as dst for get method?](#org5e6420c) | ||
3. [How can I upload files without having to specify a password?](#org6ec13f2) | ||
4. [How can I connect through a Socks Proxy](#org5501a07) | ||
9. [Change Log](#orgf32a1e7) | ||
1. [v4.2.0 (Prod Version)](#org8f7647b) | ||
2. [v4.1.0](#org790e255) | ||
3. [v4.0.4](#orgbc6936f) | ||
4. [v4.0.3](#orga5291fb) | ||
5. [v4.0.2](#orgbf82349) | ||
6. [v4.0.0](#org9d05647) | ||
7. [v2.5.2](#orgee92c3b) | ||
8. [v2.5.1](#orgca044c2) | ||
9. [v2.5.0](#orgfd95552) | ||
10. [v2.4.3](#orge8df065) | ||
11. [v2.4.2](#org1c6d986) | ||
12. [v2.4.1](#orgc755940) | ||
13. [v2.4.0](#orgc29393a) | ||
14. [v2.3.0](#orgd43c13b) | ||
15. [v3.0.0 – deprecate this version](#orgc15e6ab) | ||
16. [v2.1.1](#org3d8c204) | ||
17. [v2.0.1](#org186bc44) | ||
18. [v1.1.0](#orgeec7323) | ||
19. [v1.0.5:](#org54b2653) | ||
10. [Logging Issues](#org8c07de1) | ||
11. [Pull Requests](#orge0fc98c) | ||
12. [Contributors](#org3d70747) | ||
<a id="org43ca01c"></a> | ||
<a id="org2d4beb0"></a> | ||
@@ -74,3 +78,3 @@ # SSH2 SFTP Client | ||
<a id="org85ec107"></a> | ||
<a id="org1b2885a"></a> | ||
@@ -82,3 +86,3 @@ # Installation | ||
<a id="org7ca5e28"></a> | ||
<a id="org225126f"></a> | ||
@@ -104,3 +108,3 @@ # Basic Usage | ||
<a id="org11186bf"></a> | ||
<a id="org2cf43bd"></a> | ||
@@ -133,4 +137,23 @@ # Breaking Changes in Version 4.x | ||
<a id="orge5f7732"></a> | ||
<a id="org30c96ac"></a> | ||
# Enhancements in Version 4.2.x | ||
- Added ability to set a client name in `Client()` constructor. This can be | ||
useful when creating multiple clients as the client name will be displayed | ||
in error messages, providing a clue as to which client has failed. | ||
- Added a work-around for the SSH2 bug which results in only an `end` event | ||
being raised when the remote server drops the connection during the connect | ||
process. (see FAQ section below). | ||
- Added additional error checking to prevent attempts to call `connect()` on | ||
an already connected client. While a client can be used to make multiple | ||
connections, you must call `end()` before calling `connect()` again. Each | ||
client object can only represent a single connection to an SFTP | ||
server. However, some methods, such as `fastPut()` and `fastGet()` will use | ||
concurrency to speed up the transfer of data. | ||
- Added some more examples in the example directory of the repository | ||
<a id="org5777d99"></a> | ||
# Enhancements in Version 4.1.x | ||
@@ -147,3 +170,3 @@ | ||
<a id="org8b8cd18"></a> | ||
<a id="org8c25b6e"></a> | ||
@@ -159,3 +182,3 @@ # Documentation | ||
<a id="org820f1d6"></a> | ||
<a id="org8f330c9"></a> | ||
@@ -165,4 +188,43 @@ ## Methods | ||
<a id="org83fbd25"></a> | ||
<a id="orgb660d8f"></a> | ||
### new SftpClient(name) ===> SFTP client object | ||
Constructor to create a new `ssh2-sftp-client` object. An optional `name` string | ||
can be provided, which will be used in error messages to help identify which | ||
client has thrown the error. | ||
1. Constructor arguments | ||
- **name:** string. An optional name string used in error messages | ||
2. Example Used | ||
'use strict'; | ||
const Client = require('ssh2-sftp-client'); | ||
const config = { | ||
host: 'example.com', | ||
user: 'donald', | ||
password: 'my-secret' | ||
}; | ||
const sftp = new Client('example-client'); | ||
sftp.connect(config) | ||
.then(() => { | ||
return sftp.cwd(); | ||
}) | ||
.then(p => { | ||
console.log(`Remote working directory is ${p}`); | ||
return sftp.end(); | ||
}) | ||
.catch(err => { | ||
console.log(`Error: ${err.message}`); // error message will include 'example-client' | ||
}); | ||
<a id="org1a60cfb"></a> | ||
### connect(config) ===> SFTPstream | ||
@@ -236,3 +298,3 @@ | ||
<a id="org2871c47"></a> | ||
<a id="org8a1dff4"></a> | ||
@@ -310,3 +372,3 @@ ### list(path, pattern) ==> Array[object] | ||
<a id="org1f19090"></a> | ||
<a id="orgaa778db"></a> | ||
@@ -346,3 +408,3 @@ ### exists(path) ==> boolean | ||
<a id="org625195b"></a> | ||
<a id="org5184595"></a> | ||
@@ -394,3 +456,3 @@ ### stat(path) ==> object | ||
<a id="org2c1f4a0"></a> | ||
<a id="orga0dfcd5"></a> | ||
@@ -455,3 +517,3 @@ ### get(path, dst, options) ==> String|Stream|Buffer | ||
<a id="orgdc896c4"></a> | ||
<a id="org1f07b2e"></a> | ||
@@ -499,3 +561,3 @@ ### fastGet(remotePath, localPath, options) ===> string | ||
<a id="orgb196a2e"></a> | ||
<a id="org372e9a1"></a> | ||
@@ -553,3 +615,3 @@ ### put(src, remotePath, options) ==> string | ||
<a id="org243adae"></a> | ||
<a id="org6c596b7"></a> | ||
@@ -597,3 +659,3 @@ ### fastPut(localPath, remotePath, options) ==> string | ||
<a id="org7d9d07f"></a> | ||
<a id="orgfdb9a8f"></a> | ||
@@ -643,3 +705,3 @@ ### append(input, remotePath, options) ==> string | ||
<a id="orgb972a31"></a> | ||
<a id="orgd57aded"></a> | ||
@@ -673,3 +735,3 @@ ### mkdir(path, recursive) ==> string | ||
<a id="org5db8003"></a> | ||
<a id="orgdb74ea9"></a> | ||
@@ -704,3 +766,3 @@ ### rmdir(path, recursive) ==> string | ||
<a id="org6301aa9"></a> | ||
<a id="orga755e94"></a> | ||
@@ -730,3 +792,3 @@ ### delete(path) ==> string | ||
<a id="orgae0f051"></a> | ||
<a id="org74fb8ca"></a> | ||
@@ -756,3 +818,3 @@ ### rename(fromPath, toPath) ==> string | ||
<a id="org462d46f"></a> | ||
<a id="orgf44a17a"></a> | ||
@@ -785,3 +847,3 @@ ### chmod(path, mode) ==> string | ||
<a id="orgde5aa73"></a> | ||
<a id="orge8bc11c"></a> | ||
@@ -796,3 +858,3 @@ ### realPath(path) ===> string | ||
<a id="org705f820"></a> | ||
<a id="orgc9d3058"></a> | ||
@@ -804,3 +866,3 @@ ### cwd() ==> string | ||
<a id="org40dd1ae"></a> | ||
<a id="org72b3f6f"></a> | ||
@@ -828,3 +890,3 @@ ### end() ==> boolean | ||
<a id="org169bbdb"></a> | ||
<a id="orge7b5b8a"></a> | ||
@@ -855,3 +917,3 @@ ### Add and Remove Listeners | ||
<a id="org0dd8098"></a> | ||
<a id="org06ab353"></a> | ||
@@ -861,4 +923,34 @@ # FAQ | ||
<a id="org93da735"></a> | ||
<a id="org5bac919"></a> | ||
## Remote server drops connections with only an end event | ||
Many SFTP servers have rate limiting protection which will drop connections once | ||
a limit has been reached. In particular, openSSH has the setting `MaxStartups`, | ||
which can be a tuple of the form `max:drop:full` where `max` is the maximum | ||
allowed unauthenticated connections, `drop` is a percentage value which | ||
specifies percentage of connections to be dropped once `max` connections has | ||
been reached and `full` is the number of connections at which point all | ||
subsequent connections will be dropped. e.g. `10:30:60` means allow up to 10 | ||
unauthenticated connections after which drop 30% of connection attempts until | ||
reaching 60 unauthenticated connections, at which time, drop all attempts. | ||
Clients first make an unauthenticated connection to the SFTP server to begin | ||
negotiation of protocol settings (cipher, authentication method etc). If you are | ||
creating multiple connections in a script, it is easy to exceed the limit, | ||
resulting in some connections being dropped. As SSH2 only raises an 'end' event | ||
for these dropped connections, no error is detected. The `ssh2-sftp-client` now | ||
listens for `end` events during the connection process and if one is detected, | ||
will reject the connection promise. | ||
One way to avoid this type of issue is to add a delay between connection | ||
attempts. It does not need to be a very long delay - just sufficient to permit | ||
the previous connection to be authenticated. In fact, the default setting for | ||
openSSH is `10:30:60`, so you really just need to have enough delay to ensure | ||
that the 1st connection has completed authentication before the 11th connection | ||
is attempted. | ||
<a id="org5e6420c"></a> | ||
## How can you pass writable stream as dst for get method? | ||
@@ -926,3 +1018,3 @@ | ||
<a id="orgde3dfae"></a> | ||
<a id="org6ec13f2"></a> | ||
@@ -962,3 +1054,3 @@ ## How can I upload files without having to specify a password? | ||
<a id="org97b11c7"></a> | ||
<a id="org5501a07"></a> | ||
@@ -998,3 +1090,3 @@ ## How can I connect through a Socks Proxy | ||
<a id="orgcd88ff2"></a> | ||
<a id="orgf32a1e7"></a> | ||
@@ -1004,6 +1096,17 @@ # Change Log | ||
<a id="org4624fd1"></a> | ||
<a id="org8f7647b"></a> | ||
## v4.1.0 (Prod Version) | ||
## v4.2.0 (Prod Version) | ||
- Work-around for SSH2 `end` event bug | ||
- Added ability to set client name in constructor method | ||
- Added additional error checking to prevent `connect()` being called on | ||
already connected client | ||
- Added additional examples in `example` directory | ||
<a id="org790e255"></a> | ||
## v4.1.0 | ||
- move `end()` call to resolve into close hook | ||
@@ -1022,3 +1125,3 @@ - Prevent `put()` and `get()` from creating empty files in destination when | ||
<a id="org99482d5"></a> | ||
<a id="orgbc6936f"></a> | ||
@@ -1031,3 +1134,3 @@ ## v4.0.4 | ||
<a id="orga917421"></a> | ||
<a id="orga5291fb"></a> | ||
@@ -1040,3 +1143,3 @@ ## v4.0.3 | ||
<a id="orgc8b9bd3"></a> | ||
<a id="orgbf82349"></a> | ||
@@ -1048,3 +1151,3 @@ ## v4.0.2 | ||
<a id="org56a1855"></a> | ||
<a id="org9d05647"></a> | ||
@@ -1067,3 +1170,3 @@ ## v4.0.0 | ||
<a id="org7c011ac"></a> | ||
<a id="orgee92c3b"></a> | ||
@@ -1076,3 +1179,3 @@ ## v2.5.2 | ||
<a id="orgfb00d0f"></a> | ||
<a id="orgca044c2"></a> | ||
@@ -1084,3 +1187,3 @@ ## v2.5.1 | ||
<a id="org175b195"></a> | ||
<a id="orgfd95552"></a> | ||
@@ -1092,3 +1195,3 @@ ## v2.5.0 | ||
<a id="org5c185aa"></a> | ||
<a id="orge8df065"></a> | ||
@@ -1101,3 +1204,3 @@ ## v2.4.3 | ||
<a id="org70e34eb"></a> | ||
<a id="org1c6d986"></a> | ||
@@ -1110,3 +1213,3 @@ ## v2.4.2 | ||
<a id="orgcc4683d"></a> | ||
<a id="orgc755940"></a> | ||
@@ -1119,3 +1222,3 @@ ## v2.4.1 | ||
<a id="org4413262"></a> | ||
<a id="orgc29393a"></a> | ||
@@ -1133,3 +1236,3 @@ ## v2.4.0 | ||
<a id="orgd6201fe"></a> | ||
<a id="orgd43c13b"></a> | ||
@@ -1143,3 +1246,3 @@ ## v2.3.0 | ||
<a id="org24ca85f"></a> | ||
<a id="orgc15e6ab"></a> | ||
@@ -1152,3 +1255,3 @@ ## v3.0.0 – deprecate this version | ||
<a id="org6410067"></a> | ||
<a id="org3d8c204"></a> | ||
@@ -1161,3 +1264,3 @@ ## v2.1.1 | ||
<a id="orgd0190f0"></a> | ||
<a id="org186bc44"></a> | ||
@@ -1172,3 +1275,3 @@ ## v2.0.1 | ||
<a id="org2007ebd"></a> | ||
<a id="orgeec7323"></a> | ||
@@ -1180,3 +1283,3 @@ ## v1.1.0 | ||
<a id="org6eb1f80"></a> | ||
<a id="org54b2653"></a> | ||
@@ -1189,3 +1292,3 @@ ## v1.0.5: | ||
<a id="orgec61686"></a> | ||
<a id="org8c07de1"></a> | ||
@@ -1199,3 +1302,3 @@ # Logging Issues | ||
<a id="orgb91eb96"></a> | ||
<a id="orge0fc98c"></a> | ||
@@ -1220,3 +1323,3 @@ # Pull Requests | ||
<a id="orgdd815db"></a> | ||
<a id="org3d70747"></a> | ||
@@ -1223,0 +1326,0 @@ # Contributors |
380
src/index.js
@@ -13,4 +13,6 @@ /** | ||
let SftpClient = function() { | ||
let SftpClient = function(clientName = '') { | ||
this.client = new Client(); | ||
this.clientName = clientName; | ||
this.endCalled = false; | ||
}; | ||
@@ -27,3 +29,3 @@ | ||
*/ | ||
function formatError(err, name = 'ssh2-sftp-client', retryCount) { | ||
function formatError(err, name = 'sftp', retryCount) { | ||
let msg = ''; | ||
@@ -39,4 +41,4 @@ | ||
msg = | ||
`${name}: ${err.level} error. Address lookup failed for host ` + | ||
`${err.hostname}`; | ||
`${name}: ` + | ||
`${err.level} error. Address lookup failed for host ${err.hostname}`; | ||
break; | ||
@@ -69,3 +71,5 @@ case 'ECONNREFUSED': | ||
let listeners = emitter.eventNames(); | ||
listeners.map(name => emitter.removeAllListeners(name)); | ||
listeners.forEach(name => { | ||
emitter.removeAllListeners(name); | ||
}); | ||
} | ||
@@ -80,7 +84,17 @@ | ||
*/ | ||
function errorListener(err, source) { | ||
let newErr = formatError(err, source); | ||
throw newErr; | ||
function makeErrorListener(name) { | ||
return function(err) { | ||
throw formatError(err, name); | ||
}; | ||
} | ||
function makeEndListener(client) { | ||
return function() { | ||
if (!client.endCalled) { | ||
client.sftp = undefined; | ||
throw formatError('Connection ended unexpectedly', client.clientName); | ||
} | ||
}; | ||
} | ||
SftpClient.prototype.realPath = function(path) { | ||
@@ -166,13 +180,7 @@ const sftp = this.sftp; | ||
SftpClient.prototype.list = async function(path, pattern = /.*/) { | ||
try { | ||
if (!this.sftp) { | ||
return Promise.reject( | ||
formatError('No SFTP connection available', 'sftp.list') | ||
); | ||
} | ||
let absPath = await this.realPath(path); | ||
return this._list(absPath, pattern); | ||
} catch (err) { | ||
return Promise.reject(formatError(err, 'sftp.list')); | ||
if (!this.sftp) { | ||
throw formatError('No SFTP connection available', 'sftp.list'); | ||
} | ||
let absPath = await this.realPath(path); | ||
return this._list(absPath, pattern); | ||
}; | ||
@@ -237,5 +245,3 @@ | ||
if (!this.sftp) { | ||
return Promise.reject( | ||
formatError('No SFTP connection available', 'sftp.exists') | ||
); | ||
throw formatError('No SFTP connection available', 'sftp.exists'); | ||
} | ||
@@ -246,5 +252,5 @@ let absPath = await this.realPath(remotePath); | ||
if (err.message.match(/No such file/)) { | ||
return Promise.resolve(false); | ||
return false; | ||
} | ||
return Promise.reject(formatError(err, 'sftp.exists')); | ||
throw formatError(err, 'sftp.exists'); | ||
} | ||
@@ -288,13 +294,7 @@ }; | ||
SftpClient.prototype.stat = async function(remotePath) { | ||
try { | ||
if (!this.sftp) { | ||
return Promise.reject( | ||
formatError('No SFTP connection available', 'sftp.stat') | ||
); | ||
} | ||
let absPath = await this.realPath(remotePath); | ||
return this._stat(absPath); | ||
} catch (err) { | ||
return Promise.reject(formatError(err, 'sftp.stat')); | ||
if (!this.sftp) { | ||
throw formatError('No SFTP connection available', 'sftp.stat'); | ||
} | ||
let absPath = await this.realPath(remotePath); | ||
return this._stat(absPath); | ||
}; | ||
@@ -360,26 +360,18 @@ | ||
SftpClient.prototype.get = async function(path, dst, options) { | ||
try { | ||
if (!this.sftp) { | ||
return Promise.reject( | ||
formatError('No SFTP connection available', 'sftp.get') | ||
); | ||
} | ||
if (!this.sftp) { | ||
throw formatError('No SFTP connection available', 'sftp.get'); | ||
} | ||
let absPath = await this.realPath(path); | ||
let absPath = await this.realPath(path); | ||
let stats = await this.stat(absPath); | ||
if ((stats.mode & 0o444) === 0) { | ||
return Promise.reject( | ||
formatError(`No read permission for ${absPath}`, 'sftp.get') | ||
); | ||
} | ||
let stats = await this.stat(absPath); | ||
if ((stats.mode & 0o444) === 0) { | ||
throw formatError(`No read permission for ${absPath}`, 'sftp.get'); | ||
} | ||
if (typeof dst === 'string') { | ||
let dstPath = normalize(dst); | ||
return this._get(absPath, dstPath, options); | ||
} | ||
return this._get(absPath, dst, options); | ||
} catch (err) { | ||
return Promise.reject(formatError(err, 'sftp.get')); | ||
if (typeof dst === 'string') { | ||
let dstPath = normalize(dst); | ||
return this._get(absPath, dstPath, options); | ||
} | ||
return this._get(absPath, dst, options); | ||
}; | ||
@@ -413,14 +405,8 @@ | ||
SftpClient.prototype.fastGet = async function(remotePath, localPath, options) { | ||
try { | ||
if (!this.sftp) { | ||
return Promise.reject( | ||
formatError('No SFTP connection available', 'sftp.fastGet') | ||
); | ||
} | ||
let src = await this.realPath(remotePath); | ||
let dst = normalize(localPath); | ||
return this._fastGet(src, dst, options); | ||
} catch (err) { | ||
return Promise.reject(formatError(err, 'sftp.fastGet')); | ||
if (!this.sftp) { | ||
throw formatError('No SFTP connection available', 'sftp.fastGet'); | ||
} | ||
let src = await this.realPath(remotePath); | ||
let dst = normalize(localPath); | ||
return this._fastGet(src, dst, options); | ||
}; | ||
@@ -455,22 +441,15 @@ | ||
SftpClient.prototype.fastPut = async function(localPath, remotePath, options) { | ||
try { | ||
if (!this.sftp) { | ||
return Promise.reject( | ||
formatError('No SFTP connection available', 'sftp.fastPut') | ||
); | ||
} | ||
let src = fs.realpathSync(localPath); | ||
let dst = remotePath; | ||
if (dst.startsWith('../')) { | ||
let root = await this.realPath('..'); | ||
dst = join(root, dst.substring(3)); | ||
} else if (dst.startsWith('./')) { | ||
let root = await this.realPath('.'); | ||
dst = join(root, dst.substring(2)); | ||
} | ||
return this._fastPut(src, dst, options); | ||
} catch (err) { | ||
return Promise.reject(formatError(err, 'sftp.fastPut')); | ||
if (!this.sftp) { | ||
throw formatError('No SFTP connection available', 'sftp.fastPut'); | ||
} | ||
let src = fs.realpathSync(localPath); | ||
let dst = remotePath; | ||
if (dst.startsWith('../')) { | ||
let root = await this.realPath('..'); | ||
dst = join(root, dst.substring(3)); | ||
} else if (dst.startsWith('./')) { | ||
let root = await this.realPath('.'); | ||
dst = join(root, dst.substring(2)); | ||
} | ||
return this._fastPut(src, dst, options); | ||
}; | ||
@@ -523,25 +502,19 @@ | ||
SftpClient.prototype.put = async function(localSrc, remotePath, options) { | ||
try { | ||
if (!this.sftp) { | ||
return Promise.reject( | ||
formatError('No SFTP connections available', 'sftp.put') | ||
); | ||
} | ||
let src = localSrc; | ||
if (typeof src === 'string') { | ||
src = fs.realpathSync(src); | ||
fs.accessSync(src, fs.constants.R_OK); | ||
} | ||
let dst = remotePath; | ||
if (dst.startsWith('../')) { | ||
let root = await this.realPath('..'); | ||
dst = join(root, dst.substring(3)); | ||
} else if (dst.startsWith('./')) { | ||
let root = await this.realPath('.'); | ||
dst = join(root, dst.substring(2)); | ||
} | ||
return this._put(src, dst, options); | ||
} catch (err) { | ||
return Promise.reject(formatError(err, 'sftp.put')); | ||
if (!this.sftp) { | ||
throw formatError('No SFTP connections available', 'sftp.put'); | ||
} | ||
let src = localSrc; | ||
if (typeof src === 'string') { | ||
src = fs.realpathSync(src); | ||
fs.accessSync(src, fs.constants.R_OK); | ||
} | ||
let dst = remotePath; | ||
if (dst.startsWith('../')) { | ||
let root = await this.realPath('..'); | ||
dst = join(root, dst.substring(3)); | ||
} else if (dst.startsWith('./')) { | ||
let root = await this.realPath('.'); | ||
dst = join(root, dst.substring(2)); | ||
} | ||
return this._put(src, dst, options); | ||
}; | ||
@@ -593,32 +566,20 @@ | ||
SftpClient.prototype.append = async function(input, remotePath, options) { | ||
try { | ||
if (!this.sftp) { | ||
return Promise.reject( | ||
formatError('No SFTP connection available', 'sftp.append') | ||
); | ||
} | ||
if (typeof input === 'string') { | ||
return Promise.reject( | ||
formatError('Cannot append one file to another', 'sftp.append') | ||
); | ||
} | ||
let absPath = await this.realPath(remotePath); | ||
let stats = await this.stat(absPath); | ||
if ((stats.mode & 0o0100000) === 0) { | ||
return Promise.reject( | ||
formatError( | ||
`${remotePath} Remote path must be a regular file`, | ||
'sftp-append' | ||
) | ||
); | ||
} | ||
if ((stats.mode & 0o0444) === 0) { | ||
return Promise.reject( | ||
formatError(`${remotePath} No write permission`, 'sftp-append') | ||
); | ||
} | ||
return this._append(input, absPath, options); | ||
} catch (err) { | ||
return Promise.reject(formatError(err, 'sftp.append')); | ||
if (!this.sftp) { | ||
throw formatError('No SFTP connection available', 'sftp.append'); | ||
} | ||
if (typeof input === 'string') { | ||
throw formatError('Cannot append one file to another', 'sftp.append'); | ||
} | ||
let absPath = await this.realPath(remotePath); | ||
let stats = await this.stat(absPath); | ||
if ((stats.mode & 0o0100000) === 0) { | ||
throw formatError( | ||
`${remotePath} Remote path must be a regular file`, | ||
'sftp-append' | ||
); | ||
} | ||
if ((stats.mode & 0o0444) === 0) { | ||
throw formatError(`${remotePath} No write permission`, 'sftp-append'); | ||
} | ||
return this._append(input, absPath, options); | ||
}; | ||
@@ -651,5 +612,3 @@ | ||
if (!sftp) { | ||
return Promise.reject( | ||
formatError('No SFTP connection available', 'sftp.mkdir') | ||
); | ||
throw formatError('No SFTP connection available', 'sftp.mkdir'); | ||
} | ||
@@ -674,7 +633,7 @@ let realPath = path; | ||
} else if (parent !== 'd') { | ||
return Promise.reject(formatError('Bad directory path', 'sftp.mkdir')); | ||
throw formatError('Bad directory path', 'sftp.mkdir'); | ||
} | ||
return doMkdir(realPath); | ||
} catch (err) { | ||
return Promise.reject(formatError(err, 'sftp.mkdir')); | ||
throw formatError(err, 'sftp.mkdir'); | ||
} | ||
@@ -713,5 +672,3 @@ }; | ||
if (!sftp) { | ||
return Promise.reject( | ||
formatError('No SFTP connection available', 'sftp.rmdir') | ||
); | ||
throw formatError('No SFTP connection available', 'sftp.rmdir'); | ||
} | ||
@@ -730,3 +687,3 @@ let absPath = await this.realPath(path); | ||
} catch (err) { | ||
return Promise.reject(formatError(err, 'sftp.rmdir')); | ||
throw formatError(err, 'sftp.rmdir'); | ||
} | ||
@@ -738,3 +695,3 @@ } | ||
} catch (err) { | ||
return Promise.reject(formatError(err, 'sftp.rmdir')); | ||
throw formatError(err, 'sftp.rmdir'); | ||
} | ||
@@ -745,3 +702,3 @@ } | ||
} catch (err) { | ||
return Promise.reject(formatError(err, 'sftp.rmdir')); | ||
throw formatError(err, 'sftp.rmdir'); | ||
} | ||
@@ -773,13 +730,7 @@ }; | ||
SftpClient.prototype.delete = async function(path) { | ||
try { | ||
if (!this.sftp) { | ||
return Promise.reject( | ||
formatError('No SFTP connection available', 'sftp.delete') | ||
); | ||
} | ||
let absPath = await this.realPath(path); | ||
return this._delete(absPath); | ||
} catch (err) { | ||
return Promise.reject(formatError(err, 'sftp.delete')); | ||
if (!this.sftp) { | ||
throw formatError('No SFTP connection available', 'sftp.delete'); | ||
} | ||
let absPath = await this.realPath(path); | ||
return this._delete(absPath); | ||
}; | ||
@@ -812,21 +763,15 @@ | ||
SftpClient.prototype.rename = async function(fromPath, toPath) { | ||
try { | ||
if (!this.sftp) { | ||
return Promise.reject( | ||
formatError('No SFTP connection available', 'sftp.rename') | ||
); | ||
} | ||
let src = await this.realPath(fromPath); | ||
let dst = toPath; | ||
if (dst.startsWith('../')) { | ||
let root = await this.realPath('..'); | ||
dst = join(root, dst.substring(3)); | ||
} else if (dst.startsWith('./')) { | ||
let root = await this.realPath('.'); | ||
dst = join(root, dst.substring(2)); | ||
} | ||
return this._rename(src, dst); | ||
} catch (err) { | ||
return Promise.reject(formatError(err, 'sftp.rename')); | ||
if (!this.sftp) { | ||
throw formatError('No SFTP connection available', 'sftp.rename'); | ||
} | ||
let src = await this.realPath(fromPath); | ||
let dst = toPath; | ||
if (dst.startsWith('../')) { | ||
let root = await this.realPath('..'); | ||
dst = join(root, dst.substring(3)); | ||
} else if (dst.startsWith('./')) { | ||
let root = await this.realPath('.'); | ||
dst = join(root, dst.substring(2)); | ||
} | ||
return this._rename(src, dst); | ||
}; | ||
@@ -858,13 +803,7 @@ | ||
SftpClient.prototype.chmod = async function(remotePath, mode) { | ||
try { | ||
if (!this.sftp) { | ||
return Promise.reject( | ||
formatError('No SFTP connection available', 'sftp.chmod') | ||
); | ||
} | ||
let path = await this.realPath(remotePath); | ||
return this._chmod(path, mode); | ||
} catch (err) { | ||
return Promise.reject(formatError(err, 'sftp.chmod')); | ||
if (!this.sftp) { | ||
throw formatError('No SFTP connection available', 'sftp.chmod'); | ||
} | ||
let path = await this.realPath(remotePath); | ||
return this._chmod(path, mode); | ||
}; | ||
@@ -884,17 +823,17 @@ | ||
SftpClient.prototype.connect = function(config) { | ||
let obj = this; | ||
let self = this; | ||
var operation = retry.operation({ | ||
retries: config.retries || 2, | ||
retries: config.retries || 1, | ||
factor: config.retry_factor || 2, | ||
minTimeout: config.retry_minTimeout || 2000 | ||
minTimeout: config.retry_minTimeout || 1000 | ||
}); | ||
function retryConnect(sftpObj, config, callback) { | ||
function retryConnect(config, callback) { | ||
try { | ||
operation.attempt(function(attemptCount) { | ||
sftpObj.client | ||
self.client | ||
.on('ready', () => { | ||
sftpObj.client.sftp((err, sftp) => { | ||
self.client.sftp((err, sftp) => { | ||
if (err) { | ||
removeListeners(sftpObj.client); | ||
removeListeners(self.client); | ||
if (operation.retry(err)) { | ||
@@ -908,7 +847,9 @@ // failed to connect, but not yet reached max attempt count | ||
} | ||
sftpObj.sftp = sftp; | ||
self.sftp = sftp; | ||
// remove retry error listener and add generic error listener | ||
sftpObj.client.removeAllListeners('error'); | ||
sftpObj.client.on('error', errorListener); | ||
sftpObj.client.on('finish', withError => { | ||
self.client.removeAllListeners('error'); | ||
self.client.removeAllListeners('end'); | ||
self.client.on('end', makeEndListener(self)); | ||
self.client.on('error', makeErrorListener(self.clientName)); | ||
self.client.on('close', withError => { | ||
if (withError) { | ||
@@ -921,7 +862,4 @@ console.error('Client ended due to errors'); | ||
}) | ||
.on('end', () => { | ||
sftpObj.sftp = null; | ||
}) | ||
.on('error', err => { | ||
removeListeners(sftpObj.client); | ||
removeListeners(self.client); | ||
if (operation.retry(err)) { | ||
@@ -935,11 +873,15 @@ // failed to connect, but not yet reached max attempt count | ||
}) | ||
.on('close', withError => { | ||
if (withError) { | ||
console.error('sftp client closed due to errors'); | ||
} | ||
.on('end', () => { | ||
callback( | ||
formatError( | ||
'Connection ended unexpectedly by remote server', | ||
self.clientName | ||
) | ||
); | ||
}) | ||
.connect(config); | ||
}); | ||
} catch (err) { | ||
removeListeners(sftpObj.client); | ||
removeListeners(self.client); | ||
callback(formatError(err, 'sftp.connect'), null); | ||
@@ -950,11 +892,16 @@ } | ||
return new Promise((resolve, reject) => { | ||
try { | ||
retryConnect(obj, config, (err, sftp) => { | ||
if (self.sftp) { | ||
reject( | ||
formatError( | ||
'An existing SFTP connection is already defined', | ||
'sftp.connect' | ||
) | ||
); | ||
} else { | ||
retryConnect(config, (err, sftp) => { | ||
if (err) { | ||
reject(formatError(err, 'sftp.connect')); | ||
reject(err); | ||
} | ||
resolve(sftp); | ||
}); | ||
} catch (err) { | ||
reject(formatError(err, 'sftp.connect')); | ||
} | ||
@@ -981,14 +928,17 @@ }); | ||
SftpClient.prototype.end = function() { | ||
let obj = this; | ||
let self = this; | ||
return new Promise((resolve, reject) => { | ||
try { | ||
self.endCalled = true; | ||
// debugListeners(this.client); | ||
obj.client.on('close', () => { | ||
removeListeners(obj.client); | ||
resolve(true); | ||
}); | ||
obj.client.end(); | ||
//removeListeners(obj.client); | ||
// obj.client.on('close', () => { | ||
// removeListeners(obj.client); | ||
// //resolve(true); | ||
// }); | ||
self.client.end(); | ||
removeListeners(self.client); | ||
self.sftp = undefined; | ||
resolve(true); | ||
self.endCalled = false; | ||
} catch (err) { | ||
@@ -995,0 +945,0 @@ reject(formatError(err, 'sftp.end')); |
Sorry, the diff of this file is not supported yet
112546
1287
858