后端与接口
在 Caffeine AI/ICP React…
适用于基于 @caffeineai/core-infrastructure 的 React 前端,需要接入 Internet Identity 认证、QueryClient、后端 actor 与 mock 后端。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
在 Caffeine AI/ICP Re特定
解决思路
- 在 package.json 中声明依赖:`@caffeineai/core-infrastructure@^1.4.0`、`@caffeineai/object-storage@^1.1.0`、`@icp-sdk/auth@^7.1.0`、`@icp-sdk/core@^5.3.0`;其中 `@caffeineai/object-storage` 是 core-infrastructure 的 peer dependency,必须作为直接 npm dependency 安装(build template 已包含两者)。
- 核心基础设施在每个项目中自动包含,无需手动集成步骤;前端包 `@caffeineai/core-infrastructure` 也自动包含在项目中。
- 在 React 入口文件中用 `QueryClientProvider` 和 `InternetIdentityProvider` 包裹应用:创建 `const queryClient = new QueryClient()`;在 `ReactDOM.createRoot(document.getElementById("root")!).render(...)` 中按 `QueryClientProvider` > `InternetIdentityProvider` > `App` 的顺序嵌套。
- 使用 `useInternetIdentity()` 获取认证状态与操作,返回字段包括:`identity: Identity | undefined`、`login: (options?: LoginOptions) => void`、`clear: () => void`、`isAuthenticated: boolean`、`isInitializing: boolean`、`isLoggingIn: boolean`、`isLoginSuccess: boolean`、`isLoginError: boolean`、`loginError: Error | undefined`。
- 用 `isAuthenticated` 做条件渲染/UI gating;不要用 `isLoginSuccess` 做条件渲染,因为 `isLoginSuccess` 只在通过弹窗交互式登录后为 `true`,页面刷新恢复存储身份时不为 `true`。
- 按认证状态生命周期处理 UI:页面加载且无存储会话时 `loginStatus="idle"`、`isAuthenticated=false`;恢复存储会话时 `loginStatus="initializing"`、`isAuthenticated` 从 `false` 变 `true`;刷新后恢复存储会话时 `loginStatus="idle"`、`isAuthenticated=true`;交互登录进行中 `loginStatus="logging-in"`、`isAuthenticated=false`;交互登录刚完成 `loginStatus="success"`、`isAuthenticated=true`;登录弹窗失败/取消 `loginStatus="loginError"`、`isAuthenticated=false`。
- 登录按钮在初始化或登录中时禁用:使用 `const { login, isInitializing, isLoggingIn } = useInternetIdentity()`,并为按钮设置 `disabled={isInitializing || isLoggingIn}`。
- `login()` 和 `clear()` 是 fire-and-forget:不要 `await`,不要用本地 `useState` / `isPending` 逻辑包装;异步生命周期由 hook 的 `isLoggingIn`、`isInitializing` 等状态字段跟踪。
- 调用登录变体:普通 Internet Identity 用 `login()`;Google 用 `login({ provider: "google" })`;Microsoft 用 `login({ provider: "microsoft" })`;公司/工作区 SSO 用 `login({ ssoDomain: "acme.com" })`。所有变体都走 Internet Identity,身份、会话行为、注销行为相同,仅改变用户首先看到的登录界面。
- Google 登录无需 Google API key 或 OAuth client 设置;Microsoft 登录无需 Azure/Entra app registration,Internet Identity 持有 OAuth client,并接受个人 Microsoft 账号与工作/学校账号;Workspace SSO 让用户输入公司域名(如 `acme.com`),Internet Identity 从 `https://<domain>/.well-known/ii-openid-configuration` 发现公司 OpenID Connect provider 并登录,兼容 Okta、Entra ID 等已配置 OIDC provider。需要特定公司自己的 tenant 时用 SSO;需要无每公司配置的一键 Microsoft 按钮时用 `provider: "microsoft"`。
- 不提供 Apple 登录:Internet Identity 对 Apple 不返回 email 或 name claims,导致 attribute callback 为空。
- 所有登录变体的会话存储方式相同:`isAuthenticated`、刷新恢复会话、`clear()` 在任何变体下行为一致。
- 当后端使用 `caffeineai-authorization` 时,Google、Microsoft、SSO 登录会自动把已验证的 name/email 属性(以及 SSO domain)传给 attribute callback。
- 只在按钮的 `onClick` handler 中调用 `login()`:Internet Identity 弹窗只能在真实 click 事件分发时打开,否则会失败并报 `Signer window should not be opened outside of click handler`。
- 避免在 click 事件之外调用 `login()`:不要从 form 的 `onSubmit` 调用(`submit` 事件在 click 事件结束后触发,检查会失败);SSO domain 字段用普通 `<div>`(不是 `<form>`)和 `type="button"` 的提交按钮,在 `onClick` 中校验 domain 并直接调用 `login({ ssoDomain })`;不要从键盘 handler(如 domain input 的 Enter)或 `await` 之后调用 `login()`。
- 如果应用在调用 `login` 前验证 SSO domain,按 Internet Identity 规则镜像校验:接受至少两段 label 的正常 DNS 名(如 `acme.com`),或 loopback host `localhost` / `127.0.0.1`(可带可选 `:port`,如 `localhost:3000`);II 接受 loopback domain 用于本地测试,所以输入不能拒绝它们。
- 标准登录 UI 模式:显示醒目的 Google 和 Microsoft 按钮、普通 Internet Identity 登录按钮,以及一个要求输入 domain 的 company SSO 选项;示例中 `disabled = isInitializing || isLoggingIn`;company SSO 刻意不用 `<form>`;输入用本地 state;SSO 按钮在 `disabled` 基础上还加 `!ssoDomain.trim()`;点击时传 `login({ ssoDomain: ssoDomain.trim() })`。
- 只提供应用实际需要的登录变体:默认使用普通 `login()`,除非明确请求 Google、Microsoft 或 company SSO;当请求其中一个变体时,登录页必须显示该直接登录选项(Google 按钮、Microsoft 按钮和/或 SSO domain 输入),同时保留普通 `Sign in with Internet Identity` 按钮作为 fallback,让没有 Google/Microsoft 账号或没有注册公司域名的用户仍可登录。
- 使用 `useActor()` 创建并管理 typed backend actor:从 `@caffeineai/core-infrastructure` 导入 `useActor`,从 `declarations/backend` 导入 `createActor`,调用 `const { actor, isFetching } = useActor(createActor)`;`actor` 加载时为 `null`,之后为 typed backend actor;用 `if (!actor || isFetching) return <Loading />`;然后直接 `await actor.myBackendMethod()`。
- `useActor()` 返回 `actor: T | null` 与 `isFetching: boolean`;当身份变化(登录、注销或恢复会话)时,actor 自动用新身份重建,所有依赖 queries 被 invalidated 并 refetched。
- 视觉 QA 时 mock 后端:将 mock 放在 `src/frontend/src/mocks/backend.ts`,导出 `mockBackend`,并从 app source 传入 glob:`const mockModules = import.meta.glob("../mocks/backend.{ts,tsx,js,jsx}")`,再调用 `useActor(createActor, { mockModules })`(可包成 `useAppActor()`)。
- glob 必须写在 app 中,因为 Vite 会相对包含 `import.meta.glob` 调用的文件解析;package 看不到 app 的 `mocks/` 目录。使用 glob 而不是直接 import,可在 mock 文件不存在时保持构建通过。
- mock 仅在 `VITE_USE_MOCK=true` 时使用;否则 `useActor` 行为完全不变,并加载真实后端配置。
- 在非 React 环境:`createActorWithConfig(createActor, { mockModules })` 接受相同 option;`loadMockBackendFromModules(mockModules)` 可自行解析 mock。
适用边界 · 注意事项
- 不适用于不使用 Caffeine AI `@caffeineai/core-infrastructure` / `@icp-sdk/auth` / Internet Identity 的项目。
- 不适用于非 React 前端;React hook 用法不可用,非 React 只能使用 `createActorWithConfig` / `loadMockBackendFromModules` 等非 hook 部分。
- 如果应用不需要认证或不需要后端 actor,不要强行加入 Internet Identity 登录 UI 与 actor 管理逻辑。