// Debug const { DebugBuilder } = require("../utilities/debugBuilder.js"); const log = new DebugBuilder("server", "nodesController"); // Utilities const {getAllNodes, addNewNode, updateNodeInfo, getNodeInfoFromId, getOnlineNodes } = require("../utilities/mysqlHandler"); const utils = require("../utilities/utils"); const { sendHttpRequest, requestOptions } = require("../utilities/httpRequests.js"); const { nodeObject } = require("../utilities/recordHelper.js"); const { joinServerWrapper } = require("../commands/join"); const refreshInterval = process.env.NODE_MONITOR_REFRESH_INTERVAL ?? 1200000; /** * * @param {*} req * @param {*} res */ exports.listAllNodes = async (req, res) => { getAllNodes((allNodes) => { res.status(200).json({ "nodes_online": allNodes }); }); } // Add a new node to the storage exports.newNode = async (req, res) => { if (!req.body.name) return res.status(400).json("No name specified for new node"); try { // Try to add the new user with defaults if missing options const newNode = new nodeObject({ _name: req.body.name, _ip: req.body.ip ?? null, _port: req.body.port ?? null, _location: req.body.location ?? null, _nearbySystems: req.body.nearbySystems ?? null, _online: req.body.online ?? 0 }); addNewNode(newNode, (newNodeObject) => { // Send back a success if the user has been added and the ID for the client to keep track of res.status(202).json({"nodeId": newNodeObject.id}); }) } catch (err) { // Catch any errors if (err === "No name provided") { return res.sendStatus(400); } else log.ERROR(err) return res.sendStatus(500); } } // Get the known info for the node specified exports.getNodeInfo = async (req, res) => { if (!req.query.id) return res.status(400).json("No id specified"); getNodeInfoFromId(req.query.id, (nodeInfo) => { res.status(200).json(nodeInfo); }) } // Updates the information received from the client based on ID exports.nodeCheckIn = async (req, res) => { if (!req.body.id) return res.status(400).json("No id specified"); getNodeInfoFromId(req.body.id, (nodeInfo) => { let checkInObject = new nodeObject({}); // Convert the online status to a boolean to be worked with nodeInfo.online = nodeInfo.online !== 0; var isObjectUpdated = false; if (req.body.name && req.body.name != nodeInfo.name) { checkInObject.name = req.body.name; isObjectUpdated = true; } if (req.body.ip && req.body.ip != nodeInfo.ip) { checkInObject.ip = req.body.ip; isObjectUpdated = true; } if (req.body.port && req.body.port != nodeInfo.port) { checkInObject.port = req.body.port; isObjectUpdated = true; } if (req.body.location && req.body.location != nodeInfo.location) { checkInObject.location = req.body.location; isObjectUpdated = true; } if (req.body.nearbySystems && JSON.stringify(req.body.nearbySystems) !== JSON.stringify(nodeInfo.nearbySystems)) { checkInObject.nearbySystems = req.body.nearbySystems; isObjectUpdated = true; } if (req.body.online && (req.body.online === "true") != nodeInfo.online) { checkInObject.online = (req.body.online === "true"); isObjectUpdated = true; } // If no changes are made tell the client if (!isObjectUpdated) return res.status(200).json("No keys updated"); log.INFO("Updating the following keys for ID: ", req.body.id, checkInObject); // Adding the ID key to the body so that the client can double-check their ID checkInObject.id = nodeInfo.id; updateNodeInfo(checkInObject, () => { return res.status(202).json({"updatedKeys": checkInObject}); }) }) } /** * Request the node to join the specified server/channel and listen to the specified resource * * @param req.body.clientId The client ID to join discord with (NOT dev portal ID, right click user -> Copy ID) * @param req.body.channelId The Channel ID to join in Discord * @param req.body.presetName The preset name to listen to in Discord */ exports.requestNodeJoinServer = async (req, res) => { if (!req.body.clientId || !req.body.channelId || !req.body.presetName) return res.status(400).json("Missing information in request, requires clientId, channelId, presetName"); await joinServerWrapper(req.body.presetName, req.body.channelId, req.body.clientId) } /** * The node monitor service, this will periodically check in on the online nodes to make sure they are still online */ exports.nodeMonitorService = class nodeMonitorService { constructor() { this.log = new DebugBuilder("server", "nodeMonitorService"); } async start(){ // Wait for the a portion of the refresh period before checking in with the nodes, so the rest of the bot can start await new Promise(resolve => setTimeout(resolve, refreshInterval/10)); log.INFO("Starting Node Monitor Service"); // Check in before starting the infinite loop await this.checkInWithOnlineNodes(); while(true){ // Wait for the refresh interval, then wait for the posts to return, then wait a quarter of the refresh interval to make sure everything is cleared up await new Promise(resolve => setTimeout(resolve, refreshInterval)); await this.checkInWithOnlineNodes(); await new Promise(resolve => setTimeout(resolve, refreshInterval / 4)); continue; } } async checkInWithOnlineNodes(){ getOnlineNodes((nodes) => { this.log.DEBUG("Online Nodes: ", nodes); for (const node of nodes) { const reqOptions = new requestOptions("/client/requestCheckIn", "GET", node.ip, node.port) sendHttpRequest(reqOptions, "", (responseObj) => { if (responseObj) { this.log.DEBUG("Response from: ", node.name, responseObj); } else { this.log.DEBUG("No response from node, assuming it's offline"); const offlineNode = new nodeObject({ _online: 0, _id: node.id }); this.log.DEBUG("Offline node update object: ", offlineNode); updateNodeInfo(offlineNode, (sqlResponse) => { if (!sqlResponse) this.log.ERROR("No response from SQL object"); this.log.DEBUG("Updated offline node: ", sqlResponse); }) } }) } return; }); } }