mcpField
E
extension-core-infrastructure

extension-core-infrastructure

github.com/caffeinelabs开发228.6k 次安装1 周前更新
代码仓库

关于 extension-core-infrastructure

Caffeine AI 项目的基础设施组件,为前端提供 Internet Identity 身份验证和后端 Actor 管理能力,面向使用该平台构建应用的开发者。它会随项目自动包含,并根据身份变化重建 Actor。

主要功能

  1. 01提供 Internet Identity 身份验证
  2. 02支持 Google、Microsoft 和工作区 SSO 登录
  3. 03管理登录状态与会话恢复
  4. 04按身份变化重建后端 Actor
  5. 05支持用于视觉 QA 的模拟后端

适用场景

  1. 01根据身份状态控制界面访问
  2. 02为应用添加多种 Internet Identity 登录入口
  3. 03在登录或退出后更新后端连接
  4. 04使用模拟后端进行视觉 QA

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/caffeinelabs/skills --skill extension-core-infrastructure

安装后在 Claude Code 中输入/extension-core-infrastructure确认已加载。

附带文件migration/v0.x.y-to-v1.x.y.mdmigration/v1.x.y-to-v2.x.y.md

详细介绍

核心基础设施

适用于 Caffeine AI 的核心基础设施扩展。

概述

此组件为所有项目提供基础设施:后端连接配置、Internet Identity 身份验证钩子和 actor 管理工具。

要求

"@caffeineai/core-infrastructure": "^2.0.0"
"@caffeineai/object-storage": "^1.1.0"
"@icp-sdk/auth": "^9.0.0"
"@icp-sdk/core": "^5.3.0"

@caffeineai/object-storage 是 core-infrastructure 的对等依赖项。每个项目都必须将其作为直接 npm 依赖项安装(构建模板已包含这两个包)。

集成

核心基础设施会自动包含在每个项目中。无需手动集成。

前端

核心基础设施前端包(@caffeineai/core-infrastructure)会自动包含在每个项目中。

应用入口点

使用 InternetIdentityProvider 和 QueryClientProvider 包裹应用:

import { InternetIdentityProvider } from "@caffeineai/core-infrastructure";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import ReactDOM from "react-dom/client";
import App from "./App";

const queryClient = new QueryClient();

ReactDOM.createRoot(document.getElementById("root")!).render(
  <QueryClientProvider client={queryClient}>
    <InternetIdentityProvider>
      <App />
    </InternetIdentityProvider>
  </QueryClientProvider>,
);

useInternetIdentity() — 身份验证钩子

提供 Internet Identity 的身份状态、登录和登出功能。

返回值
字段类型描述
identityIdentity | undefined用户的身份(登录或恢复会话后可用)
login(options?: LoginOptions) => void打开 II 弹窗。触发后即返回——不要使用 await。请参阅登录方式。
clear() => void登出并清除已存储的身份。触发后即返回。
isAuthenticatedboolean用户具有有效身份时为 true。用于控制 UI 的显示。
isInitializingbooleanAuthClient 正从 IndexedDB 加载时为 true
isLoggingInbooleanII 弹窗打开期间为 true
isLoginSuccessboolean仅在交互式登录后为 true(页面重新加载后恢复会话时不会为 true)
isLoginErrorboolean登录或初始化失败时为 true
isSessionExpiredboolean会话结束时为 true(空闲/TTL/提供方撤销),主动登出不属于此情况
loginErrorError | undefinedisLoginError 为 true 时的错误对象
身份验证状态生命周期
场景loginStatusisAuthenticated
页面加载,没有已存储的会话"idle"false
正在恢复已存储的会话"initializing"false → true
页面重新加载后已恢复已存储的会话"idle"true
交互式登录进行中"logging-in"false
交互式登录刚完成"success"true
登录弹窗失败/已取消"loginError"false
会话已过期(空闲/TTL/撤销)"expired"false
在另一个标签页中登出"idle"false

重要: isLoginSuccess 仅在通过弹窗完成交互式登录后为 true——页面重新加载时恢复已存储的身份不会使其变为 true。条件渲染时始终使用 isAuthenticated。

身份验证状态会被存储,而不是仅保存在客户端实例中:在一个标签页中登录或登出,其他标签页也会同步更新。会话过期(isSessionExpired)并非主动登出——应显示会话结束页面,而不是已登出后的落地页。

用法

使用 isAuthenticated 控制经过身份验证的 UI:

const { isAuthenticated } = useInternetIdentity();

{isAuthenticated ? <AuthenticatedApp /> : <LoginScreen />}

在初始化或登录期间禁用登录按钮:

const { login, isInitializing, isLoggingIn } = useInternetIdentity();

<button onClick={() => login()} disabled={isInitializing || isLoggingIn}>
  Sign in
</button>

login() 和 clear() 触发后即返回——钩子的状态字段(isLoggingIn、isInitializing)会跟踪异步生命周期。不要使用本地 useState/isPending 逻辑来包装它们。

登录方式:普通 II、Google、Microsoft、工作区 SSO

login() 接受一个可选的 LoginOptions 对象,用于选择用户的登录方式。所有方式都会通过 Internet Identity 完成登录,并使用相同的身份、会话行为和注销方式——它们只改变用户首先看到的界面:

login();                            // Plain Internet Identity sign-in
login({ provider: "google" });      // One-click Google sign-in (II opens Google OAuth directly)
login({ provider: "microsoft" });   // One-click Microsoft sign-in (II opens Microsoft OAuth directly)
login({ ssoDomain: "acme.com" });   // Company/workspace SSO via the domain's identity provider
  • Google:无需 Google API 密钥或 OAuth 客户端设置——Internet Identity 会处理 OAuth 流程。
  • Microsoft:无需注册 Azure/Entra 应用——Internet Identity 会管理 OAuth 客户端。支持个人 Microsoft 账户以及工作或学校账户。
  • 工作区 SSO:用户输入公司域名(例如 acme.com);Internet Identity 会从 https://<domain>/.well-known/ii-openid-configuration 发现公司的 OpenID Connect 提供商,并通过该提供商完成登录(适用于 Okta、Entra ID 以及公司配置的其他 OIDC 提供商)。如果应用需要使用特定公司的自有租户,请使用此方式;如果需要无需按公司单独设置的一键式 Microsoft 按钮,请使用 provider: "microsoft"。
  • 不提供 Apple 登录:Internet Identity 不会为 Apple 返回电子邮件或姓名声明,因此属性回调将为空。
  • 所有方式的会话存储方式相同:无论使用哪种方式,isAuthenticated、重新加载时恢复会话以及 clear() 的行为都完全相同。
  • 当后端使用 caffeineai-authorization 时,Google、Microsoft 和 SSO 登录会自动将经过验证的姓名/电子邮件属性(以及 SSO 域名)传递给属性回调——请参阅 extension-authorization 技能。

只能在按钮的 onClick 处理程序中调用 login()。 只有在真实点击事件正在触发时,才能打开 Internet Identity 弹窗;其他情况下都会失败,并显示 Signer window should not be opened outside of click handler。具体来说:

  • 切勿在表单的 onSubmit 中调用 login()——submit 事件会在点击事件结束后触发,因此检查会失败。对于 SSO 域名字段,请使用普通的 <div>(而非 <form>)以及 type="button" 的提交按钮,在其 onClick 中验证域名并直接调用 login({ ssoDomain })。
  • 切勿在键盘处理程序中(例如在域名输入框中按 Enter 时)或 await 之后调用 login()——这两种情况都会在点击事件触发过程之外运行。

如果应用在调用 login 前验证 SSO 域名,请遵循 Internet Identity 自身的规则:接受至少包含两个标签的普通 DNS 名称(例如 acme.com),或环回主机——localhost 或 127.0.0.1,可选带有 :port(例如 localhost:3000)。II 接受环回域名用于本地测试,因此输入验证不得拒绝这些域名。

标准登录界面模式——醒目的 Google 和 Microsoft 按钮、普通 II 登录,以及提示用户输入域名的“公司 SSO”选项:

function SignInOptions() {
  const { login, isInitializing, isLoggingIn } = useInternetIdentity();
  const [ssoDomain, setSsoDomain] = useState("");
  const disabled = isInitializing || isLoggingIn;

  return (
    <div>
      <button onClick={() => login({ provider: "google" })} disabled={disabled}>
        Continue with Google
      </button>
      <button onClick={() => login({ provider: "microsoft" })} disabled={disabled}>
        Continue with Microsoft
      </button>
      <button onClick={() => login()} disabled={disabled}>
        Sign in with Internet Identity
      </button>
      {/* Company SSO: deliberately not a <form> — login must run inside the
          button's click event, and form onSubmit fires after the click ends */}
      <input
        value={ssoDomain}
        onChange={(e) => setSsoDomain(e.target.value)}
        placeholder="yourcompany.com"
      />
      <button
        onClick={() => login({ ssoDomain: ssoDomain.trim() })}
        disabled={disabled || !ssoDomain.trim()}
      >
        Sign in with your company
      </button>
    </div>
  );
}

仅提供应用实际需要的登录方式:除非需要 Google、Microsoft 或公司 SSO 登录,否则默认使用普通的 login()。当需要其中某种方式时,登录页面必须显示所需的直接登录选项(Google 按钮、Microsoft 按钮和/或 SSO 域名输入框),并且保留一个普通的“使用 Internet Identity 登录”按钮作为备用选项——没有 Google 或 Microsoft 账户,或没有已注册公司域名的用户仍必须能够登录。

useActor() — 后端 Actor Hook

创建并管理一个类型化的后端 Actor 实例。用户身份变更(登录/注销)时会自动重新创建 Actor。

import { useActor } from "@caffeineai/core-infrastructure";
import { createActor } from "@/backend";

function MyComponent() {
  const { actor, isFetching } = useActor(createActor);

  // actor is null while loading, then the typed backend actor
  if (!actor || isFetching) return <Loading />;

  // Call backend methods directly
  const data = await actor.myBackendMethod();
}
返回值
字段类型描述
actorT | null类型化的后端 Actor;加载期间为 null
isFetchingboolean正在创建 Actor 时为 true

身份变更(登录、注销或恢复会话)时,会自动使用新身份重新创建 Actor,并使所有依赖查询失效后重新获取。

视觉 QA 模拟后端(VITE_USE_MOCK=true)

useActor 可以使用应用自有的模拟后端,而不是连接 canister。将模拟后端放在 src/frontend/src/mocks/backend.ts,导出 mockBackend,并从应用源代码传入 glob:

import { useActor } from "@caffeineai/core-infrastructure";
import { createActor } from "@/backend";

const mockModules = import.meta.glob("../mocks/backend.{ts,tsx,js,jsx}");

export function useAppActor() {
  return useActor(createActor, { mockModules });
}

glob 必须写在应用中,因为 Vite 会根据包含该调用的文件来解析 import.meta.glob;软件包无法访问应用的 mocks/ 目录。使用 glob 而非直接导入,可以在模拟文件不存在时仍确保构建成功。只有当 VITE_USE_MOCK=true 时才会使用模拟后端;否则,useActor 的行为与之前完全相同,并会加载真实后端配置。

在 React 外部,createActorWithConfig(createActor, { mockModules }) 接受相同的选项,而 loadMockBackendFromModules(mockModules) 会自行解析模拟后端。

评论

支持 Markdown