2015-11-02 04:56:08 +00:00
var server = require ( './lib/server' ) ;
var ping = require ( './lib/ping' ) ;
2015-11-02 05:46:24 +00:00
var logger = require ( './lib/logger' ) ;
2015-11-03 04:32:54 +00:00
var mojang = require ( './lib/mojang_services' ) ;
2015-11-09 00:34:17 +00:00
var util = require ( './lib/util' ) ;
2015-12-11 04:06:27 +00:00
var db = require ( './lib/database' ) ;
2015-11-02 05:46:24 +00:00
2015-11-02 04:56:08 +00:00
var config = require ( './config.json' ) ;
2016-02-01 10:39:31 +00:00
var servers = require ( './servers.json' ) ;
2015-11-02 04:56:08 +00:00
var networkHistory = [ ] ;
var connectedClients = 0 ;
2016-03-02 03:09:38 +00:00
var currentVersionIndex = {
'PC' : 0 ,
'PE' : 0
} ;
2017-03-14 22:07:58 +00:00
2016-03-02 03:09:38 +00:00
var networkVersions = [ ] ;
2015-12-18 07:45:38 +00:00
var graphData = [ ] ;
2017-03-11 23:44:21 +00:00
var highestPlayerCount = { } ;
2015-12-18 07:45:38 +00:00
var lastGraphPush = [ ] ;
2020-03-30 05:48:47 +00:00
var graphPeaks = { } ;
2015-12-18 07:45:38 +00:00
2015-11-02 06:57:30 +00:00
function pingAll ( ) {
2015-11-02 05:21:42 +00:00
for ( var i = 0 ; i < servers . length ; i ++ ) {
2015-11-02 04:56:08 +00:00
// Make sure we lock our scope.
( function ( network ) {
2017-04-04 03:10:46 +00:00
// Asign auto generated color if not present
if ( ! network . color ) {
network . color = util . stringToColor ( network . name ) ;
}
2016-03-02 03:09:38 +00:00
var attemptedVersion = config . versions [ network . type ] [ currentVersionIndex [ network . type ] ] ;
2015-11-03 04:32:54 +00:00
ping . ping ( network . ip , network . port , network . type , config . rates . connectTimeout , function ( err , res ) {
2015-11-02 04:56:08 +00:00
// Handle our ping results, if it succeeded.
if ( err ) {
2015-12-26 20:05:39 +00:00
logger . log ( 'error' , 'Failed to ping ' + network . ip + ': ' + err . message ) ;
2015-11-02 04:56:08 +00:00
}
2015-11-09 01:08:31 +00:00
// If we have favicon override specified, use it.
if ( res && config . faviconOverride && config . faviconOverride [ network . name ] ) {
res . favicon = config . faviconOverride [ network . name ] ;
}
2016-03-02 03:09:38 +00:00
handlePing ( network , res , err , attemptedVersion ) ;
} , attemptedVersion ) ;
2015-12-26 20:21:20 +00:00
} ) ( servers [ i ] ) ;
}
2016-03-02 03:09:38 +00:00
currentVersionIndex [ 'PC' ] ++ ;
currentVersionIndex [ 'PE' ] ++ ;
if ( currentVersionIndex [ 'PC' ] >= config . versions [ 'PC' ] . length ) {
// Loop around
currentVersionIndex [ 'PC' ] = 0 ;
}
if ( currentVersionIndex [ 'PE' ] >= config . versions [ 'PE' ] . length ) {
// Loop around
currentVersionIndex [ 'PE' ] = 0 ;
}
2015-12-26 20:21:20 +00:00
}
2015-11-24 06:34:00 +00:00
2015-12-26 20:21:20 +00:00
// This is where the result of a ping is feed.
// This stores it and converts it to ship to the frontend.
2016-03-02 03:09:38 +00:00
function handlePing ( network , res , err , attemptedVersion ) {
// Log our response.
if ( ! networkHistory [ network . name ] ) {
networkHistory [ network . name ] = [ ] ;
}
// Update the version list
if ( ! networkVersions [ network . name ] ) {
networkVersions [ network . name ] = [ ] ;
}
// If the result version matches the attempted version, the version is supported
var _networkVersions = networkVersions [ network . name ] ;
if ( res ) {
if ( res . version == attemptedVersion ) {
if ( _networkVersions . indexOf ( res . version ) == - 1 ) {
_networkVersions . push ( res . version ) ;
}
} else {
// Mismatch, so remove the version from the supported version list
var index = _networkVersions . indexOf ( attemptedVersion ) ;
if ( index != - 1 ) {
_networkVersions . splice ( index , 1 ) ;
}
}
}
// Update the clients
2015-12-26 20:21:20 +00:00
var networkSnapshot = {
info : {
name : network . name ,
2016-03-02 03:09:38 +00:00
timestamp : util . getCurrentTimeMs ( ) ,
type : network . type
} ,
2017-03-11 23:44:21 +00:00
versions : _networkVersions ,
record : highestPlayerCount [ network . ip ]
2015-12-26 20:21:20 +00:00
} ;
if ( res ) {
networkSnapshot . result = res ;
2017-03-11 23:44:21 +00:00
// Validate that we have logToDatabase enabled otherwise in memory pings
// will create a record that's only valid for the runtime duration.
if ( config . logToDatabase && res . players . online > highestPlayerCount [ network . ip ] ) {
highestPlayerCount [ network . ip ] = res . players . online ;
}
2015-12-26 20:21:20 +00:00
} else if ( err ) {
networkSnapshot . error = err ;
}
2015-11-02 05:19:27 +00:00
2015-12-26 20:21:20 +00:00
server . io . sockets . emit ( 'update' , networkSnapshot ) ;
2015-11-02 04:56:08 +00:00
2015-12-26 20:21:20 +00:00
var _networkHistory = networkHistory [ network . name ] ;
2015-11-24 06:34:00 +00:00
2015-12-26 20:21:20 +00:00
// Remove our previous data that we don't need anymore.
for ( var i = 0 ; i < _networkHistory . length ; i ++ ) {
delete _networkHistory [ i ] . info ;
2015-11-02 04:56:08 +00:00
2015-12-26 20:21:20 +00:00
if ( _networkHistory [ i ] . result ) {
delete _networkHistory [ i ] . result . favicon ;
}
}
2015-12-11 04:06:27 +00:00
2015-12-26 20:21:20 +00:00
_networkHistory . push ( {
error : err ,
result : res ,
2016-03-02 03:09:38 +00:00
versions : _networkVersions ,
2015-12-26 20:21:20 +00:00
timestamp : util . getCurrentTimeMs ( ) ,
info : {
ip : network . ip ,
port : network . port ,
type : network . type ,
2017-03-11 23:44:21 +00:00
name : network . name
2015-12-26 20:21:20 +00:00
}
} ) ;
// Make sure we never log too much.
if ( _networkHistory . length > 72 ) { // 60/2.5 = 24, so 24 is one minute
_networkHistory . shift ( ) ;
}
2015-12-18 07:45:38 +00:00
2015-12-26 20:21:20 +00:00
// Log it to the database if needed.
if ( config . logToDatabase ) {
db . log ( network . ip , util . getCurrentTimeMs ( ) , res ? res . players . online : 0 ) ;
}
2015-12-18 07:45:38 +00:00
2015-12-26 20:21:20 +00:00
// Push it to our graphs.
var timeMs = util . getCurrentTimeMs ( ) ;
2015-12-18 07:45:38 +00:00
2015-12-26 20:21:20 +00:00
// The same mechanic from trimUselessPings is seen here.
// If we dropped the ping, then to avoid destroying the graph, ignore it.
// However if it's been too long since the last successful ping, we'll send it anyways.
if ( config . logToDatabase ) {
if ( ! lastGraphPush [ network . ip ] || ( timeMs - lastGraphPush [ network . ip ] >= 60 * 1000 && res ) || timeMs - lastGraphPush [ network . ip ] >= 70 * 1000 ) {
lastGraphPush [ network . ip ] = timeMs ;
2015-12-18 07:45:38 +00:00
2015-12-26 20:21:20 +00:00
// Don't have too much data!
util . trimOldPings ( graphData ) ;
2015-12-18 07:45:38 +00:00
2016-02-01 10:53:40 +00:00
if ( ! graphData [ network . name ] ) {
graphData [ network . name ] = [ ] ;
2015-12-28 02:04:20 +00:00
}
2016-02-01 10:53:40 +00:00
graphData [ network . name ] . push ( [ timeMs , res ? res . players . online : 0 ] ) ;
2015-12-26 20:21:20 +00:00
// Send the update.
server . io . sockets . emit ( 'updateHistoryGraph' , {
ip : network . ip ,
2016-02-01 10:58:05 +00:00
name : network . name ,
2015-12-26 20:21:20 +00:00
players : ( res ? res . players . online : 0 ) ,
timestamp : timeMs
2015-11-02 04:56:08 +00:00
} ) ;
2015-12-26 20:21:20 +00:00
}
2020-03-30 05:37:49 +00:00
// Update calculated graph peak regardless if the graph is being updated
// This can cause a (harmless) desync between live and stored data, but it allows it to be more accurate for long surviving processes
var networkData = graphData [ network . name ] ;
if ( networkData ) {
var graphPeakIndex = - 1 ;
var graphPeakPlayerCount = 0 ;
for ( var i = 0 ; i < networkData . length ; i ++ ) {
// [1] refers to the online player count
var point = networkData [ i ] [ 1 ] ;
if ( point > 0 && ( graphPeakIndex === - 1 || point > graphPeakPlayerCount ) ) {
graphPeakIndex = i ;
graphPeakPlayerCount = point ;
}
}
// Test if a highest index has been selected and has changed from any previous selections
var previousPeak = graphPeaks [ network . name ] ;
// [1] refers to the online player count
if ( graphPeakIndex !== - 1 && ( ! previousPeak || previousPeak [ 1 ] !== graphPeakPlayerCount ) ) {
var graphPeakData = networkData [ graphPeakIndex ] ;
graphPeaks [ network . name ] = graphPeakData ;
// Broadcast update event to clients
server . io . sockets . emit ( 'updatePeak' , {
ip : network . ip ,
name : network . name ,
players : graphPeakData [ 1 ] ,
timestamp : graphPeakData [ 0 ]
} ) ;
}
}
2015-11-02 04:56:08 +00:00
}
2015-11-02 06:57:30 +00:00
}
2015-11-02 04:56:08 +00:00
2015-11-03 04:32:54 +00:00
// Start our main loop that does everything.
function startMainLoop ( ) {
2015-11-24 23:24:17 +00:00
util . setIntervalNoDelay ( pingAll , config . rates . pingAll ) ;
2015-11-03 04:32:54 +00:00
2015-11-24 23:24:17 +00:00
util . setIntervalNoDelay ( function ( ) {
2015-11-03 04:32:54 +00:00
mojang . update ( config . rates . mojangStatusTimeout ) ;
server . io . sockets . emit ( 'updateMojangServices' , mojang . toMessage ( ) ) ;
} , config . rates . upateMojangStatus ) ;
}
2015-12-19 00:10:58 +00:00
function startServices ( ) {
2015-12-26 20:18:09 +00:00
server . start ( ) ;
2015-12-18 07:45:38 +00:00
2015-12-26 20:18:09 +00:00
// Track how many people are currently connected.
server . io . on ( 'connect' , function ( client ) {
// We're good to connect them!
connectedClients += 1 ;
2015-12-18 07:45:38 +00:00
2015-12-26 20:18:09 +00:00
logger . log ( 'info' , '%s connected, total clients: %d' , client . request . connection . remoteAddress , connectedClients ) ;
2015-12-18 07:45:38 +00:00
2016-02-07 00:26:29 +00:00
// We send the boot time (also sent in publicConfig.json) to the frontend to validate they have the same config.
// If so, they'll send back "requestListing" event, otherwise they will pull the new config and retry.
client . emit ( 'bootTime' , util . getBootTime ( ) ) ;
2015-12-19 00:10:58 +00:00
2015-12-26 20:18:09 +00:00
// Attach our listeners.
client . on ( 'disconnect' , function ( ) {
connectedClients -= 1 ;
2015-11-02 05:46:24 +00:00
2015-12-26 20:18:09 +00:00
logger . log ( 'info' , '%s disconnected, total clients: %d' , client . request . connection . remoteAddress , connectedClients ) ;
2015-12-19 01:00:57 +00:00
} ) ;
2015-12-26 20:18:09 +00:00
client . on ( 'requestHistoryGraph' , function ( ) {
if ( config . logToDatabase ) {
// Send them the big 24h graph.
client . emit ( 'historyGraph' , graphData ) ;
2020-03-30 05:37:49 +00:00
// Send current peaks, if any
2020-03-30 05:48:47 +00:00
if ( Object . keys ( graphPeaks ) . length > 0 ) {
2020-03-30 05:37:49 +00:00
client . emit ( 'peaks' , graphPeaks ) ;
}
2015-12-26 20:18:09 +00:00
}
} ) ;
2016-02-07 00:26:29 +00:00
client . on ( 'requestListing' , function ( ) {
// Send them our previous data, so they have somewhere to start.
client . emit ( 'updateMojangServices' , mojang . toMessage ( ) ) ;
// Remap our associative array into just an array.
var networkHistoryKeys = Object . keys ( networkHistory ) ;
networkHistoryKeys . sort ( ) ;
// Send each individually, this should look cleaner than waiting for one big array to transfer.
2016-02-07 00:36:51 +00:00
for ( var i = 0 ; i < servers . length ; i ++ ) {
var server = servers [ i ] ;
if ( ! ( server . name in networkHistory ) || networkHistory [ server . name ] . length < 1 ) {
// This server hasn't been ping'd yet. Send a hacky placeholder.
client . emit ( 'add' , [ [ {
error : {
description : 'Waiting'
} ,
result : null ,
timestamp : util . getCurrentTimeMs ( ) ,
info : {
ip : server . ip ,
port : server . port ,
type : server . type ,
name : server . name
}
} ] ] ) ;
} else {
client . emit ( 'add' , [ networkHistory [ networkHistoryKeys [ i ] ] ] ) ;
}
2016-02-07 00:26:29 +00:00
}
2017-03-14 22:07:58 +00:00
client . emit ( 'syncComplete' ) ;
2016-02-07 00:26:29 +00:00
} ) ;
2015-12-19 00:10:58 +00:00
} ) ;
2015-12-26 20:18:09 +00:00
startMainLoop ( ) ;
2015-12-19 00:10:58 +00:00
}
2015-11-02 04:56:08 +00:00
2015-12-19 00:25:59 +00:00
logger . log ( 'info' , 'Booting, please wait...' ) ;
2015-12-19 00:10:58 +00:00
if ( config . logToDatabase ) {
// Setup our database.
db . setup ( ) ;
2015-11-25 22:40:13 +00:00
2015-12-19 00:10:58 +00:00
var timestamp = util . getCurrentTimeMs ( ) ;
2015-12-18 07:45:38 +00:00
2015-12-19 03:23:24 +00:00
db . queryPings ( config . graphDuration , function ( data ) {
2017-03-14 22:07:58 +00:00
graphData = util . convertServerHistory ( data ) ;
completedQueries = 0 ;
2015-11-02 04:56:08 +00:00
2015-12-19 00:10:58 +00:00
logger . log ( 'info' , 'Queried and parsed ping history in %sms' , util . getCurrentTimeMs ( ) - timestamp ) ;
2015-11-02 06:01:04 +00:00
2017-03-14 22:07:58 +00:00
for ( var i = 0 ; i < servers . length ; i ++ ) {
2020-03-30 05:37:49 +00:00
// Compute graph peak from historical data
var networkData = graphData [ servers [ i ] . name ] ;
if ( networkData ) {
var graphPeakIndex = - 1 ;
var graphPeakPlayerCount = 0 ;
2020-03-30 05:39:38 +00:00
for ( var x = 0 ; x < networkData . length ; x ++ ) {
2020-03-30 05:37:49 +00:00
// [1] refers to the online player count
2020-04-02 01:41:18 +00:00
var point = networkData [ x ] [ 1 ] ;
2020-03-30 05:37:49 +00:00
if ( point > 0 && ( graphPeakIndex === - 1 || point > graphPeakPlayerCount ) ) {
2020-03-30 05:39:38 +00:00
graphPeakIndex = x ;
2020-03-30 05:37:49 +00:00
graphPeakPlayerCount = point ;
}
}
if ( graphPeakIndex !== - 1 ) {
graphPeaks [ servers [ i ] . name ] = networkData [ graphPeakIndex ] ;
logger . log ( 'info' , 'Selected graph peak %d (%s)' , networkData [ graphPeakIndex ] [ 1 ] , servers [ i ] . name ) ;
}
}
2017-03-14 22:07:58 +00:00
( function ( server ) {
db . getTotalRecord ( server . ip , function ( record ) {
2020-03-30 05:37:49 +00:00
logger . log ( 'info' , 'Computed total record %s (%d)' , server . ip , record ) ;
2017-03-14 22:07:58 +00:00
highestPlayerCount [ server . ip ] = record ;
completedQueries += 1 ;
if ( completedQueries === servers . length ) {
startServices ( ) ;
}
} ) ;
} ) ( servers [ i ] ) ;
}
2015-11-02 04:56:08 +00:00
} ) ;
2015-12-19 00:10:58 +00:00
} else {
2015-12-19 03:23:24 +00:00
logger . log ( 'warn' , 'Database logging is not enabled. You can enable it by setting "logToDatabase" to true in config.json. This requires sqlite3 to be installed.' ) ;
2015-11-02 06:57:30 +00:00
2015-12-19 00:10:58 +00:00
startServices ( ) ;
2016-03-02 03:09:38 +00:00
}