@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 +14 -0
- package/client/tunnelClient.js +39 -28
- package/package.json +1 -1
- package/server/tcpServer.js +9 -4
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
|
package/client/tunnelClient.js
CHANGED
|
@@ -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
|
|
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', (
|
|
84
|
-
logger.trace(`Received message chunk: ${
|
|
85
|
-
messageBuffer = Buffer.concat([messageBuffer,
|
|
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)
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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
|
|
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
package/server/tcpServer.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
114
|
+
const message = buildMessageBuffer(currentTunnelId, uuid, MESSAGE_TYPE_DATA, 'CLOSE');
|
|
115
|
+
tunnel.ws.send(message);
|
|
111
116
|
}
|
|
112
117
|
});
|
|
113
118
|
|