Files
DRB-CnC/Client/controllers/clientController.js
Logan Cusano f5d58d45da Semi functional client webUI
- can update client info for sure
- Working on notifications now
2023-07-22 03:27:39 -04:00

257 lines
10 KiB
JavaScript

// Debug
const { DebugBuilder } = require("../utilities/debugBuilder.js");
const log = new DebugBuilder("client", "clientController");
// Configs
require('dotenv').config();
const modes = require("../config/modes");
// Modules
const { executeAsyncConsoleCommand, BufferToJson, nodeObject } = require("../utilities/utilities");
// Utilities
const { getFullConfig } = require("../utilities/configHandler");
const { updateId, updateConfig, updateClientConfig } = require("../utilities/updateConfig");
const { updatePreset, addNewPreset, getPresets, removePreset } = require("../utilities/updatePresets");
const { onHttpError, requestOptions, sendHttpRequest } = require("../utilities/httpRequests");
var runningClientConfig = getFullConfig()
/**
* Check the body for the required fields to update or add a preset
* @param req Express req from the endpoint controller
* @param res Express res from the endpoint controller
* @param callback The callback function to call when this function completes
* @returns {*}
*/
function checkBodyForPresetFields(req, res, callback) {
if (!req.body?.systemName) return res.status(403).json({ "message": "No system in the request" });
if (!req.body?.frequencies && Array.isArray(req.body.frequencies)) return res.status(403).json({ "message": "No frequencies in the request or type is not an array" });
if (!req.body?.mode && typeof req.body.mode === "string") return res.status(403).json({ "message": "No mode in the request" });
if (!req.body?.trunkFile) {
if (modes.digitalModes.includes(req.body.mode)) return res.status(403).json({ "message": "No trunk file in the request but digital mode specified. If you are not using a trunk file for this frequency make sure to specify 'none' for trunk file in the request" })
// If there is a value keep it but if not, add nothing so the system can update that key (if needed)
req.body.trunkFile = req.body.trunkFile ?? "none";
}
return callback();
}
async function checkLocalIP() {
if (process.platform === "win32") {
// Windows
var networkConfig = await executeAsyncConsoleCommand("ipconfig");
log.DEBUG('Network Config: ', networkConfig);
var networkConfigLines = await networkConfig.split("\n").filter(line => {
if (!line.includes(":")) return false;
line = line.split(":");
if (!line.length === 2) return false;
return true;
}).map(line => {
line = String(line).split(':', 2);
line[0] = String(line[0]).replace(/[.]|[\s]/g, "").trim();
line[1] = String(line[1]).replace(/(\\r|\\n)/g, "").trim();
return line;
});
networkConfig = Object.fromEntries(networkConfigLines);
log.DEBUG("Parsed IP Config Results: ", networkConfig);
log.DEBUG("Local IP address: ", networkConfig['IPv4Address']);
return networkConfig['IPv4Address'];
}
else {
// Linux
var networkConfig = await executeAsyncConsoleCommand("ip addr");
}
}
/**
* Checks the config file for all required fields or gets and updates the required fields
*/
exports.checkConfig = async function checkConfig() {
if (!runningClientConfig.id || runningClientConfig.id == 0 || runningClientConfig.id == '0') {
await updateId(0);
runningClientConfig.id = 0;
}
if (!runningClientConfig.ip) {
const ipAddr = await checkLocalIP();
await updateConfig('CLIENT_IP', ipAddr);
runningClientConfig.ip = ipAddr;
}
if (!runningClientConfig.name) {
const lastOctet = await String(checkLocalIP()).spit('.')[-1];
const name = `Radio-Node-${lastOctet}`;
await updateConfig('CLIENT_NAME', name);
runningClientConfig.name = name;
}
if (!runningClientConfig.port) {
const port = 3010;
await updateConfig('CLIENT_PORT', port);
runningClientConfig.port = port;
}
}
/** Check in with the server
* If the bot has a saved ID, check in with the server to get any updated information or just check back in
* If the bot does not have a saved ID, it will attempt to request a new ID from the server
*
* @param {boolean} update If set to true, the client will update the server to it's config, instead of taking the server's config
*/
exports.checkIn = async (update = false) => {
let reqOptions;
await this.checkConfig();
// Check if there is an ID found, if not add the node to the server. If there was an ID, check in with the server to make sure it has the correct information
try {
if (!runningClientConfig?.id || runningClientConfig.id == 0) {
// ID was not found in the config, creating a new node
reqOptions = new requestOptions("/nodes/newNode", "POST");
sendHttpRequest(reqOptions, JSON.stringify(runningClientConfig), async (responseObject) => {
// Check if the server responded
if (!responseObject) {
log.WARN("Server did not respond to checkIn. Will wait 60 seconds then try again");
setTimeout(() => {
// Run itself again to see if the server is up now
this.checkIn();
}, 60000);
return
}
// Update the client's ID if the server accepted its
if (responseObject.statusCode === 202) {
runningClientConfig.id = responseObject.body.nodeId;
log.DEBUG("Response object from new node: ", responseObject, runningClientConfig);
await updateId(runningClientConfig.id);
}
if (responseObject.statusCode >= 300) {
// Server threw an error
log.DEBUG("HTTP Error: ", responseObject, await BufferToJson(responseObject.body));
await onHttpError(responseObject.statusCode);
}
});
}
else {
// ID is in the config, checking in with the server
if (update) reqOptions = new requestOptions(`/nodes/${runningClientConfig.id}`, "PUT");
else reqOptions = new requestOptions(`/nodes/nodeCheckIn/${runningClientConfig.id}`, "POST");
sendHttpRequest(reqOptions, JSON.stringify(runningClientConfig), (responseObject) => {
log.DEBUG("Check In Respose: ", responseObject);
// Check if the server responded
if (!responseObject) {
log.WARN("Server did not respond to checkIn. Will wait 60 seconds then try again");
setTimeout(() => {
// Run itself again to see if the server is up now
this.checkIn();
}, 60000);
return
}
if (responseObject.statusCode === 202) {
log.DEBUG("Updated keys: ", responseObject.body.updatedKeys)
// Server accepted an update
}
if (responseObject.statusCode === 200) {
// Server accepted the response but there were no keys to be updated
if (!update){
const tempUpdatedConfig = updateClientConfig(responseObject.body);
if (!tempUpdatedConfig.length > 0) return;
}
}
if (responseObject.statusCode >= 300) {
// Server threw an error
onHttpError(responseObject.statusCode);
}
});
}
}
catch (err) {
log.ERROR("Error checking in: ", err);
}
}
/** Controller for the /client/requestCheckIn endpoint
* This is the endpoint wrapper to queue a check in
*/
exports.requestCheckIn = async (req, res) => {
this.checkIn();
return res.sendStatus(200);
}
/**
* Express JS Wrapper for checking and updating client config
* @param {*} req
* @param {*} res
* @returns
*/
exports.updateClientConfigWrapper = async (req, res) => {
// Convert the online status to a boolean to be worked with
log.DEBUG("REQ Body: ", req.body);
const updatedKeys = await updateClientConfig(req.body);
if (updatedKeys) {
log.DEBUG("Keys have been updated, updating running config and checking in with the server: ", updatedKeys);
runningClientConfig = await getFullConfig();
await this.checkIn(true);
}
res.status(200).json(updatedKeys);
}
/** Controller for the /client/presets endpoint
* This is the endpoint wrapper to get the presets object
*/
exports.getPresets = async (req, res) => {
runningClientConfig.nearbySystems = getPresets();
return res.status(200).json(runningClientConfig.nearbySystems);
}
/** Controller for the /client/updatePreset endpoint
* This is the endpoint wrapper to update the selected preset (must include the whole object for that preset otherwise it will be rejected)
*/
exports.updatePreset = async (req, res) => {
checkBodyForPresetFields(req, res, () => {
updatePreset(req.body.systemName, () => {
runningClientConfig.nearbySystems = getPresets();
this.checkIn(true);
return res.sendStatus(200);
}, { frequencies: req.body.frequencies, mode: req.body.mode, trunkFile: req.body.trunkFile });
})
}
/**
* Adds a new preset to the client
*/
exports.addNewPreset = async (req, res) => {
checkBodyForPresetFields(req, res, () => {
addNewPreset(req.body.systemName, req.body.frequencies, req.body.mode, () => {
runningClientConfig.nearbySystems = getPresets();
this.checkIn(true);
return res.sendStatus(200);
}, req.body.trunkFile);
});
}
/**
* Removes a preset from the client
*/
exports.removePreset = async (req, res) => {
checkBodyForPresetFields(req, res, () => {
if (!req.body.systemName) return res.status("500").json({ "message": "You must specify a system name to delete, this must match exactly to how the system name is saved." })
removePreset(req.body.systemName, () => {
runningClientConfig.nearbySystems = getPresets();
this.checkIn(true);
return res.sendStatus(200);
}, req.body.trunkFile);
});
}
/**
* Runs the updater service
*/
exports.updateClient = async (req, res) => {
await executeAsyncConsoleCommand("systemctl start RadioNodeUpdater.service");
return res.sendStatus(200);
}