Skip to content

错误处理 ​

@tripo3d/auth v2 把所有错误收敛到一个类——TripoAuthError,并固定 6 个 name 分类。业务侧按 error.name 分流即可,几乎所有路径都能写出可预测的 catch 模板。

TripoAuthError ​

ts
import { TripoAuthError } from '@tripo3d/auth';

class TripoAuthError extends Error {
  override readonly name: TripoAuthErrorName;
  readonly code?: string;       // ory 错误体里抽出来的 id / code,便于精细分流
  override readonly cause?: unknown;  // 原始 ory error / response / TypeError 等
  override readonly message: string;
}

type TripoAuthErrorName
  = | 'FLOW'
    | 'OIDC'
    | 'VALIDATION'
    | 'NETWORK'
    | 'INTERNAL'
    | 'PRIVILEGED_SESSION_EXPIRED';

字段语义:

  • name — 6 类枚举(见下表);业务侧主要按这个分流
  • code — ory 错误体里抽出来的 id / error.id / error.code,例如 session_inactive、self_service_flow_expired;可空
  • cause — 包了 { context, error } 或原始 error;调试期可看,生产期不直接展示给用户
  • message — 优先取 ory ui.messages → ui.nodes[*].messages → error.message → fallback;可以直接 toast 给用户

6 类 name ​

name典型来源说明
FLOWory flow 调用失败:HTTP 非 2xx、ui.messages 含 error、flow 已过期、CSRF 不匹配、验证码错auth.login.* / auth.reset.* / auth.oidc.loadEdgeFlow 几乎所有「业务正常路径走不通」的错;message 直接 toast 给用户
OIDCstartOidc 拿到的 redirect_browser_to 缺失或非法;OIDC 完成页 URL 字段不合法(mode/provider 缺失)业务逻辑通常需要降级到登录页(无法继续 OIDC);toast 后跳走
VALIDATION业务侧 API 用法不对:submitCode 前没 sendCode;reset.verifyCode 前没 sendCode、reset.submitNewPassword 前没 verifyCode;buildCompleteUrl 收到非法 completeUrl / 跨站 returnTo;loadEdgeFlow flowId 空开发期 bug;UI 状态应卡住这些路径。生产环境兜底跳走 + 上报
NETWORKfetch 抛 TypeError;ory 调用拿到 FetchError / NetworkError / AbortError;HTTP 5xx 服务端故障统一文案「服务异常,请稍后重试」;建议加重试按钮,不要静默吞掉
INTERNALSDK 自检异常:TripoAuth 构造缺 basePath;flow ui.nodes 取不到 csrf_token上游配置或 SDK 使用错误;上报 + 不要直接展示原文(开发期信息)
PRIVILEGED_SESSION_EXPIREDauth.settings.updatePassword / auth.reset.submitNewPassword 时当前 session 不够 privileged(多半是 verifyCode 与改密之间间隔超过 ory 的 privileged_session_max_age,默认 15 min)toast 提示「验证已过期,请重新发送验证码」+ UI 切回验证码一步(settings.sendVerifyCode / reset.sendCode);不要上报

「未登录 / 会话已失效」语义不走错误流:fetchSessionToken 与 createDefaultFetchToken 都把它吞掉返回 undefined——业务侧 await auth.token.get() 拿到 undefined 即视作未登录,无需 try/catch。

HTTP 5xx 归类为 NETWORK

服务端 5xx 故障在语义上是基础设施问题(不是业务流程走不通),v2 把它归类为 NETWORK 而非 FLOW。业务方可借此走「服务异常 / 稍后重试」分支,而不是把 ory 文案透传给用户。

推荐 catch 模板 ​

ts
import { TripoAuthError } from '@tripo3d/auth';

function handleAuthError(error: unknown) {
  if (!(error instanceof TripoAuthError)) {
    console.error(error);
    toast.error('网络异常,请稍后重试');
    captureException(error);
    return;
  }

  switch (error.name) {
    case 'FLOW':
    case 'OIDC':
      // 业务正常路径走不通:直接 toast 把 ory 文案展示
      toast.error(error.message);
      return;

    case 'NETWORK':
      toast.error('网络异常,请稍后重试');
      // 不上报 NETWORK:高频噪音
      return;

    case 'VALIDATION':
      // 开发期 bug;生产兜底跳登录页
      console.warn('[auth] validation failed:', error.message);
      captureException(error);
      router.push('/login');
      return;

    case 'INTERNAL':
      console.error('[auth] internal:', error.message);
      captureException(error);
      toast.error('登录服务暂不可用,请刷新页面');
      return;

    case 'PRIVILEGED_SESSION_EXPIRED':
      // 改密场景(settings.updatePassword / reset.submitNewPassword):verifyCode 与改密之间停太久,session 不再 privileged
      // 业务侧应把 UI 切回验证码一步让用户重新发码;不要上报(不是 bug)
      toast.warning('验证已过期,请重新发送验证码');
      switchToSendVerifyCodeStep();
      return;
  }
}

// 未登录场景:直接走「token.get() === undefined」分流,无需进 catch
const token = await auth.token.get();
if (!token) {
  router.push('/login');
}

各场景错误来源细化 ​

验证码登录 ​

抛错位置name典型 message / code
sendCode 内部 /idp-ext/auth/check-identifier fetch 失败NETWORK'check-identifier request failed'
sendCode 内部 check-identifier 返回非 2xx / success: falseFLOWbody.error 或 'check-identifier failed (HTTP ${status})'
sendCode / submitCode 内 ory flow 拒收(验证码错 / flow 过期 / 频控)FLOW透传 ory ui.messages
submitCode 在没先 sendCode 的情况下被调VALIDATION'submitCode requires a prior sendCode call in the same session'
flow ui.nodes 取不到 csrfINTERNAL'csrf_token node not found in flow ui'

OIDC ​

抛错位置name典型 message
startOidc 拿到的 redirect_browser_to 缺失OIDC'OIDC start did not yield a redirect_browser_to URL'
startOidc 拿到的 redirect_browser_to 不是合法 URLOIDC'OIDC redirect_browser_to is not a valid URL: ...'
buildCompleteUrl 收到跨站 returnToVALIDATION'[buildCompleteUrl] returnTo must be same-site tripo3d.ai: ...'
buildCompleteUrl 收到非法 completeUrlVALIDATION'[buildCompleteUrl] invalid completeUrl: ...'(必须是绝对 URL)
readCompleteContext URL 解析失败OIDC'[readCompleteContext] invalid URL: ...'
readCompleteContext mode/provider 缺失或非法OIDC'[readCompleteContext] invalid or missing mode: ...' 等
loadEdgeFlow flowId 空VALIDATION'loadEdgeFlow requires a non-empty flowId'
loadEdgeFlow probe 失败(非 merge 语义)FLOW'edge probe failed with status ${status}'
edge continue submit form 时 document 不可用OIDC'edge continue submit requires a browser environment'

密码重置 ​

抛错位置name典型 message
verifyCode 在没先 sendCode 的情况下被调VALIDATION'reset.verifyCode requires a prior reset.sendCode call in the same session'
submitNewPassword 在没先 verifyCode 的情况下被调VALIDATION'reset.submitNewPassword requires a prior reset.verifyCode call in the same session'
verifyCode 验证码错 / flow 过期FLOW透传 ory ui.messages
submitNewPassword 新密码弱 / 新旧相同FLOW透传 ory ui.messages(提权保留,可原地改密码重试)
submitNewPassword 提权过期(ory 回 session_refresh_required)PRIVILEGED_SESSION_EXPIRED引导回验证码步重发码

Token ​

auth.token.* 的失败行为:

  • peek() — 不抛错
  • get() / refresh() — 透传 fetchToken 抛的错(自定义 fetchToken 可能抛任意类型,默认实现透传 ory TripoAuthError);保留旧 cache
  • set(jwt) — 不抛错;非法 JWT 静默忽略
  • clear() — 不抛错

TripoAuth 构造 ​

抛错位置name典型 message
构造缺 basePathINTERNAL'TripoAuth requires a non-empty basePath'
fetchSessionToken 缺 basePathINTERNAL'fetchSessionToken requires a non-empty basePath'

onLogin handler 抛错的隔离 ​

SignalApi 内部 invoke 把每个 handler 包在独立的 try/catch + Promise.resolve().catch 里:

ts
private invoke(handler, signal) {
  try {
    Promise.resolve(handler(signal)).catch(err => this.logHandlerError(err));
  } catch (err) {
    this.logHandlerError(err);
  }
}

后果:

  • 同步抛错 / 异步 reject 都不会泄漏出来
  • 一个 handler 抛错不会影响别的 handler 派发
  • 错误默认走 console.warn,但可以通过构造时传入的 onError 钩子统一监控(code: 'onlogin_handler_failed',详见下一节)
  • 业务侧自己想精细上报 → 在 handler 里包 try/catch + 显式 captureException
ts
auth.signal.onLogin(async ({ signal }) => {
  try {
    // SDK 已在派发前做了 token.refresh 兜底;保留 refresh 作为容错(内部并发去重,不会重复打后端)
    await auth.token.refresh();
    await fetchCurrentUser();
  } catch (err) {
    captureException(err);
    // SDK 同时会把这个错误抛给 onError 钩子(onlogin_handler_failed)
  }
});

onError 非阻塞错误观察钩子 ​

TripoAuthOptions.onError?: (err: TripoAuthError) => void 是构造时传入的非阻塞错误观察回调。SDK 在以下「静默运营错误」发生时调用它——这些错误不抛出、不打断业务流,仅供监控:

场景err.code
JWT 续期网络失败'token_refresh_failed'
续期拿到非法 JWT(无 exp / decode 失败)'token_invalid_jwt'
业务 onLogin handler 抛错'onlogin_handler_failed'

不传 onError 时默认回落 console.warn。

ts
const auth = new TripoAuth({
  basePath: 'https://auth.tripo3d.ai',
  onError: (err) => {
    // 统一上报这些非阻塞运营错误
    captureException(err, { tags: { authErrorCode: err.code } });
  },
});

注意:onError 只覆盖上述静默运营错误;业务正常路径抛出的 TripoAuthError(FLOW / OIDC / VALIDATION 等)仍然通过方法的 await/catch 抛给业务方,不走 onError。

showLogin 异常兜底 ​

showLogin(auth, options) 返回 void,自身不抛错:

  • 宿主忘了挂 <DialogOpener /> → dialog.show 返回空串 → dialog 不弹出,无副作用
  • 用户点 ✕ / ESC → dialog 自己关闭,无 outcome 概念
  • dialog 内部的 LoginPanel 抛错时(如 submitCode 拒收)→ 由 dialog 自己渲染 toast 反馈,不会冒到调用栈
  • 登录完成(含 mobile OIDC redirect 整页跳走)的唯一消费路径是 auth.signal.onLogin(handler)

因此 showLogin 不需要 try/catch、也没有返回值要分流;业务方只要先注册 onLogin handler 再唤起登录即可。

logout 错误路径 ​

auth.logout() 内部三步:

  1. ory createBrowserLogoutFlow() 拿到 logout_url
  2. fetcher(logoutUrl, { method: 'GET' }) 让 ory 失效 session
  3. clearSignal() + 外层 token.clear()

错误处理:

路径行为
步骤 1 抛 401 / session_inactive / 「unauthorized」视作已登出,跳过步骤 2 直接走步骤 3 清理(不抛错)
步骤 1 抛其他错rethrow,不动本地状态(外层 token.clear() 也不会跑)
步骤 2 fetch 抛错rethrow,不动本地状态
全部成功走步骤 3,清 signal cookie + token cache

业务侧典型处理:

ts
async function onLogout() {
  try {
    await auth.logout();
    router.push('/');
  } catch (err) {
    if (err instanceof TripoAuthError && err.name === 'NETWORK') {
      toast.error('网络异常,请稍后重试');
    } else {
      handleAuthError(err);
    }
  }
}

生产环境最佳实践 ​

  1. FLOW / OIDC 直接 toast error.message:ory 已经把 UI 文案准备好了
  2. NETWORK 给统一文案 + 重试按钮:不要把 ory 错误透传给用户,体验差
  3. VALIDATION 必须上报:通常是 SDK 用法 bug
  4. INTERNAL 必须上报 + 兜底:上游配置错;至少让用户能逃出当前页
  5. 不要在 try/catch 里依赖 error.code:当前 SDK 透传 ory error.id / error.code,ory 升级时可能变;安全做法是文本匹配 error.message 或类型匹配 error.name
  6. onLogin handler 抛错可通过 onError 钩子监控:默认 console.warn;传 onError 即可统一上报(code: 'onlogin_handler_failed'),无需在每个 handler 里手动包 try/catch
  7. OIDC redirect 模式没有同步 catch 机会:所有失败语义只可能体现在 OidcCompletePage 的兜底 toast 或事后 onLogin 没触发

调试技巧 ​

  • 主窗 onLogin 没触发:先看 document.cookie 里有没有 __tripo_login_signal__;没有 → 写入端有问题;有 → 读取端有问题(polling 是否启动?constructor 时已经被消费了?)
  • startOidc 抛 VALIDATION:99% 是 returnTo 跨站;改成 tripo3d.ai 同站
  • submitCode 抛 FLOW 但 message 模糊:看 error.cause,里面的 { context, error } 有原始 ory response
  • token.get() 返回 undefined 但你确认浏览器 cookie 在:ory toSession 失败;用 await auth.token.refresh() 直接调一次看抛什么;网络层错查 error.cause.error 拿 fetch error 原文

下一步 ​

基于 MIT 协议发布(内部使用)