修复登录失败分流把空文案的业务变体误判为需上报

- resolveClientAuthFailure 改用 clientAuthErrorKind 判分流,不再用 clientAuthErrorNotice 是否为 null 当判据
- 空文案的 input/session 变体改为给兜底提示、不进错误池,保持"预期拒绝永不进池"的约束
- clientAuthErrorNotice 文档注明 null 同时表示 fault 与空文案,不能当分流判据
- clientAuthHost.test.ts 增加空文案业务变体的分流用例
This commit is contained in:
2026-10-01 16:05:47 +08:00
parent b61fa52e8f
commit 8b50db9893
3 changed files with 29 additions and 5 deletions
@@ -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),
@@ -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;
@@ -210,6 +210,14 @@ test('登录失败分流只看类型化变体:业务拒绝给提示,系统
'登录失败',
),
).toEqual({ kind: 'notice', message: '登录已失效,请重新登录' });
// 文案为空的可识别变体仍按"用户可改"处理:给兜底提示,不进错误池
// (分流判据是 kind,不是"有没有提示文案")。
expect(
resolveClientAuthFailure(
{ type: 'phoneNumberInvalid', message: ' ' },
'登录失败',
),
).toEqual({ kind: 'notice', message: '登录失败' });
// 系统变体与未识别变体:显示 Rust 文案,同时交给错误池。
expect(
resolveClientAuthFailure(