@choiceform/shared-auth 0.1.1 → 0.1.2

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/LICENSE.md ADDED
@@ -0,0 +1,149 @@
1
+ # License
2
+
3
+ Portions of this software are licensed as follows:
4
+
5
+ - Content of branches other than the main branch are not licensed.
6
+ - Source code files that contain `.ee.` in their filename or `.ee` in their directory name are NOT licensed under the Sustainable Use License. To use source code files that contain `.ee.` in their filename or `.ee` in their directory name you must hold a valid Rungraf Enterprise License specifically allowing you access to such source code files and as defined in "LICENSE_EE.md".
7
+ - All third party components incorporated into the Rungraf software are licensed under the original license provided by the owner of the applicable component.
8
+ - Content outside of the above mentioned files or restrictions is available under the "Sustainable Use License" as defined below.
9
+
10
+ ---
11
+
12
+ ## Sustainable Use License
13
+
14
+ **Version 1.0**
15
+
16
+ ### Acceptance
17
+
18
+ By using the software, you agree to all of the terms and conditions below.
19
+
20
+ ### Copyright License
21
+
22
+ The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license to use, copy, distribute, make available, and prepare derivative works of the software, in each case subject to the limitations below.
23
+
24
+ ### Limitations
25
+
26
+ You may use or modify the software only for your own internal business purposes or for non-commercial or personal use. You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes. You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensor's trademarks is subject to applicable law.
27
+
28
+ ### Patents
29
+
30
+ The licensor grants you a license, under any patent claims the licensor can license, or becomes able to license, to make, have made, use, sell, offer for sale, import and have imported the software, in each case subject to the limitations and conditions in this license. This license does not cover any patent claims that you cause to be infringed by modifications or additions to the software. If you or your company make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
31
+
32
+ ### Notices
33
+
34
+ You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms. If you modify the software, you must include in any modified copies of the software a prominent notice stating that you have modified the software.
35
+
36
+ ### No Other Rights
37
+
38
+ These terms do not imply any licenses other than those expressly granted in these terms.
39
+
40
+ ### Termination
41
+
42
+ If you use the software in violation of these terms, such use is not licensed, and your license will automatically terminate. If the licensor provides you with a notice of your violation, and you cease all violation of this license no later than 30 days after you receive that notice, your license will be reinstated retroactively. However, if you violate these terms after such reinstatement, any additional violation of these terms will cause your license to terminate automatically and permanently.
43
+
44
+ ### No Liability
45
+
46
+ As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.
47
+
48
+ ### Definitions
49
+
50
+ - The **"licensor"** is the entity offering these terms, which is the Rungraf project and its contributors.
51
+
52
+ - The **"software"** is the software the licensor makes available under these terms, including any portion of it.
53
+
54
+ - **"You"** refers to the individual or entity agreeing to these terms.
55
+
56
+ - **"Your company"** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. Control means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
57
+
58
+ - **"Your license"** is the license granted to you for the software under these terms.
59
+
60
+ - **"Use"** means anything you do with the software requiring your license.
61
+
62
+ - **"Trademark"** means trademarks, service marks, and similar rights.
63
+
64
+ ---
65
+
66
+ ## Additional Terms for Rungraf
67
+
68
+ In addition to the Sustainable Use License terms above, the following specific limitations apply to Rungraf:
69
+
70
+ ### Multi-Tenant SaaS Restriction
71
+
72
+ You may NOT use Rungraf to operate a multi-tenant Software-as-a-Service (SaaS) offering where you provide Rungraf as a hosted service to multiple external customers or organizations, unless you obtain a separate commercial license from the Rungraf project.
73
+
74
+ **Examples of PROHIBITED use without commercial license:**
75
+ - Operating "AI Agent Platform as a Service" for multiple paying customers
76
+ - Providing Rungraf as a white-labeled workflow automation service
77
+ - Running a marketplace where multiple organizations build AI Agents on your hosted Rungraf instance
78
+
79
+ **Examples of PERMITTED use:**
80
+ - Deploying Rungraf within your organization for your employees and internal use
81
+ - Embedding Rungraf in your product where each customer gets their own dedicated Rungraf instance
82
+ - Using Rungraf as a backend service for your own application (single-tenant)
83
+
84
+ **Workspace Definition:** Within Rungraf, one workspace represents one tenant. A workspace provides an isolated environment for each tenant's AI Agents, workflows, data, and configurations.
85
+
86
+ ### Branding and Attribution
87
+
88
+ When using Rungraf's frontend interface, you may not remove, alter, or obscure the Rungraf logo, branding, or copyright notices displayed in the user interface. This restriction applies only to the visual frontend components.
89
+
90
+ **Frontend Definition:** The "frontend" of Rungraf includes all components located in the `packages/client/` directory when running from source code, or the "client" Docker image when running via containers.
91
+
92
+ **Examples of PERMITTED use:**
93
+ - Using only Rungraf's backend APIs with your own custom frontend (no branding requirement)
94
+ - Forking and customizing the codebase while maintaining attribution
95
+ - White-labeling with a separate commercial license
96
+
97
+ **Examples of PROHIBITED use:**
98
+ - Using the Rungraf frontend with logos and branding removed
99
+ - Distributing modified versions without proper attribution
100
+
101
+ ### Core Engine Protection
102
+
103
+ The Rungraf AI Agent Runtime (located in `packages/runner/`) and Expression Engine may be used and modified for your own purposes, but you may NOT:
104
+
105
+ - Extract the Agent Runtime as a standalone product and sell it separately
106
+ - Use Rungraf's core orchestration engine to build a competing workflow automation platform for sale
107
+ - Offer the Expression Engine as a standalone commercial service
108
+
109
+ **Examples of PERMITTED use:**
110
+ - Integrating Rungraf into your product's backend
111
+ - Modifying the runtime for your specific use case
112
+ - Contributing improvements back to the open source project
113
+
114
+ **Examples of PROHIBITED use:**
115
+ - Packaging the Elixir Agent Runtime as "SuperAgent Engine" and selling it
116
+ - Building a competitor workflow platform using Rungraf's core code
117
+
118
+ ---
119
+
120
+ ## Fair Code Principles
121
+
122
+ Rungraf follows the [Fair Code](https://faircode.io) principles. We believe in:
123
+
124
+ 1. **Transparency**: Source code is publicly available for review and contribution
125
+ 2. **Sustainability**: Protecting the project's ability to fund ongoing development
126
+ 3. **Community**: Encouraging contributions while preventing exploitation
127
+ 4. **Freedom**: Allowing use for internal business purposes and non-commercial projects
128
+
129
+ ---
130
+
131
+ ## Getting a Commercial License
132
+
133
+ If your use case falls under the restrictions above, you can obtain a commercial Rungraf Enterprise License. Enterprise licenses include:
134
+
135
+ - Multi-tenant SaaS deployment rights
136
+ - White-labeling permissions
137
+ - Priority support and SLA guarantees
138
+ - Enterprise features (SSO, advanced RBAC, audit logs)
139
+ - Custom development support
140
+
141
+ Contact us for enterprise licensing:
142
+ - **Email**: [enterprise@rungraf.io](mailto:enterprise@rungraf.io)
143
+ - **Website**: [https://rungraf.io/enterprise](https://rungraf.io/enterprise)
144
+
145
+ ---
146
+
147
+ **© 2024-2025 Rungraf Project**
148
+
149
+ This software is provided "as is", without warranty of any kind, express or implied.
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,22 @@
1
1
  import type { AuthInstance } from "../core";
2
2
  /**
3
3
  * Better Auth 认证状态同步组件
4
- * 使用官方的 useSession hook 同步认证状态
4
+ *
5
+ * 功能说明:
6
+ * - 使用 better-auth 的 useSession hook 同步认证状态到应用 store
7
+ * - 监听 authStore 状态,在用户登录成功后自动设置伴生团队
8
+ * - 确保登录流程的完整性和权限上下文的正确性
9
+ *
10
+ * 技术实现:
11
+ * - 使用 Legend State 的 use$ hook 响应式监听状态变化
12
+ * - 使用 useRef 确保伴生团队设置只执行一次
13
+ * - 分离 session 同步和团队设置的逻辑,保持代码清晰
14
+ *
15
+ * 边缘情况处理:
16
+ * - Session 加载中:设置 loading 状态
17
+ * - Session 错误:清空用户信息,重置团队设置状态
18
+ * - 无 Session 但有用户:保持用户信息(可能从 token 加载)
19
+ * - 用户登出:重置团队设置状态,允许下次登录时重新设置
5
20
  */
6
21
  export declare function AuthSync({ auth }: {
7
22
  auth: AuthInstance;
@@ -1 +1 @@
1
- {"version":3,"file":"auth-sync.d.ts","sourceRoot":"","sources":["../../src/components/auth-sync.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AA8C3C;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,QA8BxD"}
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;AAwP3C;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,QAoFxD"}
@@ -1,4 +1,5 @@
1
- import { useEffect } from "react";
1
+ import { useEffect, useRef } from "react";
2
+ import { use$ } from "@legendapp/state/react";
2
3
  /**
3
4
  * 将日期转换为 ISO 字符串
4
5
  */
@@ -26,35 +27,278 @@ function mapSessionUserToSessionUser(sessionUser, sessionCreatedAt) {
26
27
  updatedAt: toISOString(sessionUser.updatedAt) ?? "",
27
28
  };
28
29
  }
30
+ /**
31
+ * 检查是否为开发环境
32
+ */
33
+ const isDev = import.meta.env?.DEV ?? false;
34
+ /**
35
+ * 安全获取环境变量
36
+ */
37
+ function getEnvVar(key) {
38
+ return import.meta.env?.[key];
39
+ }
40
+ /**
41
+ * 解析 API 错误响应
42
+ */
43
+ async function parseErrorResponse(response) {
44
+ try {
45
+ const text = await response.text();
46
+ if (text) {
47
+ try {
48
+ const json = JSON.parse(text);
49
+ if (typeof json === "object" && json !== null) {
50
+ const data = json;
51
+ if (typeof data.error === "object" && data.error !== null) {
52
+ const errorObj = data.error;
53
+ if (typeof errorObj.message === "string") {
54
+ return errorObj.message;
55
+ }
56
+ }
57
+ else if (typeof data.message === "string") {
58
+ return data.message;
59
+ }
60
+ }
61
+ }
62
+ catch {
63
+ // 如果不是 JSON,返回原始文本(限制长度)
64
+ return text.length < 200 ? text : `${text.substring(0, 200)}...`;
65
+ }
66
+ }
67
+ }
68
+ catch {
69
+ // 忽略解析错误
70
+ }
71
+ return `HTTP ${response.status}: ${response.statusText}`;
72
+ }
73
+ /**
74
+ * 获取或创建伴生团队
75
+ *
76
+ * 功能说明:
77
+ * - 在用户登录成功后自动获取组织信息
78
+ * - 设置活动组织和团队,确保用户拥有正确的权限上下文
79
+ * - 使用 fetch API 直接调用 oneauth 和 better-auth 的 API
80
+ * - 所有错误都会被捕获,不会影响登录流程
81
+ *
82
+ * 边缘情况处理:
83
+ * - 环境变量未配置:静默跳过
84
+ * - Token 缺失:静默跳过
85
+ * - 网络错误:记录错误但不抛出
86
+ * - API 返回错误:记录详细错误信息
87
+ * - 无效响应数据:记录错误但不影响登录
88
+ */
89
+ async function setupCompanionTeam(_auth, token) {
90
+ try {
91
+ // 1. 验证环境变量配置
92
+ const oneAuthBaseUrl = getEnvVar("VITE_AUTH_API_URL") || getEnvVar("VITE_CORE_AI_API_URL");
93
+ if (!oneAuthBaseUrl) {
94
+ if (isDev) {
95
+ console.warn("[AuthSync] VITE_AUTH_API_URL is not configured, skipping companion team setup");
96
+ }
97
+ return;
98
+ }
99
+ // 2. 验证 token
100
+ if (!token || typeof token !== "string" || token.trim().length === 0) {
101
+ if (isDev) {
102
+ console.warn("[AuthSync] Token is missing or invalid, skipping companion team setup");
103
+ }
104
+ return;
105
+ }
106
+ // 3. 获取 token(优先使用 oneauth_token,如果没有则使用传入的 token)
107
+ const oneAuthTokenFromStorage = localStorage.getItem("oneauth_token");
108
+ const oneAuthToken = oneAuthTokenFromStorage ?? token;
109
+ if (!oneAuthToken || oneAuthToken.trim().length === 0) {
110
+ if (isDev) {
111
+ console.warn("[AuthSync] No valid token available, skipping companion team setup");
112
+ }
113
+ return;
114
+ }
115
+ // 4. Token 编码(与 fetchSessionWithToken 保持一致)
116
+ const encodedToken = encodeURIComponent(oneAuthToken);
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 ${encodedToken}`,
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 ${encodedToken}`,
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 ${encodedToken}`,
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
+ }
192
+ else if (isDev) {
193
+ console.warn("[AuthSync] First team has invalid ID, skipping team setup");
194
+ }
195
+ }
196
+ else if (isDev) {
197
+ console.log("[AuthSync] No teams found in organization");
198
+ }
199
+ }
200
+ catch (error) {
201
+ // 记录错误但不抛出,避免影响登录流程
202
+ const errorMessage = error instanceof Error ? error.message : String(error);
203
+ console.error("[AuthSync] Failed to setup companion team:", errorMessage);
204
+ // 在开发环境下输出更详细的错误信息
205
+ if (isDev && error instanceof Error) {
206
+ console.error("[AuthSync] Error details:", {
207
+ message: error.message,
208
+ stack: error.stack,
209
+ });
210
+ }
211
+ }
212
+ }
29
213
  /**
30
214
  * Better Auth 认证状态同步组件
31
- * 使用官方的 useSession hook 同步认证状态
215
+ *
216
+ * 功能说明:
217
+ * - 使用 better-auth 的 useSession hook 同步认证状态到应用 store
218
+ * - 监听 authStore 状态,在用户登录成功后自动设置伴生团队
219
+ * - 确保登录流程的完整性和权限上下文的正确性
220
+ *
221
+ * 技术实现:
222
+ * - 使用 Legend State 的 use$ hook 响应式监听状态变化
223
+ * - 使用 useRef 确保伴生团队设置只执行一次
224
+ * - 分离 session 同步和团队设置的逻辑,保持代码清晰
225
+ *
226
+ * 边缘情况处理:
227
+ * - Session 加载中:设置 loading 状态
228
+ * - Session 错误:清空用户信息,重置团队设置状态
229
+ * - 无 Session 但有用户:保持用户信息(可能从 token 加载)
230
+ * - 用户登出:重置团队设置状态,允许下次登录时重新设置
32
231
  */
33
232
  export function AuthSync({ auth }) {
34
- const { authClient, authActions } = auth;
35
- const { data: session, isPending, error } = authClient.useSession();
233
+ const { authClient, authActions, tokenStorage, authStore } = auth;
234
+ // 监听 authStore 中的用户状态(响应式)
235
+ const user = use$(authStore.user);
236
+ const isAuthenticated = use$(authStore.isAuthenticated);
237
+ const isLoaded = use$(authStore.isLoaded);
238
+ // 监听 better-auth 的 session(用于同步到 store)
239
+ const sessionResult = authClient.useSession();
240
+ const { data: session, isPending, error } = sessionResult || {
241
+ data: null,
242
+ isPending: false,
243
+ error: null
244
+ };
245
+ // 使用 ref 确保伴生团队设置只执行一次
246
+ const teamSetupRef = useRef(false);
247
+ // Effect 1: 同步 better-auth session 到 store
36
248
  useEffect(() => {
37
- // 根据 session 状态更新 store
249
+ // 加载中状态
38
250
  if (isPending) {
39
251
  authActions.setLoading(true);
252
+ return;
40
253
  }
41
- else {
42
- authActions.setLoading(false);
43
- if (error) {
44
- authActions.setError(error.message || "Authentication error");
45
- authActions.initialize(null, true);
46
- }
47
- else if (session) {
48
- // 映射用户数据:使用 session 的创建时间作为最后登录时间
49
- const user = mapSessionUserToSessionUser(session.user, session.session?.createdAt);
50
- authActions.initialize(user, true);
254
+ authActions.setLoading(false);
255
+ // 错误处理
256
+ if (error) {
257
+ const errorMessage = error instanceof Error
258
+ ? error.message
259
+ : (typeof error === "string" ? error : "Authentication error");
260
+ authActions.setError(errorMessage);
261
+ authActions.initialize(null, true);
262
+ teamSetupRef.current = false;
263
+ return;
264
+ }
265
+ // Session 存在:映射用户数据并更新 store
266
+ if (session?.user) {
267
+ try {
268
+ const mappedUser = mapSessionUserToSessionUser(session.user, session.session?.createdAt);
269
+ authActions.initialize(mappedUser, true);
51
270
  }
52
- else {
53
- // 没有 session,清空用户信息
54
- authActions.initialize(null, true);
271
+ catch (mappingError) {
272
+ console.error("[AuthSync] Failed to map session user:", mappingError);
273
+ // 映射失败时不清空用户信息,可能已有有效的用户数据
55
274
  }
56
275
  }
276
+ // 注意:没有 session 时不主动清空用户信息,因为可能已经从 token 加载了
57
277
  }, [session, isPending, error, authActions]);
278
+ // Effect 2: 监听 authStore 状态,设置伴生团队
279
+ useEffect(() => {
280
+ // 条件检查:只有在用户已认证且已加载完成时才设置伴生团队
281
+ if (isLoaded && isAuthenticated && user?.id && !teamSetupRef.current) {
282
+ teamSetupRef.current = true;
283
+ const token = tokenStorage.get();
284
+ // 验证 token 存在
285
+ if (token && typeof token === "string" && token.trim().length > 0) {
286
+ setupCompanionTeam(auth, token).catch((error) => {
287
+ const errorMessage = error instanceof Error ? error.message : String(error);
288
+ console.error("[AuthSync] Failed to setup companion team:", errorMessage);
289
+ // 失败后重置 ref,允许在特定条件下重试(如 token 更新后)
290
+ teamSetupRef.current = false;
291
+ });
292
+ }
293
+ else if (isDev) {
294
+ console.warn("[AuthSync] Token not found or invalid, cannot setup companion team");
295
+ }
296
+ }
297
+ // 用户登出:重置 ref,允许下次登录时重新设置
298
+ if (isLoaded && !isAuthenticated) {
299
+ teamSetupRef.current = false;
300
+ }
301
+ }, [user, isAuthenticated, isLoaded, auth, tokenStorage]);
58
302
  // 这是一个隐形组件,不渲染任何内容
59
303
  return null;
60
304
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@choiceform/shared-auth",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Shared authentication package for Choiceform projects",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -16,12 +16,6 @@
16
16
  "dist",
17
17
  "README.md"
18
18
  ],
19
- "scripts": {
20
- "build": "tsc",
21
- "dev": "tsc --watch",
22
- "clean": "rimraf dist",
23
- "prepublishOnly": "pnpm run build"
24
- },
25
19
  "repository": {
26
20
  "type": "git",
27
21
  "url": "git+https://github.com/choiceform/automation.git",
@@ -57,6 +51,10 @@
57
51
  "@types/react-dom": "18.2.22",
58
52
  "typescript": "^5.5.3",
59
53
  "rimraf": "^6.0.1"
54
+ },
55
+ "scripts": {
56
+ "build": "tsc",
57
+ "dev": "tsc --watch",
58
+ "clean": "rimraf dist"
60
59
  }
61
- }
62
-
60
+ }