Skip to content

Commit

Permalink
Merge pull request #411 from DoctorMcKay/socks-proxy
Browse files Browse the repository at this point in the history
SOCKS proxy support
  • Loading branch information
DoctorMcKay authored Nov 9, 2022
2 parents a00aef2 + 9f888fe commit 0129d6e
Show file tree
Hide file tree
Showing 8 changed files with 56 additions and 19 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,14 @@ Specify a URL here to use an HTTP proxy. For example, `http://user:[email protected]:

Added in 4.0.0.

### socksProxy

Specify a URL here to use a SOCKS proxy. SOCKS4, SOCKS4a, and SOCKS5 are supported.

Example: `socks5://x1234567:[email protected]:1080`

Added in 4.26.0.

### protocol

A value from [`EConnectionProtocol`](https://github.com/DoctorMcKay/node-steam-user/blob/master/resources/EConnectionProtocol.js).
Expand Down
17 changes: 17 additions & 0 deletions components/02-connection.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const ByteBuffer = require('bytebuffer');
const {SocksProxyAgent} = require('socks-proxy-agent');
const StdLib = require('@doctormckay/stdlib');
const SteamCrypto = require('@doctormckay/steam-crypto');

Expand Down Expand Up @@ -48,6 +49,22 @@ class SteamUserConnection extends SteamUserEnums {

this._clearChangelistUpdateTimer();
}

_getProxyAgent() {
if (this.options.socksProxy && this.options.httpProxy) {
throw new Error('Cannot specify both socksProxy and httpProxy options');
}

if (this.options.socksProxy) {
return new SocksProxyAgent(this.options.socksProxy);
}

if (this.options.httpProxy) {
return StdLib.HTTP.getProxyAgent(true, this.options.httpProxy);
}

return undefined;
}
}

// Handlers
Expand Down
4 changes: 1 addition & 3 deletions components/06-webapi.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,7 @@ class SteamUserWebAPI extends SteamUserFileStorage {
options.localAddress = this.options.localAddress;
}

if (this.options.httpProxy) {
options.agent = StdLib.HTTP.getProxyAgent(true, this.options.httpProxy);
}
options.agent = this._getProxyAgent();

let req = HTTPS.request(options, (res) => {
this.emit('debug', `API ${options.method} request to https://${HOSTNAME}${path}: ${res.statusCode}`);
Expand Down
14 changes: 12 additions & 2 deletions components/08-logon.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,13 +259,23 @@ class SteamUserLogon extends SteamUserWeb {
* @private
*/
_doConnection() {
let thisProtocol = this.options.webCompatibilityMode ? EConnectionProtocol.WebSocket : this.options.protocol;
let thisProtocol = this.options.protocol;

if (thisProtocol == EConnectionProtocol.TCP && this.options.webCompatibilityMode) {
this._warn('Forcing protocol to EConnectionProtocol.WebSocket because webCompatibilityMode is enabled');
thisProtocol = EConnectionProtocol.WebSocket;
}

if (thisProtocol == EConnectionProtocol.TCP && this.options.socksProxy) {
this._warn('Forcing protocol to EConnectionProtocol.WebSocket because a socksProxy is specified and SOCKS proxy support is incompatible with TCP');
thisProtocol = EConnectionProtocol.WebSocket;
}

if (thisProtocol == EConnectionProtocol.Auto) {
if (this._cmList.auto_pct_websocket) {
let roll = Math.floor(Math.random() * 100);
thisProtocol = roll <= this._cmList.auto_pct_websocket ? EConnectionProtocol.WebSocket : EConnectionProtocol.TCP;
this.emit('debug', 'Using ' + (thisProtocol == EConnectionProtocol.WebSocket ? 'WebSocket' : 'TCP') + '; we rolled ' + roll + ' and percent to use WS is ' + this._cmList.auto_pct_websocket);
this.emit('debug', `Using ${thisProtocol == EConnectionProtocol.WebSocket ? 'WebSocket' : 'TCP'}; we rolled ${roll} and percent to use WS is ${this._cmList.auto_pct_websocket}`);
} else {
thisProtocol = EConnectionProtocol.TCP;
}
Expand Down
28 changes: 14 additions & 14 deletions components/connection_protocols/websocket.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,12 @@ class WebSocketConnection extends BaseConnection {

let addr = servers[Math.floor(Math.random() * servers.length)];
this._debug(`Randomly chose WebSocket CM ${addr}`);
this.stream = new WS13.WebSocket("wss://" + addr + "/cmsocket/", {
"pingInterval": 30000,
"httpProxy": this.user.options.httpProxy,
"proxyTimeout": this.user.options.proxyTimeout,
"connection": {
"localAddress": this.user.options.localAddress
this.stream = new WS13.WebSocket(`wss://${addr}/cmsocket/`, {
pingInterval: 30000,
proxyTimeout: this.user.options.proxyTimeout,
connection: {
localAddress: this.user.options.localAddress,
agent: this.user._getProxyAgent()
}
});

Expand Down Expand Up @@ -110,7 +110,7 @@ class WebSocketConnection extends BaseConnection {
this._disconnected = true;
this._debug('WebSocket disconnected with error: ' + err.message);

if (err.proxyConnecting) {
if (err.proxyConnecting || err.constructor.name == 'SocksClientError') {
// This error happened while connecting to the proxy
this.user.emit('error', err);
} else {
Expand Down Expand Up @@ -207,9 +207,9 @@ class WebSocketConnection extends BaseConnection {
let options = {
host,
port,
"timeout": 700,
"path": "/cmping/",
"agent": StdLib.HTTP.getProxyAgent(true, this.user.options.httpProxy)
timeout: 700,
path: '/cmping/',
agent: this.user._getProxyAgent()
};

// The timeout option seems to not work
Expand All @@ -219,7 +219,7 @@ class WebSocketConnection extends BaseConnection {
return;
}

this._debug('CM ' + addr + ' timed out', true);
this._debug(`CM ${addr} timed out`, true);
callback(new Error(`CM ${addr} timed out`));
finished = true;
}, 700);
Expand All @@ -238,18 +238,18 @@ class WebSocketConnection extends BaseConnection {

if (res.statusCode != 200) {
// CM is disqualified
this._debug('CM ' + addr + ' disqualified: HTTP error ' + res.statusCode, true);
this._debug(`CM ${addr} disqualified: HTTP error ${res.statusCode}`, true);
callback(new Error(`CM ${addr} disqualified: HTTP error ${res.statusCode}`));
return;
}

let load = parseInt(res.headers['x-steam-cmload'], 10) || 999;
this._debug('CM ' + addr + ' latency ' + latency + ' ms + load ' + load, true);
this._debug(`CM ${addr} latency ${latency} ms + load ${load}`, true);
callback(null, {addr, load, latency});
}).on('error', (err) => {
clearTimeout(timeout);
if (!finished) {
this._debug('CM ' + addr + ' disqualified: ' + err.message, true);
this._debug(`CM ${addr} disqualified: ${err.message}`, true);
callback(new Error(`CM ${addr} disqualified: ${err.message}`)); // if error, this CM is disqualified
}
});
Expand Down
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ class SteamUser extends SteamUserTwoFactor {
_checkOptionTypes() {
// We'll infer types from DefaultOptions, but stuff that's null (for example) needs to be defined explicitly
let types = {
socksProxy: 'string',
httpProxy: 'string',
localAddress: 'string',
localPort: 'number',
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"file-manager": "^2.0.0",
"lzma": "^2.3.2",
"protobufjs": "^6.8.8",
"socks-proxy-agent": "^7.0.0",
"steam-appticket": "^1.0.1",
"steam-totp": "^2.0.1",
"steamid": "^1.1.0",
Expand Down
2 changes: 2 additions & 0 deletions resources/default_options.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const EMachineIDType = require('./EMachineIDType.js');
* @property {string|null} [localAddress=null]
* @property {number|null} [localPort=null]
* @property {string|null} [httpProxy=null]
* @property {string|null} [socksProxy=null]
* @property {EConnectionProtocol} [protocol]
* @property {string} [language='english']
* @property {boolean} [webCompatibilityMode=false]
Expand All @@ -34,6 +35,7 @@ module.exports = {
localAddress: null,
localPort: null,
httpProxy: null,
socksProxy: null,
protocol: EConnectionProtocol.Auto,
language: 'english',
webCompatibilityMode: false,
Expand Down

0 comments on commit 0129d6e

Please sign in to comment.