useAuthReady()

framework 把 device + auth 身份完全 client-only 建立(v3 设计代际,framework#44) —— auth.client.ts plugin 在 hydrate 后才调用 userStore.getDeviceId() + userStore.autoLogin()。这中间有个空窗期:hydrate 完到 auth.client 完成之间,业务请求可能拿不到 token / deviceId。

useAuthReady() 就是这个空窗期的"等待门"。源码:src/runtime/composables/useAuthReady.ts。

大多数业务不需要手动用

framework request() 函数已经内置 await useAuthReady():

// framework/src/runtime/utils/request.ts
export async function request<T>(url, options) {
  if (import.meta.client) await useAuthReady()   // ← 内置门控
  const { $request } = useNuxtApp()
  return $request(url, options)
}

业务代码:

<script setup lang="ts">
defineRouteRules({ isr: 3600 })

onMounted(async () => {
  // 不需要手动 useAuthReady(),request() 内部自动等
  const data = await request('/api/user/favorite/check')
})
</script>

何时需要显式 await useAuthReady()

业务 UI 需要"等登录完才显示用户态片段"时显式等:

<script setup lang="ts">
defineRouteRules({ ssr: false })

const userStore = useUserStore()
const isReady = ref(false)

onMounted(async () => {
  await useAuthReady()    // ← 显式等
  isReady.value = true
})
</script>

<template>
  <div v-if="isReady">
    <UserAvatar :nickname="userStore.info?.nickname" />
    <VipBadge :is-vip="userStore.isLoggedIn" />
  </div>
  <SkeletonLoader v-else />
</template>

如果不显式等,isReady 立刻为 true 但 userStore.info 还没就绪 → 模板渲染空值 → 几百毫秒后突然出现真实值 → 闪烁。

API

函数签名说明
useAuthReady()() => Promise<void>等待 auth-ready Promise。SSR 端立即 resolve;client 端等到 auth.client.ts 完成
_resolveAuthReady()() => voidframework 内部使用,applier 业务代码不应调用。auth.client.ts 完成 device + login 后调它解除门控

SSR 行为

SSR 期 framework 永不主动建立 device/auth 身份(v3 设计核心),所以 SSR 没什么可等的。useAuthReady() 在 SSR 上下文立即 resolve。

业务侧无需为 SSR 单独写分支 —— 两端行为统一。

与 request() 内部的协作机制

auth.client.ts 的 login 流程自身也调 request('/login/device') 这类接口。如果 framework 内部 login 流程也走带 gate 的 request(),会形成 deadlock(login 等 auth-ready,auth-ready 等 login 完成)。

framework 的解法:内部用 frameworkInternalRequest() 绕过 gate。源码:

// utils/request.ts
export function frameworkInternalRequest<T>(url, options): Promise<T> {
  const { $request } = useNuxtApp()
  return $request(url, options)   // 无 useAuthReady await
}

userStore.loginByUsername / loginByQrcode / loginByDevice 内部都走 frameworkInternalRequest(绕过 auth-ready 门控建立身份)。业务普通请求不要直接用 frameworkInternalRequest,全部用公开 request()。

失败兜底

auth.client.ts 内部用 try / finally 包装:

try {
  userStore.getDeviceId()
  if (!userStore.token) await userStore.autoLogin()
} finally {
  _resolveAuthReady()   // ← 即便 login 失败也解锁
}

最差情况:login 失败 → _resolveAuthReady 仍执行 → 业务请求按匿名状态发出。好过死锁让用户白屏。

auth: false 场景

swiftConfig: { auth: false }(docs 站等无 backend 项目)时 auth.client.ts 跳过 device + login,但仍然调 _resolveAuthReady() —— 业务 request() 不会被卡住,按匿名请求正常发出。

详见 auth ModuleOptions。

相关

注意事项

  • await 之后必须显式检查 isLoggedIn(Rule UAR-001)—— 本函数只保证「登录流程已结束」(device 初始化 + 凭证尝试都跑过),不保证登录成功:网络故障、凭证过期、用户主动登出都会让流程结束但无 token。误以为 await 后就一定已登录、直接访问 userStore.info 会拿到 null 并运行时崩溃。
  • 标准写法:await useAuthReady() → if (!userStore.isLoggedIn) return useLoginCheck() → 再访问用户数据。
  • 业务侧一般不用手动调 —— request() 在 client 端已自动 await useAuthReady()(Rule REQ-001),确保请求不会在身份建立前发出。只有「登录就绪后要跑自己的逻辑」才需要显式调用。