diff --git a/apps/ai-game-creator-shell/src/services/clientAuth.ts b/apps/ai-game-creator-shell/src/services/clientAuth.ts index 2af0f6ce7..04c60af19 100644 --- a/apps/ai-game-creator-shell/src/services/clientAuth.ts +++ b/apps/ai-game-creator-shell/src/services/clientAuth.ts @@ -1,6 +1,10 @@ import type { AuthUser } from '../../../../packages/shared/src/contracts/auth'; import { resolveTauriInvoke } from '../app/tauri'; -import { clientAuthErrorNotice, isClientAuthError } from './clientAuthError'; +import { + clientAuthErrorKind, + clientAuthErrorNotice, + isClientAuthError, +} from './clientAuthError'; import { subscribeTauriEvent } from './tauriEventSubscription'; /** Rust 认证态事件:只承载状态投影,不含 token 或 refresh 凭据。 */ @@ -57,7 +61,8 @@ export function getClientAuthErrorMessage(error: unknown, fallback: string) { * 只显示 Rust 文案;`report` 是系统变体 / 未识别变体 / 非结构化拒绝,显示文案之外还要带上下文 * 交给错误池(见 [`captureClientError`](./errorReporting.ts))。 * - * 判定只看类型化的 `type`,不做任何文案匹配;这条规则是"输错一次密码不该被引导上报"的判据。 + * 判定只看类型化的 `type`(`clientAuthErrorKind`),不做任何文案匹配;这条规则是 + * "输错一次密码不该被引导上报"的判据。 */ export type ClientAuthFailureResolution = | { kind: 'notice'; message: string } @@ -68,8 +73,16 @@ export function resolveClientAuthFailure( fallback: string, ): ClientAuthFailureResolution { const structured = isClientAuthError(error); - const notice = structured ? clientAuthErrorNotice(structured) : null; - if (notice !== null) return { kind: 'notice', message: notice }; + // 分流判据是 `clientAuthErrorKind`,不是"有没有提示":`clientAuthErrorNotice` 对空文案也返回 + // `null`,拿它当判据会把"Rust 给了可识别的 input/session 变体、但文案为空"错当成 fault 上报。 + if (structured && clientAuthErrorKind(structured) !== 'fault') { + return { + kind: 'notice', + message: + clientAuthErrorNotice(structured) ?? + getClientAuthErrorMessage(error, fallback), + }; + } return { kind: 'report', message: getClientAuthErrorMessage(error, fallback), diff --git a/apps/ai-game-creator-shell/src/services/clientAuthError.ts b/apps/ai-game-creator-shell/src/services/clientAuthError.ts index 741e1ce6d..94797fd16 100644 --- a/apps/ai-game-creator-shell/src/services/clientAuthError.ts +++ b/apps/ai-game-creator-shell/src/services/clientAuthError.ts @@ -51,7 +51,10 @@ export function clientAuthErrorKind( /** * 用户可读的提示:`input` / `session` 原样使用 Rust 生成的那一份文案;`fault` 返回 `null`, - * 表示"调用方处理不了,交给错误池"。空文案按"没有提示"处理,避免调用方拿空串当提示显示。 + * 表示"调用方处理不了,交给错误池"。空文案也按"没有提示"处理,避免调用方拿空串当提示显示。 + * + * **`null` 同时表示这两件事,所以它不是分流判据**:判定"要不要进错误池"必须用 + * [`clientAuthErrorKind`],只有 `fault` 才上报;否则一个文案为空的可识别变体会被误报。 */ export function clientAuthErrorNotice(error: ClientAuthError): string | null { if (clientAuthErrorKind(error) === 'fault') return null; diff --git a/apps/ai-game-creator-shell/tests/clientAuthHost.test.ts b/apps/ai-game-creator-shell/tests/clientAuthHost.test.ts index 68d8a612a..2d2d51493 100644 --- a/apps/ai-game-creator-shell/tests/clientAuthHost.test.ts +++ b/apps/ai-game-creator-shell/tests/clientAuthHost.test.ts @@ -210,6 +210,14 @@ test('登录失败分流只看类型化变体:业务拒绝给提示,系统 '登录失败', ), ).toEqual({ kind: 'notice', message: '登录已失效,请重新登录' }); + // 文案为空的可识别变体仍按"用户可改"处理:给兜底提示,不进错误池 + // (分流判据是 kind,不是"有没有提示文案")。 + expect( + resolveClientAuthFailure( + { type: 'phoneNumberInvalid', message: ' ' }, + '登录失败', + ), + ).toEqual({ kind: 'notice', message: '登录失败' }); // 系统变体与未识别变体:显示 Rust 文案,同时交给错误池。 expect( resolveClientAuthFailure(