@yousolution/node-red-contrib-you-tunnel-websocket 0.0.1 β†’ 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,11 +1,96 @@
1
- # @yousolution/node-red-contrib-you-tunnel-websocket
1
+ # node-red-contrib-you-tunnel-websocket
2
2
 
3
- A Node-RED node to create a tunnel websocket
3
+ [![npm version](https://badge.fury.io/js/%40yousolution%2Fnode-red-contrib-you-tunnel-websocket.svg)](https://badge.fury.io/js/%40yousolution%2Fnode-red-contrib-you-tunnel-websocket)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@yousolution/node-red-contrib-you-tunnel-websocket.svg)](https://www.npmjs.com/package/@yousolution/node-red-contrib-you-tunnel-websocket)
5
+
6
+ A Node-RED node collection for creating WebSocket tunnels that enable HTTP traffic routing over WebSocket connections.
4
7
 
5
8
  ## Install
6
9
 
7
- Run the following command in the root directory of your Node-RED install
10
+ Install via Node-RED palette manager, or:
11
+
12
+ ```bash
13
+ npm install @yousolution/node-red-contrib-you-tunnel-websocket
14
+ ```
15
+
16
+ ## Nodes
17
+
18
+ ### wstunnel
19
+
20
+ Configuration node for WebSocket tunnel server settings.
21
+
22
+ #### Properties
23
+
24
+ - **Name**: Descriptive name for the tunnel configuration *(required)*
25
+ - **Host**: Hostname or IP address to bind the WebSocket server (default: `localhost`)
26
+ - **Port**: Port number for the WebSocket server (default: `443`)
27
+ - **Path**: Optional path component for the WebSocket URL
28
+ - **Tunnel ID Header**: HTTP header name used to identify tunnel connections (default: `x-tunnel-id`)
29
+ - **Log Level**: Logging verbosity level (`trace`, `debug`, `info`, `warning`, `error`)
30
+
31
+ ### wstunnel server
32
+
33
+ Creates a WebSocket server that can tunnel HTTP traffic over WebSocket connections.
34
+
35
+ #### Properties
36
+
37
+ - **Name**: Optional name for the node
38
+ - **wstunnel**: Reference to a wstunnel configuration node *(required)*
39
+
40
+ #### Inputs
41
+
42
+ *none*
43
+
44
+ #### Outputs
45
+
46
+ *none*
47
+
48
+ #### Details
49
+
50
+ The server node creates a WebSocket server that listens for client connections. HTTP traffic can be tunneled through established WebSocket connections using tunnel ID headers.
51
+
52
+ Status indicators:
53
+ - πŸ”΄ **disconnected**: Server is not running
54
+ - 🟒 **listening on port [port]**: Server is active and listening
55
+ - 🟒 **clients [count]**: Number of connected WebSocket clients
56
+
57
+ ## Usage
58
+
59
+ ### Basic Setup
60
+
61
+ 1. Add a **wstunnel** configuration node:
62
+ - Set Name, Host, Port, and other options
63
+
64
+ 2. Add a **wstunnel server** node:
65
+ - Select the configuration node
66
+ - Deploy the flow
67
+
68
+ 3. The server will start listening for WebSocket connections
69
+
70
+ ### Connection URLs
71
+
72
+ WebSocket servers are accessible at:
73
+ ```
74
+ ws://[host]:[port][/path]
75
+ ```
76
+
77
+ For secure connections:
78
+ ```
79
+ wss://[host]:[port][/path]
80
+ ```
81
+
82
+ ## Requirements
83
+
84
+ - **Node-RED**: >= 2.0.0
85
+ - **Node.js**: >= 12.0.0
86
+
87
+ ## License
88
+
89
+ Apache-2.0
90
+
91
+ ## Author
8
92
 
9
- npm install @yousolution/node-red-contrib-you-tunnel-websocket
93
+ Andrea Trentin (<andrea.trentin@yousolution.cloud>)
10
94
 
11
- See [license](https://github.com/yousolution-cloud/node-red-contrib-you-tunnel-websocket/blob/master/LICENSE) (Apache License Version 2.0).
95
+ Repository: [yousolution-cloud/node-red-contrib-you-tunnel-websocket](https://github.com/yousolution-cloud/node-red-contrib-you-tunnel-websocket)</content>
96
+ <parameter name="filePath">README.md
@@ -10,7 +10,7 @@ services:
10
10
  - ./data:/data
11
11
  ports:
12
12
  - '1880:1880'
13
- # - '4443:4443'
13
+ - '4443:4443'
14
14
  deploy:
15
15
  resources:
16
16
  limits:
@@ -13,20 +13,41 @@
13
13
 
14
14
  <script type="text/x-red" data-template-name="wstunnel">
15
15
  <div class="form-row">
16
- <label for="node-config-input-name"><i class="fa fa-bookmark"></i> name</label>
16
+ <label for="node-config-input-name"><i class="fa fa-bookmark"></i> Name</label>
17
17
  <input type="text" id="node-config-input-name" placeholder="tunnel name" />
18
18
  </div>
19
19
  <div class="form-row">
20
20
  <label for="node-config-input-host"><i class="fa fa-bookmark"></i> Host</label>
21
21
  <input type="text" id="node-config-input-host" placeholder="localhost" style="width: 40%;" />
22
22
  <label for="node-config-input-port" style="margin-left: 10px; width: 35px; "> Port</label>
23
- <input type="text" id="node-config-input-port" placeholder="22" style="width:45px">
23
+ <input type="text" id="node-config-input-port" placeholder="443" style="width:45px">
24
24
  </div>
25
25
  <div class="form-row">
26
- <label for="node-config-input-srcAddr"><i class="fa fa-gear"></i> Source Address</label>
27
- <input type="text" id="node-config-input-srcAddr" placeholder="localhost">
26
+ <label for="node-config-input-path"><i class="fa fa-gear"></i> Path </label>
27
+ <input type="text" id="node-config-input-path" placeholder="">
28
28
  </div>
29
29
  <div class="form-row">
30
+ <span><b>Tunnel Id</b></span>
31
+ </div>
32
+ <div class="form-row" style="padding-left: 10px">
33
+ <span>Name of the http custom header</span>
34
+ </div>
35
+ <div class="form-row">
36
+ <label for="node-config-input-tunnelIdHeaderName"><i class="fa fa-gear"></i> Header </label>
37
+ <input type="text" id="node-config-input-tunnelIdHeaderName" placeholder="x-tunnel-id">
38
+ </div>
39
+ <div class="form-row">
40
+ <label for="node-config-input-logLevel"><i class="fa fa-list"></i> Log Level</label>
41
+ <select id="node-config-input-logLevel" style="width: 150px;">
42
+ <option value="trace">Trace</option>
43
+ <option value="debug">Debug</option>
44
+ <option value="info">Info</option>
45
+ <option value="warning">Warning</option>
46
+ <option value="error">Error</option>
47
+ </select>
48
+ </div>
49
+
50
+ <!-- <div class="form-row">
30
51
  <label for="node-config-input-srcPort"><i class="fa fa-gear"></i> Source Port</label>
31
52
  <input type="text" id="node-config-input-srcPort">
32
53
  </div>
@@ -41,7 +62,7 @@
41
62
  <div class="form-row">
42
63
  <label for="node-config-input-timeout"><i class="fa fa-gear"></i> Timeout</label>
43
64
  <input type="text" id="node-config-input-timeout">
44
- </div>
65
+ </div> -->
45
66
  </script>
46
67
 
47
68
  <script type="text/javascript">
@@ -52,11 +73,9 @@
52
73
  name: { value: '', required: true },
53
74
  host: { value: '' },
54
75
  port: { value: '' },
55
- srcAddr: { value: '' },
56
- srcPort: { value: '', required: true },
57
- dstAddr: { value: '' },
58
- dstPort: { value: '' },
59
- timeout: { value: '' },
76
+ path: { value: '' },
77
+ tunnelIdHeaderName: { value: '' },
78
+ logLevel: { value: 'info' },
60
79
  },
61
80
  label: function () {
62
81
  return this.name;
@@ -65,14 +84,90 @@
65
84
  });
66
85
  </script>
67
86
 
68
- <script type="text/x-red" data-template-name="wstunnel client">
87
+ <script type="text/html" data-help-name="wstunnel">
88
+ <p>Configuration node for WebSocket tunnel settings. This node defines the connection parameters and logging configuration for WebSocket tunnel servers.</p>
89
+
90
+ <h3>Configuration</h3>
91
+ <dl class="message-properties">
92
+ <dt>Name <span class="property-type">string</span></dt>
93
+ <dd>A descriptive name for this tunnel configuration. This is required and will be displayed in the node list.</dd>
94
+
95
+ <dt>Host <span class="property-type">string</span></dt>
96
+ <dd>The hostname or IP address where the WebSocket server will bind. Defaults to <code>localhost</code> if not specified.</dd>
97
+
98
+ <dt>Port <span class="property-type">number</span></dt>
99
+ <dd>The port number on which the WebSocket server will listen for connections. Defaults to <code>443</code> if not specified.</dd>
100
+
101
+ <dt>Path <span class="property-type">string</span></dt>
102
+ <dd>Optional path component for the WebSocket server URL. Can be left empty for root path connections.</dd>
103
+
104
+ <dt>Tunnel ID Header <span class="property-type">string</span></dt>
105
+ <dd>Name of the HTTP custom header used to identify tunnel connections. Defaults to <code>x-tunnel-id</code> if not specified.</dd>
106
+
107
+ <dt>Log Level <span class="property-type">string</span></dt>
108
+ <dd>
109
+ Sets the verbosity level for logging output. Available options:
110
+ <ul>
111
+ <li><strong>trace</strong> - Most verbose, includes all debug information</li>
112
+ <li><strong>debug</strong> - Debug information for troubleshooting</li>
113
+ <li><strong>info</strong> - General informational messages (default)</li>
114
+ <li><strong>warning</strong> - Warning messages only</li>
115
+ <li><strong>error</strong> - Error messages only</li>
116
+ </ul>
117
+ </dd>
118
+ </dl>
119
+
120
+ <h3>Usage</h3>
121
+ <p>
122
+ This configuration node is used by <code>wstunnel server</code> nodes to define connection parameters. Create one configuration node for each unique set of
123
+ connection settings you need.
124
+ </p>
125
+
126
+ <h3>Connection URL</h3>
127
+ <p>The WebSocket server will be accessible at:</p>
128
+ <pre>ws://[host]:[port][/path]</pre>
129
+
130
+ <h3>Tunnel ID Header</h3>
131
+ <p>
132
+ The tunnel ID header is used to associate incoming HTTP requests with specific WebSocket connections. Clients should include this header in their requests
133
+ to identify which tunnel to use.
134
+ </p>
135
+
136
+ <h3>Log Level Impact</h3>
137
+ <p>The selected log level affects all logging output from the reverse-ws-tunnel library:</p>
138
+ <ul>
139
+ <li><strong>trace/debug</strong> - Useful for development and troubleshooting connection issues</li>
140
+ <li><strong>info</strong> - Recommended for production environments</li>
141
+ <li><strong>warning/error</strong> - Minimal logging for performance-critical environments</li>
142
+ </ul>
143
+
144
+ <h3>Example Configuration</h3>
145
+ <ul>
146
+ <li><strong>Name:</strong> "Production Tunnel"</li>
147
+ <li><strong>Host:</strong> "0.0.0.0" (to accept connections from any interface)</li>
148
+ <li><strong>Port:</strong> "8080"</li>
149
+ <li><strong>Path:</strong> "/api/tunnel"</li>
150
+ <li><strong>Header:</strong> "x-tunnel-id"</li>
151
+ <li><strong>Log Level:</strong> "info"</li>
152
+ </ul>
153
+
154
+ <h3>Notes</h3>
155
+ <ul>
156
+ <li>Each configuration can be shared by multiple <code>wstunnel server</code> nodes</li>
157
+ <li>Changing the port or host requires redeploying affected server nodes</li>
158
+ <li>Log level changes are applied when server nodes are deployed or restarted</li>
159
+ <li>Ensure the specified port is available and not blocked by firewall rules</li>
160
+ </ul>
161
+ </script>
162
+
163
+ <!-- <script type="text/x-red" data-template-name="wstunnel client">
69
164
  <div class="form-row">
70
165
  <label for="node-input-url"><i class="fa fa-bookmark"></i> Url</label>
71
166
  <input type="text" id="node-input-url" placeholder="Url tunnel" />
72
167
  </div>
73
- </script>
168
+ </script> -->
74
169
 
75
- <script type="text/x-red" data-help-name="wstunnel client">
170
+ <!-- <script type="text/x-red" data-help-name="wstunnel client">
76
171
  <p>
77
172
  This node establishes a WebSocket client connection to a remote WebSocket server,
78
173
  enabling the creation of an HTTP tunnel over the WebSocket connection. It can be
@@ -115,9 +210,9 @@
115
210
  <li>If the connection is closed or fails, the node will attempt to reconnect based on internal reconnection logic.</li>
116
211
  <li>Ensure the WebSocket server you're connecting to supports tunneling or is configured to handle this type of traffic.</li>
117
212
  </ul>
118
- </script>
213
+ </script> -->
119
214
 
120
- <script type="text/javascript">
215
+ <!-- <script type="text/javascript">
121
216
  RED.nodes.registerType('wstunnel client', {
122
217
  category: 'network',
123
218
  color: '#a7c957',
@@ -134,84 +229,65 @@
134
229
  return this.name ? 'node_label_italic' : '';
135
230
  },
136
231
  });
137
- </script>
232
+ </script> -->
138
233
 
139
234
  <script type="text/x-red" data-template-name="wstunnel server">
140
235
  <div class="form-row">
141
- <label for="node-input-wstunnnel"><i class="fa fa-gears"></i> Cofigure Tunnel Websocket server</label>
236
+ <label for="node-input-wstunnel"><i class="fa fa-gears"></i> Cofigure Tunnel Websocket server</label>
142
237
  <input type="text" id="node-input-wstunnel">
143
238
  </div>
144
239
  </script>
145
240
 
146
- <script type="text/x-red" data-help-name="wstunnel server">
147
- <p>
148
- This node is used to configure and manage a WebSocket server tunnel. It allows the establishment
149
- of WebSocket-based communication between a server and a client. You can define the source and destination
150
- addresses and ports to tunnel the traffic through WebSocket connections.
151
- </p>
241
+ <script type="text/html" data-help-name="wstunnel server">
242
+ <p>This node starts a WebSocket server for tunneling HTTP traffic over WebSocket connections.</p>
152
243
 
153
- <h3>Parameters</h3>
154
- <table class="node-help">
155
- <tr>
156
- <td><strong>Name</strong></td>
157
- <td>A unique name for the tunnel configuration. This will help you identify the tunnel in Node-RED.</td>
158
- </tr>
159
- <tr>
160
- <td><strong>Host</strong></td>
161
- <td>The host address to which the WebSocket server should connect. This is typically the address of your server hosting the WebSocket server.</td>
162
- </tr>
163
- <tr>
164
- <td><strong>Port</strong></td>
165
- <td>The port on which the WebSocket server will listen. This is the port used to accept WebSocket connections from clients.</td>
166
- </tr>
167
- <tr>
168
- <td><strong>Source Address</strong></td>
169
- <td>The source address (IP or hostname) for the WebSocket connection. This is typically set to the address of the service initiating the WebSocket connection.</td>
170
- </tr>
171
- <tr>
172
- <td><strong>Source Port</strong></td>
173
- <td>The source port that the WebSocket server will use. This is required to define the source of the communication.</td>
174
- </tr>
175
- <tr>
176
- <td><strong>Destination Address</strong></td>
177
- <td>The destination address where the WebSocket connection should forward the tunneled HTTP traffic. This is usually set to the IP or hostname of the remote service.</td>
178
- </tr>
179
- <tr>
180
- <td><strong>Destination Port</strong></td>
181
- <td>The destination port where the tunneled HTTP traffic will be sent. This corresponds to the port of the destination service.</td>
182
- </tr>
183
- <tr>
184
- <td><strong>Timeout</strong></td>
185
- <td>Timeout in milliseconds for the WebSocket connection. If the WebSocket connection does not establish within this time, the connection attempt will be aborted.</td>
186
- </tr>
187
- </table>
244
+ <h3>Configuration</h3>
245
+ <dl class="message-properties">
246
+ <dt>wstunnel <span class="property-type">config</span></dt>
247
+ <dd>
248
+ Select a <code>wstunnel</code> configuration node that provides server options such as <code>host</code>, <code>port</code>, <code>path</code>,
249
+ <code>tunnel ID header name</code>, and <code>log level</code>.
250
+ </dd>
251
+ </dl>
188
252
 
189
- <h3>Usage</h3>
253
+ <h3>Details</h3>
254
+ <p>When deployed, the node starts a WebSocket server using the port and options defined in the selected <code>wstunnel</code> config node.</p>
255
+ <p>
256
+ The node updates its status to indicate when it is listening and displays the number of connected clients. Connections are automatically closed if the node
257
+ is redeployed or the port is changed.
258
+ </p>
259
+ <p>The server listens for WebSocket connections and uses the configured tunnel ID header to associate incoming connections.</p>
190
260
  <p>
191
- After configuring the tunnel server, the node will act as the WebSocket server. It listens for incoming
192
- WebSocket connection requests and forwards any received HTTP traffic to the specified destination address and port.
261
+ The log level configured in the <code>wstunnel</code> config node is applied when the server starts, controlling the verbosity of logging output from the
262
+ tunnel library.
193
263
  </p>
194
264
 
195
- <h3>Example</h3>
196
- <pre>
197
- [
198
- {
199
- "id": "wstunnel-server-node",
200
- "type": "wstunnel server",
201
- "wstunnel": "wstunnel-config",
202
- "name": "My WebSocket Tunnel Server",
203
- "x": 300,
204
- "y": 200,
205
- "wires": []
206
- }
207
- ]
208
- </pre>
265
+ <h3>Runtime Behavior</h3>
266
+ <ul>
267
+ <li>Status will show <code>listening on port [port]</code> when active.</li>
268
+ <li>Each client connection updates the status with the current number of connected clients.</li>
269
+ <li>All client connections are properly closed when the node is stopped or removed.</li>
270
+ </ul>
271
+
272
+ <h3>Logging</h3>
273
+ <dl class="message-properties">
274
+ <dt>Listening</dt>
275
+ <dd>Logs when the WebSocket server is up and listening on the configured port.</dd>
276
+ <dt>Connection</dt>
277
+ <dd>Logs the client IP and port when a new WebSocket client connects.</dd>
278
+ <dt>Disconnection</dt>
279
+ <dd>Logs when a client disconnects and shows remaining connected clients.</dd>
280
+ </dl>
209
281
 
210
282
  <h3>Notes</h3>
211
283
  <ul>
212
- <li>The server node requires a valid tunnel configuration to function. You can link a configuration node using the 'Configure Tunnel WebSocket server' field.</li>
213
- <li>If the WebSocket server is not running, the node will attempt to reconnect based on the configured timeout.</li>
214
- <li>Ensure the WebSocket server and client are compatible with the tunneling protocol.</li>
284
+ <li>
285
+ Ensure the <code>wstunnel</code> config node is correctly configured with a valid <strong>port</strong>, <strong>tunnel ID header</strong>, and
286
+ appropriate <strong>log level</strong>.
287
+ </li>
288
+ <li>Redeploying the flow will gracefully shut down existing WebSocket servers and restart them with updated configuration.</li>
289
+ <li>Log level changes require redeployment to take effect.</li>
290
+ <li>This node does not have input or output wires – it works autonomously based on its configuration.</li>
215
291
  </ul>
216
292
  </script>
217
293
 
package/nodes/wsTunnel.js CHANGED
@@ -13,16 +13,11 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  **/
16
- const WebSocket = require('ws');
17
- const { Buffer } = require('buffer');
18
- const net = require('net');
19
- const { v4: uuidv4 } = require('uuid');
20
- const tls = require('tls');
21
16
 
22
- const PING_INTERVAL = 1000 * 30; // in seconds
17
+ const { startWebSocketServer } = require('@remotelinker/reverse-ws-tunnel/server');
18
+ const { setLogLevel } = require('@remotelinker/reverse-ws-tunnel/utils');
19
+
23
20
  const instances = {};
24
- const HTTP_TIMEOUT = 1000 * 30; // in seconds
25
- const RECONNECT_INTERVAL = 1000 * 5;
26
21
 
27
22
  module.exports = function (RED) {
28
23
  'use strict';
@@ -30,471 +25,131 @@ module.exports = function (RED) {
30
25
  function WSTunnelNode(n) {
31
26
  RED.nodes.createNode(this, n);
32
27
 
33
- const [dstProtocol, dstHost, dstPort] = getProtocolAndHostAndDefautlPort(n.dstAddr);
34
-
35
- // console.log('{}{}{}{}{}{}{}{}');
36
- // console.log(dstProtocol);
37
- // console.log(dstHost);
38
- // console.log(dstPort);
39
- // console.log('{}{}{}{}{}{}{}{}');
40
-
41
28
  this.options = {
42
29
  name: n.name || 'websocket tunnel',
43
30
  host: n.host || 'localhost',
44
- port: n.port || 8083,
45
-
46
- srcAddr: n.srcAddr || 'localhost',
47
- srcPort: n.srcPort,
48
- dstAddr: dstHost || 'localhost',
49
- dstPort: n.dstPort || dstPort || 80,
50
- timeout: n.timeout || HTTP_TIMEOUT,
31
+ port: n.port || 443,
32
+ path: n.path || '',
33
+ tunnelIdHeaderName: n.tunnelIdHeaderName || 'x-tunnel-id',
34
+ logLevel: n.logLevel || 'info',
51
35
  };
52
36
  }
53
37
 
54
38
  RED.nodes.registerType('wstunnel', WSTunnelNode);
55
39
 
56
- function WSTunnelClient(n) {
40
+ function WSTunnelServer(n) {
57
41
  RED.nodes.createNode(this, n);
58
- let node = this;
59
- this.status({});
60
-
61
- const websocketURL = n.url;
62
-
63
- if (!websocketURL) {
64
- node.status({ fill: 'red', shape: 'dot', text: 'The URL is invalid or not defined.' });
65
- return;
66
- }
67
-
68
- function connectWebSocket() {
69
- let options = undefined;
70
- // Crea una connessione al server WebSocket
71
- const ws = new WebSocket(websocketURL, options);
72
-
73
- // Quando la connessione Γ¨ aperta, invia un messaggio al server
74
- ws.on('open', () => {
75
- console.log('Connected to the WebSocket server');
76
- node.status({ fill: 'green', shape: 'dot', text: 'WebSocket tunnel established' });
77
- });
42
+ this.tunnelConfig = RED.nodes.getNode(n.wstunnel);
43
+ const node = this;
44
+ const id = node.id;
78
45
 
79
- // Quando ricevi un messaggio dal server, stampalo
80
- ws.on('message', async (data) => {
81
- // console.log('Received from server: ', data);
82
- // console.log('Received Base64 encoded HTTP RAW request:');
46
+ // Set the log level dynamically based on configuration
47
+ const logLevel = node.tunnelConfig.options.logLevel || 'info';
48
+ setLogLevel(logLevel);
83
49
 
84
- // Decodifica la stringa Base64
85
- try {
86
- // const request = JSON.parse(Buffer.from(data, 'base64').toString('utf-8'));
87
- // const rawHttpRequest = Buffer.from(request.httpRequest, 'base64').toString('utf-8');
88
- // console.log('Decoded HTTP RAW Request:', request.httpRequest);
50
+ node.status({ fill: 'red', shape: 'ring', text: 'disconnected' });
89
51
 
90
- const extractedUUID = data.slice(0, 36);
91
- // console.log(extractedUUID);
92
- const res = await incomingRequest(data.slice(36)); // tolgo dal buffer uuid
93
- // console.log('======');
94
- // console.log(res.toString('utf-8'));
95
- // console.log('======');
96
- ws.send(Buffer.concat([extractedUUID, res]));
97
- } catch (error) {
98
- console.log(error);
99
- }
100
- });
52
+ const port = node.tunnelConfig.options.port;
53
+ const host = node.tunnelConfig.options.host ? '' : undefined;
54
+ const path = node.tunnelConfig.options.path ? '' : undefined;
55
+ const tunnelIdHeaderName = node.tunnelConfig.options.tunnelIdHeaderName;
101
56
 
102
- // Gestione degli errori di connessione
103
- ws.on('error', (error) => {
104
- node.status({ fill: 'red', shape: 'dot', text: 'Websocket error.' });
105
- console.error('Error occurred: ', error);
106
- });
57
+ instances[id] = instances[id] || { state: {} };
107
58
 
108
- // Quando la connessione Γ¨ chiusa, stampa un messaggio
109
- ws.on('close', () => {
110
- console.log('Disconnected from the WebSocket server');
111
- node.status({ fill: 'red', shape: 'ring', text: 'Disconnected from the WebSocket server' });
112
- setTimeout(connectWebSocket, RECONNECT_INTERVAL);
59
+ if (instances[id].state[port]) {
60
+ console.warn(`Server already running on port ${port} for node ${id}. Skipping creation.`);
61
+ node.status({
62
+ fill: 'green',
63
+ shape: 'dot',
64
+ text: `listening on port ${port}`,
113
65
  });
66
+ return;
114
67
  }
115
68
 
116
- connectWebSocket();
117
-
118
- async function incomingRequest(rawHttpRequest) {
119
- const { hostname, port } = extractHostAndPort(rawHttpRequest);
69
+ instances[id].state = startWebSocketServer({
70
+ port,
71
+ host,
72
+ path,
73
+ tunnelIdHeaderName,
74
+ });
120
75
 
121
- if (port == 443) {
122
- return await tlsRequest(rawHttpRequest);
123
- }
76
+ console.log(`Started WebSocketServer for node ${id} on port ${port}`);
124
77
 
125
- return await tcpRequest(rawHttpRequest);
78
+ if (instances[id].state[port].webSocketServer) {
79
+ onListening();
126
80
  }
127
81
 
128
- function tlsRequest(rawHttpRequest) {
129
- return new Promise((resolve, reject) => {
130
- const { hostname, port } = extractHostAndPort(rawHttpRequest);
131
-
132
- let responseData = Buffer.alloc(0); // Inizializza un buffer vuoto per accumulare i dati
82
+ function onListening() {
83
+ const server = instances[id].state[port].webSocketServer;
133
84
 
134
- // console.log(hostname);
135
- // console.log(port);
136
- // Creazione di un socket TCP
137
- const client = tls.connect(port, hostname, { servername: hostname }, () => {
138
- client.write(rawHttpRequest); // Invia la richiesta HTTP raw
139
- });
140
-
141
- // Ricevi i dati dalla risposta del server
142
- client.on('data', (data) => {
143
- // console.log('Received response from server:');
144
- // console.log(data.toString()); // Converte i dati ricevuti in stringa e li stampa
145
- // return resolve(data);
146
- responseData = Buffer.concat([responseData, data]); // Accumula i dati ricevuti
147
- });
148
-
149
- // Quando la connessione si chiude
150
- client.on('end', () => {
151
- console.log('Connection closed');
152
- resolve(responseData);
153
- // client.destroy()
154
- });
155
-
156
- // Gestione degli errori
157
- client.on('error', (err) => {
158
- reject(err);
159
- console.error('Error:', err);
85
+ server.on('listening', () => {
86
+ console.log('Listening ' + id);
87
+ node.status({
88
+ fill: 'green',
89
+ shape: 'dot',
90
+ text: `listening on port ${port}`,
160
91
  });
161
92
  });
162
- }
163
93
 
164
- function tcpRequest(rawHttpRequest) {
165
- return new Promise((resolve, reject) => {
166
- const { hostname, port } = extractHostAndPort(rawHttpRequest);
94
+ server.on('connection', (ws) => {
95
+ const clientAddress = ws._socket.remoteAddress;
96
+ const clientPort = ws._socket.remotePort;
97
+ const totalClients = server.clients.size;
167
98
 
168
- let responseData = Buffer.alloc(0); // Inizializza un buffer vuoto per accumulare i dati
169
-
170
- // console.log(hostname);
171
- // console.log(port);
172
-
173
- // Creazione di un socket TCP
174
- const client = net.createConnection(port, hostname, () => {
175
- client.write(rawHttpRequest); // Invia la richiesta HTTP raw
176
- });
177
-
178
- // Ricevi i dati dalla risposta del server
179
- client.on('data', (data) => {
180
- responseData = Buffer.concat([responseData, data]); // Accumula i dati ricevuti
99
+ node.status({
100
+ fill: 'green',
101
+ shape: 'dot',
102
+ text: `clients ${totalClients}`,
181
103
  });
182
104
 
183
- // Quando la connessione si chiude
184
- client.on('end', () => {
185
- console.log('Connection closed');
186
- resolve(responseData);
187
- });
188
-
189
- // Gestione degli errori
190
- client.on('error', (err) => {
191
- reject(err);
192
- console.error('Error:', err);
105
+ console.log(`WebSocket connection from ${clientAddress}:${clientPort}`);
106
+ console.log(`Total connected clients: ${totalClients}`);
107
+
108
+ ws.on('close', () => {
109
+ const remainingClients = server.clients.size;
110
+ node.status({
111
+ fill: 'green',
112
+ shape: 'dot',
113
+ text: `clients ${remainingClients}`,
114
+ });
115
+ console.log(`Client disconnected: ${clientAddress}:${clientPort}`);
116
+ console.log(`Remaining clients: ${remainingClients}`);
193
117
  });
194
118
  });
195
119
  }
196
120
 
197
- function extractHostAndPort(rawHttp) {
198
- const hostPattern = /Host:\s*([^\r\n]+)/i; // Regex per trovare la linea Host
199
- const rawHttpString = rawHttp.toString('utf-8');
200
- const match = rawHttpString.match(hostPattern);
201
-
202
- if (!match) {
203
- throw new Error('Host non trovato nella stringa HTTP raw');
204
- }
205
-
206
- const host = match[1];
207
- const [hostname, port = 443] = host.split(':'); // Porta di default 443 per HTTPS
208
-
209
- return { hostname, port: parseInt(port, 10) };
210
- }
211
-
212
- this.url = n.url;
213
- // this.operation = n.operation;
214
- // this.filename = n.filename;
215
- // this.fileExtension = n.fileExtension;
216
- // this.workdir = n.workdir;
217
- // this.tunnelConfig = RED.nodes.getNode(this.tunnel);
218
-
219
- // const globalContext = this.context().global;
220
-
221
- // if (!this.tunnelConfig) {
222
- // this.error('missing tunnel Websocket configuration');
223
- // return;
224
- // }
225
-
226
- // Hack for async function
227
- // setTimeout(async () => {
228
- // await connect(node);
229
- // });
230
-
231
- // node.on('input', async (msg, send, done) => {
232
- // setTimeout(async () => {
233
- // await connect(node);
234
- // }, node.tunnelConfig.options.reconnectTimeout);
235
- // });
236
- }
237
- RED.nodes.registerType('wstunnel client', WSTunnelClient);
238
-
239
- function WSTunnelServer(n) {
240
- instances[n.id] = instances[n.id] || {};
241
- instances[n.id].server = instances[n.id].server || null;
242
- instances[n.id].wss = instances[n.id].wss || null;
243
- instances[n.id].websocketServer = instances[n.id].websocketServer || null;
244
-
245
- RED.nodes.createNode(this, n);
246
- this.tunnelConfig = RED.nodes.getNode(n.wstunnel);
247
- let node = this;
248
- const id = n.id;
249
-
250
- node.status({ fill: 'red', shape: 'ring', text: 'disconnected' });
251
-
252
- const port = node.tunnelConfig.options.port;
253
- const srcAddr = node.tunnelConfig.options.srcAddr || 'localhost';
254
- const srcPort = node.tunnelConfig.options.srcPort || 8083;
255
- const dstAddr = node.tunnelConfig.options.dstAddr || 'localhost';
256
- const dstPort = node.tunnelConfig.options.dstPort || 80;
257
- const timeout = node.tunnelConfig.options.timeout || HTTP_TIMEOUT;
258
- // const port = 4443;
259
-
260
- // console.log('+++++++++ OPTIONS ++++++++');
261
- // console.log(port);
262
- // console.log(dstPort);
263
- // console.log(dstAddr);
264
- // console.log(timeout);
265
- // console.log('+++++++++ OPTIONS ++++++++');
266
- const options = {
267
- id,
268
- port,
269
- srcAddr,
270
- srcPort,
271
- dstAddr,
272
- dstPort,
273
- timeout,
274
- };
275
-
276
- if (instances[options.id].server) {
277
- instances[options.id].server.close(() => {
278
- TCPServer(options);
279
- });
280
- } else {
281
- TCPServer(options);
282
- }
283
-
284
- if (instances[n.id].wss) {
285
- instances[n.id].wss.close(() => {
286
- webSocketServer(node, options);
287
- });
288
- } else {
289
- webSocketServer(node, options);
290
- }
291
- }
292
-
293
- function requestFromServer(message, options) {
294
- return new Promise((resolve, reject) => {
295
- if (!instances[options.id].websocketServer) {
296
- return reject(new WebsocketTunnelError('WebSocket connection not established.', 500));
297
- }
298
-
299
- const uuid = uuidv4();
300
- const uuidBuffer = Buffer.from(uuid);
301
- const resultBuffer = Buffer.concat([uuidBuffer, Buffer.from(message)]);
302
-
303
- const timeoutId = setTimeout(() => {
304
- clearTimeout(timeoutId);
305
- reject('Timeout: No response from the client.');
306
- }, options.timeout);
307
-
308
- const onMessage = (response) => {
309
- // console.log(response.toString());
310
- const extractedUUID = response.slice(0, 36);
311
- if (Buffer.compare(uuidBuffer, extractedUUID) === 0) {
312
- resolve(response.slice(36));
313
- instances[options.id].websocketServer.removeListener('message', onMessage);
314
- clearTimeout(timeoutId);
315
- }
316
- };
317
- instances[options.id].websocketServer.on('message', onMessage);
318
-
319
- instances[options.id].websocketServer.send(resultBuffer);
320
- });
321
- }
322
-
323
- function webSocketServer(node, options) {
324
- try {
325
- instances[node.id].wss = new WebSocket.Server({ port: options.port });
326
- } catch (error) {
327
- console.log(error);
328
- }
329
-
330
- instances[node.id].wss.on('listening', () => {
331
- console.log(`WebSocket server is listening on port ${options.port}`);
332
- });
333
-
334
- instances[node.id].wss.on('connection', (ws) => {
335
- if (instances[node.id].websocketServer) {
336
- console.log('WebSocket tunnel is already active');
337
- ws.close();
338
- return;
339
- }
340
-
341
- console.log('WebSocket tunnel established');
342
- node.status({ fill: 'green', shape: 'dot', text: 'WebSocket tunnel established' });
343
- instances[node.id].websocketServer = ws;
344
-
345
- // Invia un messaggio di benvenuto al client appena connesso
346
- // ws.send('Ciao dal server WebSocket!');
347
-
348
- const interval = setInterval(() => {
349
- if (ws.readyState === WebSocket.OPEN) {
350
- ws.ping(); // invia un ping al client
351
- console.log('Ping inviato al client');
352
- }
353
- }, PING_INTERVAL);
354
-
355
- // Quando si verifica un errore
356
- ws.on('error', function error(err) {
357
- instances[node.id].websocketServer = null;
358
- console.error('WebSocket error:', err);
359
- clearInterval(interval); // pulisci l'intervallo quando la connessione Γ¨ chiusa
360
- node.error(err, { payload: 'Open websockeet tunnel error', message: err.message });
361
- node.status({ fill: 'red', shape: 'dot', text: 'Error' });
362
- });
363
-
364
- // Gestiamo i messaggi ricevuti dal client
365
- // ws.on('message', (message) => {
366
- // console.log(`Messaggio ricevuto dal client: ${message}`);
367
- // // Rispondiamo al client
368
- // // ws.send(`Hai detto: ${message}`);
369
- // });
370
-
371
- // Gestiamo la chiusura della connessione
372
- ws.on('close', () => {
373
- console.log('WebSocket tunnel closed');
374
- instances[node.id].websocketServer = null;
375
- node.status({ fill: 'red', shape: 'ring', text: 'disconnected' });
376
- clearInterval(interval); // pulisci l'intervallo quando la connessione Γ¨ chiusa
377
- });
378
-
379
- // Quando ricevi un pong dal client
380
- ws.on('pong', () => {
381
- console.log('Pong ricevuto dal client');
382
- });
383
- });
384
-
385
- instances[node.id].wss.on('error', (error) => {
386
- console.log(error);
387
- });
388
- }
389
-
390
- function TCPServer(options) {
391
- instances[options.id].server = net.createServer((socket) => {
392
- // console.log('Client connesso');
393
-
394
- // Variabile per memorizzare i dati grezzi della richiesta
395
- let rawRequestData = '';
396
-
397
- // Leggi i dati grezzi dal socket
398
- socket.on('data', async (data) => {
399
- rawRequestData += data.toString(); // Aggiungi i dati ricevuti alla variabile rawRequestData
400
-
401
- // Puoi scegliere di processare la richiesta solo quando hai ricevuto tutto il contenuto, ad esempio quando la richiesta termina
402
- if (rawRequestData.includes('\r\n\r\n')) {
403
- // Quando i dati contengono l'header completo, puoi trattarli come una richiesta completa
404
-
405
- // Sostituire il campo Host con il nuovo valore
406
- rawRequestData = rawRequestData.replace(/Host:\s*([^\r\n]*)/, `Host: ${options.dstAddr}:${options.dstPort}`);
407
- console.log('Richiesta HTTP raw ricevuta:');
408
- console.log(rawRequestData);
409
-
121
+ this.on('close', function (removed, done) {
122
+ if (instances[id] && instances[id].state && instances[id].state[port]) {
123
+ const server = instances[id].state[port].webSocketServer;
124
+ if (server) {
410
125
  try {
411
- const response = await requestFromServer(rawRequestData, options);
412
- // console.log('-----------');
413
- // console.log(response);
414
- // console.log('-----------');
415
- socket.write(response);
416
- // Rispondi al client (opzionale)
417
- // socket.write('HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nResponse OK');
418
- // Chiudi la connessione (opzionale)
419
- } catch (error) {
420
- console.log(error);
421
- let errorRespose =
422
- `HTTP/1.1 408 Request Timeout\r\n` +
423
- `Content-Type: text/plain; charset=utf-8\r\n` +
424
- // `Content-Length: 29\r\n` +
425
- `Connection: close\r\n` +
426
- `Date: ${new Date().toUTCString()}\r\n\r\n` + // usa toUTCString() invece di toGMTString
427
- `Request Timeout: The server timed out waiting for the request.`;
428
-
429
- if (error instanceof WebsocketTunnelError) {
430
- errorRespose =
431
- `HTTP/1.1 ${error.code} ${error.name}\r\n` +
432
- `Content-Type: text/plain; charset=utf-8\r\n` +
433
- // `Content-Length: 29\r\n` +
434
- `Connection: close\r\n` +
435
- `Date: ${new Date().toUTCString()}\r\n\r\n` + // usa toUTCString() invece di toGMTString
436
- `${error.message}`;
126
+ for (const client of server.clients) {
127
+ try {
128
+ client.removeAllListeners();
129
+ client.close();
130
+ } catch (clientErr) {
131
+ console.error(`Error terminating client on port ${port}:`, clientErr);
132
+ }
437
133
  }
438
134
 
439
- socket.write(errorRespose); // Invia la risposta di timeout
135
+ server.close(() => {
136
+ console.log(`Closed WebSocketServer on port ${port} during node shutdown`);
137
+ });
138
+ } catch (err) {
139
+ console.error(`Error closing WebSocketServer on port ${port}:`, err);
440
140
  }
441
- socket.end();
442
141
  }
443
- });
444
-
445
- socket.on('end', () => {
446
- console.log('Client disconnected');
447
- });
448
-
449
- socket.on('error', (err) => {
450
- console.error('Connection error:', err);
451
- });
452
- });
453
142
 
454
- try {
455
- instances[options.id].server.listen(options.srcPort, () => {
456
- console.log(`HTTP server listening on port ${options.srcPort}`);
457
- });
458
- instances[options.id].server.on('error', (error) => {
459
- console.log(error);
460
- });
461
- } catch (error) {
462
- console.log(error);
463
- }
464
- }
143
+ delete instances[id].state[port];
465
144
 
466
- function getProtocolAndHostAndDefautlPort(url) {
467
- try {
468
- const protocolPorts = {
469
- http: 80,
470
- https: 443,
471
- ftp: 21,
472
- ssh: 22,
473
- };
474
- // Add "http://" as a default protocol
475
- if (!/^https?:\/\//i.test(url)) {
476
- url = 'http://' + url;
145
+ // Se non ci sono piΓΉ porte attive per questo nodo, cancella del tutto
146
+ if (Object.keys(instances[id].state).length === 0) {
147
+ delete instances[id];
148
+ }
477
149
  }
478
- const parsedUrl = new URL(url);
479
- const protocol = parsedUrl.protocol.replace(':', '');
480
- const host = parsedUrl.hostname;
481
-
482
- const port = parsedUrl.port || protocolPorts[protocol] || null;
483
150
 
484
- return [protocol, host, port];
485
- } catch (error) {
486
- console.error('Invalid URL:', error);
487
- return null;
488
- }
489
- }
490
-
491
- class WebsocketTunnelError extends Error {
492
- constructor(message, code) {
493
- super(message);
494
- this.name = this.constructor.name;
495
- this.code = code;
496
- Error.captureStackTrace(this, this.constructor);
497
- }
151
+ done();
152
+ });
498
153
  }
499
154
 
500
155
  RED.nodes.registerType('wstunnel server', WSTunnelServer);
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@yousolution/node-red-contrib-you-tunnel-websocket",
3
- "version": "0.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Module to create websockeet tunnel for NODE-RED",
5
5
  "license": "MIT",
6
6
  "scripts": {
7
- "update": "npm pack && mv yousolution-node-red-contrib-you-tunnel-websocket-$npm_package_version.tgz ./data && cd data && rm -rf node_modules && npm i yousolution-node-red-contrib-you-tunnel-websocket-$npm_package_version.tgz && docker-compose restart",
7
+ "update": "PACKAGE_NAME=$(npm pack) && mv $PACKAGE_NAME ./data && cd data && rm -rf node_modules && npm i $PACKAGE_NAME && docker-compose restart",
8
8
  "test": "mocha 'test/**/*.spec.js'",
9
9
  "coverage": "nyc npm run test"
10
10
  },
@@ -19,11 +19,19 @@
19
19
  "node-red": {
20
20
  "version": ">=2.0.0",
21
21
  "nodes": {
22
- "wsTunnel": "/nodes/wsTunnel.js"
22
+ "wsTunnel": "./nodes/wsTunnel.js"
23
23
  }
24
24
  },
25
25
  "dependencies": {
26
- "uuid": "^11.0.5"
26
+ "@remotelinker/reverse-ws-tunnel": "^1.0.7",
27
+ "cookie": "^1.0.2",
28
+ "cross-conf-env": "^1.3.0",
29
+ "dotenv": "^17.2.1",
30
+ "express": "^4.21.2",
31
+ "http-parser-js": "^0.5.10",
32
+ "http-proxy": "^1.18.1",
33
+ "uuid": "^11.1.0",
34
+ "ws": "^8.18.0"
27
35
  },
28
36
  "repository": {
29
37
  "type": "git",