UNPKG

tsdns

Version:

TeamSpeak™ 3 DNS (TSDNS) implementation for Node.js

210 lines (185 loc) 5.54 kB
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 }); }