If you ever find yourself needing SSH connectivity within your JavaScript app, you’ll find the module ssh2 to be the best choice you can find. Of course working with so many different modules, I was surprised to see this module being used in a stream-based abstract. So if you are using React and Redux, how do you make such a thing work out so that you always get a success, or a failure? Looking at the documentation can show a few examples, but until you really dig into the API, it really is not that obvious. So let’s take a quick look at how I accomplished this with my own Redux setup.

Let’s start by looking a single SSH client-based object. Anything else within ssh2 will work the same, even though it may not be that obvious now, just remember that both the Client and Server work off the exact same object, so there is an overlap.

 let connectObject = {
  host: ip,
  user: user,
  privateKey: fs.readFileSync(`${process.cwd()}/keys/${key}`)
    }

let conn = new Client();
    
conn.on('ready', () => {
  conn.shell((error, stream) => {
    if(error) { 
      throw error;
    };
    stream.on('close', () => { 
      conn.end();
    }).on('data', data => {
      data
    }).stderr.on('data', error => {
      throw error;
    });
  stream.end(`${command}nexitn`);
  }).connect(connectObject);
});

If we look at this example similar to what is found in the documentation we see we have a data event, standard error event, and end command run at the end of the stream. If you are not very familar with Node.js stream, or streaming data in general, a way to receive a success/failure event may not be that apparent at all. A lot of this has to do with the events that are not shown within the documentation itself, and without really digging into it, even though we may be very use to modules ‘just working like they should’, it’s not exactly obvious.

Enough of that though, what I did was create a custom promise around the stream, and used some extra events to make sure I always received a result to let me know the command succeeded, or failed.

let dataOut = '<p>';
let errOut;
let connectObject = {
  host: ip,
  user: user,
  privateKey: fs.readFileSync(`${process.cwd()}/keys/${key}`)
}

let conn = new Client();
    
return new Promise((resolve, reject) => {
  conn.on('ready', () => {
    conn.shell((error, stream) => {
      if(error) { 
        reject(error);  // return errors when connecting to shell and reject
      };
      stream.on('close', () => { 
        conn.end();
      });
      stream.on('data', data => dataOut += data);
      stream.stderr.on('data', error => {
        reject(error);  // return stderr data and fail the action
      });
      stream.end(`${command}nexitn`);
    });
  }).on('error', error => {
    reject(error);  // sends reject to redux action when connect fails
  }).on('end', () => {
    resolve(dataOut);  // send a collective string of data that was returned in the shell
  }).connect(connectObject);
});

As you can see we reject an errors, including error events on the connection object. When data comes in, it’s passed to a block-level variable, and returned to the Redux action calling the function.

Enjoy!