@choiceform/shared-auth 0.1.14 → 0.1.16

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.
Files changed (95) hide show
  1. package/README.md +106 -450
  2. package/dist/api/auth-api.d.ts +28 -0
  3. package/dist/api/auth-api.d.ts.map +1 -0
  4. package/dist/api/auth-api.js +133 -0
  5. package/dist/api/client.d.ts +34 -0
  6. package/dist/api/client.d.ts.map +1 -0
  7. package/dist/api/client.js +104 -0
  8. package/dist/api/index.d.ts +12 -0
  9. package/dist/api/index.d.ts.map +1 -0
  10. package/dist/api/index.js +7 -0
  11. package/dist/api/organization-api.d.ts +96 -0
  12. package/dist/api/organization-api.d.ts.map +1 -0
  13. package/dist/api/organization-api.js +228 -0
  14. package/dist/api/team-api.d.ts +57 -0
  15. package/dist/api/team-api.d.ts.map +1 -0
  16. package/dist/api/team-api.js +118 -0
  17. package/dist/config.d.ts +4 -57
  18. package/dist/config.d.ts.map +1 -1
  19. package/dist/config.js +4 -6
  20. package/dist/core.d.ts +114 -72
  21. package/dist/core.d.ts.map +1 -1
  22. package/dist/core.js +35 -17
  23. package/dist/hooks/use-auth-init.d.ts +10 -0
  24. package/dist/hooks/use-auth-init.d.ts.map +1 -1
  25. package/dist/hooks/use-auth-init.js +59 -31
  26. package/dist/index.d.ts +12 -15
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +26 -13
  29. package/dist/init.d.ts +133 -92
  30. package/dist/init.d.ts.map +1 -1
  31. package/dist/init.js +12 -14
  32. package/dist/lib/auth-client.d.ts +49 -54
  33. package/dist/lib/auth-client.d.ts.map +1 -1
  34. package/dist/lib/auth-client.js +10 -16
  35. package/dist/services/companion-team.d.ts +16 -0
  36. package/dist/services/companion-team.d.ts.map +1 -0
  37. package/dist/services/companion-team.js +73 -0
  38. package/dist/services/index.d.ts +5 -0
  39. package/dist/services/index.d.ts.map +1 -0
  40. package/dist/services/index.js +4 -0
  41. package/dist/store/actions.d.ts +45 -33
  42. package/dist/store/actions.d.ts.map +1 -1
  43. package/dist/store/actions.js +135 -106
  44. package/dist/store/index.d.ts +8 -0
  45. package/dist/store/index.d.ts.map +1 -0
  46. package/dist/store/index.js +7 -0
  47. package/dist/store/state.d.ts +10 -7
  48. package/dist/store/state.d.ts.map +1 -1
  49. package/dist/store/state.js +31 -23
  50. package/dist/store/utils.d.ts +22 -71
  51. package/dist/store/utils.d.ts.map +1 -1
  52. package/dist/store/utils.js +28 -146
  53. package/dist/types/auth.d.ts +107 -0
  54. package/dist/types/auth.d.ts.map +1 -0
  55. package/dist/types/auth.js +4 -0
  56. package/dist/types/index.d.ts +8 -0
  57. package/dist/types/index.d.ts.map +1 -0
  58. package/dist/types/index.js +4 -0
  59. package/dist/types/organization.d.ts +111 -0
  60. package/dist/types/organization.d.ts.map +1 -0
  61. package/dist/types/organization.js +4 -0
  62. package/dist/types/team.d.ts +52 -0
  63. package/dist/types/team.d.ts.map +1 -0
  64. package/dist/types/team.js +4 -0
  65. package/dist/types/user.d.ts +44 -0
  66. package/dist/types/user.d.ts.map +1 -0
  67. package/dist/types/user.js +4 -0
  68. package/dist/utils/date.d.ts +10 -0
  69. package/dist/utils/date.d.ts.map +1 -0
  70. package/dist/utils/date.js +13 -0
  71. package/dist/utils/env.d.ts +20 -0
  72. package/dist/utils/env.d.ts.map +1 -0
  73. package/dist/utils/env.js +23 -0
  74. package/dist/utils/index.d.ts +7 -0
  75. package/dist/utils/index.d.ts.map +1 -0
  76. package/dist/utils/index.js +6 -0
  77. package/dist/utils/user-mapper.d.ts +21 -0
  78. package/dist/utils/user-mapper.d.ts.map +1 -0
  79. package/dist/utils/user-mapper.js +55 -0
  80. package/package.json +3 -4
  81. package/dist/components/auth-sync.d.ts +0 -25
  82. package/dist/components/auth-sync.d.ts.map +0 -1
  83. package/dist/components/auth-sync.js +0 -346
  84. package/dist/components/protected-route.d.ts +0 -18
  85. package/dist/components/protected-route.d.ts.map +0 -1
  86. package/dist/components/protected-route.js +0 -34
  87. package/dist/components/sign-in-page.d.ts +0 -21
  88. package/dist/components/sign-in-page.d.ts.map +0 -1
  89. package/dist/components/sign-in-page.js +0 -31
  90. package/dist/core/init-auth-sync.d.ts +0 -7
  91. package/dist/core/init-auth-sync.d.ts.map +0 -1
  92. package/dist/core/init-auth-sync.js +0 -34
  93. package/dist/types.d.ts +0 -87
  94. package/dist/types.d.ts.map +0 -1
  95. package/dist/types.js +0 -4
@@ -0,0 +1,23 @@
1
+ /**
2
+ * 环境变量工具函数
3
+ */
4
+ /**
5
+ * 检查是否为开发环境
6
+ */
7
+ export const isDev = import.meta.env?.DEV ?? false;
8
+ /**
9
+ * 安全获取环境变量
10
+ * @param key - 环境变量名
11
+ * @returns 环境变量值,不存在则返回 undefined
12
+ */
13
+ export function getEnvVar(key) {
14
+ return import.meta.env?.[key];
15
+ }
16
+ /**
17
+ * 获取 OneAuth API 基础 URL
18
+ * @param fallback - 默认值
19
+ * @returns OneAuth API 基础 URL
20
+ */
21
+ export function getAuthBaseUrl(fallback = "https://oneauth.choiceform.io") {
22
+ return getEnvVar("VITE_ONEAUTH_BASE_URL") || fallback;
23
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 工具函数导出
3
+ */
4
+ export { toISOString } from "./date";
5
+ export { isDev, getEnvVar, getAuthBaseUrl } from "./env";
6
+ export { extractSessionUser } from "./user-mapper";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAA;AACpC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * 工具函数导出
3
+ */
4
+ export { toISOString } from "./date";
5
+ export { isDev, getEnvVar, getAuthBaseUrl } from "./env";
6
+ export { extractSessionUser } from "./user-mapper";
@@ -0,0 +1,21 @@
1
+ /**
2
+ * 用户数据提取工具
3
+ *
4
+ * 从 better-auth API 响应中提取用户数据
5
+ * 由于 auth server 也是基于 better-auth,数据结构是一致的,
6
+ * 这里只做简单的数据提取和类型断言
7
+ */
8
+ import type { SessionUser } from "../types";
9
+ /**
10
+ * 从服务器响应中提取用户数据
11
+ *
12
+ * 支持的数据结构:
13
+ * - 标准格式: { user: {...}, session: {...} }
14
+ * - 嵌套格式: { session: { user: {...} } }
15
+ * - 包装格式: { data: { user: {...} } }
16
+ *
17
+ * @param responseData - 服务器返回的响应数据
18
+ * @returns SessionUser 对象,失败返回 null
19
+ */
20
+ export declare function extractSessionUser(responseData: unknown): SessionUser | null;
21
+ //# sourceMappingURL=user-mapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-mapper.d.ts","sourceRoot":"","sources":["../../src/utils/user-mapper.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAuB,MAAM,UAAU,CAAA;AAEhE;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,OAAO,GAAG,WAAW,GAAG,IAAI,CAyC5E"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * 用户数据提取工具
3
+ *
4
+ * 从 better-auth API 响应中提取用户数据
5
+ * 由于 auth server 也是基于 better-auth,数据结构是一致的,
6
+ * 这里只做简单的数据提取和类型断言
7
+ */
8
+ /**
9
+ * 从服务器响应中提取用户数据
10
+ *
11
+ * 支持的数据结构:
12
+ * - 标准格式: { user: {...}, session: {...} }
13
+ * - 嵌套格式: { session: { user: {...} } }
14
+ * - 包装格式: { data: { user: {...} } }
15
+ *
16
+ * @param responseData - 服务器返回的响应数据
17
+ * @returns SessionUser 对象,失败返回 null
18
+ */
19
+ export function extractSessionUser(responseData) {
20
+ if (!responseData || typeof responseData !== "object")
21
+ return null;
22
+ const data = responseData;
23
+ // 尝试从不同的数据结构中提取用户信息
24
+ const rawUser = data.user || // 标准格式
25
+ data.session?.user || // 嵌套格式
26
+ data.data?.user; // 包装格式
27
+ if (!rawUser || typeof rawUser !== "object")
28
+ return null;
29
+ const user = rawUser;
30
+ // 验证必需字段
31
+ if (!user.id || !user.email)
32
+ return null;
33
+ // 提取 session 相关数据
34
+ const session = data.session;
35
+ // 直接返回,利用 better-auth 返回的数据结构
36
+ return {
37
+ activeOrganizationId: session?.activeOrganizationId,
38
+ activeTeamId: session?.activeTeamId,
39
+ banExpires: user.banExpires,
40
+ banReason: user.banReason,
41
+ banned: user.banned,
42
+ createdAt: String(user.createdAt ?? ""),
43
+ email: user.email,
44
+ emailVerified: user.emailVerified ?? false,
45
+ id: user.id,
46
+ image: user.image ?? undefined,
47
+ inherentOrganizationId: user.inherentOrganizationId,
48
+ inherentTeamId: user.inherentTeamId,
49
+ lastLoginAt: session?.createdAt ? String(session.createdAt) : undefined,
50
+ metadata: user.metadata,
51
+ name: user.name ?? "",
52
+ role: user.role,
53
+ updatedAt: String(user.updatedAt ?? ""),
54
+ };
55
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@choiceform/shared-auth",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "Shared authentication package for Choiceform projects",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -42,12 +42,11 @@
42
42
  "access": "public"
43
43
  },
44
44
  "dependencies": {
45
- "better-auth": "^1.3.8"
45
+ "better-auth": "^1.4.4"
46
46
  },
47
47
  "peerDependencies": {
48
- "@choiceform/design-system": "^1.2.70",
49
48
  "@legendapp/state": "v3.0.0-beta.26",
50
- "better-auth": "^1.3.8",
49
+ "better-auth": "^1.4.4",
51
50
  "react": ">=18.0.0",
52
51
  "react-dom": ">=18.0.0"
53
52
  },
@@ -1,25 +0,0 @@
1
- import type { AuthInstance } from "../core";
2
- /**
3
- * Better Auth 认证状态同步组件
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
- * - 用户登出:重置团队设置状态,允许下次登录时重新设置
21
- */
22
- export declare function AuthSync({ auth }: {
23
- auth: AuthInstance;
24
- }): null;
25
- //# sourceMappingURL=auth-sync.d.ts.map
@@ -1 +0,0 @@
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;AAoS3C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,QA0FxD"}
@@ -1,346 +0,0 @@
1
- import { useEffect, useRef, useCallback } from "react";
2
- import { use$ } from "@legendapp/state/react";
3
- /**
4
- * 将日期转换为 ISO 字符串
5
- */
6
- function toISOString(date) {
7
- if (!date)
8
- return undefined;
9
- return typeof date === "string" ? date : new Date(date).toISOString();
10
- }
11
- /**
12
- * 将 Better Auth session 用户数据映射为 SessionUser
13
- */
14
- function mapSessionUserToSessionUser(sessionUser, sessionData) {
15
- return {
16
- banExpires: sessionUser.banExpires,
17
- banReason: sessionUser.banReason,
18
- banned: sessionUser.banned,
19
- createdAt: toISOString(sessionUser.createdAt) ?? "",
20
- email: sessionUser.email,
21
- emailVerified: sessionUser.emailVerified,
22
- id: sessionUser.id,
23
- image: sessionUser.image || undefined,
24
- lastLoginAt: toISOString(sessionData?.createdAt),
25
- name: sessionUser.name,
26
- role: sessionUser.role,
27
- updatedAt: toISOString(sessionUser.updatedAt) ?? "",
28
- activeOrganizationId: sessionData?.activeOrganizationId,
29
- activeTeamId: sessionData?.activeTeamId,
30
- };
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
- // 5. 获取组织信息
118
- const myOrganizationUrl = `${oneAuthBaseUrl}/v1/organizations/me`;
119
- if (isDev) {
120
- console.log("[AuthSync] Fetching organization from:", myOrganizationUrl);
121
- }
122
- const orgResponse = await fetch(myOrganizationUrl, {
123
- headers: {
124
- Authorization: `Bearer ${oneAuthToken}`,
125
- "Content-Type": "application/json",
126
- },
127
- });
128
- if (!orgResponse.ok) {
129
- const errorMessage = await parseErrorResponse(orgResponse);
130
- throw new Error(`Failed to fetch organization: ${errorMessage}`);
131
- }
132
- // 6. 解析组织数据
133
- let orgResponseData;
134
- try {
135
- orgResponseData = await orgResponse.json();
136
- }
137
- catch (parseError) {
138
- throw new Error(`Failed to parse organization response: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
139
- }
140
- // 7. 提取组织对象(支持 {data: {...}} 或直接是组织对象)
141
- const organization = orgResponseData?.data ||
142
- orgResponseData;
143
- // 8. 验证组织数据
144
- if (!organization || typeof organization !== "object" || !organization.id || typeof organization.id !== "string") {
145
- throw new Error(`Invalid organization data received: missing or invalid organization ID`);
146
- }
147
- // 9. 获取 auth baseURL
148
- const authBaseURL = getEnvVar("VITE_AUTH_API_URL") ||
149
- getEnvVar("VITE_CORE_AI_API_URL") ||
150
- "http://localhost:4320";
151
- // 10. 设置活动组织
152
- const setActiveOrgUrl = `${authBaseURL}/v1/auth/organization/set-active`;
153
- if (isDev) {
154
- console.log("[AuthSync] Setting active organization:", { organizationId: organization.id });
155
- }
156
- const setActiveOrgResponse = await fetch(setActiveOrgUrl, {
157
- method: "POST",
158
- headers: {
159
- Authorization: `Bearer ${oneAuthToken}`,
160
- "Content-Type": "application/json",
161
- },
162
- body: JSON.stringify({ organizationId: organization.id }),
163
- });
164
- if (!setActiveOrgResponse.ok) {
165
- const errorMessage = await parseErrorResponse(setActiveOrgResponse);
166
- throw new Error(`Failed to set active organization: ${errorMessage}`);
167
- }
168
- // 11. 设置活动团队(如果有团队)
169
- if (organization.teams && Array.isArray(organization.teams) && organization.teams.length > 0) {
170
- const firstTeam = organization.teams[0];
171
- if (firstTeam && firstTeam.id && typeof firstTeam.id === "string") {
172
- const setActiveTeamUrl = `${authBaseURL}/v1/auth/organization/set-active-team`;
173
- if (isDev) {
174
- console.log("[AuthSync] Setting active team:", { teamId: firstTeam.id });
175
- }
176
- const setActiveTeamResponse = await fetch(setActiveTeamUrl, {
177
- method: "POST",
178
- headers: {
179
- Authorization: `Bearer ${oneAuthToken}`,
180
- "Content-Type": "application/json",
181
- },
182
- body: JSON.stringify({ teamId: firstTeam.id }),
183
- });
184
- if (!setActiveTeamResponse.ok) {
185
- const errorMessage = await parseErrorResponse(setActiveTeamResponse);
186
- throw new Error(`Failed to set active team: ${errorMessage}`);
187
- }
188
- if (isDev) {
189
- console.log("[AuthSync] Successfully set active team");
190
- }
191
- // 刷新 session 并更新 authStore
192
- if (refetchSession) {
193
- refetchSession();
194
- }
195
- // 使用 fetch 直接调用 get-session 获取最新数据
196
- try {
197
- const getSessionUrl = `${authBaseURL}/v1/auth/get-session`;
198
- const sessionResponse = await fetch(getSessionUrl, {
199
- headers: {
200
- Authorization: `Bearer ${oneAuthToken}`,
201
- "Content-Type": "application/json",
202
- },
203
- });
204
- if (sessionResponse.ok) {
205
- const sessionData = await sessionResponse.json();
206
- const data = sessionData;
207
- if (data.user && data.session) {
208
- const currentUser = auth.authStore.user.get();
209
- if (currentUser) {
210
- auth.authStore.user.set({
211
- ...currentUser,
212
- activeOrganizationId: data.session.activeOrganizationId,
213
- activeTeamId: data.session.activeTeamId,
214
- });
215
- if (isDev) {
216
- console.log("[AuthSync] Updated authStore with activeOrganizationId and activeTeamId");
217
- }
218
- }
219
- }
220
- }
221
- }
222
- catch (err) {
223
- if (isDev) {
224
- console.error("[AuthSync] Failed to refresh session:", err);
225
- }
226
- }
227
- }
228
- else if (isDev) {
229
- console.warn("[AuthSync] First team has invalid ID, skipping team setup");
230
- }
231
- }
232
- else if (isDev) {
233
- console.log("[AuthSync] No teams found in organization");
234
- }
235
- }
236
- catch (error) {
237
- // 记录错误但不抛出,避免影响登录流程
238
- const errorMessage = error instanceof Error ? error.message : String(error);
239
- console.error("[AuthSync] Failed to setup companion team:", errorMessage);
240
- // 在开发环境下输出更详细的错误信息
241
- if (isDev && error instanceof Error) {
242
- console.error("[AuthSync] Error details:", {
243
- message: error.message,
244
- stack: error.stack,
245
- });
246
- }
247
- }
248
- }
249
- /**
250
- * Better Auth 认证状态同步组件
251
- *
252
- * 功能说明:
253
- * - 使用 better-auth 的 useSession hook 同步认证状态到 Legend State
254
- * - 监听 authStore 状态,在用户登录成功后自动设置组织/团队上下文
255
- * - 确保登录流程的完整性和权限上下文的正确性
256
- *
257
- * 技术实现:
258
- * - 使用 Legend State 的 use$ hook 响应式监听状态变化
259
- * - 使用 useRef 确保组织/团队设置只执行一次
260
- * - 使用 useCallback 缓存映射函数,避免不必要的重渲染
261
- * - 分离 session 同步和团队设置的逻辑,保持代码清晰
262
- *
263
- * 边缘情况处理:
264
- * - Session 加载中:设置 loading 状态
265
- * - Session 错误:清空用户信息,重置团队设置状态
266
- * - 无 Session 但有用户:保持用户信息(可能从 token 加载)
267
- * - 用户登出:重置团队设置状态,允许下次登录时重新设置
268
- */
269
- export function AuthSync({ auth }) {
270
- const { authClient, authActions, tokenStorage, authStore } = auth;
271
- // 监听 authStore 中的用户状态(响应式)
272
- const user = use$(authStore.user);
273
- const isAuthenticated = use$(authStore.isAuthenticated);
274
- const isLoaded = use$(authStore.isLoaded);
275
- // 监听 better-auth 的 session(用于同步到 store)
276
- const sessionResult = authClient.useSession();
277
- const { data: session, isPending, error, refetch } = sessionResult || {
278
- data: null,
279
- isPending: false,
280
- error: null,
281
- refetch: undefined,
282
- };
283
- // 使用 ref 确保组织/团队设置只执行一次
284
- const teamSetupRef = useRef(false);
285
- // 使用 useCallback 缓存 session 映射逻辑,避免不必要的重计算
286
- const handleSessionUpdate = useCallback((sessionData) => {
287
- if (sessionData?.user) {
288
- try {
289
- const mappedUser = mapSessionUserToSessionUser(sessionData.user, sessionData.session);
290
- authActions.initialize(mappedUser, true);
291
- }
292
- catch (mappingError) {
293
- console.error("[AuthSync] Failed to map session user:", mappingError);
294
- // 映射失败时不清空用户信息,可能已有有效的用户数据
295
- }
296
- }
297
- }, [authActions]);
298
- // Effect 1: 同步 better-auth session 到 store
299
- useEffect(() => {
300
- // 加载中状态
301
- if (isPending) {
302
- authActions.setLoading(true);
303
- return;
304
- }
305
- authActions.setLoading(false);
306
- // 错误处理
307
- if (error) {
308
- const errorMessage = error instanceof Error ? error.message : typeof error === "string" ? error : "Authentication error";
309
- authActions.setError(errorMessage);
310
- authActions.initialize(null, true);
311
- teamSetupRef.current = false;
312
- return;
313
- }
314
- // Session 存在:映射用户数据并更新 store
315
- if (session) {
316
- handleSessionUpdate(session);
317
- }
318
- // 注意:没有 session 时不主动清空用户信息,因为可能已经从 token 加载了
319
- }, [session, isPending, error, authActions, handleSessionUpdate]);
320
- // Effect 2: 监听 authStore 状态,设置伴生团队
321
- useEffect(() => {
322
- // 条件检查:只有在用户已认证且已加载完成时才设置伴生团队
323
- if (isLoaded && isAuthenticated && user?.id && !teamSetupRef.current) {
324
- teamSetupRef.current = true;
325
- const token = tokenStorage.get();
326
- // 验证 token 存在
327
- if (token && typeof token === "string" && token.trim().length > 0) {
328
- setupCompanionTeam(auth, token, refetch).catch((error) => {
329
- const errorMessage = error instanceof Error ? error.message : String(error);
330
- console.error("[AuthSync] Failed to setup companion team:", errorMessage);
331
- // 失败后重置 ref,允许在特定条件下重试(如 token 更新后)
332
- teamSetupRef.current = false;
333
- });
334
- }
335
- else if (isDev) {
336
- console.warn("[AuthSync] Token not found or invalid, cannot setup companion team");
337
- }
338
- }
339
- // 用户登出:重置 ref,允许下次登录时重新设置
340
- if (isLoaded && !isAuthenticated) {
341
- teamSetupRef.current = false;
342
- }
343
- }, [user, isAuthenticated, isLoaded, auth, tokenStorage, refetch]);
344
- // 这是一个隐形组件,不渲染任何内容
345
- return null;
346
- }
@@ -1,18 +0,0 @@
1
- import React from "react";
2
- import type { AuthInstance } from "../core";
3
- interface ProtectedRouteProps {
4
- auth: AuthInstance;
5
- children: React.ReactNode;
6
- loadingComponent?: React.ComponentType<{
7
- message?: string;
8
- }>;
9
- loadingMessage?: string;
10
- onUnauthorized: () => void;
11
- }
12
- /**
13
- * 路由保护组件
14
- * 等待认证初始化完成,根据认证状态进行路由保护
15
- */
16
- export declare const ProtectedRoute: React.FC<ProtectedRouteProps>;
17
- export default ProtectedRoute;
18
- //# sourceMappingURL=protected-route.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"protected-route.d.ts","sourceRoot":"","sources":["../../src/components/protected-route.tsx"],"names":[],"mappings":"AACA,OAAO,KAAoB,MAAM,OAAO,CAAA;AACxC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C,UAAU,mBAAmB;IAC3B,IAAI,EAAE,YAAY,CAAA;IAClB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,gBAAgB,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC5D,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,cAAc,EAAE,MAAM,IAAI,CAAA;CAC3B;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAgCxD,CAAA;AAED,eAAe,cAAc,CAAA"}
@@ -1,34 +0,0 @@
1
- import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { observer } from "@legendapp/state/react";
3
- import { useEffect } from "react";
4
- /**
5
- * 路由保护组件
6
- * 等待认证初始化完成,根据认证状态进行路由保护
7
- */
8
- export const ProtectedRoute = observer(({ children, auth, loadingComponent, loadingMessage, onUnauthorized }) => {
9
- const { authStore, authComputed } = auth;
10
- // 检查各种状态
11
- const isInitializing = authComputed.isInitializing.get();
12
- const isAuthenticated = authStore.isAuthenticated.get();
13
- // 当未认证时调用回调
14
- useEffect(() => {
15
- if (!isInitializing && !isAuthenticated) {
16
- onUnauthorized();
17
- }
18
- }, [isInitializing, isAuthenticated, onUnauthorized]);
19
- // 检查初始化状态
20
- if (isInitializing) {
21
- if (loadingComponent) {
22
- const LoadingComponent = loadingComponent;
23
- return _jsx(LoadingComponent, { message: loadingMessage });
24
- }
25
- return _jsx("div", { children: loadingMessage || "Checking authentication..." });
26
- }
27
- // 检查认证状态
28
- if (!isAuthenticated) {
29
- return null;
30
- }
31
- // 所有检查通过,渲染子组件
32
- return _jsx(_Fragment, { children: children });
33
- });
34
- export default ProtectedRoute;
@@ -1,21 +0,0 @@
1
- import React from "react";
2
- import type { AuthInstance } from "../core";
3
- interface SignInPageProps {
4
- afterElement?: React.ReactNode;
5
- auth: AuthInstance;
6
- beforeElement?: React.ReactNode;
7
- className?: string;
8
- description?: string;
9
- footerText?: React.ReactNode;
10
- githubButton?: (isSigningIn: boolean) => React.ReactNode;
11
- onAuthSuccess?: () => void;
12
- provider?: string;
13
- redirectUrl?: string;
14
- title?: string;
15
- }
16
- /**
17
- * 登录页面组件
18
- */
19
- export declare function SignInPage({ afterElement, auth, beforeElement, onAuthSuccess, redirectUrl, provider, title, description, githubButton, className, footerText, }: SignInPageProps): import("react/jsx-runtime").JSX.Element;
20
- export {};
21
- //# sourceMappingURL=sign-in-page.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sign-in-page.d.ts","sourceRoot":"","sources":["../../src/components/sign-in-page.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA8B,MAAM,OAAO,CAAA;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C,UAAU,eAAe;IACvB,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC9B,IAAI,EAAE,YAAY,CAAA;IAClB,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC5B,YAAY,CAAC,EAAE,CAAC,WAAW,EAAE,OAAO,KAAK,KAAK,CAAC,SAAS,CAAA;IACxD,aAAa,CAAC,EAAE,MAAM,IAAI,CAAA;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,EACzB,YAAY,EACZ,IAAI,EACJ,aAAa,EACb,aAAa,EACb,WAA0B,EAC1B,QAAmB,EACnB,KAAK,EACL,WAAW,EACX,YAAY,EACZ,SAAS,EACT,UAAU,GACX,EAAE,eAAe,2CA+CjB"}
@@ -1,31 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Slot } from "@choiceform/design-system";
3
- import { use$ } from "@legendapp/state/react";
4
- import { useEffect, useState } from "react";
5
- /**
6
- * 登录页面组件
7
- */
8
- export function SignInPage({ afterElement, auth, beforeElement, onAuthSuccess, redirectUrl = "/community", provider = "github", title, description, githubButton, className, footerText, }) {
9
- const { authStore, authActions } = auth;
10
- const { isAuthenticated, error } = use$(authStore);
11
- const [isSigningIn, setIsSigningIn] = useState(false);
12
- useEffect(() => {
13
- if (isAuthenticated && onAuthSuccess) {
14
- onAuthSuccess();
15
- }
16
- }, [isAuthenticated, onAuthSuccess]);
17
- const handleSignIn = async () => {
18
- // 设置本地 loading 状态
19
- setIsSigningIn(true);
20
- try {
21
- await authActions.signIn(provider, `${window.location.origin}${redirectUrl}`);
22
- // OAuth 会重定向,所以这里的代码可能不会执行
23
- // loading 状态会在页面刷新后重置
24
- }
25
- catch (error) {
26
- // 如果出错,重置 loading 状态
27
- setIsSigningIn(false);
28
- }
29
- };
30
- return (_jsxs("div", { className: className, children: [beforeElement, title && (_jsxs("div", { className: "flex flex-col gap-2", children: [title && _jsx("p", { className: "text-heading-large", children: title }), description && (_jsx("p", { className: "text-secondary-foreground text-body-large", children: description }))] })), provider === "github" && (_jsx(Slot, { onClick: handleSignIn, children: githubButton?.(isSigningIn) })), error && _jsx("p", { className: "text-danger-foreground mt-2", children: error }), footerText, afterElement] }));
31
- }
@@ -1,7 +0,0 @@
1
- import type { AuthInstance } from "../core";
2
- /**
3
- * 同步版本的认证初始化
4
- * 可以在应用渲染前调用(非 React hook)
5
- */
6
- export declare function initAuthSync(auth: AuthInstance): Promise<void>;
7
- //# sourceMappingURL=init-auth-sync.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"init-auth-sync.d.ts","sourceRoot":"","sources":["../../src/core/init-auth-sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C;;;GAGG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA+BpE"}