node-red-contrib-dynamic-websocket
Version:
A node that dynamically connects to a WebSocket URL, can send and receive messages, and reports connection state changes.
371 lines (337 loc) • 19.7 kB
HTML
<script type="text/javascript">
RED.nodes.registerType('dynamic-websocket',{
category: 'network',
color: '#a6bbcf',
defaults: {
name: {value:""},
url: {value:""},
allowSelfSigned: {value: false},
autoReconnect: {value: false},
reconnectAttempts: {value: 0},
reconnectInterval: {value: 5000},
useExponentialBackoff: {value: false},
authType: {value: "none"},
username: {value: ""},
password: {value: ""},
token: {value: ""},
tokenLocation: {value: "header"},
tokenKey: {value: "Authorization"},
headers: {value: ""},
transformMessages: {value: false},
messageFormat: {value: "json"},
binarySupport: {value: false},
validateMessages: {value: false},
messageTemplate: {value: ""}
},
credentials: {
password: {type: "password"},
token: {type: "password"}
},
inputs:1,
outputs:3,
icon: "bridge.svg",
label: function() {
return this.name||"Dynamic WebSocket";
},
paletteLabel: "Dynamic WebSocket",
oneditprepare: function() {
var node = this;
$("#node-input-url").on("change", function() {
node.url = $(this).val();
});
// Show/hide reconnection settings based on autoReconnect checkbox
function toggleReconnectOptions() {
if ($("#node-input-autoReconnect").is(":checked")) {
$("#reconnect-settings").show();
} else {
$("#reconnect-settings").hide();
}
}
$("#node-input-autoReconnect").on("change", toggleReconnectOptions);
toggleReconnectOptions();
// Show/hide authentication settings based on authType dropdown
function toggleAuthOptions() {
var authType = $("#node-input-authType").val();
$("#basic-auth-settings").hide();
$("#token-auth-settings").hide();
if (authType === "basic") {
$("#basic-auth-settings").show();
} else if (authType === "token") {
$("#token-auth-settings").show();
}
}
$("#node-input-authType").on("change", toggleAuthOptions);
toggleAuthOptions();
// Show/hide token location settings
function toggleTokenLocationOptions() {
var tokenLocation = $("#node-input-tokenLocation").val();
$("#token-key-settings").show();
}
$("#node-input-tokenLocation").on("change", toggleTokenLocationOptions);
toggleTokenLocationOptions();
// Initialize the custom headers textarea with formatted JSON
if (node.headers) {
try {
// If it's already an object, stringify it
if (typeof node.headers === 'object') {
$("#node-input-headers").val(JSON.stringify(node.headers, null, 2));
} else {
// Try to parse and format if it's a string
var headersObj = JSON.parse(node.headers);
$("#node-input-headers").val(JSON.stringify(headersObj, null, 2));
}
} catch(e) {
// If it's not valid JSON, leave as is
}
}
// Initialize the message template textarea with formatted JSON
if (node.messageTemplate) {
try {
// If it's already an object, stringify it
if (typeof node.messageTemplate === 'object') {
$("#node-input-messageTemplate").val(JSON.stringify(node.messageTemplate, null, 2));
} else {
// Try to parse and format if it's a string
var templateObj = JSON.parse(node.messageTemplate);
$("#node-input-messageTemplate").val(JSON.stringify(templateObj, null, 2));
}
} catch(e) {
// If it's not valid JSON, leave as is
}
}
// Show/hide message transformation settings based on checkbox
function toggleTransformOptions() {
if ($("#node-input-transformMessages").is(":checked")) {
$("#transform-settings").show();
} else {
$("#transform-settings").hide();
}
}
$("#node-input-transformMessages").on("change", toggleTransformOptions);
toggleTransformOptions();
// Add template examples
$("#add-template-example").on("click", function() {
var format = $("#node-input-messageFormat").val();
var template = "";
if (format === "json") {
template = {
"type": "message",
"id": "$id",
"content": "$content",
"timestamp": new Date().toISOString()
};
} else if (format === "mqtt") {
template = {
"topic": "$topic",
"payload": "$payload",
"qos": 0,
"retain": false
};
}
$("#node-input-messageTemplate").val(JSON.stringify(template, null, 2));
});
}
});
</script>
<script type="text/html" data-template-name="dynamic-websocket">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-url"><i class="fa fa-globe"></i> Default URL</label>
<input type="text" id="node-input-url" placeholder="ws://example.com/socket">
</div>
<div class="form-row">
<label for="node-input-allowSelfSigned"><i class="fa fa-lock"></i> Allow Self-Signed Certificates</label>
<input type="checkbox" id="node-input-allowSelfSigned" style="display: inline-block; width: auto; vertical-align: top;">
<span style="margin-left: 5px;">Enable this for connections with self-signed or expired certificates</span>
</div>
<div class="form-row">
<label for="node-input-autoReconnect"><i class="fa fa-refresh"></i> Auto Reconnect</label>
<input type="checkbox" id="node-input-autoReconnect" style="display: inline-block; width: auto; vertical-align: top;">
<span style="margin-left: 5px;">Automatically attempt to reconnect when disconnected</span>
</div>
<div id="reconnect-settings">
<div class="form-row">
<label for="node-input-reconnectAttempts"><i class="fa fa-repeat"></i> Max Reconnect Attempts</label>
<input type="number" id="node-input-reconnectAttempts" min="0" style="width: 100px;">
<span style="margin-left: 5px;">0 = unlimited attempts</span>
</div>
<div class="form-row">
<label for="node-input-reconnectInterval"><i class="fa fa-clock-o"></i> Reconnect Interval (ms)</label>
<input type="number" id="node-input-reconnectInterval" min="100" style="width: 100px;">
</div>
<div class="form-row">
<label for="node-input-useExponentialBackoff"><i class="fa fa-line-chart"></i> Use Exponential Backoff</label>
<input type="checkbox" id="node-input-useExponentialBackoff" style="display: inline-block; width: auto; vertical-align: top;">
<span style="margin-left: 5px;">Increase delay between reconnection attempts</span>
</div>
</div>
<div class="form-row">
<label for="node-input-authType"><i class="fa fa-key"></i> Authentication</label>
<select id="node-input-authType" style="width: 70%;">
<option value="none">None</option>
<option value="basic">Basic Authentication</option>
<option value="token">Token Authentication</option>
</select>
</div>
<div id="basic-auth-settings">
<div class="form-row">
<label for="node-input-username"><i class="fa fa-user"></i> Username</label>
<input type="text" id="node-input-username">
</div>
<div class="form-row">
<label for="node-input-password"><i class="fa fa-lock"></i> Password</label>
<input type="password" id="node-input-password">
</div>
</div>
<div id="token-auth-settings">
<div class="form-row">
<label for="node-input-token"><i class="fa fa-id-badge"></i> Token</label>
<input type="password" id="node-input-token">
</div>
<div class="form-row">
<label for="node-input-tokenLocation"><i class="fa fa-map-marker"></i> Token Location</label>
<select id="node-input-tokenLocation" style="width: 70%;">
<option value="header">Header</option>
<option value="url">URL Parameter</option>
</select>
</div>
<div id="token-key-settings" class="form-row">
<label for="node-input-tokenKey"><i class="fa fa-tag"></i> Token Key</label>
<input type="text" id="node-input-tokenKey" placeholder="Authorization">
<div style="max-width: 460px; margin-top: 5px; margin-left: 105px; color: #999;">
<small>For headers, use 'Authorization' for Bearer tokens. For URL, specify the parameter name.</small>
</div>
</div>
</div>
<div class="form-row">
<label for="node-input-headers"><i class="fa fa-list"></i> Custom Headers</label>
<textarea id="node-input-headers" rows="4" style="width: 70%; font-family: monospace;" placeholder='{"X-Custom-Header": "value"}'></textarea>
<div style="max-width: 460px; margin-top: 5px; margin-left: 105px; color: #999;">
<small>Enter custom headers as JSON object. Example: {"X-Custom-Header": "value"}</small>
</div>
</div>
<div class="form-section-header">
<i class="fa fa-exchange"></i> Message Transformation
</div>
<div class="form-row">
<label for="node-input-transformMessages"><i class="fa fa-magic"></i> Transform Messages</label>
<input type="checkbox" id="node-input-transformMessages" style="display: inline-block; width: auto; vertical-align: top;">
<span style="margin-left: 5px;">Apply template and validation to outgoing messages</span>
</div>
<div id="transform-settings">
<div class="form-row">
<label for="node-input-messageFormat"><i class="fa fa-code"></i> Message Format</label>
<select id="node-input-messageFormat" style="width: 70%;">
<option value="json">JSON</option>
<option value="mqtt">MQTT</option>
<option value="custom">Custom</option>
</select>
</div>
<div class="form-row">
<label for="node-input-binarySupport"><i class="fa fa-file-o"></i> Binary Support</label>
<input type="checkbox" id="node-input-binarySupport" style="display: inline-block; width: auto; vertical-align: top;">
<span style="margin-left: 5px;">Enable support for binary data transmission</span>
</div>
<div class="form-row">
<label for="node-input-validateMessages"><i class="fa fa-check-circle"></i> Validate Messages</label>
<input type="checkbox" id="node-input-validateMessages" style="display: inline-block; width: auto; vertical-align: top;">
<span style="margin-left: 5px;">Validate messages against template before sending</span>
</div>
<div class="form-row">
<label for="node-input-messageTemplate"><i class="fa fa-file-text-o"></i> Message Template</label>
<textarea id="node-input-messageTemplate" rows="6" style="width: 70%; font-family: monospace;" placeholder='{"type": "message", "content": "$content"}'></textarea>
<div style="max-width: 460px; margin-top: 5px; margin-left: 105px; color: #999;">
<small>Enter message template as JSON object. Use $placeholder for dynamic values.</small>
<button id="add-template-example" type="button" style="margin-top: 5px;">Add Example Template</button>
</div>
</div>
</div>
</script>
<script type="text/html" data-help-name="dynamic-websocket">
<p>A node that dynamically connects to a WebSocket URL, can send and receive messages, and reports connection state changes.</p>
<h3>Input</h3>
<dl class="message-properties">
<dt>url <span class="property-type">string</span></dt>
<dd>The WebSocket URL to connect to. If provided, it will override the default URL and be stored for future use.</dd>
<dt>close <span class="property-type">boolean</span></dt>
<dd>If set to true, it will close the current WebSocket connection and clear the stored URL.</dd>
<dt>message <span class="property-type">object | string</span></dt>
<dd>The message to be sent through the WebSocket. This will be stringified before sending.</dd>
<dt>allowSelfSigned <span class="property-type">boolean</span></dt>
<dd>If set, overrides the node's configuration for accepting self-signed certificates for this connection.</dd>
<dt>reconnect <span class="property-type">boolean</span></dt>
<dd>If set to true, forces an immediate reconnection attempt using the current URL.</dd>
<dt>autoReconnect <span class="property-type">boolean</span></dt>
<dd>If set, overrides the node's configuration for automatic reconnection.</dd>
<dt>reconnectAttempts <span class="property-type">number</span></dt>
<dd>If set, overrides the node's configuration for maximum reconnection attempts (0 = unlimited).</dd>
<dt>reconnectInterval <span class="property-type">number</span></dt>
<dd>If set, overrides the node's configuration for reconnection interval in milliseconds.</dd>
<dt>useExponentialBackoff <span class="property-type">boolean</span></dt>
<dd>If set, overrides the node's configuration for using exponential backoff for reconnection attempts.</dd>
<dt>authType <span class="property-type">string</span></dt>
<dd>Authentication type: 'none', 'basic', or 'token'. Overrides the node's configuration.</dd>
<dt>username <span class="property-type">string</span></dt>
<dd>Username for basic authentication. Overrides the node's configuration.</dd>
<dt>password <span class="property-type">string</span></dt>
<dd>Password for basic authentication. Overrides the node's configuration.</dd>
<dt>token <span class="property-type">string</span></dt>
<dd>Token for token-based authentication. Overrides the node's configuration.</dd>
<dt>tokenLocation <span class="property-type">string</span></dt>
<dd>Location for token: 'header' or 'url'. Overrides the node's configuration.</dd>
<dt>tokenKey <span class="property-type">string</span></dt>
<dd>Key name for the token (header name or URL parameter). Overrides the node's configuration.</dd>
<dt>headers <span class="property-type">object | string</span></dt>
<dd>Custom headers to include in the WebSocket connection. Can be an object or a JSON string. Overrides the node's configuration.</dd>
<dt>transformMessages <span class="property-type">boolean</span></dt>
<dd>Enable or disable message transformation. Overrides the node's configuration.</dd>
<dt>messageFormat <span class="property-type">string</span></dt>
<dd>Set the message format ("json", "mqtt", "custom"). Overrides the node's configuration.</dd>
<dt>binarySupport <span class="property-type">boolean</span></dt>
<dd>Enable or disable binary data support. Overrides the node's configuration.</dd>
<dt>validateMessages <span class="property-type">boolean</span></dt>
<dd>Enable or disable message validation. Overrides the node's configuration.</dd>
<dt>messageTemplate <span class="property-type">object | string</span></dt>
<dd>Message template to apply. Can be an object or a JSON string. Overrides the node's configuration.</dd>
<dt>binary <span class="property-type">boolean</span></dt>
<dd>Set to true to send binary data. The message must be a Buffer.</dd>
<dt>skipTransform <span class="property-type">boolean</span></dt>
<dd>Set to true to skip transformation for this message only.</dd>
</dl>
<h3>Outputs</h3>
<dl class="message-properties">
<dt>1. Received Output (top)</dt>
<dd>
<dl class="message-properties">
<dt>payload <span class="property-type">object | string</span></dt>
<dd>The data received from the WebSocket connection. If the received data is valid JSON, it will be parsed into an object. Otherwise, it will be output as a string.</dd>
</dl>
</dd>
<dt>2. State Output (middle)</dt>
<dd>
<dl class="message-properties">
<dt>state <span class="property-type">string</span></dt>
<dd>The current state of the WebSocket connection. Can be "disconnected" or "error".</dd>
<dt>error <span class="property-type">string</span></dt>
<dd>If the state is "error", this will contain the error message.</dd>
</dl>
</dd>
<dt>3. Connected Output (bottom)</dt>
<dd>
<dl class="message-properties">
<dt>state <span class="property-type">string</span></dt>
<dd>Outputs "Connected" when a connection is established.</dd>
</dl>
</dd>
</dl>
<h3>Details</h3>
<p>This node will remember the last URL it connected to, even after a Node-RED restart. To change the URL, send a new message with the desired URL in <code>msg.url</code>. To close the connection and clear the stored URL, send a message with <code>msg.close = true</code>.</p>
<p>To send a message through the WebSocket, include <code>msg.message</code> in the input message.</p>
<p>When connected, the node status will display the current WebSocket URL.</p>
<p>The node attempts to parse incoming messages as JSON. If successful, the output will be a JSON object. If parsing fails, the raw message will be output as a string.</p>
<p>The second output will emit a message whenever the connection state changes (except when closed by <code>msg.close</code>), allowing you to monitor and react to connection issues.</p>
<p>The third output will emit a "Connected" message when a connection is established, allowing you to trigger actions upon successful connection.</p>
</script>