@choiceform/shared-auth 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +452 -0
- package/dist/components/auth-sync.d.ts +9 -0
- package/dist/components/auth-sync.d.ts.map +1 -0
- package/dist/components/auth-sync.js +60 -0
- package/dist/components/protected-route.d.ts +18 -0
- package/dist/components/protected-route.d.ts.map +1 -0
- package/dist/components/protected-route.js +28 -0
- package/dist/components/sign-in-page.d.ts +49 -0
- package/dist/components/sign-in-page.d.ts.map +1 -0
- package/dist/components/sign-in-page.js +33 -0
- package/dist/config.d.ts +50 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +14 -0
- package/dist/core.d.ts +2162 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +37 -0
- package/dist/hooks/use-auth-init.d.ts +7 -0
- package/dist/hooks/use-auth-init.d.ts.map +1 -0
- package/dist/hooks/use-auth-init.js +41 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/init.d.ts +2167 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +17 -0
- package/dist/lib/auth-client.d.ts +2120 -0
- package/dist/lib/auth-client.d.ts.map +1 -0
- package/dist/lib/auth-client.js +11 -0
- package/dist/store/actions.d.ts +60 -0
- package/dist/store/actions.d.ts.map +1 -0
- package/dist/store/actions.js +234 -0
- package/dist/store/computed.d.ts +12 -0
- package/dist/store/computed.d.ts.map +1 -0
- package/dist/store/computed.js +14 -0
- package/dist/store/state.d.ts +16 -0
- package/dist/store/state.d.ts.map +1 -0
- package/dist/store/state.js +52 -0
- package/dist/store/utils.d.ts +103 -0
- package/dist/store/utils.d.ts.map +1 -0
- package/dist/store/utils.js +198 -0
- package/dist/types.d.ts +37 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/package.json +65 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-client.d.ts","sourceRoot":"","sources":["../../src/lib/auth-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createAuthClient } from "better-auth/react";
|
|
2
|
+
/**
|
|
3
|
+
* 创建 Better Auth 客户端
|
|
4
|
+
*/
|
|
5
|
+
export function createAuthClientFromConfig(config) {
|
|
6
|
+
const { baseURL, plugins = [] } = config;
|
|
7
|
+
return createAuthClient({
|
|
8
|
+
baseURL: `${baseURL}/v1/auth`,
|
|
9
|
+
plugins,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Observable } from "@legendapp/state";
|
|
2
|
+
import type { AuthState, SessionUser } from "../types";
|
|
3
|
+
import type { AuthConfig } from "../config";
|
|
4
|
+
import type { TokenStorage } from "./state";
|
|
5
|
+
/**
|
|
6
|
+
* 创建认证 actions
|
|
7
|
+
*/
|
|
8
|
+
export declare function createAuthActions(authStore: Observable<AuthState>, tokenStorage: TokenStorage, config: AuthConfig, authClient: {
|
|
9
|
+
signIn: {
|
|
10
|
+
social: (options: {
|
|
11
|
+
callbackURL: string;
|
|
12
|
+
provider: string;
|
|
13
|
+
}) => Promise<unknown>;
|
|
14
|
+
};
|
|
15
|
+
signOut: () => Promise<unknown>;
|
|
16
|
+
}): {
|
|
17
|
+
/**
|
|
18
|
+
* 初始化认证状态
|
|
19
|
+
*/
|
|
20
|
+
initialize(user: SessionUser | null, isLoaded: boolean): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* 使用 token 获取 session
|
|
23
|
+
*/
|
|
24
|
+
fetchSessionWithToken(token: string): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* 处理未授权错误(401 或其他认证失败)
|
|
27
|
+
* 清理所有认证状态和本地存储
|
|
28
|
+
*/
|
|
29
|
+
handleUnauthorized(): void;
|
|
30
|
+
/**
|
|
31
|
+
* 设置加载状态
|
|
32
|
+
*/
|
|
33
|
+
setLoading(loading: boolean): void;
|
|
34
|
+
/**
|
|
35
|
+
* 设置错误信息
|
|
36
|
+
*/
|
|
37
|
+
setError(error: string | null): void;
|
|
38
|
+
/**
|
|
39
|
+
* 登录 - 使用 OAuth(默认 GitHub)
|
|
40
|
+
*
|
|
41
|
+
* @param provider OAuth 提供商,默认为 'github'
|
|
42
|
+
* @param redirectTo 登录成功后的重定向地址(完整 URL 或相对路径)
|
|
43
|
+
* 如果未提供,将使用 defaultRedirectAfterLogin 配置
|
|
44
|
+
*/
|
|
45
|
+
signIn(provider?: string, redirectTo?: string): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* 登出
|
|
48
|
+
*/
|
|
49
|
+
signOut(): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* 更新用户信息
|
|
52
|
+
*/
|
|
53
|
+
updateUser(user: SessionUser | null): void;
|
|
54
|
+
/**
|
|
55
|
+
* 获取当前用户
|
|
56
|
+
*/
|
|
57
|
+
getUser(): SessionUser | null;
|
|
58
|
+
};
|
|
59
|
+
export type AuthActions = ReturnType<typeof createAuthActions>;
|
|
60
|
+
//# sourceMappingURL=actions.d.ts.map
|
|
@@ -0,0 +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;AAoD3C;;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;;OAEG;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"}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 将日期转换为 ISO 字符串
|
|
3
|
+
*/
|
|
4
|
+
function toISOString(date) {
|
|
5
|
+
if (!date)
|
|
6
|
+
return undefined;
|
|
7
|
+
return typeof date === "string" ? date : new Date(date).toISOString();
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* 从服务器响应中提取用户数据
|
|
11
|
+
* 支持多种数据结构格式
|
|
12
|
+
*/
|
|
13
|
+
function extractUserData(sessionData) {
|
|
14
|
+
if (!sessionData || typeof sessionData !== "object")
|
|
15
|
+
return null;
|
|
16
|
+
const data = sessionData;
|
|
17
|
+
// 尝试从不同的数据结构中提取用户信息
|
|
18
|
+
// 优先使用标准格式:{session: {...}, user: {...}}
|
|
19
|
+
const rawUserData = data.user || // 标准格式
|
|
20
|
+
data.session?.user || // 嵌套格式
|
|
21
|
+
data.data?.user || // 包装格式
|
|
22
|
+
(data.id && data.email ? data : null); // 扁平格式
|
|
23
|
+
if (!rawUserData || typeof rawUserData !== "object")
|
|
24
|
+
return null;
|
|
25
|
+
const user = rawUserData;
|
|
26
|
+
// 提取 session 创建时间作为最后登录时间
|
|
27
|
+
const sessionCreatedAt = data.session?.createdAt
|
|
28
|
+
? toISOString(data.session.createdAt)
|
|
29
|
+
: undefined;
|
|
30
|
+
return {
|
|
31
|
+
banExpires: user.banExpires,
|
|
32
|
+
banReason: user.banReason,
|
|
33
|
+
banned: user.banned,
|
|
34
|
+
createdAt: toISOString(user.createdAt) ?? "",
|
|
35
|
+
email: user.email,
|
|
36
|
+
emailVerified: user.emailVerified ?? false,
|
|
37
|
+
id: user.id,
|
|
38
|
+
image: user.image || undefined,
|
|
39
|
+
lastLoginAt: sessionCreatedAt,
|
|
40
|
+
name: user.name,
|
|
41
|
+
role: user.role,
|
|
42
|
+
updatedAt: toISOString(user.updatedAt) ?? "",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 创建认证 actions
|
|
47
|
+
*/
|
|
48
|
+
export function createAuthActions(authStore, tokenStorage, config, authClient) {
|
|
49
|
+
const { baseURL, defaultRedirectAfterLogin = "/explore", signInPath = "/sign-in", getSessionEndpoint = "/v1/auth/get-session", callbackURLBuilder, } = config;
|
|
50
|
+
const buildCallbackURL = callbackURLBuilder || ((redirectTo, baseURL) => {
|
|
51
|
+
return `${baseURL}/v1/auth/redirect?to=${encodeURIComponent(redirectTo)}`;
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
/**
|
|
55
|
+
* 初始化认证状态
|
|
56
|
+
*/
|
|
57
|
+
async initialize(user, isLoaded) {
|
|
58
|
+
const storedToken = tokenStorage.get();
|
|
59
|
+
if (!user && storedToken) {
|
|
60
|
+
await this.fetchSessionWithToken(storedToken);
|
|
61
|
+
}
|
|
62
|
+
else if (user) {
|
|
63
|
+
authStore.user.set(user);
|
|
64
|
+
authStore.isAuthenticated.set(true);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
authStore.user.set(null);
|
|
68
|
+
authStore.isAuthenticated.set(false);
|
|
69
|
+
}
|
|
70
|
+
authStore.isLoaded.set(isLoaded);
|
|
71
|
+
authStore.loading.set(!isLoaded);
|
|
72
|
+
},
|
|
73
|
+
/**
|
|
74
|
+
* 使用 token 获取 session
|
|
75
|
+
*/
|
|
76
|
+
async fetchSessionWithToken(token) {
|
|
77
|
+
try {
|
|
78
|
+
if (!token) {
|
|
79
|
+
throw new Error("Token is required");
|
|
80
|
+
}
|
|
81
|
+
// 保存 token 到 localStorage
|
|
82
|
+
tokenStorage.save(token);
|
|
83
|
+
const endpoint = `${baseURL}${getSessionEndpoint}`;
|
|
84
|
+
const response = await fetch(endpoint, {
|
|
85
|
+
method: "GET",
|
|
86
|
+
headers: {
|
|
87
|
+
Authorization: `Bearer ${encodeURIComponent(token)}`,
|
|
88
|
+
"Content-Type": "application/json",
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
const responseText = await response.text();
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
throw new Error(`Failed to fetch session: ${response.status}`);
|
|
94
|
+
}
|
|
95
|
+
// 解析响应数据
|
|
96
|
+
let sessionData = null;
|
|
97
|
+
try {
|
|
98
|
+
if (responseText && responseText !== "null" && responseText !== "") {
|
|
99
|
+
sessionData = JSON.parse(responseText);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
console.error("Failed to parse session response:", error);
|
|
104
|
+
this.handleUnauthorized();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
// 提取并映射用户数据
|
|
108
|
+
const userData = extractUserData(sessionData);
|
|
109
|
+
if (userData) {
|
|
110
|
+
authStore.user.set(userData);
|
|
111
|
+
authStore.isAuthenticated.set(true);
|
|
112
|
+
authStore.isLoaded.set(true);
|
|
113
|
+
authStore.loading.set(false);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
console.error("Invalid session data received");
|
|
117
|
+
this.handleUnauthorized();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
console.error("Failed to fetch session:", error);
|
|
122
|
+
this.handleUnauthorized();
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
/**
|
|
126
|
+
* 处理未授权错误(401 或其他认证失败)
|
|
127
|
+
* 清理所有认证状态和本地存储
|
|
128
|
+
*/
|
|
129
|
+
handleUnauthorized() {
|
|
130
|
+
authStore.user.set(null);
|
|
131
|
+
authStore.isAuthenticated.set(false);
|
|
132
|
+
tokenStorage.clear();
|
|
133
|
+
},
|
|
134
|
+
/**
|
|
135
|
+
* 设置加载状态
|
|
136
|
+
*/
|
|
137
|
+
setLoading(loading) {
|
|
138
|
+
authStore.loading.set(loading);
|
|
139
|
+
},
|
|
140
|
+
/**
|
|
141
|
+
* 设置错误信息
|
|
142
|
+
*/
|
|
143
|
+
setError(error) {
|
|
144
|
+
authStore.error.set(error);
|
|
145
|
+
},
|
|
146
|
+
/**
|
|
147
|
+
* 登录 - 使用 OAuth(默认 GitHub)
|
|
148
|
+
*
|
|
149
|
+
* @param provider OAuth 提供商,默认为 'github'
|
|
150
|
+
* @param redirectTo 登录成功后的重定向地址(完整 URL 或相对路径)
|
|
151
|
+
* 如果未提供,将使用 defaultRedirectAfterLogin 配置
|
|
152
|
+
*/
|
|
153
|
+
async signIn(provider = "github", redirectTo) {
|
|
154
|
+
// 防止重复点击
|
|
155
|
+
if (authStore.loading.get()) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
authStore.loading.set(true);
|
|
160
|
+
authStore.error.set(null);
|
|
161
|
+
if (!baseURL) {
|
|
162
|
+
throw new Error("Auth baseURL is not configured");
|
|
163
|
+
}
|
|
164
|
+
// 构建重定向地址:相对路径自动添加 origin
|
|
165
|
+
const finalRedirectTo = redirectTo
|
|
166
|
+
? redirectTo.startsWith("http")
|
|
167
|
+
? redirectTo
|
|
168
|
+
: `${window.location.origin}${redirectTo}`
|
|
169
|
+
: `${window.location.origin}${defaultRedirectAfterLogin}`;
|
|
170
|
+
const callbackURL = buildCallbackURL(finalRedirectTo, baseURL);
|
|
171
|
+
await authClient.signIn.social({
|
|
172
|
+
provider,
|
|
173
|
+
callbackURL,
|
|
174
|
+
});
|
|
175
|
+
// 注意:OAuth 重定向后,loading 状态会在页面刷新时重置
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
const message = error instanceof Error ? error.message : "Sign in failed";
|
|
179
|
+
authStore.error.set(message);
|
|
180
|
+
authStore.loading.set(false);
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
/**
|
|
184
|
+
* 登出
|
|
185
|
+
*/
|
|
186
|
+
async signOut() {
|
|
187
|
+
// 防止重复调用
|
|
188
|
+
if (authStore.loading.get()) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
authStore.loading.set(true);
|
|
193
|
+
authStore.error.set(null);
|
|
194
|
+
await authClient.signOut();
|
|
195
|
+
// 清理本地状态
|
|
196
|
+
authStore.user.set(null);
|
|
197
|
+
authStore.isAuthenticated.set(false);
|
|
198
|
+
tokenStorage.clear();
|
|
199
|
+
// 重定向到登录页
|
|
200
|
+
window.location.href = signInPath;
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
const message = error instanceof Error ? error.message : "Sign out failed";
|
|
204
|
+
authStore.error.set(message);
|
|
205
|
+
// 即使出错,也尝试清理本地状态
|
|
206
|
+
authStore.user.set(null);
|
|
207
|
+
authStore.isAuthenticated.set(false);
|
|
208
|
+
tokenStorage.clear();
|
|
209
|
+
}
|
|
210
|
+
finally {
|
|
211
|
+
authStore.loading.set(false);
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
/**
|
|
215
|
+
* 更新用户信息
|
|
216
|
+
*/
|
|
217
|
+
updateUser(user) {
|
|
218
|
+
if (user) {
|
|
219
|
+
authStore.user.set(user);
|
|
220
|
+
authStore.isAuthenticated.set(true);
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
authStore.user.set(null);
|
|
224
|
+
authStore.isAuthenticated.set(false);
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
/**
|
|
228
|
+
* 获取当前用户
|
|
229
|
+
*/
|
|
230
|
+
getUser() {
|
|
231
|
+
return authStore.user.get();
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Observable } from "@legendapp/state";
|
|
2
|
+
import type { AuthState } from "../types";
|
|
3
|
+
/**
|
|
4
|
+
* 创建认证 computed
|
|
5
|
+
*/
|
|
6
|
+
export declare function createAuthComputed(authStore: Observable<AuthState>): {
|
|
7
|
+
/**
|
|
8
|
+
* 是否正在初始化
|
|
9
|
+
*/
|
|
10
|
+
isInitializing: import("@legendapp/state").ObservableBoolean;
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=computed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"computed.d.ts","sourceRoot":"","sources":["../../src/store/computed.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAEzC;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,UAAU,CAAC,SAAS,CAAC;IAE/D;;OAEG;;EAKN"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AuthState } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* 创建认证状态 store
|
|
4
|
+
*/
|
|
5
|
+
export declare function createAuthStore(config: {
|
|
6
|
+
tokenStorageKey: string;
|
|
7
|
+
}): {
|
|
8
|
+
authStore: import("@legendapp/state").Observable<AuthState>;
|
|
9
|
+
tokenStorage: {
|
|
10
|
+
save(token: string | null): void;
|
|
11
|
+
get(): string | null;
|
|
12
|
+
clear(): void;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export type TokenStorage = ReturnType<typeof createAuthStore>["tokenStorage"];
|
|
16
|
+
//# sourceMappingURL=state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/store/state.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAEzC;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE;IAAE,eAAe,EAAE,MAAM,CAAA;CAAE;;;oBAuBnD,MAAM,GAAG,IAAI;eAclB,MAAM,GAAG,IAAI;;;EAavB;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,cAAc,CAAC,CAAA"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { observable } from "@legendapp/state";
|
|
2
|
+
/**
|
|
3
|
+
* 创建认证状态 store
|
|
4
|
+
*/
|
|
5
|
+
export function createAuthStore(config) {
|
|
6
|
+
const { tokenStorageKey } = config;
|
|
7
|
+
// 从 localStorage 读取初始 token
|
|
8
|
+
const getStoredToken = () => {
|
|
9
|
+
try {
|
|
10
|
+
return localStorage.getItem(tokenStorageKey);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
const authStore = observable({
|
|
17
|
+
user: null,
|
|
18
|
+
isAuthenticated: false,
|
|
19
|
+
isLoaded: false,
|
|
20
|
+
loading: true,
|
|
21
|
+
error: null,
|
|
22
|
+
token: getStoredToken(),
|
|
23
|
+
});
|
|
24
|
+
// 手动管理 token 的 localStorage 存储
|
|
25
|
+
const tokenStorage = {
|
|
26
|
+
save(token) {
|
|
27
|
+
try {
|
|
28
|
+
if (token) {
|
|
29
|
+
localStorage.setItem(tokenStorageKey, token);
|
|
30
|
+
authStore.token.set(token);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
localStorage.removeItem(tokenStorageKey);
|
|
34
|
+
authStore.token.set(null);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
console.error("Failed to save token to localStorage:", error);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
get() {
|
|
42
|
+
return getStoredToken();
|
|
43
|
+
},
|
|
44
|
+
clear() {
|
|
45
|
+
this.save(null);
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
return {
|
|
49
|
+
authStore,
|
|
50
|
+
tokenStorage,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { Observable } from "@legendapp/state";
|
|
2
|
+
import type { AuthState, SessionUser } from "../types";
|
|
3
|
+
import type { TokenStorage } from "./state";
|
|
4
|
+
import type { AuthActions } from "./actions";
|
|
5
|
+
/**
|
|
6
|
+
* 获取当前用户
|
|
7
|
+
*/
|
|
8
|
+
export declare function getCurrentUser(authStore: Observable<AuthState>): SessionUser | null;
|
|
9
|
+
/**
|
|
10
|
+
* 获取当前用户ID
|
|
11
|
+
*/
|
|
12
|
+
export declare function getCurrentUserId(authStore: Observable<AuthState>): string | null;
|
|
13
|
+
/**
|
|
14
|
+
* 安全获取当前用户ID
|
|
15
|
+
*/
|
|
16
|
+
export declare function getCurrentUserIdSafe(authStore: Observable<AuthState>): string | null;
|
|
17
|
+
/**
|
|
18
|
+
* 检查是否已认证
|
|
19
|
+
*/
|
|
20
|
+
export declare function isAuthenticated(authStore: Observable<AuthState>): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* 检查是否正在加载
|
|
23
|
+
*/
|
|
24
|
+
export declare function isLoading(authStore: Observable<AuthState>): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* 检查是否已加载
|
|
27
|
+
*/
|
|
28
|
+
export declare function isLoaded(authStore: Observable<AuthState>): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* 等待认证完成
|
|
31
|
+
* 使用轮询方式检查认证状态(简单但有效)
|
|
32
|
+
*
|
|
33
|
+
* 注意:如果长时间未加载,Promise 不会自动 reject
|
|
34
|
+
* 建议在外层添加超时处理
|
|
35
|
+
*/
|
|
36
|
+
export declare function waitForAuth(authStore: Observable<AuthState>): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* 获取认证令牌
|
|
39
|
+
*/
|
|
40
|
+
export declare function getAuthToken(authStore: Observable<AuthState>, tokenStorage: TokenStorage, authActions: AuthActions, authClient: {
|
|
41
|
+
getSession: () => Promise<unknown>;
|
|
42
|
+
}): Promise<string | null>;
|
|
43
|
+
/**
|
|
44
|
+
* 获取认证头
|
|
45
|
+
*/
|
|
46
|
+
export declare function getAuthHeaders(authStore: Observable<AuthState>, tokenStorage: TokenStorage, authActions: AuthActions, authClient: {
|
|
47
|
+
getSession: () => Promise<unknown>;
|
|
48
|
+
}): Promise<Record<string, string>>;
|
|
49
|
+
/**
|
|
50
|
+
* 处理 401 响应
|
|
51
|
+
*/
|
|
52
|
+
export declare function handle401Response(response: Response, authActions: AuthActions): Response;
|
|
53
|
+
/**
|
|
54
|
+
* API 客户端工具
|
|
55
|
+
*/
|
|
56
|
+
export declare function createApiClient(authStore: Observable<AuthState>, tokenStorage: TokenStorage, authActions: AuthActions, authClient: {
|
|
57
|
+
getSession: () => Promise<unknown>;
|
|
58
|
+
}): {
|
|
59
|
+
get(url: string, options?: RequestInit): Promise<Response>;
|
|
60
|
+
post(url: string, body?: unknown, options?: RequestInit): Promise<Response>;
|
|
61
|
+
put(url: string, body?: unknown, options?: RequestInit): Promise<Response>;
|
|
62
|
+
delete(url: string, options?: RequestInit): Promise<Response>;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* 用户管理器
|
|
66
|
+
*/
|
|
67
|
+
export declare function createUserManager(authStore: Observable<AuthState>): {
|
|
68
|
+
getUser(): SessionUser | null;
|
|
69
|
+
getUserId(): string | null;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* 从 AuthInstance 创建绑定好的工具函数集合
|
|
73
|
+
* 这样就不需要在每个项目中重复包装工具函数
|
|
74
|
+
*/
|
|
75
|
+
export declare function createBoundAuthUtils(instance: {
|
|
76
|
+
authStore: Observable<AuthState>;
|
|
77
|
+
tokenStorage: TokenStorage;
|
|
78
|
+
authActions: AuthActions;
|
|
79
|
+
authClient: {
|
|
80
|
+
getSession: () => Promise<unknown>;
|
|
81
|
+
};
|
|
82
|
+
}): {
|
|
83
|
+
getCurrentUser: () => SessionUser | null;
|
|
84
|
+
getCurrentUserId: () => string | null;
|
|
85
|
+
getCurrentUserIdSafe: () => string | null;
|
|
86
|
+
isAuthenticated: () => boolean;
|
|
87
|
+
isLoading: () => boolean;
|
|
88
|
+
isLoaded: () => boolean;
|
|
89
|
+
waitForAuth: () => Promise<void>;
|
|
90
|
+
getAuthToken: () => Promise<string | null>;
|
|
91
|
+
getAuthHeaders: () => Promise<Record<string, string>>;
|
|
92
|
+
apiClient: {
|
|
93
|
+
get(url: string, options?: RequestInit): Promise<Response>;
|
|
94
|
+
post(url: string, body?: unknown, options?: RequestInit): Promise<Response>;
|
|
95
|
+
put(url: string, body?: unknown, options?: RequestInit): Promise<Response>;
|
|
96
|
+
delete(url: string, options?: RequestInit): Promise<Response>;
|
|
97
|
+
};
|
|
98
|
+
userManager: {
|
|
99
|
+
getUser(): SessionUser | null;
|
|
100
|
+
getUserId(): string | null;
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/store/utils.ts"],"names":[],"mappings":"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"}
|