private-connect 0.3.6 → 0.4.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.
Files changed (2) hide show
  1. package/dist/index.js +123 -7
  2. 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
- Get a public URL instantly
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
- await runTunnelProxy(data.tunnel.tunnelId, wsUrl, host, port);
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
- createTemporaryTunnel({ host, port }).catch(console.error);
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "private-connect",
3
- "version": "0.3.6",
3
+ "version": "0.4.0",
4
4
  "description": "Test connectivity to any service. No signup required.",
5
5
  "bin": {
6
6
  "private-connect": "./dist/index.js"