npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@crimson-education/sdk

v0.3.38

Published

Crimson SDK for accessing Crimson App APIs

Downloads

966

Readme

@crimson-education/sdk 使用文档

@crimson-education/sdk 是一个用于访问 Crimson App(Roadmap 相关)后端接口的 TypeScript SDK,包含三层能力:

  • Core(框架无关)CrimsonClient + 各业务 API(missions / tasks / roadmap / users / mission library)
  • iframe(跨域/嵌入场景):通过 postMessage 初始化鉴权信息,并提供可订阅的鉴权状态
  • React(可选):基于 @tanstack/react-query 的 Provider 与 Hooks(通过 @crimson-education/sdk/react 引入)

说明:SDK 默认依赖运行时 fetch。在 Node.js 环境建议使用 Node 18+(内置 fetch),或自行注入/polyfill。


安装与构建

安装

如果已发布到 npm:

npm i @crimson-education/sdk

本地开发构建

crimson-sdk/ 目录下:

npm i
npm run build

编译产物输出到 crimson-sdk/dist/(测试也使用该目录)。


快速开始(Core)

import { createCrimsonClient } from "@crimson-education/sdk";

const client = createCrimsonClient({
  apiUrl: "https://api.example.com",
  getToken: async () => "YOUR_TOKEN",
  // 推荐:设置客户端标识,用于 API 调用追踪
  clientId: "my-app",
  // 可选:默认 Bearer;如使用自定义鉴权头格式可覆盖
  // authScheme: "crimsonauthkey",
});

// 读取 roadmap context
const ctx = await client.roadmap.context("student-uid");

// 拉取 missions(不传 start/limit 时返回按 category 分组的旧结构)
const categories = await client.missions.list("student-uid");

// 拉取 missions(强制分页结构)
const page = await client.missions.listPaginated("student-uid", undefined, {
  start: 0,
  limit: 20,
});

认证方式详解

SDK 通过统一的 Authorization 头访问后端,支持三种认证配置(getToken / serviceKey / oauth,三选一)。下表速览,再逐一详解。

| 方式 | config 字段 | 发送的头 | 适用场景 | 状态 | | ---------------- | ------------ | -------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------- | | Bearer Token | getToken | Authorization: Bearer <token> | iframe 子应用(父应用注入 JWT)、已持有 Crimson 可接受 token 的前端/后端 | ✅ 可用 | | Service Key | serviceKey | Authorization: crimsonauthkey <key> | 可信后端 server-to-server,无终端用户登录 | ✅ 可用 | | OAuth 2.0 | oauth | Authorization: Bearer <access_token> | 第三方 OAuth SSO 登录 | ⚠️ SDK 已实现,后端 /oauth/* 端点未上线,暂不可用 |

三者互斥:同一个 client 只能配其中一个;TypeScript 会在编译期阻止你同时传两个。


方式一:Bearer Token(getToken)✅

最常用。你提供一个返回 token 字符串的函数,SDK 把它拼成 Authorization: Bearer <token> 发送。SDK 不关心 token 怎么来的 —— 只要后端认它即可(通常是 Crimson Auth0 JWT)。

import { createCrimsonClient } from "@crimson-education/sdk";

const client = createCrimsonClient({
  apiUrl: "https://api.staging.app.crimsoneducation.io",
  getToken: () => localStorage.getItem("token") ?? "", // 同步或异步均可
  clientId: "my-app",
});

const me = await client.indigo.getMe();
const missions = await client.missions.list("student-uid");

要点:

  • getToken 可返回 stringPromise<string>;SDK 会自动去掉前缀 "Bearer " 再拼接。
  • 典型来源:① iframe 父应用通过 postMessage 注入的 JWT(见下方「iframe 层」/「React 层」);② 你的应用通过 Crimson Auth Gateway 等途径换到的 Auth0 token。
  • 嵌入式(iframe)场景无需手写 getToken:用 React 层的 <CrimsonProvider>,它会自动从 xprops / localStorage 读取 token。

方式二:Service Key(serviceKey)✅ —— 服务端 server-to-server

⚠️ Service Key 是服务级(god-mode)凭证,几乎不受权限限制,只能在可信后端使用,严禁出现在浏览器/前端代码或源码仓库中。 key 必须匹配后端 CRIMSON_APP_ACCESS_KEYS 白名单中的某个值。

适用于「没有 Crimson/Auth0 用户登录」的第三方后端。SDK 以 Authorization: crimsonauthkey <key> 发送;由于没有终端用户,需用 tenantDomain 显式指定租户(此模式下 SDK 跳过基于用户的租户解析)。

import { createCrimsonClient } from "@crimson-education/sdk";

// ⚠️ 仅在受信任的后端运行(Node);key 来自密钥库,切勿下发前端
const client = createCrimsonClient({
  apiUrl: process.env.CRIMSON_API_URL,
  serviceKey: process.env.CRIMSON_SERVICE_KEY,
  tenantDomain: "app.crimsoneducation.org", // 指定目标租户(服务模式建议必填)
  clientId: "your-backend",
});

// 1) 应用级 / 目录类数据
const templates = await client.library.listTemplateMissions({ limit: 20 });

// 2) 也能取指定用户的数据(目标 userId 作为参数传入)
const ctx = await client.roadmap.context("auth0-xxxx");
const missions = await client.missions.list("auth0-xxxx");

要点:

  • 默认鉴权 scheme 为 crimsonauthkey,可用 authScheme 覆盖。
  • 防呆:浏览器环境下使用 serviceKey直接抛错serviceKey 传空值(如环境变量未设置)也会抛错。
  • 未设置 tenantDomain 时,涉及租户的请求会落到默认 crimsonapp 租户(SDK 会告警)。
  • 权限:命中白名单后后端视为 isServer绕过 per-user RBAC;数据范围由 tenantDomain + 你显式传入的 userId 决定。请按最小必要使用,并妥善保管 / 定期轮换 key。

方式三:OAuth 2.0(oauth)⚠️ 暂不可用

当前后端尚未部署 /oauth/authorize/oauth/token 等端点,此模式无法实际工作。 以下为 SDK 侧已实现的用法,待后端上线后可用。

用于第三方应用让用户「用 Crimson 账号登录」(Authorization Code + PKCE)。

const client = createCrimsonClient({
  apiUrl: "https://api.example.com",
  clientId: "my-app",
  oauth: {
    clientId: "oauth-client-id",
    redirectUri: "https://my-app.com/callback",
    scope: ["profile"],
  },
});

await client.initialize(); // 加载已存 token

// 1) 发起登录(跳转到授权页)
await client.authorize();

// 2) 回调页处理(用 URL 上的 code/state 换 token)
await client.handleOAuthCallback({ code, state });

// 3) 之后照常调用;SDK 自动带上 access_token 并按需刷新
const me = await client.indigo.getMe();

// 状态 / 登出
client.isAuthenticated();
await client.logout();

嵌入式 iframe 场景(基于方式一)

new-roadmap / indigo 等嵌入应用属于「方式一」的特例:父应用注入 token,子应用用 SDK 的 iframe 层 / React 层自动接管,无需手写 getToken。详见下方「iframe 层」与「React 层」。


Core API 参考

1) CrimsonClient

入口:createCrimsonClient(config) / new CrimsonClient(config)

配置类型:CrimsonClientConfig

  • apiUrl: string:后端 API 基地址(SDK 会自动去掉末尾 /
  • getToken: () => string | Promise<string>:获取 token 的函数(与 oauthserviceKey 三选一)
  • serviceKey?: string:服务端 server-to-server 密钥(与 getTokenoauth 三选一)。默认以 crimsonauthkey scheme 发送,详见「认证方式详解」。⚠️ 仅限可信后端
  • oauth?: CrimsonOAuthConfig:OAuth 2.0 配置(与 getTokenserviceKey 三选一)。⚠️ 后端 /oauth/* 端点未上线,暂不可用
  • tenantDomain?: string:显式 x-tenant-domain(服务模式必填,用于指定租户;提供后 SDK 跳过基于用户的租户解析)
  • authScheme?: string:鉴权 scheme(getToken 模式默认 "Bearer"serviceKey 模式默认 "crimsonauthkey")。SDK 会自动去掉 token 前缀 "Bearer ",再拼接成 Authorization: "<scheme> <token>"
  • clientId?: string:客户端应用标识,用于 API 调用追踪(如 "new-roadmap""capstone"
  • clientVersion?: string:SDK 版本覆盖,默认使用 SDK 包版本

响应约定(CrimsonClient.fetch<T>):

  • 若响应 JSON 形如 { data: ... }没有 pagination 字段:SDK 会自动 解包,直接返回 data
  • 若响应 JSON 形如 { data: ..., pagination: ... }:SDK 返回整个对象(对应 PaginatedResult<T>
  • 若响应为空(如 204):返回 undefined
  • 若 HTTP 非 2xx:抛出 Error("Crimson SDK Error: <status> ... - <body>")

2) MissionsApi(client.missions

涉及接口:

  • GET /roadmap/missions
  • POST /roadmap/missions
  • PUT /roadmap/missions/:linkId
  • DELETE /roadmap/missions/:linkId
  • POST /roadmap/missions/batch
  • POST /roadmap/missions/batch-delete
  • POST /roadmap/missions/batch-restore

主要方法:

  • list(userId, filters?)
    • 不传 start/limit:返回 MissionsCategory[](兼容旧后端)
    • 传入 start/limit:返回 PaginatedResult<Mission>
    • filtersMissionFilters)支持:status[]titleroadmapIdgroupBydueDateStartdueDateEndstartlimit
  • listPaginated(userId, filters?, pagination?)
    • 始终返回 PaginatedResult<Mission>(并在后端仍返回旧结构时做转换)
  • create(data: Partial<Mission>)
  • update(linkId: string, data: Partial<Mission>)
  • delete(linkId: string)
  • batchEdit(userId, roadmapId, missions: BatchMissionOperation[])
    • 单次请求内执行 add/update/delete(详见 BatchMissionAdd/Update/Delete 类型)
  • batchDelete(linkIds: string[])
  • batchRestore(linkIds: string[])

3) TasksApi(client.tasks

涉及接口:

  • GET /roadmap/action-items
  • GET /roadmap/action-items/creators
  • POST /roadmap/action-items
  • PUT /roadmap/action-items/:id
  • DELETE /roadmap/action-items/:id
  • POST /roadmap/action-items/:actionItemId/resources
  • POST /roadmap/upload
  • GET /roadmap/download

主要方法:

  • list(params, filters?)
    • params 支持:missionId / missionLinkIds / roadmapId / userId
    • filtersTaskFilters)支持:
      • status[] - 按状态过滤(如 ['PLANNED', 'DONE']
      • description - 按描述关键词过滤
      • creatorId - 按创建者 ID 过滤
      • dueDateStart - 按截止日期开始过滤(ISO 格式)
      • dueDateEnd - 按截止日期结束过滤(ISO 格式)
      • orderBy - 排序方式:'priority' | 'dueDate' | 'missionTitle' | 'createdAt'
      • start / limit - 分页参数
    • 默认返回 Task[]
    • 同时提供 roadmapId 且提供 start/limit 时:返回 PaginatedResult<Task>
  • listPaginated(roadmapId, userId, filters?, pagination?)
    • 始终返回 PaginatedResult<Task>(内部复用 list
  • getCreators(roadmapId)
    • 获取指定 roadmap 下所有任务的创建者列表
  • getDownloadUrl(key)
    • 获取 S3 资源的预签名下载 URL
  • create(data: Partial<Task>)
    • 支持两种模式:
      • Mission 关联任务:提供 roadmapMissionId(或 missionId
      • 独立任务(Standalone):只提供 roadmapId,不关联 mission
  • createStandalone(roadmapId: string, data)
    • 创建独立任务的便捷方法
  • update(id: string, data: Partial<Task>)
    • 支持更新 resources 字段(完整替换)
  • delete(id: string)
  • addResources(actionItemId: string, resources: AddResourceInput | AddResourceInput[])
    • 向任务添加资源/附件
  • updateResources(taskId: string, resources: UpdateTaskResourceInput[])
    • 更新任务资源(支持新增、修改、删除)
    • 包含 id 的资源会被更新,不包含 id 的会新建,不在数组中的会被删除
  • getUploadUrl(filename: string, contentType: string): Promise<UploadUrlResponse>
    • 获取 S3 预签名上传 URL
    • 返回 { putUrl, url, key, bucket }
  • uploadFile(file: File | Blob, filename?: string): Promise<{ url, key }>
    • 上传文件到 S3 的便捷方法(内部调用 getUploadUrl + PUT 上传)

关于 Task 结构的“归一化”:

SDK 会将后端返回的 action item 做归一化,保证至少返回以下核心字段:

  • id
  • name(优先 name,否则使用 description
  • date(优先 date,否则使用 dueDate
  • roadmapMissionId(优先 roadmapMissionId,否则使用 missionId/linkId
  • userId(优先 userId,否则使用 creatorId
  • isComplete(优先使用后端的 isComplete,否则由 status === DONEfinishedAt 推导)

因此你可能看不到后端返回的所有原始字段(它们被统一抽象到上述字段里)。

4) RoadmapApi(client.roadmap

涉及接口:

  • GET /roadmap/context?userId=...
  • POST /roadmap/context

主要方法:

  • context(userId: string): Promise<RoadmapContext>
  • createContext(userId: string): Promise<RoadmapContext>

5) UsersApi(client.users

涉及接口:

  • GET /roadmap/users?ids=...

主要方法:

  • getByIds(userIds: string[]): Promise<User[]>
    • 根据用户 ID 列表获取用户信息
    • 用于显示任务分配者(assignedBy)等场景
    • 返回字段:userId, firstName, lastName, email, profileImageId
  • getById(userId: string): Promise<User | undefined>
    • 获取单个用户信息的便捷方法

6) MissionLibraryApi(client.library

涉及接口:

  • GET /roadmap/library/missions
  • POST /roadmap/library/missions
  • PUT /roadmap/library/missions/:missionId
  • GET /roadmap/library/tasks
  • POST /roadmap/library/missions/copy
  • POST /roadmap/library/missions/assign-bulk
  • POST /roadmap/library/tasks/assign-bulk
  • POST /roadmap/library/tasks/create-from-predefined
  • GET /roadmap/missions/:id/detail

主要方法:

  • listTemplateMissions(filters?: TemplateMissionFilters): Promise<PaginatedResult<TemplateMission>>
  • createTemplateMission(input: TemplateMissionCreateInput): Promise<TemplateMission>
  • updateTemplateMission(id: string, raw: TemplateMission, update: TemplateMissionUpdateInput): Promise<TemplateMission>
  • listTemplateTasks(filters?: TemplateTaskFilters): Promise<PaginatedResult<TemplateTask>>
  • copyTemplateMission(input: CopyTemplateMissionInput): Promise<TemplateMission[]>
  • assignBulkMission(input: AssignBulkMissionInput[]): Promise<{ code: number; msg?: string }>
  • assignBulkTask(input: AssignBulkTaskInput[]): Promise<{ code: number; msg?: string }>
  • createFromPredefinedTasks(input: { missionId: string; predefinedTaskIds: string[] }): Promise<TemplateTask[]>
  • getMissionById(missionId: string): Promise<MissionDetail | null>
    • 注意:当后端返回非 2xx(例如 404)时,SDK 会抛异常;建议使用 try/catch 处理。

7) AccountApi(client.account

涉及接口:

  • GET /api/v1/account/me/roles
  • GET /api/v1/account/me/students
  • GET /api/v1/account/me/profile

主要方法:

  • getCurrentUserRoles(): Promise<CurrentUserRoles>
    • 获取当前用户的角色列表
    • 返回 { userId, roles: [{ roleId, isPrimary }] }
  • getMyStudents(): Promise<StudentSummary[]>
    • 获取当前用户关联的学生列表(主要用于 Staff 用户)
    • 返回学生基本信息:userId, firstName, lastName, email, profileImageUrl
  • getMyProfile(): Promise<UserProfile>
    • 获取当前用户的完整 Profile 信息
    • 返回字段:
      • userId, email, firstName, lastName
      • nickname - 首选名称
      • name - 完整显示名
      • picture - 头像 URL
      • status - 用户状态
      • isMultiTenant - 是否多租户用户
      • roles[] - 角色数组(支持多角色),每个包含 roleId, roleName, isPrimary
      • tenant - 租户信息 { id, name }

8) IndigoApi(client.indigo

Indigo 专用 API,提供用户身份识别和学生-Tutor 关系查询功能。

涉及接口:

  • GET /api/v1/indigo/me
  • GET /api/v1/indigo/student/tutors
  • GET /api/v1/indigo/tutor/students

注意:所有 Indigo API 需要用户拥有 INDIGO 产品订阅才能访问。

主要方法:

  • getMe(): Promise<IndigoMe>
    • 获取当前用户的 Indigo 身份信息
    • 返回字段:
      • userId, email, firstName, lastName
      • role - 用户角色:"student""tutor"
      • tenantId, tenantName - 主租户信息
      • isMultiTenant - 是否跨租户用户
      • relatedAccounts[] - 关联账户列表(多租户场景)
  • getStudentTutors(): Promise<IndigoTutor[]>
    • 获取当前学生的 Tutor 列表
    • 支持跨租户聚合(多租户学生可看到所有租户的 Tutor)
    • 返回字段:userId, email, firstName, lastName, profileImageUrl, contractId, contractStatus, source
  • getTutorStudents(): Promise<IndigoStudent[]>
    • 获取当前 Tutor 的学生列表
    • 支持跨租户聚合(多租户 Tutor 可看到所有租户的学生)
    • 返回字段:userId, email, firstName, lastName, profileImageUrl, contractId, contractStatus, source

示例:

const client = createCrimsonClient({
  apiUrl: "https://api.crimson.io",
  clientId: "indigo-website",
  getToken: () => localStorage.getItem("token") || "",
});

// 获取用户身份
const me = await client.indigo.getMe();
console.log(`Role: ${me.role}, Multi-tenant: ${me.isMultiTenant}`);

// 学生获取 Tutor 列表
if (me.role === "student") {
  const tutors = await client.indigo.getStudentTutors();
  tutors.forEach((t) =>
    console.log(`${t.firstName} ${t.lastName} (${t.source})`),
  );
}

// Tutor 获取学生列表(按来源分组)
if (me.role === "tutor") {
  const students = await client.indigo.getTutorStudents();
  const bySource = students.reduce(
    (acc, s) => {
      (acc[s.source] ||= []).push(s);
      return acc;
    },
    {} as Record<string, typeof students>,
  );
  console.log(`Crimson App: ${bySource.crimsonapp?.length || 0} students`);
}

9) PackageItemsApi(client.packageItems

管理 package item("项目"/学科线)。仅 service key(服务端)调用;SDK 会把你配置的 tenantDomain 作为 x-tenant-domain 头发送,请求按租户隔离——list() 要求 client 配置里必须有 tenantDomain(服务端对缺失该头的 list 请求直接 400)。写方法可选带 actingUserId(审计)。详见 Package Items API 文档

// 列表:至少传 studentUserId / mentorUserId / subjectId 之一;分页,按调用方租户隔离
const { items, total, limit, offset } = await client.packageItems.list({
  mentorUserId: 'auth0|tutor-1',
  status: 'ACTIVE',
  limit: 50,
  offset: 0,
});

await client.packageItems.get('12345'); // 取单条
await client.packageItems.update('12345', { subjectId: 'subj' }); // 改学科/导师/状态/起始/课时
await client.packageItems.pause('12345', { startDate, endDate, reason }); // 暂停
await client.packageItems.resume('12345'); // 恢复
await client.packageItems.create({ studentUserId, subjectId, status: 'ACTIVE', startDate, endDate }); // 新建
  • list(filters){ items, total, limit, offset };filters:studentUserId? / mentorUserId? / subjectId?(至少一个)、status?updatedSince?(ISO,增量同步)、limit?(默认 50,上限 200)、offset?
  • update(id, data)datasubjectId? / mentorUserId?(""=取消分配)/ status?(INACTIVE=归档)/ startDate? / endDate? / initialHours? / remainingHours?
  • create(data) 必填:studentUserId / subjectId / status / startDate / endDate

10) TutorsApi(client.tutors

创建 tutor(mentor/expert)并配学科。详见 Tutors API 文档

const tutor = await client.tutors.create({ email, firstName, lastName }); // 建 TUTOR 用户
await client.tutors.assignSubjects(tutor.userId, {
  subjects: [{ name: 'Mathematics', subjectId: 'subj' }],
}); // 配学科 = 变 expert

11) BookingsApi(client.bookings

改约某节课的时间。详见 Bookings API 文档

await client.bookings.reschedule('555', {
  name: 'Session',
  start: '2026-07-01T09:00:00Z',
  end: '2026-07-01T10:00:00Z',
  calEventTypeId: 7,
  calHostUserId: 3,
});

iframe 层(嵌入/跨域场景)

入口:@crimson-education/sdk(主入口会导出 iframe 层)

典型用法:

  1. 在 iframe 内调用 setupIframeListener() 监听父页面 postMessage 注入 token
  2. 通过 getAuthState()/subscribeToAuthState() 或 React Hook useAuthState() 获取就绪状态

主要 API:

  • setupIframeListener(allowedOrigins?: string[]): CleanupFn
    • allowedOrigins 不传则使用默认白名单(也可通过 NEXT_PUBLIC_ALLOWED_PARENTS 覆盖)
  • getToken() / getUserId() / getStudentId()
  • getAuthState(): AuthState / subscribeToAuthState(cb)
  • persistStandaloneAuth(payload: ZoidProps)
    • 用于“非 iframe”本地调试:写入 localStorage 并触发就绪事件

常量:

  • XPROPS_READY_EVENT
  • STORAGE_KEYS
  • VIRTUAL_MISSION_ID

父页面发送 INIT 消息的 payload 形状(示例):

window.frames[0]?.postMessage(
  {
    type: "INIT",
    payload: {
      token,
      userId,
      studentId,
      user: {
        /* 可选 */
      },
    },
  },
  "https://your-iframe-origin.example.com",
);

React 层(@crimson-education/sdk/react

React 层通过 peerDependencies 声明依赖(不会强制安装):

  • react(>=18)
  • @tanstack/react-query(>=5)

CrimsonProvider

import { CrimsonProvider } from "@crimson-education/sdk/react";

export function App() {
  return (
    <CrimsonProvider
      apiUrl="https://api.example.com"
      clientId="my-app" // 推荐:设置客户端标识
    >
      <YourRoutes />
    </CrimsonProvider>
  );
}

Props:

  • apiUrl: string:后端 API 基地址
  • clientId?: string:客户端应用标识,用于 API 调用追踪
  • allowedParentOrigins?: string[]:允许的父页面 origin 白名单
  • queryClient?: QueryClient:可选的自定义 QueryClient 实例

CrimsonProvider 会:

  • 创建并注入 CrimsonClient(包含 clientId 配置)
  • 自动从 iframe/xprops/localStorage 读取 token(使用 iframe 层的 getToken()
  • 初始化 @tanstack/react-queryQueryClientProvider
  • 安装 postMessage 监听(setupIframeListener

Hooks(常用)

  • useAuthState():返回 { token, userId, studentId, ready, user? }
  • useMissions(userId, enabled):拉取用户 missions(扁平化 Mission[])
  • useMissionsInfinite(userId, filters?, options?):分页拉取 missions
  • useTasks(missionId, enabled):拉取某 mission 的 tasks
  • useTasksInfinite(roadmapId, userId, filters?, options?):分页拉取 tasks
  • useRoadmapContext(userId, enabled):拉取 roadmap context
  • useTaskCreators(roadmapId, enabled):获取任务创建者列表
  • CRUD mutation:
    • useCreateMission/useUpdateMission/useDeleteMission
    • useCreateTask/useUpdateTask/useDeleteTask
    • useCreateTemplateMission/useUpdateTemplateMission
    • useGetDownloadUrl:获取文件下载地址
  • 模板库相关:
    • useTemplateMissions/useTemplateMissionsInfinite
    • useTemplateTasks/useTemplateTasksInfinite
    • useTemplateMissionDetail(missionId)
  • Indigo 身份相关:
    • useIndigoMe(enabled?) - 获取当前用户的 Indigo 身份
    • useStudentTutors(enabled?) - 获取学生的 Tutor 列表
    • useTutorStudents(enabled?) - 获取 Tutor 的学生列表

Indigo Hooks 示例

import {
  useIndigoMe,
  useStudentTutors,
  useTutorStudents,
} from "@crimson-education/sdk/react";

function IndigoProfile() {
  const { data: me, isLoading: isMeLoading } = useIndigoMe();
  // 仅当用户是 student 时才请求 tutors
  const { data: tutors } = useStudentTutors(me?.role === "student");
  // 仅当用户是 tutor 时才请求 students
  const { data: students } = useTutorStudents(me?.role === "tutor");

  if (isMeLoading) return <Spinner />;

  return (
    <div>
      <h1>
        {me?.firstName} {me?.lastName}
      </h1>
      <p>Role: {me?.role}</p>

      {me?.role === "student" && tutors && (
        <section>
          <h2>My Tutors ({tutors.length})</h2>
          <ul>
            {tutors.map((t) => (
              <li key={t.contractId}>
                {t.firstName} {t.lastName}
              </li>
            ))}
          </ul>
        </section>
      )}

      {me?.role === "tutor" && students && (
        <section>
          <h2>My Students ({students.length})</h2>
          <ul>
            {students.map((s) => (
              <li key={s.contractId}>
                {s.firstName} {s.lastName} ({s.source})
              </li>
            ))}
          </ul>
        </section>
      )}
    </div>
  );
}

常见问题

1) 为什么拿到的返回值不是 { data: ... }

SDK 会默认解包后端常见的 { data: ... } 格式:大部分方法直接返回 data 本体。

只有当响应同时带 pagination(即 { data, pagination })时,才会返回完整对象。

2) Node 环境报 fetch is not defined

请使用 Node 18+,或为运行环境提供 fetch polyfill(例如 undici)。


API 调用追踪

SDK 支持向后端发送客户端标识信息,用于追踪 API 调用来源。

发送的 Headers

当配置了 clientId 时,SDK 会自动在每个请求中发送以下 headers:

| Header | 说明 | 示例值 | | ------------------- | -------------- | ------------------------- | | X-Client-ID | 客户端应用标识 | new-roadmap, capstone | | X-Client-Version | SDK 版本 | 0.3.0 | | X-Client-Platform | 运行环境 | browser, node |

后端日志

后端会记录包含以下字段的结构化日志:

{
  "type": "api_request",
  "request_id": "uuid",
  "client_id": "new-roadmap",
  "client_version": "0.3.0",
  "client_platform": "browser",
  "auth_mode": "bearer",
  "user_id": "auth0-xxx",
  "tenant": "crimsonapp",
  "method": "GET",
  "path": "/roadmap/missions",
  "status": 200,
  "duration_ms": 123,
  "timestamp": "2024-01-01T00:00:00.000Z"
}

最佳实践

  1. 始终设置 clientId:便于区分不同应用的 API 调用
  2. 使用有意义的标识:如 new-roadmapcapstoneadmin-dashboard
  3. 响应头追踪:后端会返回 X-Request-ID header,可用于调试和问题排查