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
+35 -10
View File
@@ -4,8 +4,11 @@ import { getData, type CountryRecord } from 'country-list';
import { nodecg } from './util/nodecg.js'; import { nodecg } from './util/nodecg.js';
const STARTGG_ENDPOINT = 'https://api.start.gg/gql/alpha'; const STARTGG_ENDPOINT = 'https://api.start.gg/gql/alpha';
const STARTGG_OAUTH_AUTHORIZE_ENDPOINT = 'https://api.start.gg/oauth/authorize'; const STARTGG_OAUTH_AUTHORIZE_ENDPOINT = 'https://www.start.gg/api/-/rest/oauth/authorize';
const STARTGG_OAUTH_TOKEN_ENDPOINT = 'https://api.start.gg/oauth/access_token'; 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_SCOPES = 'user.identity tournament.manager';
const STARTGG_OAUTH_CALLBACK_PATH = '/startgg/callback'; const STARTGG_OAUTH_CALLBACK_PATH = '/startgg/callback';
const STARTGG_OAUTH_DEFAULT_PORT = 34920; const STARTGG_OAUTH_DEFAULT_PORT = 34920;
@@ -55,6 +58,7 @@ interface OAuthTokenResponse {
access_token?: string; access_token?: string;
error?: string; error?: string;
error_description?: string; error_description?: string;
message?: string;
} }
const oauthSessions = new Map<string, OAuthSession>(); const oauthSessions = new Map<string, OAuthSession>();
@@ -163,6 +167,15 @@ const renderCallbackHtml = (title: string, message: string) => `<!doctype html>
</body> </body>
</html>`; </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 ( const exchangeOAuthCodeForToken = async (
code: string, code: string,
redirectUri: string, redirectUri: string,
@@ -176,7 +189,10 @@ const exchangeOAuthCodeForToken = async (
redirect_uri: redirectUri, redirect_uri: redirectUri,
}); });
const response = await fetch(STARTGG_OAUTH_TOKEN_ENDPOINT, { let lastError = 'Unknown OAuth token exchange error';
for (const tokenEndpoint of STARTGG_OAUTH_TOKEN_ENDPOINTS) {
const response = await fetch(tokenEndpoint, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
@@ -184,17 +200,26 @@ const exchangeOAuthCodeForToken = async (
body: params.toString(), body: params.toString(),
}); });
const payload = (await response.json()) as OAuthTokenResponse; const payload = await parseOAuthTokenPayload(response);
if (!response.ok) {
throw new Error(payload.error_description || payload.error || `OAuth token request failed (${response.status})`);
}
if (response.ok) {
const token = String(payload.access_token || '').trim(); const token = String(payload.access_token || '').trim();
if (!token) { if (token) {
throw new Error('OAuth token response did not include an access token'); return 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;
}
}
throw new Error(lastError);
}; };
const ensureOAuthCallbackServer = async (oauthConfig: OAuthConfig) => { const ensureOAuthCallbackServer = async (oauthConfig: OAuthConfig) => {