ssh2-sftp-client 7.0.4 → 7.2.2

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/README.md CHANGED
@@ -61,9 +61,9 @@ an SFTP client for node.js, a wrapper around [SSH2](https://github.com/mscdex/ss
61
61
 
62
62
  Documentation on the methods and available options in the underlying modules can be found on the [SSH2](https://github.com/mscdex/ssh2) project pages.
63
63
 
64
- Current stable release is **v7.0.4**.
64
+ Current stable release is **v7.2.2**.
65
65
 
66
- Code has been tested against Node versions 12.22.1, 14.17.0 and 16.2.0
66
+ Code has been tested against Node versions 14.18.3, 16.13.2 and 17.4.0
67
67
 
68
68
  Node versions < 10.x are not supported.
69
69
 
@@ -99,6 +99,8 @@ sftp.connect({
99
99
 
100
100
  - **Breaking Change** Expanded option handling for `get()` and `put()` methods. A number of use cases were identified where setting specific options on the read and write streams and the pipe operation are necessary. For example, disabling `autoClose` on streams or the `end` event in pipes. The `options` argument for `get()` and `put()` calls now supports properties for `readStreamOptions`, `writeStreamOptions` and `pipeOptions`. Note that options are only applied to streams created by the `get()` and `put()` methods. Streams passed into these methods are under the control of the client code and therefore cannot have options supplied in arguments to those streams (you would apply such options when you create the streams). Options are typically only necessary in special use cases. Most of the time, no options are required. However, if you are currently using options to either `put()` or `get()`, you will need to update your code to map these options to the new structure.
101
101
 
102
+ - **Breaking Change 7.1.0** A race condition was identified when using a put() call with a writeStream option of `autoClose: false`. In some situations, the promise would be resolved before the final close of the write stream. This could result in errors if you immediately attempt to access the uploaded file. To avoid this situatioin, the promise is now resolved once a `close` event is emitted. This means that setting `autoClose: false` can no longer be supported. The write stream for `put()` will autoClose once data writing has completed.
103
+
102
104
  - Improved event handling. A listener for a global error event is now defined to catch errors which occur in-between method calls i.e. connection lost in-between calls to the library methods. A new mechanism has also been added for removal of listeners when no longer required.
103
105
 
104
106
  # Documentation<a id="sec-5"></a>
@@ -497,11 +499,12 @@ Upload data from local system to remote server. If the `src` argument is a strin
497
499
  flags: 'w', // w - write and a - append
498
500
  encoding: null, // use null for binary files
499
501
  mode: 0o666, // mode to use for created file (rwx)
500
- autoClose: true // automatically close the write stream when finished
501
502
  }}
502
503
  ```
503
504
 
504
505
  The most common options to use are mode and encoding. The values shown above are the defaults. You do not have to set encoding to utf-8 for text files, null is fine for all file types. However, using utf-8 encoding for binary files will often result in data corruption.
506
+
507
+ Note that you cannot set `autoClose: false` for `writeStreamOptions`. If you attempt to set this property to false, it will be ignored. This is necessary to avoid a race condition which may exist when setting `autoClose` to false on the writeStream. As there is no easy way to access the writeStream once the promise has been resolved, setting this to autoClose false is not terribly useful as there is no easy way to manually close the stream after the promise has been resolved.
505
508
 
506
509
  2. Example Use
507
510
 
@@ -1356,3 +1359,6 @@ Thanks to the following for their contributions -
1356
1359
  - **anton-erofeev:** Documentation fix
1357
1360
  - **Ladislav Jacho:** Contributed solution explanation for connections hanging when transferring larger files.
1358
1361
  - **Emma Milner:** Contributed fix for put() bug
1362
+ - **Witni Davis:** Contributed PR to fix put() RCE when using 'finish' rather than 'close' to resolve promise
1363
+ - **Maik Marschner:** Contributed fix for connect() not returning sftp object. Also included test to check for this regression in future.
1364
+ - **cakemasher:** Contributed fix for removeTempListeners().
package/README.org CHANGED
@@ -9,9 +9,9 @@ convenience abstraction as well as a Promise based API.
9
9
  Documentation on the methods and available options in the underlying modules can
10
10
  be found on the [[https://github.com/mscdex/ssh2][SSH2]] project pages.
11
11
 
12
- Current stable release is *v7.0.4*.
12
+ Current stable release is *v7.2.2*.
13
13
 
14
- Code has been tested against Node versions 12.22.1, 14.17.0 and 16.2.0
14
+ Code has been tested against Node versions 14.18.3, 16.13.2 and 17.4.0
15
15
 
16
16
  Node versions < 10.x are not supported.
17
17
 
@@ -62,6 +62,14 @@ npm install ssh2-sftp-client
62
62
  currently using options to either ~put()~ or ~get()~, you will need to update
63
63
  your code to map these options to the new structure.
64
64
 
65
+ - *Breaking Change 7.1.0* A race condition was identified when using a put()
66
+ call with a writeStream option of ~autoClose: false~. In some situations, the
67
+ promise would be resolved before the final close of the write stream. This
68
+ could result in errors if you immediately attempt to access the uploaded
69
+ file. To avoid this situatioin, the promise is now resolved once a ~close~
70
+ event is emitted. This means that setting ~autoClose: false~ can no longer be
71
+ supported. The write stream for ~put()~ will autoClose once data writing has completed.
72
+
65
73
  - Improved event handling. A listener for a global error event is now defined to
66
74
  catch errors which occur in-between method calls i.e. connection lost
67
75
  in-between calls to the library methods. A new mechanism has also been added
@@ -546,7 +554,6 @@ option value. For example, you might use the following to set ~writeStream~ opti
546
554
  flags: 'w', // w - write and a - append
547
555
  encoding: null, // use null for binary files
548
556
  mode: 0o666, // mode to use for created file (rwx)
549
- autoClose: true // automatically close the write stream when finished
550
557
  }}
551
558
  #+end_src
552
559
 
@@ -555,6 +562,14 @@ the defaults. You do not have to set encoding to utf-8 for text files, null is
555
562
  fine for all file types. However, using utf-8 encoding for binary files will
556
563
  often result in data corruption.
557
564
 
565
+ Note that you cannot set ~autoClose: false~ for ~writeStreamOptions~. If you
566
+ attempt to set this property to false, it will be ignored. This is necessary to
567
+ avoid a race condition which may exist when setting ~autoClose~ to false on the
568
+ writeStream. As there is no easy way to access the writeStream once the promise
569
+ has been resolved, setting this to autoClose false is not terribly useful as
570
+ there is no easy way to manually close the stream after the promise has been
571
+ resolved.
572
+
558
573
  **** Example Use
559
574
 
560
575
  #+begin_src javascript
@@ -1765,3 +1780,9 @@ Thanks to the following for their contributions -
1765
1780
  - Ladislav Jacho :: Contributed solution explanation for connections hanging
1766
1781
  when transferring larger files.
1767
1782
  - Emma Milner :: Contributed fix for put() bug
1783
+ - Witni Davis :: Contributed PR to fix put() RCE when using 'finish' rather than
1784
+ 'close' to resolve promise
1785
+ - Maik Marschner :: Contributed fix for connect() not returning sftp object.
1786
+ Also included test to check for this regression in future.
1787
+ - cakemasher :: Contributed fix for removeTempListeners().
1788
+
package/package.json CHANGED
@@ -1,17 +1,13 @@
1
1
  {
2
2
  "name": "ssh2-sftp-client",
3
- "version": "7.0.4",
3
+ "version": "7.2.2",
4
4
  "description": "ssh2 sftp client for node",
5
5
  "main": "src/index.js",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/theophilusx/ssh2-sftp-client"
9
9
  },
10
- "keywords": [
11
- "sftp",
12
- "nodejs",
13
- "promises"
14
- ],
10
+ "keywords": ["sftp", "nodejs", "promises"],
15
11
  "scripts": {
16
12
  "test": "mocha",
17
13
  "coverage": "nyc npm run test",
@@ -32,24 +28,24 @@
32
28
  "dependencies": {
33
29
  "concat-stream": "^2.0.0",
34
30
  "promise-retry": "^2.0.1",
35
- "ssh2": "^1.4.0"
31
+ "ssh2": "^1.6.0"
36
32
  },
37
33
  "devDependencies": {
38
- "chai": "^4.2.0",
34
+ "chai": "^4.3.4",
39
35
  "chai-as-promised": "^7.1.1",
40
36
  "chai-subset": "^1.6.0",
41
37
  "checksum": "^1.0.0",
42
- "dotenv": "^10.0.0",
43
- "eslint": "^7.32.0",
38
+ "dotenv": "^15.0.0",
39
+ "eslint": "^8.5.0",
44
40
  "eslint-config-prettier": "^8.3.0",
45
- "eslint-plugin-mocha": "^9.0.0",
41
+ "eslint-plugin-mocha": "^10.0.3",
46
42
  "eslint-plugin-node": "^11.1.0",
47
- "eslint-plugin-promise": "^5.1.0",
48
- "eslint-plugin-unicorn": "^35.0.0",
49
- "mocha": "^9.0.2",
43
+ "eslint-plugin-promise": "^6.0.0",
44
+ "eslint-plugin-unicorn": "^40.1.0",
45
+ "mocha": "^9.1.2",
50
46
  "moment": "^2.29.1",
51
47
  "nyc": "^15.1.0",
52
- "prettier": "^2.3.2",
48
+ "prettier": "^2.5.0",
53
49
  "through2": "^4.0.2",
54
50
  "winston": "^3.3.3"
55
51
  }
package/src/index.js CHANGED
@@ -34,6 +34,7 @@ class SftpClient {
34
34
  this.remotePathSep = '/';
35
35
  this.remotePlatform = 'unix';
36
36
  this.debug = undefined;
37
+ this.tempListeners = {};
37
38
 
38
39
  this.client.on('close', () => {
39
40
  if (this.endCalled || this.closeHandled) {
@@ -114,7 +115,7 @@ class SftpClient {
114
115
  *
115
116
  * @param {Object} config - an SFTP configuration object
116
117
  *
117
- * @return {Promise} which will resolve to an sftp client object
118
+ * @return {Promise<Object>} which will resolve to an sftp client object
118
119
  *
119
120
  */
120
121
  getConnection(config) {
@@ -150,6 +151,7 @@ class SftpClient {
150
151
  this.client.sftp((err, sftp) => {
151
152
  if (err) {
152
153
  this.debugMsg(`getSftpChannel: SFTP Channel Error: ${err.message}`);
154
+ this.client.end();
153
155
  reject(fmtError(err, 'getSftpChannel', err.code));
154
156
  } else {
155
157
  this.debugMsg('getSftpChannel: SFTP channel established');
@@ -173,7 +175,7 @@ class SftpClient {
173
175
  *
174
176
  * @param {Object} config - an SFTP configuration object
175
177
  *
176
- * @return {Promise} which will resolve to an sftp client object
178
+ * @return {Promise<Object>} which will resolve to an sftp client object
177
179
  *
178
180
  */
179
181
  async connect(config) {
@@ -204,7 +206,7 @@ class SftpClient {
204
206
  minTimeout: config.retry_minTimeout || 1000,
205
207
  }
206
208
  );
207
- await this.getSftpChannel();
209
+ return this.getSftpChannel();
208
210
  } catch (err) {
209
211
  this.debugMsg(`connect: Error ${err.message}`);
210
212
  this._resetEventFlags();
@@ -221,7 +223,7 @@ class SftpClient {
221
223
  * Returns undefined if the path does not exists.
222
224
  *
223
225
  * @param {String} remotePath - remote path, may be relative
224
- * @returns {Promise} - remote absolute path or undefined
226
+ * @returns {Promise<String>} - remote absolute path or ''
225
227
  */
226
228
  realPath(remotePath) {
227
229
  return new Promise((resolve, reject) => {
@@ -249,6 +251,13 @@ class SftpClient {
249
251
  });
250
252
  }
251
253
 
254
+ /**
255
+ * @async
256
+ *
257
+ * Return the current workding directory path
258
+ *
259
+ * @returns {Promise<String>} - current remote working directory
260
+ */
252
261
  cwd() {
253
262
  return this.realPath('.');
254
263
  }
@@ -257,7 +266,7 @@ class SftpClient {
257
266
  * Retrieves attributes for path
258
267
  *
259
268
  * @param {String} remotePath - a string containing the path to a file
260
- * @return {Promise} stats - attributes info
269
+ * @return {Promise<Object>} stats - attributes info
261
270
  */
262
271
  async stat(remotePath) {
263
272
  const _stat = (aPath) => {
@@ -301,7 +310,7 @@ class SftpClient {
301
310
  }
302
311
  });
303
312
  }).finally(() => {
304
- removeTempListeners(this, 'stat');
313
+ removeTempListeners(this, '_stat');
305
314
  });
306
315
  };
307
316
 
@@ -323,7 +332,7 @@ class SftpClient {
323
332
  *
324
333
  * @param {string} remotePath - path to the object on the sftp server.
325
334
  *
326
- * @return {Promise} returns false if object does not exist. Returns type of
335
+ * @return {Promise<Boolean|String>} returns false if object does not exist. Returns type of
327
336
  * object if it does
328
337
  */
329
338
  async exists(remotePath) {
@@ -382,8 +391,7 @@ class SftpClient {
382
391
  *
383
392
  * @param {String} remotePath - path to remote directory
384
393
  * @param {RegExp} pattern - regular expression to match filenames
385
- * @returns {Promise} array of file description objects
386
- * @throws {Error}
394
+ * @returns {Promise<Array>} array of file description objects
387
395
  */
388
396
  list(remotePath, pattern = /.*/) {
389
397
  return new Promise((resolve, reject) => {
@@ -449,7 +457,7 @@ class SftpClient {
449
457
  * @param {Object} options - options object with supported properties of readStreamOptions,
450
458
  * writeStreamOptions and pipeOptions.
451
459
  *
452
- * @return {Promise}
460
+ * @return {Promise<String|Stream|Buffer>}
453
461
  */
454
462
  get(
455
463
  remotePath,
@@ -507,13 +515,29 @@ class SftpClient {
507
515
  )
508
516
  );
509
517
  });
510
- rdr.once('end', () => {
511
- if (typeof dst === 'string') {
512
- resolve(dst);
513
- } else {
514
- resolve(wtr);
515
- }
516
- });
518
+ if (
519
+ Object.hasOwnProperty.call(options, 'pipeOptions') &&
520
+ Object.hasOwnProperty.call(options.pipeOptions, 'end') &&
521
+ !options.pipeOptions.end
522
+ ) {
523
+ rdr.once('end', () => {
524
+ this.debugMsg('get resolved on reader end event');
525
+ if (typeof dst === 'string') {
526
+ resolve(dst);
527
+ } else {
528
+ resolve(wtr);
529
+ }
530
+ });
531
+ } else {
532
+ wtr.once('finish', () => {
533
+ this.debugMsg('get resolved on writer finish event');
534
+ if (typeof dst === 'string') {
535
+ resolve(dst);
536
+ } else {
537
+ resolve(wtr);
538
+ }
539
+ });
540
+ }
517
541
  }
518
542
  rdr.pipe(wtr, options.pipeOptions ? options.pipeOptions : {});
519
543
  }
@@ -522,14 +546,16 @@ class SftpClient {
522
546
  this._resetEventFlags();
523
547
  if (
524
548
  rdr &&
525
- options.readStreamOptions &&
549
+ Object.hasOwnProperty.call(options, 'readStreamOptions') &&
550
+ Object.hasOwnProperty.call(options.readStreamOptions, 'autoClose') &&
526
551
  options.readStreamOptions.autoClose === false
527
552
  ) {
528
553
  rdr.destroy();
529
554
  }
530
555
  if (
531
556
  wtr &&
532
- options.writeStreamOptions &&
557
+ Object.hasOwnProperty.call(options, 'writeStreamOptions') &&
558
+ Object.hasOwnProperty.call(options.writeStreamOptions, 'autoClose') &&
533
559
  options.writeStreamOptions.autoClose === false &&
534
560
  typeof dst === 'string'
535
561
  ) {
@@ -543,13 +569,10 @@ class SftpClient {
543
569
  * Downloads a file at remotePath to localPath using parallel reads
544
570
  * for faster throughput.
545
571
  *
546
- * See 'fastGet' at
547
- * https://github.com/mscdex/ssh2-streams/blob/master/SFTPStream.md
548
- *
549
572
  * @param {String} remotePath
550
573
  * @param {String} localPath
551
574
  * @param {Object} options
552
- * @return {Promise} the result of downloading the file
575
+ * @return {Promise<String>} the result of downloading the file
553
576
  */
554
577
  async fastGet(remotePath, localPath, options) {
555
578
  try {
@@ -569,7 +592,7 @@ class SftpClient {
569
592
  err.code = errorCode.badPath;
570
593
  throw err;
571
594
  }
572
- await new Promise((resolve, reject) => {
595
+ let rslt = await new Promise((resolve, reject) => {
573
596
  if (haveConnection(this, 'fastGet', reject)) {
574
597
  this.debugMsg(
575
598
  `fastGet -> remote: ${remotePath} local: ${localPath} `,
@@ -587,6 +610,7 @@ class SftpClient {
587
610
  }).finally(() => {
588
611
  removeTempListeners(this, 'fastGet');
589
612
  });
613
+ return rslt;
590
614
  } catch (err) {
591
615
  this._resetEventFlags();
592
616
  throw fmtError(err, 'fastGet');
@@ -604,7 +628,7 @@ class SftpClient {
604
628
  * @param {String} localPath
605
629
  * @param {String} remotePath
606
630
  * @param {Object} options
607
- * @return {Promise} the result of downloading the file
631
+ * @return {Promise<String>} the result of downloading the file
608
632
  */
609
633
  fastPut(localPath, remotePath, options) {
610
634
  this.debugMsg(`fastPut -> local ${localPath} remote ${remotePath}`);
@@ -664,12 +688,16 @@ class SftpClient {
664
688
  * @param {Object} options - options used for read, write stream and pipe configuration
665
689
  * value supported by node. Allowed properties are readStreamOptions,
666
690
  * writeStreamOptions and pipeOptions.
667
- * @return {Promise}
691
+ * @return {Promise<String>}
668
692
  */
669
693
  put(
670
694
  localSrc,
671
695
  remotePath,
672
- options = { readStreamOptions: {}, writeStreamOptions: {}, pipeOptions: {} }
696
+ options = {
697
+ readStreamOptions: {},
698
+ writeStreamOptions: { autoClose: true },
699
+ pipeOptions: {},
700
+ }
673
701
  ) {
674
702
  let wtr, rdr;
675
703
 
@@ -691,13 +719,15 @@ class SftpClient {
691
719
  addTempListeners(this, 'put', reject);
692
720
  wtr = this.sftp.createWriteStream(
693
721
  remotePath,
694
- options.writeStreamOptions ? options.writeStreamOptions : {}
722
+ options.writeStreamOptions
723
+ ? { ...options.writeStreamOptions, autoClose: true }
724
+ : {}
695
725
  );
696
726
  wtr.once('error', (err) => {
697
727
  this.debugMsg(`put: write stream error ${err.message}`);
698
728
  reject(fmtError(`${err.message} ${remotePath}`, 'put', err.code));
699
729
  });
700
- wtr.once('finish', () => {
730
+ wtr.once('close', () => {
701
731
  this.debugMsg('put: promise resolved');
702
732
  resolve(`Uploaded data stream to ${remotePath}`);
703
733
  });
@@ -735,19 +765,13 @@ class SftpClient {
735
765
  this._resetEventFlags();
736
766
  if (
737
767
  rdr &&
738
- options.readStreamOptions &&
768
+ Object.hasOwnProperty.call(options, 'readStreamOptions') &&
769
+ Object.hasOwnProperty.call(options.readStreamOptions, 'autoClose') &&
739
770
  options.readStreamOptions.autoClose === false &&
740
771
  typeof localSrc === 'string'
741
772
  ) {
742
773
  rdr.destroy();
743
774
  }
744
- if (
745
- wtr &&
746
- options.writeStreamOptions &&
747
- options.writeStreamOptions.autoClose === false
748
- ) {
749
- wtr.destroy();
750
- }
751
775
  });
752
776
  }
753
777
 
@@ -757,9 +781,8 @@ class SftpClient {
757
781
  * @param {Buffer|stream} input
758
782
  * @param {String} remotePath
759
783
  * @param {Object} options
760
- * @return {Promise}
784
+ * @return {Promise<String>}
761
785
  */
762
-
763
786
  async append(input, remotePath, options = {}) {
764
787
  const fileType = await this.exists(remotePath);
765
788
  if (fileType && fileType === 'd') {
@@ -807,7 +830,7 @@ class SftpClient {
807
830
  *
808
831
  * @param {string} remotePath - remote directory path.
809
832
  * @param {boolean} recursive - if true, recursively create directories
810
- * @return {Promise}
833
+ * @return {Promise<String>}
811
834
  */
812
835
  async mkdir(remotePath, recursive = false) {
813
836
  const _mkdir = (p) => {
@@ -845,6 +868,14 @@ class SftpClient {
845
868
  try {
846
869
  haveConnection(this, 'mkdir');
847
870
  let rPath = await normalizeRemotePath(this, remotePath);
871
+ let targetExists = await this.exists(rPath);
872
+ if (targetExists && targetExists !== 'd') {
873
+ let error = new Error(`Bad path: ${rPath} already exists as a file`);
874
+ error.code = errorCode.badPath;
875
+ throw error;
876
+ } else if (targetExists) {
877
+ return `${rPath} already exists`;
878
+ }
848
879
  if (!recursive) {
849
880
  return await _mkdir(rPath);
850
881
  }
@@ -873,7 +904,7 @@ class SftpClient {
873
904
  * @param {string} remotePath - path to directory to be removed
874
905
  * @param {boolean} recursive - if true, remove directories/files in target
875
906
  * directory
876
- * @return {Promise}
907
+ * @return {Promise<String>}
877
908
  */
878
909
  async rmdir(remotePath, recursive = false) {
879
910
  const _rmdir = (p) => {
@@ -926,7 +957,7 @@ class SftpClient {
926
957
  * @param {string} remotePath - path to the file to delete
927
958
  * @param {boolean} notFoundOK - if true, ignore errors for missing target.
928
959
  * Default is false.
929
- * @return {Promise} with string 'Successfully deleted file' once resolved
960
+ * @return {Promise<String>} with string 'Successfully deleted file' once resolved
930
961
  *
931
962
  */
932
963
  delete(remotePath, notFoundOK = false) {
@@ -963,7 +994,7 @@ class SftpClient {
963
994
  * @param {string} fromPath - path to the file to be renamed.
964
995
  * @param {string} toPath - path to the new name.
965
996
  *
966
- * @return {Promise}
997
+ * @return {Promise<String>}
967
998
  *
968
999
  */
969
1000
  rename(fromPath, toPath) {
@@ -1000,7 +1031,7 @@ class SftpClient {
1000
1031
  * @param {string} fromPath - path to the file to be renamed.
1001
1032
  * @param {string} toPath - path the new name.
1002
1033
  *
1003
- * @return {Promise}
1034
+ * @return {Promise<String>}
1004
1035
  *
1005
1036
  */
1006
1037
  posixRename(fromPath, toPath) {
@@ -1036,7 +1067,7 @@ class SftpClient {
1036
1067
  * @param {string} remotePath - path to the remote target object.
1037
1068
  * @param {number | string} mode - the new octal mode to set
1038
1069
  *
1039
- * @return {Promise}
1070
+ * @return {Promise<String>}
1040
1071
  */
1041
1072
  chmod(remotePath, mode) {
1042
1073
  return new Promise((resolve, reject) => {
@@ -1064,8 +1095,7 @@ class SftpClient {
1064
1095
  * @param {String} dstDir - remote destination directory
1065
1096
  * @param {RegExp} filter - (Optional) a regular expression used to select
1066
1097
  * files and directories to upload
1067
- * @returns {String}
1068
- * @throws {Error}
1098
+ * @returns {Promise<String>}
1069
1099
  */
1070
1100
  async uploadDir(srcDir, dstDir, filter = /.*/) {
1071
1101
  try {
@@ -1124,8 +1154,7 @@ class SftpClient {
1124
1154
  * @param {String} dstDir - local destination directory
1125
1155
  * @param {RegExp} filter - (Optional) a regular expression used to select
1126
1156
  * files and directories to upload
1127
- * @returns {Promise}
1128
- * @throws {Error}
1157
+ * @returns {Promise<String>}
1129
1158
  */
1130
1159
  async downloadDir(srcDir, dstDir, filter = /.*/) {
1131
1160
  try {
@@ -1176,6 +1205,7 @@ class SftpClient {
1176
1205
  *
1177
1206
  * End the SFTP connection
1178
1207
  *
1208
+ * @returns {Promise<Boolean>}
1179
1209
  */
1180
1210
  end() {
1181
1211
  let endCloseHandler;
package/src/utils.js CHANGED
@@ -58,7 +58,13 @@ function fmtError(err, name = 'sftp', eCode, retryCount) {
58
58
  return newError;
59
59
  }
60
60
 
61
- let tempListeners = [];
61
+ function addToTempListenerList(obj, name, evt, fn) {
62
+ if (name in obj.tempListeners) {
63
+ obj.tempListeners[name].push([evt, fn]);
64
+ } else {
65
+ obj.tempListeners[name] = [[evt, fn]];
66
+ }
67
+ }
62
68
 
63
69
  /**
64
70
  * Simple default error listener. Will reformat the error message and
@@ -83,7 +89,7 @@ function errorListener(client, name, reject) {
83
89
  }
84
90
  }
85
91
  };
86
- tempListeners.push(['error', fn]);
92
+ addToTempListenerList(client, name, 'error', fn);
87
93
  return fn;
88
94
  }
89
95
 
@@ -104,7 +110,7 @@ function endListener(client, name, reject) {
104
110
  }
105
111
  }
106
112
  };
107
- tempListeners.push(['end', fn]);
113
+ addToTempListenerList(client, name, 'end', fn);
108
114
  return fn;
109
115
  }
110
116
 
@@ -125,7 +131,7 @@ function closeListener(client, name, reject) {
125
131
  }
126
132
  }
127
133
  };
128
- tempListeners.push(['close', fn]);
134
+ addToTempListenerList(client, name, 'close', fn);
129
135
  return fn;
130
136
  }
131
137
 
@@ -138,10 +144,12 @@ function addTempListeners(obj, name, reject) {
138
144
 
139
145
  function removeTempListeners(obj, name) {
140
146
  obj.debugMsg(`${name}: Removing temp event listeners`);
141
- tempListeners.forEach(([e, fn]) => {
142
- obj.client.removeListener(e, fn);
143
- });
144
- tempListeners = [];
147
+ if (name in obj.tempListeners) {
148
+ obj.tempListeners[name].forEach(([e, fn]) => {
149
+ obj.client.removeListener(e, fn);
150
+ });
151
+ obj.tempListeners[name] = [];
152
+ }
145
153
  }
146
154
 
147
155
  /**
@@ -337,6 +345,7 @@ function sleep(ms) {
337
345
 
338
346
  module.exports = {
339
347
  fmtError,
348
+ addToTempListenerList,
340
349
  errorListener,
341
350
  endListener,
342
351
  closeListener,