deno-websocket icon indicating copy to clipboard operation
deno-websocket copied to clipboard

Socks proxy support

Open Chris2011 opened this issue 2 years ago • 6 comments

For the WebSocket npm package (ws) in node, it is possible to connect to a proxy. For example, if I'm running a TOR server, I can use the node package socks-proxy-agent (https://www.npmjs.com/package/socks-proxy-agent) to create such agent and hand over the option to the WebSocket constructor. Afaik, websockets for deno doesn't support such proxies. It would be nice, if we can have this for the deno package too.

Chris2011 avatar Dec 02 '21 20:12 Chris2011

Here is my node.js example:

import SocksProxyAgent from 'socks-proxy-agent';
import WebSocket from 'ws';

// SOCKS proxy to connect to
const proxy: string = process.env.socks_proxy || 'socks://127.0.0.1:9050';
console.log('using proxy server %j', proxy);

// WebSocket endpoint for the proxy to connect to
const endpoint: string = process.argv[2] || 'ws://echo.websocket.org';
console.log('attempting to connect to WebSocket %j', endpoint);

// create an instance of the `SocksProxyAgent` class with the proxy server information
const agent: unknown = SocksProxyAgent(proxy);

// initiate the WebSocket connection
const socket: WebSocket = new WebSocket(endpoint, { agent: agent } as any);

socket.on('open', function () {
  console.log('"open" event!');
  socket.send('hello world');
});

socket.on('message', function (data: unknown, flags: unknown) {
  console.log('"message" event! ', data, flags);
  socket.close();
});

Chris2011 avatar Dec 02 '21 20:12 Chris2011

👍

Mutefish0 avatar Jul 07 '22 16:07 Mutefish0

here is my workaround

Node.js child process:

// node.js
const WebSocket = require("ws");
const HttpsProxyAgent = require("https-proxy-agent");
const net = require("net");
const url = require("url");
const _socket = new net.Socket();

const endpoint = process.argv[2];

if (!endpoint.startsWith("wss://")) {
  throw new Error(`Invalid ws endpoint: ${endpoint}`);
}

_socket.connect({ port: 9123, keepAlive: true }, () => {
  let socket;
  const proxy =
    process.env.all_proxy || process.env.http_proxy || process.env.https_proxy;
  if (proxy) {
    const options = url.parse(proxy);
    const agent = new HttpsProxyAgent(options);
    socket = new WebSocket(endpoint, { agent: agent });
  } else {
    socket = new WebSocket(endpoint);
  }
  socket.on("open", function () {
    console.log("open:", endpoint);
    _socket.on("data", function (chunk) {
      const msg = chunk.toString();
      socket.send(msg);
    });
    _socket.write("onopen");
  });
  socket.on("message", function (data) {
    _socket.write(`||${data}`);
  });
  socket.on("error", function (e) {
    console.log(e);
    _socket.end();
    throw new Error(e);
  });
});

Deno main process

// mod.ts
const decoder = new TextDecoder();
const encoder = new TextEncoder();

class WebSocketNode {
  private closed = false;
  public onopen = () => {};
  public onerror = (e: Error) => {};
  public onmessage = (e: { data: string }) => {};

  private conn?: Deno.Conn;

  constructor(endpoint: string) {
    const cmd = [
      "node",
      new URL("node.js", import.meta.url).pathname,
      endpoint,
    ];
    this.listen();
    Deno.run({ cmd });
  }

  private async listen() {
    const listener = Deno.listen({ port: 9123 });
    console.log("listening on 0.0.0.0:9123");
    for await (const conn of listener) {
      this.conn = conn;
      while (!this.closed) {
        const buffer = new Uint8Array(1024 * 1024 * 10);
        const len = (await conn.read(buffer)) || 0;
        if (len > 0) {
          const msg = decoder.decode(buffer.slice(0, len));
          if (msg === "onopen") {
            this.onopen();
          } else {
            const msgs = msg.split("||").slice(1);
            for (const m of msgs) {
              this.onmessage({ data: m });
            }
          }
        }
      }
    }
  }

  public send(data: string) {
    if (this.conn) {
      this.conn.write(encoder.encode(data));
    }
  }
}

export default WebSocketNode;

Usage:

import WebSocketNode from './mod.ts';
const ws = new WebSocketNode("wss://...");
ws.onmessage = (e) => console.log(e.data)

Mutefish0 avatar Jul 07 '22 17:07 Mutefish0

So your socks5 proxy is running locally on 9123, right? I will test this out, thx. I just tried this lib, seems pretty new: https://github.com/rclarey/socks5 but it is not working with tor. Jut got generic socks error. Will let the maintainer know.

Chris2011 avatar Jul 08 '22 07:07 Chris2011

So your socks5 proxy is running locally on 9123, right? I will test this out, thx. I just tried this lib, seems pretty new: https://github.com/rclarey/socks5 but it is not working with tor. Jut got generic socks error. Will let the maintainer know.

The environment variable all_proxy, https_proxy , http_proxy specified the socks5 proxy .

Deno main process communicates with node.js child process using port 9123.

Mutefish0 avatar Jul 10 '22 09:07 Mutefish0

Ahh I see now. Thx :). Really just a workaround, but also nice.

Chris2011 avatar Jul 10 '22 09:07 Chris2011