private-connect 0.3.5 → 0.3.7
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/dist/index.js +123 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -254,11 +254,12 @@ ${c.bold}Examples:${c.reset}
|
|
|
254
254
|
npx private-connect test https://api.example.com
|
|
255
255
|
npx private-connect tunnel 3000
|
|
256
256
|
npx private-connect tunnel localhost:8080
|
|
257
|
+
npx private-connect tunnel 4096 --tcp
|
|
257
258
|
|
|
258
259
|
${c.bold}Tunnel:${c.reset}
|
|
259
260
|
• No signup required
|
|
260
261
|
• Auto-expires in 2 hours
|
|
261
|
-
•
|
|
262
|
+
• HTTP or raw TCP (--tcp flag)
|
|
262
263
|
|
|
263
264
|
${c.bold}Test:${c.reset}
|
|
264
265
|
• TCP reachability
|
|
@@ -275,9 +276,10 @@ ${c.dim}For permanent tunnels: https://privateconnect.co${c.reset}
|
|
|
275
276
|
const HUB_URL = process.env.CONNECT_HUB_URL || 'https://api.privateconnect.co';
|
|
276
277
|
const TUNNEL_DOMAIN = process.env.CONNECT_TUNNEL_DOMAIN || 'tunnel.privateconnect.co';
|
|
277
278
|
async function createTemporaryTunnel(options) {
|
|
278
|
-
const { host, port, ttl = 120 } = options;
|
|
279
|
+
const { host, port, ttl = 120, tcp = false } = options;
|
|
280
|
+
const tunnelType = tcp ? 'tcp' : 'http';
|
|
279
281
|
console.log();
|
|
280
|
-
console.log(`${c.bold}Private Connect${c.reset} - Temporary Tunnel`);
|
|
282
|
+
console.log(`${c.bold}Private Connect${c.reset} - Temporary ${tcp ? 'TCP ' : ''}Tunnel`);
|
|
281
283
|
console.log(`${c.gray}────────────────────────────────────${c.reset}`);
|
|
282
284
|
console.log();
|
|
283
285
|
// Check if local service is running
|
|
@@ -294,9 +296,8 @@ async function createTemporaryTunnel(options) {
|
|
|
294
296
|
console.log(`${ok}`);
|
|
295
297
|
// Generate a temporary tunnel ID
|
|
296
298
|
const tunnelId = (0, crypto_1.randomBytes)(6).toString('hex');
|
|
297
|
-
const publicUrl = `https://${tunnelId}.${TUNNEL_DOMAIN}`;
|
|
298
299
|
// Request tunnel from hub
|
|
299
|
-
process.stdout.write(` Requesting tunnel... `);
|
|
300
|
+
process.stdout.write(` Requesting ${tunnelType} tunnel... `);
|
|
300
301
|
try {
|
|
301
302
|
const response = await httpRequest(`${HUB_URL}/v1/tunnels/temporary`, {
|
|
302
303
|
method: 'POST',
|
|
@@ -306,6 +307,7 @@ async function createTemporaryTunnel(options) {
|
|
|
306
307
|
localHost: host,
|
|
307
308
|
localPort: port,
|
|
308
309
|
ttlMinutes: ttl,
|
|
310
|
+
type: tunnelType,
|
|
309
311
|
}),
|
|
310
312
|
});
|
|
311
313
|
if (!response.ok) {
|
|
@@ -339,6 +341,9 @@ async function createTemporaryTunnel(options) {
|
|
|
339
341
|
console.log();
|
|
340
342
|
console.log(` ${c.bold}Local:${c.reset} ${c.cyan}${host}:${port}${c.reset}`);
|
|
341
343
|
console.log(` ${c.bold}Public:${c.reset} ${c.green}${data.tunnel.publicUrl}${c.reset}`);
|
|
344
|
+
if (data.tunnel.type === 'tcp' && data.tunnel.tcpHost && data.tunnel.tcpPort) {
|
|
345
|
+
console.log(` ${c.bold}Connect:${c.reset} ${c.cyan}${data.tunnel.tcpHost}:${data.tunnel.tcpPort}${c.reset}`);
|
|
346
|
+
}
|
|
342
347
|
console.log(` ${c.bold}Expires:${c.reset} ${data.tunnel.ttlMinutes} minutes`);
|
|
343
348
|
console.log();
|
|
344
349
|
console.log(`${c.gray}────────────────────────────────────${c.reset}`);
|
|
@@ -346,7 +351,12 @@ async function createTemporaryTunnel(options) {
|
|
|
346
351
|
console.log(` ${c.dim}Press Ctrl+C to stop${c.reset}`);
|
|
347
352
|
console.log();
|
|
348
353
|
// Keep connection alive and handle incoming requests
|
|
349
|
-
|
|
354
|
+
if (data.tunnel.type === 'tcp') {
|
|
355
|
+
await runTcpTunnelProxy(data.tunnel.tunnelId, wsUrl, host, port);
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
await runTunnelProxy(data.tunnel.tunnelId, wsUrl, host, port);
|
|
359
|
+
}
|
|
350
360
|
}
|
|
351
361
|
catch (err) {
|
|
352
362
|
console.log(`${fail}`);
|
|
@@ -512,6 +522,110 @@ function forwardToLocal(host, port, request) {
|
|
|
512
522
|
req.end();
|
|
513
523
|
});
|
|
514
524
|
}
|
|
525
|
+
/**
|
|
526
|
+
* Run TCP tunnel proxy - forward raw TCP connections
|
|
527
|
+
*/
|
|
528
|
+
async function runTcpTunnelProxy(tunnelId, wsUrl, localHost, localPort) {
|
|
529
|
+
return new Promise((resolve) => {
|
|
530
|
+
const url = new url_1.URL(wsUrl.replace('ws://', 'http://').replace('wss://', 'https://'));
|
|
531
|
+
const baseUrl = `${url.protocol}//${url.host}`;
|
|
532
|
+
const namespace = url.pathname || '/temp-tunnel';
|
|
533
|
+
const socket = (0, socket_io_client_1.io)(`${baseUrl}${namespace}`, {
|
|
534
|
+
transports: ['websocket'],
|
|
535
|
+
reconnection: true,
|
|
536
|
+
reconnectionAttempts: 10,
|
|
537
|
+
reconnectionDelay: 1000,
|
|
538
|
+
});
|
|
539
|
+
// Track active TCP connections
|
|
540
|
+
const tcpConnections = new Map();
|
|
541
|
+
let connectionCount = 0;
|
|
542
|
+
socket.on('connect', () => {
|
|
543
|
+
socket.emit('register', { tunnelId }, (response) => {
|
|
544
|
+
if (!response.success) {
|
|
545
|
+
console.log(` ${c.red}Failed to register: ${response.error}${c.reset}`);
|
|
546
|
+
socket.disconnect();
|
|
547
|
+
resolve();
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
});
|
|
551
|
+
socket.on('disconnect', (reason) => {
|
|
552
|
+
// Close all TCP connections
|
|
553
|
+
for (const [, conn] of tcpConnections) {
|
|
554
|
+
conn.end();
|
|
555
|
+
}
|
|
556
|
+
tcpConnections.clear();
|
|
557
|
+
if (reason === 'io server disconnect') {
|
|
558
|
+
console.log(` ${c.yellow}Tunnel expired or closed by server${c.reset}`);
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
socket.on('tunnel_expired', () => {
|
|
562
|
+
console.log();
|
|
563
|
+
console.log(` ${c.yellow}Tunnel expired${c.reset}`);
|
|
564
|
+
console.log();
|
|
565
|
+
socket.disconnect();
|
|
566
|
+
resolve();
|
|
567
|
+
});
|
|
568
|
+
// Handle TCP dial request from hub
|
|
569
|
+
socket.on('tcp_dial', (data) => {
|
|
570
|
+
connectionCount++;
|
|
571
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
572
|
+
console.log(` ${c.gray}[${timestamp}]${c.reset} ${c.cyan}TCP${c.reset} connection ${data.connectionId.slice(0, 8)}`);
|
|
573
|
+
// Connect to local service
|
|
574
|
+
const localSocket = net.createConnection({
|
|
575
|
+
host: data.targetHost,
|
|
576
|
+
port: data.targetPort,
|
|
577
|
+
});
|
|
578
|
+
tcpConnections.set(data.connectionId, localSocket);
|
|
579
|
+
localSocket.on('connect', () => {
|
|
580
|
+
socket.emit('tcp_dial_success', { connectionId: data.connectionId });
|
|
581
|
+
});
|
|
582
|
+
localSocket.on('data', (chunk) => {
|
|
583
|
+
socket.emit('tcp_data', {
|
|
584
|
+
connectionId: data.connectionId,
|
|
585
|
+
data: chunk.toString('base64'),
|
|
586
|
+
});
|
|
587
|
+
});
|
|
588
|
+
localSocket.on('close', () => {
|
|
589
|
+
socket.emit('tcp_close', { connectionId: data.connectionId });
|
|
590
|
+
tcpConnections.delete(data.connectionId);
|
|
591
|
+
});
|
|
592
|
+
localSocket.on('error', (err) => {
|
|
593
|
+
console.log(` ${c.red}TCP error: ${err.message}${c.reset}`);
|
|
594
|
+
socket.emit('tcp_close', { connectionId: data.connectionId });
|
|
595
|
+
tcpConnections.delete(data.connectionId);
|
|
596
|
+
});
|
|
597
|
+
});
|
|
598
|
+
// Handle TCP data from hub (from remote client)
|
|
599
|
+
socket.on('tcp_data', (data) => {
|
|
600
|
+
const localSocket = tcpConnections.get(data.connectionId);
|
|
601
|
+
if (localSocket) {
|
|
602
|
+
const buffer = Buffer.from(data.data, 'base64');
|
|
603
|
+
localSocket.write(buffer);
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
// Handle TCP close from hub
|
|
607
|
+
socket.on('tcp_close', (data) => {
|
|
608
|
+
const localSocket = tcpConnections.get(data.connectionId);
|
|
609
|
+
if (localSocket) {
|
|
610
|
+
localSocket.end();
|
|
611
|
+
tcpConnections.delete(data.connectionId);
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
// Handle shutdown
|
|
615
|
+
process.on('SIGINT', () => {
|
|
616
|
+
console.log();
|
|
617
|
+
console.log(` ${c.yellow}Tunnel closed${c.reset}`);
|
|
618
|
+
console.log(` ${c.gray}Handled ${connectionCount} connections${c.reset}`);
|
|
619
|
+
console.log();
|
|
620
|
+
// Close all connections
|
|
621
|
+
for (const [, conn] of tcpConnections) {
|
|
622
|
+
conn.end();
|
|
623
|
+
}
|
|
624
|
+
socket.disconnect();
|
|
625
|
+
resolve();
|
|
626
|
+
});
|
|
627
|
+
});
|
|
628
|
+
}
|
|
515
629
|
function parseTunnelTarget(target) {
|
|
516
630
|
// Handle just port number
|
|
517
631
|
if (/^\d+$/.test(target)) {
|
|
@@ -546,10 +660,12 @@ else if (args[0] === 'tunnel') {
|
|
|
546
660
|
console.error(`${c.red}Error: Port required${c.reset}`);
|
|
547
661
|
console.error(`Usage: npx private-connect tunnel <port>`);
|
|
548
662
|
console.error(` npx private-connect tunnel localhost:3000`);
|
|
663
|
+
console.error(` npx private-connect tunnel 4096 --tcp`);
|
|
549
664
|
process.exit(1);
|
|
550
665
|
}
|
|
551
666
|
const { host, port } = parseTunnelTarget(args[1]);
|
|
552
|
-
|
|
667
|
+
const tcp = args.includes('--tcp') || args.includes('-t');
|
|
668
|
+
createTemporaryTunnel({ host, port, tcp }).catch(console.error);
|
|
553
669
|
}
|
|
554
670
|
else {
|
|
555
671
|
// Default to test if just a target is provided
|