From 5bc9a0d64a70aed61d39930d5e80f28a277e5ae2 Mon Sep 17 00:00:00 2001
From: PoleTransformer <you@example.com>
Date: Sun, 9 Mar 2025 17:30:12 -0700
Subject: [PATCH] add subprotocol selection + translation keys

---
 .../2025-02-15-2312-add-wstest.js             |  4 +-
 server/model/monitor.js                       |  1 +
 server/monitor-types/websocket-upgrade.js     |  4 +-
 server/server.js                              |  1 +
 src/lang/en.json                              | 31 ++++++++
 src/pages/EditMonitor.vue                     | 71 +++++++++++++++++++
 6 files changed, 110 insertions(+), 2 deletions(-)

diff --git a/db/knex_migrations/2025-02-15-2312-add-wstest.js b/db/knex_migrations/2025-02-15-2312-add-wstest.js
index c2b640f3e..36f81acfa 100644
--- a/db/knex_migrations/2025-02-15-2312-add-wstest.js
+++ b/db/knex_migrations/2025-02-15-2312-add-wstest.js
@@ -1,13 +1,15 @@
-// Add websocket ignore headers
+// Add websocket ignore headers and websocket subprotocol
 exports.up = function (knex) {
     return knex.schema
         .alterTable("monitor", function (table) {
             table.boolean("ws_ignore_headers").notNullable().defaultTo(false);
+            table.string("subprotocol", 255).notNullable().defaultTo("");
         });
 };
 
 exports.down = function (knex) {
     return knex.schema.alterTable("monitor", function (table) {
         table.dropColumn("ws_ignore_headers");
+        table.dropColumn("subprotocol");
     });
 };
diff --git a/server/model/monitor.js b/server/model/monitor.js
index fecde2d24..e5f2f4499 100644
--- a/server/model/monitor.js
+++ b/server/model/monitor.js
@@ -97,6 +97,7 @@ class Monitor extends BeanModel {
             childrenIDs: preloadData.childrenIDs.get(this.id) || [],
             url: this.url,
             wsIgnoreHeaders: this.getWsIgnoreHeaders(),
+            subprotocol: this.subprotocol,
             method: this.method,
             hostname: this.hostname,
             port: this.port,
diff --git a/server/monitor-types/websocket-upgrade.js b/server/monitor-types/websocket-upgrade.js
index aa18ef07a..8b2b64be3 100644
--- a/server/monitor-types/websocket-upgrade.js
+++ b/server/monitor-types/websocket-upgrade.js
@@ -21,7 +21,9 @@ class WebSocketMonitorType extends MonitorType {
      */
     async attemptUpgrade(monitor) {
         return new Promise((resolve) => {
-            const ws = new WebSocket(monitor.url);
+            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.subprotocol === "" ? new WebSocket(monitor.url) : new WebSocket(monitor.url, monitor.subprotocol);
 
             ws.addEventListener("open", (event) => {
                 // Immediately close the connection
diff --git a/server/server.js b/server/server.js
index d8aa67a38..2d5a911e0 100644
--- a/server/server.js
+++ b/server/server.js
@@ -791,6 +791,7 @@ let needSetup = false;
                 bean.type = monitor.type;
                 bean.url = monitor.url;
                 bean.wsIgnoreHeaders = monitor.wsIgnoreHeaders;
+                bean.subprotocol = monitor.subprotocol;
                 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 ff2923b58..22d5ac2e2 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -88,6 +88,37 @@
     "upsideDownModeDescription": "Flip the status upside down. If the service is reachable, it is DOWN.",
     "wsIgnoreHeadersDescription": "The websocket upgrade succeeds, but the server does not reply with Sec-WebSocket-Accept header.",
     "Ignore Sec-WebSocket-Accept header": "Ignore Sec-WebSocket-Accept header",
+    "wamp": "WAMP (The WebSocket Application Messaging Protocol)",
+    "sip": "WebSocket Transport for SIP (Session Initiation Protocol)",
+    "notificationchannel-netapi-rest.openmobilealliance.org": "OMA RESTful Network API for Notification Channel",
+    "wpcp": "Web Process Control Protocol (WPCP)",
+    "amqp": "Advanced Message Queuing Protocol (AMQP) 1.0+",
+    "jsflow": "jsFlow pubsub/queue protocol",
+    "rwpcp": "Reverse Web Process Control Protocol (RWPCP)",
+    "xmpp": "WebSocket Transport for the Extensible Messaging and Presence Protocol (XMPP)",
+    "ship": "SHIP - Smart Home IP",
+    "mielecloudconnect": "Miele Cloud Connect Protocol",
+    "v10.pcp.sap.com": "Push Channel Protocol",
+    "msrp": "WebSocket Transport for MSRP (Message Session Relay Protocol)",
+    "bfcp": "WebSocket Transport for BFCP (Binary Floor Control Protocol)",
+    "sldp.softvelum.com": "Softvelum Low Delay Protocol",
+    "opcua+uacp": "OPC UA Connection Protocol",
+    "opcua+uajson": "OPC UA JSON Encoding",
+    "v1.swindon-lattice+json": "Swindon Web Server Protocol (JSON encoding)",
+    "v1.usp": "USP (Broadband Forum User Services Platform)",
+    "coap": "Constrained Application Protocol (CoAP)",
+    "webrtc.softvelum.com": "Softvelum WebSocket signaling protocol",
+    "cobra.v2.json": "Cobra Real Time Messaging Protocol",
+    "drp": "Declarative Resource Protocol",
+    "hub.bsc.bacnet.org": "BACnet Secure Connect Hub Connection",
+    "dc.bsc.bacnet.org": "BACnet Secure Connect Direct Connection",
+    "jmap": "WebSocket Transport for JMAP (JSON Meta Application Protocol)",
+    "t140": "ITU-T T.140 Real-Time Text",
+    "done": "Done.best IoT Protocol",
+    "collection-update": "The Collection Update Websocket Subprotocol",
+    "text.ircv3.net": "Text IRC Protocol",
+    "binary.ircv3.net": "Binary IRC Protocol",
+    "v3.penguin-stats.live+proto": "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 c5a61f6bd..aa686850b 100644
--- a/src/pages/EditMonitor.vue
+++ b/src/pages/EditMonitor.vue
@@ -122,6 +122,77 @@
                                 <input id="url" v-model="monitor.url" type="url" class="form-control" :pattern="monitor.type !== 'websocket-upgrade' ? 'https?://.+' : 'wss?://.+'" required data-testid="url-input">
                             </div>
 
+                            <!-- Websocket Subprotocol -->
+                            <div v-if="monitor.type === 'websocket-upgrade'" class="my-3">
+                                <label for="type" class="form-label">{{ $t("Subprotocol") }}</label>
+                                <select id="type" v-model="monitor.subprotocol" class="form-select">
+                                    <option value="" selected>{{ $t("None") }}</option>
+                                    <option value="MBWS.huawei.com">MBWS</option>
+                                    <option value="MBLWS.huawei.com">MBLWS</option>
+                                    <option value="soap">soap</option>
+                                    <option value="wamp">{{ $t("wamp") }}</option>
+                                    <option value="v10.stomp">STOMP 1.0</option>
+                                    <option value="v11.stomp">STOMP 1.1</option>
+                                    <option value="v12.stomp">STOMP 1.2</option>
+                                    <option value="ocpp1.2">OCPP 1.2</option>
+                                    <option value="ocpp1.5">OCPP 1.5</option>
+                                    <option value="ocpp1.6">OCPP 1.6</option>
+                                    <option value="ocpp2.0">OCPP 2.0</option>
+                                    <option value="ocpp2.0.1">OCPP 2.0.1</option>
+                                    <option value="ocpp2.1">OCPP 2.1</option>
+                                    <option value="rfb">RFB</option>
+                                    <option value="sip">{{ $t("sip") }}</option>
+                                    <option value="notificationchannel-netapi-rest.openmobilealliance.org">{{ $t("notificationchannel-netapi-rest.openmobilealliance.org") }}</option>
+                                    <option value="wpcp">{{ $t("wpcp") }}</option>
+                                    <option value="amqp">{{ $t("amqp") }}</option>
+                                    <option value="mqtt">MQTT</option>
+                                    <option value="jsflow">{{ $t("jsflow") }}</option>
+                                    <option value="rwpcp">{{ $t("rwpcp") }}</option>
+                                    <option value="xmpp">{{ $t("xmpp") }}</option>
+                                    <option value="ship">{{ $t("ship") }}</option>
+                                    <option value="mielecloudconnect">{{ $t("mielecloudconnect") }}</option>
+                                    <option value="v10.pcp.sap.com">{{ $t("v10.pcp.sap.com") }}</option>
+                                    <option value="msrp">{{ $t("msrp") }}</option>
+                                    <option value="v1.saltyrtc.org">SaltyRTC 1.0</option>
+                                    <option value="TLCP-2.0.0.lightstreamer.com">TLCP 2.0.0</option>
+                                    <option value="bfcp">{{ $t("bfcp") }}</option>
+                                    <option value="sldp.softvelum.com">{{ $t("sldp.softvelum.com") }}</option>
+                                    <option value="opcua+uacp">{{ $t("opcua+uacp") }}</option>
+                                    <option value="opcua+uajson">{{ $t("opcua+uajson") }}</option>
+                                    <option value="v1.swindon-lattice+json">{{ $t("v1.swindon-lattice+json") }}</option>
+                                    <option value="v1.usp">{{ $t("v1.usp") }}</option>
+                                    <option value="mles-websocket">mles-websocket</option>
+                                    <option value="coap">{{ $t("coap") }}</option>
+                                    <option value="TLCP-2.1.0.lightstreamer.com">TLCP 2.1.0</option>
+                                    <option value="sqlnet.oracle.com">sqlnet</option>
+                                    <option value="oneM2M.R2.0.json">oneM2M R2.0 JSON</option>
+                                    <option value="oneM2M.R2.0.xml">oneM2M R2.0 XML</option>
+                                    <option value="oneM2M.R2.0.cbor">oneM2M R2.0 CBOR</option>
+                                    <option value="transit">Transit</option>
+                                    <option value="2016.serverpush.dash.mpeg.org">MPEG-DASH-ServerPush-23009-6-2017</option>
+                                    <option value="2018.mmt.mpeg.org">MPEG-MMT-23008-1-2018</option>
+                                    <option value="clue">clue</option>
+                                    <option value="webrtc.softvelum.com">{{ $t("webrtc.softvelum.com") }}</option>
+                                    <option value="cobra.v2.json">{{ $t("cobra.v2.json") }}</option>
+                                    <option value="drp">{{ $t("drp") }}</option>
+                                    <option value="hub.bsc.bacnet.org">{{ $t("hub.bsc.bacnet.org") }}</option>
+                                    <option value="dc.bsc.bacnet.org">{{ $t("dc.bsc.bacnet.org") }}</option>
+                                    <option value="jmap">{{ $t("jmap") }}</option>
+                                    <option value="t140">{{ $t("t140") }}</option>
+                                    <option value="done">{{ $t("done") }}</option>
+                                    <option value="TLCP-2.2.0.lightstreamer.com">TLCP 2.2.0</option>
+                                    <option value="collection-update">{{ $t("collection-update") }}</option>
+                                    <option value="TLCP-2.3.0.lightstreamer.com">TLCP 2.3.0</option>
+                                    <option value="text.ircv3.net">{{ $t("text.ircv3.net") }}</option>
+                                    <option value="binary.ircv3.net">{{ $t("binary.ircv3.net") }}</option>
+                                    <option value="v3.penguin-stats.live+proto">{{ $t("v3.penguin-stats.live+proto") }}</option>
+                                    <option value="TLCP-2.4.0.lightstreamer.com">TLCP 2.4.0</option>
+                                    <option value="TLCP-2.5.0.lightstreamer.com">TLCP 2.5.0</option>
+                                    <option value="Redfish">Redfish DSP0266</option>
+                                    <option value="bidib">webBiDiB</option>
+                                </select>
+                            </div>
+
                             <!-- gRPC URL -->
                             <div v-if="monitor.type === 'grpc-keyword' " class="my-3">
                                 <label for="grpc-url" class="form-label">{{ $t("URL") }}</label>