ryo-ma / deno-websocket

🦕 A simple WebSocket library like ws of node.js library for deno
https://deno.land/x/websocket
MIT License
152 stars 19 forks source link

Socks proxy support #33

Open Chris2011 opened 2 years ago

Chris2011 commented 2 years ago

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 commented 2 years ago

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();
});
Mutefish0 commented 2 years ago

👍

Mutefish0 commented 2 years ago

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)
Chris2011 commented 2 years ago

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.

Mutefish0 commented 2 years ago

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.

Chris2011 commented 2 years ago

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