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..437368780 --- /dev/null +++ b/db/knex_migrations/2025-02-15-2312-add-wstest.js @@ -0,0 +1,15 @@ +// Add websocket ignore headers and websocket subprotocol +exports.up = function (knex) { + return knex.schema + .alterTable("monitor", function (table) { + table.boolean("ws_ignore_sec_websocket_accept_header").notNullable().defaultTo(false); + table.string("ws_subprotocol", 255).notNullable().defaultTo(""); + }); +}; + +exports.down = function (knex) { + return knex.schema.alterTable("monitor", function (table) { + table.dropColumn("ws_ignore_sec_websocket_accept_header"); + table.dropColumn("ws_subprotocol"); + }); +}; diff --git a/server/model/monitor.js b/server/model/monitor.js index 5999d93e7..2bb2d475a 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, + wsIgnoreSecWebsocketAcceptHeader: this.getWsIgnoreSecWebsocketAcceptHeader(), + wsSubprotocol: this.wsSubprotocol, 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? + */ + getWsIgnoreSecWebsocketAcceptHeader() { + return Boolean(this.wsIgnoreSecWebsocketAcceptHeader); + } + /** * Parse to boolean * @returns {boolean} Is the monitor in upside down mode? diff --git a/server/monitor-types/websocket-upgrade.js b/server/monitor-types/websocket-upgrade.js new file mode 100644 index 000000000..e85c4baa7 --- /dev/null +++ b/server/monitor-types/websocket-upgrade.js @@ -0,0 +1,52 @@ +const { MonitorType } = require("./monitor-type"); +const WebSocket = require("ws"); +const { UP, DOWN } = require("../../src/util"); + +class WebSocketMonitorType extends MonitorType { + name = "websocket-upgrade"; + + /** + * @inheritdoc + */ + async check(monitor, heartbeat, _server) { + const [ message, code ] = await this.attemptUpgrade(monitor); + heartbeat.status = code === 1000 ? UP : DOWN; + heartbeat.msg = message; + } + + /** + * 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) => { + let ws; + //If user selected a subprotocol, sets Sec-WebSocket-Protocol header. Subprotocol Identifier column: https://www.iana.org/assignments/websocket/websocket.xml#subprotocol-name + ws = monitor.wsSubprotocol === "" ? new WebSocket(monitor.url) : new WebSocket(monitor.url, monitor.wsSubprotocol); + + ws.addEventListener("open", (event) => { + // Immediately close the connection + ws.close(1000); + }); + + ws.onerror = (error) => { + // Give user the choice to ignore Sec-WebSocket-Accept header + if (monitor.wsIgnoreSecWebsocketAcceptHeader && error.message === "Invalid Sec-WebSocket-Accept header") { + resolve([ "101 - OK", 1000 ]); + } + // Upgrade failed, return message to user + resolve([ error.message, error.code ]); + }; + + ws.onclose = (event) => { + // Upgrade success, connection closed successfully + resolve([ "101 - OK", event.code ]); + }; + }); + } +} + +module.exports = { + WebSocketMonitorType, +}; diff --git a/server/server.js b/server/server.js index ec5ad49f6..78a7fba37 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.wsIgnoreSecWebsocketAcceptHeader = monitor.wsIgnoreSecWebsocketAcceptHeader; + bean.wsSubprotocol = monitor.wsSubprotocol; bean.method = monitor.method; bean.body = monitor.body; bean.headers = monitor.headers; diff --git a/server/uptime-kuma-server.js b/server/uptime-kuma-server.js index 062f098d7..f65275311 100644 --- a/server/uptime-kuma-server.js +++ b/server/uptime-kuma-server.js @@ -111,6 +111,7 @@ class UptimeKumaServer { // Set Monitor Types UptimeKumaServer.monitorTypeList["real-browser"] = new RealBrowserMonitorType(); UptimeKumaServer.monitorTypeList["tailscale-ping"] = new TailscalePing(); + UptimeKumaServer.monitorTypeList["websocket-upgrade"] = new WebSocketMonitorType(); UptimeKumaServer.monitorTypeList["dns"] = new DnsMonitorType(); UptimeKumaServer.monitorTypeList["mqtt"] = new MqttMonitorType(); UptimeKumaServer.monitorTypeList["snmp"] = new SNMPMonitorType(); @@ -549,6 +550,7 @@ module.exports = { // Must be at the end to avoid circular dependencies const { RealBrowserMonitorType } = require("./monitor-types/real-browser-monitor-type"); const { TailscalePing } = require("./monitor-types/tailscale-ping"); +const { WebSocketMonitorType } = require("./monitor-types/websocket-upgrade"); const { DnsMonitorType } = require("./monitor-types/dns"); const { MqttMonitorType } = require("./monitor-types/mqtt"); const { SNMPMonitorType } = require("./monitor-types/snmp"); diff --git a/src/lang/en.json b/src/lang/en.json index c32bebaae..0b61b80ea 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -86,6 +86,40 @@ "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.", + "ignoreSecWebsocketAcceptHeaderDescription": "Allows the server to not reply with Sec-WebSocket-Accept header, if the websocket upgrade succeeds.", + "Ignore Sec-WebSocket-Accept header": "Ignore {0} header", + "wsSubprotocolDescription": "For more information on subprotocols, please consult the {documentation}", + "WebSocket Application Messaging Protocol": "WAMP (The WebSocket Application Messaging Protocol)", + "Session Initiation Protocol": "WebSocket Transport for SIP (Session Initiation Protocol)", + "Network API for Notification Channel": "OMA RESTful Network API for Notification Channel", + "Web Process Control Protocol": "Web Process Control Protocol (WPCP)", + "Advanced Message Queuing Protocol": "Advanced Message Queuing Protocol (AMQP) 1.0+", + "jsflow": "jsFlow pubsub/queue Protocol", + "Reverse Web Process Control": "Reverse Web Process Control Protocol (RWPCP)", + "Extensible Messaging and Presence Protocol": "WebSocket Transport for the Extensible Messaging and Presence Protocol (XMPP)", + "Smart Home IP": "SHIP - Smart Home IP", + "Miele Cloud Connect Protocol": "Miele Cloud Connect Protocol", + "Push Channel Protocol": "Push Channel Protocol", + "Message Session Relay Protocol": "WebSocket Transport for MSRP (Message Session Relay Protocol)", + "Binary Floor Control Protocol": "WebSocket Transport for BFCP (Binary Floor Control Protocol)", + "Softvelum Low Delay Protocol": "Softvelum Low Delay Protocol", + "OPC UA Connection Protocol": "OPC UA Connection Protocol", + "OPC UA JSON Encoding": "OPC UA JSON Encoding", + "Swindon Web Server Protocol": "Swindon Web Server Protocol (JSON encoding)", + "Broadband Forum User Services Platform": "USP (Broadband Forum User Services Platform)", + "Constrained Application Protocol": "Constrained Application Protocol (CoAP)", + "Softvelum WebSocket signaling protocol": "Softvelum WebSocket Signaling Protocol", + "Cobra Real Time Messaging Protocol": "Cobra Real Time Messaging Protocol", + "Declarative Resource Protocol": "Declarative Resource Protocol", + "BACnet Secure Connect Hub Connection": "BACnet Secure Connect Hub Connection", + "BACnet Secure Connect Direct Connection": "BACnet Secure Connect Direct Connection", + "WebSocket Transport for JMAP": "WebSocket Transport for JMAP (JSON Meta Application Protocol)", + "ITU-T T.140 Real-Time Text": "ITU-T T.140 Real-Time Text", + "Done.best IoT Protocol": "Done.best IoT Protocol", + "Collection Update": "The Collection Update Websocket Subprotocol", + "Text IRC Protocol": "Text IRC Protocol", + "Binary IRC Protocol": "Binary IRC Protocol", + "Penguin Statistics Live Protocol v3": "Penguin Statistics Live Protocol v3 (Protobuf encoding)", "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 a83f91cab..559f77fdb 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -46,6 +46,10 @@ + + @@ -113,9 +117,85 @@ -
+
- + +
+ + +
+ + + + +
@@ -335,8 +415,8 @@
- jsonata.org - {{ $t('playground') }} + jsonata.org + {{ $t('playground') }}
@@ -475,8 +555,8 @@ - jsonata.org - {{ $t('here') }} + jsonata.org + {{ $t('here') }}
@@ -546,7 +626,7 @@
@@ -555,8 +635,8 @@ - jsonata.org - {{ $t('here') }} + jsonata.org + {{ $t('here') }}
@@ -621,6 +701,16 @@
+
+ + + Sec-Websocket-Accept + +
+ {{ $t("ignoreSecWebsocketAcceptHeaderDescription") }} +
+
+