ssh2-sftp-client 6.0.1 → 7.0.0

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
@@ -4,20 +4,20 @@
4
4
 
5
5
  'use strict';
6
6
 
7
- const {Client} = require('ssh2');
7
+ const { Client } = require('ssh2');
8
8
  const fs = require('fs');
9
9
  const concat = require('concat-stream');
10
10
  const promiseRetry = require('promise-retry');
11
- const {join, parse} = require('path');
11
+ const { join, parse } = require('path');
12
12
  const {
13
13
  fmtError,
14
14
  addTempListeners,
15
15
  removeTempListeners,
16
16
  haveConnection,
17
17
  normalizeRemotePath,
18
- localExists
18
+ localExists,
19
19
  } = require('./utils');
20
- const {errorCode} = require('./constants');
20
+ const { errorCode } = require('./constants');
21
21
 
22
22
  class SftpClient {
23
23
  constructor(clientName) {
@@ -32,21 +32,30 @@ class SftpClient {
32
32
 
33
33
  this.client.on('close', () => {
34
34
  if (!this.endCalled) {
35
- this.debugMsg('Unexpected close event raised by server');
35
+ this.debugMsg(
36
+ `${this.clientName}: Unexpected close event raised by server`
37
+ );
36
38
  this.sftp = undefined;
37
39
  }
38
40
  });
41
+
39
42
  this.client.on('end', () => {
40
43
  if (!this.endCalled) {
41
- this.debugMsg('Unexpected end event raised by server');
44
+ this.debugMsg(
45
+ `${this.clientName}: Unexpected end event raised by server`
46
+ );
42
47
  this.sftp = undefined;
43
48
  }
44
49
  });
50
+
45
51
  this.client.on('error', (err) => {
46
52
  if (!this.errorHandled) {
53
+ this.debugMsg(
54
+ `${this.clientName}: Global Error Handler: ${err.message}`
55
+ );
47
56
  throw fmtError(
48
57
  `Unexpected error: ${err.message}`,
49
- 'global-error-handler',
58
+ `${this.clientName}: global error handler`,
50
59
  err.code
51
60
  );
52
61
  } else {
@@ -97,28 +106,37 @@ class SftpClient {
97
106
  * @return {Promise} which will resolve to an sftp client object
98
107
  *
99
108
  */
100
- sftpConnect(config) {
101
- let connectReady;
102
-
109
+ getConnection(config) {
110
+ let doReady;
103
111
  return new Promise((resolve, reject) => {
104
112
  addTempListeners(this, 'sftpConnect', reject);
105
- connectReady = () => {
106
- this.client.sftp((err, sftp) => {
107
- if (err) {
108
- this.debugMsg(`SFTP channel error: ${err.message} ${err.code}`);
109
- reject(fmtError(err, 'sftpConnect', err.code));
110
- } else {
111
- this.debugMsg('SFTP channel established');
112
- resolve(sftp);
113
- }
114
- });
113
+ doReady = () => {
114
+ resolve(true);
115
115
  };
116
- // addTempListeners(this, 'sftpConnect', reject);
117
- this.client.on('ready', connectReady).connect(config);
118
- }).finally((rsp) => {
119
- this.removeListener('ready', connectReady);
120
- removeTempListeners(this.client);
121
- return rsp;
116
+ this.client.on('ready', doReady);
117
+ this.client.connect(config);
118
+ })
119
+ .catch((err) => {
120
+ return Promise.reject(err);
121
+ })
122
+ .finally((resp) => {
123
+ this.removeListener('ready', doReady);
124
+ removeTempListeners(this);
125
+ return resp;
126
+ });
127
+ }
128
+
129
+ getSftpChannel() {
130
+ return new Promise((resolve, reject) => {
131
+ this.client.sftp((err, sftp) => {
132
+ if (err) {
133
+ this.debugMsg(`SFTP Channel Error: ${err.message}`);
134
+ reject(fmtError(err, 'getSftpChannel', err.code));
135
+ } else {
136
+ this.sftp = sftp;
137
+ resolve(sftp);
138
+ }
139
+ });
122
140
  });
123
141
  }
124
142
 
@@ -140,17 +158,17 @@ class SftpClient {
140
158
  return promiseRetry(
141
159
  (retry, attempt) => {
142
160
  this.debugMsg(`Connect attempt ${attempt}`);
143
- return this.sftpConnect(config).catch((err) => {
161
+ return this.getConnection(config).catch((err) => {
144
162
  retry(err);
145
163
  });
146
164
  },
147
165
  {
148
166
  retries: config.retries || 1,
149
167
  factor: config.retry_factor || 2,
150
- minTimeout: config.retry_minTimeout || 1000
168
+ minTimeout: config.retry_minTimeout || 1000,
151
169
  }
152
- ).then((sftp) => {
153
- this.sftp = sftp;
170
+ ).then(() => {
171
+ return this.getSftpChannel();
154
172
  });
155
173
  }
156
174
 
@@ -187,7 +205,7 @@ class SftpClient {
187
205
  });
188
206
  }
189
207
  }).finally((rsp) => {
190
- removeTempListeners(this.client);
208
+ removeTempListeners(this);
191
209
  return rsp;
192
210
  });
193
211
  }
@@ -238,12 +256,12 @@ class SftpClient {
238
256
  isCharacterDevice: stats.isCharacterDevice(),
239
257
  isSymbolicLink: stats.isSymbolicLink(),
240
258
  isFIFO: stats.isFIFO(),
241
- isSocket: stats.isSocket()
259
+ isSocket: stats.isSocket(),
242
260
  });
243
261
  }
244
262
  });
245
263
  }).finally((rsp) => {
246
- removeTempListeners(this.client);
264
+ removeTempListeners(this);
247
265
  return rsp;
248
266
  });
249
267
  };
@@ -350,10 +368,10 @@ class SftpClient {
350
368
  rights: {
351
369
  user: item.longname.substr(1, 3).replace(reg, ''),
352
370
  group: item.longname.substr(4, 3).replace(reg, ''),
353
- other: item.longname.substr(7, 3).replace(reg, '')
371
+ other: item.longname.substr(7, 3).replace(reg, ''),
354
372
  },
355
373
  owner: item.attrs.uid,
356
- group: item.attrs.gid
374
+ group: item.attrs.gid,
357
375
  };
358
376
  });
359
377
  }
@@ -370,7 +388,7 @@ class SftpClient {
370
388
  });
371
389
  }
372
390
  }).finally((rsp) => {
373
- removeTempListeners(this.client);
391
+ removeTempListeners(this);
374
392
  return rsp;
375
393
  });
376
394
  }
@@ -385,33 +403,44 @@ class SftpClient {
385
403
  *
386
404
  * @param {String} path, remote file path
387
405
  * @param {string|stream|undefined} dst, data destination
388
- * @param {Object} userOptions, options passed to get
406
+ * @param {Object} options, options object with supported properties of readStreamOptions,
407
+ * writeStreamOptions and pipeOptions.
389
408
  *
390
409
  * @return {Promise}
391
410
  */
392
- get(remotePath, dst, options = {}) {
411
+ get(
412
+ remotePath,
413
+ dst,
414
+ options = { readStreamOptions: {}, writeStreamOptions: {}, pipeOptions: {} }
415
+ ) {
416
+ let rdr, wtr;
417
+
393
418
  return new Promise((resolve, reject) => {
394
419
  if (haveConnection(this, 'get', reject)) {
395
420
  this.debugMsg(`get -> ${remotePath} `, options);
396
421
  addTempListeners(this, 'get', reject);
397
- let rdr = this.sftp.createReadStream(remotePath, options);
422
+ rdr = this.sftp.createReadStream(
423
+ remotePath,
424
+ options.readStreamOptions ? options.readStreamOptions : {}
425
+ );
398
426
  rdr.once('error', (err) => {
399
427
  reject(fmtError(`${err.message} ${remotePath}`, 'get', err.code));
400
428
  });
401
429
  if (dst === undefined) {
402
430
  // no dst specified, return buffer of data
403
431
  this.debugMsg('get returning buffer of data');
404
- let concatStream = concat((buff) => {
405
- rdr.removeAllListeners('error');
432
+ wtr = concat((buff) => {
433
+ //rdr.removeAllListeners('error');
406
434
  resolve(buff);
407
435
  });
408
- rdr.pipe(concatStream);
409
436
  } else {
410
- let wtr;
411
437
  if (typeof dst === 'string') {
412
438
  // dst local file path
413
439
  this.debugMsg('get returning local file');
414
- wtr = fs.createWriteStream(dst);
440
+ wtr = fs.createWriteStream(
441
+ dst,
442
+ options.writeStreamOptions ? options.writeStreamOptions : {}
443
+ );
415
444
  } else {
416
445
  this.debugMsg('get returning data into supplied stream');
417
446
  wtr = dst;
@@ -424,25 +453,34 @@ class SftpClient {
424
453
  err.code
425
454
  )
426
455
  );
427
- if (options.autoClose === false) {
428
- rdr.destroy();
429
- }
430
456
  });
431
- wtr.once('finish', () => {
432
- if (options.autoClose === false) {
433
- rdr.destroy();
434
- }
457
+ rdr.once('end', () => {
435
458
  if (typeof dst === 'string') {
436
459
  resolve(dst);
437
460
  } else {
438
461
  resolve(wtr);
439
462
  }
440
463
  });
441
- rdr.pipe(wtr);
442
464
  }
465
+ rdr.pipe(wtr, options.pipeOptions ? options.pipeOptions : {});
443
466
  }
444
467
  }).finally((rsp) => {
445
- removeTempListeners(this.client);
468
+ removeTempListeners(this);
469
+ if (
470
+ rdr &&
471
+ options.readStreamOptions &&
472
+ options.readStreamOptions.autoClose === false
473
+ ) {
474
+ rdr.destroy();
475
+ }
476
+ if (
477
+ wtr &&
478
+ options.writeStreamOptions &&
479
+ options.writeStreamOptions.autoClose === false &&
480
+ typeof dst === 'string'
481
+ ) {
482
+ wtr.destroy();
483
+ }
446
484
  return rsp;
447
485
  });
448
486
  }
@@ -490,7 +528,7 @@ class SftpClient {
490
528
  });
491
529
  }
492
530
  }).finally((rsp) => {
493
- removeTempListeners(this.client);
531
+ removeTempListeners(this);
494
532
  return rsp;
495
533
  });
496
534
  });
@@ -511,60 +549,42 @@ class SftpClient {
511
549
  */
512
550
  fastPut(localPath, remotePath, options) {
513
551
  this.debugMsg(`fastPut -> local ${localPath} remote ${remotePath}`);
514
- return localExists(localPath)
515
- .then((localStatus) => {
516
- this.debugMsg(`fastPut <- localStatus ${localStatus}`);
517
- if (localStatus !== '-') {
518
- this.debugMsg('fastPut reject bad source path');
519
- return Promise.reject(
520
- fmtError(`Bad path ${localPath}`, 'fastPut', errorCode.badPath)
552
+ return localExists(localPath).then((localStatus) => {
553
+ this.debugMsg(`fastPut <- localStatus ${localStatus}`);
554
+ if (localStatus !== '-') {
555
+ this.debugMsg('fastPut reject bad source path');
556
+ return Promise.reject(
557
+ fmtError(`Bad path ${localPath}`, 'fastPut', errorCode.badPath)
558
+ );
559
+ }
560
+ return new Promise((resolve, reject) => {
561
+ if (haveConnection(this, 'fastPut', reject)) {
562
+ this.debugMsg(
563
+ `fastPut -> local: ${localPath} remote: ${remotePath} opts: ${JSON.stringify(
564
+ options
565
+ )}`
521
566
  );
522
- }
523
- return new Promise((resolve, reject) => {
524
- fs.access(localPath, fs.constants.F_OK | fs.constants.R_OK, (err) => {
567
+ addTempListeners(this, 'fastPut', reject);
568
+ this.sftp.fastPut(localPath, remotePath, options, (err) => {
525
569
  if (err) {
526
- this.debugMsg('fastPut reject no access source');
570
+ this.debugMsg(`fastPut error ${err.message} ${err.code}`);
527
571
  reject(
528
- fmtError(`${err.message} ${localPath}`, 'fastPut', err.code)
572
+ fmtError(
573
+ `${err.message} Local: ${localPath} Remote: ${remotePath}`,
574
+ 'fastPut',
575
+ err.code
576
+ )
529
577
  );
530
- } else {
531
- this.debugMsg('fastPut source access ok');
532
- resolve(true);
533
578
  }
579
+ this.debugMsg('fastPut file transferred');
580
+ resolve(`${localPath} was successfully uploaded to ${remotePath}!`);
534
581
  });
535
- });
536
- })
537
- .then(() => {
538
- return new Promise((resolve, reject) => {
539
- if (haveConnection(this, 'fastPut', reject)) {
540
- this.debugMsg(
541
- `fastPut -> local: ${localPath} remote: ${remotePath} opts: ${JSON.stringify(
542
- options
543
- )}`
544
- );
545
- addTempListeners(this, 'fastPut', reject);
546
- this.sftp.fastPut(localPath, remotePath, options, (err) => {
547
- if (err) {
548
- this.debugMsg(`fastPut error ${err.message} ${err.code}`);
549
- reject(
550
- fmtError(
551
- `${err.message} Local: ${localPath} Remote: ${remotePath}`,
552
- 'fastPut',
553
- err.code
554
- )
555
- );
556
- }
557
- this.debugMsg('fastPut file transferred');
558
- resolve(
559
- `${localPath} was successfully uploaded to ${remotePath}!`
560
- );
561
- });
562
- }
563
- }).finally((rsp) => {
564
- removeTempListeners(this.client);
565
- return rsp;
566
- });
582
+ }
583
+ }).finally((rsp) => {
584
+ removeTempListeners(this);
585
+ return rsp;
567
586
  });
587
+ });
568
588
  }
569
589
 
570
590
  /**
@@ -574,100 +594,95 @@ class SftpClient {
574
594
  *
575
595
  * @param {String|Buffer|stream} src - source data to use
576
596
  * @param {String} remotePath - path to remote file
577
- * @param {Object} options - options used for write stream configuration
578
- * value supported by node streams.
597
+ * @param {Object} options - options used for read, write stream and pipe configuration
598
+ * value supported by node. Allowed properties are readStreamOptions,
599
+ * writeStreamOptions and pipeOptions.
579
600
  * @return {Promise}
580
601
  */
581
- put(localSrc, remotePath, options = {}) {
582
- this.debugMsg(
583
- `put ${
584
- typeof localSrc === 'string' ? localSrc : '<buffer | stream>'
585
- } -> ${remotePath}`,
586
- options
587
- );
588
- return localExists(typeof localSrc === 'string' ? localSrc : 'dummy')
589
- .then((localStatus) => {
590
- if (typeof localSrc === 'string' && localStatus !== '-') {
591
- this.debugMsg(`put: file does not exist ${localSrc} - rejecting`);
592
- return Promise.reject(
593
- fmtError(`Bad path ${localSrc}`, 'put', errorCode.badPath)
602
+ doPut(
603
+ localSrc,
604
+ remotePath,
605
+ options = { readStreamOptions: {}, writeStreamOptions: {}, pipeOptions: {} }
606
+ ) {
607
+ let wtr, rdr;
608
+
609
+ return new Promise((resolve, reject) => {
610
+ addTempListeners(this, 'put', reject);
611
+ wtr = this.sftp.createWriteStream(
612
+ remotePath,
613
+ options.writeStreamOptions ? options.writeStreamOptions : {}
614
+ );
615
+ wtr.once('error', (err) => {
616
+ reject(fmtError(`${err.message} ${remotePath}`, 'put', err.code));
617
+ });
618
+ wtr.once('finish', () => {
619
+ resolve(`Uploaded data stream to ${remotePath}`);
620
+ });
621
+ if (localSrc instanceof Buffer) {
622
+ this.debugMsg('put source is a buffer');
623
+ wtr.end(localSrc);
624
+ } else {
625
+ if (typeof localSrc === 'string') {
626
+ this.debugMsg(`put source is a file path: ${localSrc}`);
627
+ rdr = fs.createReadStream(
628
+ localSrc,
629
+ options.readStreamOptions ? options.readStreamOptons : {}
594
630
  );
631
+ } else {
632
+ this.debugMsg('put source is a stream');
633
+ rdr = localSrc;
595
634
  }
596
- return new Promise((resolve, reject) => {
597
- if (typeof localSrc === 'string') {
598
- fs.access(
599
- localSrc,
600
- fs.constants.F_OK | fs.constants.R_OK,
601
- (err) => {
602
- if (err) {
603
- this.debugMsg(`put: Cannot read ${localSrc} - rejecting`);
604
- reject(
605
- fmtError(
606
- `Permission denied ${localSrc}`,
607
- 'put',
608
- errorCode.permission
609
- )
610
- );
611
- } else {
612
- this.debugMsg('put: localSrc file OK');
613
- resolve(true);
614
- }
615
- }
616
- );
617
- } else {
618
- this.debugMsg('put: localSrc buffer or string OK');
619
- resolve(true);
620
- }
621
- });
622
- })
623
- .then(() => {
624
- return new Promise((resolve, reject) => {
625
- if (haveConnection(this, 'put', reject)) {
626
- addTempListeners(this, 'put', reject);
627
- let stream = this.sftp.createWriteStream(remotePath, options);
628
- stream.once('error', (err) => {
629
- reject(fmtError(`${err.message} ${remotePath}`, 'put', err.code));
630
- });
631
- stream.once('finish', () => {
632
- if (options.autoClose === false) {
633
- stream.destroy();
634
- }
635
- resolve(`Uploaded data stream to ${remotePath}`);
636
- });
637
- if (localSrc instanceof Buffer) {
638
- this.debugMsg('put source is a buffer');
639
- stream.end(localSrc);
640
- } else {
641
- let rdr;
642
- if (typeof localSrc === 'string') {
643
- this.debugMsg(`put source is a file path: ${localSrc}`);
644
- rdr = fs.createReadStream(localSrc);
645
- } else {
646
- this.debugMsg('put source is a stream');
647
- rdr = localSrc;
648
- }
649
- rdr.once('error', (err) => {
650
- reject(
651
- fmtError(
652
- `${err.message} ${
653
- typeof localSrc === 'string' ? localSrc : ''
654
- }`,
655
- 'put',
656
- err.code
657
- )
658
- );
659
- if (options.autoClose === false) {
660
- stream.destroy();
661
- }
662
- });
663
- rdr.pipe(stream);
664
- }
665
- }
666
- }).finally((rsp) => {
667
- removeTempListeners(this.client);
668
- return rsp;
635
+ rdr.once('error', (err) => {
636
+ reject(
637
+ fmtError(
638
+ `${err.message} ${typeof localSrc === 'string' ? localSrc : ''}`,
639
+ 'put',
640
+ err.code
641
+ )
642
+ );
669
643
  });
670
- });
644
+ rdr.pipe(wtr, options.pipeOptions ? options.pipeOptions : {});
645
+ }
646
+ }).finally((resp) => {
647
+ removeTempListeners(this);
648
+ if (
649
+ rdr &&
650
+ options.readStreamOptions &&
651
+ options.readStreamOptions.autoClose === false &&
652
+ typeof localSrc === 'string'
653
+ ) {
654
+ rdr.destroy();
655
+ }
656
+ if (
657
+ wtr &&
658
+ options.writeStreamOptions &&
659
+ options.writeStreamOptions.autoClose === false
660
+ ) {
661
+ wtr.destroy();
662
+ }
663
+ return resp;
664
+ });
665
+ }
666
+
667
+ async put(
668
+ localSrc,
669
+ remoePath,
670
+ options = { readStreamOptions: {}, writeStreamOptions: {}, pipeOptions: {} }
671
+ ) {
672
+ try {
673
+ haveConnection(this, 'put');
674
+ if (typeof localSrc === 'string') {
675
+ let type = await localExists(localSrc);
676
+ if (type !== '-' && type !== 'l') {
677
+ let err = new Error(`Bad path: ${localSrc}`);
678
+ err.code = errorCode.badPath;
679
+ throw err;
680
+ }
681
+ }
682
+ return await this.doPut(localSrc, remoePath, options);
683
+ } catch (err) {
684
+ throw fmtError(err, 'put');
685
+ }
671
686
  }
672
687
 
673
688
  /**
@@ -704,7 +719,7 @@ class SftpClient {
704
719
  }
705
720
  }
706
721
  }).finally((rsp) => {
707
- removeTempListeners(this.client);
722
+ removeTempListeners(this);
708
723
  return rsp;
709
724
  });
710
725
  }
@@ -731,7 +746,7 @@ class SftpClient {
731
746
  resolve(`${p} directory created`);
732
747
  });
733
748
  }).finally((rsp) => {
734
- removeTempListeners(this.client);
749
+ removeTempListeners(this);
735
750
  return rsp;
736
751
  });
737
752
  };
@@ -782,7 +797,7 @@ class SftpClient {
782
797
  resolve('Successfully removed directory');
783
798
  });
784
799
  }).finally((rsp) => {
785
- removeTempListeners(this.client);
800
+ removeTempListeners(this);
786
801
  return rsp;
787
802
  });
788
803
  };
@@ -848,7 +863,7 @@ class SftpClient {
848
863
  });
849
864
  }
850
865
  }).finally((rsp) => {
851
- removeTempListeners(this.client);
866
+ removeTempListeners(this);
852
867
  return rsp;
853
868
  });
854
869
  }
@@ -884,7 +899,7 @@ class SftpClient {
884
899
  });
885
900
  }
886
901
  }).finally((rsp) => {
887
- removeTempListeners(this.client);
902
+ removeTempListeners(this);
888
903
  return rsp;
889
904
  });
890
905
  }
@@ -921,7 +936,7 @@ class SftpClient {
921
936
  });
922
937
  }
923
938
  }).finally((rsp) => {
924
- removeTempListeners(this.client);
939
+ removeTempListeners(this);
925
940
  return rsp;
926
941
  });
927
942
  }
@@ -947,7 +962,7 @@ class SftpClient {
947
962
  resolve('Successfully change file mode');
948
963
  });
949
964
  }).finally((rsp) => {
950
- removeTempListeners(this.client);
965
+ removeTempListeners(this);
951
966
  return rsp;
952
967
  });
953
968
  }
@@ -978,7 +993,7 @@ class SftpClient {
978
993
  }
979
994
  let dirEntries = fs.readdirSync(srcDir, {
980
995
  encoding: 'utf8',
981
- withFileTypes: true
996
+ withFileTypes: true,
982
997
  });
983
998
  dirEntries = dirEntries.filter((item) => filter.test(item.name));
984
999
  for (let e of dirEntries) {
@@ -990,7 +1005,7 @@ class SftpClient {
990
1005
  let src = join(srcDir, e.name);
991
1006
  let dst = dstDir + this.remotePathSep + e.name;
992
1007
  await this.fastPut(src, dst);
993
- this.client.emit('upload', {source: src, destination: dst});
1008
+ this.client.emit('upload', { source: src, destination: dst });
994
1009
  } else {
995
1010
  this.debugMsg(
996
1011
  `uploadDir: File ignored: ${e.name} not a regular file`
@@ -1025,12 +1040,12 @@ class SftpClient {
1025
1040
  this.debugMsg(`downloadDir -> ${srcDir} ${dstDir}`);
1026
1041
  haveConnection(this, 'downloadDir');
1027
1042
  let fileList = await this.list(srcDir, filter);
1028
- let dstStatus = await localExists(dstDir);
1043
+ let dstStatus = await localExists(dstDir, true);
1029
1044
  if (dstStatus && dstStatus !== 'd') {
1030
1045
  throw fmtError(`Bad path ${dstDir}`, 'downloadDir', errorCode.badPath);
1031
1046
  }
1032
1047
  if (!dstStatus) {
1033
- fs.mkdirSync(dstDir, {recursive: true});
1048
+ fs.mkdirSync(dstDir, { recursive: true });
1034
1049
  }
1035
1050
  for (let f of fileList) {
1036
1051
  if (f.type === 'd') {
@@ -1041,7 +1056,7 @@ class SftpClient {
1041
1056
  let src = srcDir + this.remotePathSep + f.name;
1042
1057
  let dst = join(dstDir, f.name);
1043
1058
  await this.fastGet(src, dst);
1044
- this.client.emit('download', {source: src, destination: dst});
1059
+ this.client.emit('download', { source: src, destination: dst });
1045
1060
  } else {
1046
1061
  this.debugMsg(
1047
1062
  `downloadDir: File ignored: ${f.name} not regular file`
@@ -1079,7 +1094,7 @@ class SftpClient {
1079
1094
  this.client.end();
1080
1095
  }
1081
1096
  }).finally(() => {
1082
- removeTempListeners(this.client);
1097
+ removeTempListeners(this);
1083
1098
  this.removeListener('close', endCloseHandler);
1084
1099
  return true;
1085
1100
  });