ssh2-sftp-client 6.0.0 → 6.0.1

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.org CHANGED
@@ -1,6 +1,14 @@
1
1
  * Change Logging
2
2
 
3
- ** V6.0.0
3
+ ** Version 6.0.1
4
+ - Fix issue with connect retry not releasing 'ready' listeners
5
+ - Add finally clauses to all promises to ensure temporary listeners are deleted
6
+ - Add nyc module to report on code test coverage
7
+ - Add additional utils tests to increase test coverage
8
+ - Removed some dead code and unused utility functions to reduce download size
9
+ - Cleanup tests and reduce inter-test dependencies
10
+
11
+ ** V6.0.0.0
4
12
  - Update connection retry code to use the promise-retry module instead of
5
13
  plain rety module
6
14
  - Added optional filter argument for uploadDir/downlDir to select which files
package/README.md CHANGED
@@ -2,7 +2,8 @@
2
2
  - [Installation](#sec-2)
3
3
  - [Basic Usage](#sec-3)
4
4
  - [Version 6.x Changes](#sec-4)
5
- - [Version 6.0.0 Changes](#sec-4-1)
5
+ - [Version 6.0.1](#sec-4-1)
6
+ - [Version 6.0.0 Changes](#sec-4-2)
6
7
  - [Documentation](#sec-5)
7
8
  - [Specifying Paths](#sec-5-1)
8
9
  - [Methods](#sec-5-2)
@@ -104,7 +105,15 @@ sftp.connect({
104
105
 
105
106
  # Version 6.x Changes<a id="sec-4"></a>
106
107
 
107
- ## Version 6.0.0 Changes<a id="sec-4-1"></a>
108
+ ## Version 6.0.1<a id="sec-4-1"></a>
109
+
110
+ - Fix issue with connect retry not releasing 'ready' listener.
111
+ - Add finally clauses to all promises to ensure release of temporary listeners.
112
+ - Add nyc module to improve test coverage
113
+ - Added additional utils tests to improve test coverage
114
+ - Removed some unnecessary util functions to reduce code size
115
+
116
+ ## Version 6.0.0 Changes<a id="sec-4-2"></a>
108
117
 
109
118
  - Added new optional argument *notFoundOK* to `delete()` method. If true, no error is thrown when trying to delete a file which does not exist. Default is false.
110
119
  - Added new filter argument to `uploadDir()` and `downloadDir()` methods. The filter argument is a regular expression used to match the files and directories to be included in the upload or download. Defaults to match all files and directories.
package/README.org CHANGED
@@ -68,6 +68,14 @@ npm install ssh2-sftp-client
68
68
  #+end_src
69
69
 
70
70
  * Version 6.x Changes
71
+ ** Version 6.0.1
72
+
73
+ - Fix issue with connect retry not releasing 'ready' listener.
74
+ - Add finally clauses to all promises to ensure release of temporary listeners.
75
+ - Add nyc module to improve test coverage
76
+ - Added additional utils tests to improve test coverage
77
+ - Removed some unnecessary util functions to reduce code size
78
+
71
79
  ** Version 6.0.0 Changes
72
80
 
73
81
  - Added new optional argument /notFoundOK/ to ~delete()~ method. If true, no
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ssh2-sftp-client",
3
- "version": "6.0.0",
3
+ "version": "6.0.1",
4
4
  "description": "ssh2 sftp client for node",
5
5
  "main": "src/index.js",
6
6
  "repository": {
@@ -9,7 +9,8 @@
9
9
  },
10
10
  "keywords": ["sftp", "nodejs", "promises"],
11
11
  "scripts": {
12
- "test": "mocha"
12
+ "test": "mocha",
13
+ "coverage": "nyc npm run test"
13
14
  },
14
15
  "author": "Tim Cross",
15
16
  "email": "theophilusx@gmail.com",
@@ -23,7 +24,8 @@
23
24
  "dependencies": {
24
25
  "concat-stream": "^2.0.0",
25
26
  "promise-retry": "^2.0.1",
26
- "ssh2": "^0.8.9"
27
+ "ssh2": "^0.8.9",
28
+ "winston": "^3.3.3"
27
29
  },
28
30
  "devDependencies": {
29
31
  "chai": "^4.2.0",
@@ -33,6 +35,7 @@
33
35
  "dotenv": "^8.2.0",
34
36
  "mocha": "^8.2.1",
35
37
  "moment": "^2.29.1",
38
+ "nyc": "^15.1.0",
36
39
  "through2": "^4.0.2"
37
40
  }
38
41
  }
package/src/index.js CHANGED
@@ -7,10 +7,16 @@
7
7
  const {Client} = require('ssh2');
8
8
  const fs = require('fs');
9
9
  const concat = require('concat-stream');
10
- //const retry = require('retry');
11
10
  const promiseRetry = require('promise-retry');
12
11
  const {join, parse} = require('path');
13
- const utils = require('./utils');
12
+ const {
13
+ fmtError,
14
+ addTempListeners,
15
+ removeTempListeners,
16
+ haveConnection,
17
+ normalizeRemotePath,
18
+ localExists
19
+ } = require('./utils');
14
20
  const {errorCode} = require('./constants');
15
21
 
16
22
  class SftpClient {
@@ -38,7 +44,7 @@ class SftpClient {
38
44
  });
39
45
  this.client.on('error', (err) => {
40
46
  if (!this.errorHandled) {
41
- throw utils.formatError(
47
+ throw fmtError(
42
48
  `Unexpected error: ${err.message}`,
43
49
  'global-error-handler',
44
50
  err.code
@@ -92,30 +98,49 @@ class SftpClient {
92
98
  *
93
99
  */
94
100
  sftpConnect(config) {
101
+ let connectReady;
102
+
95
103
  return new Promise((resolve, reject) => {
96
- let connectReady = () => {
104
+ addTempListeners(this, 'sftpConnect', reject);
105
+ connectReady = () => {
97
106
  this.client.sftp((err, sftp) => {
98
107
  if (err) {
99
108
  this.debugMsg(`SFTP channel error: ${err.message} ${err.code}`);
100
- reject(utils.formatError(err, 'sftpConnect', err.code));
109
+ reject(fmtError(err, 'sftpConnect', err.code));
101
110
  } else {
102
- //this.sftp = sftp;
111
+ this.debugMsg('SFTP channel established');
103
112
  resolve(sftp);
104
113
  }
105
- this.client.removeListener('ready', connectReady);
106
114
  });
107
115
  };
108
- utils.addTempListeners(this, 'sftpConnect', reject);
116
+ // addTempListeners(this, 'sftpConnect', reject);
109
117
  this.client.on('ready', connectReady).connect(config);
118
+ }).finally((rsp) => {
119
+ this.removeListener('ready', connectReady);
120
+ removeTempListeners(this.client);
121
+ return rsp;
110
122
  });
111
123
  }
112
124
 
113
- retryConnect(config) {
125
+ connect(config) {
126
+ if (config.debug) {
127
+ this.debug = config.debug;
128
+ this.debugMsg('Debugging turned on');
129
+ }
130
+ if (this.sftp) {
131
+ this.debugMsg('Already connected - reject');
132
+ return Promise.reject(
133
+ fmtError(
134
+ 'An existing SFTP connection is already defined',
135
+ 'connect',
136
+ errorCode.connect
137
+ )
138
+ );
139
+ }
114
140
  return promiseRetry(
115
141
  (retry, attempt) => {
116
142
  this.debugMsg(`Connect attempt ${attempt}`);
117
143
  return this.sftpConnect(config).catch((err) => {
118
- utils.removeTempListeners(this.client);
119
144
  retry(err);
120
145
  });
121
146
  },
@@ -124,30 +149,9 @@ class SftpClient {
124
149
  factor: config.retry_factor || 2,
125
150
  minTimeout: config.retry_minTimeout || 1000
126
151
  }
127
- );
128
- }
129
-
130
- async connect(config) {
131
- try {
132
- if (config.debug) {
133
- this.debug = config.debug;
134
- this.debugMsg('Debugging turned on');
135
- }
136
- if (this.sftp) {
137
- this.debugMsg('Already connected - reject');
138
- throw utils.formatError(
139
- 'An existing SFTP connection is already defined',
140
- 'connect',
141
- errorCode.connect
142
- );
143
- }
144
- //this.sftp = await this.sftpConnect(config);
145
- this.sftp = await this.retryConnect(config);
146
- this.debugMsg('SFTP Connection established');
147
- utils.removeTempListeners(this.client);
148
- } catch (err) {
149
- throw utils.formatError(err.message, 'connect', err.errorCode);
150
- }
152
+ ).then((sftp) => {
153
+ this.sftp = sftp;
154
+ });
151
155
  }
152
156
 
153
157
  /**
@@ -165,8 +169,8 @@ class SftpClient {
165
169
  realPath(remotePath) {
166
170
  return new Promise((resolve, reject) => {
167
171
  this.debugMsg(`realPath -> ${remotePath}`);
168
- utils.addTempListeners(this, 'realPath', reject);
169
- if (utils.haveConnection(this, 'realPath', reject)) {
172
+ addTempListeners(this, 'realPath', reject);
173
+ if (haveConnection(this, 'realPath', reject)) {
170
174
  this.sftp.realpath(remotePath, (err, absPath) => {
171
175
  if (err) {
172
176
  this.debugMsg(`realPath Error: ${err.message} Code: ${err.code}`);
@@ -174,19 +178,17 @@ class SftpClient {
174
178
  resolve('');
175
179
  } else {
176
180
  reject(
177
- utils.formatError(
178
- `${err.message} ${remotePath}`,
179
- 'realPath',
180
- err.code
181
- )
181
+ fmtError(`${err.message} ${remotePath}`, 'realPath', err.code)
182
182
  );
183
183
  }
184
184
  }
185
185
  this.debugMsg(`realPath <- ${absPath}`);
186
186
  resolve(absPath);
187
- utils.removeTempListeners(this.client);
188
187
  });
189
188
  }
189
+ }).finally((rsp) => {
190
+ removeTempListeners(this.client);
191
+ return rsp;
190
192
  });
191
193
  }
192
194
 
@@ -204,13 +206,13 @@ class SftpClient {
204
206
  const _stat = (aPath) => {
205
207
  return new Promise((resolve, reject) => {
206
208
  this.debugMsg(`stat -> ${aPath}`);
207
- utils.addTempListeners(this, 'stat', reject);
209
+ addTempListeners(this, 'stat', reject);
208
210
  this.sftp.stat(aPath, (err, stats) => {
209
211
  if (err) {
210
212
  this.debugMsg(`stat error ${err.message} code: ${err.code}`);
211
213
  if (err.code === 2 || err.code === 4) {
212
214
  reject(
213
- utils.formatError(
215
+ fmtError(
214
216
  `No such file: ${remotePath}`,
215
217
  '_stat',
216
218
  errorCode.notexist
@@ -218,11 +220,7 @@ class SftpClient {
218
220
  );
219
221
  } else {
220
222
  reject(
221
- utils.formatError(
222
- `${err.message} ${remotePath}`,
223
- '_stat',
224
- err.code
225
- )
223
+ fmtError(`${err.message} ${remotePath}`, '_stat', err.code)
226
224
  );
227
225
  }
228
226
  } else {
@@ -243,17 +241,23 @@ class SftpClient {
243
241
  isSocket: stats.isSocket()
244
242
  });
245
243
  }
246
- utils.removeTempListeners(this.client);
247
244
  });
245
+ }).finally((rsp) => {
246
+ removeTempListeners(this.client);
247
+ return rsp;
248
248
  });
249
249
  };
250
250
 
251
251
  try {
252
- utils.haveConnection(this, 'stat');
253
- let absPath = await utils.normalizeRemotePath(this, remotePath);
252
+ haveConnection(this, 'stat');
253
+ let absPath = await normalizeRemotePath(this, remotePath);
254
254
  return _stat(absPath);
255
255
  } catch (err) {
256
- return utils.handleError(err, 'stat');
256
+ if (err.custom) {
257
+ throw err;
258
+ } else {
259
+ throw fmtError(err, 'stat', err.code);
260
+ }
257
261
  }
258
262
  }
259
263
 
@@ -270,11 +274,11 @@ class SftpClient {
270
274
  */
271
275
  async exists(remotePath) {
272
276
  try {
273
- if (utils.haveConnection(this, 'exists')) {
277
+ if (haveConnection(this, 'exists')) {
274
278
  if (remotePath === '.') {
275
279
  return 'd';
276
280
  }
277
- let absPath = await utils.normalizeRemotePath(this, remotePath);
281
+ let absPath = await normalizeRemotePath(this, remotePath);
278
282
  try {
279
283
  this.debugMsg(`exists -> ${absPath}`);
280
284
  let info = await this.stat(absPath);
@@ -299,7 +303,11 @@ class SftpClient {
299
303
  return false;
300
304
  }
301
305
  } catch (err) {
302
- return utils.handleError(err, 'exists');
306
+ if (err.custom) {
307
+ throw err;
308
+ } else {
309
+ throw fmtError(err, 'exists', err.code);
310
+ }
303
311
  }
304
312
  }
305
313
 
@@ -319,20 +327,14 @@ class SftpClient {
319
327
  */
320
328
  list(remotePath, pattern = /.*/) {
321
329
  return new Promise((resolve, reject) => {
322
- if (utils.haveConnection(this, 'list', reject)) {
330
+ if (haveConnection(this, 'list', reject)) {
323
331
  const reg = /-/gi;
324
332
  this.debugMsg(`list -> ${remotePath} filter -> ${pattern}`);
325
- utils.addTempListeners(this, 'list', reject);
333
+ addTempListeners(this, 'list', reject);
326
334
  this.sftp.readdir(remotePath, (err, fileList) => {
327
335
  if (err) {
328
336
  this.debugMsg(`list error ${err.message} code: ${err.code}`);
329
- reject(
330
- utils.formatError(
331
- `${err.message} ${remotePath}`,
332
- 'list',
333
- err.code
334
- )
335
- );
337
+ reject(fmtError(`${err.message} ${remotePath}`, 'list', err.code));
336
338
  } else {
337
339
  this.debugMsg('list <- ', fileList);
338
340
  let newList = [];
@@ -365,9 +367,11 @@ class SftpClient {
365
367
  }
366
368
  resolve(newList.filter((item) => regex.test(item.name)));
367
369
  }
368
- utils.removeTempListeners(this.client);
369
370
  });
370
371
  }
372
+ }).finally((rsp) => {
373
+ removeTempListeners(this.client);
374
+ return rsp;
371
375
  });
372
376
  }
373
377
 
@@ -387,16 +391,12 @@ class SftpClient {
387
391
  */
388
392
  get(remotePath, dst, options = {}) {
389
393
  return new Promise((resolve, reject) => {
390
- if (utils.haveConnection(this, 'get', reject)) {
394
+ if (haveConnection(this, 'get', reject)) {
391
395
  this.debugMsg(`get -> ${remotePath} `, options);
392
- utils.addTempListeners(this, 'get', reject);
396
+ addTempListeners(this, 'get', reject);
393
397
  let rdr = this.sftp.createReadStream(remotePath, options);
394
398
  rdr.once('error', (err) => {
395
- utils.removeListeners(rdr);
396
- reject(
397
- utils.formatError(`${err.message} ${remotePath}`, 'get', err.code)
398
- );
399
- utils.removeTempListeners(this.client);
399
+ reject(fmtError(`${err.message} ${remotePath}`, 'get', err.code));
400
400
  });
401
401
  if (dst === undefined) {
402
402
  // no dst specified, return buffer of data
@@ -404,7 +404,6 @@ class SftpClient {
404
404
  let concatStream = concat((buff) => {
405
405
  rdr.removeAllListeners('error');
406
406
  resolve(buff);
407
- utils.removeTempListeners(this.client);
408
407
  });
409
408
  rdr.pipe(concatStream);
410
409
  } else {
@@ -418,9 +417,8 @@ class SftpClient {
418
417
  wtr = dst;
419
418
  }
420
419
  wtr.once('error', (err) => {
421
- utils.removeListeners(rdr);
422
420
  reject(
423
- utils.formatError(
421
+ fmtError(
424
422
  `${err.message} ${typeof dst === 'string' ? dst : ''}`,
425
423
  'get',
426
424
  err.code
@@ -429,10 +427,8 @@ class SftpClient {
429
427
  if (options.autoClose === false) {
430
428
  rdr.destroy();
431
429
  }
432
- utils.removeTempListeners(this.client);
433
430
  });
434
431
  wtr.once('finish', () => {
435
- utils.removeListeners(rdr);
436
432
  if (options.autoClose === false) {
437
433
  rdr.destroy();
438
434
  }
@@ -441,11 +437,13 @@ class SftpClient {
441
437
  } else {
442
438
  resolve(wtr);
443
439
  }
444
- utils.removeTempListeners(this.client);
445
440
  });
446
441
  rdr.pipe(wtr);
447
442
  }
448
443
  }
444
+ }).finally((rsp) => {
445
+ removeTempListeners(this.client);
446
+ return rsp;
449
447
  });
450
448
  }
451
449
 
@@ -470,30 +468,30 @@ class SftpClient {
470
468
  ftype === false
471
469
  ? `No such file ${remotePath}`
472
470
  : `Not a regular file ${remotePath}`;
473
- return Promise.reject(
474
- utils.formatError(msg, 'fastGet', errorCode.badPath)
475
- );
471
+ return Promise.reject(fmtError(msg, 'fastGet', errorCode.badPath));
476
472
  }
477
473
  })
478
474
  .then(() => {
479
475
  return new Promise((resolve, reject) => {
480
- if (utils.haveConnection(this, 'fastGet', reject)) {
476
+ if (haveConnection(this, 'fastGet', reject)) {
481
477
  this.debugMsg(
482
478
  `fastGet -> remote: ${remotePath} local: ${localPath} `,
483
479
  options
484
480
  );
485
- utils.addTempListeners(this, 'fastGet', reject);
481
+ addTempListeners(this, 'fastGet', reject);
486
482
  this.sftp.fastGet(remotePath, localPath, options, (err) => {
487
483
  if (err) {
488
484
  this.debugMsg(`fastGet error ${err.message} code: ${err.code}`);
489
- reject(utils.formatError(err, 'fastGet'));
485
+ reject(fmtError(err, 'fastGet'));
490
486
  }
491
487
  resolve(
492
488
  `${remotePath} was successfully download to ${localPath}!`
493
489
  );
494
- utils.removeTempListeners(this.client);
495
490
  });
496
491
  }
492
+ }).finally((rsp) => {
493
+ removeTempListeners(this.client);
494
+ return rsp;
497
495
  });
498
496
  });
499
497
  }
@@ -513,18 +511,13 @@ class SftpClient {
513
511
  */
514
512
  fastPut(localPath, remotePath, options) {
515
513
  this.debugMsg(`fastPut -> local ${localPath} remote ${remotePath}`);
516
- return utils
517
- .localExists(localPath)
514
+ return localExists(localPath)
518
515
  .then((localStatus) => {
519
516
  this.debugMsg(`fastPut <- localStatus ${localStatus}`);
520
517
  if (localStatus !== '-') {
521
518
  this.debugMsg('fastPut reject bad source path');
522
519
  return Promise.reject(
523
- utils.formatError(
524
- `Bad path ${localPath}`,
525
- 'fastPut',
526
- errorCode.badPath
527
- )
520
+ fmtError(`Bad path ${localPath}`, 'fastPut', errorCode.badPath)
528
521
  );
529
522
  }
530
523
  return new Promise((resolve, reject) => {
@@ -532,11 +525,7 @@ class SftpClient {
532
525
  if (err) {
533
526
  this.debugMsg('fastPut reject no access source');
534
527
  reject(
535
- utils.formatError(
536
- `${err.message} ${localPath}`,
537
- 'fastPut',
538
- err.code
539
- )
528
+ fmtError(`${err.message} ${localPath}`, 'fastPut', err.code)
540
529
  );
541
530
  } else {
542
531
  this.debugMsg('fastPut source access ok');
@@ -547,18 +536,18 @@ class SftpClient {
547
536
  })
548
537
  .then(() => {
549
538
  return new Promise((resolve, reject) => {
550
- if (utils.haveConnection(this, 'fastPut', reject)) {
539
+ if (haveConnection(this, 'fastPut', reject)) {
551
540
  this.debugMsg(
552
541
  `fastPut -> local: ${localPath} remote: ${remotePath} opts: ${JSON.stringify(
553
542
  options
554
543
  )}`
555
544
  );
556
- utils.addTempListeners(this, 'fastPut', reject);
545
+ addTempListeners(this, 'fastPut', reject);
557
546
  this.sftp.fastPut(localPath, remotePath, options, (err) => {
558
547
  if (err) {
559
548
  this.debugMsg(`fastPut error ${err.message} ${err.code}`);
560
549
  reject(
561
- utils.formatError(
550
+ fmtError(
562
551
  `${err.message} Local: ${localPath} Remote: ${remotePath}`,
563
552
  'fastPut',
564
553
  err.code
@@ -571,11 +560,10 @@ class SftpClient {
571
560
  );
572
561
  });
573
562
  }
563
+ }).finally((rsp) => {
564
+ removeTempListeners(this.client);
565
+ return rsp;
574
566
  });
575
- })
576
- .then((msg) => {
577
- utils.removeTempListeners(this.client);
578
- return msg;
579
567
  });
580
568
  }
581
569
 
@@ -597,13 +585,12 @@ class SftpClient {
597
585
  } -> ${remotePath}`,
598
586
  options
599
587
  );
600
- return utils
601
- .localExists(typeof localSrc === 'string' ? localSrc : 'dummy')
588
+ return localExists(typeof localSrc === 'string' ? localSrc : 'dummy')
602
589
  .then((localStatus) => {
603
590
  if (typeof localSrc === 'string' && localStatus !== '-') {
604
591
  this.debugMsg(`put: file does not exist ${localSrc} - rejecting`);
605
592
  return Promise.reject(
606
- utils.formatError(`Bad path ${localSrc}`, 'put', errorCode.badPath)
593
+ fmtError(`Bad path ${localSrc}`, 'put', errorCode.badPath)
607
594
  );
608
595
  }
609
596
  return new Promise((resolve, reject) => {
@@ -615,7 +602,7 @@ class SftpClient {
615
602
  if (err) {
616
603
  this.debugMsg(`put: Cannot read ${localSrc} - rejecting`);
617
604
  reject(
618
- utils.formatError(
605
+ fmtError(
619
606
  `Permission denied ${localSrc}`,
620
607
  'put',
621
608
  errorCode.permission
@@ -635,26 +622,17 @@ class SftpClient {
635
622
  })
636
623
  .then(() => {
637
624
  return new Promise((resolve, reject) => {
638
- if (utils.haveConnection(this, 'put', reject)) {
639
- utils.addTempListeners(this, 'put', reject);
625
+ if (haveConnection(this, 'put', reject)) {
626
+ addTempListeners(this, 'put', reject);
640
627
  let stream = this.sftp.createWriteStream(remotePath, options);
641
628
  stream.once('error', (err) => {
642
- reject(
643
- utils.formatError(
644
- `${err.message} ${remotePath}`,
645
- 'put',
646
- err.code
647
- )
648
- );
649
- utils.removeTempListeners(this.client);
629
+ reject(fmtError(`${err.message} ${remotePath}`, 'put', err.code));
650
630
  });
651
631
  stream.once('finish', () => {
652
- utils.removeListeners(stream);
653
632
  if (options.autoClose === false) {
654
633
  stream.destroy();
655
634
  }
656
635
  resolve(`Uploaded data stream to ${remotePath}`);
657
- utils.removeTempListeners(this.client);
658
636
  });
659
637
  if (localSrc instanceof Buffer) {
660
638
  this.debugMsg('put source is a buffer');
@@ -669,9 +647,8 @@ class SftpClient {
669
647
  rdr = localSrc;
670
648
  }
671
649
  rdr.once('error', (err) => {
672
- utils.removeListeners(stream);
673
650
  reject(
674
- utils.formatError(
651
+ fmtError(
675
652
  `${err.message} ${
676
653
  typeof localSrc === 'string' ? localSrc : ''
677
654
  }`,
@@ -682,11 +659,13 @@ class SftpClient {
682
659
  if (options.autoClose === false) {
683
660
  stream.destroy();
684
661
  }
685
- utils.removeTempListeners(this.client);
686
662
  });
687
663
  rdr.pipe(stream);
688
664
  }
689
665
  }
666
+ }).finally((rsp) => {
667
+ removeTempListeners(this.client);
668
+ return rsp;
690
669
  });
691
670
  });
692
671
  }
@@ -701,31 +680,21 @@ class SftpClient {
701
680
  */
702
681
  append(input, remotePath, options = {}) {
703
682
  return new Promise((resolve, reject) => {
704
- if (utils.haveConnection(this, 'append', reject)) {
683
+ if (haveConnection(this, 'append', reject)) {
705
684
  if (typeof input === 'string') {
706
- reject(
707
- utils.formatError('Cannot append one file to another', 'append')
708
- );
685
+ reject(fmtError('Cannot append one file to another', 'append'));
709
686
  } else {
710
687
  this.debugMsg(`append -> remote: ${remotePath} `, options);
711
- utils.addTempListeners(this, 'append', reject);
688
+ addTempListeners(this, 'append', reject);
712
689
  options.flags = 'a';
713
690
  let stream = this.sftp.createWriteStream(remotePath, options);
714
691
  stream.once('error', (err) => {
715
- utils.removeListeners(stream);
716
692
  reject(
717
- utils.formatError(
718
- `${err.message} ${remotePath}`,
719
- 'append',
720
- err.code
721
- )
693
+ fmtError(`${err.message} ${remotePath}`, 'append', err.code)
722
694
  );
723
- utils.removeTempListeners(this.client);
724
695
  });
725
696
  stream.once('finish', () => {
726
- utils.removeListeners(stream);
727
697
  resolve(`Appended data to ${remotePath}`);
728
- utils.removeTempListeners(this.client);
729
698
  });
730
699
  if (input instanceof Buffer) {
731
700
  stream.end(input);
@@ -734,6 +703,9 @@ class SftpClient {
734
703
  }
735
704
  }
736
705
  }
706
+ }).finally((rsp) => {
707
+ removeTempListeners(this.client);
708
+ return rsp;
737
709
  });
738
710
  }
739
711
 
@@ -750,23 +722,23 @@ class SftpClient {
750
722
  const _mkdir = (p) => {
751
723
  return new Promise((resolve, reject) => {
752
724
  this.debugMsg(`mkdir -> ${p}`);
753
- utils.addTempListeners(this, 'mkdir', reject);
725
+ addTempListeners(this, 'mkdir', reject);
754
726
  this.sftp.mkdir(p, (err) => {
755
727
  if (err) {
756
728
  this.debugMsg(`mkdir error ${err.message} code: ${err.code}`);
757
- reject(
758
- utils.formatError(`${err.message} ${p}`, '_mkdir', err.code)
759
- );
729
+ reject(fmtError(`${err.message} ${p}`, '_mkdir', err.code));
760
730
  }
761
731
  resolve(`${p} directory created`);
762
- utils.removeTempListeners(this.client);
763
732
  });
733
+ }).finally((rsp) => {
734
+ removeTempListeners(this.client);
735
+ return rsp;
764
736
  });
765
737
  };
766
738
 
767
739
  try {
768
- utils.haveConnection(this, 'mkdir');
769
- let rPath = await utils.normalizeRemotePath(this, remotePath);
740
+ haveConnection(this, 'mkdir');
741
+ let rPath = await normalizeRemotePath(this, remotePath);
770
742
  if (!recursive) {
771
743
  return _mkdir(rPath);
772
744
  }
@@ -779,7 +751,11 @@ class SftpClient {
779
751
  }
780
752
  return _mkdir(rPath);
781
753
  } catch (err) {
782
- return utils.handleError(`${err.message} ${remotePath}`, 'mkdir');
754
+ if (err.custom) {
755
+ throw err;
756
+ } else {
757
+ throw fmtError(`${err.message} ${remotePath}`, 'mkdir', err.code);
758
+ }
783
759
  }
784
760
  }
785
761
 
@@ -797,23 +773,23 @@ class SftpClient {
797
773
  const _rmdir = (p) => {
798
774
  return new Promise((resolve, reject) => {
799
775
  this.debugMsg(`rmdir -> ${p}`);
800
- utils.addTempListeners(this, 'rmdir', reject);
776
+ addTempListeners(this, 'rmdir', reject);
801
777
  this.sftp.rmdir(p, (err) => {
802
778
  if (err) {
803
779
  this.debugMsg(`rmdir error ${err.message} code: ${err.code}`);
804
- reject(
805
- utils.formatError(`${err.message} ${p}`, '_rmdir', err.code)
806
- );
780
+ reject(fmtError(`${err.message} ${p}`, '_rmdir', err.code));
807
781
  }
808
782
  resolve('Successfully removed directory');
809
- utils.removeTempListeners(this.client);
810
783
  });
784
+ }).finally((rsp) => {
785
+ removeTempListeners(this.client);
786
+ return rsp;
811
787
  });
812
788
  };
813
789
 
814
790
  try {
815
- utils.haveConnection(this, 'rmdir');
816
- let absPath = await utils.normalizeRemotePath(this, remotePath);
791
+ haveConnection(this, 'rmdir');
792
+ let absPath = await normalizeRemotePath(this, remotePath);
817
793
  if (!recursive) {
818
794
  return _rmdir(absPath);
819
795
  }
@@ -832,7 +808,11 @@ class SftpClient {
832
808
  }
833
809
  return _rmdir(absPath);
834
810
  } catch (err) {
835
- return utils.handleError(err, 'rmdir');
811
+ if (err.custom) {
812
+ throw err;
813
+ } else {
814
+ throw fmtError(err, 'rmdir', err.code);
815
+ }
836
816
  }
837
817
  }
838
818
 
@@ -849,9 +829,9 @@ class SftpClient {
849
829
  */
850
830
  delete(remotePath, notFoundOK = false) {
851
831
  return new Promise((resolve, reject) => {
852
- if (utils.haveConnection(this, 'delete', reject)) {
832
+ if (haveConnection(this, 'delete', reject)) {
853
833
  this.debugMsg(`delete -> ${remotePath}`);
854
- utils.addTempListeners(this, 'delete', reject);
834
+ addTempListeners(this, 'delete', reject);
855
835
  this.sftp.unlink(remotePath, (err) => {
856
836
  if (err) {
857
837
  this.debugMsg(`delete error ${err.message} code: ${err.code}`);
@@ -860,18 +840,16 @@ class SftpClient {
860
840
  resolve(`Successfully deleted ${remotePath}`);
861
841
  } else {
862
842
  reject(
863
- utils.formatError(
864
- `${err.message} ${remotePath}`,
865
- 'delete',
866
- err.code
867
- )
843
+ fmtError(`${err.message} ${remotePath}`, 'delete', err.code)
868
844
  );
869
845
  }
870
846
  }
871
847
  resolve(`Successfully deleted ${remotePath}`);
872
- utils.removeTempListeners(this.client);
873
848
  });
874
849
  }
850
+ }).finally((rsp) => {
851
+ removeTempListeners(this.client);
852
+ return rsp;
875
853
  });
876
854
  }
877
855
 
@@ -888,14 +866,14 @@ class SftpClient {
888
866
  */
889
867
  rename(fromPath, toPath) {
890
868
  return new Promise((resolve, reject) => {
891
- if (utils.haveConnection(this, 'rename', reject)) {
869
+ if (haveConnection(this, 'rename', reject)) {
892
870
  this.debugMsg(`rename -> ${fromPath} ${toPath}`);
893
- utils.addTempListeners(this, 'rename', reject);
871
+ addTempListeners(this, 'rename', reject);
894
872
  this.sftp.rename(fromPath, toPath, (err) => {
895
873
  if (err) {
896
874
  this.debugMsg(`rename error ${err.message} code: ${err.code}`);
897
875
  reject(
898
- utils.formatError(
876
+ fmtError(
899
877
  `${err.message} From: ${fromPath} To: ${toPath}`,
900
878
  'rename',
901
879
  err.code
@@ -903,9 +881,11 @@ class SftpClient {
903
881
  );
904
882
  }
905
883
  resolve(`Successfully renamed ${fromPath} to ${toPath}`);
906
- utils.removeTempListeners(this.client);
907
884
  });
908
885
  }
886
+ }).finally((rsp) => {
887
+ removeTempListeners(this.client);
888
+ return rsp;
909
889
  });
910
890
  }
911
891
 
@@ -923,14 +903,14 @@ class SftpClient {
923
903
  */
924
904
  posixRename(fromPath, toPath) {
925
905
  return new Promise((resolve, reject) => {
926
- if (utils.haveConnection(this, 'posixRename', reject)) {
906
+ if (haveConnection(this, 'posixRename', reject)) {
927
907
  this.debugMsg(`posixRename -> ${fromPath} ${toPath}`);
928
- utils.addTempListeners(this, 'posixRename', reject);
908
+ addTempListeners(this, 'posixRename', reject);
929
909
  this.sftp.ext_openssh_rename(fromPath, toPath, (err) => {
930
910
  if (err) {
931
911
  this.debugMsg(`posixRename error ${err.message} code: ${err.code}`);
932
912
  reject(
933
- utils.formatError(
913
+ fmtError(
934
914
  `${err.message} From: ${fromPath} To: ${toPath}`,
935
915
  'posixRename',
936
916
  err.code
@@ -938,9 +918,11 @@ class SftpClient {
938
918
  );
939
919
  }
940
920
  resolve(`Successful POSIX rename ${fromPath} to ${toPath}`);
941
- utils.removeTempListeners(this.client);
942
921
  });
943
922
  }
923
+ }).finally((rsp) => {
924
+ removeTempListeners(this.client);
925
+ return rsp;
944
926
  });
945
927
  }
946
928
 
@@ -957,16 +939,16 @@ class SftpClient {
957
939
  chmod(remotePath, mode) {
958
940
  return new Promise((resolve, reject) => {
959
941
  this.debugMsg(`chmod -> ${remotePath} ${mode}`);
960
- utils.addTempListeners(this, 'chmod', reject);
942
+ addTempListeners(this, 'chmod', reject);
961
943
  this.sftp.chmod(remotePath, mode, (err) => {
962
944
  if (err) {
963
- reject(
964
- utils.formatError(`${err.message} ${remotePath}`, 'chmod', err.code)
965
- );
945
+ reject(fmtError(`${err.message} ${remotePath}`, 'chmod', err.code));
966
946
  }
967
947
  resolve('Successfully change file mode');
968
- utils.removeTempListeners(this.client);
969
948
  });
949
+ }).finally((rsp) => {
950
+ removeTempListeners(this.client);
951
+ return rsp;
970
952
  });
971
953
  }
972
954
 
@@ -986,14 +968,10 @@ class SftpClient {
986
968
  async uploadDir(srcDir, dstDir, filter = /.*/) {
987
969
  try {
988
970
  this.debugMsg(`uploadDir -> ${srcDir} ${dstDir}`);
989
- utils.haveConnection(this, 'uploadDir');
971
+ haveConnection(this, 'uploadDir');
990
972
  let dstStatus = await this.exists(dstDir);
991
973
  if (dstStatus && dstStatus !== 'd') {
992
- throw utils.formatError(
993
- `Bad path ${dstDir}`,
994
- 'uploadDir',
995
- errorCode.badPath
996
- );
974
+ throw fmtError(`Bad path ${dstDir}`, 'uploadDir', errorCode.badPath);
997
975
  }
998
976
  if (!dstStatus) {
999
977
  await this.mkdir(dstDir, true);
@@ -1021,7 +999,11 @@ class SftpClient {
1021
999
  }
1022
1000
  return `${srcDir} uploaded to ${dstDir}`;
1023
1001
  } catch (err) {
1024
- return utils.handleError(err, 'uploadDir');
1002
+ if (err.custom) {
1003
+ throw err;
1004
+ } else {
1005
+ throw fmtError(err, 'uploadDir');
1006
+ }
1025
1007
  }
1026
1008
  }
1027
1009
 
@@ -1041,15 +1023,11 @@ class SftpClient {
1041
1023
  async downloadDir(srcDir, dstDir, filter = /.*/) {
1042
1024
  try {
1043
1025
  this.debugMsg(`downloadDir -> ${srcDir} ${dstDir}`);
1044
- utils.haveConnection(this, 'downloadDir');
1026
+ haveConnection(this, 'downloadDir');
1045
1027
  let fileList = await this.list(srcDir, filter);
1046
- let dstStatus = await utils.localExists(dstDir);
1028
+ let dstStatus = await localExists(dstDir);
1047
1029
  if (dstStatus && dstStatus !== 'd') {
1048
- throw utils.formatError(
1049
- `Bad path ${dstDir}`,
1050
- 'downloadDir',
1051
- errorCode.badPath
1052
- );
1030
+ throw fmtError(`Bad path ${dstDir}`, 'downloadDir', errorCode.badPath);
1053
1031
  }
1054
1032
  if (!dstStatus) {
1055
1033
  fs.mkdirSync(dstDir, {recursive: true});
@@ -1072,7 +1050,11 @@ class SftpClient {
1072
1050
  }
1073
1051
  return `${srcDir} downloaded to ${dstDir}`;
1074
1052
  } catch (err) {
1075
- return utils.handleError(err, 'downloadDir');
1053
+ if (err.custom) {
1054
+ throw err;
1055
+ } else {
1056
+ throw fmtError(err, 'downloadDir', err.code);
1057
+ }
1076
1058
  }
1077
1059
  }
1078
1060
 
@@ -1083,42 +1065,23 @@ class SftpClient {
1083
1065
  *
1084
1066
  */
1085
1067
  end() {
1068
+ let endCloseHandler;
1086
1069
  return new Promise((resolve, reject) => {
1087
- const endErrorListener = (err) => {
1088
- // we don't care about errors at this point
1089
- // so do nothiing
1090
- this.debugMsg(
1091
- `endErrorListener called with ${err.message} and code ${err.code}`
1092
- );
1093
- this.errorHandled = true;
1094
- if (err.code !== 'ECONNRESET') {
1095
- reject(utils.formatError(err, 'end'));
1096
- } else {
1097
- this.debugMsg('Error is ECONNRESET - ignoring error');
1098
- }
1099
- };
1100
-
1101
- try {
1102
- if (!utils.hasListener(this.client, 'error', 'endErrorListener')) {
1103
- this.debugMsg('Adding enderrorListener');
1104
- this.client.prependListener('error', endErrorListener);
1105
- } else {
1106
- this.debugMsg('endErrorListener already set');
1107
- }
1108
- this.endCalled = true;
1109
- if (utils.haveConnection(this, 'end', reject)) {
1110
- this.debugMsg('Have connection - calling end()');
1111
- this.client.end();
1112
- } else {
1113
- this.debugMsg('No connection - skipping call to end()');
1114
- }
1115
- resolve(true);
1116
- } catch (err) {
1117
- utils.handleError(err, 'end', reject);
1118
- } finally {
1070
+ this.endCalled = true;
1071
+ addTempListeners(this, 'end', reject);
1072
+ endCloseHandler = () => {
1119
1073
  this.sftp = undefined;
1120
- this.endCalled = false;
1074
+ resolve(true);
1075
+ };
1076
+ this.on('close', endCloseHandler);
1077
+ if (haveConnection(this, 'end', reject)) {
1078
+ this.debugMsg('Have connection - calling end()');
1079
+ this.client.end();
1121
1080
  }
1081
+ }).finally(() => {
1082
+ removeTempListeners(this.client);
1083
+ this.removeListener('close', endCloseHandler);
1084
+ return true;
1122
1085
  });
1123
1086
  }
1124
1087
  }
package/src/utils.js CHANGED
@@ -1,6 +1,5 @@
1
1
  'use strict';
2
2
 
3
- const {prependListener} = require('cluster');
4
3
  const fs = require('fs');
5
4
  const {errorCode} = require('./constants');
6
5
 
@@ -13,7 +12,7 @@ const {errorCode} = require('./constants');
13
12
  * attempts to complete before giving up
14
13
  * @returns {Error} New error with custom error message
15
14
  */
16
- function formatError(err, name = 'sftp', eCode, retryCount) {
15
+ function fmtError(err, name = 'sftp', eCode, retryCount) {
17
16
  let msg = '';
18
17
  let code = '';
19
18
  let retry = retryCount
@@ -46,9 +45,6 @@ function formatError(err, name = 'sftp', eCode, retryCount) {
46
45
  `${name}: Remote host has reset the connection: ` +
47
46
  `${err.message}${retry}`;
48
47
  break;
49
- case 'ENOENT':
50
- msg = `${name}: ${err.message}${retry}`;
51
- break;
52
48
  default:
53
49
  msg = `${name}: ${err.message}${retry}`;
54
50
  }
@@ -60,44 +56,6 @@ function formatError(err, name = 'sftp', eCode, retryCount) {
60
56
  return newError;
61
57
  }
62
58
 
63
- /**
64
- * Tests an error to see if it is one which has already been customised
65
- * by this module or not. If not, applies appropriate customisation.
66
- *
67
- * @param {Error} err - an Error object
68
- * @param {String} name - name to be used in customised error message
69
- * @param {Function} reject - If defined, call this function instead of
70
- * throwing the error
71
- * @throws {Error}
72
- */
73
- function handleError(err, name, reject) {
74
- if (reject) {
75
- if (err.custom) {
76
- reject(err);
77
- } else {
78
- reject(formatError(err, name));
79
- }
80
- } else {
81
- if (err.custom) {
82
- throw err;
83
- } else {
84
- throw formatError(err, name, err.code);
85
- }
86
- }
87
- }
88
-
89
- /**
90
- * Remove all ready, error and end listeners.
91
- *
92
- * @param {Emitter} emitter - The emitter object to remove listeners from
93
- */
94
- function removeListeners(emitter) {
95
- let listeners = emitter.eventNames();
96
- listeners.forEach((name) => {
97
- emitter.removeAllListeners(name);
98
- });
99
- }
100
-
101
59
  let tempListeners = [];
102
60
 
103
61
  /**
@@ -108,13 +66,13 @@ let tempListeners = [];
108
66
  * @throws {Error} Throws new error
109
67
  */
110
68
  function errorListener(client, name, reject) {
111
- let fn = function (err) {
69
+ let fn = (err) => {
112
70
  if (!client.errorHandled) {
113
71
  client.errorHandled = true;
114
72
  if (reject) {
115
- reject(formatError(err, name, err.code));
73
+ reject(fmtError(err, name, err.code));
116
74
  } else {
117
- throw formatError(err, name, err.code);
75
+ throw fmtError(err, name, err.code);
118
76
  }
119
77
  }
120
78
  client.debugMsg(`Handled Error: ${err.message} ${err.code}`);
@@ -125,15 +83,15 @@ function errorListener(client, name, reject) {
125
83
 
126
84
  function endListener(client, name, reject) {
127
85
  let fn = function () {
86
+ client.debugMsg(`Handled end event for ${name}`);
128
87
  if (!client.endCalled) {
88
+ client.sftp = undefined;
129
89
  if (reject) {
130
- reject(formatError('Unexpected end event raised', name));
90
+ reject(fmtError('Unexpected end event raised', name));
131
91
  } else {
132
- throw formatError('Unexpected end event raised', name);
92
+ throw fmtError('Unexpected end event raised', name);
133
93
  }
134
94
  }
135
- client.debugMsg(`Handled end event for ${name}`);
136
- client.sftp = undefined;
137
95
  };
138
96
  tempListeners.push(['end', fn]);
139
97
  return fn;
@@ -141,15 +99,15 @@ function endListener(client, name, reject) {
141
99
 
142
100
  function closeListener(client, name, reject) {
143
101
  let fn = function () {
102
+ client.debugMsg(`handled close event for ${name}`);
144
103
  if (!client.endCalled) {
104
+ client.sftp = undefined;
145
105
  if (reject) {
146
- reject(formatError('Unexpected close event raised', name));
106
+ reject(fmtError('Unexpected close event raised', name));
147
107
  } else {
148
- throw formatError('Unexpected close event raised', name);
108
+ throw fmtError('Unexpected close event raised', name);
149
109
  }
150
110
  }
151
- client.debugMsg(`handled close event for ${name}`);
152
- client.sftp = undefined;
153
111
  };
154
112
  tempListeners.push(['close', fn]);
155
113
  return fn;
@@ -213,7 +171,7 @@ async function normalizeRemotePath(client, aPath) {
213
171
  }
214
172
  return aPath;
215
173
  } catch (err) {
216
- throw formatError(err, 'normalizeRemotePath');
174
+ throw fmtError(err, 'normalizeRemotePath');
217
175
  }
218
176
  }
219
177
 
@@ -229,7 +187,7 @@ async function normalizeRemotePath(client, aPath) {
229
187
  */
230
188
  function haveConnection(client, name, reject) {
231
189
  if (!client.sftp) {
232
- let newError = formatError(
190
+ let newError = fmtError(
233
191
  'No SFTP connection available',
234
192
  name,
235
193
  errorCode.connect
@@ -244,31 +202,8 @@ function haveConnection(client, name, reject) {
244
202
  return true;
245
203
  }
246
204
 
247
- function dumpListeners(emitter) {
248
- let eventNames = emitter.eventNames();
249
- if (eventNames.length) {
250
- console.log('Listener Data');
251
- eventNames.map((n) => {
252
- let listeners = emitter.listeners(n);
253
- console.log(`${n}: ${emitter.listenerCount(n)}`);
254
- console.dir(listeners);
255
- listeners.map((l) => {
256
- console.log(`listener name = ${l.name}`);
257
- });
258
- });
259
- }
260
- }
261
-
262
- function hasListener(emitter, eventName, listenerName) {
263
- let listeners = emitter.listeners(eventName);
264
- let matches = listeners.filter((l) => l.name == listenerName);
265
- return matches.length === 0 ? false : true;
266
- }
267
-
268
205
  module.exports = {
269
- formatError,
270
- handleError,
271
- removeListeners,
206
+ fmtError,
272
207
  errorListener,
273
208
  endListener,
274
209
  closeListener,
@@ -276,7 +211,5 @@ module.exports = {
276
211
  removeTempListeners,
277
212
  localExists,
278
213
  normalizeRemotePath,
279
- haveConnection,
280
- dumpListeners,
281
- hasListener
214
+ haveConnection
282
215
  };