From 8775f8b1fe11db98e1807107f8ec644da50d0f35 Mon Sep 17 00:00:00 2001 From: FragginWagon Date: Tue, 3 Feb 2026 12:50:25 -0500 Subject: [PATCH] Refactor code for improved readability and consistency - Updated CSRF middleware to enhance cookie value decoding. - Reformatted OAuth proxy token store initialization for better clarity. - Adjusted Challonge proxy router for consistent line breaks and readability. - Enhanced OAuth router error handling and response formatting. - Improved session router for better readability and consistency in fetching provider records. - Refactored OAuth token store to improve key derivation logging. - Cleaned up cookie options utility for better readability. - Enhanced Challonge client credentials composable for consistent API calls. - Streamlined OAuth composable for improved logging. - Refactored main.js for better readability in session initialization. - Improved Challonge v2.1 service error handling for better clarity. - Cleaned up API client utility for improved readability. - Enhanced ApiKeyManager.vue for better text formatting. - Refactored ChallongeTest.vue for improved readability in composable usage. --- .../server/data/oauth-tokens.json | 6 +- .../pokedex.online/server/middleware/csrf.js | 20 +++-- .../pokedex.online/server/oauth-proxy.js | 4 +- .../pokedex.online/server/routes/challonge.js | 35 ++++++--- .../pokedex.online/server/routes/oauth.js | 77 ++++++++++++++----- .../pokedex.online/server/routes/session.js | 31 ++++++-- .../server/services/oauth-token-store.js | 9 ++- .../server/utils/cookie-options.js | 6 +- .../useChallongeClientCredentials.js | 28 +++++-- .../src/composables/useOAuth.js | 4 +- code/websites/pokedex.online/src/main.js | 16 ++-- .../src/services/challonge-v2.1.service.js | 7 +- .../src/utilities/api-client.js | 8 +- .../src/views/ApiKeyManager.vue | 3 +- .../src/views/ChallongeTest.vue | 4 +- 15 files changed, 182 insertions(+), 76 deletions(-) diff --git a/code/websites/pokedex.online/server/data/oauth-tokens.json b/code/websites/pokedex.online/server/data/oauth-tokens.json index e2e199b..34fd26e 100644 --- a/code/websites/pokedex.online/server/data/oauth-tokens.json +++ b/code/websites/pokedex.online/server/data/oauth-tokens.json @@ -1,7 +1,7 @@ { "version": 1, "alg": "aes-256-gcm", - "iv": "kpWH+zywSfvDciE2", - "tag": "iKy403D2R0VWgFlepKYwiA==", - "ciphertext": "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" + "iv": "5Cz4Hk2Pwvay6Y30", + "tag": "2c9avPK0pPiSoEZVVn7bGQ==", + "ciphertext": "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" } \ No newline at end of file diff --git a/code/websites/pokedex.online/server/middleware/csrf.js b/code/websites/pokedex.online/server/middleware/csrf.js index 2fd07d4..a25eee3 100644 --- a/code/websites/pokedex.online/server/middleware/csrf.js +++ b/code/websites/pokedex.online/server/middleware/csrf.js @@ -47,16 +47,22 @@ export function csrfMiddleware(options = {}) { // current '/' path). cookie-parser will pick one value, but the browser may // send both. Accept if the header matches ANY provided cookie value. const rawHeader = req.headers?.cookie || ''; - const rawValues = getCookieValuesFromHeader(rawHeader, cookieName).map(v => { - try { - return decodeURIComponent(v); - } catch { - return v; + const rawValues = getCookieValuesFromHeader(rawHeader, cookieName).map( + v => { + try { + return decodeURIComponent(v); + } catch { + return v; + } } - }); + ); const anyMatch = csrfHeader && rawValues.includes(csrfHeader); - if (!csrfHeader || (!csrfCookie && !anyMatch) || (csrfCookie !== csrfHeader && !anyMatch)) { + if ( + !csrfHeader || + (!csrfCookie && !anyMatch) || + (csrfCookie !== csrfHeader && !anyMatch) + ) { return res.status(403).json({ error: 'CSRF validation failed', code: 'CSRF_FAILED' diff --git a/code/websites/pokedex.online/server/oauth-proxy.js b/code/websites/pokedex.online/server/oauth-proxy.js index 7f7e645..ffb8061 100644 --- a/code/websites/pokedex.online/server/oauth-proxy.js +++ b/code/websites/pokedex.online/server/oauth-proxy.js @@ -60,7 +60,9 @@ app.use( ); // Encrypted per-session provider token store -const tokenStore = createOAuthTokenStore({ sessionSecret: config.session.secret }); +const tokenStore = createOAuthTokenStore({ + sessionSecret: config.session.secret +}); // Mount API routes (nginx strips /api/ prefix before forwarding) app.use('/gamemaster', gamemasterRouter); diff --git a/code/websites/pokedex.online/server/routes/challonge.js b/code/websites/pokedex.online/server/routes/challonge.js index ca92f60..a932469 100644 --- a/code/websites/pokedex.online/server/routes/challonge.js +++ b/code/websites/pokedex.online/server/routes/challonge.js @@ -69,7 +69,8 @@ export function createChallongeProxyRouter({ config, tokenStore }) { return res.status(500).json({ error: 'SID middleware not configured' }); } - const challongeRecord = (await tokenStore.getProviderRecord(req.sid, 'challonge')) || {}; + const challongeRecord = + (await tokenStore.getProviderRecord(req.sid, 'challonge')) || {}; // Determine upstream path relative to this router mount // This router is mounted at /challonge, so req.url starts with /v1/... or /v2.1/... @@ -110,7 +111,8 @@ export function createChallongeProxyRouter({ config, tokenStore }) { const app = challongeRecord.client_credentials; if (!app?.client_id || !app?.client_secret) { return res.status(401).json({ - error: 'Challonge client credentials not configured for this session', + error: + 'Challonge client credentials not configured for this session', code: 'CHALLONGE_CLIENT_CREDENTIALS_REQUIRED' }); } @@ -130,7 +132,11 @@ export function createChallongeProxyRouter({ config, tokenStore }) { expires_at: computeExpiresAt(exchanged.expires_in) }; - await tokenStore.setProviderRecord(req.sid, 'challonge', challongeRecord); + await tokenStore.setProviderRecord( + req.sid, + 'challonge', + challongeRecord + ); accessToken = challongeRecord.client_credentials.access_token; } @@ -158,7 +164,11 @@ export function createChallongeProxyRouter({ config, tokenStore }) { } let accessToken = user.access_token; - if (isExpired(user.expires_at) && user.refresh_token && config.challonge.configured) { + if ( + isExpired(user.expires_at) && + user.refresh_token && + config.challonge.configured + ) { try { const refreshed = await refreshUserOAuth({ config, @@ -172,7 +182,11 @@ export function createChallongeProxyRouter({ config, tokenStore }) { scope: refreshed.scope, expires_at: computeExpiresAt(refreshed.expires_in) }; - await tokenStore.setProviderRecord(req.sid, 'challonge', challongeRecord); + await tokenStore.setProviderRecord( + req.sid, + 'challonge', + challongeRecord + ); accessToken = challongeRecord.user_oauth.access_token; } catch (err) { logger.warn('Failed to refresh Challonge user OAuth token', { @@ -213,10 +227,13 @@ export function createChallongeProxyRouter({ config, tokenStore }) { ) { const apiKey = challongeRecord.api_key?.token; if (apiKey) { - logger.warn('Challonge v2.1 user OAuth unauthorized; retrying with API key', { - status: upstreamResponse.status, - path: upstreamPath - }); + logger.warn( + 'Challonge v2.1 user OAuth unauthorized; retrying with API key', + { + status: upstreamResponse.status, + path: upstreamPath + } + ); const retryHeaders = { ...headers }; delete retryHeaders.authorization; diff --git a/code/websites/pokedex.online/server/routes/oauth.js b/code/websites/pokedex.online/server/routes/oauth.js index 28461ec..1b3d05b 100644 --- a/code/websites/pokedex.online/server/routes/oauth.js +++ b/code/websites/pokedex.online/server/routes/oauth.js @@ -119,13 +119,18 @@ export function createOAuthRouter({ config, tokenStore }) { } if (!code) { - return res.status(400).json({ error: 'Authorization code is required', code: 'MISSING_CODE' }); + return res.status(400).json({ + error: 'Authorization code is required', + code: 'MISSING_CODE' + }); } if (provider === 'discord') { const clientId = process.env.VITE_DISCORD_CLIENT_ID; const clientSecret = process.env.DISCORD_CLIENT_SECRET; - const redirectUri = process.env.DISCORD_REDIRECT_URI || process.env.VITE_DISCORD_REDIRECT_URI; + const redirectUri = + process.env.DISCORD_REDIRECT_URI || + process.env.VITE_DISCORD_REDIRECT_URI; if (!clientId || !clientSecret || !redirectUri) { return res.status(503).json({ @@ -155,7 +160,10 @@ export function createOAuthRouter({ config, tokenStore }) { } if (!response.ok) { - logger.warn('Discord token exchange failed', { status: response.status, payload }); + logger.warn('Discord token exchange failed', { + status: response.status, + payload + }); return res.status(response.status).json({ error: 'Discord token exchange failed', code: 'DISCORD_TOKEN_EXCHANGE_FAILED', @@ -197,7 +205,10 @@ export function createOAuthRouter({ config, tokenStore }) { const payload = await response.json().catch(() => ({})); if (!response.ok) { - logger.warn('Challonge token exchange failed', { status: response.status, payload }); + logger.warn('Challonge token exchange failed', { + status: response.status, + payload + }); return res.status(response.status).json({ error: 'Challonge token exchange failed', code: 'CHALLONGE_TOKEN_EXCHANGE_FAILED', @@ -205,7 +216,8 @@ export function createOAuthRouter({ config, tokenStore }) { }); } - const existing = (await tokenStore.getProviderRecord(req.sid, 'challonge')) || {}; + const existing = + (await tokenStore.getProviderRecord(req.sid, 'challonge')) || {}; const user_oauth = { access_token: payload.access_token, refresh_token: payload.refresh_token, @@ -223,7 +235,10 @@ export function createOAuthRouter({ config, tokenStore }) { return res.json(redactProviderRecord('challonge', record)); } - return res.status(400).json({ error: `Unknown provider: ${provider}`, code: 'UNKNOWN_PROVIDER' }); + return res.status(400).json({ + error: `Unknown provider: ${provider}`, + code: 'UNKNOWN_PROVIDER' + }); }); // Store Challonge API key (v1 compatibility) per session @@ -233,7 +248,9 @@ export function createOAuthRouter({ config, tokenStore }) { return res.status(500).json({ error: 'SID middleware not configured' }); } if (!apiKey) { - return res.status(400).json({ error: 'apiKey is required', code: 'MISSING_API_KEY' }); + return res + .status(400) + .json({ error: 'apiKey is required', code: 'MISSING_API_KEY' }); } apiKey = String(apiKey).trim(); @@ -241,10 +258,13 @@ export function createOAuthRouter({ config, tokenStore }) { apiKey = apiKey.slice('bearer '.length).trim(); } if (!apiKey) { - return res.status(400).json({ error: 'apiKey is required', code: 'MISSING_API_KEY' }); + return res + .status(400) + .json({ error: 'apiKey is required', code: 'MISSING_API_KEY' }); } - const existing = (await tokenStore.getProviderRecord(req.sid, 'challonge')) || {}; + const existing = + (await tokenStore.getProviderRecord(req.sid, 'challonge')) || {}; const record = { ...existing, api_key: { @@ -260,7 +280,8 @@ export function createOAuthRouter({ config, tokenStore }) { return res.status(500).json({ error: 'SID middleware not configured' }); } - const existing = (await tokenStore.getProviderRecord(req.sid, 'challonge')) || {}; + const existing = + (await tokenStore.getProviderRecord(req.sid, 'challonge')) || {}; const record = { ...existing }; if (record.api_key) delete record.api_key; await tokenStore.setProviderRecord(req.sid, 'challonge', record); @@ -278,7 +299,8 @@ export function createOAuthRouter({ config, tokenStore }) { if (typeof clientSecret === 'string') clientSecret = clientSecret.trim(); if (typeof scope === 'string') scope = scope.trim(); - const existing = (await tokenStore.getProviderRecord(req.sid, 'challonge')) || {}; + const existing = + (await tokenStore.getProviderRecord(req.sid, 'challonge')) || {}; const prev = existing.client_credentials || {}; const effectiveClientId = clientId || prev.client_id; const effectiveClientSecret = clientSecret || prev.client_secret; @@ -286,7 +308,8 @@ export function createOAuthRouter({ config, tokenStore }) { if (!effectiveClientId || !effectiveClientSecret) { return res.status(400).json({ - error: 'clientId and clientSecret are required (or must already be stored for this session)', + error: + 'clientId and clientSecret are required (or must already be stored for this session)', code: 'MISSING_CLIENT_CREDENTIALS' }); } @@ -304,7 +327,10 @@ export function createOAuthRouter({ config, tokenStore }) { const payload = await response.json().catch(() => ({})); if (!response.ok) { - logger.warn('Challonge client_credentials token exchange failed', { status: response.status, payload }); + logger.warn('Challonge client_credentials token exchange failed', { + status: response.status, + payload + }); return res.status(response.status).json({ error: 'Challonge client credentials exchange failed', code: 'CHALLONGE_CLIENT_CREDENTIALS_FAILED', @@ -333,7 +359,8 @@ export function createOAuthRouter({ config, tokenStore }) { return res.status(500).json({ error: 'SID middleware not configured' }); } - const existing = (await tokenStore.getProviderRecord(req.sid, 'challonge')) || {}; + const existing = + (await tokenStore.getProviderRecord(req.sid, 'challonge')) || {}; const record = { ...existing }; if (record.client_credentials) delete record.client_credentials; await tokenStore.setProviderRecord(req.sid, 'challonge', record); @@ -346,7 +373,8 @@ export function createOAuthRouter({ config, tokenStore }) { return res.status(500).json({ error: 'SID middleware not configured' }); } - const existing = (await tokenStore.getProviderRecord(req.sid, 'challonge')) || {}; + const existing = + (await tokenStore.getProviderRecord(req.sid, 'challonge')) || {}; const creds = existing.client_credentials; if (!creds) { return res.json(redactProviderRecord('challonge', existing)); @@ -373,19 +401,27 @@ export function createOAuthRouter({ config, tokenStore }) { const record = await tokenStore.getProviderRecord(req.sid, provider); if (!record) { - return res.status(400).json({ error: 'No stored tokens', code: 'NO_TOKENS' }); + return res + .status(400) + .json({ error: 'No stored tokens', code: 'NO_TOKENS' }); } if (provider === 'discord') { const refreshToken = record.refresh_token; if (!refreshToken) { - return res.status(400).json({ error: 'No refresh token available', code: 'NO_REFRESH_TOKEN' }); + return res.status(400).json({ + error: 'No refresh token available', + code: 'NO_REFRESH_TOKEN' + }); } const clientId = process.env.VITE_DISCORD_CLIENT_ID; const clientSecret = process.env.DISCORD_CLIENT_SECRET; if (!clientId || !clientSecret) { - return res.status(503).json({ error: 'Discord OAuth not configured', code: 'DISCORD_NOT_CONFIGURED' }); + return res.status(503).json({ + error: 'Discord OAuth not configured', + code: 'DISCORD_NOT_CONFIGURED' + }); } const response = await fetch('https://discord.com/api/oauth2/token', { @@ -473,7 +509,10 @@ export function createOAuthRouter({ config, tokenStore }) { return res.json(redactProviderRecord('challonge', updatedRecord)); } - return res.status(400).json({ error: `Unknown provider: ${provider}`, code: 'UNKNOWN_PROVIDER' }); + return res.status(400).json({ + error: `Unknown provider: ${provider}`, + code: 'UNKNOWN_PROVIDER' + }); }); return router; diff --git a/code/websites/pokedex.online/server/routes/session.js b/code/websites/pokedex.online/server/routes/session.js index e02244b..89910be 100644 --- a/code/websites/pokedex.online/server/routes/session.js +++ b/code/websites/pokedex.online/server/routes/session.js @@ -1,6 +1,10 @@ import express from 'express'; import fetch from 'node-fetch'; -import { COOKIE_NAMES, getCsrfCookieOptions, generateToken } from '../utils/cookie-options.js'; +import { + COOKIE_NAMES, + getCsrfCookieOptions, + generateToken +} from '../utils/cookie-options.js'; export function createSessionRouter({ config, tokenStore }) { const router = express.Router(); @@ -75,7 +79,8 @@ export function createSessionRouter({ config, tokenStore }) { return res.status(500).json({ error: 'SID middleware not configured' }); } - const challonge = (await tokenStore.getProviderRecord(req.sid, 'challonge')) || {}; + const challonge = + (await tokenStore.getProviderRecord(req.sid, 'challonge')) || {}; return res.json({ sid: req.sid, @@ -83,9 +88,13 @@ export function createSessionRouter({ config, tokenStore }) { hasApiKey: !!challonge.api_key?.token, hasUserOAuth: !!challonge.user_oauth?.access_token, userOAuthExpiresAt: challonge.user_oauth?.expires_at || null, - hasClientCredentials: !!(challonge.client_credentials?.client_id && challonge.client_credentials?.client_secret), + hasClientCredentials: !!( + challonge.client_credentials?.client_id && + challonge.client_credentials?.client_secret + ), hasClientCredentialsToken: !!challonge.client_credentials?.access_token, - clientCredentialsExpiresAt: challonge.client_credentials?.expires_at || null + clientCredentialsExpiresAt: + challonge.client_credentials?.expires_at || null } }); }); @@ -96,17 +105,23 @@ export function createSessionRouter({ config, tokenStore }) { return res.status(500).json({ error: 'SID middleware not configured' }); } - const challonge = (await tokenStore.getProviderRecord(req.sid, 'challonge')) || {}; + const challonge = + (await tokenStore.getProviderRecord(req.sid, 'challonge')) || {}; - const base = 'https://api.challonge.com/v2.1/tournaments.json?page=1&per_page=1&state=pending'; + const base = + 'https://api.challonge.com/v2.1/tournaments.json?page=1&per_page=1&state=pending'; const results = { sid: req.sid, endpoints: { userTournamentsSample: base, - appTournamentsSample: 'https://api.challonge.com/v2.1/application/tournaments.json?page=1&per_page=1&state=pending' + appTournamentsSample: + 'https://api.challonge.com/v2.1/application/tournaments.json?page=1&per_page=1&state=pending' }, methods: { - user_oauth: { present: !!challonge.user_oauth?.access_token, probe: null }, + user_oauth: { + present: !!challonge.user_oauth?.access_token, + probe: null + }, api_key: { present: !!challonge.api_key?.token, probe: null }, client_credentials: { present: !!challonge.client_credentials?.access_token, diff --git a/code/websites/pokedex.online/server/services/oauth-token-store.js b/code/websites/pokedex.online/server/services/oauth-token-store.js index af63a16..2099912 100644 --- a/code/websites/pokedex.online/server/services/oauth-token-store.js +++ b/code/websites/pokedex.online/server/services/oauth-token-store.js @@ -27,7 +27,9 @@ function getEncryptionKey(sessionSecret) { } // Dev fallback: derive from session secret (still better than plaintext) - logger.warn('OAUTH_TOKEN_ENC_KEY not set; deriving key from SESSION_SECRET (dev only).'); + logger.warn( + 'OAUTH_TOKEN_ENC_KEY not set; deriving key from SESSION_SECRET (dev only).' + ); return crypto.createHash('sha256').update(sessionSecret).digest(); } @@ -149,7 +151,10 @@ export function createOAuthTokenStore({ sessionSecret }) { const ts = now(); if (existing) { existing.lastSeenAt = ts; - existing.expiresAt = Math.min(existing.createdAt + SEVEN_DAYS_MS, ts + ONE_DAY_MS); + existing.expiresAt = Math.min( + existing.createdAt + SEVEN_DAYS_MS, + ts + ONE_DAY_MS + ); return existing; } diff --git a/code/websites/pokedex.online/server/utils/cookie-options.js b/code/websites/pokedex.online/server/utils/cookie-options.js index bddf54e..28e6f59 100644 --- a/code/websites/pokedex.online/server/utils/cookie-options.js +++ b/code/websites/pokedex.online/server/utils/cookie-options.js @@ -9,10 +9,12 @@ export const COOKIE_NAMES = { }; export function getCookieSecurityConfig(config) { - const deploymentTarget = config?.deploymentTarget || process.env.DEPLOYMENT_TARGET; + const deploymentTarget = + config?.deploymentTarget || process.env.DEPLOYMENT_TARGET; const nodeEnv = config?.nodeEnv || process.env.NODE_ENV; - const isProdTarget = deploymentTarget === 'production' || nodeEnv === 'production'; + const isProdTarget = + deploymentTarget === 'production' || nodeEnv === 'production'; return { secure: isProdTarget, diff --git a/code/websites/pokedex.online/src/composables/useChallongeClientCredentials.js b/code/websites/pokedex.online/src/composables/useChallongeClientCredentials.js index b9086ea..87f46e9 100644 --- a/code/websites/pokedex.online/src/composables/useChallongeClientCredentials.js +++ b/code/websites/pokedex.online/src/composables/useChallongeClientCredentials.js @@ -51,11 +51,14 @@ export function useChallongeClientCredentials() { loading.value = true; error.value = ''; try { - status.value = await apiClient.post('/oauth/challonge/client-credentials', { - clientId, - clientSecret, - scope - }); + status.value = await apiClient.post( + '/oauth/challonge/client-credentials', + { + clientId, + clientSecret, + scope + } + ); return true; } catch (err) { error.value = err.message || 'Failed to save credentials'; @@ -69,7 +72,10 @@ export function useChallongeClientCredentials() { loading.value = true; error.value = ''; try { - status.value = await apiClient.post('/oauth/challonge/client-credentials', { scope }); + status.value = await apiClient.post( + '/oauth/challonge/client-credentials', + { scope } + ); return true; } finally { loading.value = false; @@ -84,7 +90,10 @@ export function useChallongeClientCredentials() { loading.value = true; error.value = ''; try { - status.value = await apiClient.post('/oauth/challonge/client-credentials/logout', {}); + status.value = await apiClient.post( + '/oauth/challonge/client-credentials/logout', + {} + ); return true; } catch (err) { error.value = err.message || 'Logout failed'; @@ -98,7 +107,10 @@ export function useChallongeClientCredentials() { loading.value = true; error.value = ''; try { - status.value = await apiClient.post('/oauth/challonge/client-credentials/clear', {}); + status.value = await apiClient.post( + '/oauth/challonge/client-credentials/clear', + {} + ); return true; } catch (err) { error.value = err.message || 'Failed to clear credentials'; diff --git a/code/websites/pokedex.online/src/composables/useOAuth.js b/code/websites/pokedex.online/src/composables/useOAuth.js index 40894c6..a4bbbb7 100644 --- a/code/websites/pokedex.online/src/composables/useOAuth.js +++ b/code/websites/pokedex.online/src/composables/useOAuth.js @@ -263,9 +263,7 @@ export function useOAuth(provider = 'challonge') { sessionStorage.removeItem('oauth_provider'); sessionStorage.removeItem('oauth_return_to'); - console.log( - `✅ ${provider} OAuth authentication successful` - ); + console.log(`✅ ${provider} OAuth authentication successful`); return data; } catch (err) { const backendCode = err?.data?.code; diff --git a/code/websites/pokedex.online/src/main.js b/code/websites/pokedex.online/src/main.js index 2aa86a5..2752c97 100644 --- a/code/websites/pokedex.online/src/main.js +++ b/code/websites/pokedex.online/src/main.js @@ -22,12 +22,12 @@ app.directive('highlight', vHighlight); // Prime session + CSRF cookies (server uses SID cookies and double-submit CSRF) (async () => { - try { - await fetch('/api/session/init', { credentials: 'include' }); - await fetch('/api/session/csrf', { credentials: 'include' }); - } catch (err) { - console.warn('Failed to initialize session/CSRF cookies:', err); - } finally { - app.mount('#app'); - } + try { + await fetch('/api/session/init', { credentials: 'include' }); + await fetch('/api/session/csrf', { credentials: 'include' }); + } catch (err) { + console.warn('Failed to initialize session/CSRF cookies:', err); + } finally { + app.mount('#app'); + } })(); diff --git a/code/websites/pokedex.online/src/services/challonge-v2.1.service.js b/code/websites/pokedex.online/src/services/challonge-v2.1.service.js index 6e09460..f6d7242 100644 --- a/code/websites/pokedex.online/src/services/challonge-v2.1.service.js +++ b/code/websites/pokedex.online/src/services/challonge-v2.1.service.js @@ -166,7 +166,9 @@ export function createChallongeV2Client(auth, options = {}) { if (debug) { console.error('[Challonge v2.1 JSON Parse Error]', parseError); } - const error = new Error(`HTTP ${response.status}: Failed to parse response`); + const error = new Error( + `HTTP ${response.status}: Failed to parse response` + ); error.status = response.status; throw error; } @@ -207,7 +209,8 @@ export function createChallongeV2Client(auth, options = {}) { const fallbackMessage = response.statusText || 'Request failed'; const finalMessage = - typeof messageFromBody === 'string' && messageFromBody.trim().length === 0 + typeof messageFromBody === 'string' && + messageFromBody.trim().length === 0 ? fallbackMessage : messageFromBody || fallbackMessage; diff --git a/code/websites/pokedex.online/src/utilities/api-client.js b/code/websites/pokedex.online/src/utilities/api-client.js index d7cb462..bbbe5ae 100644 --- a/code/websites/pokedex.online/src/utilities/api-client.js +++ b/code/websites/pokedex.online/src/utilities/api-client.js @@ -90,7 +90,8 @@ export function createApiClient(config = {}) { }; // Default JSON content type unless caller overrides / uses FormData - const hasBody = fetchOptions.body !== undefined && fetchOptions.body !== null; + const hasBody = + fetchOptions.body !== undefined && fetchOptions.body !== null; const isFormData = typeof FormData !== 'undefined' && fetchOptions.body instanceof FormData; if (hasBody && !isFormData && !headers['Content-Type']) { @@ -160,7 +161,10 @@ export function createApiClient(config = {}) { } // Some endpoints may return 204/304 (no body). Avoid JSON parse errors. - if (processedResponse.status === 204 || processedResponse.status === 304) { + if ( + processedResponse.status === 204 || + processedResponse.status === 304 + ) { return null; } diff --git a/code/websites/pokedex.online/src/views/ApiKeyManager.vue b/code/websites/pokedex.online/src/views/ApiKeyManager.vue index 6743751..20ef987 100644 --- a/code/websites/pokedex.online/src/views/ApiKeyManager.vue +++ b/code/websites/pokedex.online/src/views/ApiKeyManager.vue @@ -94,7 +94,8 @@