Add start.gg OAuth login flow for local users

This commit is contained in:
Pandipipas
2026-02-16 00:22:54 +01:00
parent 165482a7e0
commit 78dc137679
2 changed files with 381 additions and 2 deletions
+90 -2
View File
@@ -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<StartGGTournament | null>(null);
const startGGPlayers = ref<StartGGImportedPlayer[]>([]);
const selectedStartGGPlayerIds = ref<string[]>([]);
const oauthLoading = ref(false);
const oauthSessionId = ref('');
let oauthPollingTimer: ReturnType<typeof setInterval> | 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 = <T>(messageName: string, payload: unknown): Promise<T>
});
});
const clearOAuthPolling = () => {
if (oauthPollingTimer) {
clearInterval(oauthPollingTimer);
oauthPollingTimer = null;
}
};
const checkOAuthStatus = async () => {
if (!oauthSessionId.value) {
return;
}
try {
const status = await sendNodeCGMessage<OAuthStatusResponse>('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<OAuthSessionResponse>('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();
});
</script>
<template>
@@ -346,7 +425,7 @@ onMounted(() => {
Integración start.gg
</div>
<div class="text-caption q-mb-md">
Pega tu token personal de start.gg para cargar automáticamente tus torneos creados o donde eres admin.
Conecta por OAuth (recomendado) o pega tu token personal para cargar tus torneos creados o donde eres admin.
</div>
<div class="row q-col-gutter-sm items-center">
<div class="col-12">
@@ -358,6 +437,15 @@ onMounted(() => {
type="password"
/>
</div>
<div class="col-12">
<QBtn
color="primary"
icon="login"
label="Conectar con start.gg"
:loading="oauthLoading"
@click="connectWithStartGGOAuth"
/>
</div>
<div class="col-12">
<QBtn
color="secondary"