Handle start.gg OAuth token endpoint variants and robust errors

This commit is contained in:
Pandipipas
2026-02-16 00:45:59 +01:00
parent 19136e6a93
commit 4e214a573a
+43 -18
View File
@@ -4,8 +4,11 @@ import { getData, type CountryRecord } from 'country-list';
import { nodecg } from './util/nodecg.js';
const STARTGG_ENDPOINT = 'https://api.start.gg/gql/alpha';
const STARTGG_OAUTH_AUTHORIZE_ENDPOINT = 'https://api.start.gg/oauth/authorize';
const STARTGG_OAUTH_TOKEN_ENDPOINT = 'https://api.start.gg/oauth/access_token';
const STARTGG_OAUTH_AUTHORIZE_ENDPOINT = 'https://www.start.gg/api/-/rest/oauth/authorize';
const STARTGG_OAUTH_TOKEN_ENDPOINTS = [
'https://www.start.gg/api/-/rest/oauth/access_token',
'https://api.start.gg/oauth/access_token',
];
const STARTGG_OAUTH_SCOPES = 'user.identity tournament.manager';
const STARTGG_OAUTH_CALLBACK_PATH = '/startgg/callback';
const STARTGG_OAUTH_DEFAULT_PORT = 34920;
@@ -55,6 +58,7 @@ interface OAuthTokenResponse {
access_token?: string;
error?: string;
error_description?: string;
message?: string;
}
const oauthSessions = new Map<string, OAuthSession>();
@@ -163,6 +167,15 @@ const renderCallbackHtml = (title: string, message: string) => `<!doctype html>
</body>
</html>`;
const parseOAuthTokenPayload = async (response: Response): Promise<OAuthTokenResponse> => {
const rawBody = await response.text();
try {
return JSON.parse(rawBody) as OAuthTokenResponse;
} catch {
return { message: rawBody };
}
};
const exchangeOAuthCodeForToken = async (
code: string,
redirectUri: string,
@@ -176,25 +189,37 @@ const exchangeOAuthCodeForToken = async (
redirect_uri: redirectUri,
});
const response = await fetch(STARTGG_OAUTH_TOKEN_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: params.toString(),
});
let lastError = 'Unknown OAuth token exchange error';
const payload = (await response.json()) as OAuthTokenResponse;
if (!response.ok) {
throw new Error(payload.error_description || payload.error || `OAuth token request failed (${response.status})`);
for (const tokenEndpoint of STARTGG_OAUTH_TOKEN_ENDPOINTS) {
const response = await fetch(tokenEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: params.toString(),
});
const payload = await parseOAuthTokenPayload(response);
if (response.ok) {
const token = String(payload.access_token || '').trim();
if (token) {
return token;
}
lastError = payload.error_description || payload.error || payload.message || 'OAuth token response did not include an access token';
continue;
}
lastError = payload.error_description || payload.error || payload.message || `OAuth token request failed (${response.status})`;
if (response.status !== 404) {
break;
}
}
const token = String(payload.access_token || '').trim();
if (!token) {
throw new Error('OAuth token response did not include an access token');
}
return token;
throw new Error(lastError);
};
const ensureOAuthCallbackServer = async (oauthConfig: OAuthConfig) => {