Skip to content

Commit

Permalink
Properly handle errors from GetCMListForConnect
Browse files Browse the repository at this point in the history
  • Loading branch information
DoctorMcKay committed Oct 31, 2023
1 parent 8bcb1b4 commit d6ad6fb
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 89 deletions.
146 changes: 58 additions & 88 deletions components/06-webapi.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const HTTPS = require('https');
const {HttpClient} = require('@doctormckay/stdlib/http');
const VDF = require('kvparser');
const Zlib = require('zlib');

const SteamUserFileStorage = require('./05-filestorage.js');

Expand All @@ -19,91 +18,63 @@ class SteamUserWebAPI extends SteamUserFileStorage {
* @protected
*/
async _apiRequest(httpMethod, iface, method, version, data, cacheSeconds) {
return new Promise((resolve, reject) => {
data = data || {};
httpMethod = httpMethod.toUpperCase(); // just in case

// Pad the version with zeroes to make it 4 digits long, because Valve
version = version.toString();
while (version.length < 4) {
version = '0' + version;
}

data.format = 'vdf'; // for parity with the Steam client

let query = buildQueryString(data);
let headers = Object.assign(getDefaultHeaders(), this.options.additionalHeaders);
let path = `/${iface}/${method}/v${version}/`;

if (httpMethod == 'POST') {
headers['Content-Type'] = 'application/x-www-form-urlencoded';
headers['Content-Length'] = Buffer.byteLength(query);
} else {
path += '?' + query;
}

let options = {
hostname: HOSTNAME,
path,
method: httpMethod,
headers
};

let cacheKey = `API_${httpMethod}_https://${options.hostname}${options.path}`;
let cacheValue;
if (cacheSeconds && (cacheValue = this._ttlCache.get(cacheKey))) {
this.emit('debug', `[WebAPI] Using cached value for ${cacheKey}`);
return resolve(cacheValue);
}

if (this.options.localAddress) {
options.localAddress = this.options.localAddress;
}

options.agent = this._getProxyAgent();

let req = HTTPS.request(options, (res) => {
this.emit('debug', `API ${options.method} request to https://${HOSTNAME}${path}: ${res.statusCode}`);

if (res.statusCode != 200) {
res.on('data', () => {
}); // discard the response
return reject(new Error('HTTP error ' + res.statusCode));
}

let responseData = '';

let stream = res;
if (res.headers['content-encoding'] && res.headers['content-encoding'].toLowerCase() == 'gzip') {
stream = Zlib.createGunzip();
res.pipe(stream);
}

stream.on('data', (data) => {
responseData += data;
});

stream.on('end', () => {
try {
responseData = VDF.parse(responseData);
} catch (ex) {
return reject(ex);
}

if (cacheSeconds) {
this._ttlCache.add(cacheKey, responseData, 1000 * cacheSeconds);
}

resolve(responseData);
});
});

req.on('error', function(err) {
reject(err);
});

req.end(httpMethod == 'POST' ? query : null);
data = data || {};
httpMethod = httpMethod.toUpperCase(); // just in case

// Pad the version with zeroes to make it 4 digits long, because Valve
version = version.toString();
while (version.length < 4) {
version = '0' + version;
}

data.format = 'vdf'; // for parity with the Steam client

let client = this._httpClient || new HttpClient({
userAgent: USER_AGENT,
httpsAgent: this._getProxyAgent(),
localAddress: this.options.localAddress,
defaultHeaders: Object.assign(getDefaultHeaders(), this.options.additionalHeaders),
gzip: true
});
this._httpClient = client;

let url = `https://${HOSTNAME}/${iface}/${method}/v${version}/`;

let cacheKey = `API_${httpMethod}_${url}`;
let cacheValue;
if (cacheSeconds && (cacheValue = this._ttlCache.get(cacheKey))) {
this.emit('debug', `[WebAPI] Using cached value for ${cacheKey}`);
return cacheValue;
}

let headers = {};
let body = null;
if (httpMethod == 'GET') {
url += `?${buildQueryString(data)}`;
} else {
headers['Content-Type'] = 'application/x-www-form-urlencoded';
body = buildQueryString(data);
}

let res = await client.request({
method: httpMethod,
url,
body
});

this.emit('debug', `API ${httpMethod} request to ${url}: ${res.statusCode}`);

if (res.statusCode != 200) {
throw new Error(`HTTP error ${res.statusCode}`);
}

let responseData = VDF.parse(res.textBody);

if (cacheSeconds) {
this._ttlCache.add(cacheKey, responseData, 1000 * cacheSeconds);
}

return responseData;
}
}

Expand Down Expand Up @@ -132,8 +103,7 @@ function getDefaultHeaders() {
return {
Accept: 'text/html,*/*;q=0.9',
'Accept-Encoding': 'gzip,identity,*;q=0',
'Accept-Charset': 'ISO-8859-1,utf-8,*;q=0.7',
'User-Agent': USER_AGENT
'Accept-Charset': 'ISO-8859-1,utf-8,*;q=0.7'
};
}

Expand Down
23 changes: 22 additions & 1 deletion components/09-logon.js
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,28 @@ class SteamUserLogon extends SteamUserMachineAuth {
break;
}

let cmListResponse = await this._apiRequest('GET', 'ISteamDirectory', 'GetCMListForConnect', 1, getCmListQueryString, 300);
let cmListResponse;
try {
cmListResponse = await this._apiRequest(
'GET',
'ISteamDirectory',
'GetCMListForConnect',
1,
getCmListQueryString,
300
);
} catch (ex) {
this.emit('debug', `GetCMListForConnect error: ${ex.message}`);

if (++this._getCmListAttempts >= 10) {
this.emit('error', ex);
} else {
setTimeout(() => this._doConnection());
}

return;
}

if (!cmListResponse.response || !cmListResponse.response.serverlist || Object.keys(cmListResponse.response.serverlist).length == 0) {
this.emit('error', new Error('No Steam servers available'));
return;
Expand Down
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ class SteamUser extends SteamUserTwoFactor {
this._incomingMessageQueue = [];
this._useMessageQueue = false; // we only use the message queue while we're processing a multi message
this._ttlCache = new StdLib.DataStructures.TTLCache(1000 * 60 * 5); // default 5 minutes
this._getCmListAttempts = 0;

delete this._machineAuthToken;
delete this._shouldAttemptRefreshTokenRenewal;
Expand Down

0 comments on commit d6ad6fb

Please sign in to comment.