2023-12-30 23:03:54 +00:00
const minecraftJavaPing = require ( "mcping-js" ) ;
const minecraftBedrockPing = require ( "mcpe-ping-fixed" ) ;
2020-04-21 22:59:53 +00:00
2023-12-30 23:03:54 +00:00
const logger = require ( "./logger" ) ;
const MessageOf = require ( "./message" ) ;
const { TimeTracker } = require ( "./time" ) ;
2020-05-11 23:29:26 +00:00
2023-12-30 23:03:54 +00:00
const { getPlayerCountOrNull } = require ( "./util" ) ;
2020-04-21 22:59:53 +00:00
2023-12-30 23:03:54 +00:00
const config = require ( "../config" ) ;
2020-04-21 22:59:53 +00:00
2023-12-30 23:03:54 +00:00
function ping ( serverRegistration , timeout , callback , version ) {
2020-05-08 21:53:48 +00:00
switch ( serverRegistration . data . type ) {
2023-12-30 23:03:54 +00:00
case "PC" :
2020-05-15 01:56:59 +00:00
serverRegistration . dnsResolver . resolve ( ( host , port , remainingTimeout ) => {
2023-12-30 23:03:54 +00:00
const server = new minecraftJavaPing . MinecraftServer (
host ,
port || 25565
) ;
2020-05-10 04:28:37 +00:00
2020-05-15 02:03:14 +00:00
server . ping ( remainingTimeout , version , ( err , res ) => {
2020-04-21 22:59:53 +00:00
if ( err ) {
2023-12-30 23:03:54 +00:00
callback ( err ) ;
2020-04-21 22:59:53 +00:00
} else {
const payload = {
players : {
2023-12-30 23:03:54 +00:00
online : capPlayerCount (
serverRegistration . data . ip ,
parseInt ( res . players . online )
) ,
2020-04-21 22:59:53 +00:00
} ,
2023-12-30 23:03:54 +00:00
version : parseInt ( res . version . protocol ) ,
} ;
2020-04-21 22:59:53 +00:00
// Ensure the returned favicon is a data URI
2023-12-30 23:03:54 +00:00
if ( res . favicon && res . favicon . startsWith ( "data:image/" ) ) {
payload . favicon = res . favicon ;
2020-04-21 22:59:53 +00:00
}
2023-12-30 23:03:54 +00:00
callback ( null , payload ) ;
2020-04-21 22:59:53 +00:00
}
2023-12-30 23:03:54 +00:00
} ) ;
} ) ;
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 ;
2020-04-21 22:59:53 +00:00
default :
2023-12-30 23:03:54 +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
2023-12-30 23:03:54 +00:00
function capPlayerCount ( host , playerCount ) {
const maxPlayerCount = 250000 ;
2020-04-21 22:59:53 +00:00
if ( playerCount !== Math . min ( playerCount , maxPlayerCount ) ) {
2023-12-30 23:03:54 +00:00
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 ;
2020-04-21 22:59:53 +00:00
} else if ( playerCount !== Math . max ( playerCount , 0 ) ) {
2023-12-30 23:03:54 +00:00
logger . log (
"warn" ,
"%s returned an invalid player count of %d, setting to 0." ,
host ,
playerCount
) ;
return 0 ;
2020-04-21 22:59:53 +00:00
}
2023-12-30 23:03:54 +00:00
return playerCount ;
2020-04-21 22:59:53 +00:00
}
class PingController {
2023-12-30 23:03:54 +00:00
constructor ( app ) {
this . _app = app ;
this . _isRunningTasks = false ;
2020-04-21 22:59:53 +00:00
}
2023-12-30 23:03:54 +00:00
schedule ( ) {
setInterval ( this . pingAll , config . rates . pingAll ) ;
2023-12-30 23:54:53 +00:00
// todo: make this a cron job?
2020-04-21 22:59:53 +00:00
2023-12-30 23:03:54 +00:00
this . pingAll ( ) ;
2020-04-21 22:59:53 +00:00
}
pingAll = ( ) => {
2023-12-30 23:03:54 +00:00
const { timestamp , updateHistoryGraph } =
this . _app . timeTracker . newPointTimestamp ( ) ;
2020-05-11 09:12:46 +00:00
2023-12-30 23:03:54 +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 ) {
2023-12-30 23:03:54 +00:00
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 ) {
2023-12-30 23:03:54 +00:00
const unsafePlayerCount = getPlayerCountOrNull ( result . resp ) ;
2020-05-08 08:35:22 +00:00
2023-12-30 23:03:54 +00:00
this . _app . database . insertPing (
serverRegistration . data . ip ,
timestamp ,
unsafePlayerCount
) ;
2020-05-08 05:36:15 +00:00
}
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
2023-12-30 23:03:54 +00:00
const update = serverRegistration . handlePing (
timestamp ,
result . resp ,
result . err ,
result . version ,
updateHistoryGraph
) ;
updates [ serverRegistration . serverId ] = update ;
2020-05-08 05:36:15 +00:00
}
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
2023-12-30 23:03:54 +00:00
this . _app . server . broadcast (
MessageOf ( "updateServers" , {
timestamp : TimeTracker . toSeconds ( timestamp ) ,
updateHistoryGraph ,
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 ) {
2023-12-30 23:03:54 +00:00
logger . log (
"warn" ,
'Started re-pinging servers before the last loop has finished! You may need to increase "rates.pingAll" in config.json'
) ;
2020-05-10 04:28:37 +00:00
2023-12-30 23:03:54 +00:00
return ;
2020-05-10 04:28:37 +00:00
}
2023-12-30 23:03:54 +00:00
this . _isRunningTasks = true ;
2020-05-10 04:28:37 +00:00
2023-12-30 23:03:54 +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 ) {
2023-12-30 23:03:54 +00:00
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
) ;
}
2020-04-29 09:01:10 +00:00
2023-12-30 23:03:54 +00:00
results [ serverRegistration . serverId ] = {
resp ,
err ,
version ,
} ;
2020-05-05 21:49:01 +00:00
2023-12-30 23:03:54 +00:00
if (
Object . keys ( results ) . length === this . _app . serverRegistrations . length
) {
// Loop has completed, release the locking flag
this . _isRunningTasks = false ;
2020-05-10 04:28:37 +00:00
2023-12-30 23:03:54 +00:00
callback ( results ) ;
}
} ,
version . protocolId
) ;
2020-05-08 05:36:15 +00:00
}
2023-12-30 23:03:54 +00:00
} ;
2020-03-30 06:31:35 +00:00
}
2023-12-30 23:03:54 +00:00
module . exports = PingController ;