UNPKG

steam-user

Version:

Steam client for Individual and AnonUser Steam account types

215 lines (181 loc) 5.39 kB
const HTTPS = require('https'); const WS13 = require('websocket13'); const BaseConnection = require('./base.js'); /** * @typedef CmServer * @property {string} endpoint * @property {string} legacy_endpoint * @property {string} type * @property {string} dc * @property {string} realm * @property {string} load * @property {string} wtd_load */ class WebSocketConnection extends BaseConnection { /** * @param {SteamUser} user * @param {CmServer} chosenServer */ constructor(user, chosenServer) { super(user); this.connectionType = 'WS'; let addr = chosenServer.endpoint; this._debug(`Connecting to WebSocket CM ${addr}`); 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() } }); // Set up the connection timeout this.stream.setTimeout(this.user._connectTimeout); this.stream.on('debug', msg => this._debug(msg, true)); this.stream.on('message', this._readMessage.bind(this)); this.stream.on('disconnected', (code, reason, initiatedByUs) => { if (this._disconnected) { return; } this._disconnected = true; this._debug(`WebSocket closed by ${initiatedByUs ? 'us' : 'remote'} with code ${code} and reason "${reason}"`); this.user._handleConnectionClose(this); }); this.stream.on('error', (err) => { if (this._disconnected) { return; } this._disconnected = true; this._debug('WebSocket disconnected with error: ' + err.message); if (err.proxyConnecting || err.constructor.name == 'SocksClientError') { // This error happened while connecting to the proxy this.user._cleanupClosedConnection(); this.user.emit('error', err); } else { this.user._handleConnectionClose(this); } }); this.stream.on('connected', () => { this._debug('WebSocket connection success; now logging in'); this.stream.setTimeout(0); // Disable timeout this.user._sendLogOn(); }); this.stream.on('timeout', () => { if (this._disconnected) { return; } this._disconnected = true; this._debug('WS connection timed out'); this.user._connectTimeout = Math.min(this.user._connectTimeout * 2, 10000); // 10 seconds max this.stream.disconnect(); this.user._doConnection(); }); } /** * End the connection * @param {boolean} [andIgnore=false] - Pass true to also ignore all further events from this connection */ end(andIgnore) { if (this.stream && [WS13.State.Connected, WS13.State.Connecting].indexOf(this.stream.state) != -1) { this._debug('Ending connection' + (andIgnore ? ' and removing all listeners' : '')); if (andIgnore) { this.stream.removeAllListeners(); this.stream.on('error', () => { }); this.stream.disconnect(); this._disconnected = true; return; } this.stream.disconnect(); } else { this._debug('We wanted to end connection, but it\'s not connected or connecting'); } } /** * Send data over the connection. * @param {Buffer} data */ send(data) { if (this._disconnected) { return; } try { this.stream.send(data); } catch (ex) { this._debug('WebSocket send error: ' + ex.message); try { this._disconnected = true; this.stream.disconnect(WS13.StatusCode.AbnormalTermination); this.user._handleConnectionClose(this); } catch (ex) { this._debug('WebSocket teardown error: ' + ex.message); } } } /** * Read a message from the WebSocket * @param {int} type * @param {string|Buffer} msg * @private */ _readMessage(type, msg) { if (type != WS13.FrameType.Data.Binary) { this._debug('Got frame with wrong data type: ' + type); return; } this.user._handleNetMessage(msg, this); } /** * Ping a CM and return its load and latency * @param addr * @param callback * @private */ _pingCM(addr, callback) { let host = addr.split(':')[0]; let port = parseInt(addr.split(':')[1] || '443', 10); let options = { host, port, timeout: 700, path: '/cmping/', agent: this.user._getProxyAgent() }; // The timeout option seems to not work let finished = false; let timeout = setTimeout(() => { if (finished) { return; } this._debug(`CM ${addr} timed out`, true); callback(new Error(`CM ${addr} timed out`)); finished = true; }, 700); let start = Date.now(); HTTPS.get(options, (res) => { clearTimeout(timeout); if (finished) { return; } let latency = Date.now() - start; res.on('data', () => { }); // there is no body, so just throw it away if (res.statusCode != 200) { // CM is disqualified 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); callback(null, {addr, load, latency}); }).on('error', (err) => { clearTimeout(timeout); if (!finished) { this._debug(`CM ${addr} disqualified: ${err.message}`, true); callback(new Error(`CM ${addr} disqualified: ${err.message}`)); // if error, this CM is disqualified } }); } } module.exports = WebSocketConnection;