From 78dc137679a2632e7ae82281857b0e2d30030b17 Mon Sep 17 00:00:00 2001 From: Pandipipas <62224708+Pandipipas@users.noreply.github.com> Date: Mon, 16 Feb 2026 00:22:54 +0100 Subject: [PATCH] Add start.gg OAuth login flow for local users --- src/dashboard/scoreko-dev/views/Players.vue | 92 ++++++- src/extension/startgg.ts | 291 ++++++++++++++++++++ 2 files changed, 381 insertions(+), 2 deletions(-) diff --git a/src/dashboard/scoreko-dev/views/Players.vue b/src/dashboard/scoreko-dev/views/Players.vue index 8954688..00ad156 100644 --- a/src/dashboard/scoreko-dev/views/Players.vue +++ b/src/dashboard/scoreko-dev/views/Players.vue @@ -4,7 +4,7 @@ import { useHead } from '@unhead/vue'; defineOptions({ name: 'PlayersView' }); import type { QTableColumn } from 'quasar'; -import { computed, onMounted, reactive, ref, watch } from 'vue'; +import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'; import { countryOptions, getCountryLabel } from '../../../shared/countries'; import type { Schemas } from '../../../types'; import { usePlayersStore } from '../stores/players'; @@ -96,6 +96,21 @@ const selectedTournament = ref(null); const startGGPlayers = ref([]); const selectedStartGGPlayerIds = ref([]); +const oauthLoading = ref(false); +const oauthSessionId = ref(''); +let oauthPollingTimer: ReturnType | null = null; + +interface OAuthSessionResponse { + sessionId: string; + authUrl: string; +} + +interface OAuthStatusResponse { + status: 'pending' | 'completed' | 'error' | 'expired'; + token?: string; + error?: string; +} + watch(startGGToken, (value) => { localStorage.setItem(STARTGG_TOKEN_STORAGE_KEY, value); }); @@ -111,6 +126,66 @@ const sendNodeCGMessage = (messageName: string, payload: unknown): Promise }); }); +const clearOAuthPolling = () => { + if (oauthPollingTimer) { + clearInterval(oauthPollingTimer); + oauthPollingTimer = null; + } +}; + +const checkOAuthStatus = async () => { + if (!oauthSessionId.value) { + return; + } + + try { + const status = await sendNodeCGMessage('startgg:getOAuthSessionStatus', { + sessionId: oauthSessionId.value, + }); + + if (status.status === 'completed' && status.token) { + startGGToken.value = status.token; + oauthLoading.value = false; + clearOAuthPolling(); + oauthSessionId.value = ''; + tournamentsError.value = ''; + await loadRecentTournaments(); + return; + } + + if (status.status === 'error' || status.status === 'expired') { + oauthLoading.value = false; + clearOAuthPolling(); + oauthSessionId.value = ''; + tournamentsError.value = status.error || 'No se pudo completar el login OAuth con start.gg.'; + } + } catch (error) { + oauthLoading.value = false; + clearOAuthPolling(); + oauthSessionId.value = ''; + tournamentsError.value = error instanceof Error ? error.message : 'No se pudo verificar el estado OAuth.'; + } +}; + +const connectWithStartGGOAuth = async () => { + oauthLoading.value = true; + tournamentsError.value = ''; + clearOAuthPolling(); + + try { + const session = await sendNodeCGMessage('startgg:createOAuthSession', {}); + oauthSessionId.value = session.sessionId; + window.open(session.authUrl, '_blank', 'noopener,noreferrer'); + + oauthPollingTimer = setInterval(() => { + void checkOAuthStatus(); + }, 1500); + } catch (error) { + oauthLoading.value = false; + tournamentsError.value = error instanceof Error ? error.message : 'No se pudo iniciar OAuth con start.gg.'; + } +}; + const loadRecentTournaments = async () => { const token = startGGToken.value.trim(); if (!token) { @@ -252,6 +327,10 @@ onMounted(() => { void loadRecentTournaments(); } }); + +onBeforeUnmount(() => { + clearOAuthPolling(); +});