diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index 93e3bfb..7b424d1 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -113,6 +113,7 @@ import { } from '@/helpers/events.js' import { install_create_modpack_instance, install_get_modpack_preview } from '@/helpers/install' import { type DirectLinkSyncReport, get as getInstance, run } from '@/helpers/instance' +import { PlayerSelectionNavigatedAwayError } from '@/helpers/instance-player' import { reconcileMojangAuthSourceAtStartup } from '@/helpers/mojang-auth' import { cancelLogin, get as getCreds, login, logout } from '@/helpers/mr_auth.ts' import { mergeUrlQuery, parseModrinthLink } from '@/helpers/project-links.ts' @@ -1757,6 +1758,10 @@ async function handleCommand(e) { } else if (e.event === 'LaunchInstance') { const instance = await getInstance(e.id).catch(() => null) const handleLaunchCommandError = async (launchError) => { + // Navigating to the skin-site login to pick a player is a deliberate + // user action, not a launch failure: stay silent and let the user + // re-trigger the launch after signing in. + if (launchError instanceof PlayerSelectionNavigatedAwayError) return const handled = (await minecraftCrashModal.value?.handleLaunchError(launchError, { instance_id: e.id, diff --git a/apps/app-frontend/src/components/instance/InstancePlayerModal.vue b/apps/app-frontend/src/components/instance/InstancePlayerModal.vue index 51c4629..74f517e 100644 --- a/apps/app-frontend/src/components/instance/InstancePlayerModal.vue +++ b/apps/app-frontend/src/components/instance/InstancePlayerModal.vue @@ -14,6 +14,7 @@ import { import { users } from '@/helpers/auth' import { getInstanceMode } from '@/helpers/hosted-packs' import { + PlayerSelectionNavigatedAwayError, registerInstancePlayerPicker, saveInstancePlayer, waitForSkinSiteSession, @@ -127,6 +128,14 @@ async function select(player: PlayerChoice) { function signInSkinSite() { awaitingSkinLogin.value = true hideForLogin = true + active.value = false + generation++ + // Settle the pending selection so `prepareInstancePlayer` releases its + // in-flight entry; otherwise a later launch would await this forever and + // never re-prompt. The caller treats this sentinel as a silent abort. + rejectSelection?.(new PlayerSelectionNavigatedAwayError()) + resolveSelection = undefined + rejectSelection = undefined openSkinSiteLogin() modal.value?.hide() void router.push('/starlight-skin') diff --git a/apps/app-frontend/src/helpers/instance-player.ts b/apps/app-frontend/src/helpers/instance-player.ts index 86f55b1..5b5bbbc 100644 --- a/apps/app-frontend/src/helpers/instance-player.ts +++ b/apps/app-frontend/src/helpers/instance-player.ts @@ -17,6 +17,18 @@ export type InstancePlayer = { skin_site_user?: string | null } export type PlayerChoice = InstancePlayer & { head?: string } + +/** + * Thrown when the player picker is dismissed because the user navigated to the + * skin-site login page. Callers should treat this as a silent launch abort + * (the user will pick a player next time), not as a real failure. + */ +export class PlayerSelectionNavigatedAwayError extends Error { + constructor() { + super('已跳转至皮肤站登录,请登录后重新启动。') + this.name = 'PlayerSelectionNavigatedAwayError' + } +} type Picker = (instanceId: string, locked: InstancePlayer | null) => Promise let picker: Picker | undefined const preparing = new Map>()