tsdns
Version:
TeamSpeak™ 3 DNS (TSDNS) implementation for Node.js
210 lines (185 loc) • 5.54 kB
JavaScript
import { EventEmitter } from 'events';
import { createServer } from 'net';
/**
* Default TSDNS port (TCP).
* @type {Number}
*/
export const DEFAULT_TSDNS_PORT = 41144;
/**
* Default TeamSpeak 3 voice port (UDP).
* @type {Number}
*/
export const DEFAULT_TS3_PORT = 9987;
/**
* Default request timeout: 1 second.
* @type {Number}
*/
export const DEFAULT_TIMEOUT = 1000;
/**
* @class {TSDNS}
* @extends {events.EventEmitter}
*/
export default class TSDNS extends EventEmitter {
/**
* Creates a new TSDNS instance.
* @param {Number} port Optional. Defaults to the default TSDNS port.
* @param {String} host Host to listen on.
* @param {Number} timeout Optional. Defaults to 1 second.
*/
constructor({
port = DEFAULT_TSDNS_PORT,
host,
timeout = DEFAULT_TIMEOUT
} = {}) {
super();
this.timeout = timeout;
// create server
let server = this._server = createServer().listen(port, host);
// wire up events
server
.on('listening', () => this.emit('listening'))
.on('error', (error) => this._onServerError(error))
.on('close', (hadError) => this._onServerClose(hadError))
.on('connection', (socket) => this._onServerConnection(socket));
}
/**
* Returns the address and port of the underlying server.
* @see {net.Server#address}
* @return {Object} { address: String, port: Number, family: String }
*/
address() {
return this._server.address();
}
/**
* Closes the TSDNS server.
* @param {Function} cb Optional listener for the 'close' event.
*/
close(cb = () => null) {
this.on('close', cb);
this._server.close();
}
/**
* Handle a server error event.
* @param {Error} error
* @private
*/
_onServerError(error) {
this.emit('error', error);
}
/**
* Handle a server close event.
* Also performs some clean up work.
* @param {Boolean} hadError
* @private
*/
_onServerClose(hadError) {
this.emit('close', hadError);
// clean up
this._server.removeAllListeners;
delete this._server;
this.removeAllListeners();
}
/**
* Handle a server connection event.
* @param {net.Socket} socket
* @private
*/
_onServerConnection(socket) {
// configure socket
socket
.setEncoding('utf8')
.setNoDelay(true)
.setTimeout(this.timeout)
.unref();
// wire up events
socket
.on('error', (error) => this._onSocketError(socket, error))
.on('close', (hadError) => this._onSocketClose(socket, hadError))
.on('timeout', () => this._onSocketTimeout(socket))
.once('data', (data) => this._onSocketData(socket, data));
}
/**
* Handle a socket error event.
* @param {net.Socket} socket
* @param {Error} error
* @private
*/
_onSocketError(socket, error) {
socket.destroy();
this.emit('socket-error', socket, error);
}
/**
* Handle a socket close event.
* @param {net.Socket} socket
* @param {Boolean} hadError
* @private
*/
_onSocketClose(socket, hadError) {
this.emit('socket-close', socket, hadError);
}
/**
* Handle a socket timeout event.
* @param {net.Socket} socket
* @private
*/
_onSocketTimeout(socket) {
socket.destroy();
this.emit('socket-timeout', socket);
}
/**
* Handle a socket data event.
* @param {net.Socket} socket
* @param {String} data
* @private
*/
_onSocketData(socket, data) {
let { remoteAddress, remotePort } = socket;
let [host, port] = data.toLowerCase().split(':');
host = host || null;
port = port ? Number.parseInt(port, 10) : null;
/**
* Respond to a request.
* @param {String|Boolean} address The address to connect to or `false` to
* send a 404 error.
* Default to `false`.
* @param {Number} port The port to connect to. Defaults to the
* requested port, or the default TeamSpeak
* port (9987), if not port was requested.
*/
let didRespond = false;
function respond(address = false, port = port || DEFAULT_TS3_PORT) {
if (didRespond) {
return;
}
didRespond = true;
if (address) {
socket.end(`${address}:${port}`);
} else {
socket.end('404');
}
}
/**
* Event: request
* @param {String|null} host The requested host.
* @param {Number|null} port The requested port.
* @param {String} raw The raw, unparsed request.
* @param {Function} respond Call this to respond to a request.
* @param {String} remoteAddress Address of the requesting client.
* @param {Number} remotePort Port the client is requesting from.
* @param {net.Socket} socket The underlying socket instance.
*/
this.emit('request', {
host, port, raw: data,
respond,
remoteAddress, remotePort, socket
});
}
}
/**
* Conveinience function for creating a TSDNS instane.
* @param {String} host Optional
* @return {TSDNS}
*/
export function createTSDNS(host) {
return new TSDNS({ host });
}