@crimson-education/sdk 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +377 -0
- package/dist/core/account.d.ts +14 -0
- package/dist/core/account.js +30 -0
- package/dist/core/auth/index.d.ts +11 -0
- package/dist/core/auth/index.js +25 -0
- package/dist/core/auth/oauth-adapter.d.ts +78 -0
- package/dist/core/auth/oauth-adapter.js +341 -0
- package/dist/core/auth/pkce.d.ts +20 -0
- package/dist/core/auth/pkce.js +112 -0
- package/dist/core/auth/token-manager.d.ts +68 -0
- package/dist/core/auth/token-manager.js +294 -0
- package/dist/core/auth/token-storage.d.ts +46 -0
- package/dist/core/auth/token-storage.js +155 -0
- package/dist/core/auth/types.d.ts +148 -0
- package/dist/core/auth/types.js +15 -0
- package/dist/core/client.d.ts +84 -0
- package/dist/core/client.js +229 -0
- package/dist/core/index.d.ts +11 -0
- package/dist/core/index.js +47 -0
- package/dist/core/missionLibrary.d.ts +68 -0
- package/dist/core/missionLibrary.js +143 -0
- package/dist/core/missions.d.ts +45 -0
- package/dist/core/missions.js +140 -0
- package/dist/core/roadmap.d.ts +8 -0
- package/dist/core/roadmap.js +18 -0
- package/dist/core/studentProfile.d.ts +21 -0
- package/dist/core/studentProfile.js +41 -0
- package/dist/core/tasks.d.ts +117 -0
- package/dist/core/tasks.js +288 -0
- package/dist/core/types.d.ts +402 -0
- package/dist/core/types.js +2 -0
- package/dist/core/users.d.ts +21 -0
- package/dist/core/users.js +46 -0
- package/dist/iframe/auth-state.d.ts +7 -0
- package/dist/iframe/auth-state.js +125 -0
- package/dist/iframe/constants.d.ts +8 -0
- package/dist/iframe/constants.js +29 -0
- package/dist/iframe/index.d.ts +5 -0
- package/dist/iframe/index.js +17 -0
- package/dist/iframe/listener.d.ts +2 -0
- package/dist/iframe/listener.js +57 -0
- package/dist/iframe/types.d.ts +18 -0
- package/dist/iframe/types.js +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +22 -0
- package/dist/react/hooks/index.d.ts +10 -0
- package/dist/react/hooks/index.js +48 -0
- package/dist/react/hooks/useAccount.d.ts +13 -0
- package/dist/react/hooks/useAccount.js +39 -0
- package/dist/react/hooks/useAuthState.d.ts +2 -0
- package/dist/react/hooks/useAuthState.js +18 -0
- package/dist/react/hooks/useMissionLibrary.d.ts +31 -0
- package/dist/react/hooks/useMissionLibrary.js +183 -0
- package/dist/react/hooks/useMissions.d.ts +24 -0
- package/dist/react/hooks/useMissions.js +104 -0
- package/dist/react/hooks/useOAuth.d.ts +94 -0
- package/dist/react/hooks/useOAuth.js +211 -0
- package/dist/react/hooks/useRoadmapContext.d.ts +2 -0
- package/dist/react/hooks/useRoadmapContext.js +29 -0
- package/dist/react/hooks/useStudentProfile.d.ts +24 -0
- package/dist/react/hooks/useStudentProfile.js +65 -0
- package/dist/react/hooks/useTasks.d.ts +26 -0
- package/dist/react/hooks/useTasks.js +137 -0
- package/dist/react/hooks/useUsers.d.ts +9 -0
- package/dist/react/hooks/useUsers.js +50 -0
- package/dist/react/index.d.ts +3 -0
- package/dist/react/index.js +21 -0
- package/dist/react/provider.d.ts +16 -0
- package/dist/react/provider.js +41 -0
- package/package.json +61 -0
package/README.md
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
# @crimson/sdk 使用文档
|
|
2
|
+
|
|
3
|
+
`@crimson/sdk` 是一个用于访问 Crimson App(Roadmap 相关)后端接口的 TypeScript SDK,包含三层能力:
|
|
4
|
+
|
|
5
|
+
- **Core(框架无关)**:`CrimsonClient` + 各业务 API(missions / tasks / roadmap / users / mission library)
|
|
6
|
+
- **iframe(跨域/嵌入场景)**:通过 `postMessage` 初始化鉴权信息,并提供可订阅的鉴权状态
|
|
7
|
+
- **React(可选)**:基于 `@tanstack/react-query` 的 Provider 与 Hooks(通过 `@crimson/sdk/react` 引入)
|
|
8
|
+
|
|
9
|
+
> 说明:SDK 默认依赖运行时 `fetch`。在 Node.js 环境建议使用 Node 18+(内置 `fetch`),或自行注入/polyfill。
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 安装与构建
|
|
14
|
+
|
|
15
|
+
### 安装
|
|
16
|
+
|
|
17
|
+
如果已发布到 npm:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm i @crimson/sdk
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### 本地开发构建
|
|
24
|
+
|
|
25
|
+
在 `crimson-sdk/` 目录下:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm i
|
|
29
|
+
npm run build
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
编译产物输出到 `crimson-sdk/dist/`(测试也使用该目录)。
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 快速开始(Core)
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { createCrimsonClient } from "@crimson/sdk";
|
|
40
|
+
|
|
41
|
+
const client = createCrimsonClient({
|
|
42
|
+
apiUrl: "https://api.example.com",
|
|
43
|
+
getToken: async () => "YOUR_TOKEN",
|
|
44
|
+
// 推荐:设置客户端标识,用于 API 调用追踪
|
|
45
|
+
clientId: "my-app",
|
|
46
|
+
// 可选:默认 Bearer;如使用自定义鉴权头格式可覆盖
|
|
47
|
+
// authScheme: "crimsonauthkey",
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// 读取 roadmap context
|
|
51
|
+
const ctx = await client.roadmap.context("student-uid");
|
|
52
|
+
|
|
53
|
+
// 拉取 missions(不传 start/limit 时返回按 category 分组的旧结构)
|
|
54
|
+
const categories = await client.missions.list("student-uid");
|
|
55
|
+
|
|
56
|
+
// 拉取 missions(强制分页结构)
|
|
57
|
+
const page = await client.missions.listPaginated("student-uid", undefined, {
|
|
58
|
+
start: 0,
|
|
59
|
+
limit: 20,
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Core API 参考
|
|
66
|
+
|
|
67
|
+
### 1) CrimsonClient
|
|
68
|
+
|
|
69
|
+
入口:`createCrimsonClient(config)` / `new CrimsonClient(config)`
|
|
70
|
+
|
|
71
|
+
配置类型:`CrimsonClientConfig`
|
|
72
|
+
|
|
73
|
+
- `apiUrl: string`:后端 API 基地址(SDK 会自动去掉末尾 `/`)
|
|
74
|
+
- `getToken: () => string | Promise<string>`:获取 token 的函数
|
|
75
|
+
- `authScheme?: string`:鉴权 scheme(默认 `"Bearer"`)。SDK 会自动去掉 token 前缀 `"Bearer "`,再拼接成 `Authorization: "<scheme> <token>"`
|
|
76
|
+
- `clientId?: string`:客户端应用标识,用于 API 调用追踪(如 `"new-roadmap"`、`"capstone"`)
|
|
77
|
+
- `clientVersion?: string`:SDK 版本覆盖,默认使用 SDK 包版本
|
|
78
|
+
|
|
79
|
+
响应约定(`CrimsonClient.fetch<T>`):
|
|
80
|
+
|
|
81
|
+
- 若响应 JSON 形如 `{ data: ... }` 且 **没有** `pagination` 字段:SDK 会自动 **解包**,直接返回 `data`
|
|
82
|
+
- 若响应 JSON 形如 `{ data: ..., pagination: ... }`:SDK 返回整个对象(对应 `PaginatedResult<T>`)
|
|
83
|
+
- 若响应为空(如 204):返回 `undefined`
|
|
84
|
+
- 若 HTTP 非 2xx:抛出 `Error("Crimson SDK Error: <status> ... - <body>")`
|
|
85
|
+
|
|
86
|
+
### 2) MissionsApi(`client.missions`)
|
|
87
|
+
|
|
88
|
+
涉及接口:
|
|
89
|
+
|
|
90
|
+
- `GET /roadmap/missions`
|
|
91
|
+
- `POST /roadmap/missions`
|
|
92
|
+
- `PUT /roadmap/missions/:linkId`
|
|
93
|
+
- `DELETE /roadmap/missions/:linkId`
|
|
94
|
+
- `POST /roadmap/missions/batch`
|
|
95
|
+
- `POST /roadmap/missions/batch-delete`
|
|
96
|
+
- `POST /roadmap/missions/batch-restore`
|
|
97
|
+
|
|
98
|
+
主要方法:
|
|
99
|
+
|
|
100
|
+
- `list(userId, filters?)`
|
|
101
|
+
- 不传 `start/limit`:返回 `MissionsCategory[]`(兼容旧后端)
|
|
102
|
+
- 传入 `start/limit`:返回 `PaginatedResult<Mission>`
|
|
103
|
+
- `filters`(`MissionFilters`)支持:`status[]`、`title`、`roadmapId`、`groupBy`、`dueDateStart`、`dueDateEnd`、`start`、`limit`
|
|
104
|
+
- `listPaginated(userId, filters?, pagination?)`
|
|
105
|
+
- 始终返回 `PaginatedResult<Mission>`(并在后端仍返回旧结构时做转换)
|
|
106
|
+
- `create(data: Partial<Mission>)`
|
|
107
|
+
- `update(linkId: string, data: Partial<Mission>)`
|
|
108
|
+
- `delete(linkId: string)`
|
|
109
|
+
- `batchEdit(userId, roadmapId, missions: BatchMissionOperation[])`
|
|
110
|
+
- 单次请求内执行 add/update/delete(详见 `BatchMissionAdd/Update/Delete` 类型)
|
|
111
|
+
- `batchDelete(linkIds: string[])`
|
|
112
|
+
- `batchRestore(linkIds: string[])`
|
|
113
|
+
|
|
114
|
+
### 3) TasksApi(`client.tasks`)
|
|
115
|
+
|
|
116
|
+
涉及接口:
|
|
117
|
+
|
|
118
|
+
- `GET /roadmap/action-items`
|
|
119
|
+
- `POST /roadmap/action-items`
|
|
120
|
+
- `PUT /roadmap/action-items/:id`
|
|
121
|
+
- `DELETE /roadmap/action-items/:id`
|
|
122
|
+
- `POST /roadmap/action-items/:actionItemId/resources`
|
|
123
|
+
- `POST /roadmap/upload`
|
|
124
|
+
|
|
125
|
+
主要方法:
|
|
126
|
+
|
|
127
|
+
- `list(params, filters?)`
|
|
128
|
+
- `params` 支持:`missionId` / `missionLinkIds` / `roadmapId` / `userId`
|
|
129
|
+
- `filters`(`TaskFilters`)支持:
|
|
130
|
+
- `status[]` - 按状态过滤(如 `['PLANNED', 'DONE']`)
|
|
131
|
+
- `description` - 按描述关键词过滤
|
|
132
|
+
- `creatorId` - 按创建者 ID 过滤
|
|
133
|
+
- `dueDateStart` - 按截止日期开始过滤(ISO 格式)
|
|
134
|
+
- `dueDateEnd` - 按截止日期结束过滤(ISO 格式)
|
|
135
|
+
- `orderBy` - 排序方式:`'priority'` | `'dueDate'` | `'missionTitle'` | `'createdAt'`
|
|
136
|
+
- `start` / `limit` - 分页参数
|
|
137
|
+
- 默认返回 `Task[]`
|
|
138
|
+
- 当 **同时提供** `roadmapId` 且提供 `start/limit` 时:返回 `PaginatedResult<Task>`
|
|
139
|
+
- `listPaginated(roadmapId, userId, filters?, pagination?)`
|
|
140
|
+
- 始终返回 `PaginatedResult<Task>`(内部复用 `list`)
|
|
141
|
+
- `create(data: Partial<Task>)`
|
|
142
|
+
- 支持两种模式:
|
|
143
|
+
- Mission 关联任务:提供 `roadmapMissionId`(或 `missionId`)
|
|
144
|
+
- 独立任务(Standalone):只提供 `roadmapId`,不关联 mission
|
|
145
|
+
- `createStandalone(roadmapId: string, data)`
|
|
146
|
+
- 创建独立任务的便捷方法
|
|
147
|
+
- `update(id: string, data: Partial<Task>)`
|
|
148
|
+
- 支持更新 `resources` 字段(完整替换)
|
|
149
|
+
- `delete(id: string)`
|
|
150
|
+
- `addResources(actionItemId: string, resources: AddResourceInput | AddResourceInput[])`
|
|
151
|
+
- 向任务添加资源/附件
|
|
152
|
+
- `updateResources(taskId: string, resources: UpdateTaskResourceInput[])`
|
|
153
|
+
- 更新任务资源(支持新增、修改、删除)
|
|
154
|
+
- 包含 `id` 的资源会被更新,不包含 `id` 的会新建,不在数组中的会被删除
|
|
155
|
+
- `getUploadUrl(filename: string, contentType: string): Promise<UploadUrlResponse>`
|
|
156
|
+
- 获取 S3 预签名上传 URL
|
|
157
|
+
- 返回 `{ putUrl, url, key, bucket }`
|
|
158
|
+
- `uploadFile(file: File | Blob, filename?: string): Promise<{ url, key }>`
|
|
159
|
+
- 上传文件到 S3 的便捷方法(内部调用 `getUploadUrl` + PUT 上传)
|
|
160
|
+
|
|
161
|
+
关于 Task 结构的“归一化”:
|
|
162
|
+
|
|
163
|
+
SDK 会将后端返回的 action item 做归一化,保证至少返回以下核心字段:
|
|
164
|
+
|
|
165
|
+
- `id`
|
|
166
|
+
- `name`(优先 `name`,否则使用 `description`)
|
|
167
|
+
- `date`(优先 `date`,否则使用 `dueDate`)
|
|
168
|
+
- `roadmapMissionId`(优先 `roadmapMissionId`,否则使用 `missionId/linkId`)
|
|
169
|
+
- `userId`(优先 `userId`,否则使用 `creatorId`)
|
|
170
|
+
- `isComplete`(优先使用后端的 `isComplete`,否则由 `status === DONE` 或 `finishedAt` 推导)
|
|
171
|
+
|
|
172
|
+
因此你可能看不到后端返回的所有原始字段(它们被统一抽象到上述字段里)。
|
|
173
|
+
|
|
174
|
+
### 4) RoadmapApi(`client.roadmap`)
|
|
175
|
+
|
|
176
|
+
涉及接口:
|
|
177
|
+
|
|
178
|
+
- `GET /roadmap/context?userId=...`
|
|
179
|
+
- `POST /roadmap/context`
|
|
180
|
+
|
|
181
|
+
主要方法:
|
|
182
|
+
|
|
183
|
+
- `context(userId: string): Promise<RoadmapContext>`
|
|
184
|
+
- `createContext(userId: string): Promise<RoadmapContext>`
|
|
185
|
+
|
|
186
|
+
### 5) UsersApi(`client.users`)
|
|
187
|
+
|
|
188
|
+
涉及接口:
|
|
189
|
+
|
|
190
|
+
- `GET /roadmap/users?ids=...`
|
|
191
|
+
|
|
192
|
+
主要方法:
|
|
193
|
+
|
|
194
|
+
- `getByIds(userIds: string[]): Promise<User[]>`
|
|
195
|
+
- 根据用户 ID 列表获取用户信息
|
|
196
|
+
- 用于显示任务分配者(assignedBy)等场景
|
|
197
|
+
- 返回字段:`userId`, `firstName`, `lastName`, `email`, `profileImageId`
|
|
198
|
+
- `getById(userId: string): Promise<User | undefined>`
|
|
199
|
+
- 获取单个用户信息的便捷方法
|
|
200
|
+
|
|
201
|
+
### 6) MissionLibraryApi(`client.library`)
|
|
202
|
+
|
|
203
|
+
涉及接口:
|
|
204
|
+
|
|
205
|
+
- `GET /roadmap/library/missions`
|
|
206
|
+
- `GET /roadmap/library/tasks`
|
|
207
|
+
- `POST /roadmap/library/missions/copy`
|
|
208
|
+
- `POST /roadmap/library/missions/assign-bulk`
|
|
209
|
+
- `POST /roadmap/library/tasks/assign-bulk`
|
|
210
|
+
- `POST /roadmap/library/tasks/create-from-predefined`
|
|
211
|
+
- `GET /roadmap/missions/:id/detail`
|
|
212
|
+
|
|
213
|
+
主要方法:
|
|
214
|
+
|
|
215
|
+
- `listTemplateMissions(filters?: TemplateMissionFilters): Promise<PaginatedResult<TemplateMission>>`
|
|
216
|
+
- `listTemplateTasks(filters?: TemplateTaskFilters): Promise<PaginatedResult<TemplateTask>>`
|
|
217
|
+
- `copyTemplateMission(input: CopyTemplateMissionInput): Promise<TemplateMission[]>`
|
|
218
|
+
- `assignBulkMission(input: AssignBulkMissionInput[]): Promise<{ code: number; msg?: string }>`
|
|
219
|
+
- `assignBulkTask(input: AssignBulkTaskInput[]): Promise<{ code: number; msg?: string }>`
|
|
220
|
+
- `createFromPredefinedTasks(input: { missionId: string; predefinedTaskIds: string[] }): Promise<TemplateTask[]>`
|
|
221
|
+
- `getMissionById(missionId: string): Promise<MissionDetail | null>`
|
|
222
|
+
- 注意:当后端返回非 2xx(例如 404)时,SDK 会抛异常;建议使用 `try/catch` 处理。
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## iframe 层(嵌入/跨域场景)
|
|
227
|
+
|
|
228
|
+
入口:`@crimson/sdk`(主入口会导出 iframe 层)
|
|
229
|
+
|
|
230
|
+
典型用法:
|
|
231
|
+
|
|
232
|
+
1. 在 iframe 内调用 `setupIframeListener()` 监听父页面 `postMessage` 注入 token
|
|
233
|
+
2. 通过 `getAuthState()/subscribeToAuthState()` 或 React Hook `useAuthState()` 获取就绪状态
|
|
234
|
+
|
|
235
|
+
主要 API:
|
|
236
|
+
|
|
237
|
+
- `setupIframeListener(allowedOrigins?: string[]): CleanupFn`
|
|
238
|
+
- `allowedOrigins` 不传则使用默认白名单(也可通过 `NEXT_PUBLIC_ALLOWED_PARENTS` 覆盖)
|
|
239
|
+
- `getToken()` / `getUserId()` / `getStudentId()`
|
|
240
|
+
- `getAuthState(): AuthState` / `subscribeToAuthState(cb)`
|
|
241
|
+
- `persistStandaloneAuth(payload: ZoidProps)`
|
|
242
|
+
- 用于“非 iframe”本地调试:写入 localStorage 并触发就绪事件
|
|
243
|
+
|
|
244
|
+
常量:
|
|
245
|
+
|
|
246
|
+
- `XPROPS_READY_EVENT`
|
|
247
|
+
- `STORAGE_KEYS`
|
|
248
|
+
- `VIRTUAL_MISSION_ID`
|
|
249
|
+
|
|
250
|
+
父页面发送 INIT 消息的 payload 形状(示例):
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
window.frames[0]?.postMessage(
|
|
254
|
+
{
|
|
255
|
+
type: "INIT",
|
|
256
|
+
payload: {
|
|
257
|
+
token,
|
|
258
|
+
userId,
|
|
259
|
+
studentId,
|
|
260
|
+
user: {
|
|
261
|
+
/* 可选 */
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
"https://your-iframe-origin.example.com",
|
|
266
|
+
);
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## React 层(`@crimson/sdk/react`)
|
|
272
|
+
|
|
273
|
+
React 层通过 `peerDependencies` 声明依赖(不会强制安装):
|
|
274
|
+
|
|
275
|
+
- `react`(>=18)
|
|
276
|
+
- `@tanstack/react-query`(>=5)
|
|
277
|
+
|
|
278
|
+
### CrimsonProvider
|
|
279
|
+
|
|
280
|
+
```tsx
|
|
281
|
+
import { CrimsonProvider } from "@crimson/sdk/react";
|
|
282
|
+
|
|
283
|
+
export function App() {
|
|
284
|
+
return (
|
|
285
|
+
<CrimsonProvider
|
|
286
|
+
apiUrl="https://api.example.com"
|
|
287
|
+
clientId="my-app" // 推荐:设置客户端标识
|
|
288
|
+
>
|
|
289
|
+
<YourRoutes />
|
|
290
|
+
</CrimsonProvider>
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Props:
|
|
296
|
+
|
|
297
|
+
- `apiUrl: string`:后端 API 基地址
|
|
298
|
+
- `clientId?: string`:客户端应用标识,用于 API 调用追踪
|
|
299
|
+
- `allowedParentOrigins?: string[]`:允许的父页面 origin 白名单
|
|
300
|
+
- `queryClient?: QueryClient`:可选的自定义 QueryClient 实例
|
|
301
|
+
|
|
302
|
+
`CrimsonProvider` 会:
|
|
303
|
+
|
|
304
|
+
- 创建并注入 `CrimsonClient`(包含 clientId 配置)
|
|
305
|
+
- 自动从 iframe/xprops/localStorage 读取 token(使用 iframe 层的 `getToken()`)
|
|
306
|
+
- 初始化 `@tanstack/react-query` 的 `QueryClientProvider`
|
|
307
|
+
- 安装 `postMessage` 监听(`setupIframeListener`)
|
|
308
|
+
|
|
309
|
+
### Hooks(常用)
|
|
310
|
+
|
|
311
|
+
- `useAuthState()`:返回 `{ token, userId, studentId, ready, user? }`
|
|
312
|
+
- `useMissions(userId, enabled)`:拉取用户 missions(扁平化 Mission[])
|
|
313
|
+
- `useMissionsInfinite(userId, filters?, options?)`:分页拉取 missions
|
|
314
|
+
- `useTasks(missionId, enabled)`:拉取某 mission 的 tasks
|
|
315
|
+
- `useTasksInfinite(roadmapId, userId, filters?, options?)`:分页拉取 tasks
|
|
316
|
+
- `useRoadmapContext(userId, enabled)`:拉取 roadmap context
|
|
317
|
+
- CRUD mutation:
|
|
318
|
+
- `useCreateMission/useUpdateMission/useDeleteMission`
|
|
319
|
+
- `useCreateTask/useUpdateTask/useDeleteTask`
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## 常见问题
|
|
324
|
+
|
|
325
|
+
### 1) 为什么拿到的返回值不是 `{ data: ... }`?
|
|
326
|
+
|
|
327
|
+
SDK 会默认解包后端常见的 `{ data: ... }` 格式:大部分方法直接返回 `data` 本体。
|
|
328
|
+
|
|
329
|
+
只有当响应同时带 `pagination`(即 `{ data, pagination }`)时,才会返回完整对象。
|
|
330
|
+
|
|
331
|
+
### 2) Node 环境报 `fetch is not defined`?
|
|
332
|
+
|
|
333
|
+
请使用 Node 18+,或为运行环境提供 `fetch` polyfill(例如 `undici`)。
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## API 调用追踪
|
|
338
|
+
|
|
339
|
+
SDK 支持向后端发送客户端标识信息,用于追踪 API 调用来源。
|
|
340
|
+
|
|
341
|
+
### 发送的 Headers
|
|
342
|
+
|
|
343
|
+
当配置了 `clientId` 时,SDK 会自动在每个请求中发送以下 headers:
|
|
344
|
+
|
|
345
|
+
| Header | 说明 | 示例值 |
|
|
346
|
+
| ------------------- | -------------- | ------------------------- |
|
|
347
|
+
| `X-Client-ID` | 客户端应用标识 | `new-roadmap`, `capstone` |
|
|
348
|
+
| `X-Client-Version` | SDK 版本 | `0.2.0` |
|
|
349
|
+
| `X-Client-Platform` | 运行环境 | `browser`, `node` |
|
|
350
|
+
|
|
351
|
+
### 后端日志
|
|
352
|
+
|
|
353
|
+
后端会记录包含以下字段的结构化日志:
|
|
354
|
+
|
|
355
|
+
```json
|
|
356
|
+
{
|
|
357
|
+
"type": "api_request",
|
|
358
|
+
"request_id": "uuid",
|
|
359
|
+
"client_id": "new-roadmap",
|
|
360
|
+
"client_version": "0.2.0",
|
|
361
|
+
"client_platform": "browser",
|
|
362
|
+
"auth_mode": "bearer",
|
|
363
|
+
"user_id": "auth0-xxx",
|
|
364
|
+
"tenant": "crimsonapp",
|
|
365
|
+
"method": "GET",
|
|
366
|
+
"path": "/roadmap/missions",
|
|
367
|
+
"status": 200,
|
|
368
|
+
"duration_ms": 123,
|
|
369
|
+
"timestamp": "2024-01-01T00:00:00.000Z"
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### 最佳实践
|
|
374
|
+
|
|
375
|
+
1. **始终设置 `clientId`**:便于区分不同应用的 API 调用
|
|
376
|
+
2. **使用有意义的标识**:如 `new-roadmap`、`capstone`、`admin-dashboard`
|
|
377
|
+
3. **响应头追踪**:后端会返回 `X-Request-ID` header,可用于调试和问题排查
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CrimsonClient } from "./client";
|
|
2
|
+
import type { CurrentUserRoles } from "./types";
|
|
3
|
+
export declare class AccountApi {
|
|
4
|
+
private client;
|
|
5
|
+
constructor(client: CrimsonClient);
|
|
6
|
+
/**
|
|
7
|
+
* Get the current logged-in user's roles
|
|
8
|
+
*
|
|
9
|
+
* @returns Current user's roles including:
|
|
10
|
+
* - userId: The user's ID
|
|
11
|
+
* - roles: Array of role objects with roleId and isPrimary flag
|
|
12
|
+
*/
|
|
13
|
+
getCurrentUserRoles(): Promise<CurrentUserRoles>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.AccountApi = void 0;
|
|
13
|
+
class AccountApi {
|
|
14
|
+
constructor(client) {
|
|
15
|
+
this.client = client;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Get the current logged-in user's roles
|
|
19
|
+
*
|
|
20
|
+
* @returns Current user's roles including:
|
|
21
|
+
* - userId: The user's ID
|
|
22
|
+
* - roles: Array of role objects with roleId and isPrimary flag
|
|
23
|
+
*/
|
|
24
|
+
getCurrentUserRoles() {
|
|
25
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
26
|
+
return this.client.fetch("/api/v1/account/me/roles");
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.AccountApi = AccountApi;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth Authentication Module
|
|
3
|
+
* Provides OAuth 2.0 authentication with PKCE support
|
|
4
|
+
*/
|
|
5
|
+
export type { OAuthConfig, OAuthTokens, TokenStorage, OAuthAuthState, OAuthAuthStateListener, TokenRefreshFunction, AuthorizeOptions, CallbackParams, OAuthError, PkceState, } from "./types";
|
|
6
|
+
export { generateCodeVerifier, generateCodeChallenge, generateState, } from "./pkce";
|
|
7
|
+
export { LocalStorageTokenStorage, SessionStorageTokenStorage, MemoryTokenStorage, createDefaultTokenStorage, } from "./token-storage";
|
|
8
|
+
export { TokenManager } from "./token-manager";
|
|
9
|
+
export type { TokenManagerConfig } from "./token-manager";
|
|
10
|
+
export { OAuthAdapter, createCrimsonOAuthAdapter } from "./oauth-adapter";
|
|
11
|
+
export type { OAuthAdapterConfig } from "./oauth-adapter";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OAuth Authentication Module
|
|
4
|
+
* Provides OAuth 2.0 authentication with PKCE support
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.createCrimsonOAuthAdapter = exports.OAuthAdapter = exports.TokenManager = exports.createDefaultTokenStorage = exports.MemoryTokenStorage = exports.SessionStorageTokenStorage = exports.LocalStorageTokenStorage = exports.generateState = exports.generateCodeChallenge = exports.generateCodeVerifier = void 0;
|
|
8
|
+
// PKCE utilities
|
|
9
|
+
var pkce_1 = require("./pkce");
|
|
10
|
+
Object.defineProperty(exports, "generateCodeVerifier", { enumerable: true, get: function () { return pkce_1.generateCodeVerifier; } });
|
|
11
|
+
Object.defineProperty(exports, "generateCodeChallenge", { enumerable: true, get: function () { return pkce_1.generateCodeChallenge; } });
|
|
12
|
+
Object.defineProperty(exports, "generateState", { enumerable: true, get: function () { return pkce_1.generateState; } });
|
|
13
|
+
// Token storage implementations
|
|
14
|
+
var token_storage_1 = require("./token-storage");
|
|
15
|
+
Object.defineProperty(exports, "LocalStorageTokenStorage", { enumerable: true, get: function () { return token_storage_1.LocalStorageTokenStorage; } });
|
|
16
|
+
Object.defineProperty(exports, "SessionStorageTokenStorage", { enumerable: true, get: function () { return token_storage_1.SessionStorageTokenStorage; } });
|
|
17
|
+
Object.defineProperty(exports, "MemoryTokenStorage", { enumerable: true, get: function () { return token_storage_1.MemoryTokenStorage; } });
|
|
18
|
+
Object.defineProperty(exports, "createDefaultTokenStorage", { enumerable: true, get: function () { return token_storage_1.createDefaultTokenStorage; } });
|
|
19
|
+
// Token manager
|
|
20
|
+
var token_manager_1 = require("./token-manager");
|
|
21
|
+
Object.defineProperty(exports, "TokenManager", { enumerable: true, get: function () { return token_manager_1.TokenManager; } });
|
|
22
|
+
// OAuth adapter
|
|
23
|
+
var oauth_adapter_1 = require("./oauth-adapter");
|
|
24
|
+
Object.defineProperty(exports, "OAuthAdapter", { enumerable: true, get: function () { return oauth_adapter_1.OAuthAdapter; } });
|
|
25
|
+
Object.defineProperty(exports, "createCrimsonOAuthAdapter", { enumerable: true, get: function () { return oauth_adapter_1.createCrimsonOAuthAdapter; } });
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth Adapter
|
|
3
|
+
* Main entry point for OAuth 2.0 authentication with PKCE
|
|
4
|
+
*/
|
|
5
|
+
import { OAuthConfig, OAuthTokens, TokenStorage, OAuthAuthState, OAuthAuthStateListener, AuthorizeOptions, CallbackParams } from "./types";
|
|
6
|
+
export interface OAuthAdapterConfig extends OAuthConfig {
|
|
7
|
+
/** Token storage implementation (defaults to localStorage) */
|
|
8
|
+
storage?: TokenStorage;
|
|
9
|
+
/** Enable automatic token refresh (default: true) */
|
|
10
|
+
autoRefresh?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare class OAuthAdapter {
|
|
13
|
+
private config;
|
|
14
|
+
private tokenManager;
|
|
15
|
+
private storage;
|
|
16
|
+
private initialized;
|
|
17
|
+
constructor(config: OAuthAdapterConfig);
|
|
18
|
+
/**
|
|
19
|
+
* Initialize the OAuth adapter
|
|
20
|
+
* Loads existing tokens and sets up auto-refresh
|
|
21
|
+
*/
|
|
22
|
+
initialize(): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Get the authorization URL for OAuth flow
|
|
25
|
+
*/
|
|
26
|
+
getAuthorizeUrl(options?: AuthorizeOptions): Promise<string>;
|
|
27
|
+
/**
|
|
28
|
+
* Start the OAuth authorization flow
|
|
29
|
+
* Redirects the user to the authorization page
|
|
30
|
+
*/
|
|
31
|
+
authorize(options?: AuthorizeOptions): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Handle the OAuth callback
|
|
34
|
+
* Exchanges the authorization code for tokens
|
|
35
|
+
*/
|
|
36
|
+
handleCallback(params: CallbackParams): Promise<OAuthTokens>;
|
|
37
|
+
/**
|
|
38
|
+
* Manually refresh the access token
|
|
39
|
+
*/
|
|
40
|
+
refreshToken(): Promise<OAuthTokens | null>;
|
|
41
|
+
/**
|
|
42
|
+
* Logout - revoke tokens and clear storage
|
|
43
|
+
*/
|
|
44
|
+
logout(): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Get current access token if valid
|
|
47
|
+
*/
|
|
48
|
+
getAccessToken(): Promise<string | null>;
|
|
49
|
+
/**
|
|
50
|
+
* Get current authentication state
|
|
51
|
+
*/
|
|
52
|
+
getAuthState(): OAuthAuthState;
|
|
53
|
+
/**
|
|
54
|
+
* Subscribe to authentication state changes
|
|
55
|
+
*/
|
|
56
|
+
subscribe(listener: OAuthAuthStateListener): () => void;
|
|
57
|
+
/**
|
|
58
|
+
* Check if user is authenticated
|
|
59
|
+
*/
|
|
60
|
+
isAuthenticated(): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Clean up resources
|
|
63
|
+
*/
|
|
64
|
+
destroy(): void;
|
|
65
|
+
private exchangeCode;
|
|
66
|
+
private refreshTokenWithApi;
|
|
67
|
+
private revokeToken;
|
|
68
|
+
private parseTokenResponse;
|
|
69
|
+
private storePkceState;
|
|
70
|
+
private getPkceState;
|
|
71
|
+
private clearPkceState;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Create an OAuth adapter with default configuration for Crimson API
|
|
75
|
+
*/
|
|
76
|
+
export declare function createCrimsonOAuthAdapter(config: Omit<OAuthAdapterConfig, "authorizationEndpoint" | "tokenEndpoint" | "revocationEndpoint"> & {
|
|
77
|
+
apiUrl: string;
|
|
78
|
+
}): OAuthAdapter;
|