2020-05-10 04:28:37 +00:00
const minecraftJavaPing = require ( 'mcping-js' )
2020-04-21 22:59:53 +00:00
const minecraftBedrockPing = require ( 'mcpe-ping-fixed' )
const logger = require ( './logger' )
2020-05-05 21:49:01 +00:00
const MessageOf = require ( './message' )
2020-04-21 22:59:53 +00:00
const config = require ( '../config' )
2020-05-08 21:53:48 +00:00
function ping ( serverRegistration , timeout , callback , version ) {
switch ( serverRegistration . data . type ) {
2020-04-21 22:59:53 +00:00
case 'PC' :
2020-05-08 21:53:48 +00:00
serverRegistration . unfurlSrv ( ( host , port ) => {
2020-05-10 04:28:37 +00:00
const server = new minecraftJavaPing . MinecraftServer ( host , port || 25565 )
server . ping ( timeout , version , ( err , res ) => {
2020-04-21 22:59:53 +00:00
if ( err ) {
callback ( err )
} else {
const payload = {
players : {
2020-05-10 05:28:29 +00:00
online : capPlayerCount ( serverRegistration . data . ip , parseInt ( res . players . online ) )
2020-04-21 22:59:53 +00:00
} ,
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 )
}
2020-05-10 04:28:37 +00:00
} )
2020-04-21 22:59:53 +00:00
} )
break
case 'PE' :
2020-05-08 21:53:48 +00:00
minecraftBedrockPing ( serverRegistration . data . ip , serverRegistration . data . port || 19132 , ( err , res ) => {
2020-04-21 22:59:53 +00:00
if ( err ) {
callback ( err )
} else {
callback ( null , {
players : {
2020-05-08 21:53:48 +00:00
online : capPlayerCount ( serverRegistration . data . ip , parseInt ( res . currentPlayers ) )
2020-04-21 22:59:53 +00:00
}
} )
}
} , timeout )
break
default :
2020-05-08 21:53:48 +00:00
throw new Error ( 'Unsupported type: ' + serverRegistration . data . type )
2020-05-08 04:46:59 +00:00
}
2015-11-02 06:57:30 +00:00
}
2020-03-30 06:31:35 +00:00
// 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
2020-04-21 22:59:53 +00:00
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
2020-05-10 04:28:37 +00:00
this . _isRunningTasks = false
2020-04-21 22:59:53 +00:00
}
schedule ( ) {
setInterval ( this . pingAll , config . rates . pingAll )
this . pingAll ( )
}
pingAll = ( ) => {
2020-05-08 06:22:07 +00:00
const timestamp = this . _app . timeTracker . newTimestamp ( )
2020-04-21 22:59:53 +00:00
2020-05-11 09:12:46 +00:00
// Flag each group as history graph additions each minute
// This is sent to the frontend for graph updates
const updateHistoryGraph = config . logToDatabase && ( ! this . _lastHistoryGraphUpdate || timestamp - this . _lastHistoryGraphUpdate >= 60 * 1000 )
if ( updateHistoryGraph ) {
this . _lastHistoryGraphUpdate = timestamp
}
2020-05-08 05:36:15 +00:00
this . startPingTasks ( results => {
const updates = [ ]
2020-04-21 22:59:53 +00:00
2020-05-08 05:36:15 +00:00
for ( const serverRegistration of this . _app . serverRegistrations ) {
const result = results [ serverRegistration . serverId ]
2020-04-21 22:59:53 +00:00
2020-05-08 05:36:15 +00:00
// Log to database if enabled
2020-05-11 08:10:23 +00:00
// Use null to represent a failed ping
2020-05-08 05:36:15 +00:00
if ( config . logToDatabase ) {
2020-05-11 08:10:23 +00:00
const playerCount = result . resp ? result . resp . players . online : null
2020-05-08 08:35:22 +00:00
2020-05-08 05:36:15 +00:00
this . _app . database . insertPing ( serverRegistration . data . ip , timestamp , playerCount )
}
2020-04-21 22:59:53 +00:00
2020-05-08 05:36:15 +00:00
// 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
2020-05-11 09:12:46 +00:00
const update = serverRegistration . handlePing ( timestamp , result . resp , result . err , result . version , updateHistoryGraph )
2020-05-08 08:35:22 +00:00
2020-05-08 05:36:15 +00:00
updates [ serverRegistration . serverId ] = update
}
2020-04-21 22:59:53 +00:00
2020-05-08 05:36:15 +00:00
// Send object since updates uses serverIds as keys
// Send a single timestamp entry since it is shared
this . _app . server . broadcast ( MessageOf ( 'updateServers' , {
2020-05-11 09:12:46 +00:00
timestamp : Math . floor ( timestamp / 1000 ) ,
updateHistoryGraph ,
2020-05-08 05:36:15 +00:00
updates
} ) )
} )
}
2020-04-29 09:21:50 +00:00
2020-05-08 05:36:15 +00:00
startPingTasks = ( callback ) => {
2020-05-10 04:28:37 +00:00
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
2020-05-08 05:36:15 +00:00
const results = [ ]
2020-04-21 22:59:53 +00:00
2020-05-08 05:36:15 +00:00
for ( const serverRegistration of this . _app . serverRegistrations ) {
const version = serverRegistration . getNextProtocolVersion ( )
2020-04-21 22:59:53 +00:00
2020-05-08 21:53:48 +00:00
ping ( serverRegistration , config . rates . connectTimeout , ( err , resp ) => {
2020-05-08 05:36:15 +00:00
if ( err ) {
logger . log ( 'error' , 'Failed to ping %s: %s' , serverRegistration . data . ip , err . message )
}
2020-04-29 09:01:10 +00:00
2020-05-08 05:36:15 +00:00
results [ serverRegistration . serverId ] = {
resp ,
err ,
version
}
2020-05-05 21:49:01 +00:00
2020-05-10 04:28:37 +00:00
if ( Object . keys ( results ) . length === this . _app . serverRegistrations . length ) {
// Loop has completed, release the locking flag
this . _isRunningTasks = false
2020-05-08 05:36:15 +00:00
callback ( results )
}
} , version . protocolId )
}
2020-04-21 22:59:53 +00:00
}
2020-03-30 06:31:35 +00:00
}
2020-04-21 22:59:53 +00:00
module . exports = PingController