npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@stream-toolbox/tunnel

v1.0.0

Published

Forward data bidirectionally between two duplexes, like `a.pipe(b).pipe(a)`, but with better handling for various cases.

Downloads

2

Readme

@stream-toolbox/tunnel

English 中文文档


↔️ Forward data bidirectionally between two duplexes, like a.pipe(b).pipe(a), but with better handling for various cases.

Installation

npm i @stream-toolbox/tunnel

Quick Start

const { createServer, createConnection } = require("node:net");
const tunnel = require("@stream-toolbox/tunnel");

// Map the serverPort of serverAddress to port 8001 of local host
const serverAddress = "xxx.xxx.xxx.xxx"; // IP or hostname
const serverPort = 22;

const localAddress = "0.0.0.0";
const localPort = 8001;

const proxyServer = createServer((clientSocket) => {
  const clientAddress = clientSocket.remoteAddress;

  console.log(`TCP connection established successfully: ${clientAddress} <===> ${localAddress}`);

  const serverSocket = createConnection({
    host: serverAddress,
    port: serverPort,
    timeout: 3000,
  })
    .once("timeout", () => {
      console.log(`TCP connection establishment timed out: ${localAddress} <===> ${serverAddress}`);
      serverSocket.destroy();
      clientSocket.destroy();
    })
    .once("error", (err) => {
      console.log(`TCP connection establishment failed: ${localAddress} <===> ${serverAddress}`, err);
      serverSocket.destroy();
      clientSocket.destroy();
    })
    .once("connect", () => {
      console.log(`TCP connection established successfully: ${localAddress} <===> ${serverAddress}`);

      // after connection established, remove the connection timeout and failure event listeners
      serverSocket.removeAllListeners("error");
      serverSocket.removeAllListeners("timeout");
      serverSocket.setTimeout(0);

      // Bidirectional forwarding data of clientSocket and serverSocket
      tunnel(clientSocket, serverSocket, (err, time) => {
        console.log(`Tunnel closed, tunnel alive for ${time} milliseconds`);
        console.log(`${clientAddress} sended ${serverSocket.bytesWritten} bytes data to ${serverAddress}`);
        console.log(`${serverAddress} sended ${clientSocket.bytesWritten} bytes data to ${clientAddress}`);

        if (err) {
          console.log(`The tunnel is broken because of ${err.causedBy === serverSocket ? "serverSocket" : "clientSocket"} throws an error:`, err);
        }
      });
    });
}).listen(localPort, localAddress, () => {
  console.log("proxy server start", proxyServer.address());
});

If you can use the ssh -i ~/.ssh/id_rsa -p 22 [email protected] command to remotely log in to the xxx.xxx.xxx.xxx host, after starting the above proxy server locally, now you should also be able to use ssh -i ~/.ssh/id_rsa -p 8001 [email protected] to log in to the host.

Diagram when the client is directly connected to the remote ssh server:

Diagram when connecting through the proxy server:

API

type DuplexLike = {
  rs: Readable; // readable stream
  ws: Writable; // writable stream
  allowHalfOpen?: boolean; // default false,which means when rs ended,call ws.end() automatically
  allowPipeHalfOpen?: boolean; // default false,which means when rs ended,end the writable stream of the other side of tunnel automatically
};

function tunnel(a: Duplex | DuplexLike, b: Duplex | DuplexLike): Promise<number>;
function tunnel(a: Duplex | DuplexLike, b: Duplex | DuplexLike, condition: number): Promise<number>;
function tunnel(a: Duplex | DuplexLike, b: Duplex | DuplexLike, callback: callback): void;
function tunnel(a: Duplex | DuplexLike, b: Duplex | DuplexLike, condition: number, callback: callback): void;

For the explanation of allowHalfOpen, allowPipeHalfOpen and condition, refer to the figure below:


↔️ 在两个 duplex 间双向转发数据,类似 a.pipe(b).pipe(a),但是对各种情况有更完善的处理。

安装

npm i @stream-toolbox/tunnel

快速开始

const { createServer, createConnection } = require("node:net");
const tunnel = require("@stream-toolbox/tunnel");

// 将 serverAddress 的 serverPort 端口映射到本机的 8001 端口
const serverAddress = "xxx.xxx.xxx.xxx"; // IP 地址或域名
const serverPort = 22;

const localAddress = "0.0.0.0";
const localPort = 8001;

const proxyServer = createServer((clientSocket) => {
  const clientAddress = clientSocket.remoteAddress;

  console.log(`TCP 连接建立成功: ${clientAddress} <===> ${localAddress}`);

  const serverSocket = createConnection({
    host: serverAddress,
    port: serverPort,
    timeout: 3000,
  })
    .once("timeout", () => {
      console.log(`TCP 连接建立超时: ${localAddress} <===> ${serverAddress}`);
      serverSocket.destroy();
      clientSocket.destroy();
    })
    .once("error", (err) => {
      console.log(`TCP 连接建立失败: ${localAddress} <===> ${serverAddress}`, err);
      serverSocket.destroy();
      clientSocket.destroy();
    })
    .once("connect", () => {
      console.log(`TCP 连接建立成功: ${localAddress} <===> ${serverAddress}`);

      // TCP 连接建立成功后,移除连接超时和连接失败的事件监听
      serverSocket.removeAllListeners("error");
      serverSocket.removeAllListeners("timeout");
      serverSocket.setTimeout(0);

      // 对 clientSocket 和 serverSocket 的数据进行双向转发
      tunnel(clientSocket, serverSocket, (err, time) => {
        console.log(`隧道已断开,隧道存活了 ${time} 毫秒`);
        console.log(`${clientAddress} 累积向 ${serverAddress} 发送了 ${serverSocket.bytesWritten} 字节的数据`);
        console.log(`${serverAddress} 累积向 ${clientAddress} 发送了 ${clientSocket.bytesWritten} 字节的数据`);

        if (err) {
          console.log(`隧道断开是因为 ${err.causedBy === serverSocket ? "serverSocket" : "clientSocket"} 抛出了错误:`, err);
        }
      });
    });
}).listen(localPort, localAddress, () => {
  console.log("proxy server start", proxyServer.address());
});

如果你可以使用 ssh -i ~/.ssh/id_rsa -p 22 [email protected] 命令远程登录到 xxx.xxx.xxx.xxx 主机,在本地启动上面的代理服务后,你现在应该也可以使用 ssh -i ~/.ssh/id_rsa -p 8001 [email protected] 登录进去该主机了。

客户端与 SSH 服务端直连时的示意图:

通过代理服务连接时的示意图:

API

type DuplexLike = {
  rs: Readable; // readable stream
  ws: Writable; // writable stream
  allowHalfOpen?: boolean; // 默认 false,即当 rs 结束时,自动终止 ws
  allowPipeHalfOpen?: boolean; // 默认 false,即当 rs 结束时,自动终止 tunnel 另一端的 ws
};

function tunnel(a: Duplex | DuplexLike, b: Duplex | DuplexLike): Promise<number>;
function tunnel(a: Duplex | DuplexLike, b: Duplex | DuplexLike, condition: number): Promise<number>;
function tunnel(a: Duplex | DuplexLike, b: Duplex | DuplexLike, callback: callback): void;
function tunnel(a: Duplex | DuplexLike, b: Duplex | DuplexLike, condition: number, callback: callback): void;

关于 allowHalfOpen, allowPipeHalfOpen 以及 condition 参数的解释参考下图: