api-response-manager 2.6.4 ā 2.6.6
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 +1 -1
- package/commands/tunnel.js +78 -12
- package/package.json +1 -1
package/README.md
CHANGED
package/commands/tunnel.js
CHANGED
|
@@ -153,19 +153,10 @@ async function connectTunnelClient(tunnelId, subdomain, localPort, protocol = 'h
|
|
|
153
153
|
console.log(chalk.white('Your local server is now accessible at:'));
|
|
154
154
|
console.log(chalk.cyan.bold(` ${message.publicUrl}\n`));
|
|
155
155
|
|
|
156
|
-
// Show TCP/SSH specific info
|
|
156
|
+
// Show TCP/SSH specific info and start local proxy
|
|
157
157
|
if (isTcpTunnel && message.tcpHost && message.tcpPort) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
console.log(chalk.white(` TCP Port: ${message.tcpPort}\n`));
|
|
161
|
-
if (message.sshCommand) {
|
|
162
|
-
console.log(chalk.gray('SSH Command (Linux/Mac):'));
|
|
163
|
-
console.log(chalk.cyan(` ssh -o ProxyCommand="sh -c '{ printf \\"SUBDOMAIN:${subdomain}\\\\n\\"; cat; } | nc %h %p'" user@${message.tcpHost} -p ${message.tcpPort}\n`));
|
|
164
|
-
console.log(chalk.gray('SSH Command (Windows with ncat):'));
|
|
165
|
-
console.log(chalk.cyan(` ssh -o ProxyCommand="cmd /c \\"(echo SUBDOMAIN:${subdomain}& type CON) | ncat ${message.tcpHost} ${message.tcpPort}\\"" user@${message.tcpHost}\n`));
|
|
166
|
-
console.log(chalk.gray('SSH with Key (passwordless):'));
|
|
167
|
-
console.log(chalk.cyan(` ssh -i ~/.ssh/your_key.pem -o ProxyCommand="sh -c '{ printf \\"SUBDOMAIN:${subdomain}\\\\n\\"; cat; } | nc %h %p'" user@${message.tcpHost} -p ${message.tcpPort}\n`));
|
|
168
|
-
}
|
|
158
|
+
// Start a local TCP proxy that transparently handles subdomain multiplexing
|
|
159
|
+
startLocalTcpProxy(subdomain, message.tcpHost, message.tcpPort, silent);
|
|
169
160
|
} else {
|
|
170
161
|
// Display QR code for HTTP/HTTPS tunnels (not for TCP/SSH)
|
|
171
162
|
displayQRCode(message.publicUrl);
|
|
@@ -284,6 +275,11 @@ async function connectTunnelClient(tunnelId, subdomain, localPort, protocol = 'h
|
|
|
284
275
|
process.on('SIGINT', () => {
|
|
285
276
|
if (heartbeatInterval) clearInterval(heartbeatInterval);
|
|
286
277
|
console.log(chalk.yellow('\n\nā Stopping tunnel...'));
|
|
278
|
+
// Close local TCP proxy if running
|
|
279
|
+
if (localProxyServer) {
|
|
280
|
+
localProxyServer.close();
|
|
281
|
+
localProxyServer = null;
|
|
282
|
+
}
|
|
287
283
|
ws.close();
|
|
288
284
|
});
|
|
289
285
|
}
|
|
@@ -565,6 +561,76 @@ async function configureIngress(tunnelId, rules, options) {
|
|
|
565
561
|
}
|
|
566
562
|
}
|
|
567
563
|
|
|
564
|
+
// Local TCP proxy for SSH/TCP tunnels (ngrok-style)
|
|
565
|
+
// Starts a local TCP server that transparently handles subdomain multiplexing.
|
|
566
|
+
// User just connects to localhost:<localProxyPort> ā the proxy injects the
|
|
567
|
+
// SUBDOMAIN header and relays all data bidirectionally to the tunnel server.
|
|
568
|
+
let localProxyServer = null;
|
|
569
|
+
|
|
570
|
+
function startLocalTcpProxy(subdomain, remoteHost, remotePort, silent) {
|
|
571
|
+
localProxyServer = net.createServer((clientSocket) => {
|
|
572
|
+
if (!silent) {
|
|
573
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
574
|
+
console.log(chalk.gray(`[${timestamp}]`), chalk.magenta('SSH'), chalk.white('New connection via local proxy'));
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Connect to the tunnel server's TCP port
|
|
578
|
+
const remoteSocket = net.createConnection({
|
|
579
|
+
host: remoteHost,
|
|
580
|
+
port: remotePort
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
remoteSocket.on('connect', () => {
|
|
584
|
+
// Send subdomain header first, then pipe everything bidirectionally
|
|
585
|
+
remoteSocket.write(`SUBDOMAIN:${subdomain}\n`, () => {
|
|
586
|
+
// After header is sent, relay data both ways
|
|
587
|
+
clientSocket.pipe(remoteSocket, { end: true });
|
|
588
|
+
remoteSocket.pipe(clientSocket, { end: true });
|
|
589
|
+
});
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
remoteSocket.on('error', (error) => {
|
|
593
|
+
if (!silent) {
|
|
594
|
+
console.error(chalk.red(` Proxy remote error: ${error.message}`));
|
|
595
|
+
}
|
|
596
|
+
clientSocket.end();
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
clientSocket.on('error', (error) => {
|
|
600
|
+
if (!silent && error.code !== 'ECONNRESET') {
|
|
601
|
+
console.error(chalk.red(` Proxy client error: ${error.message}`));
|
|
602
|
+
}
|
|
603
|
+
remoteSocket.end();
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
clientSocket.on('end', () => {
|
|
607
|
+
remoteSocket.end();
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
remoteSocket.on('end', () => {
|
|
611
|
+
clientSocket.end();
|
|
612
|
+
});
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
// Listen on port 0 to get a random available port
|
|
616
|
+
localProxyServer.listen(0, '127.0.0.1', () => {
|
|
617
|
+
const localPort = localProxyServer.address().port;
|
|
618
|
+
|
|
619
|
+
if (!silent) {
|
|
620
|
+
console.log(chalk.yellow.bold('š SSH/TCP Tunnel Ready!\n'));
|
|
621
|
+
console.log(chalk.white(' Connect via SSH:\n'));
|
|
622
|
+
console.log(chalk.cyan.bold(` ssh <username>@localhost -p ${localPort}\n`));
|
|
623
|
+
console.log(chalk.gray(' SSH with Key:'));
|
|
624
|
+
console.log(chalk.cyan(` ssh -i ~/.ssh/your_key <username>@localhost -p ${localPort}\n`));
|
|
625
|
+
console.log(chalk.gray.italic(` Replace <username> with your device's SSH username\n`));
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
localProxyServer.on('error', (error) => {
|
|
630
|
+
console.error(chalk.red(`Local proxy error: ${error.message}`));
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
|
|
568
634
|
// TCP connection handlers for SSH/TCP tunnels
|
|
569
635
|
function handleTcpConnect(message, localPort, ws, silent) {
|
|
570
636
|
const { connectionId, remoteAddress, remotePort } = message;
|