/*Canopy - The next generation of stoner streaming software Copyright (C) 2024-2025 Rainbownapkin and the TTN Community This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see .*/ //Node const fs = require('node:fs/promises'); //Config const config = require('../../config.json'); /** * Creates and returns a custom exception, tagged as a 'custom' exception, using the 'custom' boolean property. * This is used to denote that this error was generated on purpose, with a human readable message, that can be securely sent to the client. * Unexpected exceptions should only be logged internally, however, as they may contain sensitive data. * * @param {String} msg - Error message to send the client * @param {String} type - Error type to send back to the client * @returns {Error} The exception to smith */ module.exports.exceptionSmith = function(msg, type){ //Create the new error with the given message const exception = new Error(msg); //Set the error type exception.type = type; //Mark the error as a custom error exception.custom = true; //Return the error return exception; } /** * Main error handling function * @param {Express.Response} res - Response being sent out to the client who caused the issue * @param {String} msg - Error message to send the client * @param {String} type - Error type to send back to the client * @param {Number} status - HTTP(s) Status Code to send back to the client * @returns {Express.Response} If we have a usable Express Response object, return it back after it's been cashed */ module.exports.errorHandler = function(res, msg, type = "Generic", status = 400){ //Some controllers do things after sending headers, for those, we should remain silent if(!res.headersSent){ res.status(status); return res.send({errors: [{type, msg, date: new Date()}]}); } } /** * Handles local exceptions which where not directly created by user interaction * @param {Error} err - Exception to handle */ module.exports.localExceptionHandler = function(err){ //If we're being verbose if(config.verbose){ //Log the error module.exports.dumpError(err); } } /** * Handles exceptions which where directly the fault of user action >:( * @param {Express.Response} res - Express Response object to bitch at * @param {Error} err - Error created by the jerk in question */ module.exports.exceptionHandler = function(res, err){ //If this is a self-made problem if(err.custom){ module.exports.errorHandler(res, err.message, err.type); }else{ //Locally handle the exception module.exports.localExceptionHandler(err); //if not yell at the browser for fucking up, and tell it what it did wrong. module.exports.errorHandler(res, "An unexpected server crash was just prevented. You should probably report this to an admin.", "Caught Exception"); } } /** * Basic error-handling for socket.io so we don't just silently swallow errors. * @param {Socket} socket - Socket error originated from * @param {String} msg - Error message to send the client * @param {String} type - Error type to send back to the client * @returns {Boolean} - Passthrough from socket.emit */ module.exports.socketErrorHandler = function(socket, msg, type = "Generic"){ return socket.emit("error", {errors: [{type, msg, date: new Date()}]}); } /** * Generates error messages for simple errors generated by socket.io interaction * @param {Socket} socket - Socket error originated from * @param {Error} err - Error created by the jerk in question * @returns {Boolean} - Passthrough from socket.emit */ module.exports.socketExceptionHandler = function(socket, err){ //If this is a self made problem if(err.custom){ //yell at the browser for fucking up, and tell it what it did wrong. return module.exports.socketErrorHandler(socket, err.message, err.type); }else{ //Locally handle the exception module.exports.localExceptionHandler(err); //if not yell at the browser for fucking up return module.exports.socketErrorHandler(socket, "An unexpected server crash was just prevented. You should probably report this to an admin.", "Server"); } } /** * Generates error messages and drops connection for critical errors caused by socket.io interaction * @param {Socket} socket - Socket error originated from * @param {Error} err - Error created by the jerk in question * @returns {Boolean} - Passthrough from socket.disconnect */ module.exports.socketCriticalExceptionHandler = function(socket, err){ //If this is a self made problem if(err.custom){ //yell at the browser for fucking up, and tell it what it did wrong. socket.emit("kick", {type: "Disconnected", reason: `Server Error: ${err.message}`}); }else{ //Locally handle the exception module.exports.localExceptionHandler(err); //yell at the browser for fucking up socket.emit("kick", {type: "Disconnected", reason: "An unexpected server crash was just prevented. You should probably report this to an admin."}); } return socket.disconnect(); } /** * Prints warning text to server console * @param {String} string - String to print to console */ module.exports.consoleWarn = function(string){ console.warn('\x1b[31m\x1b[4m%s\x1b[0m',string); } /** * Basic error-handling middleware to ensure we're not dumping stack traces to the client, as that would be insecure * @param {Error} err - Error to handle * @param {Express.Request} req - Express Request * @param {Express.Response} res - Express Response * @param {Function} next - Next function in the Express middleware chain (Not that it's getting called XP) */ module.exports.errorMiddleware = function(err, req, res, next){ //Set generic error var reason = "Server Error"; //If it's un-authorized if(err.status == 403){ reason = "Unauthorized" } module.exports.errorHandler(res, err.message, reason, err.status); } /** * Dumps unexpected server crashes to dedicated log files * @param {Error} err - error to dump to file * @param {Date} date - Date of error, defaults to now */ module.exports.dumpError = async function(err, date = new Date()){ try{ //Crash directory const dir = "./log/crash/" //Double check crash folder exists try{ await fs.stat(dir); //If we caught an error (most likely it's missing) }catch(err){ //Shout about it module.exports.consoleWarn("Log folder missing, mking dir!") //Make it if doesn't await fs.mkdir(dir, {recursive: true}); } //Assemble log file path const path = `${dir}${date.getTime()}.log`; //Generate error file content const content = `Error Date: ${date.toLocaleString()} (UTC-${date.getTimezoneOffset()/60})\nError Type: ${err.name}\nError Msg:${err.message}\nStack Trace:\n\n${err.stack}`; //Write content to file fs.writeFile(path, content); //Whine about the error module.exports.consoleWarn(`Warning: Unexpected Server Crash gracefully dumped to '${path}'... SOMETHING MAY BE VERY BROKEN!!!!`); //If somethine went really really wrong }catch(doubleErr){ //Use humor to cope with the pain module.exports.consoleWarn("Yo Dawg, I herd you like errors, so I put an error in your error dump, so you can dump while you dump:"); //Dump the original error to console module.exports.consoleWarn(err); //Dump the error we had saving that error to file to console module.exports.consoleWarn(doubleErr); } } module.exports.welcomeWagon = function(count, date){ //Inject values into ascii art const art = ` \x1b[32m ! \x1b[0m \x1b[32m 420 \x1b[0m \x1b[32m\x1b[40m${config.instanceName}\x1b[0m\x1b[2m, Powered By:\x1b[0m \x1b[32m 420 \x1b[0m \x1b[32m WEEED \x1b[0m CCCC AAA NN N OOO PPPP Y Y \x1b[32m! WEEED !\x1b[0m C A A NN N O O P P Y Y \x1b[32mWEE EEEEE EED\x1b[0m C A A N N N O O P P Y Y \x1b[32m WEE EEEEE EED\x1b[0m C AAAAA N N N O O PPPP Y \x1b[32m WEE EEE EED\x1b[0m C A A N N N O O P Y \x1b[32m WEE EEE EED\x1b[0m C A A N NN O O P Y \x1b[32m WEEEEED\x1b[0m CCCC A A N NN OOO P Y \x1b[32m WEEE ! EEED\x1b[0m \x1b[32m !\x1b[0m \x1b[34mInitialization Complete!\x1b[0m This server has booted \x1b[4m${count}\x1b[0m time${count == 1 ? '' : 's'}. \x1b[32m !\x1b[0m This server was first booted on \x1b[4m${date}\x1b[0m.` //Dump art to console console.log(art); //Add some extra padding for the port printout from server.js process.stdout.write(' '); }