diff --git a/src/extension/startgg.ts b/src/extension/startgg.ts index 9242308..abc54bc 100644 --- a/src/extension/startgg.ts +++ b/src/extension/startgg.ts @@ -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(); @@ -163,6 +167,15 @@ const renderCallbackHtml = (title: string, message: string) => ` `; +const parseOAuthTokenPayload = async (response: Response): Promise => { + 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) => {