@choiceform/shared-auth 0.1.1 → 0.1.3
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 +51 -0
- package/dist/components/auth-sync.d.ts +17 -1
- package/dist/components/auth-sync.d.ts.map +1 -1
- package/dist/components/auth-sync.js +309 -21
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +1 -1
- package/dist/lib/auth-client.d.ts +8 -0
- package/dist/lib/auth-client.d.ts.map +1 -1
- package/dist/lib/auth-client.js +29 -1
- package/dist/store/actions.d.ts +9 -1
- package/dist/store/actions.d.ts.map +1 -1
- package/dist/store/actions.js +31 -11
- package/dist/types.d.ts +50 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
- ✅ 预配置的 API 客户端(自动添加认证头)
|
|
15
15
|
- ✅ 用户管理器工具
|
|
16
16
|
- ✅ 响应式状态管理(基于 Legend State)
|
|
17
|
+
- ✅ 自动伴生团队设置(登录时自动获取并设置活动组织和团队)
|
|
17
18
|
|
|
18
19
|
## 安装
|
|
19
20
|
|
|
@@ -32,6 +33,15 @@ VITE_AUTH_API_URL=http://localhost:4320
|
|
|
32
33
|
|
|
33
34
|
**注意:** 如果你的项目仍在使用 `VITE_CORE_AI_API_URL`,代码会自动向后兼容,但建议迁移到新的 `VITE_AUTH_API_URL`。
|
|
34
35
|
|
|
36
|
+
### 伴生团队功能
|
|
37
|
+
|
|
38
|
+
`AuthSync` 组件会在用户登录成功后自动:
|
|
39
|
+
1. 从 OneAuth API 获取用户的组织信息
|
|
40
|
+
2. 设置活动组织
|
|
41
|
+
3. 设置活动团队(如果存在)
|
|
42
|
+
|
|
43
|
+
此功能需要 `VITE_AUTH_API_URL` 环境变量已配置。如果未配置,功能会静默跳过,不影响登录流程。
|
|
44
|
+
|
|
35
45
|
## 快速开始
|
|
36
46
|
|
|
37
47
|
### 1. 初始化认证系统
|
|
@@ -155,6 +165,21 @@ function LoginPage() {
|
|
|
155
165
|
- `apiClient`: 预配置的 API 客户端(包含 get/post/put/delete 方法)
|
|
156
166
|
- `userManager`: 用户管理器(包含 getUser/getUserId 方法)
|
|
157
167
|
|
|
168
|
+
### `AuthSync`
|
|
169
|
+
|
|
170
|
+
Better Auth 认证状态同步组件。
|
|
171
|
+
|
|
172
|
+
**功能:**
|
|
173
|
+
- 同步 better-auth 的 session 状态到应用 store
|
|
174
|
+
- 在用户登录成功后自动设置伴生团队(活动组织和团队)
|
|
175
|
+
- 使用响应式状态监听,确保状态同步的实时性
|
|
176
|
+
|
|
177
|
+
**Props:**
|
|
178
|
+
|
|
179
|
+
- `auth`: AuthInstance
|
|
180
|
+
|
|
181
|
+
**注意:** 此组件需要在应用的根组件中渲染,通常与 `ProtectedRoute` 一起使用。
|
|
182
|
+
|
|
158
183
|
### `ProtectedRoute`
|
|
159
184
|
|
|
160
185
|
路由保护组件。
|
|
@@ -447,6 +472,32 @@ A: `apiClient` 会自动处理 401 响应,当检测到未授权时会调用 `h
|
|
|
447
472
|
|
|
448
473
|
A: 可以使用 `createAuth` 替代 `initAuth`,它提供更多配置选项,包括自定义回调 URL 构建函数等。
|
|
449
474
|
|
|
475
|
+
### Q: 伴生团队功能是什么?
|
|
476
|
+
|
|
477
|
+
A: 在用户登录成功后,`AuthSync` 组件会自动:
|
|
478
|
+
- 从 OneAuth API (`/v1/organizations/me`) 获取用户的组织信息
|
|
479
|
+
- 调用 better-auth API 设置活动组织 (`/v1/auth/organization/set-active`)
|
|
480
|
+
- 调用 better-auth API 设置活动团队 (`/v1/auth/organization/set-active-team`)
|
|
481
|
+
|
|
482
|
+
这确保了用户在登录后拥有正确的权限上下文。如果此功能失败,不会影响登录流程,只会在控制台记录错误信息。
|
|
483
|
+
|
|
484
|
+
### Q: 如何禁用伴生团队功能?
|
|
485
|
+
|
|
486
|
+
A: 不配置 `VITE_AUTH_API_URL` 环境变量即可。功能会静默跳过。
|
|
487
|
+
|
|
488
|
+
## 更新日志
|
|
489
|
+
|
|
490
|
+
### v0.1.2
|
|
491
|
+
|
|
492
|
+
- ✨ 新增:登录时自动获取并设置伴生团队功能
|
|
493
|
+
- 🔧 改进:优化错误处理和边缘情况处理
|
|
494
|
+
- 📝 改进:完善代码文档和注释
|
|
495
|
+
- 🐛 修复:修复 token 编码问题,确保 API 调用正常工作
|
|
496
|
+
|
|
497
|
+
### v0.1.1
|
|
498
|
+
|
|
499
|
+
- 初始版本
|
|
500
|
+
|
|
450
501
|
## 许可证
|
|
451
502
|
|
|
452
503
|
MIT
|
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
import type { AuthInstance } from "../core";
|
|
2
2
|
/**
|
|
3
3
|
* Better Auth 认证状态同步组件
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
|
+
* 功能说明:
|
|
6
|
+
* - 使用 better-auth 的 useSession hook 同步认证状态到 Legend State
|
|
7
|
+
* - 监听 authStore 状态,在用户登录成功后自动设置组织/团队上下文
|
|
8
|
+
* - 确保登录流程的完整性和权限上下文的正确性
|
|
9
|
+
*
|
|
10
|
+
* 技术实现:
|
|
11
|
+
* - 使用 Legend State 的 use$ hook 响应式监听状态变化
|
|
12
|
+
* - 使用 useRef 确保组织/团队设置只执行一次
|
|
13
|
+
* - 使用 useCallback 缓存映射函数,避免不必要的重渲染
|
|
14
|
+
* - 分离 session 同步和团队设置的逻辑,保持代码清晰
|
|
15
|
+
*
|
|
16
|
+
* 边缘情况处理:
|
|
17
|
+
* - Session 加载中:设置 loading 状态
|
|
18
|
+
* - Session 错误:清空用户信息,重置团队设置状态
|
|
19
|
+
* - 无 Session 但有用户:保持用户信息(可能从 token 加载)
|
|
20
|
+
* - 用户登出:重置团队设置状态,允许下次登录时重新设置
|
|
5
21
|
*/
|
|
6
22
|
export declare function AuthSync({ auth }: {
|
|
7
23
|
auth: AuthInstance;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-sync.d.ts","sourceRoot":"","sources":["../../src/components/auth-sync.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth-sync.d.ts","sourceRoot":"","sources":["../../src/components/auth-sync.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAuS3C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,QA0FxD"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { useEffect } from "react";
|
|
1
|
+
import { useEffect, useRef, useCallback } from "react";
|
|
2
|
+
import { use$ } from "@legendapp/state/react";
|
|
2
3
|
/**
|
|
3
4
|
* 将日期转换为 ISO 字符串
|
|
4
5
|
*/
|
|
@@ -10,7 +11,7 @@ function toISOString(date) {
|
|
|
10
11
|
/**
|
|
11
12
|
* 将 Better Auth session 用户数据映射为 SessionUser
|
|
12
13
|
*/
|
|
13
|
-
function mapSessionUserToSessionUser(sessionUser,
|
|
14
|
+
function mapSessionUserToSessionUser(sessionUser, sessionData) {
|
|
14
15
|
return {
|
|
15
16
|
banExpires: sessionUser.banExpires,
|
|
16
17
|
banReason: sessionUser.banReason,
|
|
@@ -20,41 +21,328 @@ function mapSessionUserToSessionUser(sessionUser, sessionCreatedAt) {
|
|
|
20
21
|
emailVerified: sessionUser.emailVerified,
|
|
21
22
|
id: sessionUser.id,
|
|
22
23
|
image: sessionUser.image || undefined,
|
|
23
|
-
lastLoginAt: toISOString(
|
|
24
|
+
lastLoginAt: toISOString(sessionData?.createdAt),
|
|
24
25
|
name: sessionUser.name,
|
|
25
26
|
role: sessionUser.role,
|
|
26
27
|
updatedAt: toISOString(sessionUser.updatedAt) ?? "",
|
|
28
|
+
activeOrganizationId: sessionData?.activeOrganizationId,
|
|
29
|
+
activeTeamId: sessionData?.activeTeamId,
|
|
27
30
|
};
|
|
28
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* 检查是否为开发环境
|
|
34
|
+
*/
|
|
35
|
+
const isDev = import.meta.env?.DEV ?? false;
|
|
36
|
+
/**
|
|
37
|
+
* 安全获取环境变量
|
|
38
|
+
*/
|
|
39
|
+
function getEnvVar(key) {
|
|
40
|
+
return import.meta.env?.[key];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 解析 API 错误响应
|
|
44
|
+
*/
|
|
45
|
+
async function parseErrorResponse(response) {
|
|
46
|
+
try {
|
|
47
|
+
const text = await response.text();
|
|
48
|
+
if (text) {
|
|
49
|
+
try {
|
|
50
|
+
const json = JSON.parse(text);
|
|
51
|
+
if (typeof json === "object" && json !== null) {
|
|
52
|
+
const data = json;
|
|
53
|
+
if (typeof data.error === "object" && data.error !== null) {
|
|
54
|
+
const errorObj = data.error;
|
|
55
|
+
if (typeof errorObj.message === "string") {
|
|
56
|
+
return errorObj.message;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else if (typeof data.message === "string") {
|
|
60
|
+
return data.message;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// 如果不是 JSON,返回原始文本(限制长度)
|
|
66
|
+
return text.length < 200 ? text : `${text.substring(0, 200)}...`;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// 忽略解析错误
|
|
72
|
+
}
|
|
73
|
+
return `HTTP ${response.status}: ${response.statusText}`;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* 获取或创建伴生团队
|
|
77
|
+
*
|
|
78
|
+
* 功能说明:
|
|
79
|
+
* - 在用户登录成功后自动获取组织信息
|
|
80
|
+
* - 设置活动组织和团队,确保用户拥有正确的权限上下文
|
|
81
|
+
* - 使用 fetch API 直接调用 oneauth 和 better-auth 的 API
|
|
82
|
+
* - 所有错误都会被捕获,不会影响登录流程
|
|
83
|
+
*
|
|
84
|
+
* 边缘情况处理:
|
|
85
|
+
* - 环境变量未配置:静默跳过
|
|
86
|
+
* - Token 缺失:静默跳过
|
|
87
|
+
* - 网络错误:记录错误但不抛出
|
|
88
|
+
* - API 返回错误:记录详细错误信息
|
|
89
|
+
* - 无效响应数据:记录错误但不影响登录
|
|
90
|
+
*/
|
|
91
|
+
async function setupCompanionTeam(auth, token, refetchSession) {
|
|
92
|
+
try {
|
|
93
|
+
// 1. 验证环境变量配置
|
|
94
|
+
const oneAuthBaseUrl = getEnvVar("VITE_AUTH_API_URL") || getEnvVar("VITE_CORE_AI_API_URL");
|
|
95
|
+
if (!oneAuthBaseUrl) {
|
|
96
|
+
if (isDev) {
|
|
97
|
+
console.warn("[AuthSync] VITE_AUTH_API_URL is not configured, skipping companion team setup");
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// 2. 验证 token
|
|
102
|
+
if (!token || typeof token !== "string" || token.trim().length === 0) {
|
|
103
|
+
if (isDev) {
|
|
104
|
+
console.warn("[AuthSync] Token is missing or invalid, skipping companion team setup");
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// 3. 获取 token(优先使用 oneauth_token,如果没有则使用传入的 token)
|
|
109
|
+
const oneAuthTokenFromStorage = localStorage.getItem("oneauth_token");
|
|
110
|
+
const oneAuthToken = oneAuthTokenFromStorage ?? token;
|
|
111
|
+
if (!oneAuthToken || oneAuthToken.trim().length === 0) {
|
|
112
|
+
if (isDev) {
|
|
113
|
+
console.warn("[AuthSync] No valid token available, skipping companion team setup");
|
|
114
|
+
}
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// 4. Token 编码(与 fetchSessionWithToken 保持一致)
|
|
118
|
+
const encodedToken = encodeURIComponent(oneAuthToken);
|
|
119
|
+
// 5. 获取组织信息
|
|
120
|
+
const myOrganizationUrl = `${oneAuthBaseUrl}/v1/organizations/me`;
|
|
121
|
+
if (isDev) {
|
|
122
|
+
console.log("[AuthSync] Fetching organization from:", myOrganizationUrl);
|
|
123
|
+
}
|
|
124
|
+
const orgResponse = await fetch(myOrganizationUrl, {
|
|
125
|
+
headers: {
|
|
126
|
+
Authorization: `Bearer ${encodedToken}`,
|
|
127
|
+
"Content-Type": "application/json",
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
if (!orgResponse.ok) {
|
|
131
|
+
const errorMessage = await parseErrorResponse(orgResponse);
|
|
132
|
+
throw new Error(`Failed to fetch organization: ${errorMessage}`);
|
|
133
|
+
}
|
|
134
|
+
// 6. 解析组织数据
|
|
135
|
+
let orgResponseData;
|
|
136
|
+
try {
|
|
137
|
+
orgResponseData = await orgResponse.json();
|
|
138
|
+
}
|
|
139
|
+
catch (parseError) {
|
|
140
|
+
throw new Error(`Failed to parse organization response: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
|
|
141
|
+
}
|
|
142
|
+
// 7. 提取组织对象(支持 {data: {...}} 或直接是组织对象)
|
|
143
|
+
const organization = orgResponseData?.data ||
|
|
144
|
+
orgResponseData;
|
|
145
|
+
// 8. 验证组织数据
|
|
146
|
+
if (!organization || typeof organization !== "object" || !organization.id || typeof organization.id !== "string") {
|
|
147
|
+
throw new Error(`Invalid organization data received: missing or invalid organization ID`);
|
|
148
|
+
}
|
|
149
|
+
// 9. 获取 auth baseURL
|
|
150
|
+
const authBaseURL = getEnvVar("VITE_AUTH_API_URL") ||
|
|
151
|
+
getEnvVar("VITE_CORE_AI_API_URL") ||
|
|
152
|
+
"http://localhost:4320";
|
|
153
|
+
// 10. 设置活动组织
|
|
154
|
+
const setActiveOrgUrl = `${authBaseURL}/v1/auth/organization/set-active`;
|
|
155
|
+
if (isDev) {
|
|
156
|
+
console.log("[AuthSync] Setting active organization:", { organizationId: organization.id });
|
|
157
|
+
}
|
|
158
|
+
const setActiveOrgResponse = await fetch(setActiveOrgUrl, {
|
|
159
|
+
method: "POST",
|
|
160
|
+
headers: {
|
|
161
|
+
Authorization: `Bearer ${encodedToken}`,
|
|
162
|
+
"Content-Type": "application/json",
|
|
163
|
+
},
|
|
164
|
+
body: JSON.stringify({ organizationId: organization.id }),
|
|
165
|
+
});
|
|
166
|
+
if (!setActiveOrgResponse.ok) {
|
|
167
|
+
const errorMessage = await parseErrorResponse(setActiveOrgResponse);
|
|
168
|
+
throw new Error(`Failed to set active organization: ${errorMessage}`);
|
|
169
|
+
}
|
|
170
|
+
// 11. 设置活动团队(如果有团队)
|
|
171
|
+
if (organization.teams && Array.isArray(organization.teams) && organization.teams.length > 0) {
|
|
172
|
+
const firstTeam = organization.teams[0];
|
|
173
|
+
if (firstTeam && firstTeam.id && typeof firstTeam.id === "string") {
|
|
174
|
+
const setActiveTeamUrl = `${authBaseURL}/v1/auth/organization/set-active-team`;
|
|
175
|
+
if (isDev) {
|
|
176
|
+
console.log("[AuthSync] Setting active team:", { teamId: firstTeam.id });
|
|
177
|
+
}
|
|
178
|
+
const setActiveTeamResponse = await fetch(setActiveTeamUrl, {
|
|
179
|
+
method: "POST",
|
|
180
|
+
headers: {
|
|
181
|
+
Authorization: `Bearer ${encodedToken}`,
|
|
182
|
+
"Content-Type": "application/json",
|
|
183
|
+
},
|
|
184
|
+
body: JSON.stringify({ teamId: firstTeam.id }),
|
|
185
|
+
});
|
|
186
|
+
if (!setActiveTeamResponse.ok) {
|
|
187
|
+
const errorMessage = await parseErrorResponse(setActiveTeamResponse);
|
|
188
|
+
throw new Error(`Failed to set active team: ${errorMessage}`);
|
|
189
|
+
}
|
|
190
|
+
if (isDev) {
|
|
191
|
+
console.log("[AuthSync] Successfully set active team");
|
|
192
|
+
}
|
|
193
|
+
// 刷新 session 并更新 authStore
|
|
194
|
+
if (refetchSession) {
|
|
195
|
+
refetchSession();
|
|
196
|
+
}
|
|
197
|
+
// 使用 fetch 直接调用 get-session 获取最新数据
|
|
198
|
+
try {
|
|
199
|
+
const getSessionUrl = `${authBaseURL}/v1/auth/get-session`;
|
|
200
|
+
const sessionResponse = await fetch(getSessionUrl, {
|
|
201
|
+
headers: {
|
|
202
|
+
Authorization: `Bearer ${encodedToken}`,
|
|
203
|
+
"Content-Type": "application/json",
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
if (sessionResponse.ok) {
|
|
207
|
+
const sessionData = await sessionResponse.json();
|
|
208
|
+
const data = sessionData;
|
|
209
|
+
if (data.user && data.session) {
|
|
210
|
+
const currentUser = auth.authStore.user.get();
|
|
211
|
+
if (currentUser) {
|
|
212
|
+
auth.authStore.user.set({
|
|
213
|
+
...currentUser,
|
|
214
|
+
activeOrganizationId: data.session.activeOrganizationId,
|
|
215
|
+
activeTeamId: data.session.activeTeamId,
|
|
216
|
+
});
|
|
217
|
+
if (isDev) {
|
|
218
|
+
console.log("[AuthSync] Updated authStore with activeOrganizationId and activeTeamId");
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
if (isDev) {
|
|
226
|
+
console.error("[AuthSync] Failed to refresh session:", err);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
else if (isDev) {
|
|
231
|
+
console.warn("[AuthSync] First team has invalid ID, skipping team setup");
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
else if (isDev) {
|
|
235
|
+
console.log("[AuthSync] No teams found in organization");
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
// 记录错误但不抛出,避免影响登录流程
|
|
240
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
241
|
+
console.error("[AuthSync] Failed to setup companion team:", errorMessage);
|
|
242
|
+
// 在开发环境下输出更详细的错误信息
|
|
243
|
+
if (isDev && error instanceof Error) {
|
|
244
|
+
console.error("[AuthSync] Error details:", {
|
|
245
|
+
message: error.message,
|
|
246
|
+
stack: error.stack,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
29
251
|
/**
|
|
30
252
|
* Better Auth 认证状态同步组件
|
|
31
|
-
*
|
|
253
|
+
*
|
|
254
|
+
* 功能说明:
|
|
255
|
+
* - 使用 better-auth 的 useSession hook 同步认证状态到 Legend State
|
|
256
|
+
* - 监听 authStore 状态,在用户登录成功后自动设置组织/团队上下文
|
|
257
|
+
* - 确保登录流程的完整性和权限上下文的正确性
|
|
258
|
+
*
|
|
259
|
+
* 技术实现:
|
|
260
|
+
* - 使用 Legend State 的 use$ hook 响应式监听状态变化
|
|
261
|
+
* - 使用 useRef 确保组织/团队设置只执行一次
|
|
262
|
+
* - 使用 useCallback 缓存映射函数,避免不必要的重渲染
|
|
263
|
+
* - 分离 session 同步和团队设置的逻辑,保持代码清晰
|
|
264
|
+
*
|
|
265
|
+
* 边缘情况处理:
|
|
266
|
+
* - Session 加载中:设置 loading 状态
|
|
267
|
+
* - Session 错误:清空用户信息,重置团队设置状态
|
|
268
|
+
* - 无 Session 但有用户:保持用户信息(可能从 token 加载)
|
|
269
|
+
* - 用户登出:重置团队设置状态,允许下次登录时重新设置
|
|
32
270
|
*/
|
|
33
271
|
export function AuthSync({ auth }) {
|
|
34
|
-
const { authClient, authActions } = auth;
|
|
35
|
-
|
|
272
|
+
const { authClient, authActions, tokenStorage, authStore } = auth;
|
|
273
|
+
// 监听 authStore 中的用户状态(响应式)
|
|
274
|
+
const user = use$(authStore.user);
|
|
275
|
+
const isAuthenticated = use$(authStore.isAuthenticated);
|
|
276
|
+
const isLoaded = use$(authStore.isLoaded);
|
|
277
|
+
// 监听 better-auth 的 session(用于同步到 store)
|
|
278
|
+
const sessionResult = authClient.useSession();
|
|
279
|
+
const { data: session, isPending, error, refetch } = sessionResult || {
|
|
280
|
+
data: null,
|
|
281
|
+
isPending: false,
|
|
282
|
+
error: null,
|
|
283
|
+
refetch: undefined,
|
|
284
|
+
};
|
|
285
|
+
// 使用 ref 确保组织/团队设置只执行一次
|
|
286
|
+
const teamSetupRef = useRef(false);
|
|
287
|
+
// 使用 useCallback 缓存 session 映射逻辑,避免不必要的重计算
|
|
288
|
+
const handleSessionUpdate = useCallback((sessionData) => {
|
|
289
|
+
if (sessionData?.user) {
|
|
290
|
+
try {
|
|
291
|
+
const mappedUser = mapSessionUserToSessionUser(sessionData.user, sessionData.session);
|
|
292
|
+
authActions.initialize(mappedUser, true);
|
|
293
|
+
}
|
|
294
|
+
catch (mappingError) {
|
|
295
|
+
console.error("[AuthSync] Failed to map session user:", mappingError);
|
|
296
|
+
// 映射失败时不清空用户信息,可能已有有效的用户数据
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}, [authActions]);
|
|
300
|
+
// Effect 1: 同步 better-auth session 到 store
|
|
36
301
|
useEffect(() => {
|
|
37
|
-
//
|
|
302
|
+
// 加载中状态
|
|
38
303
|
if (isPending) {
|
|
39
304
|
authActions.setLoading(true);
|
|
305
|
+
return;
|
|
40
306
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
307
|
+
authActions.setLoading(false);
|
|
308
|
+
// 错误处理
|
|
309
|
+
if (error) {
|
|
310
|
+
const errorMessage = error instanceof Error ? error.message : typeof error === "string" ? error : "Authentication error";
|
|
311
|
+
authActions.setError(errorMessage);
|
|
312
|
+
authActions.initialize(null, true);
|
|
313
|
+
teamSetupRef.current = false;
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
// Session 存在:映射用户数据并更新 store
|
|
317
|
+
if (session) {
|
|
318
|
+
handleSessionUpdate(session);
|
|
319
|
+
}
|
|
320
|
+
// 注意:没有 session 时不主动清空用户信息,因为可能已经从 token 加载了
|
|
321
|
+
}, [session, isPending, error, authActions, handleSessionUpdate]);
|
|
322
|
+
// Effect 2: 监听 authStore 状态,设置伴生团队
|
|
323
|
+
useEffect(() => {
|
|
324
|
+
// 条件检查:只有在用户已认证且已加载完成时才设置伴生团队
|
|
325
|
+
if (isLoaded && isAuthenticated && user?.id && !teamSetupRef.current) {
|
|
326
|
+
teamSetupRef.current = true;
|
|
327
|
+
const token = tokenStorage.get();
|
|
328
|
+
// 验证 token 存在
|
|
329
|
+
if (token && typeof token === "string" && token.trim().length > 0) {
|
|
330
|
+
setupCompanionTeam(auth, token, refetch).catch((error) => {
|
|
331
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
332
|
+
console.error("[AuthSync] Failed to setup companion team:", errorMessage);
|
|
333
|
+
// 失败后重置 ref,允许在特定条件下重试(如 token 更新后)
|
|
334
|
+
teamSetupRef.current = false;
|
|
335
|
+
});
|
|
51
336
|
}
|
|
52
|
-
else {
|
|
53
|
-
|
|
54
|
-
authActions.initialize(null, true);
|
|
337
|
+
else if (isDev) {
|
|
338
|
+
console.warn("[AuthSync] Token not found or invalid, cannot setup companion team");
|
|
55
339
|
}
|
|
56
340
|
}
|
|
57
|
-
|
|
341
|
+
// 用户登出:重置 ref,允许下次登录时重新设置
|
|
342
|
+
if (isLoaded && !isAuthenticated) {
|
|
343
|
+
teamSetupRef.current = false;
|
|
344
|
+
}
|
|
345
|
+
}, [user, isAuthenticated, isLoaded, auth, tokenStorage, refetch]);
|
|
58
346
|
// 这是一个隐形组件,不渲染任何内容
|
|
59
347
|
return null;
|
|
60
348
|
}
|
package/dist/core.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAE1C;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
|
package/dist/core.js
CHANGED
|
@@ -7,7 +7,7 @@ import { createBoundAuthUtils } from "./store/utils";
|
|
|
7
7
|
* 创建认证系统实例
|
|
8
8
|
*/
|
|
9
9
|
export function createAuth(config) {
|
|
10
|
-
// 创建 auth client
|
|
10
|
+
// 创建 auth client (传递 tokenStorageKey 以支持 bearer token 认证)
|
|
11
11
|
const authClient = createAuthClientFromConfig(config);
|
|
12
12
|
// 创建 store
|
|
13
13
|
const { authStore, tokenStorage } = createAuthStore({
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import type { AuthConfig } from "../config";
|
|
2
2
|
/**
|
|
3
3
|
* 创建 Better Auth 客户端
|
|
4
|
+
*
|
|
5
|
+
* 配置说明:
|
|
6
|
+
* - 使用 Bearer Token 认证模式
|
|
7
|
+
* - Token 从 localStorage 中动态读取
|
|
8
|
+
* - 支持服务器端 Bearer Plugin 的 session 管理
|
|
9
|
+
*
|
|
10
|
+
* @param config - 认证配置对象
|
|
11
|
+
* @returns Better Auth 客户端实例
|
|
4
12
|
*/
|
|
5
13
|
export declare function createAuthClientFromConfig(config: AuthConfig): {
|
|
6
14
|
signIn: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-client.d.ts","sourceRoot":"","sources":["../../src/lib/auth-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAE3C
|
|
1
|
+
{"version":3,"file":"auth-client.d.ts","sourceRoot":"","sources":["../../src/lib/auth-client.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/auth-client.js
CHANGED
|
@@ -1,11 +1,39 @@
|
|
|
1
1
|
import { createAuthClient } from "better-auth/react";
|
|
2
2
|
/**
|
|
3
3
|
* 创建 Better Auth 客户端
|
|
4
|
+
*
|
|
5
|
+
* 配置说明:
|
|
6
|
+
* - 使用 Bearer Token 认证模式
|
|
7
|
+
* - Token 从 localStorage 中动态读取
|
|
8
|
+
* - 支持服务器端 Bearer Plugin 的 session 管理
|
|
9
|
+
*
|
|
10
|
+
* @param config - 认证配置对象
|
|
11
|
+
* @returns Better Auth 客户端实例
|
|
4
12
|
*/
|
|
5
13
|
export function createAuthClientFromConfig(config) {
|
|
6
|
-
const { baseURL, plugins = [] } = config;
|
|
14
|
+
const { baseURL, plugins = [], tokenStorageKey = "auth-token" } = config;
|
|
7
15
|
return createAuthClient({
|
|
8
16
|
baseURL: `${baseURL}/v1/auth`,
|
|
9
17
|
plugins,
|
|
18
|
+
fetchOptions: {
|
|
19
|
+
auth: {
|
|
20
|
+
type: "Bearer",
|
|
21
|
+
token: () => {
|
|
22
|
+
// 从 localStorage 获取存储的 bearer token
|
|
23
|
+
// SSR 环境下 localStorage 不存在,返回空字符串
|
|
24
|
+
if (typeof window === "undefined" || typeof localStorage === "undefined") {
|
|
25
|
+
return "";
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const token = localStorage.getItem(tokenStorageKey);
|
|
29
|
+
return token || "";
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// localStorage 访问失败(如隐私模式),返回空字符串
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
10
38
|
});
|
|
11
39
|
}
|
package/dist/store/actions.d.ts
CHANGED
|
@@ -19,7 +19,15 @@ export declare function createAuthActions(authStore: Observable<AuthState>, toke
|
|
|
19
19
|
*/
|
|
20
20
|
initialize(user: SessionUser | null, isLoaded: boolean): Promise<void>;
|
|
21
21
|
/**
|
|
22
|
-
* 使用
|
|
22
|
+
* 使用 Bearer Token 获取 session
|
|
23
|
+
*
|
|
24
|
+
* 流程:
|
|
25
|
+
* 1. 保存 token 到 localStorage
|
|
26
|
+
* 2. 使用 Bearer Token 请求 session endpoint
|
|
27
|
+
* 3. 解析响应数据并提取用户信息
|
|
28
|
+
* 4. 更新 authStore 状态
|
|
29
|
+
*
|
|
30
|
+
* @param token - Bearer Token
|
|
23
31
|
*/
|
|
24
32
|
fetchSessionWithToken(token: string): Promise<void>;
|
|
25
33
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/store/actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/store/actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAgE3C;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,UAAU,CAAC,SAAS,CAAC,EAChC,YAAY,EAAE,YAAY,EAC1B,MAAM,EAAE,UAAU,EAClB,UAAU,EAAE;IACV,MAAM,EAAE;QACN,MAAM,EAAE,CAAC,OAAO,EAAE;YAAE,WAAW,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;KACjF,CAAA;IACD,OAAO,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;CAChC;IAeC;;OAEG;qBACoB,WAAW,GAAG,IAAI,YAAY,OAAO;IAe5D;;;;;;;;;;OAUG;iCACgC,MAAM;IAuDzC;;;OAGG;;IAOH;;OAEG;wBACiB,OAAO;IAI3B;;OAEG;oBACa,MAAM,GAAG,IAAI;IAI7B;;;;;;OAMG;sBACoB,MAAM,eAA0B,MAAM;IAmC7D;;OAEG;;IAgCH;;OAEG;qBACc,WAAW,GAAG,IAAI;IAUnC;;OAEG;eACQ,WAAW,GAAG,IAAI;EAIhC;AAED,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAA"}
|
package/dist/store/actions.js
CHANGED
|
@@ -8,14 +8,21 @@ function toISOString(date) {
|
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
10
|
* 从服务器响应中提取用户数据
|
|
11
|
-
*
|
|
11
|
+
*
|
|
12
|
+
* 支持多种数据结构格式以提供更好的兼容性:
|
|
13
|
+
* - 标准格式: { user: {...}, session: {...} }
|
|
14
|
+
* - 嵌套格式: { session: { user: {...} } }
|
|
15
|
+
* - 包装格式: { data: { user: {...} } }
|
|
16
|
+
* - 扁平格式: { id, email, ... }
|
|
17
|
+
*
|
|
18
|
+
* @param sessionData - 服务器返回的 session 数据
|
|
19
|
+
* @returns 映射后的 SessionUser 对象,失败返回 null
|
|
12
20
|
*/
|
|
13
21
|
function extractUserData(sessionData) {
|
|
14
22
|
if (!sessionData || typeof sessionData !== "object")
|
|
15
23
|
return null;
|
|
16
24
|
const data = sessionData;
|
|
17
25
|
// 尝试从不同的数据结构中提取用户信息
|
|
18
|
-
// 优先使用标准格式:{session: {...}, user: {...}}
|
|
19
26
|
const rawUserData = data.user || // 标准格式
|
|
20
27
|
data.session?.user || // 嵌套格式
|
|
21
28
|
data.data?.user || // 包装格式
|
|
@@ -23,10 +30,13 @@ function extractUserData(sessionData) {
|
|
|
23
30
|
if (!rawUserData || typeof rawUserData !== "object")
|
|
24
31
|
return null;
|
|
25
32
|
const user = rawUserData;
|
|
26
|
-
// 提取 session
|
|
27
|
-
const
|
|
28
|
-
|
|
33
|
+
// 提取 session 相关数据(activeOrganizationId 和 activeTeamId 存储在 session 中)
|
|
34
|
+
const sessionInfo = data.session;
|
|
35
|
+
const sessionCreatedAt = sessionInfo?.createdAt
|
|
36
|
+
? toISOString(sessionInfo.createdAt)
|
|
29
37
|
: undefined;
|
|
38
|
+
const activeOrganizationId = sessionInfo?.activeOrganizationId;
|
|
39
|
+
const activeTeamId = sessionInfo?.activeTeamId;
|
|
30
40
|
return {
|
|
31
41
|
banExpires: user.banExpires,
|
|
32
42
|
banReason: user.banReason,
|
|
@@ -40,6 +50,8 @@ function extractUserData(sessionData) {
|
|
|
40
50
|
name: user.name,
|
|
41
51
|
role: user.role,
|
|
42
52
|
updatedAt: toISOString(user.updatedAt) ?? "",
|
|
53
|
+
activeOrganizationId,
|
|
54
|
+
activeTeamId,
|
|
43
55
|
};
|
|
44
56
|
}
|
|
45
57
|
/**
|
|
@@ -71,14 +83,22 @@ export function createAuthActions(authStore, tokenStorage, config, authClient) {
|
|
|
71
83
|
authStore.loading.set(!isLoaded);
|
|
72
84
|
},
|
|
73
85
|
/**
|
|
74
|
-
* 使用
|
|
86
|
+
* 使用 Bearer Token 获取 session
|
|
87
|
+
*
|
|
88
|
+
* 流程:
|
|
89
|
+
* 1. 保存 token 到 localStorage
|
|
90
|
+
* 2. 使用 Bearer Token 请求 session endpoint
|
|
91
|
+
* 3. 解析响应数据并提取用户信息
|
|
92
|
+
* 4. 更新 authStore 状态
|
|
93
|
+
*
|
|
94
|
+
* @param token - Bearer Token
|
|
75
95
|
*/
|
|
76
96
|
async fetchSessionWithToken(token) {
|
|
77
97
|
try {
|
|
78
98
|
if (!token) {
|
|
79
99
|
throw new Error("Token is required");
|
|
80
100
|
}
|
|
81
|
-
// 保存 token 到 localStorage
|
|
101
|
+
// 保存 token 到 localStorage(供 authClient 使用)
|
|
82
102
|
tokenStorage.save(token);
|
|
83
103
|
const endpoint = `${baseURL}${getSessionEndpoint}`;
|
|
84
104
|
const response = await fetch(endpoint, {
|
|
@@ -88,10 +108,10 @@ export function createAuthActions(authStore, tokenStorage, config, authClient) {
|
|
|
88
108
|
"Content-Type": "application/json",
|
|
89
109
|
},
|
|
90
110
|
});
|
|
91
|
-
const responseText = await response.text();
|
|
92
111
|
if (!response.ok) {
|
|
93
112
|
throw new Error(`Failed to fetch session: ${response.status}`);
|
|
94
113
|
}
|
|
114
|
+
const responseText = await response.text();
|
|
95
115
|
// 解析响应数据
|
|
96
116
|
let sessionData = null;
|
|
97
117
|
try {
|
|
@@ -100,7 +120,7 @@ export function createAuthActions(authStore, tokenStorage, config, authClient) {
|
|
|
100
120
|
}
|
|
101
121
|
}
|
|
102
122
|
catch (error) {
|
|
103
|
-
console.error("Failed to parse session response:", error);
|
|
123
|
+
console.error("[fetchSessionWithToken] Failed to parse session response:", error);
|
|
104
124
|
this.handleUnauthorized();
|
|
105
125
|
return;
|
|
106
126
|
}
|
|
@@ -113,12 +133,12 @@ export function createAuthActions(authStore, tokenStorage, config, authClient) {
|
|
|
113
133
|
authStore.loading.set(false);
|
|
114
134
|
}
|
|
115
135
|
else {
|
|
116
|
-
console.error("Invalid session data received");
|
|
136
|
+
console.error("[fetchSessionWithToken] Invalid session data received");
|
|
117
137
|
this.handleUnauthorized();
|
|
118
138
|
}
|
|
119
139
|
}
|
|
120
140
|
catch (error) {
|
|
121
|
-
console.error("Failed to fetch session:", error);
|
|
141
|
+
console.error("[fetchSessionWithToken] Failed to fetch session:", error);
|
|
122
142
|
this.handleUnauthorized();
|
|
123
143
|
}
|
|
124
144
|
},
|
package/dist/types.d.ts
CHANGED
|
@@ -1,37 +1,87 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 认证相关的类型定义
|
|
3
3
|
*/
|
|
4
|
+
/**
|
|
5
|
+
* 会话用户信息
|
|
6
|
+
*
|
|
7
|
+
* 包含用户基本信息和当前活动的组织/团队上下文
|
|
8
|
+
*/
|
|
4
9
|
export type SessionUser = {
|
|
10
|
+
/** 当前活动的组织 ID */
|
|
11
|
+
activeOrganizationId?: string;
|
|
12
|
+
/** 当前活动的团队 ID */
|
|
13
|
+
activeTeamId?: string;
|
|
14
|
+
/** 禁用到期时间 */
|
|
5
15
|
banExpires?: string;
|
|
16
|
+
/** 禁用原因 */
|
|
6
17
|
banReason?: string;
|
|
18
|
+
/** 是否被禁用 */
|
|
7
19
|
banned?: boolean;
|
|
20
|
+
/** 用户创建时间 */
|
|
8
21
|
createdAt: string;
|
|
22
|
+
/** 用户邮箱 */
|
|
9
23
|
email: string;
|
|
24
|
+
/** 邮箱是否已验证 */
|
|
10
25
|
emailVerified: boolean;
|
|
26
|
+
/** 用户 ID */
|
|
11
27
|
id: string;
|
|
28
|
+
/** 用户头像 URL */
|
|
12
29
|
image?: string;
|
|
30
|
+
/** 最后登录时间 */
|
|
13
31
|
lastLoginAt?: string;
|
|
32
|
+
/** 用户名称 */
|
|
14
33
|
name: string;
|
|
34
|
+
/** 用户角色 */
|
|
15
35
|
role?: string;
|
|
36
|
+
/** 用户信息更新时间 */
|
|
16
37
|
updatedAt: string;
|
|
17
38
|
};
|
|
39
|
+
/**
|
|
40
|
+
* Better Auth Session 对象
|
|
41
|
+
*
|
|
42
|
+
* 包含会话信息和关联的用户数据
|
|
43
|
+
*/
|
|
18
44
|
export type Session = {
|
|
45
|
+
/** 当前活动的组织 ID(存储在 session 中) */
|
|
46
|
+
activeOrganizationId?: string;
|
|
47
|
+
/** 当前活动的团队 ID(存储在 session 中) */
|
|
48
|
+
activeTeamId?: string;
|
|
49
|
+
/** 会话创建时间 */
|
|
19
50
|
createdAt: string;
|
|
51
|
+
/** 会话过期时间 */
|
|
20
52
|
expiresAt: string;
|
|
53
|
+
/** 会话 ID */
|
|
21
54
|
id: string;
|
|
55
|
+
/** 客户端 IP 地址 */
|
|
22
56
|
ipAddress?: string;
|
|
57
|
+
/** 会话 Token */
|
|
23
58
|
token: string;
|
|
59
|
+
/** 会话更新时间 */
|
|
24
60
|
updatedAt: string;
|
|
61
|
+
/** 关联的用户信息 */
|
|
25
62
|
user: SessionUser;
|
|
63
|
+
/** 客户端 User-Agent */
|
|
26
64
|
userAgent?: string;
|
|
65
|
+
/** 用户 ID */
|
|
27
66
|
userId: string;
|
|
28
67
|
};
|
|
68
|
+
/**
|
|
69
|
+
* 认证状态
|
|
70
|
+
*
|
|
71
|
+
* 存储在 Legend State 中的认证状态数据
|
|
72
|
+
*/
|
|
29
73
|
export interface AuthState {
|
|
74
|
+
/** 错误信息 */
|
|
30
75
|
error: string | null;
|
|
76
|
+
/** 是否已认证 */
|
|
31
77
|
isAuthenticated: boolean;
|
|
78
|
+
/** 是否已加载完成 */
|
|
32
79
|
isLoaded: boolean;
|
|
80
|
+
/** 是否正在加载 */
|
|
33
81
|
loading: boolean;
|
|
82
|
+
/** 认证 Token */
|
|
34
83
|
token: string | null;
|
|
84
|
+
/** 当前用户信息 */
|
|
35
85
|
user: SessionUser | null;
|
|
36
86
|
}
|
|
37
87
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,CAAC,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,iBAAiB;IACjB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,iBAAiB;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY;IACZ,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW;IACX,KAAK,EAAE,MAAM,CAAC;IACd,cAAc;IACd,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,eAAe;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe;IACf,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED;;;;GAIG;AACH,MAAM,MAAM,OAAO,GAAG;IACpB,gCAAgC;IAChC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,gCAAgC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,gBAAgB;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe;IACf,KAAK,EAAE,MAAM,CAAC;IACd,aAAa;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc;IACd,IAAI,EAAE,WAAW,CAAC;IAClB,qBAAqB;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY;IACZ,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,WAAW;IACX,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,YAAY;IACZ,eAAe,EAAE,OAAO,CAAA;IACxB,cAAc;IACd,QAAQ,EAAE,OAAO,CAAA;IACjB,aAAa;IACb,OAAO,EAAE,OAAO,CAAA;IAChB,eAAe;IACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,aAAa;IACb,IAAI,EAAE,WAAW,GAAG,IAAI,CAAA;CACzB"}
|