prettyify code
This commit is contained in:
240
lib/servers.js
240
lib/servers.js
@ -1,263 +1,297 @@
|
||||
const crypto = require('crypto')
|
||||
const crypto = require("crypto");
|
||||
|
||||
const DNSResolver = require('./dns')
|
||||
const Server = require('./server')
|
||||
const DNSResolver = require("./dns");
|
||||
const Server = require("./server");
|
||||
|
||||
const { GRAPH_UPDATE_TIME_GAP, TimeTracker } = require('./time')
|
||||
const { getPlayerCountOrNull } = require('./util')
|
||||
const { GRAPH_UPDATE_TIME_GAP, TimeTracker } = require("./time");
|
||||
const { getPlayerCountOrNull } = require("./util");
|
||||
|
||||
const config = require('../config')
|
||||
const minecraftVersions = require('../minecraft_versions')
|
||||
const config = require("../config");
|
||||
const minecraftVersions = require("../minecraft_versions");
|
||||
|
||||
class ServerRegistration {
|
||||
serverId
|
||||
lastFavicon
|
||||
versions = []
|
||||
recordData
|
||||
graphData = []
|
||||
serverId;
|
||||
lastFavicon;
|
||||
versions = [];
|
||||
recordData;
|
||||
graphData = [];
|
||||
|
||||
constructor (app, serverId, data) {
|
||||
this._app = app
|
||||
this.serverId = serverId
|
||||
this.data = data
|
||||
this._pingHistory = []
|
||||
this.dnsResolver = new DNSResolver(this.data.ip, this.data.port)
|
||||
constructor(app, serverId, data) {
|
||||
this._app = app;
|
||||
this.serverId = serverId;
|
||||
this.data = data;
|
||||
this._pingHistory = [];
|
||||
this.dnsResolver = new DNSResolver(this.data.ip, this.data.port);
|
||||
}
|
||||
|
||||
handlePing (timestamp, resp, err, version, updateHistoryGraph) {
|
||||
handlePing(timestamp, resp, err, version, updateHistoryGraph) {
|
||||
// Use null to represent a failed ping
|
||||
const unsafePlayerCount = getPlayerCountOrNull(resp)
|
||||
const unsafePlayerCount = getPlayerCountOrNull(resp);
|
||||
|
||||
// Store into in-memory ping data
|
||||
TimeTracker.pushAndShift(this._pingHistory, unsafePlayerCount, TimeTracker.getMaxServerGraphDataLength())
|
||||
TimeTracker.pushAndShift(
|
||||
this._pingHistory,
|
||||
unsafePlayerCount,
|
||||
TimeTracker.getMaxServerGraphDataLength()
|
||||
);
|
||||
|
||||
// Only notify the frontend to append to the historical graph
|
||||
// if both the graphing behavior is enabled and the backend agrees
|
||||
// that the ping is eligible for addition
|
||||
if (updateHistoryGraph) {
|
||||
TimeTracker.pushAndShift(this.graphData, unsafePlayerCount, TimeTracker.getMaxGraphDataLength())
|
||||
TimeTracker.pushAndShift(
|
||||
this.graphData,
|
||||
unsafePlayerCount,
|
||||
TimeTracker.getMaxGraphDataLength()
|
||||
);
|
||||
}
|
||||
|
||||
// Delegate out update payload generation
|
||||
return this.getUpdate(timestamp, resp, err, version)
|
||||
return this.getUpdate(timestamp, resp, err, version);
|
||||
}
|
||||
|
||||
getUpdate (timestamp, resp, err, version) {
|
||||
const update = {}
|
||||
getUpdate(timestamp, resp, err, version) {
|
||||
const update = {};
|
||||
|
||||
// Always append a playerCount value
|
||||
// When resp is undefined (due to an error), playerCount will be null
|
||||
update.playerCount = getPlayerCountOrNull(resp)
|
||||
update.playerCount = getPlayerCountOrNull(resp);
|
||||
|
||||
if (resp) {
|
||||
if (resp.version && this.updateProtocolVersionCompat(resp.version, version.protocolId, version.protocolIndex)) {
|
||||
if (
|
||||
resp.version &&
|
||||
this.updateProtocolVersionCompat(
|
||||
resp.version,
|
||||
version.protocolId,
|
||||
version.protocolIndex
|
||||
)
|
||||
) {
|
||||
// Append an updated version listing
|
||||
update.versions = this.versions
|
||||
update.versions = this.versions;
|
||||
}
|
||||
|
||||
if (config.logToDatabase && (!this.recordData || resp.players.online > this.recordData.playerCount)) {
|
||||
if (
|
||||
config.logToDatabase &&
|
||||
(!this.recordData || resp.players.online > this.recordData.playerCount)
|
||||
) {
|
||||
this.recordData = {
|
||||
playerCount: resp.players.online,
|
||||
timestamp: TimeTracker.toSeconds(timestamp)
|
||||
}
|
||||
timestamp: TimeTracker.toSeconds(timestamp),
|
||||
};
|
||||
|
||||
// Append an updated recordData
|
||||
update.recordData = this.recordData
|
||||
update.recordData = this.recordData;
|
||||
|
||||
// Update record in database
|
||||
this._app.database.updatePlayerCountRecord(this.data.ip, resp.players.online, timestamp)
|
||||
this._app.database.updatePlayerCountRecord(
|
||||
this.data.ip,
|
||||
resp.players.online,
|
||||
timestamp
|
||||
);
|
||||
}
|
||||
|
||||
if (this.updateFavicon(resp.favicon)) {
|
||||
update.favicon = this.getFaviconUrl()
|
||||
update.favicon = this.getFaviconUrl();
|
||||
}
|
||||
|
||||
if (config.logToDatabase) {
|
||||
// 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
|
||||
if (this.findNewGraphPeak()) {
|
||||
update.graphPeakData = this.getGraphPeak()
|
||||
update.graphPeakData = this.getGraphPeak();
|
||||
}
|
||||
}
|
||||
} else if (err) {
|
||||
// Append a filtered copy of err
|
||||
// This ensures any unintended data is not leaked
|
||||
update.error = this.filterError(err)
|
||||
update.error = this.filterError(err);
|
||||
}
|
||||
|
||||
return update
|
||||
return update;
|
||||
}
|
||||
|
||||
getPingHistory () {
|
||||
getPingHistory() {
|
||||
if (this._pingHistory.length > 0) {
|
||||
const payload = {
|
||||
versions: this.versions,
|
||||
recordData: this.recordData,
|
||||
favicon: this.getFaviconUrl()
|
||||
}
|
||||
favicon: this.getFaviconUrl(),
|
||||
};
|
||||
|
||||
// Only append graphPeakData if defined
|
||||
// The value is lazy computed and conditional that config->logToDatabase == true
|
||||
const graphPeakData = this.getGraphPeak()
|
||||
const graphPeakData = this.getGraphPeak();
|
||||
|
||||
if (graphPeakData) {
|
||||
payload.graphPeakData = graphPeakData
|
||||
payload.graphPeakData = graphPeakData;
|
||||
}
|
||||
|
||||
// Assume the ping was a success and define result
|
||||
// pingHistory does not keep error references, so its impossible to detect if this is an error
|
||||
// It is also pointless to store that data since it will be short lived
|
||||
payload.playerCount = this._pingHistory[this._pingHistory.length - 1]
|
||||
payload.playerCount = this._pingHistory[this._pingHistory.length - 1];
|
||||
|
||||
// Send a copy of pingHistory
|
||||
// Include the last value even though it is contained within payload
|
||||
// The frontend will only push to its graphData from playerCountHistory
|
||||
payload.playerCountHistory = this._pingHistory
|
||||
payload.playerCountHistory = this._pingHistory;
|
||||
|
||||
return payload
|
||||
return payload;
|
||||
}
|
||||
|
||||
return {
|
||||
error: {
|
||||
message: 'Pinging...'
|
||||
message: "Pinging...",
|
||||
},
|
||||
recordData: this.recordData,
|
||||
graphPeakData: this.getGraphPeak(),
|
||||
favicon: this.data.favicon
|
||||
}
|
||||
favicon: this.data.favicon,
|
||||
};
|
||||
}
|
||||
|
||||
loadGraphPoints (startTime, timestamps, points) {
|
||||
this.graphData = TimeTracker.everyN(timestamps, startTime, GRAPH_UPDATE_TIME_GAP, (i) => points[i])
|
||||
loadGraphPoints(startTime, timestamps, points) {
|
||||
this.graphData = TimeTracker.everyN(
|
||||
timestamps,
|
||||
startTime,
|
||||
GRAPH_UPDATE_TIME_GAP,
|
||||
(i) => points[i]
|
||||
);
|
||||
}
|
||||
|
||||
findNewGraphPeak () {
|
||||
let index = -1
|
||||
findNewGraphPeak() {
|
||||
let index = -1;
|
||||
for (let i = 0; i < this.graphData.length; i++) {
|
||||
const point = this.graphData[i]
|
||||
const point = this.graphData[i];
|
||||
if (point !== null && (index === -1 || point > this.graphData[index])) {
|
||||
index = i
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
if (index >= 0) {
|
||||
const lastGraphPeakIndex = this._graphPeakIndex
|
||||
this._graphPeakIndex = index
|
||||
return index !== lastGraphPeakIndex
|
||||
const lastGraphPeakIndex = this._graphPeakIndex;
|
||||
this._graphPeakIndex = index;
|
||||
return index !== lastGraphPeakIndex;
|
||||
} else {
|
||||
this._graphPeakIndex = undefined
|
||||
return false
|
||||
this._graphPeakIndex = undefined;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
getGraphPeak () {
|
||||
getGraphPeak() {
|
||||
if (this._graphPeakIndex === undefined) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
return {
|
||||
playerCount: this.graphData[this._graphPeakIndex],
|
||||
timestamp: this._app.timeTracker.getGraphPointAt(this._graphPeakIndex)
|
||||
}
|
||||
timestamp: this._app.timeTracker.getGraphPointAt(this._graphPeakIndex),
|
||||
};
|
||||
}
|
||||
|
||||
updateFavicon (favicon) {
|
||||
updateFavicon(favicon) {
|
||||
// If data.favicon is defined, then a favicon override is present
|
||||
// Disregard the incoming favicon, regardless if it is different
|
||||
if (this.data.favicon) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
if (favicon && favicon !== this.lastFavicon) {
|
||||
this.lastFavicon = favicon
|
||||
this.lastFavicon = favicon;
|
||||
|
||||
// Generate an updated hash
|
||||
// This is used by #getFaviconUrl
|
||||
this.faviconHash = crypto.createHash('md5').update(favicon).digest('hex').toString()
|
||||
this.faviconHash = crypto
|
||||
.createHash("md5")
|
||||
.update(favicon)
|
||||
.digest("hex")
|
||||
.toString();
|
||||
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
getFaviconUrl () {
|
||||
getFaviconUrl() {
|
||||
if (this.faviconHash) {
|
||||
return Server.getHashedFaviconUrl(this.faviconHash)
|
||||
return Server.getHashedFaviconUrl(this.faviconHash);
|
||||
} else if (this.data.favicon) {
|
||||
return this.data.favicon
|
||||
return this.data.favicon;
|
||||
}
|
||||
}
|
||||
|
||||
updateProtocolVersionCompat (incomingId, outgoingId, protocolIndex) {
|
||||
updateProtocolVersionCompat(incomingId, outgoingId, protocolIndex) {
|
||||
// If the result version matches the attempted version, the version is supported
|
||||
const isSuccess = incomingId === outgoingId
|
||||
const indexOf = this.versions.indexOf(protocolIndex)
|
||||
const isSuccess = incomingId === outgoingId;
|
||||
const indexOf = this.versions.indexOf(protocolIndex);
|
||||
|
||||
// Test indexOf to avoid inserting previously recorded protocolIndex values
|
||||
if (isSuccess && indexOf < 0) {
|
||||
this.versions.push(protocolIndex)
|
||||
this.versions.push(protocolIndex);
|
||||
|
||||
// Sort versions in ascending order
|
||||
// This matches protocol ids to Minecraft versions release order
|
||||
this.versions.sort((a, b) => a - b)
|
||||
this.versions.sort((a, b) => a - b);
|
||||
|
||||
return true
|
||||
return true;
|
||||
} else if (!isSuccess && indexOf >= 0) {
|
||||
this.versions.splice(indexOf, 1)
|
||||
return true
|
||||
this.versions.splice(indexOf, 1);
|
||||
return true;
|
||||
}
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
getNextProtocolVersion () {
|
||||
getNextProtocolVersion() {
|
||||
// Minecraft Bedrock Edition does not have protocol versions
|
||||
if (this.data.type === 'PE') {
|
||||
if (this.data.type === "PE") {
|
||||
return {
|
||||
protocolId: 0,
|
||||
protocolIndex: 0
|
||||
}
|
||||
protocolIndex: 0,
|
||||
};
|
||||
}
|
||||
const protocolVersions = minecraftVersions[this.data.type]
|
||||
if (typeof this._nextProtocolIndex === 'undefined' || this._nextProtocolIndex + 1 >= protocolVersions.length) {
|
||||
this._nextProtocolIndex = 0
|
||||
const protocolVersions = minecraftVersions[this.data.type];
|
||||
if (
|
||||
typeof this._nextProtocolIndex === "undefined" ||
|
||||
this._nextProtocolIndex + 1 >= protocolVersions.length
|
||||
) {
|
||||
this._nextProtocolIndex = 0;
|
||||
} else {
|
||||
this._nextProtocolIndex++
|
||||
this._nextProtocolIndex++;
|
||||
}
|
||||
return {
|
||||
protocolId: protocolVersions[this._nextProtocolIndex].protocolId,
|
||||
protocolIndex: this._nextProtocolIndex
|
||||
}
|
||||
protocolIndex: this._nextProtocolIndex,
|
||||
};
|
||||
}
|
||||
|
||||
filterError (err) {
|
||||
let message = 'Unknown error'
|
||||
filterError(err) {
|
||||
let message = "Unknown error";
|
||||
|
||||
// Attempt to match to the first possible value
|
||||
for (const key of ['message', 'description', 'errno']) {
|
||||
for (const key of ["message", "description", "errno"]) {
|
||||
if (err[key]) {
|
||||
message = err[key]
|
||||
break
|
||||
message = err[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Trim the message if too long
|
||||
if (message.length > 28) {
|
||||
message = message.substring(0, 28) + '...'
|
||||
message = message.substring(0, 28) + "...";
|
||||
}
|
||||
|
||||
return {
|
||||
message: message
|
||||
}
|
||||
message: message,
|
||||
};
|
||||
}
|
||||
|
||||
getPublicData () {
|
||||
getPublicData() {
|
||||
// Return a custom object instead of data directly to avoid data leakage
|
||||
return {
|
||||
name: this.data.name,
|
||||
ip: this.data.ip,
|
||||
type: this.data.type,
|
||||
color: this.data.color
|
||||
}
|
||||
color: this.data.color,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ServerRegistration
|
||||
module.exports = ServerRegistration;
|
||||
|
Reference in New Issue
Block a user