@remotelinker/reverse-ws-tunnel 1.0.8 → 1.0.9

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
@@ -30,6 +30,20 @@ Reverse WebSocket Tunnel is a library that enables you to expose local services
30
30
 
31
31
  ---
32
32
 
33
+ ## ✨ v1.0.9 - What's New
34
+
35
+ ### 🐛 Bug Fixes
36
+ - **Message Format Standardization**: Fixed inconsistent message formats between server components
37
+ - **Ping/Pong Reliability**: Resolved issues with application-level heartbeat failing during data transfer
38
+ - **Connection Stability**: Improved connection handling and reduced timeout issues
39
+
40
+ ### 🔧 Technical Improvements
41
+ - **Unified Message Protocol**: All server messages now use consistent `buildMessageBuffer` format
42
+ - **Simplified Client Architecture**: Removed hybrid parsing logic for better maintainability
43
+ - **Enhanced Buffer Management**: Improved message buffering and parsing reliability
44
+
45
+ ---
46
+
33
47
  ## 📦 Installation
34
48
 
35
49
  ```bash
@@ -19,7 +19,7 @@ const HEALTH_TIMEOUT = 45 * 1000; // 45 secondi sliding window
19
19
  const RECONNECT_BACKOFF = [1000, 2000, 5000, 10000, 30000]; // Backoff progressivo
20
20
 
21
21
  /**
22
- * Starts the WebSocket tunnel client.
22
+ * Starts WebSocket tunnel client.
23
23
  * @param {Object} config - Configuration for tunnel.
24
24
  */
25
25
  function connectWebSocket(config) {
@@ -80,13 +80,16 @@ function connectWebSocket(config) {
80
80
 
81
81
  let messageBuffer = Buffer.alloc(0);
82
82
 
83
- ws.on('message', (chunk) => {
84
- logger.trace(`Received message chunk: ${chunk.length} bytes`);
85
- messageBuffer = Buffer.concat([messageBuffer, chunk]);
83
+ ws.on('message', (data) => {
84
+ logger.trace(`Received message chunk: ${data.length} bytes`);
85
+ messageBuffer = Buffer.concat([messageBuffer, data]);
86
86
 
87
87
  while (messageBuffer.length >= 4) {
88
88
  const length = messageBuffer.readUInt32BE(0);
89
- if (messageBuffer.length < 4 + length) break;
89
+ if (messageBuffer.length < 4 + length) {
90
+ logger.trace(`Waiting for more data: need ${4 + length} bytes, have ${messageBuffer.length}`);
91
+ break;
92
+ }
90
93
 
91
94
  const message = messageBuffer.slice(4, 4 + length);
92
95
  messageBuffer = messageBuffer.slice(4 + length);
@@ -98,32 +101,40 @@ function connectWebSocket(config) {
98
101
 
99
102
  logger.trace(`Received WS message for uuid=${uuid}, type=${type}, length=${payload.length}`);
100
103
 
101
- if (type === MESSAGE_TYPE_DATA) {
102
- if (payload.toString() === 'CLOSE') {
103
- logger.debug(`Received CLOSE for uuid=${uuid}`);
104
- if (clients[uuid]) {
105
- clients[uuid].end();
104
+ if (type === MESSAGE_TYPE_DATA) {
105
+ if (payload.toString() === 'CLOSE') {
106
+ logger.debug(`Received CLOSE for uuid=${uuid}`);
107
+ if (clients[uuid]) {
108
+ clients[uuid].end();
109
+ }
110
+ return;
111
+ }
112
+
113
+ const client = clients[uuid] || createTcpClient(targetUrl, targetPort, ws, tunnelId, uuid);
114
+
115
+ if (!client.write(payload)) {
116
+ logger.debug(`Backpressure on TCP socket for uuid=${uuid}`);
117
+ client.once('drain', () => {
118
+ logger.info(`TCP socket drained for uuid=${uuid}`);
119
+ });
106
120
  }
107
121
  return;
108
- }
109
122
 
110
- const client = clients[uuid] || createTcpClient(targetUrl, targetPort, ws, tunnelId, uuid);
111
-
112
- } else if (type === MESSAGE_TYPE_APP_PONG) {
113
- try {
114
- const pongData = JSON.parse(payload.toString());
115
- // Accetta solo pong con seq >= pingSeq - 10 (finestra di 10 ping)
116
- if (pongData.seq >= (pingSeq - 10)) {
117
- lastPongTs = Date.now();
118
- logger.trace(`App pong received: seq=${pongData.seq}`);
119
- } else {
120
- logger.debug(`Ignoring old pong: seq=${pongData.seq}`);
123
+ } else if (type === MESSAGE_TYPE_APP_PONG) {
124
+ try {
125
+ const pongData = JSON.parse(payload.toString());
126
+ // Accetta solo pong con seq >= pingSeq - 10 (finestra di 10 ping)
127
+ if (pongData.seq >= (pingSeq - 10)) {
128
+ lastPongTs = Date.now();
129
+ logger.trace(`App pong received: seq=${pongData.seq}`);
130
+ } else {
131
+ logger.debug(`Ignoring old pong: seq=${pongData.seq}`);
132
+ }
133
+ } catch (err) {
134
+ logger.error(`Invalid app pong format: ${err.message}`);
121
135
  }
122
- } catch (err) {
123
- logger.error(`Invalid app pong format: ${err.message}`);
136
+ return;
124
137
  }
125
- return;
126
- }
127
138
  }
128
139
  });
129
140
 
@@ -193,7 +204,7 @@ function heartBeat(ws) {
193
204
  }
194
205
 
195
206
  /**
196
- * Creates a TCP connection to the target service.
207
+ * Creates a TCP connection to target service.
197
208
  */
198
209
  function createTcpClient(targetUrl, targetPort, ws, tunnelId, uuid) {
199
210
  const hostname = new URL(targetUrl).hostname;
@@ -271,4 +282,4 @@ function resetClients() { // for testing
271
282
  module.exports = {
272
283
  connectWebSocket,
273
284
  resetClients, // for testing
274
- };
285
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotelinker/reverse-ws-tunnel",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "A Node.js library for creating secure reverse tunnels over WebSocket connections",
5
5
  "main": "index.cjs",
6
6
  "types": "types/index.d.ts",
@@ -5,6 +5,7 @@ const { HTTPParser, methods } = require('http-parser-js');
5
5
  const state = require('./state');
6
6
  const { MESSAGE_TYPE_DATA } = require('./constants');
7
7
  const { logger } = require('../utils/logger');
8
+ const { buildMessageBuffer } = require('../client/utils');
8
9
 
9
10
  function startTCPServer(port, tunnelIdHeaderName, websocketPort) {
10
11
  const wsPortKey = String(websocketPort);
@@ -60,7 +61,8 @@ function startTCPServer(port, tunnelIdHeaderName, websocketPort) {
60
61
  isWebSocket = headers['upgrade']?.toLowerCase() === 'websocket';
61
62
 
62
63
  logger.trace(`Sending initial headers (${rawHeaders.length} bytes) to tunnel [${currentTunnelId}]`);
63
- tunnel.ws.send(Buffer.concat([uuidBuffer, Buffer.from([MESSAGE_TYPE_DATA]), Buffer.from(rawHeaders)]));
64
+ const message = buildMessageBuffer(currentTunnelId, uuid, MESSAGE_TYPE_DATA, rawHeaders);
65
+ tunnel.ws.send(message);
64
66
 
65
67
  if (isWebSocket) parser.close();
66
68
  };
@@ -70,7 +72,8 @@ function startTCPServer(port, tunnelIdHeaderName, websocketPort) {
70
72
  if (tunnel?.ws && !isWebSocket) {
71
73
  const body = chunk.slice(offset, offset + length);
72
74
  logger.trace(`Forwarding body (${body.length} bytes) to tunnel [${currentTunnelId}]`);
73
- tunnel.ws.send(Buffer.concat([uuidBuffer, Buffer.from([MESSAGE_TYPE_DATA]), body]));
75
+ const message = buildMessageBuffer(currentTunnelId, uuid, MESSAGE_TYPE_DATA, body);
76
+ tunnel.ws.send(message);
74
77
  }
75
78
  };
76
79
 
@@ -91,7 +94,8 @@ function startTCPServer(port, tunnelIdHeaderName, websocketPort) {
91
94
  if (isWebSocket) {
92
95
  if (tunnel?.ws) {
93
96
  logger.trace(`Forwarding WebSocket TCP data (${chunk.length} bytes) for tunnel [${currentTunnelId}]`);
94
- tunnel.ws.send(Buffer.concat([uuidBuffer, Buffer.from([MESSAGE_TYPE_DATA]), chunk]));
97
+ const message = buildMessageBuffer(currentTunnelId, uuid, MESSAGE_TYPE_DATA, chunk);
98
+ tunnel.ws.send(message);
95
99
  }
96
100
  } else {
97
101
  try {
@@ -107,7 +111,8 @@ function startTCPServer(port, tunnelIdHeaderName, websocketPort) {
107
111
  const tunnel = state[wsPortKey]?.websocketTunnels?.[currentTunnelId];
108
112
  if (tunnel?.ws) {
109
113
  logger.debug(`TCP socket end for tunnel [${currentTunnelId}] (uuid: ${uuid})`);
110
- tunnel.ws.send(Buffer.concat([uuidBuffer, Buffer.from([MESSAGE_TYPE_DATA]), Buffer.from('CLOSE')]));
114
+ const message = buildMessageBuffer(currentTunnelId, uuid, MESSAGE_TYPE_DATA, 'CLOSE');
115
+ tunnel.ws.send(message);
111
116
  }
112
117
  });
113
118