diff --git a/README.md b/README.md index 34e34020f..d0b1ac17e 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ It is a temporary live demo, all data will be deleted after 10 minutes. Sponsore ## ⭐ Features -- Monitoring uptime for HTTP(s) / TCP / HTTP(s) Keyword / HTTP(s) Json Query / Ping / DNS Record / Push / Steam Game Server / Docker Containers +- Monitoring uptime for HTTP(s) / TCP / HTTP(s) Keyword / HTTP(s) Json Query / Websocket / Ping / DNS Record / Push / Steam Game Server / Docker Containers - Fancy, Reactive, Fast UI/UX - Notifications via Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP), and [90+ notification services, click here for the full list](https://github.com/louislam/uptime-kuma/tree/master/src/components/notifications) - 20-second intervals diff --git a/db/knex_migrations/2025-02-15-2312-add-wstest.js b/db/knex_migrations/2025-02-15-2312-add-wstest.js new file mode 100644 index 000000000..966cff13c --- /dev/null +++ b/db/knex_migrations/2025-02-15-2312-add-wstest.js @@ -0,0 +1,15 @@ +// Add websocket URL +exports.up = function (knex) { + return knex.schema + .alterTable("monitor", function (table) { + table.text("wsurl"); + table.boolean("ws_ignore_headers").notNullable().defaultTo(false); + }); +}; + +exports.down = function (knex) { + return knex.schema.alterTable("monitor", function (table) { + table.dropColumn("wsurl"); + table.dropColumn("ws_ignore_headers"); + }); +}; diff --git a/server/model/monitor.js b/server/model/monitor.js index 3ad8cfafc..77498fa3e 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -96,6 +96,8 @@ class Monitor extends BeanModel { parent: this.parent, childrenIDs: preloadData.childrenIDs.get(this.id) || [], url: this.url, + wsurl: this.wsurl, + wsIgnoreHeaders: this.getWsIgnoreHeaders(), method: this.method, hostname: this.hostname, port: this.port, @@ -255,6 +257,14 @@ class Monitor extends BeanModel { return Boolean(this.ignoreTls); } + /** + * Parse to boolean + * @returns {boolean} Should WS headers be ignored? + */ + getWsIgnoreHeaders() { + return Boolean(this.wsIgnoreHeaders); + } + /** * Parse to boolean * @returns {boolean} Is the monitor in upside down mode? diff --git a/server/monitor-types/websocket.js b/server/monitor-types/websocket.js index 47ac1df2a..3e84b5b83 100644 --- a/server/monitor-types/websocket.js +++ b/server/monitor-types/websocket.js @@ -1,5 +1,6 @@ const { MonitorType } = require("./monitor-type"); -const { UP } = require("../../src/util"); +const WebSocket = require("ws"); +const { UP, DOWN } = require("../../src/util"); const childProcessAsync = require("promisify-child-process"); class websocket extends MonitorType { @@ -9,8 +10,8 @@ class websocket extends MonitorType { * @inheritdoc */ async check(monitor, heartbeat, _server) { - //let status_code = await this.attemptUpgrade(monitor.url); - let statusCode = await this.curlTest(monitor.url); + let statusCode = await this.attemptUpgrade(monitor); + //let statusCode = await this.curlTest(monitor.url); this.updateStatus(heartbeat, statusCode); } @@ -28,40 +29,45 @@ class websocket extends MonitorType { } /** - * Checks if status code is 101 and sets status + * Checks if status code is 1000(Normal Closure) and sets status and message * @param {object} heartbeat The heartbeat object to update. - * @param {string} statusCode Status code from curl + * @param {[ string, int ]} status Array containing a status message and response code * @returns {void} */ - updateStatus(heartbeat, statusCode) { - if (statusCode === "101") { - heartbeat.status = UP; - } - heartbeat.msg = statusCode; + updateStatus(heartbeat, [ message, code ]) { + heartbeat.status = code === 1000 ? UP : DOWN; + heartbeat.msg = message; } - // Attempt at using websocket library. Abandoned this idea because of the lack of control of headers. Certain websocket servers don't return the Sec-WebSocket-Accept, which causes websocket to error out. - // async attemptUpgrade(hostname) { - // return new Promise((resolve) => { - // const ws = new WebSocket('wss://' + hostname); + /** + * Uses the builtin Websocket API to establish a connection to target server + * @param {object} monitor The monitor object for input parameters. + * @returns {[ string, int ]} Array containing a status message and response code + */ + async attemptUpgrade(monitor) { + return new Promise((resolve) => { + const ws = new WebSocket(monitor.wsurl); - // ws.addEventListener("open", (event) => { - // ws.close(); - // }); + ws.addEventListener("open", (event) => { + ws.close(1000); + }); - // ws.onerror = (error) => { - // console.log(error.message); - // }; + ws.onerror = (error) => { + // console.log(error.message); + // Give user the choice to ignore Sec-WebSocket-Accept header + if (monitor.wsIgnoreHeaders && error.message === "Invalid Sec-WebSocket-Accept header") { + resolve([ "101 - OK", 1000 ]); + } + resolve([ error.message, error.code ]); + }; - // ws.onclose = (event) => { - // if (event.code === 1005) { - // resolve(true); - // } else { - // resolve(false); - // } - // }; - // }) - // } + ws.onclose = (event) => { + // console.log(event.message); + // console.log(event.code); + resolve([ "101 - OK", event.code ]); + }; + }); + } } module.exports = { diff --git a/server/server.js b/server/server.js index ec5ad49f6..0e19b6e62 100644 --- a/server/server.js +++ b/server/server.js @@ -790,6 +790,8 @@ let needSetup = false; bean.parent = monitor.parent; bean.type = monitor.type; bean.url = monitor.url; + bean.wsurl = monitor.wsurl; + bean.wsIgnoreHeaders = monitor.wsIgnoreHeaders; bean.method = monitor.method; bean.body = monitor.body; bean.headers = monitor.headers; diff --git a/src/lang/en.json b/src/lang/en.json index e215f1031..81adc0736 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -86,6 +86,7 @@ "ignoreTLSError": "Ignore TLS/SSL errors for HTTPS websites", "ignoreTLSErrorGeneral": "Ignore TLS/SSL error for connection", "upsideDownModeDescription": "Flip the status upside down. If the service is reachable, it is DOWN.", + "wsIgnoreHeadersDescription": "Test non compliant Websocket servers that don't respond with correct headers.", "maxRedirectDescription": "Maximum number of redirects to follow. Set to 0 to disable redirects.", "Upside Down Mode": "Upside Down Mode", "Max. Redirects": "Max. Redirects", diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index b17b6be24..679eac298 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -117,11 +117,17 @@ -