2020-04-21 22:59:53 +00:00
const dns = require ( 'dns' )
const minecraftJavaPing = require ( 'mc-ping-updated' )
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' )
function ping ( host , port , type , timeout , callback , version ) {
switch ( type ) {
case 'PC' :
unfurlSrv ( host , port , ( host , port ) => {
minecraftJavaPing ( host , port || 25565 , ( err , res ) => {
if ( err ) {
callback ( err )
} else {
const payload = {
players : {
online : capPlayerCount ( host , 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 )
}
} , timeout , version )
} )
break
case 'PE' :
minecraftBedrockPing ( host , port || 19132 , ( err , res ) => {
if ( err ) {
callback ( err )
} else {
callback ( null , {
players : {
online : capPlayerCount ( host , parseInt ( res . currentPlayers ) )
}
} )
}
} , timeout )
break
default :
throw new Error ( 'Unsupported type: ' + type )
}
2015-11-02 07:04:49 +00:00
}
2020-04-21 22:59:53 +00:00
function unfurlSrv ( hostname , port , callback ) {
2020-05-08 04:46:59 +00:00
if ( config . performance && config . performance . skipUnfurlSrv ) {
callback ( hostname , port )
return
}
2020-04-21 22:59:53 +00:00
dns . resolveSrv ( '_minecraft._tcp.' + hostname , ( _ , records ) => {
if ( ! records || records . length < 1 ) {
callback ( hostname , port )
} else {
callback ( records [ 0 ] . name , records [ 0 ] . port )
}
} )
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
}
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-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
if ( config . logToDatabase ) {
const playerCount = result . resp ? result . resp . players . online : 0
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
const update = serverRegistration . handlePing ( timestamp , result . resp , result . err , result . version )
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' , {
timestamp ,
updates
} ) )
} )
}
2020-04-29 09:21:50 +00:00
2020-05-08 05:36:15 +00:00
startPingTasks = ( callback ) => {
const results = [ ]
let remainingTasks = this . _app . serverRegistrations . length
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 05:36:15 +00:00
ping ( serverRegistration . data . ip , serverRegistration . data . port , serverRegistration . data . type , config . rates . connectTimeout , ( err , resp ) => {
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-08 05:36:15 +00:00
if ( -- remainingTasks === 0 ) {
callback ( results )
}
} , version . protocolId )
}
2020-04-21 22:59:53 +00:00
}
2020-05-08 06:54:04 +00:00
getMaxServerGraphDataLength ( ) {
return Math . ceil ( config . serverGraphDuration / config . rates . pingAll )
}
getMaxGraphDataLength ( ) {
return Math . ceil ( config . graphDuration / config . rates . pingAll )
}
2020-03-30 06:31:35 +00:00
}
2020-04-21 22:59:53 +00:00
module . exports = PingController