From ca9e127e3e1a39c8719ce6ae50e813a73c5aa78b Mon Sep 17 00:00:00 2001 From: Nick Krecklow Date: Tue, 5 May 2020 16:49:01 -0500 Subject: [PATCH 1/3] replace socket.io usage with WebSockets --- assets/js/app.js | 3 - assets/js/main.js | 203 +++++++++++++++++++++++++--------------------- config.json | 2 +- lib/app.js | 64 ++++++++------- lib/message.js | 6 ++ lib/mojang.js | 9 +- lib/ping.js | 5 +- lib/server.js | 79 ++++++++++-------- package-lock.json | 30 ++++++- package.json | 8 +- 10 files changed, 240 insertions(+), 169 deletions(-) create mode 100644 lib/message.js diff --git a/assets/js/app.js b/assets/js/app.js index c58425b..061da70 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -81,9 +81,6 @@ export class App { document.getElementById('stat_totalPlayers').innerText = 0 document.getElementById('stat_networks').innerText = 0 - // Modify page state to display loading overlay - this.caption.set('Lost connection!') - this.setPageReady(false) } diff --git a/assets/js/main.js b/assets/js/main.js index 2b3b36d..70e6b17 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,123 +1,142 @@ import { App } from './app' -import io from 'socket.io-client' - const app = new App() document.addEventListener('DOMContentLoaded', function () { - const socket = io.connect({ - reconnect: true, - reconnectDelay: 1000, - reconnectionAttempts: 10 - }) + const webSocket = new WebSocket('ws://' + location.host) // The backend will automatically push data once connected - socket.on('connect', function () { + webSocket.onopen = () => { app.caption.set('Loading...') - }) + } - socket.on('disconnect', function () { + webSocket.onclose = (event) => { app.handleDisconnect() + // Modify page state to display loading overlay + // Code 1006 denotes "Abnormal closure", most likely from the server or client losing connection + // See https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent + // Treat other codes as active errors (besides connectivity errors) when displaying the message + if (event.code === 1006) { + app.caption.set('Lost connection!') + } else { + app.caption.set('Disconnected due to error.') + } + // Reset modified DOM structures document.getElementById('big-graph-mobile-load-request').style.display = 'none' - }) - socket.on('historyGraph', function (data) { - // Consider the graph visible since a payload has been received - // This is used for the manual graph load request behavior - app.graphDisplayManager.isVisible = true + // TODO: Reconnect behavior + } - app.graphDisplayManager.buildPlotInstance(data) + webSocket.onmessage = (message) => { + const payload = JSON.parse(message.data) - // Build checkbox elements for graph controls - let lastRowCounter = 0 - let controlsHTML = '' + switch (payload.message) { + case 'init': + app.setPublicConfig(payload.config) - app.serverRegistry.getServerRegistrations() - .map(serverRegistration => serverRegistration.data.name) - .sort() - .forEach(serverName => { - const serverRegistration = app.serverRegistry.getServerRegistration(serverName) + // Display the main page component + // Called here instead of syncComplete so the DOM can be drawn prior to the graphs being drawn + // Otherwise flot.js will cause visual alignment bugs + app.setPageReady(true) - controlsHTML += '' + - '' + - ' ' + serverName + - '' - - // Occasionally break table rows using a magic number - if (++lastRowCounter % 6 === 0) { - controlsHTML += '' + // Allow the graphDisplayManager to control whether or not the historical graph is loaded + // Defer to isGraphVisible from the publicConfig to understand if the frontend will ever receive a graph payload + if (app.publicConfig.isGraphVisible) { + if (app.graphDisplayManager.isVisible) { + // Send request as a plain text string to avoid the server needing to parse JSON + // This is mostly to simplify the backend server's need for error handling + webSocket.send('requestHistoryGraph') + } else { + document.getElementById('big-graph-mobile-load-request').style.display = 'block' + } } - }) - // Apply generated HTML and show controls - document.getElementById('big-graph-checkboxes').innerHTML = '' + - controlsHTML + - '
' + payload.servers.forEach(app.addServer) - document.getElementById('big-graph-controls').style.display = 'block' + if (payload.mojangServices) { + app.mojangUpdater.updateStatus(payload.mojangServices) + } - // Bind click event for updating graph data - app.graphDisplayManager.initEventListeners() - }) + // Init payload contains all data needed to render the page + // Alert the app it is ready + app.handleSyncComplete() - socket.on('add', function (data) { - data.forEach(app.addServer) - }) + break - socket.on('update', function (data) { - // The backend may send "update" events prior to receiving all "add" events - // A server has only been added once it's ServerRegistration is defined - // Checking undefined protects from this race condition - const serverRegistration = app.serverRegistry.getServerRegistration(data.serverId) + case 'updateServer': { + // The backend may send "update" events prior to receiving all "add" events + // A server has only been added once it's ServerRegistration is defined + // Checking undefined protects from this race condition + const serverRegistration = app.serverRegistry.getServerRegistration(payload.serverId) - if (serverRegistration) { - serverRegistration.updateServerStatus(data, false, app.publicConfig.minecraftVersions) - } + if (serverRegistration) { + serverRegistration.updateServerStatus(payload, false, app.publicConfig.minecraftVersions) + } - // Use update payloads to conditionally append data to graph - // Skip any incoming updates if the graph is disabled - if (data.updateHistoryGraph && app.graphDisplayManager.isVisible) { - // Update may not be successful, safely append 0 points - const playerCount = data.result ? data.result.players.online : 0 + // Use update payloads to conditionally append data to graph + // Skip any incoming updates if the graph is disabled + if (payload.updateHistoryGraph && app.graphDisplayManager.isVisible) { + // Update may not be successful, safely append 0 points + const playerCount = payload.result ? payload.result.players.online : 0 - app.graphDisplayManager.addGraphPoint(serverRegistration.serverId, data.timestamp, playerCount) + app.graphDisplayManager.addGraphPoint(serverRegistration.serverId, payload.timestamp, playerCount) - // Only redraw the graph if not mutating hidden data - if (serverRegistration.isVisible) { - app.graphDisplayManager.requestRedraw() + // Only redraw the graph if not mutating hidden data + if (serverRegistration.isVisible) { + app.graphDisplayManager.requestRedraw() + } + } + break + } + + case 'updateMojangServices': { + app.mojangUpdater.updateStatus(payload) + break + } + + case 'historyGraph': { + // Consider the graph visible since a payload has been received + // This is used for the manual graph load request behavior + app.graphDisplayManager.isVisible = true + + app.graphDisplayManager.buildPlotInstance(payload.graphData) + + // Build checkbox elements for graph controls + let lastRowCounter = 0 + let controlsHTML = '' + + app.serverRegistry.getServerRegistrations() + .map(serverRegistration => serverRegistration.data.name) + .sort() + .forEach(serverName => { + const serverRegistration = app.serverRegistry.getServerRegistration(serverName) + + controlsHTML += '' + + '' + + ' ' + serverName + + '' + + // Occasionally break table rows using a magic number + if (++lastRowCounter % 6 === 0) { + controlsHTML += '' + } + }) + + // Apply generated HTML and show controls + document.getElementById('big-graph-checkboxes').innerHTML = '' + + controlsHTML + + '
' + + document.getElementById('big-graph-controls').style.display = 'block' + + // Bind click event for updating graph data + app.graphDisplayManager.initEventListeners() + break } } - }) - - socket.on('updateMojangServices', function (data) { - app.mojangUpdater.updateStatus(data) - }) - - socket.on('setPublicConfig', function (data) { - app.setPublicConfig(data) - - // Display the main page component - // Called here instead of syncComplete so the DOM can be drawn prior to the graphs being drawn - // Otherwise flot.js will cause visual alignment bugs - app.setPageReady(true) - - // Allow the graphDisplayManager to control whether or not the historical graph is loaded - // Defer to isGraphVisible from the publicConfig to understand if the frontend will ever receive a graph payload - if (data.isGraphVisible) { - if (app.graphDisplayManager.isVisible) { - socket.emit('requestHistoryGraph') - } else { - document.getElementById('big-graph-mobile-load-request').style.display = 'block' - } - } - }) - - // Fired once the backend has sent all requested data - socket.on('syncComplete', function () { - app.handleSyncComplete() - }) + } window.addEventListener('resize', function () { app.percentageBar.redraw() @@ -128,7 +147,9 @@ document.addEventListener('DOMContentLoaded', function () { document.getElementById('big-graph-mobile-load-request-button').addEventListener('click', function () { // Send a graph data request to the backend - socket.emit('requestHistoryGraph') + // Send request as a plain text string to avoid the server needing to parse JSON + // This is mostly to simplify the backend server's need for error handling + webSocket.send('requestHistoryGraph') // Hide the activation link to avoid multiple requests document.getElementById('big-graph-mobile-load-request').style.display = 'none' diff --git a/config.json b/config.json index 3fd9359..0a11b03 100644 --- a/config.json +++ b/config.json @@ -9,6 +9,6 @@ "pingAll": 3000, "connectTimeout": 2500 }, - "logToDatabase": false, + "logToDatabase": true, "graphDuration": 86400000 } diff --git a/lib/app.js b/lib/app.js index b62f44a..820b42d 100644 --- a/lib/app.js +++ b/lib/app.js @@ -2,6 +2,7 @@ const Database = require('./database') const MojangUpdater = require('./mojang') const PingController = require('./ping') const Server = require('./server') +const MessageOf = require('./message') const config = require('../config') const minecraftVersions = require('../minecraft_versions') @@ -36,43 +37,46 @@ class App { handleClientConnection = (client) => { if (config.logToDatabase) { - client.on('requestHistoryGraph', () => { - // Send historical graphData built from all serverRegistrations - const graphData = {} + client.on('message', (message) => { + if (message === 'requestHistoryGraph') { + // Send historical graphData built from all serverRegistrations + const graphData = {} - this.serverRegistrations.forEach((serverRegistration) => { - graphData[serverRegistration.serverId] = serverRegistration.graphData - }) + this.serverRegistrations.forEach((serverRegistration) => { + graphData[serverRegistration.serverId] = serverRegistration.graphData + }) - client.emit('historyGraph', graphData) + // Send graphData in object wrapper to avoid needing to explicity filter + // any header data being appended by #MessageOf since the graph data is fed + // directly into the flot.js graphing system + client.send(MessageOf('historyGraph', { + graphData: graphData + })) + } }) } - client.emit('setPublicConfig', (() => { - // Remap minecraftVersion entries into name values - const minecraftVersionNames = {} - Object.keys(minecraftVersions).forEach(function (key) { - minecraftVersionNames[key] = minecraftVersions[key].map(version => version.name) - }) + const initMessage = { + config: (() => { + // Remap minecraftVersion entries into name values + const minecraftVersionNames = {} + Object.keys(minecraftVersions).forEach(function (key) { + minecraftVersionNames[key] = minecraftVersions[key].map(version => version.name) + }) - // Send configuration data for rendering the page - return { - graphDuration: config.graphDuration, - servers: this.serverRegistrations.map(serverRegistration => serverRegistration.data), - minecraftVersions: minecraftVersionNames, - isGraphVisible: config.logToDatabase - } - })()) + // Send configuration data for rendering the page + return { + graphDuration: config.graphDuration, + servers: this.serverRegistrations.map(serverRegistration => serverRegistration.data), + minecraftVersions: minecraftVersionNames, + isGraphVisible: config.logToDatabase + } + })(), + mojangServices: this.mojangUpdater.getLastUpdate(), + servers: this.serverRegistrations.map(serverRegistration => serverRegistration.getPingHistory()) + } - // Send last Mojang update, if any - this.mojangUpdater.sendLastUpdate(client) - - // Send pingHistory of all ServerRegistrations - client.emit('add', this.serverRegistrations.map(serverRegistration => serverRegistration.getPingHistory())) - - // Always send last - // This tells the frontend to do final processing and render - client.emit('syncComplete') + client.send(MessageOf('init', initMessage)) } } diff --git a/lib/message.js b/lib/message.js new file mode 100644 index 0000000..0a407f7 --- /dev/null +++ b/lib/message.js @@ -0,0 +1,6 @@ +module.exports = function MessageOf (name, data) { + return JSON.stringify({ + message: name, + ...data + }) +} diff --git a/lib/mojang.js b/lib/mojang.js index 5f932b5..7f83dab 100644 --- a/lib/mojang.js +++ b/lib/mojang.js @@ -1,6 +1,7 @@ const request = require('request') const logger = require('./logger') +const MessageOf = require('./message') const config = require('../config') @@ -65,14 +66,12 @@ class MojangUpdater { if (this._hasUpdated) { this._hasUpdated = false - this._app.server.broadcast('updateMojangServices', this._services) + this._app.server.broadcast(MessageOf('updateMojangServices', this._services)) } } - sendLastUpdate (client) { - if (this._services) { - client.emit('updateMojangServices', this._services) - } + getLastUpdate () { + return this._services } handleServiceUpdate (url, color) { diff --git a/lib/ping.js b/lib/ping.js index ac6a67c..65ea8af 100644 --- a/lib/ping.js +++ b/lib/ping.js @@ -4,6 +4,7 @@ const minecraftJavaPing = require('mc-ping-updated') const minecraftBedrockPing = require('mcpe-ping-fixed') const logger = require('./logger') +const MessageOf = require('./message') const config = require('../config') @@ -125,7 +126,9 @@ class PingController { // 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 - this._app.server.broadcast('update', serverRegistration.getUpdate(timestamp, resp, err, version, updateHistoryGraph)) + const updateMessage = serverRegistration.getUpdate(timestamp, resp, err, version, updateHistoryGraph) + + this._app.server.broadcast(MessageOf('updateServer', updateMessage)) } } diff --git a/lib/server.js b/lib/server.js index eb52491..9a6b371 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,8 +1,8 @@ const http = require('http') +const WebSocket = require('ws') const finalHttpHandler = require('finalhandler') const serveStatic = require('serve-static') -const io = require('socket.io') const logger = require('./logger') @@ -12,52 +12,65 @@ function getRemoteAddr (req) { class Server { constructor (clientSocketHandler) { - this._clientSocketHandler = clientSocketHandler - this._connectedSockets = 0 + this.createHttpServer() + this.createWebSocketServer(clientSocketHandler) + } - this._http = http.createServer(this.handleHttpRequest) + createHttpServer () { + const distServeStatic = serveStatic('dist/') + const faviconsServeStatic = serveStatic('favicons/') - this._distServeStatic = serveStatic('dist/') - this._faviconsServeStatic = serveStatic('favicons/') + this._http = http.createServer((req, res) => { + logger.log('info', '%s requested: %s', getRemoteAddr(req), req.url) + + // Attempt to handle req using distServeStatic, otherwise fail over to faviconServeStatic + // If faviconServeStatic fails, pass to finalHttpHandler to terminate + distServeStatic(req, res, () => { + faviconsServeStatic(req, res, finalHttpHandler(req, res)) + }) + }) + } + + createWebSocketServer (proxyClientSocketHandler) { + this._wss = new WebSocket.Server({ + server: this._http + }) + + this._wss.on('connection', (client, req) => { + logger.log('info', '%s connected, total clients: %d', getRemoteAddr(req), this.getConnectedClients()) + + // Bind disconnect event for logging + client.on('close', () => { + logger.log('info', '%s disconnected, total clients: %d', getRemoteAddr(req), this.getConnectedClients()) + }) + + // Pass client off to proxy handler + proxyClientSocketHandler(client) + }) } listen (host, port) { this._http.listen(port, host) - this._io = io.listen(this._http) - this._io.on('connect', this.handleClientSocket) - logger.log('info', 'Started on %s:%d', host, port) } - broadcast (event, payload) { - this._io.sockets.emit(event, payload) - } - - handleHttpRequest = (req, res) => { - logger.log('info', '%s requested: %s', getRemoteAddr(req), req.url) - - // Attempt to handle req using distServeStatic, otherwise fail over to faviconServeStatic - // If faviconServeStatic fails, pass to finalHttpHandler to terminate - this._distServeStatic(req, res, () => { - this._faviconsServeStatic(req, res, finalHttpHandler(req, res)) + broadcast (payload) { + this._wss.clients.forEach(client => { + if (client.readyState === WebSocket.OPEN) { + client.send(payload) + } }) } - handleClientSocket = (client) => { - this._connectedSockets++ - - logger.log('info', '%s connected, total clients: %d', getRemoteAddr(client.request), this._connectedSockets) - - // Bind disconnect event for logging - client.on('disconnect', () => { - this._connectedSockets-- - - logger.log('info', '%s disconnected, total clients: %d', getRemoteAddr(client.request), this._connectedSockets) + getConnectedClients () { + let count = 0 + this._wss.clients.forEach(client => { + if (client.readyState === WebSocket.OPEN) { + count++ + } }) - - // Pass client off to proxy handler - this._clientSocketHandler(client) + return count } } diff --git a/package-lock.json b/package-lock.json index c460edf..4522922 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1836,6 +1836,15 @@ "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", "dev": true }, + "bufferutil": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.1.tgz", + "integrity": "sha512-xowrxvpxojqkagPcWRQVXZl0YXhRhAtBEIq3VoER1NH5Mw1n1o0ojdspp+GS2J//2gCVyrzQDApQ4unGF+QOoA==", + "optional": true, + "requires": { + "node-gyp-build": "~3.7.0" + } + }, "bufferview": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/bufferview/-/bufferview-1.0.1.tgz", @@ -5876,6 +5885,12 @@ "integrity": "sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw==", "dev": true }, + "node-gyp-build": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-3.7.0.tgz", + "integrity": "sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w==", + "optional": true + }, "node-libs-browser": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", @@ -8782,6 +8797,15 @@ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", "dev": true }, + "utf-8-validate": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.2.tgz", + "integrity": "sha512-SwV++i2gTD5qh2XqaPzBnNX88N6HdyhQrNNRykvcS0QKvItV9u3vPEJr+X5Hhfb1JC0r0e1alL0iB09rY8+nmw==", + "optional": true, + "requires": { + "node-gyp-build": "~3.7.0" + } + }, "util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", @@ -9041,9 +9065,9 @@ } }, "ws": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz", - "integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==" + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.5.tgz", + "integrity": "sha512-C34cIU4+DB2vMyAbmEKossWq2ZQDr6QEyuuCzWrM9zfw1sGc0mYiJ0UnG9zzNykt49C2Fi34hvr2vssFQRS6EA==" }, "xml-name-validator": { "version": "3.0.0", diff --git a/package.json b/package.json index 90ca944..0c51944 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,9 @@ "mcpe-ping-fixed": "0.0.3", "request": "2.88.2", "serve-static": "^1.14.1", - "socket.io": "2.3.0", "sqlite3": "4.1.1", - "winston": "^2.0.0" + "winston": "^2.0.0", + "ws": "^7.2.5" }, "repository": { "type": "git", @@ -44,5 +44,9 @@ "scripts": { "build": "eslint assets/js/*.js && parcel build assets/html/index.html", "dev": "parcel build assets/html/index.html --no-minify" + }, + "optionalDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" } } From 438a72724b0ce86f95b6215bdafac3ff501d5d1f Mon Sep 17 00:00:00 2001 From: Nick Krecklow Date: Tue, 5 May 2020 17:17:12 -0500 Subject: [PATCH 2/3] add WebSocket reconnection logic --- assets/js/app.js | 8 ++ assets/js/main.js | 141 +------------------------------- assets/js/socket.js | 191 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 202 insertions(+), 138 deletions(-) create mode 100644 assets/js/socket.js diff --git a/assets/js/app.js b/assets/js/app.js index 061da70..c291dc6 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -1,4 +1,5 @@ import { ServerRegistry } from './servers' +import { SocketManager } from './socket' import { SortController } from './sort' import { GraphDisplayManager } from './graph' import { MojangUpdater } from './mojang' @@ -13,6 +14,7 @@ export class App { this.tooltip = new Tooltip() this.caption = new Caption() this.serverRegistry = new ServerRegistry(this) + this.socketManager = new SocketManager(this) this.sortController = new SortController(this) this.graphDisplayManager = new GraphDisplayManager(this) this.mojangUpdater = new MojangUpdater() @@ -22,6 +24,11 @@ export class App { this._taskIds = [] } + // Called once the DOM is ready and the app can begin setup + init () { + this.socketManager.createWebSocket() + } + setPageReady (isReady) { document.getElementById('push').style.display = isReady ? 'block' : 'none' document.getElementById('footer').style.display = isReady ? 'block' : 'none' @@ -60,6 +67,7 @@ export class App { // Reset individual tracker elements to flush any held data this.serverRegistry.reset() + this.socketManager.reset() this.sortController.reset() this.graphDisplayManager.reset() this.mojangUpdater.reset() diff --git a/assets/js/main.js b/assets/js/main.js index 70e6b17..c0ee1b5 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -2,141 +2,8 @@ import { App } from './app' const app = new App() -document.addEventListener('DOMContentLoaded', function () { - const webSocket = new WebSocket('ws://' + location.host) - - // The backend will automatically push data once connected - webSocket.onopen = () => { - app.caption.set('Loading...') - } - - webSocket.onclose = (event) => { - app.handleDisconnect() - - // Modify page state to display loading overlay - // Code 1006 denotes "Abnormal closure", most likely from the server or client losing connection - // See https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent - // Treat other codes as active errors (besides connectivity errors) when displaying the message - if (event.code === 1006) { - app.caption.set('Lost connection!') - } else { - app.caption.set('Disconnected due to error.') - } - - // Reset modified DOM structures - document.getElementById('big-graph-mobile-load-request').style.display = 'none' - - // TODO: Reconnect behavior - } - - webSocket.onmessage = (message) => { - const payload = JSON.parse(message.data) - - switch (payload.message) { - case 'init': - app.setPublicConfig(payload.config) - - // Display the main page component - // Called here instead of syncComplete so the DOM can be drawn prior to the graphs being drawn - // Otherwise flot.js will cause visual alignment bugs - app.setPageReady(true) - - // Allow the graphDisplayManager to control whether or not the historical graph is loaded - // Defer to isGraphVisible from the publicConfig to understand if the frontend will ever receive a graph payload - if (app.publicConfig.isGraphVisible) { - if (app.graphDisplayManager.isVisible) { - // Send request as a plain text string to avoid the server needing to parse JSON - // This is mostly to simplify the backend server's need for error handling - webSocket.send('requestHistoryGraph') - } else { - document.getElementById('big-graph-mobile-load-request').style.display = 'block' - } - } - - payload.servers.forEach(app.addServer) - - if (payload.mojangServices) { - app.mojangUpdater.updateStatus(payload.mojangServices) - } - - // Init payload contains all data needed to render the page - // Alert the app it is ready - app.handleSyncComplete() - - break - - case 'updateServer': { - // The backend may send "update" events prior to receiving all "add" events - // A server has only been added once it's ServerRegistration is defined - // Checking undefined protects from this race condition - const serverRegistration = app.serverRegistry.getServerRegistration(payload.serverId) - - if (serverRegistration) { - serverRegistration.updateServerStatus(payload, false, app.publicConfig.minecraftVersions) - } - - // Use update payloads to conditionally append data to graph - // Skip any incoming updates if the graph is disabled - if (payload.updateHistoryGraph && app.graphDisplayManager.isVisible) { - // Update may not be successful, safely append 0 points - const playerCount = payload.result ? payload.result.players.online : 0 - - app.graphDisplayManager.addGraphPoint(serverRegistration.serverId, payload.timestamp, playerCount) - - // Only redraw the graph if not mutating hidden data - if (serverRegistration.isVisible) { - app.graphDisplayManager.requestRedraw() - } - } - break - } - - case 'updateMojangServices': { - app.mojangUpdater.updateStatus(payload) - break - } - - case 'historyGraph': { - // Consider the graph visible since a payload has been received - // This is used for the manual graph load request behavior - app.graphDisplayManager.isVisible = true - - app.graphDisplayManager.buildPlotInstance(payload.graphData) - - // Build checkbox elements for graph controls - let lastRowCounter = 0 - let controlsHTML = '' - - app.serverRegistry.getServerRegistrations() - .map(serverRegistration => serverRegistration.data.name) - .sort() - .forEach(serverName => { - const serverRegistration = app.serverRegistry.getServerRegistration(serverName) - - controlsHTML += '' + - '' + - ' ' + serverName + - '' - - // Occasionally break table rows using a magic number - if (++lastRowCounter % 6 === 0) { - controlsHTML += '' - } - }) - - // Apply generated HTML and show controls - document.getElementById('big-graph-checkboxes').innerHTML = '' + - controlsHTML + - '
' - - document.getElementById('big-graph-controls').style.display = 'block' - - // Bind click event for updating graph data - app.graphDisplayManager.initEventListeners() - break - } - } - } +document.addEventListener('DOMContentLoaded', () => { + app.init() window.addEventListener('resize', function () { app.percentageBar.redraw() @@ -147,9 +14,7 @@ document.addEventListener('DOMContentLoaded', function () { document.getElementById('big-graph-mobile-load-request-button').addEventListener('click', function () { // Send a graph data request to the backend - // Send request as a plain text string to avoid the server needing to parse JSON - // This is mostly to simplify the backend server's need for error handling - webSocket.send('requestHistoryGraph') + app.socketManager.sendHistoryGraphRequest() // Hide the activation link to avoid multiple requests document.getElementById('big-graph-mobile-load-request').style.display = 'none' diff --git a/assets/js/socket.js b/assets/js/socket.js new file mode 100644 index 0000000..fd0d6de --- /dev/null +++ b/assets/js/socket.js @@ -0,0 +1,191 @@ +export class SocketManager { + constructor (app) { + this._app = app + this._hasRequestedHistoryGraph = false + this._reconnectDelayBase = 0 + } + + reset () { + this._hasRequestedHistoryGraph = false + } + + createWebSocket () { + this._webSocket = new WebSocket('ws://' + location.host) + + // The backend will automatically push data once connected + this._webSocket.onopen = () => { + this._app.caption.set('Loading...') + + // Reset reconnection scheduling since the WebSocket has been established + this._reconnectDelayBase = 0 + } + + this._webSocket.onclose = (event) => { + this._app.handleDisconnect() + + // Modify page state to display loading overlay + // Code 1006 denotes "Abnormal closure", most likely from the server or client losing connection + // See https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent + // Treat other codes as active errors (besides connectivity errors) when displaying the message + if (event.code === 1006) { + this._app.caption.set('Lost connection!') + } else { + this._app.caption.set('Disconnected due to error.') + } + + // Reset modified DOM structures + document.getElementById('big-graph-mobile-load-request').style.display = 'none' + + // Schedule socket reconnection attempt + this.scheduleReconnect() + } + + this._webSocket.onmessage = (message) => { + const payload = JSON.parse(message.data) + + switch (payload.message) { + case 'init': + this._app.setPublicConfig(payload.config) + + // Display the main page component + // Called here instead of syncComplete so the DOM can be drawn prior to the graphs being drawn + // Otherwise flot.js will cause visual alignment bugs + this._app.setPageReady(true) + + // Allow the graphDisplayManager to control whether or not the historical graph is loaded + // Defer to isGraphVisible from the publicConfig to understand if the frontend will ever receive a graph payload + if (this._app.publicConfig.isGraphVisible) { + if (this._app.graphDisplayManager.isVisible) { + this.sendHistoryGraphRequest() + } else { + document.getElementById('big-graph-mobile-load-request').style.display = 'block' + } + } + + payload.servers.forEach(this._app.addServer) + + if (payload.mojangServices) { + this._app.mojangUpdater.updateStatus(payload.mojangServices) + } + + // Init payload contains all data needed to render the page + // Alert the app it is ready + this._app.handleSyncComplete() + + break + + case 'updateServer': { + // The backend may send "update" events prior to receiving all "add" events + // A server has only been added once it's ServerRegistration is defined + // Checking undefined protects from this race condition + const serverRegistration = this._app.serverRegistry.getServerRegistration(payload.serverId) + + if (serverRegistration) { + serverRegistration.updateServerStatus(payload, false, this._app.publicConfig.minecraftVersions) + } + + // Use update payloads to conditionally append data to graph + // Skip any incoming updates if the graph is disabled + if (payload.updateHistoryGraph && this._app.graphDisplayManager.isVisible) { + // Update may not be successful, safely append 0 points + const playerCount = payload.result ? payload.result.players.online : 0 + + this._app.graphDisplayManager.addGraphPoint(serverRegistration.serverId, payload.timestamp, playerCount) + + // Only redraw the graph if not mutating hidden data + if (serverRegistration.isVisible) { + this._app.graphDisplayManager.requestRedraw() + } + } + break + } + + case 'updateMojangServices': { + this._app.mojangUpdater.updateStatus(payload) + break + } + + case 'historyGraph': { + // Consider the graph visible since a payload has been received + // This is used for the manual graph load request behavior + this._app.graphDisplayManager.isVisible = true + + this._app.graphDisplayManager.buildPlotInstance(payload.graphData) + + // Build checkbox elements for graph controls + let lastRowCounter = 0 + let controlsHTML = '' + + this._app.serverRegistry.getServerRegistrations() + .map(serverRegistration => serverRegistration.data.name) + .sort() + .forEach(serverName => { + const serverRegistration = this._app.serverRegistry.getServerRegistration(serverName) + + controlsHTML += '' + + '' + + ' ' + serverName + + '' + + // Occasionally break table rows using a magic number + if (++lastRowCounter % 6 === 0) { + controlsHTML += '' + } + }) + + // Apply generated HTML and show controls + document.getElementById('big-graph-checkboxes').innerHTML = '' + + controlsHTML + + '
' + + document.getElementById('big-graph-controls').style.display = 'block' + + // Bind click event for updating graph data + this._app.graphDisplayManager.initEventListeners() + break + } + } + } + } + + scheduleReconnect () { + // Release any active WebSocket references + this._webSocket = undefined + + this._reconnectDelayBase++ + + // Exponential backoff for reconnection attempts + // Clamp ceiling value to 30 seconds + this._reconnectDelaySeconds = Math.min((this._reconnectDelayBase * this._reconnectDelayBase), 30) + + const reconnectInterval = setInterval(() => { + this._reconnectDelaySeconds-- + + if (this._reconnectDelaySeconds === 0) { + // Explicitly clear interval, this avoids race conditions + // #clearInterval first to avoid potential errors causing pre-mature returns + clearInterval(reconnectInterval) + + // Update displayed text + this._app.caption.set('Reconnecting...') + + // Attempt reconnection + // Only attempt when reconnectDelaySeconds === 0 and not <= 0, otherwise multiple attempts may be started + this.createWebSocket() + } else if (this._reconnectDelaySeconds > 0) { + // Update displayed text + this._app.caption.set('Reconnecting in ' + this._reconnectDelaySeconds + 's...') + } + }, 1000) + } + + sendHistoryGraphRequest () { + if (!this._hasRequestedHistoryGraph) { + this._hasRequestedHistoryGraph = true + + // Send request as a plain text string to avoid the server needing to parse JSON + // This is mostly to simplify the backend server's need for error handling + this._webSocket.send('requestHistoryGraph') + } + } +} From 84e38c90def15a0578fcb7c3a50463577832a106 Mon Sep 17 00:00:00 2001 From: Nick Krecklow Date: Tue, 5 May 2020 17:20:52 -0500 Subject: [PATCH 3/3] release 5.3.0 --- config.json | 2 +- docs/CHANGELOG.md | 5 + package-lock.json | 270 ++-------------------------------------------- package.json | 2 +- 4 files changed, 17 insertions(+), 262 deletions(-) diff --git a/config.json b/config.json index 0a11b03..3fd9359 100644 --- a/config.json +++ b/config.json @@ -9,6 +9,6 @@ "pingAll": 3000, "connectTimeout": 2500 }, - "logToDatabase": true, + "logToDatabase": false, "graphDuration": 86400000 } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 71f50be..08be340 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,3 +1,8 @@ +**5.3.0** *(May 5 2020)* +- Replaces socket.io library (and dependency) with vanilla WebSockets. +- Frontend reconnect behavior has been improved to use exponential backoff behavior (up to 30 seconds) with unlimited retries. +- The "Lost connection!" message will now show the reconnection attempt timer. + **5.2.1** *(May 1 2020)* - The historical graph will now auto scale its yaxis steps. diff --git a/package-lock.json b/package-lock.json index 4522922..31d4acf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "minetrack", - "version": "5.2.1", + "version": "5.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1132,15 +1132,6 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, - "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - } - }, "acorn": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", @@ -1177,11 +1168,6 @@ "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", "dev": true }, - "after": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" - }, "ajv": { "version": "6.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", @@ -1334,11 +1320,6 @@ "es-abstract": "^1.17.0-next.1" } }, - "arraybuffer.slice": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" - }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -1422,7 +1403,8 @@ "async-limiter": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true }, "asynckit": { "version": "0.4.0", @@ -1517,11 +1499,6 @@ "lodash.clone": "^4.5.0" } }, - "backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" - }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -1582,22 +1559,12 @@ } } }, - "base64-arraybuffer": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", - "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" - }, "base64-js": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", "dev": true }, - "base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" - }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -1606,14 +1573,6 @@ "tweetnacl": "^0.14.3" } }, - "better-assert": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", - "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", - "requires": { - "callsite": "1.0.0" - } - }, "binary-extensions": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", @@ -1629,11 +1588,6 @@ "file-uri-to-path": "1.0.0" } }, - "blob": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", - "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" - }, "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", @@ -1914,11 +1868,6 @@ "caller-callsite": "^2.0.0" } }, - "callsite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", - "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" - }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2216,20 +2165,11 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, - "component-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" - }, "component-emitter": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" - }, - "component-inherit": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", - "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true }, "concat-map": { "version": "0.0.1", @@ -2288,11 +2228,6 @@ } } }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" - }, "copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", @@ -2744,6 +2679,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, "requires": { "ms": "^2.1.1" } @@ -3033,59 +2969,6 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, - "engine.io": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.4.0.tgz", - "integrity": "sha512-XCyYVWzcHnK5cMz7G4VTu2W7zJS7SM1QkcelghyIk/FmobWBtXE7fwhBusEKvCSqc3bMh8fNFMlUkCKTFRxH2w==", - "requires": { - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "0.3.1", - "debug": "~4.1.0", - "engine.io-parser": "~2.2.0", - "ws": "^7.1.2" - } - }, - "engine.io-client": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.0.tgz", - "integrity": "sha512-a4J5QO2k99CM2a0b12IznnyQndoEvtA4UAldhGzKqnHf42I3Qs2W5SPnDvatZRcMaNZs4IevVicBPayxYt6FwA==", - "requires": { - "component-emitter": "1.2.1", - "component-inherit": "0.0.3", - "debug": "~4.1.0", - "engine.io-parser": "~2.2.0", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "ws": "~6.1.0", - "xmlhttprequest-ssl": "~1.5.4", - "yeast": "0.1.2" - }, - "dependencies": { - "ws": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", - "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", - "requires": { - "async-limiter": "~1.0.0" - } - } - } - }, - "engine.io-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.0.tgz", - "integrity": "sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w==", - "requires": { - "after": "0.8.2", - "arraybuffer.slice": "~0.0.7", - "base64-arraybuffer": "0.1.5", - "blob": "0.0.5", - "has-binary2": "~1.0.2" - } - }, "entities": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", @@ -4641,19 +4524,6 @@ "ansi-regex": "^2.0.0" } }, - "has-binary2": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", - "requires": { - "isarray": "2.0.1" - } - }, - "has-cors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" - }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -4935,11 +4805,6 @@ "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", "dev": true }, - "indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" - }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -5335,7 +5200,8 @@ "isarray": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", + "dev": true }, "isexe": { "version": "2.0.0", @@ -5799,7 +5665,8 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "mute-stream": { "version": "0.0.8", @@ -5862,11 +5729,6 @@ } } }, - "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" - }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -6056,11 +5918,6 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A=" }, - "object-component": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", - "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" - }, "object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -6446,22 +6303,6 @@ "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", "dev": true }, - "parseqs": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", - "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", - "requires": { - "better-assert": "~1.0.0" - } - }, - "parseuri": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", - "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", - "requires": { - "better-assert": "~1.0.0" - } - }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -7879,82 +7720,6 @@ } } }, - "socket.io": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz", - "integrity": "sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg==", - "requires": { - "debug": "~4.1.0", - "engine.io": "~3.4.0", - "has-binary2": "~1.0.2", - "socket.io-adapter": "~1.1.0", - "socket.io-client": "2.3.0", - "socket.io-parser": "~3.4.0" - } - }, - "socket.io-adapter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", - "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==" - }, - "socket.io-client": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz", - "integrity": "sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==", - "requires": { - "backo2": "1.0.2", - "base64-arraybuffer": "0.1.5", - "component-bind": "1.0.0", - "component-emitter": "1.2.1", - "debug": "~4.1.0", - "engine.io-client": "~3.4.0", - "has-binary2": "~1.0.2", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "object-component": "0.0.3", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "socket.io-parser": "~3.3.0", - "to-array": "0.1.4" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "socket.io-parser": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz", - "integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==", - "requires": { - "component-emitter": "1.2.1", - "debug": "~3.1.0", - "isarray": "2.0.1" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - } - } - } - } - }, - "socket.io-parser": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.0.tgz", - "integrity": "sha512-/G/VOI+3DBp0+DJKW4KesGnQkQPFmUCbA/oO2QGT6CWxU7hLGWqU3tyuzeSK/dqcyeHsQg1vTe9jiZI8GU9SCQ==", - "requires": { - "component-emitter": "1.2.1", - "debug": "~4.1.0", - "isarray": "2.0.1" - } - }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -8481,11 +8246,6 @@ "os-tmpdir": "~1.0.2" } }, - "to-array": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", - "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" - }, "to-arraybuffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", @@ -9081,11 +8841,6 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, - "xmlhttprequest-ssl": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", - "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" - }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -9214,11 +8969,6 @@ "camelcase": "^5.0.0", "decamelize": "^1.2.0" } - }, - "yeast": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" } } } diff --git a/package.json b/package.json index 0c51944..07005f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minetrack", - "version": "5.2.1", + "version": "5.3.0", "description": "A Minecraft server tracker that lets you focus on the basics.", "main": "main.js", "dependencies": {