From 2954f7b76953c01166c4590fb21309da1c636537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Fri, 2 Oct 2026 02:56:00 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8DAGC=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E5=A4=B1=E8=B4=A5=E4=B8=8D=E7=BB=99=E5=8F=AF?= =?UTF-8?q?=E8=A7=81=E5=8F=8D=E9=A6=88=E7=9A=84=E5=9B=9E=E9=80=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - hydrateAuth 的 catch 恢复改动前的可见反馈:登录状态检查超时等非载体错误先写 authCheckError/loginStatus 再原样抛出 - 系统变体先按类型化载荷(reason/status/detail/serverMessage)拼出提示文案,再原样抛出交全局 unhandledrejection 上报 - appSurface 用例改为断言启动超时后登录页显示原因并保留「重试登录状态检查」 --- .../src/app/AuthenticatedClient.tsx | 98 +++++++++++++++++-- .../tests/appSurface/auth.suite.ts | 8 +- 2 files changed, 96 insertions(+), 10 deletions(-) diff --git a/apps/ai-game-creator-shell/src/app/AuthenticatedClient.tsx b/apps/ai-game-creator-shell/src/app/AuthenticatedClient.tsx index 825c7e98d..c26b260bf 100644 --- a/apps/ai-game-creator-shell/src/app/AuthenticatedClient.tsx +++ b/apps/ai-game-creator-shell/src/app/AuthenticatedClient.tsx @@ -35,10 +35,16 @@ import { captureClientError, installWebviewLogBridge, } from '../services/errorReporting'; +import type { AuthNetworkFailure } from '../services/generated/AuthNetworkFailure'; +import type { AuthResponseInvalid } from '../services/generated/AuthResponseInvalid'; +import type { AuthServiceUnavailable } from '../services/generated/AuthServiceUnavailable'; +import type { ClientSessionPersistFailed } from '../services/generated/ClientSessionPersistFailed'; import type { PasswordLoginRejected } from '../services/generated/PasswordLoginRejected'; import type { PhoneCodeLoginRejected } from '../services/generated/PhoneCodeLoginRejected'; +import type { RuntimeSessionInstallFailed } from '../services/generated/RuntimeSessionInstallFailed'; import type { SendCodeRejected } from '../services/generated/SendCodeRejected'; import type { ServerAddressRejected } from '../services/generated/ServerAddressRejected'; +import type { UnexpectedRejection } from '../services/generated/UnexpectedRejection'; import { beginPlatformSessionClearTransition, beginPlatformSessionTransition, @@ -269,7 +275,13 @@ export function AuthenticatedClient({ if (!isActiveRun()) return; // 失败一律先离开检查态:系统变体虽然要原样抛出上报,界面也不能卡在 loading。 setAuthStatus('unauthenticated'); - if (!(error instanceof ClientAuthErrorWrapper)) throw error; + // 启动检查超时等我们自己的失败:改动前会显示原因 + 重试按钮,这里恢复同样的可见反馈。 + if (!(error instanceof ClientAuthErrorWrapper)) { + const text = error instanceof Error ? error.message : String(error); + setAuthCheckError(text); + setLoginStatus(text); + throw error; + } const failure = error.error; switch (failure.type) { // 预期业务拒绝:调用方补上本次操作的上下文,展示后不进错误池。 @@ -368,15 +380,83 @@ export function AuthenticatedClient({ case 'sessionInvalidated': case 'permissionDenied': break; - // 系统失败:调用方处理不了,原样抛出 → 全局 unhandledrejection 交给错误池。 - case 'authNetworkFailure': - case 'authServiceUnavailable': - case 'unexpectedRejection': - case 'authResponseInvalid': - case 'clientSessionPersistFailed': - case 'runtimeSessionInstallFailed': - case 'authClientInitFailed': + // 系统失败:调用方处理不了,先按原始事实给用户可见反馈,再原样抛出 → 全局 unhandledrejection 交给错误池。 + case 'authNetworkFailure': { + const payload = failure as AuthNetworkFailure; + const text = + payload.reason === 'timeout' + ? '登录服务响应超时,请检查服务器地址和网络后重试' + : '无法连接登录服务,请确认配套后端或 API 代理已启动后重试'; + setAuthCheckError(text); + setLoginStatus(text); throw error; + } + case 'authServiceUnavailable': { + const payload = failure as AuthServiceUnavailable; + const text = `登录服务暂时不可用(HTTP ${payload.status})`; + setAuthCheckError(text); + setLoginStatus(text); + throw error; + } + case 'unexpectedRejection': { + const payload = failure as UnexpectedRejection; + const text = payload.serverMessage + ? `登录服务请求失败(HTTP ${payload.status}):${payload.serverMessage}` + : `登录服务请求失败(HTTP ${payload.status})`; + setAuthCheckError(text); + setLoginStatus(text); + throw error; + } + case 'authResponseInvalid': { + const payload = failure as AuthResponseInvalid; + let text: string; + switch (payload.reason) { + case 'notJson': + text = '登录服务响应不是合法 JSON'; + break; + case 'invalidBody': + text = '登录服务响应结构不符合契约'; + break; + case 'missingRefreshCookie': + text = '登录服务未返回新的续期凭据'; + break; + case 'missingUserIdentity': + text = '登录服务未返回用户身份'; + break; + case 'serverRejected': + text = payload.serverMessage + ? `登录服务拒绝了本次请求:${payload.serverMessage}` + : '登录服务拒绝了本次请求'; + break; + default: { + expectNever(payload.reason); + text = '登录服务响应不符合契约'; + } + } + setAuthCheckError(text); + setLoginStatus(text); + throw error; + } + case 'clientSessionPersistFailed': { + const payload = failure as ClientSessionPersistFailed; + const text = `本机登录凭据读写失败:${payload.detail}`; + setAuthCheckError(text); + setLoginStatus(text); + throw error; + } + case 'runtimeSessionInstallFailed': { + const payload = failure as RuntimeSessionInstallFailed; + const text = `本机运行时会话安装失败:${payload.detail}`; + setAuthCheckError(text); + setLoginStatus(text); + throw error; + } + case 'authClientInitFailed': { + const text = '创建登录网络客户端失败'; + setAuthCheckError(text); + setLoginStatus(text); + throw error; + } default: { expectNever(failure); throw error; diff --git a/apps/ai-game-creator-shell/tests/appSurface/auth.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/auth.suite.ts index 0fad9ed16..9ead2250f 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/auth.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/auth.suite.ts @@ -127,9 +127,15 @@ export function registerAuthTests() { ).toBe(true), { timeout: 20_000 }, ); + // 启动检查失败在改动前就会显示原因 + 重试按钮;上报是叠加的,不是替换。 + expect( + await screen.findByText( + '检查登录状态超时,请检查服务器地址和网络后重试', + ), + ).not.toBeNull(); expect( screen.queryByRole('button', { name: '重试登录状态检查' }), - ).toBeNull(); + ).not.toBeNull(); await screen.findByRole('main', { name: '登录' }); } finally { window.removeEventListener('unhandledrejection', onRejection);