ssh-client-wrapper 1.8.2 → 2.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.
@@ -0,0 +1,22 @@
1
+ name: report coverage data to code climate
2
+ on:
3
+ push:
4
+ tags:
5
+ - 'v*'
6
+ jobs:
7
+ coverage:
8
+ needs:
9
+ - test
10
+ name: coverage report
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v3
14
+ - uses: actions/download-artifact@v3
15
+ with:
16
+ name: code-coverage-report
17
+ - uses: paambaati/codeclimate-action@v5.0.0
18
+ env:
19
+ CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
20
+ with:
21
+ coverageLocations: "coverage-report.lcov:lcov"
22
+ debug: true
@@ -14,9 +14,9 @@ jobs:
14
14
  - uses: actions/checkout@v3
15
15
  - uses: actions/setup-node@v3
16
16
  with:
17
- node-version: 18
17
+ node-version: 20
18
18
  registry-url: https://registry.npmjs.org/
19
19
  - run: npm ci
20
20
  - run: npm publish
21
21
  env:
22
- NODE_AUTH_TOKEN: ${{secrets.npm_token}}
22
+ NODE_AUTH_TOKEN: ${{ secrets.npm_token }}
@@ -34,3 +34,9 @@ jobs:
34
34
  node-version: ${{ matrix.node-version }}
35
35
  - run: npm ci
36
36
  - run: npm run test
37
+ - run: npm run coverage
38
+ - uses: actions/upload-artifact@v3
39
+ with:
40
+ name: code-coverage-report
41
+ path: coverage-report.lcov
42
+ retention-days: 1
package/lib/fork.js CHANGED
@@ -1,35 +1,33 @@
1
1
  "use strict";
2
- const debug = require("debug")("sshClientWrapper:fork");
3
- const debugVerbose = require("debug")("sshClientWrapper:fork_verbose");
4
- const debugMasterVerbose = require("debug")("sshClientWrapper:fork_masterPty_verbose");
5
- const debugSendPty = require("debug")("sshClientWrapper:fork_sendpty");
2
+ const debug = require("debug")("sshClientWrapper:debug:fork");
3
+ const debugVerbose = require("debug")("sshClientWrapper:verbose:fork");
4
+ const debugMasterVerbose = require("debug")("sshClientWrapper:insecure:fork_masterPty");
5
+ const debugSendPty = require("debug")("sshClientWrapper:insecure:fork_sendpty");
6
6
  const { spawn } = require("node-pty");
7
7
 
8
- const { rePwPrompt, rePhPrompt, reNewHostPrompt, buff2String } = require("./util.js");
8
+ const { rePwPrompt, rePhPrompt, reNewHostPrompt, buff2String, sendPty, watchDogTimer } = require("./util.js");
9
9
 
10
10
  const nodePtyOpt = {
11
11
  shell: process.platform === "win32" ? process.env.ComSpec : process.env.SHELL,
12
12
  windowsHide: true
13
13
  };
14
14
 
15
- function sshLoginCallback(output, ptyProcess, pw, ph) {
15
+ function sshLoginCallback(output, ptyProcess, pw, ph, logger = debugSendPty) {
16
16
  if (reNewHostPrompt.test(output)) {
17
- debugSendPty("yes");
18
- ptyProcess.write("yes\n");
17
+ sendPty(ptyProcess, "yes\n", logger, 17);
19
18
  return;
20
19
  }
21
20
  if (rePwPrompt.test(output)) {
22
21
  if (typeof pw === "string") {
23
22
  debug("use given password");
24
- debugSendPty(pw);
25
- ptyProcess.write(`${pw}\n`);
23
+ sendPty(ptyProcess, `${pw}\n`, logger, 23);
26
24
  return;
27
25
  }
28
26
  if (typeof pw === "function") {
29
27
  debug("call password callback function");
30
28
  pw()
31
29
  .then((v)=>{
32
- ptyProcess.write(`${v}\n`);
30
+ sendPty(ptyProcess, `${v}\n`, logger, 30);
33
31
  });
34
32
  return;
35
33
  }
@@ -37,36 +35,49 @@ function sshLoginCallback(output, ptyProcess, pw, ph) {
37
35
  if (rePhPrompt.test(output)) {
38
36
  if (typeof ph === "string") {
39
37
  debug("use given passphrase");
40
- debugSendPty(ph);
41
- ptyProcess.write(`${ph}\n`);
38
+ sendPty(ptyProcess, `${ph}\n`, logger, 38);
42
39
  return;
43
40
  }
44
41
  if (typeof ph === "function") {
45
42
  debug("call passphrase callback function");
46
43
  ph()
47
44
  .then((v)=>{
48
- ptyProcess.write(`${v}\n`);
45
+ sendPty(ptyProcess, `${v}\n`, logger, 45);
49
46
  });
50
47
  }
51
48
  }
52
49
  }
53
50
 
54
- function getMasterPty(hostInfo) {
51
+ /**
52
+ * create pty for master ssh session and store to hostInfo.masterPty
53
+ */
54
+ function getMasterPty(hostInfo, logger) {
55
55
  debug("fork pty for master connection");
56
+
57
+ if (hostInfo.masterPty) {
58
+ debug(`kill existing master pty for ${hostInfo.host}`);
59
+ hostInfo.masterPty.kill();
60
+ }
56
61
  const ptyProcess = spawn(nodePtyOpt.shell, [], nodePtyOpt);
57
62
  ptyProcess.onData(buff2String.bind(null, debugMasterVerbose));
58
63
  ptyProcess.onData((data)=>{
59
64
  const output = data.toString();
60
- sshLoginCallback(output, ptyProcess, hostInfo.password, hostInfo.passphrase);
65
+ sshLoginCallback(output, ptyProcess, hostInfo.password, hostInfo.passphrase, logger);
61
66
  });
62
- return ptyProcess;
67
+ hostInfo.masterPty = ptyProcess;
63
68
  }
64
69
 
65
- async function fork(hostInfo, cmd, args, outputCallback) {
66
- let ptyProcess = null;
67
- const p = new Promise((resolve, reject)=>{
70
+ async function fork(hostInfo, cmd, args, timeout, outputCallback) {
71
+ return new Promise((resolve, reject)=>{
68
72
  debug(`cmd=${cmd}: args=${args}`);
69
- ptyProcess = spawn(cmd, args, nodePtyOpt);
73
+ const ptyProcess = spawn(cmd, args, nodePtyOpt);
74
+ let ac = null;
75
+ if (timeout > 0) {
76
+ ac = watchDogTimer(timeout, cmd, ()=>{
77
+ ptyProcess.kill();
78
+ reject(new Error(`watchdog timer expired ${timeout}`));
79
+ });
80
+ }
70
81
  ptyProcess.onData(buff2String.bind(null, debugVerbose));
71
82
  ptyProcess.onData((data)=>{
72
83
  const output = data.toString();
@@ -86,6 +97,10 @@ async function fork(hostInfo, cmd, args, outputCallback) {
86
97
  ptyProcess.onExit(({ exitCode, signal })=>{
87
98
  debug("onExit", exitCode, signal);
88
99
 
100
+ if (ac !== null) {
101
+ ac.abort();
102
+ }
103
+
89
104
  if (signal > 0) {
90
105
  const err = new Error("signal caught");
91
106
  err.signal = signal;
@@ -99,8 +114,6 @@ async function fork(hostInfo, cmd, args, outputCallback) {
99
114
  return reject(err);
100
115
  });
101
116
  });
102
- p.pty = ptyProcess;
103
- return p;
104
117
  }
105
118
  module.exports = {
106
119
  fork,
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
- const debugLib = require("debug");
3
- const debug = debugLib("sshClientWrapper:interface");
2
+ const debug = require("debug")("sshClientWrapper:interface");
3
+ const debugVerbose = require("debug")("sshClientWrapper:verbose:interface");
4
4
  const { sshExec, canConnect, disconnect, ls } = require("./sshExec.js");
5
5
  const { send, recv } = require("./rsyncExec.js");
6
6
  const { isArrayOfString, sanityCheck } = require("./util.js");
@@ -30,12 +30,19 @@ const { isArrayOfString, sanityCheck } = require("./util.js");
30
30
  * @param {string[]} hostInfo.sshOpt - additional options for ssh
31
31
  */
32
32
 
33
+ const logAndReject = (message)=>{
34
+ debug(message);
35
+ return Promise.reject(new Error(message));
36
+ };
37
+
33
38
  /**
34
39
  * Facade class.
35
40
  * @constructor
36
41
  */
37
42
  class SshClientWrapper {
38
43
  constructor(hostInfo) {
44
+ debug("constructor called for", hostInfo.host);
45
+ debugVerbose("hostInfo=", hostInfo);
39
46
  sanityCheck(hostInfo);
40
47
  this.hostInfo = { ...hostInfo };
41
48
  this.hostInfo.masterPty = null;
@@ -44,18 +51,19 @@ class SshClientWrapper {
44
51
  /**
45
52
  * Execute command on remote host.
46
53
  * @param {string} cmd - Cmdline which will be executed.
54
+ * @param {Integer} timeout - timeout in secconds
47
55
  * @param { Function } outputCallback - call back routine for output
48
56
  * @returns {Integer} - Return code of cmd.
49
57
  *
50
58
  * If stdout and stderr is array, last 5 line of stdout and stderr is stored in them.
51
59
  */
52
- async exec(cmd, outputCallback) {
60
+ async exec(cmd, timeout = 0, outputCallback = null) {
53
61
  debug("exec called", cmd);
54
62
 
55
- if (typeof cmd !== "string") {
56
- return Promise.reject(new Error("cmd must be string"));
63
+ if (typeof cmd !== "string" || cmd === "") {
64
+ return logAndReject("cmd must be string");
57
65
  }
58
- return sshExec(this.hostInfo, cmd, outputCallback);
66
+ return sshExec(this.hostInfo, cmd, timeout, outputCallback);
59
67
  }
60
68
 
61
69
 
@@ -63,10 +71,12 @@ class SshClientWrapper {
63
71
  * execute ls command and return output
64
72
  * @param {string} target - file or directory path to watch
65
73
  * @param {string[]} lsOpt - optional arguments for ls
74
+ * @param {Integer} timeout - timeout in secconds
66
75
  * @returns {string[]} - output from ls
67
76
  */
68
- async ls(target, lsOpt = []) {
69
- return ls(this.hostInfo, target, lsOpt);
77
+ async ls(target, lsOpt = [], timeout = 0) {
78
+ debug("ls called", target, lsOpt);
79
+ return ls(this.hostInfo, target, lsOpt, timeout);
70
80
  }
71
81
 
72
82
  /**
@@ -81,7 +91,7 @@ class SshClientWrapper {
81
91
  debug("watch called", cmd, regexp, retryDelay, maxRetry);
82
92
 
83
93
  if (typeof cmd !== "string") {
84
- return Promise.reject(new Error("cmd must be string"));
94
+ return logAndReject("cmd must be string");
85
95
  }
86
96
  if (!(regexp instanceof RegExp)) {
87
97
  const err = new Error("illegal regexp specified");
@@ -89,7 +99,7 @@ class SshClientWrapper {
89
99
  err.cmd = cmd;
90
100
  err.retryDelay = retryDelay;
91
101
  err.maxRetry = maxRetry;
92
- return Promise.reject(err);
102
+ return Promise.reject(new Error("err"));
93
103
  }
94
104
  //not implemented yet !!!
95
105
  return this.exec(cmd, ()=>{
@@ -101,27 +111,30 @@ class SshClientWrapper {
101
111
  * @param {string[]} src - File or directory name which to be recieve.
102
112
  * @param {string} dst - Destination path.
103
113
  * @param {string[]} opt - option for rsync
114
+ * @param {Integer} timeout - timeout in secconds
104
115
  * @returns {Promise} - Resolved with undefined when file transfer is done.
105
116
  */
106
- async send(src, dst, opt) {
117
+ async send(src, dst, opt = [], timeout = 0) {
107
118
  debug("send called", src, dst, opt);
108
119
 
109
120
  if (!isArrayOfString(src)) {
110
- const err = "src must be array of string";
111
- debug(err);
112
- return new Error(err);
121
+ return logAndReject("src must be array of string");
122
+ }
123
+ if (!src.some((e)=>{
124
+ return e !== "";
125
+ })) {
126
+ return logAndReject("src must contain non-empty string");
113
127
  }
114
128
  if (typeof dst !== "string") {
115
- const err = "dst must be string";
116
- debug(err);
117
- return new Error(err);
129
+ return logAndReject("dst must be string");
130
+ }
131
+ if (dst === "") {
132
+ return logAndReject("dst must be non-empty string");
118
133
  }
119
134
  if (typeof opt !== "undefined" && !isArrayOfString(opt)) {
120
- const err = "opt must be array of string";
121
- debug(err);
122
- return new Error(err);
135
+ return logAndReject("opt must be array of string");
123
136
  }
124
- return send(this.hostInfo, src, dst, opt);
137
+ return send(this.hostInfo, src, dst, opt, timeout);
125
138
  }
126
139
 
127
140
  /**
@@ -129,27 +142,30 @@ class SshClientWrapper {
129
142
  * @param {string[]} src - File or directory name which to be recieve.
130
143
  * @param {string} dst - Destination path.
131
144
  * @param {string[]} opt - option for rsync
145
+ * @param {Integer} timeout - timeout in secconds
132
146
  * @returns {Promise} - Resolved with undefined when file transfer is done.
133
147
  */
134
- async recv(src, dst, opt) {
148
+ async recv(src, dst, opt = [], timeout = 0) {
135
149
  debug("recv called", src, dst, opt);
136
150
 
137
151
  if (!isArrayOfString(src)) {
138
- const err = "src must be array of string";
139
- debug(err);
140
- return new Error(err);
152
+ return logAndReject("src must be array of string");
153
+ }
154
+ if (!src.some((e)=>{
155
+ return e !== "";
156
+ })) {
157
+ return logAndReject("src must contain non-empty string");
141
158
  }
142
159
  if (typeof dst !== "string") {
143
- const err = "dst must be string";
144
- debug(err);
145
- return new Error(err);
160
+ return logAndReject("dst must be string");
161
+ }
162
+ if (dst === "") {
163
+ return logAndReject("dst must be non-empty string");
146
164
  }
147
165
  if (typeof opt !== "undefined" && !isArrayOfString(opt)) {
148
- const err = "opt must be array of string";
149
- debug(err);
150
- return new Error(err);
166
+ return logAndReject("opt must be array of string");
151
167
  }
152
- return recv(this.hostInfo, src, dst, opt);
168
+ return recv(this.hostInfo, src, dst, opt, timeout);
153
169
  }
154
170
 
155
171
  /**
@@ -158,10 +174,11 @@ class SshClientWrapper {
158
174
  * @returns {Promise} - Resolved with true on success, otherwise rejected with Error.
159
175
  */
160
176
  async canConnect(timeout = 60) {
161
- debug("canConnect called timeout=", timeout);
177
+ debug("canConnect called with timeout=", timeout);
178
+ debugVerbose("hostInfo=", this.hostInfo);
162
179
 
163
180
  if (!Number.isInteger(timeout) || timeout <= 0) {
164
- return new Error("timeout must be positive integer");
181
+ return logAndReject("timeout must be positive integer");
165
182
  }
166
183
  return canConnect(this.hostInfo, timeout);
167
184
  }
@@ -170,7 +187,7 @@ class SshClientWrapper {
170
187
  * Disconnect master session
171
188
  */
172
189
  async disconnect() {
173
- debug("disconnect called");
190
+ debug(`disconnect from ${this.hostInfo.host} called`);
174
191
  disconnect(this.hostInfo);
175
192
  }
176
193
  }
package/lib/rsyncExec.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  const path = require("path");
3
- const debug = require("debug")("sshClientWrapper:rsyncExec");
4
- const debugVerbose = require("debug")("sshClientWrapper:rsyncExec_verbose");
3
+ const debug = require("debug")("sshClientWrapper:debug:rsyncExec");
4
+ const debugVerbose = require("debug")("sshClientWrapper:verbose:rsyncExec");
5
5
  const { glob } = require("glob");
6
6
 
7
7
  const { connect, retryWrapper, sshExec } = require("./sshExec.js");
@@ -16,10 +16,11 @@ const { sshCmd, rsyncCmd, getSshOption } = require("./util.js");
16
16
  * @param {[string]} argSrc - File or directory name which to be send.
17
17
  * @param {string} dst - Destination path.
18
18
  * @param {[string]} rsyncOpt - optional arguments for rsync
19
+ * @param {Integer} timeout - timeout in secconds
19
20
  *
20
21
  * this function execute rsync -avv --copy-unsafe-links src rsync://[user@]host[:port]/dst
21
22
  */
22
- async function send(hostInfo, argSrc, dst, rsyncOpt = []) {
23
+ async function send(hostInfo, argSrc, dst, rsyncOpt, timeout) {
23
24
  await connect(hostInfo);
24
25
  const promises = await Promise.allSettled(argSrc.map((e)=>{
25
26
  return glob(e);
@@ -65,7 +66,7 @@ async function send(hostInfo, argSrc, dst, rsyncOpt = []) {
65
66
  }
66
67
  debug(`send ${src} to ${hostInfo.host}:${dst}`);
67
68
  debugVerbose(`send: ${rsyncCmd} ${args.join(" ")}`);
68
- return retryWrapper(fork.bind(null, hostInfo, rsyncCmd, args), hostInfo);
69
+ return retryWrapper(fork.bind(null, hostInfo, rsyncCmd, args, timeout), hostInfo);
69
70
  }
70
71
 
71
72
  /**
@@ -74,10 +75,11 @@ async function send(hostInfo, argSrc, dst, rsyncOpt = []) {
74
75
  * @param {[string]} argSrc - File or directory name which to be send.
75
76
  * @param {string} dst - Destination path.
76
77
  * @param {[string]} rsyncOpt - optional arguments for rsync
78
+ * @param {Integer} timeout - timeout in secconds
77
79
  *
78
80
  * this function execute rsync -av --copy-unsafe-links rsync://[user@]host[:port]/src dst
79
81
  */
80
- async function recv(hostInfo, argSrc, dst, rsyncOpt = []) {
82
+ async function recv(hostInfo, argSrc, dst, rsyncOpt, timeout) {
81
83
  await connect(hostInfo);
82
84
  const src = `${hostInfo.host}:${argSrc.join(" ")}`;
83
85
  const sshArgs = getSshOption(hostInfo, true);
@@ -87,7 +89,7 @@ async function recv(hostInfo, argSrc, dst, rsyncOpt = []) {
87
89
 
88
90
  debug(`recv ${src} to ${dst}`);
89
91
  debugVerbose(`recv: ${rsyncCmd} ${args.join(" ")}`);
90
- return retryWrapper(fork.bind(null, hostInfo, rsyncCmd, args), hostInfo);
92
+ return retryWrapper(fork.bind(null, hostInfo, rsyncCmd, args, timeout), hostInfo);
91
93
  }
92
94
  module.exports = {
93
95
  send,
package/lib/sshExec.js CHANGED
@@ -1,19 +1,20 @@
1
1
  "use strict";
2
- const { setTimeout: setTimeoutPromise } = require("timers/promises");
3
2
  const crypto = require("crypto");
4
3
  const promiseRetry = require("promise-retry");
5
- const debug = require("debug")("sshClientWrapper:sshExec");
6
- const debugVerbose = require("debug")("sshClientWrapper:sshExec_verbose");
7
- const { sshCmd, getSshOption, sanityCheck } = require("./util.js");
4
+ const debug = require("debug")("sshClientWrapper:debug:sshExec");
5
+ const debugVerbose = require("debug")("sshClientWrapper:verbose:sshExec");
6
+ const debugSendMasterPty = require("debug")("sshClientWrapper:insecure:sshExec_masterPty");
7
+ const { sendPty, sshCmd, getSshOption, sanityCheck, watchDogTimer } = require("./util.js");
8
8
  const { fork, getMasterPty } = require("./fork.js");
9
9
 
10
10
  /**
11
11
  * Execute command on remote host.
12
12
  * @param {Object} hostInfo - hostinfo object
13
13
  * @param {string} cmd - Cmdline which will be executed on remote host.
14
+ * @param {number} timeout - timeout (in sec.)
14
15
  * @param {Function} outputCallback - Callback routine for stdout and stderr.
15
16
  */
16
- async function sshExec(hostInfo, cmd, outputCallback) {
17
+ async function sshExec(hostInfo, cmd, timeout, outputCallback) {
17
18
  await connect(hostInfo);
18
19
  debug("exec", cmd, "on remote server");
19
20
  const args = getSshOption(hostInfo, false);
@@ -21,7 +22,7 @@ async function sshExec(hostInfo, cmd, outputCallback) {
21
22
  debugVerbose(`exec: ${sshCmd} ${args.join(" ")}`);
22
23
 
23
24
  try {
24
- const rt = await retryWrapper(fork.bind(null, hostInfo, sshCmd, args, outputCallback), hostInfo);
25
+ const rt = await retryWrapper(fork.bind(null, hostInfo, sshCmd, args, timeout, outputCallback), hostInfo);
25
26
  return rt;
26
27
  } catch (e) {
27
28
  if (typeof e.rt === "undefined") {
@@ -64,9 +65,7 @@ async function canConnect(hostInfo, timeout = 60) {
64
65
  * @returns {boolean} - true if master session exists
65
66
  */
66
67
  async function existsMaster(hostInfo) {
67
- const ac = new AbortController();
68
- const signal = ac.signal;
69
-
68
+ debugVerbose("existsMaster called");
70
69
  const args = getSshOption(hostInfo);
71
70
  args.push("-Ocheck");
72
71
 
@@ -75,31 +74,12 @@ async function existsMaster(hostInfo) {
75
74
  output += e;
76
75
  };
77
76
 
78
- let rt = true; //if timeout occurred during ssh -Ocheck, it will turn to false
79
- let p;
77
+ const rt = true; //if timeout occurred during ssh -Ocheck, it will turn to false
80
78
  const timeout = hostInfo.ConnectTimeout || 60;
81
- setTimeoutPromise(timeout * 1000, "neverUsedValue", { signal })
82
- .then(()=>{
83
- debug(`can not finish master session check within ${timeout} sec`);
84
-
85
- if (p.pty) {
86
- p.pty.kill();
87
- }
88
- rt = false;
89
- })
90
- .catch((err)=>{
91
- if (err.name === "AbortError") {
92
- debug("timeout was aborted because master session check has done");
93
- } else {
94
- throw err;
95
- }
96
- });
79
+ debug(`check master session: ${sshCmd} ${args.join(" ")}`);
97
80
 
98
81
  try {
99
- debug(`check master session: ${sshCmd} ${args.join(" ")}`);
100
- p = fork(hostInfo, sshCmd, args, cb);
101
- await p;
102
- ac.abort();
82
+ await fork(hostInfo, sshCmd, args, timeout, cb);
103
83
  } catch (err) {
104
84
  if (err.rt !== 255) {
105
85
  debug("check master session failed", err);
@@ -119,36 +99,22 @@ async function connect(hostInfo, timeout = 60) {
119
99
  debugVerbose("master connection exists");
120
100
  return;
121
101
  }
122
- debug(`create master ssh session to ${hostInfo.host}`);
123
102
 
124
- if (!hostInfo.masterPty) {
125
- debug(`create master pty for ${hostInfo.host}`);
126
- hostInfo.masterPty = getMasterPty(hostInfo);
127
- } else {
128
- debug(`master pty for ${hostInfo.host} exists`);
129
- }
103
+ debug(`create master pty for ${hostInfo.host}`);
104
+ getMasterPty(hostInfo, debugSendMasterPty);
105
+
106
+ debug(`create master ssh session to ${hostInfo.host}`);
130
107
 
131
- const ac = new AbortController();
132
- const signal = ac.signal;
133
- setTimeoutPromise(timeout * 1000, "neverUsedValue", { signal })
134
- .then(()=>{
135
- debug(`can not connect within ${timeout} sec`);
136
- hostInfo.masterPty.write("\n");
137
- hostInfo.masterPty.write("exit\n");
108
+ const ac = watchDogTimer(timeout, "make connection", ()=>{
109
+ sendPty(hostInfo.masterPty, "\n", debugSendMasterPty, 137);
110
+ sendPty(hostInfo.masterPty, "exit\n", debugSendMasterPty, 138);
138
111
 
139
- const err = new Error(`can not connect within ${timeout} sec`);
140
- err.host = hostInfo.host;
141
- err.user = hostInfo.user;
142
- err.port = hostInfo.port;
143
- throw err;
144
- })
145
- .catch((err)=>{
146
- if (err.name === "AbortError") {
147
- debug("timeout was aborted because connection check has done");
148
- } else {
149
- throw err;
150
- }
151
- });
112
+ const err = new Error(`can not connect within ${timeout} sec`);
113
+ err.host = hostInfo.host;
114
+ err.user = hostInfo.user;
115
+ err.port = hostInfo.port;
116
+ throw err;
117
+ });
152
118
 
153
119
  const words = crypto.randomUUID();
154
120
  const re = new RegExp(`^${words}`);
@@ -164,7 +130,6 @@ async function connect(hostInfo, timeout = 60) {
164
130
 
165
131
  if (re.test(output)) {
166
132
  debug("test done. call exit on remotehost");
167
- hostInfo.masterPty.write("exit\n");
168
133
  done = true;
169
134
  resolve();
170
135
  } else if (/Permission denied \(/.test(output)) {
@@ -186,11 +151,14 @@ async function connect(hostInfo, timeout = 60) {
186
151
  const args = getSshOption(hostInfo);
187
152
  args.push(`echo ${words}`);
188
153
  const cmd = `${sshCmd} ${args.join(" ")}\n`;
189
- debugVerbose(cmd);
190
- hostInfo.masterPty.write(cmd);
191
- await p;
192
- ac.abort();
193
- debug(`connected to ${hostInfo.host}`);
154
+ sendPty(hostInfo.masterPty, cmd, debugSendMasterPty, 190);
155
+
156
+ try {
157
+ await p;
158
+ debug(`connected to ${hostInfo.host}`);
159
+ } finally {
160
+ ac.abort();
161
+ }
194
162
  }
195
163
 
196
164
  /**
@@ -208,7 +176,7 @@ async function disconnect(hostInfo) {
208
176
  };
209
177
  try {
210
178
  debug(`disconnect: ${sshCmd} ${args.join(" ")}`);
211
- await fork(hostInfo, sshCmd, args, cb);
179
+ await fork(hostInfo, sshCmd, args, 0, cb);
212
180
  } catch (err) {
213
181
  if (err.rt === 255) {
214
182
  return;
@@ -225,7 +193,7 @@ async function disconnect(hostInfo) {
225
193
 
226
194
  if (hostInfo.masterPty) {
227
195
  debug("exit masterPty");
228
- hostInfo.masterPty.write("exit \n");
196
+ sendPty(hostInfo.masterPty, "exit \n", debugSendMasterPty, 228);
229
197
  hostInfo.masterPty = null;
230
198
  }
231
199
  }
@@ -235,12 +203,13 @@ async function disconnect(hostInfo) {
235
203
  * @param {Object} hostInfo - hostinfo object
236
204
  * @param {string} target - file or directory path to watch
237
205
  * @param {string[]} lsOpt - optional arguments for ls
206
+ * @param {number} timeout - timeout (in sec.)
238
207
  * @returns {string[]} - output from ls
239
208
  */
240
- async function ls(hostInfo, target, lsOpt = []) {
209
+ async function ls(hostInfo, target, lsOpt, timeout) {
241
210
  const outputArray = [];
242
211
  try {
243
- await sshExec(hostInfo, `ls ${lsOpt.join(" ")} ${target}`, (data)=>{
212
+ await sshExec(hostInfo, `ls ${lsOpt.join(" ")} ${target}`, timeout, (data)=>{
244
213
  outputArray.push(data);
245
214
  });
246
215
  } catch (e) {