@remotelinker/reverse-ws-tunnel 1.0.4

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/CHANGELOG.md ADDED
@@ -0,0 +1,54 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [1.0.3] - 2025-11-29
6
+
7
+ ### Features
8
+ - Updated `startClient` to return a client instance.
9
+ - Added `connected` and `disconnected` events to the client instance.
10
+ - Added `close()` method to the client instance to terminate the connection and stop reconnection.
11
+
12
+ ## [1.0.2] - 2025-10-05
13
+
14
+ ### Fixes
15
+ - Corrected a typo in the environment variable name for `allowInsicureCerts` in the client configuration loader.
16
+
17
+ ## [1.0.1] - 2025-09-16
18
+
19
+ ### Features
20
+ - Added a comprehensive example of a reverse tunnel to README.md.
21
+ - Included a minimal web server to act as the tunnel's target.
22
+ - Updated the client configuration to align with the new example.
23
+ - Generated a CHANGELOG.md file from the project's git history.
24
+
25
+ ### Refactoring
26
+ - Refactored WebSocket connection state management.
27
+
28
+ ## [1.0.0] - 2025-09-16
29
+
30
+ ### Features
31
+ - Added dynamic environment configuration.
32
+ - Added client IP to server connection function.
33
+ - Added support for custom config path.
34
+ - Added unit tests with Jest.
35
+ - Added `config.toml` for configuration.
36
+ - Added logger.
37
+ - Added examples.
38
+ - Added dynamic allowance of insecure HTTPS certificates.
39
+ - Added support for multiple servers on different ports.
40
+ - Added return of the WebSocket server instance.
41
+ - Added dynamic TCP server.
42
+ - Initial working version.
43
+
44
+ ### Fixes
45
+ - Fixed WebSocket server heartbeat.
46
+ - Fixed `allowInsicureCerts` option.
47
+ - Fixed logger path.
48
+ - Fixed heartbeat issue.
49
+ - Disabled secure proxy when set to false.
50
+
51
+ ### Refactoring
52
+ - Refactored dist directory structure.
53
+ - Changed `tunnelIdHeaderName`.
54
+ - Refactored with ChatGPT suggestions.
package/README.md ADDED
@@ -0,0 +1,406 @@
1
+ # 🚀 Reverse WebSocket Tunnel
2
+
3
+ **A Node.js library for creating secure reverse tunnels over WebSocket connections.**
4
+
5
+ [![Version](https://img.shields.io/npm/v/@remotelinker/reverse-ws-tunnel.svg)](https://www.npmjs.com/package/@remotelinker/reverse-ws-tunnel)
6
+ [![License](https://img.shields.io/npm/l/@remotelinker/reverse-ws-tunnel.svg)](LICENSE)
7
+
8
+
9
+
10
+ ## 📖 What is Reverse WebSocket Tunnel?
11
+
12
+ Reverse WebSocket Tunnel is a library that enables you to expose local services to the internet through a WebSocket tunnel, similar to tools like ngrok or localtunnel. It consists of two main components:
13
+
14
+ - **Server**: Runs on a publicly accessible server and accepts WebSocket connections from clients
15
+ - **Client**: Runs locally and creates a tunnel to expose local services through the server
16
+
17
+ ### Use Cases
18
+
19
+ - **Development**: Expose local development servers for testing webhooks, APIs, or sharing work
20
+ - **IoT & Edge**: Connect devices behind NAT/firewalls to cloud services
21
+ - **Microservices**: Enable secure communication between services across different networks
22
+ - **CI/CD**: Create temporary endpoints for automated testing
23
+
24
+ ### How It Works
25
+
26
+ 1. The **server** listens for WebSocket connections and HTTP requests
27
+ 2. The **client** connects to the server via WebSocket and registers a tunnel ID
28
+ 3. When the server receives HTTP requests for a specific tunnel, it forwards them through the WebSocket to the client
29
+ 4. The client proxies the requests to the local target service and sends responses back through the tunnel
30
+
31
+ ---
32
+
33
+ ## 📦 Installation
34
+
35
+ ```bash
36
+ npm install @remotelinker/reverse-ws-tunnel
37
+ ```
38
+
39
+ ## 🚀 Quick Start
40
+
41
+ ### Server Setup
42
+
43
+ ```javascript
44
+ const { startWebSocketServer } = require('@remotelinker/reverse-ws-tunnel/server');
45
+
46
+ // Start the WebSocket tunnel server
47
+ startWebSocketServer({
48
+ port: 443,
49
+ host: '0.0.0.0',
50
+ path: '/tunnel',
51
+ tunnelIdHeaderName: 'x-tunnel-id',
52
+ });
53
+ ```
54
+
55
+ ### Client Setup
56
+
57
+ ```javascript
58
+ const { startClient } = require('@remotelinker/reverse-ws-tunnel/client');
59
+
60
+ // Connect to the tunnel server and expose local service
61
+ const client = startClient({
62
+ tunnelId: '1cf2755f-c151-4281-b3f0-55c399035f87',
63
+ wsUrl: 'wss://yourdomain.com/tunnel',
64
+ targetUrl: 'http://localhost:3000',
65
+ tunnelEntryPort: 4443,
66
+ allowInsicureCerts: false,
67
+ });
68
+
69
+ // Listen for events
70
+ client.on('connected', () => {
71
+ console.log('Connected to tunnel');
72
+ });
73
+
74
+ client.on('disconnected', () => {
75
+ console.log('Disconnected from tunnel');
76
+ });
77
+
78
+ // Close connection
79
+ // client.close();
80
+ ```
81
+
82
+ ---
83
+
84
+ ## ⚙️ Configuration
85
+
86
+ You can configure the library using:
87
+
88
+ 1. **Environment variables**
89
+ 2. **TOML configuration files** (`config.toml`)
90
+ 3. **Direct JavaScript parameters**
91
+
92
+ _Configuration priority: JavaScript parameters > config.toml > environment variables_
93
+
94
+ ### 🖥️ Server Configuration
95
+
96
+ #### JavaScript API
97
+
98
+ ```javascript
99
+ const { startWebSocketServer } = require('@remotelinker/reverse-ws-tunnel/server');
100
+
101
+ startWebSocketServer({
102
+ port: 443, // WebSocket server port
103
+ host: '0.0.0.0', // Host to bind (optional)
104
+ path: '/tunnel', // WebSocket path (optional)
105
+ tunnelIdHeaderName: 'x-tunnel-id', // Header name for tunnel identification
106
+ });
107
+ ```
108
+
109
+ #### Environment Variables
110
+
111
+ | Variable | Description | Default | Example |
112
+ | ----------------------- | ------------------------- | ------------- | ------------- |
113
+ | `WS_PORT` | WebSocket server port | `443` | `8080` |
114
+ | `HOST` | Host address to bind | `undefined` | `0.0.0.0` |
115
+ | `PATH_URL` | WebSocket endpoint path | `undefined` | `/tunnel` |
116
+ | `TUNNEL_ID_HEADER_NAME` | HTTP header for tunnel ID | `x-tunnel-id` | `x-tunnel-id` |
117
+ | `LOG_LEVEL` | Logging verbosity | `info` | `debug` |
118
+
119
+ #### TOML Configuration (`config.toml`)
120
+
121
+ ```toml
122
+ # WebSocket server configuration
123
+ wsPort = 443
124
+ host = "0.0.0.0"
125
+ path = "/tunnel"
126
+ tunnelIdHeaderName = "x-tunnel-id"
127
+ ```
128
+
129
+ #### Example Server
130
+
131
+ ```bash
132
+ # Run the example server
133
+ npm run example:server
134
+ ```
135
+
136
+ ### 💻 Client Configuration
137
+
138
+ #### JavaScript API
139
+
140
+ ```javascript
141
+ const { startClient } = require('@remotelinker/reverse-ws-tunnel/client');
142
+
143
+ const client = startClient({
144
+ tunnelId: '1cf2755f-c151-4281-b3f0-55c399035f87', // Unique tunnel identifier (UUID)
145
+ wsUrl: 'wss://example.com/tunnel', // WebSocket server URL
146
+ targetUrl: 'http://localhost:3000', // Local service to expose
147
+ tunnelEntryUrl: 'http://localhost:4443', // Optional: tunnel entry URL
148
+ tunnelEntryPort: 4443, // TCP port for tunnel entry
149
+ allowInsicureCerts: false, // Allow insecure SSL certificates
150
+ headers: {
151
+ // Optional: custom headers
152
+ Authorization: 'Bearer token',
153
+ 'X-Custom-Header': 'value',
154
+ },
155
+ autoReconnect: true, // Automatically reconnect on close (default: true)
156
+ });
157
+
158
+ // Event handling
159
+ client.on('connected', () => console.log('Tunnel connected'));
160
+ client.on('disconnected', () => console.log('Tunnel disconnected'));
161
+
162
+ // Close the tunnel
163
+ // client.close();
164
+ ```
165
+
166
+ #### Environment Variables
167
+
168
+ | Variable | Description | Required | Default | Example |
169
+ | ---------------------- | ---------------------------------- | -------- | ------- | -------------------------------------- |
170
+ | `TUNNEL_ID` | Unique tunnel identifier (UUID-v4) | ✅ | - | `1cf2755f-c151-4281-b3f0-55c399035f87` |
171
+ | `WS_URL` | WebSocket server URL | ✅ | - | `wss://example.com/tunnel` |
172
+ | `TARGET_URL` | Local service URL to expose | ✅ | - | `http://localhost:3000` |
173
+ | `TUNNEL_ENTRY_URL` | Tunnel entry point URL | ❌ | - | `http://localhost:4443` |
174
+ | `TUNNEL_ENTRY_PORT` | TCP port for tunnel entry | ❌ | - | `4443` |
175
+ | `HEADERS` | Custom headers (JSON string) | ❌ | - | `{"Authorization":"Bearer token"}` |
176
+ | `ALLOW_INSICURE_CERTS` | Allow insecure SSL certificates | ❌ | `false` | `true` |
177
+ | `AUTO_RECONNECT` | Automatically reconnect on close | ❌ | `true` | `false` |
178
+ | `LOG_LEVEL` | Logging level | ❌ | `info` | `debug` |
179
+
180
+ #### TOML Configuration (`config.toml`)
181
+
182
+ ```toml
183
+ # Unique identifier of the tunnel (UUID-v4)
184
+ tunnelId = "1cf2755f-c151-4281-b3f0-55c399035f87"
185
+
186
+ # WebSocket server URL to connect to
187
+ wsUrl = "wss://example.com/tunnel"
188
+
189
+ # Target URL where the traffic will be forwarded
190
+ targetUrl = "http://localhost:3000"
191
+
192
+ # Optional URL for tunnel entry point
193
+ tunnelEntryUrl = "http://localhost:4443"
194
+
195
+ # TCP port to open for incoming tunnel connections
196
+ tunnelEntryPort = 4443
197
+
198
+ # Whether to allow insecure SSL certificates (dev/test only)
199
+ allowInsicureCerts = false
200
+
201
+ # Automatically reconnect on close
202
+ autoReconnect = true
203
+
204
+ # Log verbosity level: error, warn, info, debug, trace
205
+ logLevel = "info"
206
+
207
+ # Custom headers to send with requests
208
+ [headers]
209
+ Authorization = "Bearer your-token"
210
+ X-Custom-Header = "custom-value"
211
+ ```
212
+
213
+ #### Example Client
214
+
215
+ ```bash
216
+ # Run the example client
217
+ npm run example:client
218
+ ```
219
+
220
+ ---
221
+
222
+ ## 🐳 Docker Deployment
223
+
224
+ ### Building the Docker Image
225
+
226
+ ```bash
227
+ npm run docker:build
228
+ ```
229
+
230
+ ### Running with Docker Compose
231
+
232
+ ```bash
233
+ npm run docker:deploy
234
+ ```
235
+
236
+ The service will start on port 4443 by default.
237
+
238
+ ### Docker Environment Variables
239
+
240
+ When using Docker, you can pass all the environment variables mentioned above:
241
+
242
+ ```bash
243
+ docker run -e TUNNEL_ID=your-uuid -e WS_URL=wss://example.com -e TARGET_URL=http://localhost:3000 remotelinker/reverse-ws-tunnel
244
+ ```
245
+
246
+ ---
247
+
248
+ ## 📝 Logging
249
+
250
+ The library uses Winston for logging with configurable levels:
251
+
252
+ - `error`: Only error messages
253
+ - `warn`: Warnings and errors
254
+ - `info`: General information (default)
255
+ - `debug`: Detailed debugging information
256
+ - `trace`: Very verbose output including message traces
257
+
258
+ Set the log level via:
259
+
260
+ - Environment variable: `LOG_LEVEL=debug`
261
+ - TOML config: `logLevel = "debug"`
262
+
263
+ ---
264
+
265
+ ## 🔧 Advanced Usage
266
+
267
+ ### Custom Headers
268
+
269
+ You can send custom headers with tunnel requests:
270
+
271
+ ```javascript
272
+ // Environment variable (JSON string)
273
+ process.env.HEADERS = JSON.stringify({
274
+ 'Authorization': 'Bearer your-token',
275
+ 'X-API-Key': 'your-api-key'
276
+ });
277
+
278
+ // Or in config.toml
279
+ [headers]
280
+ Authorization = "Bearer your-token"
281
+ X-API-Key = "your-api-key"
282
+ ```
283
+
284
+ ### SSL/TLS Configuration
285
+
286
+ For development, you might need to allow insecure certificates:
287
+
288
+ ```javascript
289
+ startClient({
290
+ // ... other config
291
+ allowInsicureCerts: true, // Only for development!
292
+ });
293
+ ```
294
+
295
+ ### Multiple Tunnels
296
+
297
+ Each client needs a unique `tunnelId` (UUID-v4). You can run multiple clients with different tunnel IDs to expose multiple services.
298
+
299
+ ---
300
+
301
+ ## 🧪 Examples
302
+
303
+ The repository includes working examples:
304
+
305
+ - **Server**: `examples/server/` - Shows how to set up a tunnel server.
306
+ - **Client**: `examples/client/` - Shows how to connect and expose a local service.
307
+ - **Web Server**: `examples/webserver/` - A minimal target web server.
308
+
309
+ ### Complete Reverse Tunnel Example
310
+
311
+ This example demonstrates how to set up a complete reverse tunnel to expose a local web server to the internet.
312
+
313
+ **1. Start the Target Web Server**
314
+
315
+ First, start the minimal web server that will be the destination of our tunnel. This server will respond with "Hello, World!".
316
+
317
+ ```bash
318
+ # Terminal 1: Start the web server
319
+ node examples/webserver/webserver-example.js
320
+ # Server running on http://localhost:3000/
321
+ ```
322
+
323
+ **2. Start the Tunnel Server**
324
+
325
+ Next, start the tunnel server. This server runs on a publicly accessible machine and listens for WebSocket connections from the tunnel client.
326
+
327
+ The example server is located in `examples/server/`.
328
+
329
+ ```bash
330
+ # Terminal 2: Start the tunnel server
331
+ npm run example:server
332
+ ```
333
+
334
+ This will start the server using the configuration from `examples/server/config.toml`. By default, it listens on port `3000` for WebSocket connections and port `4443` for public HTTP requests.
335
+
336
+ **3. Start the Tunnel Client**
337
+
338
+ Now, start the tunnel client. The client connects to the tunnel server and exposes the local web server.
339
+
340
+ We need to configure the client to connect to our tunnel server and point to our local web server. The example client configuration is in `examples/client/config.toml`. Let's modify it to match our setup.
341
+
342
+ **`examples/client/config.toml`**
343
+
344
+ ```toml
345
+ # Unique identifier of the tunnel (UUID-v4)
346
+ tunnelId = "1cf2755f-c151-4281-b3f0-55c399035f87"
347
+
348
+ # WebSocket server URL to connect to
349
+ wsUrl = "ws://localhost:8080/tunnel"
350
+
351
+ # Target URL where the traffic will be forwarded
352
+ targetUrl = "http://localhost:8080"
353
+
354
+ # TCP port to open for incoming tunnel connections
355
+ tunnelEntryPort = 4443
356
+ ```
357
+
358
+ Now, run the client:
359
+
360
+ ```bash
361
+ # Terminal 3: Start the tunnel client
362
+ npm run example:client
363
+ ```
364
+
365
+ **4. Test the Tunnel**
366
+
367
+ The tunnel is now active. The tunnel server is listening for requests on port `4443` and will forward them to your local web server running on port `8080`.
368
+
369
+ You can test it by making a `curl` request to the tunnel server's public endpoint, including the `x-tunnel-id` header:
370
+
371
+ ```bash
372
+ # Terminal 4: Test the tunnel
373
+ curl -X GET http://localhost:8083 -H "x-tunnel-id: 1cf2755f-c151-4281-b3f0-55c399035f87"
374
+ ```
375
+
376
+ You should see the "Hello, World!" response from your local web server.
377
+
378
+ ```
379
+ Hello, World!
380
+ ```
381
+
382
+ ---
383
+
384
+ ## ⚠️ Security Considerations
385
+
386
+ - **Production Use**: Ready for production use
387
+ - **SSL/TLS**: Always use secure WebSocket connections (`wss://`) in production
388
+ - **Authentication**: Implement proper authentication mechanisms for your tunnels
389
+ - **Rate Limiting**: Consider implementing rate limiting on the server side
390
+ - **Firewall**: Ensure proper firewall rules are in place
391
+
392
+ ---
393
+
394
+ ## 📄 License
395
+
396
+ ISC License - see [LICENSE](LICENSE) file for details.
397
+
398
+ ---
399
+
400
+ ## 🤝 Contributing
401
+
402
+ This project is in active development. Contributions, issues, and feature requests are welcome!
403
+
404
+ ## 📞 Support
405
+
406
+ For questions and support, please open an issue on the GitHub repository.
@@ -0,0 +1,34 @@
1
+ const { startHttpProxyServer } = require('./proxyServer');
2
+ const { connectWebSocket } = require('./tunnelClient');
3
+ const { setLogContext } = require('../utils/logger');
4
+
5
+ function startClient({ targetUrl, allowInsicureCerts, wsUrl, tunnelId, tunnelEntryUrl, tunnelEntryPort, headers, environment, autoReconnect }) {
6
+ setLogContext('CLIENT');
7
+ environment = environment || 'production';
8
+ const proxy = startHttpProxyServer(targetUrl, allowInsicureCerts);
9
+ const TARGET_PORT = proxy.port;
10
+
11
+ const client = connectWebSocket({
12
+ wsUrl,
13
+ tunnelId,
14
+ targetUrl,
15
+ targetPort: TARGET_PORT,
16
+ tunnelEntryUrl,
17
+ tunnelEntryPort,
18
+ headers,
19
+ environment,
20
+ autoReconnect,
21
+ });
22
+
23
+ const originalClose = client.close;
24
+ client.close = () => {
25
+ originalClose.call(client);
26
+ proxy.close();
27
+ };
28
+
29
+ return client;
30
+ }
31
+
32
+ module.exports = {
33
+ startClient,
34
+ };
@@ -0,0 +1,74 @@
1
+ const http = require('http');
2
+ const httpProxy = require('http-proxy');
3
+ const { logger } = require('../utils/logger');
4
+
5
+ /**
6
+ * Starts an HTTP/WebSocket proxy server to forward to the target URL.
7
+ * @param {string} targetUrl - The URL to forward requests to.
8
+ * @param {boolean} allowInsecureCerts - If true, allows self-signed certs.
9
+ * @returns {number} - The port the proxy is listening on.
10
+ */
11
+ function startHttpProxyServer(targetUrl, allowInsecureCerts = false) {
12
+ logger.info('Starting HTTP/WS proxy server...');
13
+ logger.debug(`Target URL: ${targetUrl}`);
14
+ logger.debug(`Allow insecure certs: ${allowInsecureCerts}`);
15
+
16
+ const proxy = httpProxy.createProxyServer({});
17
+
18
+ const server = http.createServer((req, res) => {
19
+ logger.trace(`Incoming HTTP request: ${req.method} ${req.url}`);
20
+
21
+ if (!targetUrl) {
22
+ logger.warn('Request received without targetUrl set');
23
+ res.writeHead(400, { 'Content-Type': 'text/plain' });
24
+ return res.end('Missing TARGET_URL');
25
+ }
26
+
27
+ proxy.web(req, res, { target: targetUrl, changeOrigin: true, secure: !allowInsecureCerts }, (err) => {
28
+ logger.error('Proxy web error:', err);
29
+ if (!res.headersSent) {
30
+ res.writeHead(502);
31
+ res.end('Bad gateway');
32
+ } else {
33
+ res.end();
34
+ }
35
+ });
36
+ });
37
+
38
+ server.on('upgrade', (req, socket, head) => {
39
+ logger.trace(`Incoming WebSocket upgrade: ${req.url}`);
40
+ proxy.ws(req, socket, head, { target: targetUrl, changeOrigin: false, secure: !allowInsecureCerts }, (err) => {
41
+ logger.error('Proxy WS upgrade error:', err);
42
+ socket.end();
43
+ });
44
+ });
45
+
46
+ proxy.on('error', (err, req, res) => {
47
+ logger.error('Proxy internal error:', err);
48
+ if (res && !res.headersSent) {
49
+ res.writeHead(500, { 'Content-Type': 'text/plain' });
50
+ res.end('Proxy error');
51
+ }
52
+ });
53
+
54
+ let assignedPort;
55
+
56
+ server.listen(0, () => {
57
+ assignedPort = server.address().port;
58
+ logger.info(`Proxy server is listening on port ${assignedPort}`);
59
+ });
60
+
61
+ return {
62
+ get port() {
63
+ return assignedPort || server.address()?.port;
64
+ },
65
+ close: () => {
66
+ logger.info('Closing proxy server');
67
+ server.close();
68
+ },
69
+ };
70
+ }
71
+
72
+ module.exports = {
73
+ startHttpProxyServer,
74
+ };