Add JSDoc to server/*

Signed-off-by: Matthew Nickson <mnickson@sidingsmedia.com>
This commit is contained in:
Matthew Nickson 2022-04-20 19:56:40 +01:00
parent 45f44b183d
commit 03b2d8d521
No known key found for this signature in database
GPG key ID: BF229DCFD4748E05
14 changed files with 380 additions and 60 deletions

View file

@ -1,8 +1,12 @@
const { checkLogin } = require("./util-server");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
class TwoFA { class TwoFA {
/**
* Disable 2FA for specified user
* @param {number} userID ID of user to disable
* @returns {Promise<void>}
*/
static async disable2FA(userID) { static async disable2FA(userID) {
return await R.exec("UPDATE `user` SET twofa_status = 0 WHERE id = ? ", [ return await R.exec("UPDATE `user` SET twofa_status = 0 WHERE id = ? ", [
userID, userID,

View file

@ -6,10 +6,10 @@ const { debug } = require("../src/util");
const { loginRateLimiter } = require("./rate-limiter"); const { loginRateLimiter } = require("./rate-limiter");
/** /**
* * Login to web app
* @param username : string * @param {string} username
* @param password : string * @param {string} password
* @returns {Promise<Bean|null>} * @returns {Promise<(Bean|null)>}
*/ */
exports.login = async function (username, password) { exports.login = async function (username, password) {
let user = await R.findOne("user", " username = ? AND active = 1 ", [ let user = await R.findOne("user", " username = ? AND active = 1 ", [
@ -30,6 +30,19 @@ exports.login = async function (username, password) {
return null; return null;
}; };
/**
* Callback for myAuthorizer
* @callback myAuthorizerCB
* @param {any} err Any error encountered
* @param {boolean} authorized Is the client authorized?
*/
/**
* Custom authorizer for express-basic-auth
* @param {string} username
* @param {string} password
* @param {myAuthorizerCB} callback
*/
function myAuthorizer(username, password, callback) { function myAuthorizer(username, password, callback) {
setting("disableAuth").then((result) => { setting("disableAuth").then((result) => {
if (result) { if (result) {

View file

@ -6,6 +6,7 @@ exports.latestVersion = null;
let interval; let interval;
/** Start 48 hour check interval */
exports.startInterval = () => { exports.startInterval = () => {
let check = async () => { let check = async () => {
try { try {
@ -28,6 +29,11 @@ exports.startInterval = () => {
interval = setInterval(check, 3600 * 1000 * 48); interval = setInterval(check, 3600 * 1000 * 48);
}; };
/**
* Enable the check update feature
* @param {boolean} value Should the check update feature be enabled?
* @returns {Promise<void>}
*/
exports.enableCheckUpdate = async (value) => { exports.enableCheckUpdate = async (value) => {
await setSetting("checkUpdate", value); await setSetting("checkUpdate", value);

View file

@ -7,6 +7,11 @@ const { io } = require("./server");
const { setting } = require("./util-server"); const { setting } = require("./util-server");
const checkVersion = require("./check-version"); const checkVersion = require("./check-version");
/**
* Send list of notification providers to client
* @param {Socket} socket Socket.io socket instance
* @returns {Promise<Bean[]>}
*/
async function sendNotificationList(socket) { async function sendNotificationList(socket) {
const timeLogger = new TimeLogger(); const timeLogger = new TimeLogger();
@ -28,8 +33,11 @@ async function sendNotificationList(socket) {
/** /**
* Send Heartbeat History list to socket * Send Heartbeat History list to socket
* @param toUser True = send to all browsers with the same user id, False = send to the current browser only * @param {Socket} socket Socket.io instance
* @param overwrite Overwrite client-side's heartbeat list * @param {number} monitorID ID of monitor to send heartbeat history
* @param {boolean} [toUser=false] True = send to all browsers with the same user id, False = send to the current browser only
* @param {boolean} [overwrite=false] Overwrite client-side's heartbeat list
* @returns {Promise<void>}
*/ */
async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite = false) { async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite = false) {
const timeLogger = new TimeLogger(); const timeLogger = new TimeLogger();
@ -56,10 +64,11 @@ async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite =
/** /**
* Important Heart beat list (aka event list) * Important Heart beat list (aka event list)
* @param socket * @param {Socket} socket Socket.io instance
* @param monitorID * @param {number} monitorID ID of monitor to send heartbeat history
* @param toUser True = send to all browsers with the same user id, False = send to the current browser only * @param {boolean} [toUser=false] True = send to all browsers with the same user id, False = send to the current browser only
* @param overwrite Overwrite client-side's heartbeat list * @param {boolean} [overwrite=false] Overwrite client-side's heartbeat list
* @returns {Promise<void>}
*/ */
async function sendImportantHeartbeatList(socket, monitorID, toUser = false, overwrite = false) { async function sendImportantHeartbeatList(socket, monitorID, toUser = false, overwrite = false) {
const timeLogger = new TimeLogger(); const timeLogger = new TimeLogger();
@ -83,6 +92,11 @@ async function sendImportantHeartbeatList(socket, monitorID, toUser = false, ove
} }
/**
* Send application info
* @param {Socket} socket Socket.io socket instance
* @returns {Promise<void>}
*/
async function sendInfo(socket) { async function sendInfo(socket) {
socket.emit("info", { socket.emit("info", {
version: checkVersion.version, version: checkVersion.version,

View file

@ -63,6 +63,10 @@ class Database {
static noReject = true; static noReject = true;
/**
* Initialize the database
* @param {Object} args Arguments to initialize DB with
*/
static init(args) { static init(args) {
// Data Directory (must be end with "/") // Data Directory (must be end with "/")
Database.dataDir = process.env.DATA_DIR || args["data-dir"] || "./data/"; Database.dataDir = process.env.DATA_DIR || args["data-dir"] || "./data/";
@ -80,6 +84,12 @@ class Database {
console.log(`Data Dir: ${Database.dataDir}`); console.log(`Data Dir: ${Database.dataDir}`);
} }
/**
* Connect to the database
* @param {boolean} [testMode=false] Should the connection be
* started in test mode?
* @returns {Promise<void>}
*/
static async connect(testMode = false) { static async connect(testMode = false) {
const acquireConnectionTimeout = 120 * 1000; const acquireConnectionTimeout = 120 * 1000;
@ -129,6 +139,7 @@ class Database {
console.log("SQLite Version: " + await R.getCell("SELECT sqlite_version()")); console.log("SQLite Version: " + await R.getCell("SELECT sqlite_version()"));
} }
/** Patch the database */
static async patch() { static async patch() {
let version = parseInt(await setting("database_version")); let version = parseInt(await setting("database_version"));
@ -173,7 +184,9 @@ class Database {
} }
/** /**
* Patch DB using new process
* Call it from patch() only * Call it from patch() only
* @private
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
static async patch2() { static async patch2() {
@ -212,9 +225,12 @@ class Database {
} }
/** /**
* Patch database using new patching process
* Used it patch2() only * Used it patch2() only
* @private
* @param sqlFilename * @param sqlFilename
* @param databasePatchedFiles * @param databasePatchedFiles
* @returns {Promise<void>}
*/ */
static async patch2Recursion(sqlFilename, databasePatchedFiles) { static async patch2Recursion(sqlFilename, databasePatchedFiles) {
let value = this.patchList[sqlFilename]; let value = this.patchList[sqlFilename];
@ -249,12 +265,12 @@ class Database {
} }
/** /**
* Sadly, multi sql statements is not supported by many sqlite libraries, I have to implement it myself * Load an SQL file and execute it
* @param filename * @param filename Filename of SQL file to import
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
static async importSQLFile(filename) { static async importSQLFile(filename) {
// Sadly, multi sql statements is not supported by many sqlite libraries, I have to implement it myself
await R.getCell("SELECT 1"); await R.getCell("SELECT 1");
let text = fs.readFileSync(filename).toString(); let text = fs.readFileSync(filename).toString();
@ -282,6 +298,10 @@ class Database {
} }
} }
/**
* Aquire a direct connection to database
* @returns {any}
*/
static getBetterSQLite3Database() { static getBetterSQLite3Database() {
return R.knex.client.acquireConnection(); return R.knex.client.acquireConnection();
} }
@ -317,7 +337,7 @@ class Database {
/** /**
* One backup one time in this process. * One backup one time in this process.
* Reset this.backupPath if you want to backup again * Reset this.backupPath if you want to backup again
* @param version * @param {string} version Version code of backup
*/ */
static backup(version) { static backup(version) {
if (! this.backupPath) { if (! this.backupPath) {
@ -339,9 +359,7 @@ class Database {
} }
} }
/** /** Restore from most recent backup */
*
*/
static restore() { static restore() {
if (this.backupPath) { if (this.backupPath) {
console.error("Patching the database failed!!! Restoring the backup"); console.error("Patching the database failed!!! Restoring the backup");
@ -383,6 +401,7 @@ class Database {
} }
} }
/** Get the size of the database */
static getSize() { static getSize() {
debug("Database.getSize()"); debug("Database.getSize()");
let stats = fs.statSync(Database.path); let stats = fs.statSync(Database.path);
@ -390,6 +409,10 @@ class Database {
return stats.size; return stats.size;
} }
/**
* Shrink the database
* @returns {Promise<void>}
*/
static async shrink() { static async shrink() {
await R.exec("VACUUM"); await R.exec("VACUUM");
} }

View file

@ -6,6 +6,11 @@ let fs = require("fs");
let ImageDataURI = (() => { let ImageDataURI = (() => {
/**
* Decode the data:image/ URI
* @param {string} dataURI data:image/ URI to decode
* @returns {Object}
*/
function decode(dataURI) { function decode(dataURI) {
if (!/data:image\//.test(dataURI)) { if (!/data:image\//.test(dataURI)) {
console.log("ImageDataURI :: Error :: It seems that it is not an Image Data URI. Couldn't match \"data:image/\""); console.log("ImageDataURI :: Error :: It seems that it is not an Image Data URI. Couldn't match \"data:image/\"");
@ -20,6 +25,12 @@ let ImageDataURI = (() => {
}; };
} }
/**
* Endcode an image into data:image/ URI
* @param {(Buffer|string)} data Data to encode
* @param {string} mediaType Media type of data
* @returns {(string|null)}
*/
function encode(data, mediaType) { function encode(data, mediaType) {
if (!data || !mediaType) { if (!data || !mediaType) {
console.log("ImageDataURI :: Error :: Missing some of the required params: data, mediaType "); console.log("ImageDataURI :: Error :: Missing some of the required params: data, mediaType ");
@ -33,6 +44,12 @@ let ImageDataURI = (() => {
return dataImgBase64; return dataImgBase64;
} }
/**
* Write data URI to file
* @param {string} dataURI data:image/ URI
* @param {string} filePath Path to write file to
* @returns {Promise<string>}
*/
function outputFile(dataURI, filePath) { function outputFile(dataURI, filePath) {
filePath = filePath || "./"; filePath = filePath || "./";
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

View file

@ -9,6 +9,11 @@ const jobs = [
}, },
]; ];
/**
* Initialize background jobs
* @param {Object} args Arguments to pass to workers
* @returns {Bree}
*/
const initBackgroundJobs = function (args) { const initBackgroundJobs = function (args) {
const bree = new Bree({ const bree = new Bree({
root: path.resolve("server", "jobs"), root: path.resolve("server", "jobs"),

View file

@ -32,6 +32,7 @@ class Notification {
providerList = {}; providerList = {};
/** Initialize the notification providers */
static init() { static init() {
console.log("Prepare Notification Providers"); console.log("Prepare Notification Providers");
@ -81,13 +82,13 @@ class Notification {
} }
/** /**
* * Send a notification
* @param notification : BeanModel * @param {BeanModel} notification
* @param msg : string General Message * @param {string} msg General Message
* @param monitorJSON : object Monitor details (For Up/Down only) * @param {Object} monitorJSON Monitor details (For Up/Down only)
* @param heartbeatJSON : object Heartbeat details (For Up/Down only) * @param {Object} heartbeatJSON Heartbeat details (For Up/Down only)
* @returns {Promise<string>} Successful msg * @returns {Promise<string>} Successful msg
* Throw Error with fail msg * @throws Error with fail msg
*/ */
static async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { static async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
if (this.providerList[notification.type]) { if (this.providerList[notification.type]) {
@ -97,28 +98,35 @@ class Notification {
} }
} }
/**
* Save a notification
* @param {Object} notification Notification to save
* @param {?number} notificationID ID of notification to update
* @param {number} userID ID of user who adds notification
* @returns {Promise<Bean>}
*/
static async save(notification, notificationID, userID) { static async save(notification, notificationID, userID) {
let bean let bean;
if (notificationID) { if (notificationID) {
bean = await R.findOne("notification", " id = ? AND user_id = ? ", [ bean = await R.findOne("notification", " id = ? AND user_id = ? ", [
notificationID, notificationID,
userID, userID,
]) ]);
if (! bean) { if (! bean) {
throw new Error("notification not found") throw new Error("notification not found");
} }
} else { } else {
bean = R.dispense("notification") bean = R.dispense("notification");
} }
bean.name = notification.name; bean.name = notification.name;
bean.user_id = userID; bean.user_id = userID;
bean.config = JSON.stringify(notification); bean.config = JSON.stringify(notification);
bean.is_default = notification.isDefault || false; bean.is_default = notification.isDefault || false;
await R.store(bean) await R.store(bean);
if (notification.applyExisting) { if (notification.applyExisting) {
await applyNotificationEveryMonitor(bean.id, userID); await applyNotificationEveryMonitor(bean.id, userID);
@ -127,19 +135,29 @@ class Notification {
return bean; return bean;
} }
/**
* Delete a notification
* @param {number} notificationID ID of notification to delete
* @param {number} userID ID of user who created notification
* @returns {Promise<void>}
*/
static async delete(notificationID, userID) { static async delete(notificationID, userID) {
let bean = await R.findOne("notification", " id = ? AND user_id = ? ", [ let bean = await R.findOne("notification", " id = ? AND user_id = ? ", [
notificationID, notificationID,
userID, userID,
]) ]);
if (! bean) { if (! bean) {
throw new Error("notification not found") throw new Error("notification not found");
} }
await R.trash(bean) await R.trash(bean);
} }
/**
* Check if apprise exists
* @returns {boolean} Does the command apprise exist?
*/
static checkApprise() { static checkApprise() {
let commandExistsSync = require("command-exists").sync; let commandExistsSync = require("command-exists").sync;
let exists = commandExistsSync("apprise"); let exists = commandExistsSync("apprise");
@ -148,6 +166,12 @@ class Notification {
} }
/**
* Apply the notification to every monitor
* @param {number} notificationID ID of notification to apply
* @param {number} userID ID of user who created notification
* @returns {Promise<void>}
*/
async function applyNotificationEveryMonitor(notificationID, userID) { async function applyNotificationEveryMonitor(notificationID, userID) {
let monitors = await R.getAll("SELECT id FROM monitor WHERE user_id = ?", [ let monitors = await R.getAll("SELECT id FROM monitor WHERE user_id = ?", [
userID userID
@ -157,17 +181,17 @@ async function applyNotificationEveryMonitor(notificationID, userID) {
let checkNotification = await R.findOne("monitor_notification", " monitor_id = ? AND notification_id = ? ", [ let checkNotification = await R.findOne("monitor_notification", " monitor_id = ? AND notification_id = ? ", [
monitors[i].id, monitors[i].id,
notificationID, notificationID,
]) ]);
if (! checkNotification) { if (! checkNotification) {
let relation = R.dispense("monitor_notification"); let relation = R.dispense("monitor_notification");
relation.monitor_id = monitors[i].id; relation.monitor_id = monitors[i].id;
relation.notification_id = notificationID; relation.notification_id = notificationID;
await R.store(relation) await R.store(relation);
} }
} }
} }
module.exports = { module.exports = {
Notification, Notification,
} };

View file

@ -2,22 +2,42 @@ const passwordHashOld = require("password-hash");
const bcrypt = require("bcryptjs"); const bcrypt = require("bcryptjs");
const saltRounds = 10; const saltRounds = 10;
/**
* Hash a password
* @param {string} password
* @returns {string}
*/
exports.generate = function (password) { exports.generate = function (password) {
return bcrypt.hashSync(password, saltRounds); return bcrypt.hashSync(password, saltRounds);
} };
/**
* Verify a password against a hash
* @param {string} password
* @param {string} hash
* @returns {boolean} Does the password match the hash?
*/
exports.verify = function (password, hash) { exports.verify = function (password, hash) {
if (isSHA1(hash)) { if (isSHA1(hash)) {
return passwordHashOld.verify(password, hash) return passwordHashOld.verify(password, hash);
} }
return bcrypt.compareSync(password, hash); return bcrypt.compareSync(password, hash);
} };
/**
* Is the hash a SHA1 hash
* @param {string} hash
* @returns {boolean}
*/
function isSHA1(hash) { function isSHA1(hash) {
return (typeof hash === "string" && hash.startsWith("sha1")) return (typeof hash === "string" && hash.startsWith("sha1"))
} }
/**
* Does the hash need to be rehashed?
* @returns {boolean}
*/
exports.needRehash = function (hash) { exports.needRehash = function (hash) {
return isSHA1(hash); return isSHA1(hash);
} };

View file

@ -8,6 +8,11 @@ const util = require("./util-server");
module.exports = Ping; module.exports = Ping;
/**
* Constructor for ping class
* @param {string} host Host to ping
* @param {string} options Command line options for ping
*/
function Ping(host, options) { function Ping(host, options) {
if (!host) { if (!host) {
throw new Error("You must specify a host to ping!"); throw new Error("You must specify a host to ping!");
@ -75,8 +80,17 @@ function Ping(host, options) {
Ping.prototype.__proto__ = events.EventEmitter.prototype; Ping.prototype.__proto__ = events.EventEmitter.prototype;
// SEND A PING /**
// =========== * Callback for send
* @callback pingCB
* @param {any} err Any error encountered
* @param {number} ms Ping time in ms
*/
/**
* Send a ping
* @param {pingCB} callback Callback to call with results
*/
Ping.prototype.send = function (callback) { Ping.prototype.send = function (callback) {
let self = this; let self = this;
callback = callback || function (err, ms) { callback = callback || function (err, ms) {
@ -145,8 +159,10 @@ Ping.prototype.send = function (callback) {
} }
}; };
// CALL Ping#send(callback) ON A TIMER /**
// =================================== * Ping every interval
* @param {pingCB} callback Callback to call with results
*/
Ping.prototype.start = function (callback) { Ping.prototype.start = function (callback) {
let self = this; let self = this;
this._i = setInterval(function () { this._i = setInterval(function () {
@ -155,8 +171,7 @@ Ping.prototype.start = function (callback) {
self.send(callback); self.send(callback);
}; };
// STOP SENDING PINGS /** Stop sending pings */
// ==================
Ping.prototype.stop = function () { Ping.prototype.stop = function () {
clearInterval(this._i); clearInterval(this._i);
}; };
@ -165,7 +180,7 @@ Ping.prototype.stop = function () {
* Try to convert to UTF-8 for Windows, as the ping's output on Windows is not UTF-8 and could be in other languages * Try to convert to UTF-8 for Windows, as the ping's output on Windows is not UTF-8 and could be in other languages
* Thank @pemassi * Thank @pemassi
* https://github.com/louislam/uptime-kuma/issues/570#issuecomment-941984094 * https://github.com/louislam/uptime-kuma/issues/570#issuecomment-941984094
* @param data * @param {any} data
* @returns {string} * @returns {string}
*/ */
function convertOutput(data) { function convertOutput(data) {

View file

@ -34,6 +34,9 @@ const monitor_status = new PrometheusClient.Gauge({
class Prometheus { class Prometheus {
monitorLabelValues = {} monitorLabelValues = {}
/**
* @param {Object} monitor Monitor object to monitor
*/
constructor(monitor) { constructor(monitor) {
this.monitorLabelValues = { this.monitorLabelValues = {
monitor_name: monitor.name, monitor_name: monitor.name,
@ -44,6 +47,11 @@ class Prometheus {
}; };
} }
/**
* Update the metrics page
* @param {Object} heartbeat Heartbeat details
* @param {Object} tlsInfo TLS details
*/
update(heartbeat, tlsInfo) { update(heartbeat, tlsInfo) {
if (typeof tlsInfo !== "undefined") { if (typeof tlsInfo !== "undefined") {

View file

@ -2,11 +2,26 @@ const { RateLimiter } = require("limiter");
const { debug } = require("../src/util"); const { debug } = require("../src/util");
class KumaRateLimiter { class KumaRateLimiter {
/**
* @param {Object} config Rate limiter configuration object
*/
constructor(config) { constructor(config) {
this.errorMessage = config.errorMessage; this.errorMessage = config.errorMessage;
this.rateLimiter = new RateLimiter(config); this.rateLimiter = new RateLimiter(config);
} }
/**
* Callback for pass
* @callback passCB
* @param {Object} err Too many requests
*/
/**
* Should the request be passed through
* @param {passCB} callback
* @param {number} [num=1] Number of tokens to remove
* @returns {Promise<boolean>}
*/
async pass(callback, num = 1) { async pass(callback, num = 1) {
const remainingRequests = await this.removeTokens(num); const remainingRequests = await this.removeTokens(num);
debug("Rate Limit (remainingRequests):" + remainingRequests); debug("Rate Limit (remainingRequests):" + remainingRequests);
@ -22,6 +37,11 @@ class KumaRateLimiter {
return true; return true;
} }
/**
* Remove a given number of tokens
* @param {number} [num=1] Number of tokens to remove
* @returns {Promise<number>}
*/
async removeTokens(num = 1) { async removeTokens(num = 1) {
return await this.rateLimiter.removeTokens(num); return await this.rateLimiter.removeTokens(num);
} }

View file

@ -1362,6 +1362,13 @@ exports.entryPage = "dashboard";
})(); })();
/**
* Update notifications for a given monitor
* @param {number} monitorID ID of monitor to update
* @param {Array<number>} notificationIDList List of new notification
* providers to add
* @returns {Promise<void>}
*/
async function updateMonitorNotification(monitorID, notificationIDList) { async function updateMonitorNotification(monitorID, notificationIDList) {
await R.exec("DELETE FROM monitor_notification WHERE monitor_id = ? ", [ await R.exec("DELETE FROM monitor_notification WHERE monitor_id = ? ", [
monitorID, monitorID,
@ -1377,6 +1384,13 @@ async function updateMonitorNotification(monitorID, notificationIDList) {
} }
} }
/**
* Check if a given user owns a specific monitor
* @param {number} userID
* @param {number} monitorID
* @returns {Promise<void>}
* @throws {Error} The specified user does not own the monitor
*/
async function checkOwner(userID, monitorID) { async function checkOwner(userID, monitorID) {
let row = await R.getRow("SELECT id FROM monitor WHERE id = ? AND user_id = ? ", [ let row = await R.getRow("SELECT id FROM monitor WHERE id = ? AND user_id = ? ", [
monitorID, monitorID,
@ -1388,12 +1402,23 @@ async function checkOwner(userID, monitorID) {
} }
} }
/**
* Send the monitor list to clients
* @param {Socket} socket Socket.io instance
* @returns {Object}
*/
async function sendMonitorList(socket) { async function sendMonitorList(socket) {
let list = await getMonitorJSONList(socket.userID); let list = await getMonitorJSONList(socket.userID);
io.to(socket.userID).emit("monitorList", list); io.to(socket.userID).emit("monitorList", list);
return list; return list;
} }
/**
* Function called after user login
* @param {Socket} socket Socket.io instance
* @param {Object} user User object
* @returns {Promise<void>}
*/
async function afterLogin(socket, user) { async function afterLogin(socket, user) {
socket.userID = user.id; socket.userID = user.id;
socket.join(user.id); socket.join(user.id);
@ -1416,6 +1441,11 @@ async function afterLogin(socket, user) {
} }
} }
/**
* Get a JSON representation of monitor list
* @param {number} userID
* @returns {Promise<Object>}
*/
async function getMonitorJSONList(userID) { async function getMonitorJSONList(userID) {
let result = {}; let result = {};
@ -1430,6 +1460,12 @@ async function getMonitorJSONList(userID) {
return result; return result;
} }
/**
* Initialize the database
* @param {boolean} [testMode=false] Should the connection be
* started in test mode?
* @returns {Promise<void>}
*/
async function initDatabase(testMode = false) { async function initDatabase(testMode = false) {
if (! fs.existsSync(Database.path)) { if (! fs.existsSync(Database.path)) {
console.log("Copying Database"); console.log("Copying Database");
@ -1464,6 +1500,12 @@ async function initDatabase(testMode = false) {
jwtSecret = jwtSecretBean.value; jwtSecret = jwtSecretBean.value;
} }
/**
* Start the specified monitor
* @param {number} userID ID of user who owns monitor
* @param {number} monitorID ID of monitor to start
* @returns {Promise<void>}
*/
async function startMonitor(userID, monitorID) { async function startMonitor(userID, monitorID) {
await checkOwner(userID, monitorID); await checkOwner(userID, monitorID);
@ -1486,10 +1528,22 @@ async function startMonitor(userID, monitorID) {
monitor.start(io); monitor.start(io);
} }
/**
* Restart a given monitor
* @param {number} userID ID of user who owns monitor
* @param {number} monitorID ID of monitor to start
* @returns {Promise<void>}
*/
async function restartMonitor(userID, monitorID) { async function restartMonitor(userID, monitorID) {
return await startMonitor(userID, monitorID); return await startMonitor(userID, monitorID);
} }
/**
* Pause a given monitor
* @param {number} userID ID of user who owns monitor
* @param {number} monitorID ID of monitor to start
* @returns {Promise<void>}
*/
async function pauseMonitor(userID, monitorID) { async function pauseMonitor(userID, monitorID) {
await checkOwner(userID, monitorID); await checkOwner(userID, monitorID);
@ -1505,9 +1559,7 @@ async function pauseMonitor(userID, monitorID) {
} }
} }
/** /** Resume active monitors */
* Resume active monitors
*/
async function startMonitors() { async function startMonitors() {
let list = await R.find("monitor", " active = 1 "); let list = await R.find("monitor", " active = 1 ");
@ -1522,6 +1574,11 @@ async function startMonitors() {
} }
} }
/**
* Shutdown the application
* @param {string} signal Shutdown signal
* @returns {Promise<void>}
*/
async function shutdownFunction(signal) { async function shutdownFunction(signal) {
console.log("Shutdown requested"); console.log("Shutdown requested");
console.log("Called signal: " + signal); console.log("Called signal: " + signal);
@ -1535,6 +1592,7 @@ async function shutdownFunction(signal) {
await Database.close(); await Database.close();
} }
/** Final function called before application exits */
function finalFunction() { function finalFunction() {
console.log("Graceful shutdown successful!"); console.log("Graceful shutdown successful!");
} }

View file

@ -37,6 +37,12 @@ exports.initJWTSecret = async () => {
return jwtSecretBean; return jwtSecretBean;
}; };
/**
* Send TCP request to specified hostname and port
* @param {string} hostname Hostname / address of machine
* @param {number} port TCP port to test
* @returns {Promise<number>} Maximum time in ms rounded to nearest integer
*/
exports.tcping = function (hostname, port) { exports.tcping = function (hostname, port) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
tcpp.ping({ tcpp.ping({
@ -58,6 +64,11 @@ exports.tcping = function (hostname, port) {
}); });
}; };
/**
* Ping the specified machine
* @param {string} hostname Hostname / address of machine
* @returns {Promise<number>} Time for ping in ms rounded to nearest integer
*/
exports.ping = async (hostname) => { exports.ping = async (hostname) => {
try { try {
return await exports.pingAsync(hostname); return await exports.pingAsync(hostname);
@ -71,6 +82,12 @@ exports.ping = async (hostname) => {
} }
}; };
/**
* Ping the specified machine
* @param {string} hostname Hostname / address of machine to ping
* @param {boolean} ipv6 Should IPv6 be used?
* @returns {Promise<number>} Time for ping in ms rounded to nearest integer
*/
exports.pingAsync = function (hostname, ipv6 = false) { exports.pingAsync = function (hostname, ipv6 = false) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const ping = new Ping(hostname, { const ping = new Ping(hostname, {
@ -89,6 +106,14 @@ exports.pingAsync = function (hostname, ipv6 = false) {
}); });
}; };
// `string[]`, `Object[]` and `Object`.
/**
* Resolves a given record using the specified DNS server
* @param {string} hostname The hostname of the record to lookup
* @param {string} resolver_server The DNS server to use
* @param {string} rrtype The type of record to request
* @returns {Promise<(string[]|Object[]|Object)>}
*/
exports.dnsResolve = function (hostname, resolver_server, rrtype) { exports.dnsResolve = function (hostname, resolver_server, rrtype) {
const resolver = new Resolver(); const resolver = new Resolver();
resolver.setServers([resolver_server]); resolver.setServers([resolver_server]);
@ -113,6 +138,11 @@ exports.dnsResolve = function (hostname, resolver_server, rrtype) {
}); });
}; };
/**
* Retrieve value of setting based on key
* @param {string} key Key of setting to retrieve
* @returns {Promise<Object>} Object representation of setting
*/
exports.setting = async function (key) { exports.setting = async function (key) {
let value = await R.getCell("SELECT `value` FROM setting WHERE `key` = ? ", [ let value = await R.getCell("SELECT `value` FROM setting WHERE `key` = ? ", [
key, key,
@ -127,6 +157,13 @@ exports.setting = async function (key) {
} }
}; };
/**
* Sets the specified setting to specifed value
* @param {string} key Key of setting to set
* @param {any} value Value to set to
* @param {?string} type Type of setting
* @returns {Promise<void>}
*/
exports.setSetting = async function (key, value, type = null) { exports.setSetting = async function (key, value, type = null) {
let bean = await R.findOne("setting", " `key` = ? ", [ let bean = await R.findOne("setting", " `key` = ? ", [
key, key,
@ -140,6 +177,11 @@ exports.setSetting = async function (key, value, type = null) {
await R.store(bean); await R.store(bean);
}; };
/**
* Get settings based on type
* @param {?string} type The type of setting
* @returns {Promise<Bean>}
*/
exports.getSettings = async function (type) { exports.getSettings = async function (type) {
let list = await R.getAll("SELECT `key`, `value` FROM setting WHERE `type` = ? ", [ let list = await R.getAll("SELECT `key`, `value` FROM setting WHERE `type` = ? ", [
type, type,
@ -158,6 +200,12 @@ exports.getSettings = async function (type) {
return result; return result;
}; };
/**
* Set settings based on type
* @param {?string} type Type of settings to set
* @param {Object} data Values of settings
* @returns {Promise<void>}
*/
exports.setSettings = async function (type, data) { exports.setSettings = async function (type, data) {
let keyList = Object.keys(data); let keyList = Object.keys(data);
@ -184,12 +232,23 @@ exports.setSettings = async function (type, data) {
}; };
// ssl-checker by @dyaa // ssl-checker by @dyaa
// param: res - response object from axios //https://github.com/dyaa/ssl-checker/blob/master/src/index.ts
// return an object containing the certificate information
/**
* Get number of days between two dates
* @param {Date} validFrom Start date
* @param {Date} validTo End date
* @returns {number}
*/
const getDaysBetween = (validFrom, validTo) => const getDaysBetween = (validFrom, validTo) =>
Math.round(Math.abs(+validFrom - +validTo) / 8.64e7); Math.round(Math.abs(+validFrom - +validTo) / 8.64e7);
/**
* Get days remaining from a time range
* @param {Date} validFrom Start date
* @param {Date} validTo End date
* @returns {number}
*/
const getDaysRemaining = (validFrom, validTo) => { const getDaysRemaining = (validFrom, validTo) => {
const daysRemaining = getDaysBetween(validFrom, validTo); const daysRemaining = getDaysBetween(validFrom, validTo);
if (new Date(validTo).getTime() < new Date().getTime()) { if (new Date(validTo).getTime() < new Date().getTime()) {
@ -198,8 +257,11 @@ const getDaysRemaining = (validFrom, validTo) => {
return daysRemaining; return daysRemaining;
}; };
// Fix certificate Info for display /**
// param: info - the chain obtained from getPeerCertificate() * Fix certificate info for display
* @param {Object} info The chain obtained from getPeerCertificate()
* @returns {Object} An object representing certificate information
*/
const parseCertificateInfo = function (info) { const parseCertificateInfo = function (info) {
let link = info; let link = info;
let i = 0; let i = 0;
@ -239,6 +301,11 @@ const parseCertificateInfo = function (info) {
return info; return info;
}; };
/**
* Check if certificate is valid
* @param {Object} res Response object from axios
* @returns {Object} Object containing certificate information
*/
exports.checkCertificate = function (res) { exports.checkCertificate = function (res) {
const info = res.request.res.socket.getPeerCertificate(true); const info = res.request.res.socket.getPeerCertificate(true);
const valid = res.request.res.socket.authorized || false; const valid = res.request.res.socket.authorized || false;
@ -252,12 +319,13 @@ exports.checkCertificate = function (res) {
}; };
}; };
// Check if the provided status code is within the accepted ranges /**
// Param: status - the status code to check * Check if the provided status code is within the accepted ranges
// Param: accepted_codes - an array of accepted status codes * @param {string} status The status code to check
// Return: true if the status code is within the accepted ranges, false otherwise * @param {Array<string>} accepted_codes An array of accepted status codes
// Will throw an error if the provided status code is not a valid range string or code string * @returns {boolean} True if status code within range, false otherwise
* @throws {Error} Will throw an error if the provided status code is not a valid range string or code string
*/
exports.checkStatusCode = function (status, accepted_codes) { exports.checkStatusCode = function (status, accepted_codes) {
if (accepted_codes == null || accepted_codes.length === 0) { if (accepted_codes == null || accepted_codes.length === 0) {
return false; return false;
@ -281,6 +349,12 @@ exports.checkStatusCode = function (status, accepted_codes) {
return false; return false;
}; };
/**
* Get total number of clients in room
* @param {Server} io Socket server instance
* @param {string} roomName Name of room to check
* @returns {number}
*/
exports.getTotalClientInRoom = (io, roomName) => { exports.getTotalClientInRoom = (io, roomName) => {
const sockets = io.sockets; const sockets = io.sockets;
@ -304,23 +378,36 @@ exports.getTotalClientInRoom = (io, roomName) => {
} }
}; };
/**
* Allow CORS all origins if development
* @param {Object} res Response object from axios
*/
exports.allowDevAllOrigin = (res) => { exports.allowDevAllOrigin = (res) => {
if (process.env.NODE_ENV === "development") { if (process.env.NODE_ENV === "development") {
exports.allowAllOrigin(res); exports.allowAllOrigin(res);
} }
}; };
/**
* Allow CORS all origins
* @param {Object} res Response object from axios
*/
exports.allowAllOrigin = (res) => { exports.allowAllOrigin = (res) => {
res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
}; };
/**
* Check if a user is logged in
* @param {Socket} socket Socket instance
*/
exports.checkLogin = (socket) => { exports.checkLogin = (socket) => {
if (! socket.userID) { if (! socket.userID) {
throw new Error("You are not logged in."); throw new Error("You are not logged in.");
} }
}; };
/** Start Unit tests */
exports.startUnitTest = async () => { exports.startUnitTest = async () => {
console.log("Starting unit test..."); console.log("Starting unit test...");
const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm"; const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm";
@ -341,7 +428,8 @@ exports.startUnitTest = async () => {
}; };
/** /**
* @param body : Buffer * Convert unknown string to UTF8
* @param {Uint8Array} body Buffer
* @returns {string} * @returns {string}
*/ */
exports.convertToUTF8 = (body) => { exports.convertToUTF8 = (body) => {
@ -359,6 +447,11 @@ try {
}); });
} catch (_) { } } catch (_) { }
/**
* Write error to log file
* @param {any} error The error to write
* @param {boolean} outputToConsole Should the error also be output to console?
*/
exports.errorLog = (error, outputToConsole = true) => { exports.errorLog = (error, outputToConsole = true) => {
try { try {
if (logFile) { if (logFile) {