ssh2-sftp-client 11.0.0 → 12.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/src/index.js CHANGED
@@ -2,7 +2,6 @@
2
2
  const { Client } = require('ssh2');
3
3
  const fs = require('node:fs');
4
4
  const concat = require('concat-stream');
5
- const promiseRetry = require('promise-retry');
6
5
  const { join, parse } = require('node:path');
7
6
  const {
8
7
  globalListener,
@@ -26,7 +25,7 @@ class SftpClient {
26
25
  close: () => console.log('Global close listener: close event raised'),
27
26
  },
28
27
  ) {
29
- this.version = '11.0.0';
28
+ this.version = '12.0.1';
30
29
  this.client = new Client();
31
30
  this.sftp = undefined;
32
31
  this.clientName = clientName;
@@ -58,35 +57,32 @@ class SftpClient {
58
57
  fmtError(err, name = 'sftp', eCode, retryCount) {
59
58
  let msg = '';
60
59
  let code = '';
61
- const retry = retryCount
62
- ? ` after ${retryCount} ${retryCount > 1 ? 'attempts' : 'attempt'}`
63
- : '';
64
60
 
65
61
  if (err === undefined) {
66
62
  msg = `${name}: Undefined error - probably a bug!`;
67
63
  code = errorCode.generic;
68
64
  } else if (typeof err === 'string') {
69
- msg = `${name}: ${err}${retry}`;
65
+ msg = `${name}: ${err}`;
70
66
  code = eCode || errorCode.generic;
71
67
  } else if (err.custom) {
72
- msg = `${name}->${err.message}${retry}`;
68
+ msg = `${name}->${err.message}`;
73
69
  code = err.code;
74
70
  } else {
75
71
  switch (err.code) {
76
72
  case 'ENOTFOUND': {
77
- msg = `${name}: Address lookup failed for host${retry}`;
73
+ msg = `${name}: Address lookup failed for host`;
78
74
  break;
79
75
  }
80
76
  case 'ECONNREFUSED': {
81
- msg = `${name}: Remote host refused connection${retry}`;
77
+ msg = `${name}: Remote host refused connection`;
82
78
  break;
83
79
  }
84
80
  case 'ECONNRESET': {
85
- msg = `${name}: Remote host has reset the connection: ${err.message}${retry}`;
81
+ msg = `${name}: Remote host has reset the connection: ${err.message}`;
86
82
  break;
87
83
  }
88
84
  default: {
89
- msg = `${name}: ${err.message}${retry}`;
85
+ msg = `${name}: ${err.message}`;
90
86
  }
91
87
  }
92
88
  code = err.code || errorCode.generic;
@@ -122,118 +118,61 @@ class SftpClient {
122
118
  }
123
119
 
124
120
  /**
125
- * @async
126
121
  *
127
- * Create a new SFTP connection to a remote SFTP server
122
+ * Create a new SFTP connection to a remote SFTP server.
123
+ * The connection options are the same as those offered
124
+ * by the underlying SSH2 module.
128
125
  *
129
126
  * @param {Object} config - an SFTP configuration object
130
127
  *
131
128
  * @return {Promise<Object>} which will resolve to an sftp client object
132
129
  */
133
- getConnection(config) {
130
+ connect(config) {
134
131
  let doReady, listeners;
135
132
  return new Promise((resolve, reject) => {
136
133
  listeners = addTempListeners(this, 'getConnection', reject);
134
+ if (config.debug) {
135
+ this.debug = config.debug;
136
+ this.debugMsg('connect: Debugging turned on');
137
+ this.debugMsg(`ssh2-sftp-client Version: ${this.version} `, process.versions);
138
+ }
139
+ this.promiseLimit = config.promiseLimit ?? 10;
140
+
137
141
  doReady = () => {
138
- resolve(true);
142
+ this.client.sftp((err, sftp) => {
143
+ if (err) {
144
+ reject(this.fmtError(err));
145
+ } else {
146
+ this.sftp = sftp;
147
+ resolve(sftp);
148
+ }
149
+ });
139
150
  };
140
151
  this.on('ready', doReady);
152
+
141
153
  try {
142
- this.client.connect(config);
154
+ if (this.sftp) {
155
+ reject(
156
+ this.fmtError(
157
+ 'An existing SFTP connection is already defined',
158
+ 'connect',
159
+ errorCode.connect,
160
+ ),
161
+ );
162
+ } else {
163
+ this.client.connect(config);
164
+ }
143
165
  } catch (err) {
166
+ this.end();
144
167
  reject(err);
145
168
  }
146
169
  }).finally(() => {
147
170
  this.removeListener('ready', doReady);
148
171
  removeTempListeners(this, listeners, 'getConnection');
172
+ this._resetEventFlags();
149
173
  });
150
174
  }
151
175
 
152
- getSftpChannel() {
153
- return new Promise((resolve, reject) => {
154
- this.client.sftp((err, sftp) => {
155
- if (err) {
156
- reject(this.fmtError(err, 'getSftpChannel', err.code));
157
- } else {
158
- this.sftp = sftp;
159
- resolve(sftp);
160
- }
161
- });
162
- });
163
- }
164
-
165
- /**
166
- * @async
167
- *
168
- * Create a new SFTP connection to a remote SFTP server.
169
- * The connection options are the same as those offered
170
- * by the underlying SSH2 module.
171
- *
172
- * @param {Object} config - an SFTP configuration object
173
- *
174
- * @return {Promise<Object>} which will resolve to an sftp client object
175
- */
176
- async connect(config) {
177
- let listeners;
178
-
179
- try {
180
- listeners = addTempListeners(this, 'connect');
181
- if (config.debug) {
182
- this.debug = config.debug;
183
- this.debugMsg('connect: Debugging turned on');
184
- this.debugMsg(`ssh2-sftp-client Version: ${this.version} `, process.versions);
185
- }
186
- this.promiseLimit = config.promiseLimit ?? 10;
187
- if (this.sftp) {
188
- throw this.fmtError(
189
- 'An existing SFTP connection is already defined',
190
- 'connect',
191
- errorCode.connect,
192
- );
193
- }
194
- const retryOpts = {
195
- retries: config.retries ?? 1,
196
- factor: config.retry_factor ?? 2,
197
- minTimeout: config.retry_minTimeout ?? 25000,
198
- };
199
- await promiseRetry(retryOpts, async (retry, attempt) => {
200
- try {
201
- this.debugMsg(`connect: Connect attempt ${attempt}`);
202
- await this.getConnection(config);
203
- } catch (err) {
204
- switch (err.code) {
205
- case 'ENOTFOUND':
206
- case 'ECONNREFUSED':
207
- case 'ERR_SOCKET_BAD_PORT': {
208
- throw err;
209
- }
210
- case undefined: {
211
- if (
212
- err.message.endsWith('All configured authentication methods failed') ||
213
- err.message.startsWith('Cannot parse privateKey')
214
- ) {
215
- throw err;
216
- }
217
- retry(err);
218
- break;
219
- }
220
- default: {
221
- retry(err);
222
- }
223
- }
224
- }
225
- });
226
- const sftp = await this.getSftpChannel();
227
- this.endCalled = false;
228
- return sftp;
229
- } catch (err) {
230
- this.end();
231
- throw err.custom ? err : this.fmtError(err, 'connect');
232
- } finally {
233
- removeTempListeners(this, listeners, 'connect');
234
- }
235
- }
236
-
237
176
  /**
238
177
  * @async
239
178
  *
@@ -265,6 +204,7 @@ class SftpClient {
265
204
  }).finally(() => {
266
205
  if (addListeners) {
267
206
  removeTempListeners(this, listeners, 'realPath');
207
+ this._resetEventFlags();
268
208
  }
269
209
  });
270
210
  }
@@ -329,6 +269,7 @@ class SftpClient {
329
269
  }).finally(() => {
330
270
  if (addListeners) {
331
271
  removeTempListeners(this, listeners, '_xstat');
272
+ this._resetEventFlags();
332
273
  }
333
274
  });
334
275
  }
@@ -459,6 +400,7 @@ class SftpClient {
459
400
  }).finally(() => {
460
401
  if (addListeners) {
461
402
  removeTempListeners(this, listeners, 'list');
403
+ this._resetEventFlags();
462
404
  }
463
405
  });
464
406
  }
@@ -499,7 +441,7 @@ class SftpClient {
499
441
  pipeOptions: { ...options?.pipeOptions, end: true },
500
442
  };
501
443
  rdr = this.sftp.createReadStream(remotePath, options.readStreamOptions);
502
- rdr.once('error', (err) => {
444
+ rdr.on('error', (err) => {
503
445
  if (dst && typeof dst === 'string' && wtr && !wtr.destroyed) {
504
446
  wtr.destroy();
505
447
  }
@@ -507,13 +449,11 @@ class SftpClient {
507
449
  });
508
450
  if (dst === undefined) {
509
451
  // no dst specified, return buffer of data
510
- this.debugMsg('get resolving with buffer of data');
511
452
  wtr = concat((buff) => {
512
453
  resolve(buff);
513
454
  });
514
455
  } else if (typeof dst === 'string') {
515
456
  // dst local file path
516
- this.debugMsg(`get called with file path destination ${dst}`);
517
457
  const localCheck = haveLocalCreate(dst);
518
458
  if (localCheck.status) {
519
459
  wtr = fs.createWriteStream(dst, options.writeStreamOptions);
@@ -527,10 +467,9 @@ class SftpClient {
527
467
  );
528
468
  }
529
469
  } else {
530
- this.debugMsg('get called with stream destination');
531
470
  wtr = dst;
532
471
  }
533
- wtr.once('error', (err) => {
472
+ wtr.on('error', (err) => {
534
473
  reject(
535
474
  this.fmtError(
536
475
  `${err.message} ${typeof dst === 'string' ? dst : '<stream>'}`,
@@ -554,6 +493,7 @@ class SftpClient {
554
493
  }
555
494
  if (addListeners) {
556
495
  removeTempListeners(this, listeners, 'get');
496
+ this._resetEventFlags();
557
497
  }
558
498
  });
559
499
  }
@@ -589,6 +529,7 @@ class SftpClient {
589
529
  }).finally(() => {
590
530
  if (addListeners) {
591
531
  removeTempListeners(this, listeners, '_fastGet');
532
+ this._resetEventFlags();
592
533
  }
593
534
  });
594
535
  }
@@ -655,6 +596,7 @@ class SftpClient {
655
596
  }).finally(() => {
656
597
  if (addListeners) {
657
598
  removeTempListeners(this, listeners, '_fastPut');
599
+ this._resetEventFlags();
658
600
  }
659
601
  });
660
602
  }
@@ -711,7 +653,7 @@ class SftpClient {
711
653
  };
712
654
  if (haveConnection(this, '_put', reject)) {
713
655
  wtr = this.sftp.createWriteStream(rPath, opts.writeStreamOptions);
714
- wtr.once('error', (err) => {
656
+ wtr.on('error', (err) => {
715
657
  if (typeof lPath === 'string' && rdr && !rdr.destroyed) {
716
658
  rdr.destroy();
717
659
  }
@@ -727,17 +669,14 @@ class SftpClient {
727
669
  resolve(`Uploaded data stream to ${rPath}`);
728
670
  });
729
671
  if (lPath instanceof Buffer) {
730
- this.debugMsg('put source is a buffer');
731
672
  wtr.end(lPath);
732
673
  } else {
733
674
  if (typeof lPath === 'string') {
734
- this.debugMsg('put source is string path');
735
675
  rdr = fs.createReadStream(lPath, opts.readStreamOptions);
736
676
  } else {
737
- this.debugMsg('put source is a stream');
738
677
  rdr = lPath;
739
678
  }
740
- rdr.once('error', (err) => {
679
+ rdr.on('error', (err) => {
741
680
  reject(
742
681
  this.fmtError(
743
682
  `Read stream error: ${err.message} ${
@@ -757,6 +696,7 @@ class SftpClient {
757
696
  }
758
697
  if (addListeners) {
759
698
  removeTempListeners(this, listeners, '_put');
699
+ this._resetEventFlags();
760
700
  }
761
701
  });
762
702
  }
@@ -812,6 +752,7 @@ class SftpClient {
812
752
  }).finally(() => {
813
753
  if (addListeners) {
814
754
  removeTempListeners(this, listeners, '_append');
755
+ this._resetEventFlags();
815
756
  }
816
757
  });
817
758
  }
@@ -883,6 +824,7 @@ class SftpClient {
883
824
  }).finally(() => {
884
825
  if (addListeners) {
885
826
  removeTempListeners(this, listeners, '_doMkdir');
827
+ this._resetEventFlags();
886
828
  }
887
829
  });
888
830
  }
@@ -956,6 +898,7 @@ class SftpClient {
956
898
  });
957
899
  }).finally(() => {
958
900
  removeTempListeners(this, listeners, '_rmdir');
901
+ this._resetEventFlags();
959
902
  });
960
903
  };
961
904
 
@@ -1045,6 +988,7 @@ class SftpClient {
1045
988
  }).finally(() => {
1046
989
  if (addListeners) {
1047
990
  removeTempListeners(this, listeners, 'delete');
991
+ this._resetEventFlags();
1048
992
  }
1049
993
  });
1050
994
  }
@@ -1083,6 +1027,7 @@ class SftpClient {
1083
1027
  }).finally(() => {
1084
1028
  if (addListeners) {
1085
1029
  removeTempListeners(this, listeners, 'rename');
1030
+ this._resetEventFlags();
1086
1031
  }
1087
1032
  });
1088
1033
  }
@@ -1121,6 +1066,7 @@ class SftpClient {
1121
1066
  }
1122
1067
  }).finally(() => {
1123
1068
  removeTempListeners(this, listeners, 'posixRename');
1069
+ this._resetEventFlags();
1124
1070
  });
1125
1071
  }
1126
1072
 
@@ -1152,6 +1098,7 @@ class SftpClient {
1152
1098
  }).finally(() => {
1153
1099
  if (addListeners) {
1154
1100
  removeTempListeners(this, listeners, 'chmod');
1101
+ this._resetEventFlags();
1155
1102
  }
1156
1103
  });
1157
1104
  }
@@ -1237,6 +1184,7 @@ class SftpClient {
1237
1184
  throw this.fmtError(`${e.message} ${srcDir} to ${dstDir}`, 'uploadFiles', e.code);
1238
1185
  } finally {
1239
1186
  removeTempListeners(this, listeners, uploadFiles);
1187
+ this._resetEventFlags();
1240
1188
  }
1241
1189
  };
1242
1190
 
@@ -1362,6 +1310,7 @@ class SftpClient {
1362
1310
  );
1363
1311
  } finally {
1364
1312
  removeTempListeners(this, listeners, 'downloadFiles');
1313
+ this._resetEventFlags();
1365
1314
  }
1366
1315
  };
1367
1316
 
@@ -1408,7 +1357,8 @@ class SftpClient {
1408
1357
  } catch (err) {
1409
1358
  throw err.custom ? err : this.fmtError(err.message, 'createReadStream', err.code);
1410
1359
  } finally {
1411
- removeTempListeners(this, listeners, 'createReadStreame');
1360
+ removeTempListeners(this, listeners, 'createReadStream');
1361
+ this._resetEventFlags();
1412
1362
  }
1413
1363
  }
1414
1364
 
@@ -1434,6 +1384,7 @@ class SftpClient {
1434
1384
  throw err.custom ? err : this.fmtError(err.message, 'createWriteStream', err.code);
1435
1385
  } finally {
1436
1386
  removeTempListeners(this, listeners, 'createWriteStream');
1387
+ this._resetEventFlags();
1437
1388
  }
1438
1389
  }
1439
1390
 
@@ -1497,6 +1448,7 @@ class SftpClient {
1497
1448
  throw err.custom ? err : this.fmtError(err, 'rcopy');
1498
1449
  } finally {
1499
1450
  removeTempListeners(this, listeners, 'rcopy');
1451
+ this._resetEventFlags();
1500
1452
  }
1501
1453
  }
1502
1454
  /**
@@ -1528,6 +1480,7 @@ class SftpClient {
1528
1480
  }).finally(() => {
1529
1481
  removeTempListeners(this, listeners, 'end');
1530
1482
  this.removeListener('close', endCloseHandler);
1483
+ this._resetEventFlags();
1531
1484
  });
1532
1485
  }
1533
1486
  }
package/src/utils.js CHANGED
@@ -88,7 +88,7 @@ function endListener(client, name, reject) {
88
88
  client.endHandled = true;
89
89
  client.debugMsg(`${name} endListener - handling unexpected end event`);
90
90
  const newError = new Error(`${name}: Unexpected end event`);
91
- newError.code = errorCode.ERR_GENERIC_CLIENT;
91
+ newError.code = errorCode.generic;
92
92
  if (reject) {
93
93
  reject(newError);
94
94
  } else {
@@ -109,7 +109,7 @@ function closeListener(client, name, reject) {
109
109
  client.closeHandled = true;
110
110
  client.debugMsg(`${name} closeListener - handling unexpected close event`);
111
111
  const newError = new Error(`${name}: Unexpected close event`);
112
- newError.code = errorCode.ERR_GENERIC_CLIENT;
112
+ newError.code = errorCode.generic;
113
113
  if (reject) {
114
114
  reject(newError);
115
115
  } else {