6ce1697a39
* ping fail log configurable * fix json * fix config namespace in ping * changes
160 lines
4.9 KiB
JavaScript
160 lines
4.9 KiB
JavaScript
const minecraftJavaPing = require('mcping-js')
|
|
const minecraftBedrockPing = require('mcpe-ping-fixed')
|
|
|
|
const logger = require('./logger')
|
|
const MessageOf = require('./message')
|
|
const { TimeTracker } = require('./time')
|
|
|
|
const { getPlayerCountOrNull } = require('./util')
|
|
|
|
const config = require('../config')
|
|
|
|
function ping (serverRegistration, timeout, callback, version) {
|
|
switch (serverRegistration.data.type) {
|
|
case 'PC':
|
|
serverRegistration.dnsResolver.resolve((host, port, remainingTimeout) => {
|
|
const server = new minecraftJavaPing.MinecraftServer(host, port || 25565)
|
|
|
|
server.ping(remainingTimeout, version, (err, res) => {
|
|
if (err) {
|
|
callback(err)
|
|
} else {
|
|
const payload = {
|
|
players: {
|
|
online: capPlayerCount(serverRegistration.data.ip, parseInt(res.players.online))
|
|
},
|
|
version: parseInt(res.version.protocol)
|
|
}
|
|
|
|
// Ensure the returned favicon is a data URI
|
|
if (res.favicon && res.favicon.startsWith('data:image/')) {
|
|
payload.favicon = res.favicon
|
|
}
|
|
|
|
callback(null, payload)
|
|
}
|
|
})
|
|
})
|
|
break
|
|
|
|
case 'PE':
|
|
minecraftBedrockPing(serverRegistration.data.ip, serverRegistration.data.port || 19132, (err, res) => {
|
|
if (err) {
|
|
callback(err)
|
|
} else {
|
|
callback(null, {
|
|
players: {
|
|
online: capPlayerCount(serverRegistration.data.ip, parseInt(res.currentPlayers))
|
|
}
|
|
})
|
|
}
|
|
}, timeout)
|
|
break
|
|
|
|
default:
|
|
throw new Error('Unsupported type: ' + serverRegistration.data.type)
|
|
}
|
|
}
|
|
|
|
// player count can be up to 1^32-1, which is a massive scale and destroys browser performance when rendering graphs
|
|
// Artificially cap and warn to prevent propogating garbage
|
|
function capPlayerCount (host, playerCount) {
|
|
const maxPlayerCount = 250000
|
|
|
|
if (playerCount !== Math.min(playerCount, maxPlayerCount)) {
|
|
logger.log('warn', '%s returned a player count of %d, Minetrack has capped it to %d to prevent browser performance issues with graph rendering. If this is in error, please edit maxPlayerCount in ping.js!', host, playerCount, maxPlayerCount)
|
|
|
|
return maxPlayerCount
|
|
} else if (playerCount !== Math.max(playerCount, 0)) {
|
|
logger.log('warn', '%s returned an invalid player count of %d, setting to 0.', host, playerCount)
|
|
|
|
return 0
|
|
}
|
|
return playerCount
|
|
}
|
|
|
|
class PingController {
|
|
constructor (app) {
|
|
this._app = app
|
|
this._isRunningTasks = false
|
|
}
|
|
|
|
schedule () {
|
|
setInterval(this.pingAll, config.rates.pingAll)
|
|
|
|
this.pingAll()
|
|
}
|
|
|
|
pingAll = () => {
|
|
const { timestamp, updateHistoryGraph } = this._app.timeTracker.newPointTimestamp()
|
|
|
|
this.startPingTasks(results => {
|
|
const updates = []
|
|
|
|
for (const serverRegistration of this._app.serverRegistrations) {
|
|
const result = results[serverRegistration.serverId]
|
|
|
|
// Log to database if enabled
|
|
// Use null to represent a failed ping
|
|
if (config.logToDatabase) {
|
|
const unsafePlayerCount = getPlayerCountOrNull(result.resp)
|
|
|
|
this._app.database.insertPing(serverRegistration.data.ip, timestamp, unsafePlayerCount)
|
|
}
|
|
|
|
// Generate a combined update payload
|
|
// This includes any modified fields and flags used by the frontend
|
|
// This will not be cached and can contain live metadata
|
|
const update = serverRegistration.handlePing(timestamp, result.resp, result.err, result.version, updateHistoryGraph)
|
|
|
|
updates[serverRegistration.serverId] = update
|
|
}
|
|
|
|
// Send object since updates uses serverIds as keys
|
|
// Send a single timestamp entry since it is shared
|
|
this._app.server.broadcast(MessageOf('updateServers', {
|
|
timestamp: TimeTracker.toSeconds(timestamp),
|
|
updateHistoryGraph,
|
|
updates
|
|
}))
|
|
})
|
|
}
|
|
|
|
startPingTasks = (callback) => {
|
|
if (this._isRunningTasks) {
|
|
logger.log('warn', 'Started re-pinging servers before the last loop has finished! You may need to increase "rates.pingAll" in config.json')
|
|
|
|
return
|
|
}
|
|
|
|
this._isRunningTasks = true
|
|
|
|
const results = []
|
|
|
|
for (const serverRegistration of this._app.serverRegistrations) {
|
|
const version = serverRegistration.getNextProtocolVersion()
|
|
|
|
ping(serverRegistration, config.rates.connectTimeout, (err, resp) => {
|
|
if (err && config.logFailedPings !== false) {
|
|
logger.log('error', 'Failed to ping %s: %s', serverRegistration.data.ip, err.message)
|
|
}
|
|
|
|
results[serverRegistration.serverId] = {
|
|
resp,
|
|
err,
|
|
version
|
|
}
|
|
|
|
if (Object.keys(results).length === this._app.serverRegistrations.length) {
|
|
// Loop has completed, release the locking flag
|
|
this._isRunningTasks = false
|
|
|
|
callback(results)
|
|
}
|
|
}, version.protocolId)
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = PingController
|