From 42b0c7e7af085d0de9cd1d24830a51e60ac2dbc3 Mon Sep 17 00:00:00 2001 From: Ap_Tx <3045929398@qq.com> Date: Sat, 19 Sep 2026 20:01:46 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=9C=AA=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E7=9A=AE=E8=82=A4=E7=AB=99=E6=97=B6=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E5=8D=A1=E4=BD=8F=E4=B8=94=E4=B8=8D=E5=86=8D=E5=BC=B9=E7=8E=A9?= =?UTF-8?q?=E5=AE=B6=E9=80=89=E6=8B=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 未登录皮肤站时启动游戏会弹窗要求选择/登录玩家。若点击“登录皮肤站” 跳转后不登录直接返回,再次启动不会重新弹窗,而是卡在“正在启动”。 原因:InstancePlayerModal 的 signInSkinSite 跳转登录时只隐藏弹窗, 没有 settle 玩家选择的 Promise,因此 prepareInstancePlayer 在 preparing 表中留下的任务永不结束。再次启动时复用了这个悬空任务, 既不再弹窗也无法继续启动。 修复: - instance-player 新增哨兵错误 PlayerSelectionNavigatedAwayError。 - signInSkinSite 跳转登录前以该错误 reject 当前选择,使 prepareInstancePlayer 的任务正常结束、清理 in-flight 记录,下次 启动会重新弹出玩家选择。 - App.vue 的启动错误处理识别该哨兵:跳转登录是用户主动操作而非启动 失败,静默中止启动,不弹错误提示。 --- apps/app-frontend/src/App.vue | 5 +++++ .../src/components/instance/InstancePlayerModal.vue | 9 +++++++++ apps/app-frontend/src/helpers/instance-player.ts | 12 ++++++++++++ 3 files changed, 26 insertions(+) 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>()