@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可返回string或Promise<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 的函数(与oauth、serviceKey三选一)serviceKey?: string:服务端 server-to-server 密钥(与getToken、oauth三选一)。默认以crimsonauthkeyscheme 发送,详见「认证方式详解」。⚠️ 仅限可信后端oauth?: CrimsonOAuthConfig:OAuth 2.0 配置(与getToken、serviceKey三选一)。⚠️ 后端/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/missionsPOST /roadmap/missionsPUT /roadmap/missions/:linkIdDELETE /roadmap/missions/:linkIdPOST /roadmap/missions/batchPOST /roadmap/missions/batch-deletePOST /roadmap/missions/batch-restore
主要方法:
list(userId, filters?)- 不传
start/limit:返回MissionsCategory[](兼容旧后端) - 传入
start/limit:返回PaginatedResult<Mission> filters(MissionFilters)支持:status[]、title、roadmapId、groupBy、dueDateStart、dueDateEnd、start、limit
- 不传
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类型)
- 单次请求内执行 add/update/delete(详见
batchDelete(linkIds: string[])batchRestore(linkIds: string[])
3) TasksApi(client.tasks)
涉及接口:
GET /roadmap/action-itemsGET /roadmap/action-items/creatorsPOST /roadmap/action-itemsPUT /roadmap/action-items/:idDELETE /roadmap/action-items/:idPOST /roadmap/action-items/:actionItemId/resourcesPOST /roadmap/uploadGET /roadmap/download
主要方法:
list(params, filters?)params支持:missionId/missionLinkIds/roadmapId/userIdfilters(TaskFilters)支持: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
- 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 上传)
- 上传文件到 S3 的便捷方法(内部调用
关于 Task 结构的“归一化”:
SDK 会将后端返回的 action item 做归一化,保证至少返回以下核心字段:
idname(优先name,否则使用description)date(优先date,否则使用dueDate)roadmapMissionId(优先roadmapMissionId,否则使用missionId/linkId)userId(优先userId,否则使用creatorId)isComplete(优先使用后端的isComplete,否则由status === DONE或finishedAt推导)
因此你可能看不到后端返回的所有原始字段(它们被统一抽象到上述字段里)。
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/missionsPOST /roadmap/library/missionsPUT /roadmap/library/missions/:missionIdGET /roadmap/library/tasksPOST /roadmap/library/missions/copyPOST /roadmap/library/missions/assign-bulkPOST /roadmap/library/tasks/assign-bulkPOST /roadmap/library/tasks/create-from-predefinedGET /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处理。
- 注意:当后端返回非 2xx(例如 404)时,SDK 会抛异常;建议使用
7) AccountApi(client.account)
涉及接口:
GET /api/v1/account/me/rolesGET /api/v1/account/me/studentsGET /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,lastNamenickname- 首选名称name- 完整显示名picture- 头像 URLstatus- 用户状态isMultiTenant- 是否多租户用户roles[]- 角色数组(支持多角色),每个包含roleId,roleName,isPrimarytenant- 租户信息{ id, name }
8) IndigoApi(client.indigo)
Indigo 专用 API,提供用户身份识别和学生-Tutor 关系查询功能。
涉及接口:
GET /api/v1/indigo/meGET /api/v1/indigo/student/tutorsGET /api/v1/indigo/tutor/students
注意:所有 Indigo API 需要用户拥有 INDIGO 产品订阅才能访问。
主要方法:
getMe(): Promise<IndigoMe>- 获取当前用户的 Indigo 身份信息
- 返回字段:
userId,email,firstName,lastNamerole- 用户角色:"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)的data:subjectId? / 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' }],
}); // 配学科 = 变 expert11) 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 层)
典型用法:
- 在 iframe 内调用
setupIframeListener()监听父页面postMessage注入 token - 通过
getAuthState()/subscribeToAuthState()或 React HookuseAuthState()获取就绪状态
主要 API:
setupIframeListener(allowedOrigins?: string[]): CleanupFnallowedOrigins不传则使用默认白名单(也可通过NEXT_PUBLIC_ALLOWED_PARENTS覆盖)
getToken()/getUserId()/getStudentId()getAuthState(): AuthState/subscribeToAuthState(cb)persistStandaloneAuth(payload: ZoidProps)- 用于“非 iframe”本地调试:写入 localStorage 并触发就绪事件
常量:
XPROPS_READY_EVENTSTORAGE_KEYSVIRTUAL_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-query的QueryClientProvider - 安装
postMessage监听(setupIframeListener)
Hooks(常用)
useAuthState():返回{ token, userId, studentId, ready, user? }useMissions(userId, enabled):拉取用户 missions(扁平化 Mission[])useMissionsInfinite(userId, filters?, options?):分页拉取 missionsuseTasks(missionId, enabled):拉取某 mission 的 tasksuseTasksInfinite(roadmapId, userId, filters?, options?):分页拉取 tasksuseRoadmapContext(userId, enabled):拉取 roadmap contextuseTaskCreators(roadmapId, enabled):获取任务创建者列表- CRUD mutation:
useCreateMission/useUpdateMission/useDeleteMissionuseCreateTask/useUpdateTask/useDeleteTaskuseCreateTemplateMission/useUpdateTemplateMissionuseGetDownloadUrl:获取文件下载地址
- 模板库相关:
useTemplateMissions/useTemplateMissionsInfiniteuseTemplateTasks/useTemplateTasksInfiniteuseTemplateMissionDetail(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"
}最佳实践
- 始终设置
clientId:便于区分不同应用的 API 调用 - 使用有意义的标识:如
new-roadmap、capstone、admin-dashboard - 响应头追踪:后端会返回
X-Request-IDheader,可用于调试和问题排查
