@choiceform/shared-auth 0.1.16 → 0.1.18

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 (105) hide show
  1. package/README.md +286 -134
  2. package/dist/__tests__/auth-utils.test.d.ts +5 -0
  3. package/dist/__tests__/auth-utils.test.d.ts.map +1 -0
  4. package/dist/__tests__/auth-utils.test.js +96 -0
  5. package/dist/__tests__/store.test.d.ts +5 -0
  6. package/dist/__tests__/store.test.d.ts.map +1 -0
  7. package/dist/__tests__/store.test.js +210 -0
  8. package/dist/__tests__/user-mapper.test.d.ts +5 -0
  9. package/dist/__tests__/user-mapper.test.d.ts.map +1 -0
  10. package/dist/__tests__/user-mapper.test.js +76 -0
  11. package/dist/api/auth-api.d.ts +93 -9
  12. package/dist/api/auth-api.d.ts.map +1 -1
  13. package/dist/api/auth-api.js +219 -80
  14. package/dist/api/client.d.ts +10 -0
  15. package/dist/api/client.d.ts.map +1 -1
  16. package/dist/api/client.js +10 -0
  17. package/dist/api/organization-api.d.ts +2 -7
  18. package/dist/api/organization-api.d.ts.map +1 -1
  19. package/dist/api/organization-api.js +2 -17
  20. package/dist/api/team-api.d.ts +1 -5
  21. package/dist/api/team-api.d.ts.map +1 -1
  22. package/dist/api/team-api.js +5 -11
  23. package/dist/components/auth-sync.d.ts +27 -0
  24. package/dist/components/auth-sync.d.ts.map +1 -0
  25. package/dist/components/auth-sync.js +117 -0
  26. package/dist/components/protected-route.d.ts +18 -0
  27. package/dist/components/protected-route.d.ts.map +1 -0
  28. package/dist/components/protected-route.js +34 -0
  29. package/dist/components/sign-in-page.d.ts +21 -0
  30. package/dist/components/sign-in-page.d.ts.map +1 -0
  31. package/dist/components/sign-in-page.js +31 -0
  32. package/dist/config.js +1 -1
  33. package/dist/core.d.ts +148 -71
  34. package/dist/core.d.ts.map +1 -1
  35. package/dist/core.js +109 -28
  36. package/dist/hooks/index.d.ts +8 -0
  37. package/dist/hooks/index.d.ts.map +1 -0
  38. package/dist/hooks/index.js +7 -0
  39. package/dist/hooks/use-auth-init.d.ts +4 -3
  40. package/dist/hooks/use-auth-init.d.ts.map +1 -1
  41. package/dist/hooks/use-auth-init.js +18 -30
  42. package/dist/hooks/use-auth-sync.d.ts +60 -0
  43. package/dist/hooks/use-auth-sync.d.ts.map +1 -0
  44. package/dist/hooks/use-auth-sync.js +116 -0
  45. package/dist/hooks/use-email-verification.d.ts +85 -0
  46. package/dist/hooks/use-email-verification.d.ts.map +1 -0
  47. package/dist/hooks/use-email-verification.js +145 -0
  48. package/dist/hooks/use-protected-route.d.ts +67 -0
  49. package/dist/hooks/use-protected-route.d.ts.map +1 -0
  50. package/dist/hooks/use-protected-route.js +102 -0
  51. package/dist/index.d.ts +12 -6
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +43 -13
  54. package/dist/init.d.ts +127 -70
  55. package/dist/init.d.ts.map +1 -1
  56. package/dist/lib/auth-client.d.ts.map +1 -1
  57. package/dist/lib/auth-client.js +75 -2
  58. package/dist/services/auth-service.d.ts +101 -0
  59. package/dist/services/auth-service.d.ts.map +1 -0
  60. package/dist/services/auth-service.js +356 -0
  61. package/dist/services/callback-service.d.ts +33 -0
  62. package/dist/services/callback-service.d.ts.map +1 -0
  63. package/dist/services/callback-service.js +473 -0
  64. package/dist/services/companion-team.d.ts.map +1 -1
  65. package/dist/services/companion-team.js +41 -39
  66. package/dist/services/index.d.ts +2 -0
  67. package/dist/services/index.d.ts.map +1 -1
  68. package/dist/services/index.js +2 -0
  69. package/dist/store/actions.d.ts +54 -51
  70. package/dist/store/actions.d.ts.map +1 -1
  71. package/dist/store/actions.js +111 -243
  72. package/dist/store/computed.d.ts +72 -1
  73. package/dist/store/computed.d.ts.map +1 -1
  74. package/dist/store/computed.js +90 -3
  75. package/dist/store/index.d.ts +3 -3
  76. package/dist/store/index.d.ts.map +1 -1
  77. package/dist/store/index.js +2 -2
  78. package/dist/store/state.d.ts +10 -0
  79. package/dist/store/state.d.ts.map +1 -1
  80. package/dist/store/state.js +11 -1
  81. package/dist/store/utils.d.ts +3 -34
  82. package/dist/store/utils.d.ts.map +1 -1
  83. package/dist/store/utils.js +2 -22
  84. package/dist/types/auth.d.ts +106 -0
  85. package/dist/types/auth.d.ts.map +1 -1
  86. package/dist/types/callback.d.ts +35 -0
  87. package/dist/types/callback.d.ts.map +1 -0
  88. package/dist/types/callback.js +1 -0
  89. package/dist/types/index.d.ts +4 -3
  90. package/dist/types/index.d.ts.map +1 -1
  91. package/dist/types/organization.d.ts +19 -3
  92. package/dist/types/organization.d.ts.map +1 -1
  93. package/dist/types/team.d.ts +6 -2
  94. package/dist/types/team.d.ts.map +1 -1
  95. package/dist/types/user.d.ts +7 -3
  96. package/dist/types/user.d.ts.map +1 -1
  97. package/dist/utils/auth-utils.d.ts +60 -0
  98. package/dist/utils/auth-utils.d.ts.map +1 -0
  99. package/dist/utils/auth-utils.js +146 -0
  100. package/dist/utils/index.d.ts +1 -0
  101. package/dist/utils/index.d.ts.map +1 -1
  102. package/dist/utils/index.js +1 -0
  103. package/dist/utils/user-mapper.d.ts.map +1 -1
  104. package/dist/utils/user-mapper.js +2 -1
  105. package/package.json +10 -2
package/dist/core.js CHANGED
@@ -1,55 +1,136 @@
1
1
  /**
2
2
  * 认证系统核心
3
+ *
4
+ * 架构设计:
5
+ * - Store Layer: 响应式状态管理(authStore + storeActions)
6
+ * - Service Layer: 业务逻辑(authService)
7
+ * - API Layer: HTTP 请求(apiClient + authApi/organizationApi/teamApi)
8
+ *
9
+ * 使用方式:
10
+ * ```typescript
11
+ * // 初始化
12
+ * const auth = createAuth({ baseURL: 'https://api.example.com' })
13
+ *
14
+ * // 响应式读取状态
15
+ * const user = use$(auth.authStore.user)
16
+ *
17
+ * // 业务操作
18
+ * await auth.authService.signInWithEmail(email, password)
19
+ *
20
+ * // 状态更新
21
+ * auth.storeActions.setUser(user)
22
+ * ```
3
23
  */
4
24
  import { createAuthClientFromConfig } from "./lib/auth-client";
5
25
  import { createAuthStore } from "./store/state";
6
- import { createAuthActions } from "./store/actions";
26
+ import { createStoreActions } from "./store/actions";
7
27
  import { createAuthComputed } from "./store/computed";
8
- import { createBoundAuthUtils } from "./store/utils";
28
+ import { waitForAuth as waitForAuthUtil, createUserManager } from "./store/utils";
9
29
  import { createApiClient, createAuthApi, createOrganizationApi, createTeamApi } from "./api";
30
+ import { createAuthService } from "./services/auth-service";
10
31
  export function createAuth(config) {
32
+ // ============================================================
33
+ // 1. Better Auth 客户端
34
+ // ============================================================
11
35
  const authClient = createAuthClientFromConfig(config);
12
- const { authStore, tokenStorage } = createAuthStore({
13
- tokenStorageKey: config.tokenStorageKey || "auth-token",
14
- });
15
- // magicLink 由 magicLinkClient 插件提供,email 是 better-auth 内置方法
16
36
  const signIn = authClient.signIn;
17
37
  const signUp = authClient.signUp;
18
- const authActions = createAuthActions(authStore, tokenStorage, config, {
19
- signIn: {
20
- email: signIn.email,
21
- magicLink: signIn.magicLink,
22
- social: signIn.social,
23
- },
24
- signOut: authClient.signOut,
25
- signUp: signUp ? { email: signUp.email } : undefined,
38
+ const deleteUser = authClient.deleteUser;
39
+ // ============================================================
40
+ // 2. Store Layer - 响应式状态
41
+ // ============================================================
42
+ const { authStore, tokenStorage } = createAuthStore({
43
+ tokenStorageKey: config.tokenStorageKey || "auth-token",
26
44
  });
45
+ const storeActions = createStoreActions(authStore, tokenStorage);
27
46
  const authComputed = createAuthComputed(authStore);
47
+ // ============================================================
48
+ // 3. API Layer
49
+ // ============================================================
28
50
  const apiClient = createApiClient({
29
51
  authStore,
30
52
  tokenStorage,
31
- unauthorizedHandler: authActions,
53
+ unauthorizedHandler: storeActions,
32
54
  baseURL: config.baseURL,
33
55
  });
34
- const authApi = createAuthApi(apiClient, config.baseURL);
56
+ const authApi = createAuthApi(apiClient);
35
57
  const organizationApi = createOrganizationApi(apiClient, config.baseURL);
36
58
  const teamApi = createTeamApi(apiClient);
37
- const instance = {
59
+ // ============================================================
60
+ // 4. Service Layer - 业务逻辑
61
+ // ============================================================
62
+ const authService = createAuthService({
63
+ authApi,
64
+ storeActions,
65
+ tokenStorage,
66
+ }, {
67
+ deleteUser,
68
+ signIn: {
69
+ email: signIn.email,
70
+ magicLink: signIn.magicLink,
71
+ social: signIn.social,
72
+ },
73
+ signOut: authClient.signOut,
74
+ signUp: signUp ? { email: signUp.email } : undefined,
75
+ });
76
+ // ============================================================
77
+ // 5. Active 状态管理
78
+ // ============================================================
79
+ const basePath = "/v1/auth/organization";
80
+ async function setActiveOrganization(request) {
81
+ const response = await apiClient.post(`${basePath}/set-active`, request);
82
+ if (!response.ok) {
83
+ throw new Error(`Failed to set active organization: ${response.status}`);
84
+ }
85
+ storeActions.setActiveOrganizationId(response.data.id);
86
+ return response.data;
87
+ }
88
+ async function setActiveTeam(request) {
89
+ const response = await apiClient.post(`${basePath}/set-active-team`, request);
90
+ if (!response.ok) {
91
+ throw new Error(`Failed to set active team: ${response.status}`);
92
+ }
93
+ storeActions.setActiveTeamId(request.teamId);
94
+ }
95
+ async function setActiveOrganizationAndTeam(organizationId, teamId) {
96
+ const organization = await setActiveOrganization({ organizationId });
97
+ await setActiveTeam({ teamId });
98
+ return organization;
99
+ }
100
+ // ============================================================
101
+ // 6. 返回 Auth 实例
102
+ // ============================================================
103
+ return {
104
+ // API Layer
38
105
  apiClient,
39
- authActions,
40
106
  authApi,
41
- authClient,
42
- authComputed,
43
- authStore,
44
107
  organizationApi,
45
108
  teamApi,
46
- tokenStorage,
47
- };
48
- const utils = createBoundAuthUtils({
49
- apiClient,
50
- authActions,
109
+ // Store Layer
51
110
  authStore,
111
+ authComputed,
52
112
  tokenStorage,
53
- });
54
- return { ...instance, ...utils };
113
+ storeActions,
114
+ // Service Layer
115
+ authService,
116
+ // Active 状态管理
117
+ setActiveOrganization,
118
+ setActiveTeam,
119
+ setActiveOrganizationAndTeam,
120
+ // Better Auth Client(高级用法)
121
+ authClient,
122
+ // 快捷方法
123
+ getCurrentUser: storeActions.getUser,
124
+ getCurrentUserId: storeActions.getUserId,
125
+ isAuthenticated: storeActions.isAuthenticated,
126
+ isLoading: storeActions.isLoading,
127
+ isLoaded: storeActions.isLoaded,
128
+ getAuthToken: () => tokenStorage.get(),
129
+ getAuthHeaders: () => {
130
+ const token = tokenStorage.get();
131
+ return token ? { Authorization: `Bearer ${token}` } : {};
132
+ },
133
+ waitForAuth: () => waitForAuthUtil(authStore),
134
+ userManager: createUserManager(authStore),
135
+ };
55
136
  }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Hooks 导出
3
+ */
4
+ export { useAuthInit, initializeAuth } from "./use-auth-init";
5
+ export { useAuthSync, type UseAuthSyncConfig, type UseAuthSyncResult } from "./use-auth-sync";
6
+ export { useProtectedRoute, type UseProtectedRouteConfig, type UseProtectedRouteResult, type ProtectionStatus, } from "./use-protected-route";
7
+ export { useEmailVerification, type UseEmailVerificationConfig, type UseEmailVerificationResult, } from "./use-email-verification";
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAC7D,OAAO,EAAE,WAAW,EAAE,KAAK,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAC7F,OAAO,EACL,iBAAiB,EACjB,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,EAC5B,KAAK,gBAAgB,GACtB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,oBAAoB,EACpB,KAAK,0BAA0B,EAC/B,KAAK,0BAA0B,GAChC,MAAM,0BAA0B,CAAA"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Hooks 导出
3
+ */
4
+ export { useAuthInit, initializeAuth } from "./use-auth-init";
5
+ export { useAuthSync } from "./use-auth-sync";
6
+ export { useProtectedRoute, } from "./use-protected-route";
7
+ export { useEmailVerification, } from "./use-email-verification";
@@ -3,15 +3,16 @@ import type { AuthInstance } from "../core";
3
3
  * 初始化认证状态(非 hook 版本)
4
4
  * 可在 React Router 的 clientLoader 中调用
5
5
  *
6
+ * 职责:
7
+ * 1. 处理 URL 中的 token(OAuth 回调)并保存
8
+ * 2. 其他情况交给 Better Auth 的 useSession() 处理
9
+ *
6
10
  * @returns 是否已认证
7
11
  */
8
12
  export declare function initializeAuth(auth: AuthInstance): Promise<boolean>;
9
13
  /**
10
14
  * 认证初始化 hook
11
15
  * 在应用启动时检查 URL 中的 token 或 localStorage 中的 token
12
- *
13
- * 注意:在 React Router Framework 模式下,应使用 initializeAuth 函数
14
- * 在 clientLoader 中调用,而不是使用此 hook
15
16
  */
16
17
  export declare function useAuthInit(auth: AuthInstance): void;
17
18
  //# sourceMappingURL=use-auth-init.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-auth-init.d.ts","sourceRoot":"","sources":["../../src/hooks/use-auth-init.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,CAoDzE;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,QAQ7C"}
1
+ {"version":3,"file":"use-auth-init.d.ts","sourceRoot":"","sources":["../../src/hooks/use-auth-init.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,CAwCzE;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,QAQ7C"}
@@ -3,24 +3,24 @@ import { useEffect } from "react";
3
3
  * 初始化认证状态(非 hook 版本)
4
4
  * 可在 React Router 的 clientLoader 中调用
5
5
  *
6
+ * 职责:
7
+ * 1. 处理 URL 中的 token(OAuth 回调)并保存
8
+ * 2. 其他情况交给 Better Auth 的 useSession() 处理
9
+ *
6
10
  * @returns 是否已认证
7
11
  */
8
12
  export async function initializeAuth(auth) {
9
- const { authActions, tokenStorage, authStore } = auth;
13
+ const { authService, tokenStorage, authStore, storeActions } = auth;
10
14
  // 如果已经初始化,直接返回
11
15
  if (authStore.isLoaded.get()) {
12
16
  return authStore.isAuthenticated.get();
13
17
  }
14
- // 检查 URL 中的 token
18
+ // 检查 URL 中的 token(OAuth 回调)
15
19
  const urlParams = new URLSearchParams(window.location.search);
16
20
  let tokenFromUrl = urlParams.get("token");
17
21
  if (tokenFromUrl) {
18
- // URL 中的 token 可能被编码,尝试解码
19
- try {
20
- tokenFromUrl = decodeURIComponent(tokenFromUrl);
21
- }
22
- catch {
23
- // 解码失败,使用原值
22
+ if (decodeURIComponent(tokenFromUrl) === tokenFromUrl) {
23
+ tokenFromUrl = encodeURIComponent(tokenFromUrl);
24
24
  }
25
25
  // 立即清理 URL 中的 token 参数(避免暴露)
26
26
  urlParams.delete("token");
@@ -28,45 +28,33 @@ export async function initializeAuth(auth) {
28
28
  ? `${window.location.pathname}?${urlParams.toString()}`
29
29
  : window.location.pathname;
30
30
  window.history.replaceState({}, "", newUrl);
31
- // 使用 token 获取 session
31
+ // URL token 必须验证并保存(OAuth 回调)
32
32
  try {
33
- await authActions.fetchSessionWithToken(tokenFromUrl);
33
+ await authService.fetchAndSetSession(tokenFromUrl);
34
34
  return authStore.isAuthenticated.get();
35
35
  }
36
36
  catch {
37
- // Token 无效,继续检查 localStorage
38
- }
39
- }
40
- // 检查 localStorage 中是否有存储的 token
41
- const storedToken = tokenStorage.get();
42
- if (storedToken) {
43
- try {
44
- await authActions.fetchSessionWithToken(storedToken);
45
- return authStore.isAuthenticated.get();
46
- }
47
- catch {
48
- // Token 无效,初始化为未认证状态
49
- await authActions.initialize(null, true);
37
+ // Token 无效,清理并标记为已加载
38
+ tokenStorage.clear();
39
+ storeActions.initialize(null, true);
50
40
  return false;
51
41
  }
52
42
  }
53
- // 没有 token,标记为已加载
54
- await authActions.initialize(null, true);
55
- return false;
43
+ // 没有 URL token 时,不主动请求 session
44
+ // 让 Better Auth 的 useSession() 来处理 localStorage token
45
+ // 这里只标记初始化流程已完成(但 session 状态由 useSession 管理)
46
+ return authStore.isAuthenticated.get();
56
47
  }
57
48
  /**
58
49
  * 认证初始化 hook
59
50
  * 在应用启动时检查 URL 中的 token 或 localStorage 中的 token
60
- *
61
- * 注意:在 React Router Framework 模式下,应使用 initializeAuth 函数
62
- * 在 clientLoader 中调用,而不是使用此 hook
63
51
  */
64
52
  export function useAuthInit(auth) {
65
53
  useEffect(() => {
66
54
  initializeAuth(auth).catch((error) => {
67
55
  console.error("Failed to initialize auth:", error);
68
56
  // 即使认证初始化失败,也标记为已加载
69
- auth.authActions.initialize(null, true);
57
+ auth.storeActions.initialize(null, true);
70
58
  });
71
59
  }, [auth]);
72
60
  }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * 认证状态同步 Hook
3
+ *
4
+ * 职责:
5
+ * 1. 监听 better-auth 的 session 变化
6
+ * 2. 同步认证状态到 authStore
7
+ * 3. 处理新用户的 companion team 设置
8
+ *
9
+ * 注意:
10
+ * - URL token 参数的处理由 useAuthInit 负责
11
+ * - 主题/语言同步由项目自行处理
12
+ */
13
+ import type { AuthInstance } from "../core";
14
+ import type { SessionUser } from "../types";
15
+ /** useAuthSync 返回值类型 */
16
+ export interface UseAuthSyncResult {
17
+ isAuthenticated: boolean;
18
+ isLoaded: boolean;
19
+ isLoading: boolean;
20
+ user: SessionUser | null;
21
+ }
22
+ /** useAuthSync 配置 */
23
+ export interface UseAuthSyncConfig {
24
+ /**
25
+ * 认证状态变化回调
26
+ */
27
+ onAuthChange?: (isAuthenticated: boolean) => void;
28
+ /**
29
+ * 是否跳过 companion team 设置
30
+ * 某些页面(如删除用户回调)不需要设置 companion team
31
+ */
32
+ skipCompanionTeam?: boolean;
33
+ /**
34
+ * 跳过 companion team 的路径列表
35
+ * 如:['/auth/callback', '/auth/delete-success']
36
+ */
37
+ skipCompanionTeamPaths?: string[];
38
+ }
39
+ /**
40
+ * 认证状态同步 Hook
41
+ *
42
+ * 使用示例:
43
+ * ```tsx
44
+ * function App() {
45
+ * useAuthSync(auth, {
46
+ * skipCompanionTeamPaths: ['/auth/callback', '/auth/delete-success'],
47
+ * onAuthChange: (isAuthenticated) => {
48
+ * if (isAuthenticated) {
49
+ * syncTheme()
50
+ * syncLanguage()
51
+ * }
52
+ * }
53
+ * })
54
+ *
55
+ * return <YourApp />
56
+ * }
57
+ * ```
58
+ */
59
+ export declare function useAuthSync(auth: AuthInstance, config?: UseAuthSyncConfig): UseAuthSyncResult;
60
+ //# sourceMappingURL=use-auth-sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-auth-sync.d.ts","sourceRoot":"","sources":["../../src/hooks/use-auth-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAG3C,wBAAwB;AACxB,MAAM,WAAW,iBAAiB;IAChC,eAAe,EAAE,OAAO,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,WAAW,GAAG,IAAI,CAAA;CACzB;AAED,qBAAqB;AACrB,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,YAAY,CAAC,EAAE,CAAC,eAAe,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;OAGG;IACH,sBAAsB,CAAC,EAAE,MAAM,EAAE,CAAA;CAClC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,GAAE,iBAAsB,GAAG,iBAAiB,CA8FjG"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * 认证状态同步 Hook
3
+ *
4
+ * 职责:
5
+ * 1. 监听 better-auth 的 session 变化
6
+ * 2. 同步认证状态到 authStore
7
+ * 3. 处理新用户的 companion team 设置
8
+ *
9
+ * 注意:
10
+ * - URL token 参数的处理由 useAuthInit 负责
11
+ * - 主题/语言同步由项目自行处理
12
+ */
13
+ import { useEffect, useMemo, useRef } from "react";
14
+ import { setupCompanionTeam } from "../services/companion-team";
15
+ import { extractSessionUser } from "../utils/user-mapper";
16
+ /**
17
+ * 认证状态同步 Hook
18
+ *
19
+ * 使用示例:
20
+ * ```tsx
21
+ * function App() {
22
+ * useAuthSync(auth, {
23
+ * skipCompanionTeamPaths: ['/auth/callback', '/auth/delete-success'],
24
+ * onAuthChange: (isAuthenticated) => {
25
+ * if (isAuthenticated) {
26
+ * syncTheme()
27
+ * syncLanguage()
28
+ * }
29
+ * }
30
+ * })
31
+ *
32
+ * return <YourApp />
33
+ * }
34
+ * ```
35
+ */
36
+ export function useAuthSync(auth, config = {}) {
37
+ const { skipCompanionTeam = false, skipCompanionTeamPaths = [], onAuthChange, } = config;
38
+ const teamSetupRef = useRef(false);
39
+ const prevAuthRef = useRef(null);
40
+ // 获取 better-auth session
41
+ const { data: sessionData, isPending } = auth.authClient.useSession();
42
+ // 检测是否是新用户(从 URL 参数读取)
43
+ const isNewUser = useMemo(() => {
44
+ if (typeof window === "undefined")
45
+ return false;
46
+ const searchParams = new URLSearchParams(window.location.search);
47
+ return searchParams.get("isNew") === "true";
48
+ }, []);
49
+ // 检查当前路径是否应该跳过 companion team
50
+ const shouldSkipCompanionTeam = useMemo(() => {
51
+ if (skipCompanionTeam)
52
+ return true;
53
+ if (typeof window === "undefined")
54
+ return false;
55
+ const pathname = window.location.pathname;
56
+ return skipCompanionTeamPaths.some((path) => pathname === path || pathname.startsWith(path));
57
+ }, [skipCompanionTeam, skipCompanionTeamPaths]);
58
+ // 提取用户信息
59
+ const user = useMemo(() => extractSessionUser(sessionData), [sessionData]);
60
+ const isAuthenticated = !!user?.id;
61
+ const isLoaded = !isPending;
62
+ // 同步认证状态到 authStore
63
+ useEffect(() => {
64
+ auth.storeActions.setUser(user);
65
+ auth.authStore.isLoaded.set(isLoaded);
66
+ auth.authStore.loading.set(!isLoaded);
67
+ }, [user, isLoaded, auth]);
68
+ // Companion team 设置(只有邮箱验证后才执行)
69
+ useEffect(() => {
70
+ if (shouldSkipCompanionTeam)
71
+ return;
72
+ // 必须邮箱已验证才执行 companion team 设置
73
+ if (isLoaded &&
74
+ isAuthenticated &&
75
+ user?.id &&
76
+ user.emailVerified === true &&
77
+ !teamSetupRef.current) {
78
+ teamSetupRef.current = true;
79
+ const token = auth.tokenStorage.get();
80
+ if (token && typeof token === "string" && token.trim().length > 0) {
81
+ setupCompanionTeam(auth, token, {
82
+ onComplete: () => {
83
+ // 清除 isNew 参数
84
+ if (isNewUser && typeof window !== "undefined") {
85
+ const url = new URL(window.location.href);
86
+ url.searchParams.delete("isNew");
87
+ window.history.replaceState({}, "", url.pathname + url.search);
88
+ }
89
+ },
90
+ onError: () => {
91
+ teamSetupRef.current = false;
92
+ },
93
+ });
94
+ }
95
+ }
96
+ // 用户登出时重置
97
+ if (!user && teamSetupRef.current) {
98
+ teamSetupRef.current = false;
99
+ }
100
+ }, [user, isAuthenticated, isLoaded, auth, shouldSkipCompanionTeam, isNewUser]);
101
+ // 认证状态变化回调
102
+ useEffect(() => {
103
+ if (!onAuthChange)
104
+ return;
105
+ if (prevAuthRef.current === isAuthenticated)
106
+ return;
107
+ prevAuthRef.current = isAuthenticated;
108
+ onAuthChange(isAuthenticated);
109
+ }, [isAuthenticated, onAuthChange]);
110
+ return {
111
+ user,
112
+ isAuthenticated,
113
+ isLoaded,
114
+ isLoading: !isLoaded,
115
+ };
116
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * 邮箱验证 Hook
3
+ *
4
+ * 职责:
5
+ * 1. 重新发送验证邮件
6
+ * 2. 检查邮箱是否已验证
7
+ * 3. 更换邮箱(登出并跳转)
8
+ * 4. 倒计时逻辑
9
+ */
10
+ import type { AuthInstance } from "../core";
11
+ /** useEmailVerification 配置 */
12
+ export interface UseEmailVerificationConfig {
13
+ /** 邮箱地址 */
14
+ email: string;
15
+ /** 语言 */
16
+ lang?: string;
17
+ /** 倒计时时长(秒),默认 60 */
18
+ countdownDuration?: number;
19
+ /** 重定向函数 */
20
+ onRedirect?: (path: string) => void;
21
+ /** 发送成功回调 */
22
+ onSendSuccess?: (email: string) => void;
23
+ /** 发送失败回调 */
24
+ onSendError?: (error: Error) => void;
25
+ /** 已验证回调 */
26
+ onAlreadyVerified?: () => void;
27
+ }
28
+ /** useEmailVerification 返回值 */
29
+ export interface UseEmailVerificationResult {
30
+ /** 是否正在发送 */
31
+ isLoading: boolean;
32
+ /** 邮箱是否已验证 */
33
+ isAlreadyVerified: boolean;
34
+ /** 是否在倒计时中 */
35
+ isCountingDown: boolean;
36
+ /** 剩余倒计时秒数 */
37
+ countdown: number;
38
+ /** 重新发送验证邮件 */
39
+ resendVerification: () => Promise<void>;
40
+ /** 更换邮箱(登出并跳转) */
41
+ changeEmail: () => Promise<void>;
42
+ }
43
+ /**
44
+ * 邮箱验证 Hook
45
+ *
46
+ * 使用示例:
47
+ * ```tsx
48
+ * function VerifyEmailPage({ email, lang }) {
49
+ * const navigate = useNavigate()
50
+ *
51
+ * const {
52
+ * isLoading,
53
+ * isAlreadyVerified,
54
+ * isCountingDown,
55
+ * countdown,
56
+ * resendVerification,
57
+ * changeEmail
58
+ * } = useEmailVerification(auth, {
59
+ * email,
60
+ * lang,
61
+ * onRedirect: (path) => navigate(path),
62
+ * onSendSuccess: (email) => toast.success(`验证邮件已发送到 ${email}`),
63
+ * onSendError: () => toast.error('发送失败'),
64
+ * onAlreadyVerified: () => navigate('/community')
65
+ * })
66
+ *
67
+ * return (
68
+ * <div>
69
+ * {isAlreadyVerified ? (
70
+ * <p>邮箱已验证,正在跳转...</p>
71
+ * ) : (
72
+ * <>
73
+ * <button onClick={resendVerification} disabled={isLoading || isCountingDown}>
74
+ * {isCountingDown ? `${countdown}s 后重试` : '重新发送'}
75
+ * </button>
76
+ * <button onClick={changeEmail}>使用其他邮箱</button>
77
+ * </>
78
+ * )}
79
+ * </div>
80
+ * )
81
+ * }
82
+ * ```
83
+ */
84
+ export declare function useEmailVerification(auth: AuthInstance, config: UseEmailVerificationConfig): UseEmailVerificationResult;
85
+ //# sourceMappingURL=use-email-verification.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-email-verification.d.ts","sourceRoot":"","sources":["../../src/hooks/use-email-verification.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C,8BAA8B;AAC9B,MAAM,WAAW,0BAA0B;IACzC,WAAW;IACX,KAAK,EAAE,MAAM,CAAA;IACb,SAAS;IACT,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,qBAAqB;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,YAAY;IACZ,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,aAAa;IACb,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC,aAAa;IACb,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IACpC,YAAY;IACZ,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAA;CAC/B;AAED,+BAA+B;AAC/B,MAAM,WAAW,0BAA0B;IACzC,aAAa;IACb,SAAS,EAAE,OAAO,CAAA;IAClB,cAAc;IACd,iBAAiB,EAAE,OAAO,CAAA;IAC1B,cAAc;IACd,cAAc,EAAE,OAAO,CAAA;IACvB,cAAc;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,eAAe;IACf,kBAAkB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACvC,kBAAkB;IAClB,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACjC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,0BAA0B,GACjC,0BAA0B,CA4G5B"}