Multivit4min / TS3-NodeJS-Library

TeamSpeak 3 Server Query Library supports SSH and RAW Query
https://multivit4min.github.io/TS3-NodeJS-Library/
MIT License
150 stars 19 forks source link

Unhandled 'error' event #53

Closed SvenC56 closed 5 years ago

SvenC56 commented 5 years ago

Hi,

when I try to connect to a not reachable server I get the following error which can't be catched.

events.js:167
      throw er; // Unhandled 'error' event
      ^

Error: Timed out while waiting for handshake
    at Timeout._onTimeout (C:\xxx\Dev\teamspeak-node-rest-api\teamspeak-node-rest-api\node_modules\ssh2\lib\client.js:694:19)    at ontimeout (timers.js:424:11)
    at tryOnTimeout (timers.js:288:5)
    at listOnTimeout (timers.js:251:5)
    at Timer.processTimers (timers.js:211:10)
Emitted 'error' event at:
    at TS3Query.TeamSpeak3._ts3.on.e (C:\xxx\Dev\teamspeak-node-rest-api\teamspeak-node-rest-api\node_modules\ts3-nodejs-library\TeamSpeak3.js:84:38)
    at TS3Query.emit (events.js:182:13)
    at TS3Query.handleError (C:\xxx\Dev\teamspeak-node-rest-api\teamspeak-node-rest-api\node_modules\ts3-nodejs-library\transport\TS3Query.js:168:10)
    at SSH.emit (events.js:182:13)
    at SSH._handleError (C:\xxx\Dev\teamspeak-node-rest-api\teamspeak-node-rest-api\node_modules\ts3-nodejs-library\transport\protocols\ssh.js:75:10)
    at Client.emit (events.js:182:13)
    at Timeout._onTimeout (C:\xxx\Dev\teamspeak-node-rest-api\teamspeak-node-rest-api\node_modules\ssh2\lib\client.js:696:14)    at ontimeout (timers.js:424:11)
    at tryOnTimeout (timers.js:288:5)
    at listOnTimeout (timers.js:251:5)

My Code: index.js

...
let connection = new Teamspeak(username, password, host, server_port, query_port, protocol);
    try {
        let response = await connection.getClientList();
        return res.send(response);
    } catch (e) {
        return res.status(400).send({
            message: e
        });
    }
...

TeamSpeak Contructor

import TeamSpeak3 from 'ts3-nodejs-library';
import consola from 'consola';

function Teamspeak(username, password, host, server_port, query_port, protocol) {
  this.username = username;
  this.password = password;
  this.host = host;
  this.server_port = server_port;
  this.query_port = query_port;
  this.protocol = protocol;
}

Teamspeak.prototype.connect = async function () {
  try {
    let connection = await new TeamSpeak3({
      username: this.username,
      password: this.password,
      host: this.host,
      serverport: this.server_port,
      queryport: this.query_port,
      protocol: this.protocol
    });
    connection.setMaxListeners(100);
    await connection.useByPort(this.server_port, 'Node JS Bot');
  } catch (e) {
    throw e;
  }

  return connection;
};

Teamspeak.prototype.getClientList = async function () {
  try {
    const connection = await this.connect();
    //Retrieves a List of non Query Clients
    const clients = await connection.clientList({
      client_type: 0
    });
    return clients;
  } catch (e) {
    throw e;
  }
};

module.exports = Teamspeak;
Multivit4min commented 5 years ago

The error is catchable via listening to the error event

connection.on("error", err => /* you can handle the error here */)
SvenC56 commented 5 years ago

Hi thanks for the very fast reply. I get the same error with the following code:

Teamspeak.prototype.connect = async function () {
    let connection = await new TeamSpeak3({
      username: this.username,
      password: this.password,
      host: this.host,
      serverport: this.server_port,
      queryport: this.query_port,
      protocol: this.protocol
    });
    connection.setMaxListeners(100);
    await connection.useByPort(this.server_port, 'Node JS Bot');

  connection.on("error", err => { throw(err); });

  return connection;
};
Multivit4min commented 5 years ago

Alright i guess i found the problem, the ssh2 lib seem to throw some error instead of emitting an error

can you test again by replacing the constructor of node_modules/TS3-NodeJS-Library/transport/protocols/ssh.js

to:

  constructor(config) {
    super()
    this._data = ""
    this._ssh = new Client()
    process.nextTick(() => {
      try {
        this._ssh
          .on("ready", this._handleReady.bind(this))
          .on("banner", this._handleData.bind(this))
          .on("error", this._handleError.bind(this))
          .on("close", this._handleClose.bind(this))
          .connect({
            host: config.host,
            port: config.queryport,
            username: config.username,
            password: config.password,
            readyTimeout: config.readyTimeout
          })
      } catch (e) {
        this._handleError(e)
      }
    })
  }

and see if this fixes the error for you? I am only able to trigger a similar error which is catchable like that

SvenC56 commented 5 years ago

Sadly this did not solve my problem. Sadly none of my console.logs got triggered.

import TeamSpeak3 from 'ts3-nodejs-library';
import consola from 'consola';

function Teamspeak(username, password, host, server_port, query_port, protocol) {
  this.username = username;
  this.password = password;
  this.host = host;
  this.server_port = server_port;
  this.query_port = query_port;
  this.protocol = protocol;
}

Teamspeak.prototype.connect = async function () {
  let connection = null;
  try {
    connection = await new TeamSpeak3({
      username: this.username,
      password: this.password,
      host: this.host,
      serverport: this.server_port,
      queryport: this.query_port,
      protocol: this.protocol
    });
    await connection.setMaxListeners(100);
    await connection.useByPort(this.server_port, 'Node JS Bot');
  } catch (e) {
    console.log('test 1', e);
    throw e;
  }

  connection.on("error", e => consola.error('test 2', e));

  return connection;
};

Teamspeak.prototype.getClientList = async function () {
  try {
    const connection = await this.connect();
    //Retrieves a List of non Query Clients
    const clients = await connection.clientList({
      client_type: 0
    });
    return clients;
  } catch (e) {
    console.log('test 3', e);
    throw e;
  }
};

module.exports = Teamspeak;
Multivit4min commented 5 years ago

you need to define the connection.on("error", ...) listener before calling the next await

Teamspeak.prototype.connect = async function () {
  let connection = null;
  try {
    connection = new TeamSpeak3({
      username: this.username,
      password: this.password,
      host: this.host,
      serverport: this.server_port,
      queryport: this.query_port,
      protocol: this.protocol
    });
    //here
    connection.on("error", e => consola.error('test 2', e));
    connection.setMaxListeners(100);
    await connection.useByPort(this.server_port, 'Node JS Bot');
  } catch (e) {
    console.log('test 1', e);
    throw e;
  }

  return connection;
};

you are also only able to call await on functions which return an actual promise

SvenC56 commented 5 years ago

Sadly this is not working for me. I think that the event handler on error does not forward the error to my catch block...

Teamspeak.prototype.connect = async function () {
  let connection = null;
  try {
    connection = new TeamSpeak3({
      username: this.username,
      password: this.password,
      host: this.host,
      serverport: this.server_port,
      queryport: this.query_port,
      protocol: this.protocol
    });
    connection.on('error', (e) => {
      throw (e);
    });
    connection.setMaxListeners(100);
    await connection.useByPort(this.server_port, 'Node JS Bot');
  } catch (e) {
    throw (e);
  }
  return connection;
};
[nodemon] restarting due to changes...
[nodemon] starting `babel-node src`
√ Server is running on port 3000.                                                                                                                                                                                                                                                                           23:26:31
E:\Dev\teamspeak-node-rest-api\src\services\teamspeak.js:31
      throw e;
      ^

Error: Timed out while waiting for handshake
    at Timeout._onTimeout (E:\Dev\teamspeak-node-rest-api\node_modules\ssh2\lib\client.js:694:19)
    at listOnTimeout (timers.js:324:15)
    at processTimers (timers.js:268:5)
[nodemon] app crashed - waiting for file changes before starting...
Multivit4min commented 5 years ago

I am unable to reproduce this with following code:

const TeamSpeak = require("./TeamSpeak3")

const ts3 = new TeamSpeak({
  username: "serveradmin",
  password: "1234",
  host: "7.1.1.1",
  queryport: 9987,
  protocol: "ssh",
  readyTimeout: 5000
})

ts3.on("ready", () => console.log("ready"))

ts3.on("error", err => {
  console.log("Caught error", err.message)
})

please make sure that you replace the whole constructor code, or download and replace it from the current repository via transport/protocols/ssh.js

SvenC56 commented 5 years ago

I guess I understand the problem. I need to wait for the ready event to return the connection. I do not get the code working as expected.

So the ready event listener should be async so I can return the connection Object when the event fires. At the moment this is not the case (I guess).

Try Catch is not needed due to the fact that we listen on errors anyways, right?

Teamspeak.prototype.connect = async function () {
  let connection = new TeamSpeak3({
    username: this.username,
    password: this.password,
    host: this.host,
    serverport: this.server_port,
    queryport: this.query_port,
    protocol: this.protocol
  });
  connection.on('error', (e) => {
    consola.error(e);
    return e;
  });
  connection.on('ready', async () => {
    return connection;
  });
  connection.setMaxListeners(100);
  // await connection.useByPort(this.server_port, 'Node JS Bot');
};

Teamspeak.prototype.getClientList = async function () {
  try {
    const connection = await this.connect();
    console.log('hello');
    //Retrieves a List of non Query Clients
    const clients = await connection.clientList({
      client_type: 0
    });
    return clients;
  } catch (e) {
    throw (e);
  }
};

Watch at the order of the "hello" console log.

[nodemon] restarting due to changes...
[nodemon] starting `babel-node src`
√ Server is running on port 3000.                                                                                                                     10:52:05
hello

 ERROR  Timed out while waiting for handshake                                                                                                         10:52:26

  at Timeout._onTimeout (node_modules\ssh2\lib\client.js:694:19)
  at ontimeout (timers.js:424:11)
  at tryOnTimeout (timers.js:288:5)
  at listOnTimeout (timers.js:251:5)
  at Timer.processTimers (timers.js:211:10)
Multivit4min commented 5 years ago

yes only errors which come from executing query commands (for example: ts3.clientList(), ts3.poke(), ...) will throw an error async all other errors go to the error handler

what you might want to try when creating your connection:

Teamspeak.prototype.connect = function () {
  return new Promise((fulfill, reject) => {
    let connection = new TeamSpeak3({
      username: this.username,
      password: this.password,
      host: this.host,
      serverport: this.server_port,
      queryport: this.query_port,
      protocol: this.protocol
    })
    const errorHandler = e => reject(e)
    connection.once('error', errorHandler)
    connection.on('ready', async () => {
      connection.removeListener("error", errorHandler)
      fulfill(connection)
    });
    connection.setMaxListeners(100)
    // await connection.useByPort(this.server_port, 'Node JS Bot');
  })
};
SvenC56 commented 5 years ago

Hi this solution is working for me. Why I didn't came up with this idea earlier...? :)