diff --git a/apps/app-frontend/src/components/ui/AccountsCard.vue b/apps/app-frontend/src/components/ui/AccountsCard.vue index 85fc43b..82fe394 100644 --- a/apps/app-frontend/src/components/ui/AccountsCard.vue +++ b/apps/app-frontend/src/components/ui/AccountsCard.vue @@ -291,6 +291,7 @@ import { users, } from '@/helpers/auth' import { process_listener } from '@/helpers/events' +import { registerSkinSitePlayers } from '@/helpers/instance-player' import { getPlayerHeadUrl } from '@/helpers/rendering/batch-skin-renderer.ts' import type { Skin } from '@/helpers/skins' import { get_available_skins } from '@/helpers/skins' @@ -630,11 +631,22 @@ async function setAccount(account: MinecraftCredential) { } watch( - [skinSitePlayers, defaultUser], - ([availablePlayers, selectedLocalUser]) => { + [skinSitePlayers, defaultUser, skinSiteUser], + ([availablePlayers, selectedLocalUser, siteUser]) => { if (!selectedLocalUser && !selectedSkinSitePlayerId.value && availablePlayers.length > 0) { selectSkinSitePlayer(availablePlayers[0].uuid) } + // Register skin-site players as launcher accounts as soon as they are + // available, so the account picker shows them without requiring a first + // launch. `registerSkinSitePlayers` is idempotent and best-effort. + if (siteUser?.uuid && availablePlayers.length > 0) { + const pendingIds = availablePlayers.map((player) => player.uuid) + void registerSkinSitePlayers(pendingIds, siteUser.uuid) + .then(() => refreshValues()) + .catch((error) => { + console.warn('Failed to register skin site players:', error) + }) + } }, { immediate: true }, ) diff --git a/apps/app-frontend/src/helpers/instance-player.ts b/apps/app-frontend/src/helpers/instance-player.ts index c44725b..86f55b1 100644 --- a/apps/app-frontend/src/helpers/instance-player.ts +++ b/apps/app-frontend/src/helpers/instance-player.ts @@ -56,6 +56,52 @@ export async function authenticateInstancePlayer(player: InstancePlayer) { } } +/** + * Registers every skin-site player as a launcher account so they show up in the + * account picker immediately after signing in to the skin site, instead of only + * after a first launch. Idempotent: players that already exist in the account + * list (matched by profile UUID) are skipped, and already-signed-in players are + * not re-requested. Best-effort: a single player failing does not abort the rest. + */ +export async function registerSkinSitePlayers( + playerIds: string[], + userUuid: string, +): Promise { + if (playerIds.length === 0) return + if (skinSiteStatus.value !== 'signed-in' || skinSiteUser.value?.uuid !== userUuid) return + + let known = new Set() + try { + const existing = await users() + known = new Set( + (existing as Array<{ profile?: { id?: string } }>) + .map((account) => account?.profile?.id) + .filter((id): id is string => typeof id === 'string'), + ) + } catch { + // If the account list cannot be read, still attempt to register; the + // backend upsert is idempotent. + } + + for (const playerId of playerIds) { + if (known.has(playerId)) continue + if (skinSiteStatus.value !== 'signed-in' || skinSiteUser.value?.uuid !== userUuid) return + try { + // Request a fresh download token per player: the skin site login + // endpoint may bind a token to a single player id. + const token = await requestSkinSiteDownloadToken() + await invoke('plugin:auth|login_skin_site_player', { + token, + playerId, + userId: userUuid, + }) + known.add(playerId) + } catch (error) { + console.warn(`Failed to register skin site player ${playerId}:`, error) + } + } +} + export async function saveInstancePlayer(instanceId: string, player: InstancePlayer) { await authenticateInstancePlayer(player) await invoke('plugin:auth|set_instance_player', { instanceId, player })