From 64966606339eaf7fc4923cf2a69df9f1a18695fb Mon Sep 17 00:00:00 2001 From: mmorrison Date: Sat, 12 Jan 2019 21:32:24 -0600 Subject: [PATCH] Add ping field, start improving README for 2.0 --- README.md | 32 +++++++++++++++++++++++++++++++- protocols/core.js | 43 +++++++++++++++++++++++++++++++++++++++---- protocols/nadeo.js | 3 +++ 3 files changed, 73 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fff824d..6d125c4 100644 --- a/README.md +++ b/README.md @@ -443,9 +443,39 @@ gamedig --type minecraft --host mc.example.com --port 11234 The output of the command will be in JSON format. -Major Version Changes +Changelog --- +### 2.0 +##### Breaking changes +* Node 8 is now required +* Removed the `port_query` option. You can now pass either the server's game port **or** query port in the `port` option, and +GameDig will automatically discover the proper port to query. Passing the query port is more likely be successful in +unusual cases, as otherwise it must be automatically derived from the game port. +* Removed `callback` parameter from Gamedig.query. Only promises are now supported. If you would like to continue +using callbacks, you can use node's `util.callbackify` function to convert the method to callback format. +* Removed `query` field from response object, as it was poorly documented and unstable. +##### Minor Changes +* Rewrote core to use promises extensively for better error-handling. Async chains have been dramatically simplified +by using async/await across the codebase, eliminating callback chains and the 'async' dependency. +* Replaced `--output pretty` cli parameter with `--pretty`. +* You can now query from CLI using shorthand syntax: `gamedig --type [:]` +* UDP socket is only opened if needed by a query. +* Automatic query port detection -- If provided with a non-standard port, gamedig will attempt to discover if it is a +game port or query port by querying twice: once to the port provided, and once to the port including the game's query +port offset (if available). +* Simplified detection of BC2 when using battlefield protocol. +* Fixed buildandshoot not reading player list +* Added new `connect` field to the response object. This will typically include the game's `ip:port` (the port will reflect the server's +game port, even if you passed in a query port in your request). For some games, this may be a server ID or connection url +if an IP:Port is not appropriate. +* Added new `ping` field (in milliseconds) to the response object. Since icmp packets are often blocked by NATs, and node has poor support +for raw sockets, this time is derived from the rtt of one of the UDP requests, tcp socket connection, or http requests made +during the query. + + + + ### 1.0 * First official release * Node.js 6.0 is now required diff --git a/protocols/core.js b/protocols/core.js index 39edf46..7db0a67 100644 --- a/protocols/core.js +++ b/protocols/core.js @@ -21,6 +21,8 @@ class Core extends EventEmitter { // Sent to us by QueryRunner this.options = null; this.udpSocket = null; + this.shortestRTT = 0; + this.usedTcp = false; } async runAllAttempts() { @@ -68,7 +70,6 @@ class Core extends EventEmitter { } async runOnce() { - const startMillis = Date.now(); const options = this.options; if (('host' in options) && !('address' in options)) { options.address = await this.parseDns(options.host); @@ -94,13 +95,13 @@ class Core extends EventEmitter { // because lots of servers prefix with spaces to try to appear first state.name = (state.name || '').trim(); - state.duration = Date.now() - startMillis; if (!('connect' in state)) { state.connect = '' + (state.gameHost || this.options.host || this.options.address) + ':' + (state.gamePort || this.options.port) } + state.ping = this.shortestRTT; delete state.gameHost; delete state.gamePort; @@ -148,6 +149,23 @@ class Core extends EventEmitter { else return await resolveStandard(host); } + /** Param can be a time in ms, or a promise (which will be timed) */ + registerRtt(param) { + if (param.then) { + const start = Date.now(); + param.then(() => { + const end = Date.now(); + const rtt = end - start; + this.registerRtt(rtt); + }).catch(() => {}); + } else { + this.debugLog("Registered RTT: " + param + "ms"); + if (this.shortestRTT === 0 || param < this.shortestRTT) { + this.shortestRTT = param; + } + } + } + // utils /** @returns {Reader} */ reader(buffer) { @@ -186,6 +204,7 @@ class Core extends EventEmitter { * @returns {Promise} */ async withTcp(fn, port) { + this.usedTcp = true; const address = this.options.address; if (!port) port = this.options.port; this.assertValidPort(port); @@ -216,6 +235,7 @@ class Core extends EventEmitter { socket.on('ready', resolve); socket.on('close', () => reject(new Error('TCP Connection Refused'))); }); + this.registerRtt(connectionPromise); connectionTimeout = Promises.createTimeout(this.options.socketTimeout, 'TCP Opening'); await Promise.race([ connectionPromise, @@ -284,10 +304,17 @@ class Core extends EventEmitter { let timeout; try { const promise = new Promise((resolve, reject) => { + const start = Date.now(); + let end = null; socketCallback = (fromAddress, fromPort, buffer) => { try { if (fromAddress !== address) return; if (fromPort !== port) return; + if (end === null) { + end = Date.now(); + const rtt = end-start; + this.registerRtt(rtt); + } this.debugLog(log => { log(fromAddress + ':' + fromPort + " <--UDP"); log(HexUtil.debugDump(buffer)); @@ -307,7 +334,6 @@ class Core extends EventEmitter { const wrappedTimeout = new Promise((resolve, reject) => { timeout.catch((e) => { this.debugLog("UDP timeout detected"); - let success = false; if (onTimeout) { try { const result = onTimeout(); @@ -331,6 +357,12 @@ class Core extends EventEmitter { } async request(params) { + // If we haven't opened a raw tcp socket yet during this query, just open one and then immediately close it. + // This will give us a much more accurate RTT than using the rtt of the http request. + if (!this.usedTcp) { + await this.withTcp(() => {}); + } + let requestPromise; try { requestPromise = requestAsync({ @@ -344,7 +376,10 @@ class Core extends EventEmitter { .then((response) => log(params.uri + " <--HTTP " + response.statusCode)) .catch(() => {}); }); - const wrappedPromise = requestPromise.then(response => response.body); + const wrappedPromise = requestPromise.then(response => { + if (response.statusCode !== 200) throw new Error("Bad status code: " + response.statusCode); + return response.body; + }); return await Promise.race([wrappedPromise, this.abortedPromise]); } finally { requestPromise && requestPromise.cancel(); diff --git a/protocols/nadeo.js b/protocols/nadeo.js index f214360..2280bd7 100644 --- a/protocols/nadeo.js +++ b/protocols/nadeo.js @@ -4,7 +4,10 @@ const gbxremote = require('gbxremote'), class Nadeo extends Core { async run(state) { await this.withClient(async client => { + const start = Date.now(); await this.methodCall(client, 'Authenticate', this.options.login, this.options.password); + this.registerRtt(Date.now()-start); + //const data = this.methodCall(client, 'GetStatus'); {