From 6fb66728e6f4045ba684e1c5e3912be7883e1647 Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Fri, 15 Apr 2022 20:08:54 +0100 Subject: [PATCH 01/15] [empty commit] pull request for Add JSDoc comments From 60c63cc18ef0b8d2228f2927e3e8872a3851017d Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Sat, 16 Apr 2022 11:10:51 +0100 Subject: [PATCH 02/15] Add JSDoc to server/jobs/* Signed-off-by: Matthew Nickson --- server/jobs/util-worker.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server/jobs/util-worker.js b/server/jobs/util-worker.js index 9426840d7..52ad8f9ae 100644 --- a/server/jobs/util-worker.js +++ b/server/jobs/util-worker.js @@ -2,12 +2,20 @@ const { parentPort, workerData } = require("worker_threads"); const Database = require("../database"); const path = require("path"); +/** + * Send message to parent process + * @param {any} any The message to log + */ const log = function (any) { if (parentPort) { parentPort.postMessage(any); } }; +/** + * Exit the worker process + * @param {number} error The status code to exit + */ const exit = function (error) { if (error && error != 0) { process.exit(error); @@ -20,6 +28,7 @@ const exit = function (error) { } }; +/** Connects to the database */ const connectDb = async function () { const dbPath = path.join( process.env.DATA_DIR || workerData["data-dir"] || "./data/" From 5a209c74e1411d2729e3ab06bb180048fd1cdc24 Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Sat, 16 Apr 2022 20:24:53 +0100 Subject: [PATCH 03/15] Add JSDoc to server/notification-providers/* Signed-off-by: Matthew Nickson --- server/notification-providers/aliyun-sms.js | 18 ++++++++++- server/notification-providers/bark.js | 20 +++++++++++-- server/notification-providers/dingding.js | 19 +++++++++++- .../notification-provider.js | 16 ++++++---- server/notification-providers/slack.js | 1 + server/notification-providers/teams.js | 30 +++++++++++++++++++ server/notification-providers/wecom.js | 6 ++++ 7 files changed, 101 insertions(+), 9 deletions(-) diff --git a/server/notification-providers/aliyun-sms.js b/server/notification-providers/aliyun-sms.js index 6a2063200..1a64690df 100644 --- a/server/notification-providers/aliyun-sms.js +++ b/server/notification-providers/aliyun-sms.js @@ -37,6 +37,12 @@ class AliyunSMS extends NotificationProvider { } } + /** + * Send the SMS notification + * @param {BeanModel} notification Notification details + * @param {string} msgbody Message template + * @returns {boolean} True if successful else false + */ async sendSms(notification, msgbody) { let params = { PhoneNumbers: notification.phonenumber, @@ -70,7 +76,12 @@ class AliyunSMS extends NotificationProvider { return false; } - /** Aliyun request sign */ + /** + * Aliyun request sign + * @param {Object} param Parameters object to sign + * @param {string} AccessKeySecret Secret key to sign parameters with + * @returns {string} + */ sign(param, AccessKeySecret) { let param2 = {}; let data = []; @@ -93,6 +104,11 @@ class AliyunSMS extends NotificationProvider { .digest("base64"); } + /** + * Convert status constant to string + * @param {const} status The status constant + * @returns {string} + */ statusToString(status) { switch (status) { case DOWN: diff --git a/server/notification-providers/bark.js b/server/notification-providers/bark.js index 4ebe978ad..b2f256128 100644 --- a/server/notification-providers/bark.js +++ b/server/notification-providers/bark.js @@ -49,7 +49,12 @@ class Bark extends NotificationProvider { } } - // add additional parameter for better on device styles (iOS 15 optimized) + /** + * Add additional parameter for better on device styles (iOS 15 + * optimized) + * @param {string} postUrl URL to append parameters to + * @returns {string} + */ appendAdditionalParameters(postUrl) { // grouping all our notifications postUrl += "?group=" + barkNotificationGroup; @@ -60,7 +65,11 @@ class Bark extends NotificationProvider { return postUrl; } - // thrown if failed to check result, result code should be in range 2xx + /** + * Check if result is successful + * @param {Object} result Axios response object + * @throws {Error} The status code is not in range 2xx + */ checkResult(result) { if (result.status == null) { throw new Error("Bark notification failed with invalid response!"); @@ -70,6 +79,13 @@ class Bark extends NotificationProvider { } } + /** + * Send the message + * @param {string} title Message title + * @param {string} subtitle Message + * @param {string} endpoint Endpoint to send request to + * @returns {string} + */ async postNotification(title, subtitle, endpoint) { // url encode title and subtitle title = encodeURIComponent(title); diff --git a/server/notification-providers/dingding.js b/server/notification-providers/dingding.js index cf08f14bf..f3946cf87 100644 --- a/server/notification-providers/dingding.js +++ b/server/notification-providers/dingding.js @@ -37,6 +37,12 @@ class DingDing extends NotificationProvider { } } + /** + * Send message to DingDing + * @param {BeanModel} notification + * @param {Object} params Parameters of message + * @returns {boolean} True if successful else false + */ async sendToDingDing(notification, params) { let timestamp = Date.now(); @@ -56,7 +62,12 @@ class DingDing extends NotificationProvider { return false; } - /** DingDing sign */ + /** + * DingDing sign + * @param {Date} timestamp Timestamp of message + * @param {string} secretKey Secret key to sign data with + * @returns {string} + */ sign(timestamp, secretKey) { return Crypto .createHmac("sha256", Buffer.from(secretKey, "utf8")) @@ -64,7 +75,13 @@ class DingDing extends NotificationProvider { .digest("base64"); } + /** + * Convert status constant to string + * @param {const} status The status constant + * @returns {string} + */ statusToString(status) { + // TODO: Move to notification-provider.js to avoid repetition in classes switch (status) { case DOWN: return "DOWN"; diff --git a/server/notification-providers/notification-provider.js b/server/notification-providers/notification-provider.js index 61c6242d7..6765633d5 100644 --- a/server/notification-providers/notification-provider.js +++ b/server/notification-providers/notification-provider.js @@ -7,17 +7,23 @@ class NotificationProvider { name = undefined; /** - * @param notification : BeanModel - * @param msg : string General Message - * @param monitorJSON : object Monitor details (For Up/Down only) - * @param heartbeatJSON : object Heartbeat details (For Up/Down only) + * Send a notification + * @param {BeanModel} notification + * @param {string} msg General Message + * @param {?Object} monitorJSON Monitor details (For Up/Down only) + * @param {?Object} heartbeatJSON Heartbeat details (For Up/Down only) * @returns {Promise} Return Successful Message - * Throw Error with fail msg + * @throws Error with fail msg */ async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { throw new Error("Have to override Notification.send(...)"); } + /** + * Throws an error + * @param {any} error The error to throw + * @throws {any} The error specified + */ throwGeneralAxiosError(error) { let msg = "Error: " + error + " "; diff --git a/server/notification-providers/slack.js b/server/notification-providers/slack.js index b4dad6fe3..da1d6e66b 100644 --- a/server/notification-providers/slack.js +++ b/server/notification-providers/slack.js @@ -10,6 +10,7 @@ class Slack extends NotificationProvider { /** * Deprecated property notification.slackbutton * Set it as primary base url if this is not yet set. + * @param {string} url The primary base URL to use */ static async deprecateURL(url) { let currentPrimaryBaseURL = await setting("primaryBaseURL"); diff --git a/server/notification-providers/teams.js b/server/notification-providers/teams.js index 859af569c..c398e03c2 100644 --- a/server/notification-providers/teams.js +++ b/server/notification-providers/teams.js @@ -5,6 +5,12 @@ const { DOWN, UP } = require("../../src/util"); class Teams extends NotificationProvider { name = "teams"; + /** + * Generate the message to send + * @param {const} status The status constant + * @param {string} monitorName Name of monitor + * @returns {string} + */ _statusMessageFactory = (status, monitorName) => { if (status === DOWN) { return `🔴 Application [${monitorName}] went down`; @@ -14,6 +20,11 @@ class Teams extends NotificationProvider { return "Notification"; }; + /** + * Select theme color to use based on status + * @param {const} status The status constant + * @returns {string} Selected color in hex RGB format + */ _getThemeColor = (status) => { if (status === DOWN) { return "ff0000"; @@ -24,6 +35,14 @@ class Teams extends NotificationProvider { return "008cff"; }; + /** + * Generate payload for notification + * @param {const} status The status of the monitor + * @param {string} monitorMessage Message to send + * @param {string} monitorName Name of monitor affected + * @param {string} monitorUrl URL of monitor affected + * @returns {Object} + */ _notificationPayloadFactory = ({ status, monitorMessage, @@ -74,10 +93,21 @@ class Teams extends NotificationProvider { }; }; + /** + * Send the notification + * @param {string} webhookUrl URL to send the request to + * @param {Object} payload Payload generated by _notificationPayloadFactory + */ _sendNotification = async (webhookUrl, payload) => { await axios.post(webhookUrl, payload); }; + /** + * Send a general notification + * @param {string} webhookUrl URL to send request to + * @param {string} msg Message to send + * @returns {Promise} + */ _handleGeneralNotification = (webhookUrl, msg) => { const payload = this._notificationPayloadFactory({ monitorMessage: msg diff --git a/server/notification-providers/wecom.js b/server/notification-providers/wecom.js index 7ba8c3783..4da478ad4 100644 --- a/server/notification-providers/wecom.js +++ b/server/notification-providers/wecom.js @@ -24,6 +24,12 @@ class WeCom extends NotificationProvider { } } + /** + * Generate the message to send + * @param {Object} heartbeatJSON Heartbeat details (For Up/Down only) + * @param {string} msg General message + * @returns {Object} + */ composeMessage(heartbeatJSON, msg) { let title; if (msg != null && heartbeatJSON != null && heartbeatJSON['status'] == UP) { From 45f44b183d706aadc6c9af901e0e68fb2e73946e Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Sat, 16 Apr 2022 21:11:45 +0100 Subject: [PATCH 04/15] Add JSDoc to server/model/* Signed-off-by: Matthew Nickson --- server/model/group.js | 9 +++++ server/model/heartbeat.js | 9 +++++ server/model/incident.js | 5 +++ server/model/monitor.js | 74 +++++++++++++++++++++++++++++++++++---- server/model/tag.js | 5 +++ 5 files changed, 96 insertions(+), 6 deletions(-) diff --git a/server/model/group.js b/server/model/group.js index 567f3865b..eb5006e14 100644 --- a/server/model/group.js +++ b/server/model/group.js @@ -3,6 +3,11 @@ const { R } = require("redbean-node"); class Group extends BeanModel { + /** + * Return a object that ready to parse to JSON for public + * Only show necessary data to public + * @returns {Object} + */ async toPublicJSON() { let monitorBeanList = await this.getMonitorList(); let monitorList = []; @@ -19,6 +24,10 @@ class Group extends BeanModel { }; } + /** + * Get all monitors + * @returns {Array} + */ async getMonitorList() { return R.convertToBeans("monitor", await R.getAll(` SELECT monitor.* FROM monitor, monitor_group diff --git a/server/model/heartbeat.js b/server/model/heartbeat.js index e0a77c069..38ba6f46a 100644 --- a/server/model/heartbeat.js +++ b/server/model/heartbeat.js @@ -13,6 +13,11 @@ const { BeanModel } = require("redbean-node/dist/bean-model"); */ class Heartbeat extends BeanModel { + /** + * Return a object that ready to parse to JSON for public + * Only show necessary data to public + * @returns {Object} + */ toPublicJSON() { return { status: this.status, @@ -22,6 +27,10 @@ class Heartbeat extends BeanModel { }; } + /** + * Return a object that ready to parse to JSON + * @returns {Object} + */ toJSON() { return { monitorID: this.monitor_id, diff --git a/server/model/incident.js b/server/model/incident.js index 89c117e90..e28478f77 100644 --- a/server/model/incident.js +++ b/server/model/incident.js @@ -2,6 +2,11 @@ const { BeanModel } = require("redbean-node/dist/bean-model"); class Incident extends BeanModel { + /** + * Return a object that ready to parse to JSON for public + * Only show necessary data to public + * @returns {Object} + */ toPublicJSON() { return { id: this.id, diff --git a/server/model/monitor.js b/server/model/monitor.js index c4441d63e..31c5a9cbb 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -26,6 +26,7 @@ class Monitor extends BeanModel { /** * Return a object that ready to parse to JSON for public * Only show necessary data to public + * @returns {Object} */ async toPublicJSON() { return { @@ -36,6 +37,7 @@ class Monitor extends BeanModel { /** * Return a object that ready to parse to JSON + * @returns {Object} */ async toJSON() { @@ -107,10 +109,18 @@ class Monitor extends BeanModel { return Boolean(this.upsideDown); } + /** + * Get status codes that are acceptable + * @returns {Object} + */ getAcceptedStatuscodes() { return JSON.parse(this.accepted_statuscodes_json); } + /** + * Start monitor + * @param {Server} io Socket server instance + */ start(io) { let previousBeat = null; let retries = 0; @@ -463,6 +473,7 @@ class Monitor extends BeanModel { } } + /** Stop monitor */ stop() { clearTimeout(this.heartbeatInterval); this.isStop = true; @@ -472,7 +483,7 @@ class Monitor extends BeanModel { * Helper Method: * returns URL object for further usage * returns null if url is invalid - * @returns {null|URL} + * @returns {(null|URL)} */ getUrl() { try { @@ -485,7 +496,7 @@ class Monitor extends BeanModel { /** * Store TLS info to database * @param checkCertificateResult - * @returns {Promise} + * @returns {Promise} */ async updateTlsInfo(checkCertificateResult) { let tls_info_bean = await R.findOne("monitor_tls_info", "monitor_id = ?", [ @@ -527,6 +538,12 @@ class Monitor extends BeanModel { return checkCertificateResult; } + /** + * Send statistics to clients + * @param {Server} io Socket server instance + * @param {number} monitorID ID of monitor to send + * @param {number} userID ID of user to send to + */ static async sendStats(io, monitorID, userID) { const hasClients = getTotalClientInRoom(io, userID) > 0; @@ -541,8 +558,8 @@ class Monitor extends BeanModel { } /** - * - * @param duration : int Hours + * Send the average ping to user + * @param {number} duration Hours */ static async sendAvgPing(duration, io, monitorID, userID) { const timeLogger = new TimeLogger(); @@ -562,6 +579,12 @@ class Monitor extends BeanModel { io.to(userID).emit("avgPing", monitorID, avgPing); } + /** + * Send certificate information to information + * @param {Server} io Socket server instance + * @param {number} monitorID ID of monitor to send + * @param {number} userID ID of user to send to + */ static async sendCertInfo(io, monitorID, userID) { let tls_info = await R.findOne("monitor_tls_info", "monitor_id = ?", [ monitorID, @@ -575,7 +598,8 @@ class Monitor extends BeanModel { * Uptime with calculation * Calculation based on: * https://www.uptrends.com/support/kb/reporting/calculation-of-uptime-and-downtime - * @param duration : int Hours + * @param {number} duration Hours + * @param {number} monitorID ID of monitor to calculate */ static async calcUptime(duration, monitorID) { const timeLogger = new TimeLogger(); @@ -641,13 +665,23 @@ class Monitor extends BeanModel { /** * Send Uptime - * @param duration : int Hours + * @param {number} duration Hours + * @param {Server} io Socket server instance + * @param {number} monitorID ID of monitor to send + * @param {number} userID ID of user to send to */ static async sendUptime(duration, io, monitorID, userID) { const uptime = await this.calcUptime(duration, monitorID); io.to(userID).emit("uptime", monitorID, duration, uptime); } + /** + * Has status of monitor changed since last beat? + * @param {boolean} isFirstBeat Is this the first beat of this monitor? + * @param {const} previousBeatStatus Status of the previous beat + * @param {const} currentBeatStatus Status of the current beat + * @returns {boolean} True if is an important beat else false + */ static isImportantBeat(isFirstBeat, previousBeatStatus, currentBeatStatus) { // * ? -> ANY STATUS = important [isFirstBeat] // UP -> PENDING = not important @@ -666,6 +700,12 @@ class Monitor extends BeanModel { return isImportant; } + /** + * Send a notification about a monitor + * @param {boolean} isFirstBeat Is this beat the first of this monitor? + * @param {Monitor} monitor The monitor to send a notificaton about + * @param {Bean} bean Status information about monitor + */ static async sendNotification(isFirstBeat, monitor, bean) { if (!isFirstBeat || bean.status === DOWN) { const notificationList = await Monitor.getNotificationList(monitor); @@ -690,6 +730,11 @@ class Monitor extends BeanModel { } } + /** + * Get list of notification providers for a given monitor + * @param {Monitor} monitor Monitor to get notification providers for + * @returns {Promise[]>} + */ static async getNotificationList(monitor) { let notificationList = await R.getAll("SELECT notification.* FROM notification, monitor_notification WHERE monitor_id = ? AND monitor_notification.notification_id = notification.id ", [ monitor.id, @@ -697,6 +742,10 @@ class Monitor extends BeanModel { return notificationList; } + /** + * Send notification about a certificate + * @param {Object} tlsInfoObject Information about certificate + */ async sendCertNotification(tlsInfoObject) { if (tlsInfoObject && tlsInfoObject.certInfo && tlsInfoObject.certInfo.daysRemaining) { const notificationList = await Monitor.getNotificationList(this); @@ -708,6 +757,14 @@ class Monitor extends BeanModel { } } + /** + * Send a certificate notification when certificate expires in less + * than target days + * @param {number} daysRemaining Number of days remaining on certifcate + * @param {number} targetDays Number of days to alert after + * @param {Array>} notificationList List of notification providers + * @returns {Promise} + */ async sendCertNotificationByTargetDays(daysRemaining, targetDays, notificationList) { if (daysRemaining > targetDays) { @@ -755,6 +812,11 @@ class Monitor extends BeanModel { } } + /** + * Get the status of the previous heartbeat + * @param {number} monitorID ID of monitor to check + * @returns {Promise>} + */ static async getPreviousHeartbeat(monitorID) { return await R.getRow(` SELECT status, time FROM heartbeat diff --git a/server/model/tag.js b/server/model/tag.js index 748280a70..5a8d97d31 100644 --- a/server/model/tag.js +++ b/server/model/tag.js @@ -1,6 +1,11 @@ const { BeanModel } = require("redbean-node/dist/bean-model"); class Tag extends BeanModel { + + /** + * Return a object that ready to parse to JSON + * @returns {Object} + */ toJSON() { return { id: this._id, From 03b2d8d5210b354562e73136934eeb849c90f35b Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Wed, 20 Apr 2022 19:56:40 +0100 Subject: [PATCH 05/15] Add JSDoc to server/* Signed-off-by: Matthew Nickson --- server/2fa.js | 6 +- server/auth.js | 21 +++++-- server/check-version.js | 6 ++ server/client.js | 26 +++++++-- server/database.js | 37 ++++++++++--- server/image-data-uri.js | 17 ++++++ server/jobs.js | 5 ++ server/notification.js | 58 ++++++++++++++------ server/password-hash.js | 28 ++++++++-- server/ping-lite.js | 29 +++++++--- server/prometheus.js | 8 +++ server/rate-limiter.js | 20 +++++++ server/server.js | 64 +++++++++++++++++++++- server/util-server.js | 115 +++++++++++++++++++++++++++++++++++---- 14 files changed, 380 insertions(+), 60 deletions(-) diff --git a/server/2fa.js b/server/2fa.js index bc8145cff..c7076da20 100644 --- a/server/2fa.js +++ b/server/2fa.js @@ -1,8 +1,12 @@ -const { checkLogin } = require("./util-server"); const { R } = require("redbean-node"); class TwoFA { + /** + * Disable 2FA for specified user + * @param {number} userID ID of user to disable + * @returns {Promise} + */ static async disable2FA(userID) { return await R.exec("UPDATE `user` SET twofa_status = 0 WHERE id = ? ", [ userID, diff --git a/server/auth.js b/server/auth.js index c476ea1e3..422dd666f 100644 --- a/server/auth.js +++ b/server/auth.js @@ -6,10 +6,10 @@ const { debug } = require("../src/util"); const { loginRateLimiter } = require("./rate-limiter"); /** - * - * @param username : string - * @param password : string - * @returns {Promise} + * Login to web app + * @param {string} username + * @param {string} password + * @returns {Promise<(Bean|null)>} */ exports.login = async function (username, password) { let user = await R.findOne("user", " username = ? AND active = 1 ", [ @@ -30,6 +30,19 @@ exports.login = async function (username, password) { 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) { setting("disableAuth").then((result) => { if (result) { diff --git a/server/check-version.js b/server/check-version.js index a3465ddf0..daec4975d 100644 --- a/server/check-version.js +++ b/server/check-version.js @@ -6,6 +6,7 @@ exports.latestVersion = null; let interval; +/** Start 48 hour check interval */ exports.startInterval = () => { let check = async () => { try { @@ -28,6 +29,11 @@ exports.startInterval = () => { interval = setInterval(check, 3600 * 1000 * 48); }; +/** + * Enable the check update feature + * @param {boolean} value Should the check update feature be enabled? + * @returns {Promise} + */ exports.enableCheckUpdate = async (value) => { await setSetting("checkUpdate", value); diff --git a/server/client.js b/server/client.js index c7b3bc162..8b0f75209 100644 --- a/server/client.js +++ b/server/client.js @@ -7,6 +7,11 @@ const { io } = require("./server"); const { setting } = require("./util-server"); const checkVersion = require("./check-version"); +/** + * Send list of notification providers to client + * @param {Socket} socket Socket.io socket instance + * @returns {Promise} + */ async function sendNotificationList(socket) { const timeLogger = new TimeLogger(); @@ -28,8 +33,11 @@ async function sendNotificationList(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 overwrite Overwrite client-side's heartbeat list + * @param {Socket} socket Socket.io instance + * @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} */ async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite = false) { const timeLogger = new TimeLogger(); @@ -56,10 +64,11 @@ async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite = /** * Important Heart beat list (aka event list) - * @param socket - * @param monitorID - * @param toUser 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 {Socket} socket Socket.io instance + * @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} */ async function sendImportantHeartbeatList(socket, monitorID, toUser = false, overwrite = false) { 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} + */ async function sendInfo(socket) { socket.emit("info", { version: checkVersion.version, diff --git a/server/database.js b/server/database.js index afcace705..04c51c217 100644 --- a/server/database.js +++ b/server/database.js @@ -63,6 +63,10 @@ class Database { static noReject = true; + /** + * Initialize the database + * @param {Object} args Arguments to initialize DB with + */ static init(args) { // Data Directory (must be end with "/") Database.dataDir = process.env.DATA_DIR || args["data-dir"] || "./data/"; @@ -80,6 +84,12 @@ class Database { console.log(`Data Dir: ${Database.dataDir}`); } + /** + * Connect to the database + * @param {boolean} [testMode=false] Should the connection be + * started in test mode? + * @returns {Promise} + */ static async connect(testMode = false) { const acquireConnectionTimeout = 120 * 1000; @@ -129,6 +139,7 @@ class Database { console.log("SQLite Version: " + await R.getCell("SELECT sqlite_version()")); } + /** Patch the database */ static async patch() { let version = parseInt(await setting("database_version")); @@ -173,7 +184,9 @@ class Database { } /** + * Patch DB using new process * Call it from patch() only + * @private * @returns {Promise} */ static async patch2() { @@ -212,9 +225,12 @@ class Database { } /** + * Patch database using new patching process * Used it patch2() only + * @private * @param sqlFilename * @param databasePatchedFiles + * @returns {Promise} */ static async patch2Recursion(sqlFilename, databasePatchedFiles) { 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 - * @param filename + * Load an SQL file and execute it + * @param filename Filename of SQL file to import * @returns {Promise} */ 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"); let text = fs.readFileSync(filename).toString(); @@ -282,6 +298,10 @@ class Database { } } + /** + * Aquire a direct connection to database + * @returns {any} + */ static getBetterSQLite3Database() { return R.knex.client.acquireConnection(); } @@ -317,7 +337,7 @@ class Database { /** * One backup one time in this process. * Reset this.backupPath if you want to backup again - * @param version + * @param {string} version Version code of backup */ static backup(version) { if (! this.backupPath) { @@ -339,9 +359,7 @@ class Database { } } - /** - * - */ + /** Restore from most recent backup */ static restore() { if (this.backupPath) { console.error("Patching the database failed!!! Restoring the backup"); @@ -383,6 +401,7 @@ class Database { } } + /** Get the size of the database */ static getSize() { debug("Database.getSize()"); let stats = fs.statSync(Database.path); @@ -390,6 +409,10 @@ class Database { return stats.size; } + /** + * Shrink the database + * @returns {Promise} + */ static async shrink() { await R.exec("VACUUM"); } diff --git a/server/image-data-uri.js b/server/image-data-uri.js index 3ccaab7d5..b5b6f2d68 100644 --- a/server/image-data-uri.js +++ b/server/image-data-uri.js @@ -6,6 +6,11 @@ let fs = require("fs"); let ImageDataURI = (() => { + /** + * Decode the data:image/ URI + * @param {string} dataURI data:image/ URI to decode + * @returns {Object} + */ function decode(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/\""); @@ -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) { if (!data || !mediaType) { console.log("ImageDataURI :: Error :: Missing some of the required params: data, mediaType "); @@ -33,6 +44,12 @@ let ImageDataURI = (() => { return dataImgBase64; } + /** + * Write data URI to file + * @param {string} dataURI data:image/ URI + * @param {string} filePath Path to write file to + * @returns {Promise} + */ function outputFile(dataURI, filePath) { filePath = filePath || "./"; return new Promise((resolve, reject) => { diff --git a/server/jobs.js b/server/jobs.js index 0469d5cab..e4e7514b3 100644 --- a/server/jobs.js +++ b/server/jobs.js @@ -9,6 +9,11 @@ const jobs = [ }, ]; +/** + * Initialize background jobs + * @param {Object} args Arguments to pass to workers + * @returns {Bree} + */ const initBackgroundJobs = function (args) { const bree = new Bree({ root: path.resolve("server", "jobs"), diff --git a/server/notification.js b/server/notification.js index 4d72c81c7..a95af022c 100644 --- a/server/notification.js +++ b/server/notification.js @@ -32,6 +32,7 @@ class Notification { providerList = {}; + /** Initialize the notification providers */ static init() { console.log("Prepare Notification Providers"); @@ -81,13 +82,13 @@ class Notification { } /** - * - * @param notification : BeanModel - * @param msg : string General Message - * @param monitorJSON : object Monitor details (For Up/Down only) - * @param heartbeatJSON : object Heartbeat details (For Up/Down only) + * Send a notification + * @param {BeanModel} notification + * @param {string} msg General Message + * @param {Object} monitorJSON Monitor details (For Up/Down only) + * @param {Object} heartbeatJSON Heartbeat details (For Up/Down only) * @returns {Promise} Successful msg - * Throw Error with fail msg + * @throws Error with fail msg */ static async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { 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} + */ static async save(notification, notificationID, userID) { - let bean + let bean; if (notificationID) { bean = await R.findOne("notification", " id = ? AND user_id = ? ", [ notificationID, userID, - ]) + ]); if (! bean) { - throw new Error("notification not found") + throw new Error("notification not found"); } } else { - bean = R.dispense("notification") + bean = R.dispense("notification"); } bean.name = notification.name; bean.user_id = userID; bean.config = JSON.stringify(notification); bean.is_default = notification.isDefault || false; - await R.store(bean) + await R.store(bean); if (notification.applyExisting) { await applyNotificationEveryMonitor(bean.id, userID); @@ -127,19 +135,29 @@ class Notification { return bean; } + /** + * Delete a notification + * @param {number} notificationID ID of notification to delete + * @param {number} userID ID of user who created notification + * @returns {Promise} + */ static async delete(notificationID, userID) { let bean = await R.findOne("notification", " id = ? AND user_id = ? ", [ notificationID, userID, - ]) + ]); 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() { let commandExistsSync = require("command-exists").sync; 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} + */ async function applyNotificationEveryMonitor(notificationID, userID) { let monitors = await R.getAll("SELECT id FROM monitor WHERE user_id = ?", [ userID @@ -157,17 +181,17 @@ async function applyNotificationEveryMonitor(notificationID, userID) { let checkNotification = await R.findOne("monitor_notification", " monitor_id = ? AND notification_id = ? ", [ monitors[i].id, notificationID, - ]) + ]); if (! checkNotification) { let relation = R.dispense("monitor_notification"); relation.monitor_id = monitors[i].id; relation.notification_id = notificationID; - await R.store(relation) + await R.store(relation); } } } module.exports = { Notification, -} +}; diff --git a/server/password-hash.js b/server/password-hash.js index 91e5e1add..482aa475a 100644 --- a/server/password-hash.js +++ b/server/password-hash.js @@ -2,22 +2,42 @@ const passwordHashOld = require("password-hash"); const bcrypt = require("bcryptjs"); const saltRounds = 10; +/** + * Hash a password + * @param {string} password + * @returns {string} + */ exports.generate = function (password) { 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) { if (isSHA1(hash)) { - return passwordHashOld.verify(password, hash) + return passwordHashOld.verify(password, hash); } return bcrypt.compareSync(password, hash); -} +}; +/** + * Is the hash a SHA1 hash + * @param {string} hash + * @returns {boolean} + */ function isSHA1(hash) { return (typeof hash === "string" && hash.startsWith("sha1")) } +/** + * Does the hash need to be rehashed? + * @returns {boolean} + */ exports.needRehash = function (hash) { return isSHA1(hash); -} +}; diff --git a/server/ping-lite.js b/server/ping-lite.js index 0075e80bd..2c84a4734 100644 --- a/server/ping-lite.js +++ b/server/ping-lite.js @@ -8,6 +8,11 @@ const util = require("./util-server"); 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) { if (!host) { throw new Error("You must specify a host to ping!"); @@ -75,8 +80,17 @@ function Ping(host, options) { 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) { let self = this; 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) { let self = this; this._i = setInterval(function () { @@ -155,8 +171,7 @@ Ping.prototype.start = function (callback) { self.send(callback); }; -// STOP SENDING PINGS -// ================== +/** Stop sending pings */ Ping.prototype.stop = function () { 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 * Thank @pemassi * https://github.com/louislam/uptime-kuma/issues/570#issuecomment-941984094 - * @param data + * @param {any} data * @returns {string} */ function convertOutput(data) { diff --git a/server/prometheus.js b/server/prometheus.js index f91b0a13b..acf2302b9 100644 --- a/server/prometheus.js +++ b/server/prometheus.js @@ -34,6 +34,9 @@ const monitor_status = new PrometheusClient.Gauge({ class Prometheus { monitorLabelValues = {} + /** + * @param {Object} monitor Monitor object to monitor + */ constructor(monitor) { this.monitorLabelValues = { 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) { if (typeof tlsInfo !== "undefined") { diff --git a/server/rate-limiter.js b/server/rate-limiter.js index 0bacc14c7..2df956c07 100644 --- a/server/rate-limiter.js +++ b/server/rate-limiter.js @@ -2,11 +2,26 @@ const { RateLimiter } = require("limiter"); const { debug } = require("../src/util"); class KumaRateLimiter { + /** + * @param {Object} config Rate limiter configuration object + */ constructor(config) { this.errorMessage = config.errorMessage; 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} + */ async pass(callback, num = 1) { const remainingRequests = await this.removeTokens(num); debug("Rate Limit (remainingRequests):" + remainingRequests); @@ -22,6 +37,11 @@ class KumaRateLimiter { return true; } + /** + * Remove a given number of tokens + * @param {number} [num=1] Number of tokens to remove + * @returns {Promise} + */ async removeTokens(num = 1) { return await this.rateLimiter.removeTokens(num); } diff --git a/server/server.js b/server/server.js index 153cac4fd..88394472d 100644 --- a/server/server.js +++ b/server/server.js @@ -1362,6 +1362,13 @@ exports.entryPage = "dashboard"; })(); +/** + * Update notifications for a given monitor + * @param {number} monitorID ID of monitor to update + * @param {Array} notificationIDList List of new notification + * providers to add + * @returns {Promise} + */ async function updateMonitorNotification(monitorID, notificationIDList) { await R.exec("DELETE FROM monitor_notification WHERE monitor_id = ? ", [ 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} + * @throws {Error} The specified user does not own the monitor + */ async function checkOwner(userID, monitorID) { let row = await R.getRow("SELECT id FROM monitor WHERE id = ? AND user_id = ? ", [ 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) { let list = await getMonitorJSONList(socket.userID); io.to(socket.userID).emit("monitorList", list); return list; } +/** + * Function called after user login + * @param {Socket} socket Socket.io instance + * @param {Object} user User object + * @returns {Promise} + */ async function afterLogin(socket, user) { socket.userID = 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} + */ async function getMonitorJSONList(userID) { let result = {}; @@ -1430,6 +1460,12 @@ async function getMonitorJSONList(userID) { return result; } +/** + * Initialize the database + * @param {boolean} [testMode=false] Should the connection be + * started in test mode? + * @returns {Promise} + */ async function initDatabase(testMode = false) { if (! fs.existsSync(Database.path)) { console.log("Copying Database"); @@ -1464,6 +1500,12 @@ async function initDatabase(testMode = false) { 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} + */ async function startMonitor(userID, monitorID) { await checkOwner(userID, monitorID); @@ -1486,10 +1528,22 @@ async function startMonitor(userID, monitorID) { 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} + */ async function restartMonitor(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} + */ async function pauseMonitor(userID, monitorID) { await checkOwner(userID, monitorID); @@ -1505,9 +1559,7 @@ async function pauseMonitor(userID, monitorID) { } } -/** - * Resume active monitors - */ +/** Resume active monitors */ async function startMonitors() { 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} + */ async function shutdownFunction(signal) { console.log("Shutdown requested"); console.log("Called signal: " + signal); @@ -1535,6 +1592,7 @@ async function shutdownFunction(signal) { await Database.close(); } +/** Final function called before application exits */ function finalFunction() { console.log("Graceful shutdown successful!"); } diff --git a/server/util-server.js b/server/util-server.js index 2264ebea9..8f48505df 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -37,6 +37,12 @@ exports.initJWTSecret = async () => { 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} Maximum time in ms rounded to nearest integer + */ exports.tcping = function (hostname, port) { return new Promise((resolve, reject) => { tcpp.ping({ @@ -58,6 +64,11 @@ exports.tcping = function (hostname, port) { }); }; +/** + * Ping the specified machine + * @param {string} hostname Hostname / address of machine + * @returns {Promise} Time for ping in ms rounded to nearest integer + */ exports.ping = async (hostname) => { try { 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} Time for ping in ms rounded to nearest integer + */ exports.pingAsync = function (hostname, ipv6 = false) { return new Promise((resolve, reject) => { 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) { const resolver = new Resolver(); 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 representation of setting + */ exports.setting = async function (key) { let value = await R.getCell("SELECT `value` FROM setting WHERE `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} + */ exports.setSetting = async function (key, value, type = null) { let bean = await R.findOne("setting", " `key` = ? ", [ key, @@ -140,6 +177,11 @@ exports.setSetting = async function (key, value, type = null) { await R.store(bean); }; +/** + * Get settings based on type + * @param {?string} type The type of setting + * @returns {Promise} + */ exports.getSettings = async function (type) { let list = await R.getAll("SELECT `key`, `value` FROM setting WHERE `type` = ? ", [ type, @@ -158,6 +200,12 @@ exports.getSettings = async function (type) { return result; }; +/** + * Set settings based on type + * @param {?string} type Type of settings to set + * @param {Object} data Values of settings + * @returns {Promise} + */ exports.setSettings = async function (type, data) { let keyList = Object.keys(data); @@ -184,12 +232,23 @@ exports.setSettings = async function (type, data) { }; // ssl-checker by @dyaa -// param: res - response object from axios -// return an object containing the certificate information +//https://github.com/dyaa/ssl-checker/blob/master/src/index.ts +/** + * Get number of days between two dates + * @param {Date} validFrom Start date + * @param {Date} validTo End date + * @returns {number} + */ const getDaysBetween = (validFrom, validTo) => 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 daysRemaining = getDaysBetween(validFrom, validTo); if (new Date(validTo).getTime() < new Date().getTime()) { @@ -198,8 +257,11 @@ const getDaysRemaining = (validFrom, validTo) => { 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) { let link = info; let i = 0; @@ -239,6 +301,11 @@ const parseCertificateInfo = function (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) { const info = res.request.res.socket.getPeerCertificate(true); 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 -// Param: accepted_codes - an array of accepted status codes -// Return: true if the status code is within the accepted ranges, false otherwise -// Will throw an error if the provided status code is not a valid range string or code string - +/** + * Check if the provided status code is within the accepted ranges + * @param {string} status The status code to check + * @param {Array} accepted_codes An array of accepted status codes + * @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) { if (accepted_codes == null || accepted_codes.length === 0) { return false; @@ -281,6 +349,12 @@ exports.checkStatusCode = function (status, accepted_codes) { 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) => { 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) => { if (process.env.NODE_ENV === "development") { exports.allowAllOrigin(res); } }; +/** + * Allow CORS all origins + * @param {Object} res Response object from axios + */ exports.allowAllOrigin = (res) => { res.header("Access-Control-Allow-Origin", "*"); 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) => { if (! socket.userID) { throw new Error("You are not logged in."); } }; +/** Start Unit tests */ exports.startUnitTest = async () => { console.log("Starting unit test..."); 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} */ exports.convertToUTF8 = (body) => { @@ -359,6 +447,11 @@ try { }); } 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) => { try { if (logFile) { From 3d04befc1f1f547eb366056003126ffd56aa8be3 Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Thu, 21 Apr 2022 16:43:24 +0100 Subject: [PATCH 06/15] Add JSDoc to server/routers/* Signed-off-by: Matthew Nickson --- server/routers/api-router.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/routers/api-router.js b/server/routers/api-router.js index 578655e20..6d1a8d7b8 100644 --- a/server/routers/api-router.js +++ b/server/routers/api-router.js @@ -196,6 +196,11 @@ router.get("/api/status-page/heartbeat/:slug", cache("1 minutes"), async (reques } }); +/** + * Send a 403 response + * @param {Object} res Express response object + * @param {string} [msg=""] Message to send + */ function send403(res, msg = "") { res.status(403).json({ "status": "fail", From 068b9205537cb2fb20677f9b6daf9b16d4a0087b Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Thu, 21 Apr 2022 17:10:23 +0100 Subject: [PATCH 07/15] Add JSDoc to server/socket-handlers/* Signed-off-by: Matthew Nickson --- .../cloudflared-socket-handler.js | 18 ++++++++++++++++++ .../socket-handlers/database-socket-handler.js | 4 ++++ server/socket-handlers/proxy-socket-handler.js | 4 ++++ .../status-page-socket-handler.js | 5 +++++ 4 files changed, 31 insertions(+) diff --git a/server/socket-handlers/cloudflared-socket-handler.js b/server/socket-handlers/cloudflared-socket-handler.js index fa20dff8d..4c34f52f5 100644 --- a/server/socket-handlers/cloudflared-socket-handler.js +++ b/server/socket-handlers/cloudflared-socket-handler.js @@ -6,15 +6,28 @@ const io = UptimeKumaServer.getInstance().io; const prefix = "cloudflared_"; const cloudflared = new CloudflaredTunnel(); +/** + * Change running state + * @param {string} running Is it running? + * @param {string} message Message to pass + */ cloudflared.change = (running, message) => { io.to("cloudflared").emit(prefix + "running", running); io.to("cloudflared").emit(prefix + "message", message); }; +/** + * Emit an error message + * @param {string} errorMessage + */ cloudflared.error = (errorMessage) => { io.to("cloudflared").emit(prefix + "errorMessage", errorMessage); }; +/** + * Handler for cloudflared + * @param {Socket} socket Socket.io instance + */ module.exports.cloudflaredSocketHandler = (socket) => { socket.on(prefix + "join", async () => { @@ -69,6 +82,10 @@ module.exports.cloudflaredSocketHandler = (socket) => { }; +/** + * Automatically start cloudflared + * @param {string} token Cloudflared tunnel token + */ module.exports.autoStart = async (token) => { if (!token) { token = await setting("cloudflaredTunnelToken"); @@ -85,6 +102,7 @@ module.exports.autoStart = async (token) => { } }; +/** Stop cloudflared */ module.exports.stop = async () => { console.log("Stop cloudflared"); if (cloudflared) { diff --git a/server/socket-handlers/database-socket-handler.js b/server/socket-handlers/database-socket-handler.js index 42fdb129c..041cbba06 100644 --- a/server/socket-handlers/database-socket-handler.js +++ b/server/socket-handlers/database-socket-handler.js @@ -1,6 +1,10 @@ const { checkLogin } = require("../util-server"); const Database = require("../database"); +/** + * Handlers for database + * @param {Socket} socket Socket.io instance + */ module.exports = (socket) => { // Post or edit incident diff --git a/server/socket-handlers/proxy-socket-handler.js b/server/socket-handlers/proxy-socket-handler.js index 7862ff16b..e67a829ff 100644 --- a/server/socket-handlers/proxy-socket-handler.js +++ b/server/socket-handlers/proxy-socket-handler.js @@ -4,6 +4,10 @@ const { sendProxyList } = require("../client"); const { UptimeKumaServer } = require("../uptime-kuma-server"); const server = UptimeKumaServer.getInstance(); +/** + * Handlers for proxy + * @param {Socket} socket Socket.io instance + */ module.exports.proxySocketHandler = (socket) => { socket.on("addProxy", async (proxy, proxyID, callback) => { try { diff --git a/server/socket-handlers/status-page-socket-handler.js b/server/socket-handlers/status-page-socket-handler.js index 20d193823..0a0dc6865 100644 --- a/server/socket-handlers/status-page-socket-handler.js +++ b/server/socket-handlers/status-page-socket-handler.js @@ -8,6 +8,10 @@ const apicache = require("../modules/apicache"); const StatusPage = require("../model/status_page"); const { UptimeKumaServer } = require("../uptime-kuma-server"); +/** + * Socket handlers for status page + * @param {Socket} socket Socket.io instance to add listeners on + */ module.exports.statusPageSocketHandler = (socket) => { // Post or edit incident @@ -338,6 +342,7 @@ module.exports.statusPageSocketHandler = (socket) => { /** * Check slug a-z, 0-9, - only * Regex from: https://stackoverflow.com/questions/22454258/js-regex-string-validation-for-slug + * @param {string} slug Slug to test */ function checkSlug(slug) { if (typeof slug !== "string") { From 4ddbf7192099fc2c271761b0dc3080cb9954fa26 Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Thu, 21 Apr 2022 17:15:39 +0100 Subject: [PATCH 08/15] Fixed trailing spaces in database.js Signed-off-by: Matthew Nickson --- server/database.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/database.js b/server/database.js index 9cfcd7b8e..4189754a2 100644 --- a/server/database.js +++ b/server/database.js @@ -93,7 +93,7 @@ class Database { * Connect to the database * @param {boolean} [testMode=false] Should the connection be * started in test mode? - * @param {boolean} [autoloadModels=true] Should models be + * @param {boolean} [autoloadModels=true] Should models be * automatically loaded? * @param {boolean} [noLog=false] Should logs not be output? * @returns {Promise} @@ -243,7 +243,7 @@ class Database { await setSetting("databasePatchedFiles", databasePatchedFiles); } - /** + /** * Migrate status page value in setting to "status_page" table * @returns {Promise} */ From 0083485d4caa06c15064c06e6bc42f7ad2e2d190 Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Thu, 21 Apr 2022 18:30:04 +0100 Subject: [PATCH 09/15] Updated server/model/* JSDoc to match new methods Signed-off-by: Matthew Nickson --- server/model/monitor.js | 13 +++++++++++++ server/model/proxy.js | 4 ++++ server/model/status_page.js | 33 +++++++++++++++++++++++++++++++++ server/model/user.js | 11 +++++------ 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/server/model/monitor.js b/server/model/monitor.js index 00da052f7..d5dc1d905 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -103,6 +103,10 @@ class Monitor extends BeanModel { return data; } + /** + * Get all tags applied to this monitor + * @returns {Promise[]>} + */ async getTags() { return await R.getAll("SELECT mt.*, tag.name, tag.color FROM monitor_tag mt JOIN tag ON mt.tag_id = tag.id WHERE mt.monitor_id = ?", [ this.id ]); } @@ -116,6 +120,10 @@ class Monitor extends BeanModel { return Buffer.from(user + ":" + pass).toString("base64"); } + /** + * Is the TLS expiry notification enabled? + * @returns {boolean} + */ isEnabledExpiryNotification() { return Boolean(this.expiryNotification); } @@ -507,6 +515,7 @@ class Monitor extends BeanModel { }; + /** Get a heartbeat and handle errors */ const safeBeat = async () => { try { await beat(); @@ -540,6 +549,10 @@ class Monitor extends BeanModel { this.prometheus().remove(); } + /** + * Get a new prometheus instance + * @returns {Prometheus} + */ prometheus() { return new Prometheus(this); } diff --git a/server/model/proxy.js b/server/model/proxy.js index 7ddec4349..34a55d2fe 100644 --- a/server/model/proxy.js +++ b/server/model/proxy.js @@ -1,6 +1,10 @@ const { BeanModel } = require("redbean-node/dist/bean-model"); class Proxy extends BeanModel { + /** + * Return a object that ready to parse to JSON + * @returns {Object} + */ toJSON() { return { id: this._id, diff --git a/server/model/status_page.js b/server/model/status_page.js index b1befc258..605bdd3ec 100644 --- a/server/model/status_page.js +++ b/server/model/status_page.js @@ -6,6 +6,7 @@ class StatusPage extends BeanModel { static domainMappingList = { }; /** + * Loads domain mapping from DB * Return object like this: { "test-uptime.kuma.pet": "default" } * @returns {Promise} */ @@ -17,6 +18,12 @@ class StatusPage extends BeanModel { `); } + /** + * Send status page list to client + * @param {Server} io io Socket server instance + * @param {Socket} socket Socket.io instance + * @returns {Promise} + */ static async sendStatusPageList(io, socket) { let result = {}; @@ -30,6 +37,11 @@ class StatusPage extends BeanModel { return list; } + /** + * Update list of domain names + * @param {Array} domainNameList + * @returns {Promise} + */ async updateDomainNameList(domainNameList) { if (!Array.isArray(domainNameList)) { @@ -69,6 +81,10 @@ class StatusPage extends BeanModel { } } + /** + * Get list of domain names + * @returns {Array} + */ getDomainNameList() { let domainList = []; for (let domain in StatusPage.domainMappingList) { @@ -81,6 +97,10 @@ class StatusPage extends BeanModel { return domainList; } + /** + * Return a object that ready to parse to JSON + * @returns {Object} + */ async toJSON() { return { id: this.id, @@ -98,6 +118,11 @@ class StatusPage extends BeanModel { }; } + /** + * Return a object that ready to parse to JSON for public + * Only show necessary data to public + * @returns {Object} + */ async toPublicJSON() { return { slug: this.slug, @@ -113,12 +138,20 @@ class StatusPage extends BeanModel { }; } + /** + * Convert slug to status page ID + * @param {string} slug + */ static async slugToID(slug) { return await R.getCell("SELECT id FROM status_page WHERE slug = ? ", [ slug ]); } + /** + * Get path to the icon for the page + * @returns {string} + */ getIcon() { if (!this.icon) { return "/icon.svg"; diff --git a/server/model/user.js b/server/model/user.js index b243f87fc..fc619c74b 100644 --- a/server/model/user.js +++ b/server/model/user.js @@ -3,12 +3,11 @@ const passwordHash = require("../password-hash"); const { R } = require("redbean-node"); class User extends BeanModel { - /** - * + * Reset user password * Fix #1510, as in the context reset-password.js, there is no auto model mapping. Call this static function instead. - * @param userID - * @param newPassword + * @param {number} userID ID of user to update + * @param {string} newPassword * @returns {Promise} */ static async resetPassword(userID, newPassword) { @@ -19,8 +18,8 @@ class User extends BeanModel { } /** - * - * @param newPassword + * Reset this users password + * @param {string} newPassword * @returns {Promise} */ async resetPassword(newPassword) { From c2f6c5b42e58c12666ca01312454285c059313fc Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Thu, 21 Apr 2022 18:53:07 +0100 Subject: [PATCH 10/15] Added JSDoc docs for mqttAsync Signed-off-by: Matthew Nickson --- server/util-server.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server/util-server.js b/server/util-server.js index a6d1fbb26..f7c8b967b 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -108,7 +108,12 @@ exports.pingAsync = function (hostname, ipv6 = false) { /** * MQTT Monitor - * TODO: Add docs for MQTT monitor + * @param {string} hostname Hostname / address of machine to test + * @param {string} topic MQTT topic + * @param {string} okMessage Expected result + * @param {Object} [options={}] MQTT options. Contains port, username, + * password and interval (interval defaults to 20) + * @returns {Promise} */ exports.mqttAsync = function (hostname, topic, okMessage, options = {}) { return new Promise((resolve, reject) => { From 9996ba1636e7ba679091dab3e0e2a5b47357ef10 Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Thu, 21 Apr 2022 19:55:01 +0100 Subject: [PATCH 11/15] Add JSDoc to server/modules/apicache/* Signed-off-by: Matthew Nickson --- server/modules/apicache/apicache.js | 258 ++++++++++++++++-------- server/modules/apicache/memory-cache.js | 28 +++ 2 files changed, 206 insertions(+), 80 deletions(-) diff --git a/server/modules/apicache/apicache.js b/server/modules/apicache/apicache.js index 25f0a54f5..5d600500e 100644 --- a/server/modules/apicache/apicache.js +++ b/server/modules/apicache/apicache.js @@ -13,27 +13,49 @@ let t = { let instances = []; +/** + * Does a === b + * @param {any} a + * @returns {function(any): boolean} + */ let matches = function (a) { return function (b) { return a === b; }; }; +/** + * Does a!==b + * @param {any} a + * @returns {function(any): boolean} + */ let doesntMatch = function (a) { return function (b) { return !matches(a)(b); }; }; +/** + * Get log duration + * @param {number} d Time in ms + * @param {string} prefix Prefix for log + * @returns {string} Coloured log string + */ let logDuration = function (d, prefix) { let str = d > 1000 ? (d / 1000).toFixed(2) + "sec" : d + "ms"; return "\x1b[33m- " + (prefix ? prefix + " " : "") + str + "\x1b[0m"; }; +/** + * Get safe headers + * @param {Object} res Express response object + * @returns {Object} + */ function getSafeHeaders(res) { return res.getHeaders ? res.getHeaders() : res._headers; } +/** Constructor for ApiCache instance */ function ApiCache() { let memCache = new MemoryCache(); @@ -70,10 +92,10 @@ function ApiCache() { /** * Logs a message to the console if the `DEBUG` environment variable is set. - * @param {string} a - The first argument to log. - * @param {string} b - The second argument to log. - * @param {string} c - The third argument to log. - * @param {string} d - The fourth argument to log, and so on... (optional) + * @param {string} a The first argument to log. + * @param {string} b The second argument to log. + * @param {string} c The third argument to log. + * @param {string} d The fourth argument to log, and so on... (optional) * * Generated by Trelent */ @@ -90,8 +112,8 @@ function ApiCache() { * Returns true if the given request and response should be logged. * @param {Object} request The HTTP request object. * @param {Object} response The HTTP response object. - * - * Generated by Trelent + * @param {function(Object, Object):boolean} toggle + * @returns {boolean} */ function shouldCacheResponse(request, response, toggle) { let opt = globalOptions; @@ -116,10 +138,9 @@ function ApiCache() { } /** - * Adds a key to the index. - * @param {string} key The key to add. - * - * Generated by Trelent + * Add key to index array + * @param {string} key Key to add + * @param {Object} req Express request object */ function addIndexEntries(key, req) { let groupName = req.apicacheGroup; @@ -135,8 +156,11 @@ function ApiCache() { /** * Returns a new object containing only the whitelisted headers. - * @param {Object} headers The original object of header names and values. - * @param {Array.} globalOptions.headerWhitelist An array of strings representing the whitelisted header names to keep in the output object. + * @param {Object} headers The original object of header names and + * values. + * @param {string[]} globalOptions.headerWhitelist An array of + * strings representing the whitelisted header names to keep in the + * output object. * * Generated by Trelent */ @@ -152,8 +176,10 @@ function ApiCache() { } /** + * Create a cache object * @param {Object} headers The response headers to filter. - * @returns {Object} A new object containing only the whitelisted response headers. + * @returns {Object} A new object containing only the whitelisted + * response headers. * * Generated by Trelent */ @@ -170,8 +196,9 @@ function ApiCache() { /** * Sets a cache value for the given key. * @param {string} key The cache key to set. - * @param {*} value The cache value to set. - * @param {number} duration How long in milliseconds the cached response should be valid for (defaults to 1 hour). + * @param {any} value The cache value to set. + * @param {number} duration How long in milliseconds the cached + * response should be valid for (defaults to 1 hour). * * Generated by Trelent */ @@ -199,7 +226,8 @@ function ApiCache() { /** * Appends content to the response. - * @param {string|Buffer} content The content to append. + * @param {Object} res Express response object + * @param {(string|Buffer)} content The content to append. * * Generated by Trelent */ @@ -229,11 +257,15 @@ function ApiCache() { } /** - * Monkeypatches the response object to add cache control headers and create a cache object. - * @param {Object} req - The request object. - * @param {Object} res - The response object. - * - * Generated by Trelent + * Monkeypatches the response object to add cache control headers + * and create a cache object. + * @param {Object} req Express request object + * @param {Object} res Express response object + * @param {function} next Function to call next + * @param {string} key Key to add response as + * @param {number} duration Time to cache response for + * @param {string} strDuration Duration in string form + * @param {function(Object, Object):boolean} toggle */ function makeResponseCacheable(req, res, next, key, duration, strDuration, toggle) { // monkeypatch res.end to create cache object @@ -302,11 +334,15 @@ function ApiCache() { } /** - * @param {Request} request - * @param {Response} response - * @returns {boolean|undefined} true if the request should be cached, false otherwise. If undefined, defaults to true. - * - * Generated by Trelent + * Send a cached response to client + * @param {Request} request Express request object + * @param {Response} response Express response object + * @param {object} cacheObject Cache object to send + * @param {function(Object, Object):boolean} toggle + * @param {function} next Function to call next + * @param {number} duration Not used + * @returns {boolean|undefined} true if the request should be + * cached, false otherwise. If undefined, defaults to true. */ function sendCachedResponse(request, response, cacheObject, toggle, next, duration) { if (toggle && !toggle(request, response)) { @@ -348,12 +384,19 @@ function ApiCache() { return response.end(data, cacheObject.encoding); } + /** Sync caching options */ function syncOptions() { for (let i in middlewareOptions) { Object.assign(middlewareOptions[i].options, globalOptions, middlewareOptions[i].localOptions); } } + /** + * Clear key from cache + * @param {string} target Key to clear + * @param {boolean} isAutomatic Is the key being cleared automatically + * @returns {number} + */ this.clear = function (target, isAutomatic) { let group = index.groups[target]; let redis = globalOptions.redisClient; @@ -430,10 +473,11 @@ function ApiCache() { /** * Converts a duration string to an integer number of milliseconds. - * @param {string} duration - The string to convert. - * @returns {number} The converted value in milliseconds, or the defaultDuration if it can't be parsed. - * - * Generated by Trelent + * @param {(string|number)} duration The string to convert. + * @param {number} defaultDuration The default duration to return if + * can't parse duration + * @returns {number} The converted value in milliseconds, or the + * defaultDuration if it can't be parsed. */ function parseDuration(duration, defaultDuration) { if (typeof duration === "number") { @@ -457,17 +501,24 @@ function ApiCache() { return defaultDuration; } + /** + * Parse duration + * @param {(number|string)} duration + * @returns {number} Duration parsed to a number + */ this.getDuration = function (duration) { return parseDuration(duration, globalOptions.defaultDuration); }; /** - * Return cache performance statistics (hit rate). Suitable for putting into a route: + * Return cache performance statistics (hit rate). Suitable for + * putting into a route: * * app.get('/api/cache/performance', (req, res) => { * res.json(apicache.getPerformance()) * }) * + * @returns {any[]} */ this.getPerformance = function () { return performanceArray.map(function (p) { @@ -475,6 +526,11 @@ function ApiCache() { }); }; + /** + * Get index of a group + * @param {string} group + * @returns {number} + */ this.getIndex = function (group) { if (group) { return index.groups[group]; @@ -483,6 +539,14 @@ function ApiCache() { } }; + /** + * Express middleware + * @param {(string|number)} strDuration Duration to cache responses + * for. + * @param {function(Object, Object):boolean} middlewareToggle + * @param {Object} localOptions Options for APICache + * @returns + */ this.middleware = function cache(strDuration, middlewareToggle, localOptions) { let duration = instance.getDuration(strDuration); let opt = {}; @@ -506,63 +570,72 @@ function ApiCache() { options(localOptions); /** - * A Function for non tracking performance - */ + * A Function for non tracking performance + */ function NOOPCachePerformance() { this.report = this.hit = this.miss = function () {}; // noop; } /** - * A function for tracking and reporting hit rate. These statistics are returned by the getPerformance() call above. - */ + * A function for tracking and reporting hit rate. These + * statistics are returned by the getPerformance() call above. + */ function CachePerformance() { /** - * Tracks the hit rate for the last 100 requests. - * If there have been fewer than 100 requests, the hit rate just considers the requests that have happened. - */ + * Tracks the hit rate for the last 100 requests. If there + * have been fewer than 100 requests, the hit rate just + * considers the requests that have happened. + */ this.hitsLast100 = new Uint8Array(100 / 4); // each hit is 2 bits /** - * Tracks the hit rate for the last 1000 requests. - * If there have been fewer than 1000 requests, the hit rate just considers the requests that have happened. - */ + * Tracks the hit rate for the last 1000 requests. If there + * have been fewer than 1000 requests, the hit rate just + * considers the requests that have happened. + */ this.hitsLast1000 = new Uint8Array(1000 / 4); // each hit is 2 bits /** - * Tracks the hit rate for the last 10000 requests. - * If there have been fewer than 10000 requests, the hit rate just considers the requests that have happened. - */ + * Tracks the hit rate for the last 10000 requests. If there + * have been fewer than 10000 requests, the hit rate just + * considers the requests that have happened. + */ this.hitsLast10000 = new Uint8Array(10000 / 4); // each hit is 2 bits /** - * Tracks the hit rate for the last 100000 requests. - * If there have been fewer than 100000 requests, the hit rate just considers the requests that have happened. - */ + * Tracks the hit rate for the last 100000 requests. If + * there have been fewer than 100000 requests, the hit rate + * just considers the requests that have happened. + */ this.hitsLast100000 = new Uint8Array(100000 / 4); // each hit is 2 bits /** - * The number of calls that have passed through the middleware since the server started. - */ + * The number of calls that have passed through the + * middleware since the server started. + */ this.callCount = 0; /** - * The total number of hits since the server started - */ + * The total number of hits since the server started + */ this.hitCount = 0; /** - * The key from the last cache hit. This is useful in identifying which route these statistics apply to. - */ + * The key from the last cache hit. This is useful in + * identifying which route these statistics apply to. + */ this.lastCacheHit = null; /** - * The key from the last cache miss. This is useful in identifying which route these statistics apply to. - */ + * The key from the last cache miss. This is useful in + * identifying which route these statistics apply to. + */ this.lastCacheMiss = null; /** - * Return performance statistics - */ + * Return performance statistics + * @returns {Object} + */ this.report = function () { return { lastCacheHit: this.lastCacheHit, @@ -579,10 +652,13 @@ function ApiCache() { }; /** - * Computes a cache hit rate from an array of hits and misses. - * @param {Uint8Array} array An array representing hits and misses. - * @returns a number between 0 and 1, or null if the array has no hits or misses - */ + * Computes a cache hit rate from an array of hits and + * misses. + * @param {Uint8Array} array An array representing hits and + * misses. + * @returns {number} a number between 0 and 1, or null if + * the array has no hits or misses + */ this.hitRate = function (array) { let hits = 0; let misses = 0; @@ -608,16 +684,17 @@ function ApiCache() { }; /** - * Record a hit or miss in the given array. It will be recorded at a position determined - * by the current value of the callCount variable. - * @param {Uint8Array} array An array representing hits and misses. - * @param {boolean} hit true for a hit, false for a miss - * Each element in the array is 8 bits, and encodes 4 hit/miss records. - * Each hit or miss is encoded as to bits as follows: - * 00 means no hit or miss has been recorded in these bits - * 01 encodes a hit - * 10 encodes a miss - */ + * Record a hit or miss in the given array. It will be + * recorded at a position determined by the current value of + * the callCount variable. + * @param {Uint8Array} array An array representing hits and + * misses. + * @param {boolean} hit true for a hit, false for a miss + * Each element in the array is 8 bits, and encodes 4 + * hit/miss records. Each hit or miss is encoded as to bits + * as follows: 00 means no hit or miss has been recorded in + * these bits 01 encodes a hit 10 encodes a miss + */ this.recordHitInArray = function (array, hit) { let arrayIndex = ~~(this.callCount / 4) % array.length; let bitOffset = (this.callCount % 4) * 2; // 2 bits per record, 4 records per uint8 array element @@ -627,9 +704,11 @@ function ApiCache() { }; /** - * Records the hit or miss in the tracking arrays and increments the call count. - * @param {boolean} hit true records a hit, false records a miss - */ + * Records the hit or miss in the tracking arrays and + * increments the call count. + * @param {boolean} hit true records a hit, false records a + * miss + */ this.recordHit = function (hit) { this.recordHitInArray(this.hitsLast100, hit); this.recordHitInArray(this.hitsLast1000, hit); @@ -642,18 +721,18 @@ function ApiCache() { }; /** - * Records a hit event, setting lastCacheMiss to the given key - * @param {string} key The key that had the cache hit - */ + * Records a hit event, setting lastCacheMiss to the given key + * @param {string} key The key that had the cache hit + */ this.hit = function (key) { this.recordHit(true); this.lastCacheHit = key; }; /** - * Records a miss event, setting lastCacheMiss to the given key - * @param {string} key The key that had the cache miss - */ + * Records a miss event, setting lastCacheMiss to the given key + * @param {string} key The key that had the cache miss + */ this.miss = function (key) { this.recordHit(false); this.lastCacheMiss = key; @@ -664,6 +743,13 @@ function ApiCache() { performanceArray.push(perf); + /** + * Cache a request + * @param {Object} req Express request object + * @param {Object} res Express response object + * @param {function} next Function to call next + * @returns {any} + */ let cache = function (req, res, next) { function bypass() { debug("bypass detected, skipping cache."); @@ -771,6 +857,11 @@ function ApiCache() { return cache; }; + /** + * Process options + * @param {Object} options + * @returns {Object} + */ this.options = function (options) { if (options) { Object.assign(globalOptions, options); @@ -791,6 +882,7 @@ function ApiCache() { } }; + /** Reset the index */ this.resetIndex = function () { index = { all: [], @@ -798,6 +890,11 @@ function ApiCache() { }; }; + /** + * Create a new instance of ApiCache + * @param {Object} config Config to pass + * @returns {ApiCache} + */ this.newInstance = function (config) { let instance = new ApiCache(); @@ -808,6 +905,7 @@ function ApiCache() { return instance; }; + /** Clone this instance */ this.clone = function () { return this.newInstance(this.options()); }; diff --git a/server/modules/apicache/memory-cache.js b/server/modules/apicache/memory-cache.js index ad831e2e4..a91eee324 100644 --- a/server/modules/apicache/memory-cache.js +++ b/server/modules/apicache/memory-cache.js @@ -3,6 +3,15 @@ function MemoryCache() { this.size = 0; } +/** + * + * @param {string} key Key to store cache as + * @param {any} value Value to store + * @param {number} time Time to store for + * @param {function(any, string)} timeoutCallback Callback to call in + * case of timeout + * @returns {Object} + */ MemoryCache.prototype.add = function (key, value, time, timeoutCallback) { let old = this.cache[key]; let instance = this; @@ -22,6 +31,11 @@ MemoryCache.prototype.add = function (key, value, time, timeoutCallback) { return entry; }; +/** + * Delete a cache entry + * @param {string} key Key to delete + * @returns {null} + */ MemoryCache.prototype.delete = function (key) { let entry = this.cache[key]; @@ -36,18 +50,32 @@ MemoryCache.prototype.delete = function (key) { return null; }; +/** + * Get value of key + * @param {string} key + * @returns {Object} + */ MemoryCache.prototype.get = function (key) { let entry = this.cache[key]; return entry; }; +/** + * Get value of cache entry + * @param {string} key + * @returns {any} + */ MemoryCache.prototype.getValue = function (key) { let entry = this.get(key); return entry && entry.value; }; +/** + * Clear cache + * @returns {boolean} + */ MemoryCache.prototype.clear = function () { Object.keys(this.cache).forEach(function (key) { this.delete(key); From 09e61d9d6321076313790d606282bfb83a8c4065 Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Thu, 21 Apr 2022 20:02:18 +0100 Subject: [PATCH 12/15] Changed Array to type[] Signed-off-by: Matthew Nickson --- server/model/group.js | 2 +- server/model/monitor.js | 2 +- server/model/status_page.js | 4 ++-- server/server.js | 2 +- server/util-server.js | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/server/model/group.js b/server/model/group.js index 40fb1c7b9..f24fe99db 100644 --- a/server/model/group.js +++ b/server/model/group.js @@ -27,7 +27,7 @@ class Group extends BeanModel { /** * Get all monitors - * @returns {Array} + * @returns {Bean[]} */ async getMonitorList() { return R.convertToBeans("monitor", await R.getAll(` diff --git a/server/model/monitor.js b/server/model/monitor.js index d5dc1d905..b97e424d1 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -840,7 +840,7 @@ class Monitor extends BeanModel { * than target days * @param {number} daysRemaining Number of days remaining on certifcate * @param {number} targetDays Number of days to alert after - * @param {Array>} notificationList List of notification providers + * @param {LooseObject[]} notificationList List of notification providers * @returns {Promise} */ async sendCertNotificationByTargetDays(daysRemaining, targetDays, notificationList) { diff --git a/server/model/status_page.js b/server/model/status_page.js index 605bdd3ec..a48dc7b26 100644 --- a/server/model/status_page.js +++ b/server/model/status_page.js @@ -39,7 +39,7 @@ class StatusPage extends BeanModel { /** * Update list of domain names - * @param {Array} domainNameList + * @param {string[]} domainNameList * @returns {Promise} */ async updateDomainNameList(domainNameList) { @@ -83,7 +83,7 @@ class StatusPage extends BeanModel { /** * Get list of domain names - * @returns {Array} + * @returns {Object[]} */ getDomainNameList() { let domainList = []; diff --git a/server/server.js b/server/server.js index 944a2fffb..1c7a2b6e8 100644 --- a/server/server.js +++ b/server/server.js @@ -1475,7 +1475,7 @@ try { /** * Update notifications for a given monitor * @param {number} monitorID ID of monitor to update - * @param {Array} notificationIDList List of new notification + * @param {number[]} notificationIDList List of new notification * providers to add * @returns {Promise} */ diff --git a/server/util-server.js b/server/util-server.js index f7c8b967b..72f633a23 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -387,7 +387,7 @@ exports.checkCertificate = function (res) { /** * Check if the provided status code is within the accepted ranges * @param {string} status The status code to check - * @param {Array} acceptedCodes An array of accepted status codes + * @param {string[]} acceptedCodes An array of accepted status codes * @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 */ From 1eecdec2d96ac54a3da55e4e99dfd7a7c672728f Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Fri, 22 Apr 2022 18:42:47 +0100 Subject: [PATCH 13/15] Update JSDoc for better clarity Co-authored-by: Nelson Chan Signed-off-by: Matthew Nickson --- server/jobs/util-worker.js | 4 +++- server/model/monitor.js | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/server/jobs/util-worker.js b/server/jobs/util-worker.js index 89629a2fa..1aeec794d 100644 --- a/server/jobs/util-worker.js +++ b/server/jobs/util-worker.js @@ -3,7 +3,9 @@ const Database = require("../database"); const path = require("path"); /** - * Send message to parent process + * Send message to parent process for logging + * since worker_thread does not have access to stdout, this is used + * instead of console.log() * @param {any} any The message to log */ const log = function (any) { diff --git a/server/model/monitor.js b/server/model/monitor.js index b97e424d1..60c984f31 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -145,7 +145,7 @@ class Monitor extends BeanModel { } /** - * Get status codes that are acceptable + * Get accepted status codes * @returns {Object} */ getAcceptedStatuscodes() { @@ -658,7 +658,7 @@ class Monitor extends BeanModel { } /** - * Send certificate information to information + * Send certificate information to client * @param {Server} io Socket server instance * @param {number} monitorID ID of monitor to send * @param {number} userID ID of user to send to From 46da5e51be43330d8f194186b50cd9b9b03dfa24 Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Fri, 22 Apr 2022 19:10:13 +0100 Subject: [PATCH 14/15] Fix JSDoc grammar Signed-off-by: Matthew Nickson --- server/model/group.js | 2 +- server/model/heartbeat.js | 4 ++-- server/model/incident.js | 2 +- server/model/monitor.js | 2 +- server/model/proxy.js | 2 +- server/model/status_page.js | 4 ++-- server/model/tag.js | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/server/model/group.js b/server/model/group.js index f24fe99db..599b758bc 100644 --- a/server/model/group.js +++ b/server/model/group.js @@ -4,7 +4,7 @@ const { R } = require("redbean-node"); class Group extends BeanModel { /** - * Return a object that ready to parse to JSON for public + * Return an object that ready to parse to JSON for public * Only show necessary data to public * @param {boolean} [showTags=false] Should the JSON include monitor tags * @returns {Object} diff --git a/server/model/heartbeat.js b/server/model/heartbeat.js index 38ba6f46a..a0b40d08e 100644 --- a/server/model/heartbeat.js +++ b/server/model/heartbeat.js @@ -14,7 +14,7 @@ const { BeanModel } = require("redbean-node/dist/bean-model"); class Heartbeat extends BeanModel { /** - * Return a object that ready to parse to JSON for public + * Return an object that ready to parse to JSON for public * Only show necessary data to public * @returns {Object} */ @@ -28,7 +28,7 @@ class Heartbeat extends BeanModel { } /** - * Return a object that ready to parse to JSON + * Return an object that ready to parse to JSON * @returns {Object} */ toJSON() { diff --git a/server/model/incident.js b/server/model/incident.js index e28478f77..f954cc74a 100644 --- a/server/model/incident.js +++ b/server/model/incident.js @@ -3,7 +3,7 @@ const { BeanModel } = require("redbean-node/dist/bean-model"); class Incident extends BeanModel { /** - * Return a object that ready to parse to JSON for public + * Return an object that ready to parse to JSON for public * Only show necessary data to public * @returns {Object} */ diff --git a/server/model/monitor.js b/server/model/monitor.js index 60c984f31..e1a2b80bf 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -41,7 +41,7 @@ class Monitor extends BeanModel { } /** - * Return a object that ready to parse to JSON + * Return an object that ready to parse to JSON * @returns {Object} */ async toJSON(includeSensitiveData = true) { diff --git a/server/model/proxy.js b/server/model/proxy.js index 34a55d2fe..4517b11bf 100644 --- a/server/model/proxy.js +++ b/server/model/proxy.js @@ -2,7 +2,7 @@ const { BeanModel } = require("redbean-node/dist/bean-model"); class Proxy extends BeanModel { /** - * Return a object that ready to parse to JSON + * Return an object that ready to parse to JSON * @returns {Object} */ toJSON() { diff --git a/server/model/status_page.js b/server/model/status_page.js index a48dc7b26..99b284ab4 100644 --- a/server/model/status_page.js +++ b/server/model/status_page.js @@ -98,7 +98,7 @@ class StatusPage extends BeanModel { } /** - * Return a object that ready to parse to JSON + * Return an object that ready to parse to JSON * @returns {Object} */ async toJSON() { @@ -119,7 +119,7 @@ class StatusPage extends BeanModel { } /** - * Return a object that ready to parse to JSON for public + * Return an object that ready to parse to JSON for public * Only show necessary data to public * @returns {Object} */ diff --git a/server/model/tag.js b/server/model/tag.js index 5a8d97d31..b31eb8682 100644 --- a/server/model/tag.js +++ b/server/model/tag.js @@ -3,7 +3,7 @@ const { BeanModel } = require("redbean-node/dist/bean-model"); class Tag extends BeanModel { /** - * Return a object that ready to parse to JSON + * Return an object that ready to parse to JSON * @returns {Object} */ toJSON() { From 587faecf87a87ffc513c8b2ef0010ba64a84dc14 Mon Sep 17 00:00:00 2001 From: Matthew Nickson Date: Fri, 22 Apr 2022 19:42:42 +0100 Subject: [PATCH 15/15] Made value nullable in apicache JSDoc Signed-off-by: Matthew Nickson --- server/modules/apicache/apicache.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/modules/apicache/apicache.js b/server/modules/apicache/apicache.js index 5d600500e..41930b24d 100644 --- a/server/modules/apicache/apicache.js +++ b/server/modules/apicache/apicache.js @@ -656,7 +656,7 @@ function ApiCache() { * misses. * @param {Uint8Array} array An array representing hits and * misses. - * @returns {number} a number between 0 and 1, or null if + * @returns {?number} a number between 0 and 1, or null if * the array has no hits or misses */ this.hitRate = function (array) {