feat: add access token management and gateway token exchange functionality

This commit is contained in:
2026-01-16 19:23:40 +00:00
parent 9c85a50645
commit b5f53a7c42
10 changed files with 378 additions and 33 deletions
+8
View File
@@ -5,3 +5,11 @@ declare module '*.vue' {
const component: DefineComponent<{}, {}, any>;
export default component;
}
interface ImportMetaEnv {
readonly VITE_GAME_WEB_URL?: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
@@ -27,6 +27,7 @@ const notice = ref('');
const profiles = ref<LobbyProfile[]>([]);
const profileDetails = ref<Record<string, LobbyInfo | undefined>>({});
const profileMapPreviews = ref<Record<string, MapPreviewBundle | undefined>>({});
const entryLoading = ref<Record<string, boolean>>({});
onMounted(async () => {
try {
@@ -75,6 +76,48 @@ const handleLogout = async () => {
// await trpc.auth.logout.mutation();
await router.push('/');
};
const resolveGameUrl = (path: string, profileName: string, gameToken: string): string | null => {
const baseUrl = import.meta.env.VITE_GAME_WEB_URL ?? '';
if (!baseUrl) {
return null;
}
const base = new URL(baseUrl, window.location.origin);
const normalizedPath = path.replace(/^\//, '');
const url = new URL(normalizedPath, base);
url.searchParams.set('profile', profileName);
url.searchParams.set('gameToken', gameToken);
return url.toString();
};
const handleEnter = async (profile: LobbyProfile, targetPath: string) => {
if (entryLoading.value[profile.profileName]) {
return;
}
const sessionToken = window.localStorage.getItem('sammo-session-token');
if (!sessionToken) {
await router.push('/');
return;
}
entryLoading.value[profile.profileName] = true;
try {
const issued = await trpc.auth.issueGameSession.mutate({
sessionToken,
profile: profile.profileName,
});
const url = resolveGameUrl(targetPath, issued.profile, issued.gameToken);
if (!url) {
alert('게임 프론트엔드 주소가 설정되지 않았습니다.');
return;
}
window.location.href = url;
} catch (e) {
console.error('Failed to issue game session', e);
alert('게임 서버 접속에 실패했습니다.');
} finally {
entryLoading.value[profile.profileName] = false;
}
};
</script>
<template>
@@ -186,12 +229,16 @@ const handleLogout = async () => {
<button
v-if="profileDetails[profile.profileName]?.myGeneral"
class="w-full bg-zinc-700 hover:bg-zinc-600 text-white py-1.5 rounded text-sm transition-colors"
:disabled="entryLoading[profile.profileName]"
@click="handleEnter(profile, '/')"
>
입장
</button>
<button
v-else
class="w-full bg-zinc-700 hover:bg-zinc-600 text-white py-1.5 rounded text-sm transition-colors"
:disabled="entryLoading[profile.profileName]"
@click="handleEnter(profile, '/join')"
>
장수생성
</button>