From 568224f2d981d85e598519d657acd98153678969 Mon Sep 17 00:00:00 2001 From: FragginWagon Date: Thu, 29 Jan 2026 05:09:33 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20composable=20for=20interactin?= =?UTF-8?q?g=20with=20Challonge=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/composables/useChallongeClient.js | 197 ++++++++++++++++++ .../composables/useChallongeClient.test.js | 0 2 files changed, 197 insertions(+) create mode 100644 code/websites/pokedex.online/src/composables/useChallongeClient.js create mode 100644 code/websites/pokedex.online/tests/unit/composables/useChallongeClient.test.js diff --git a/code/websites/pokedex.online/src/composables/useChallongeClient.js b/code/websites/pokedex.online/src/composables/useChallongeClient.js new file mode 100644 index 0000000..c19ace1 --- /dev/null +++ b/code/websites/pokedex.online/src/composables/useChallongeClient.js @@ -0,0 +1,197 @@ +/** + * Challonge Client Composable + * + * Manages Challonge API client initialization with support for: + * - API v1 and v2.1 + * - Multiple authentication methods (API Key, OAuth, Client Credentials) + * - Smart auth selection based on tournament scope + * - Reactive client updates + * + * @example + * ```js + * const { + * client, + * apiVersion, + * tournamentScope, + * switchVersion, + * setScope + * } = useChallongeClient(); + * + * // Use client for API calls + * await client.value.tournaments.list(); + * ``` + */ + +import { ref, computed } from 'vue'; +import { useChallongeApiKey } from './useChallongeApiKey.js'; +import { useChallongeOAuth } from './useChallongeOAuth.js'; +import { useChallongeClientCredentials } from './useChallongeClientCredentials.js'; +import { + createChallongeV1Client, + createChallongeV2Client, + AuthType, + ScopeType +} from '../services/challonge.service.js'; + +export function useChallongeClient(options = {}) { + const { debug = false } = options; + + // Get authentication sources + const { getApiKey } = useChallongeApiKey(); + const { + isAuthenticated: isOAuthAuthenticated, + accessToken: oauthToken + } = useChallongeOAuth(); + const { + isAuthenticated: isClientCredsAuthenticated, + accessToken: clientCredsToken + } = useChallongeClientCredentials(); + + // Configuration state + const apiVersion = ref('v2.1'); // 'v1' or 'v2.1' + const tournamentScope = ref(ScopeType.USER); + const debugMode = ref(debug); + + // Reactive API key + const apiKey = computed(() => getApiKey()); + + // Masked API key for display + const maskedApiKey = computed(() => { + if (!apiKey.value) return ''; + return apiKey.value.slice(0, 4) + '•••••••' + apiKey.value.slice(-4); + }); + + /** + * Create API client reactively based on version, auth method, and scope + */ + const client = computed(() => { + if (apiVersion.value === 'v1') { + // v1 only supports API key + if (!apiKey.value) return null; + return createChallongeV1Client(apiKey.value); + } else { + // v2.1 supports OAuth, client credentials, and API key + // Smart priority based on scope selection: + // - APPLICATION scope: prefer client credentials (designed for this) + // - USER scope: prefer OAuth user tokens or API key + + if (tournamentScope.value === ScopeType.APPLICATION) { + // APPLICATION scope - prefer client credentials + if (isClientCredsAuthenticated.value && clientCredsToken.value) { + if (debugMode.value) { + console.log('🔐 Using Client Credentials token for APPLICATION scope'); + } + return createChallongeV2Client( + { token: clientCredsToken.value, type: AuthType.OAUTH }, + { debug: debugMode.value } + ); + } else if (isOAuthAuthenticated.value && oauthToken.value) { + if (debugMode.value) { + console.log('🔐 Using OAuth user token for APPLICATION scope'); + } + return createChallongeV2Client( + { token: oauthToken.value, type: AuthType.OAUTH }, + { debug: debugMode.value } + ); + } + } else { + // USER scope - prefer OAuth user tokens or API key + if (isOAuthAuthenticated.value && oauthToken.value) { + if (debugMode.value) { + console.log('🔐 Using OAuth user token for USER scope'); + } + return createChallongeV2Client( + { token: oauthToken.value, type: AuthType.OAUTH }, + { debug: debugMode.value } + ); + } else if (apiKey.value) { + if (debugMode.value) { + console.log('🔑 Using API Key for USER scope'); + } + return createChallongeV2Client( + { token: apiKey.value, type: AuthType.API_KEY }, + { debug: debugMode.value } + ); + } + } + + // Fallback: try API key + if (apiKey.value) { + if (debugMode.value) { + console.log('🔑 Using API Key (fallback)'); + } + return createChallongeV2Client( + { token: apiKey.value, type: AuthType.API_KEY }, + { debug: debugMode.value } + ); + } + + return null; + } + }); + + /** + * Current authentication type being used + */ + const authType = computed(() => { + if (apiVersion.value === 'v1') { + return 'API Key'; + } + if (isClientCredsAuthenticated.value) { + return 'Client Credentials'; + } + if (isOAuthAuthenticated.value) { + return 'OAuth'; + } + return 'API Key'; + }); + + /** + * Switch API version + */ + function switchVersion(version) { + if (version !== 'v1' && version !== 'v2.1') { + throw new Error('Invalid API version. Must be "v1" or "v2.1"'); + } + apiVersion.value = version; + } + + /** + * Set tournament scope (v2.1 only) + */ + function setScope(scope) { + if (scope !== ScopeType.USER && scope !== ScopeType.APPLICATION) { + throw new Error('Invalid scope type'); + } + tournamentScope.value = scope; + } + + /** + * Toggle debug mode + */ + function setDebugMode(enabled) { + debugMode.value = enabled; + } + + return { + // State + apiVersion, + tournamentScope, + debugMode, + apiKey, + maskedApiKey, + client, + authType, + isOAuthAuthenticated, + isClientCredsAuthenticated, + + // Methods + switchVersion, + setScope, + setDebugMode, + + // Constants + ScopeType, + AuthType + }; +} diff --git a/code/websites/pokedex.online/tests/unit/composables/useChallongeClient.test.js b/code/websites/pokedex.online/tests/unit/composables/useChallongeClient.test.js new file mode 100644 index 0000000..e69de29