@yiminlab/authkit 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/dist/index-jde0aIND.d.mts +349 -0
- package/dist/index-jde0aIND.d.ts +349 -0
- package/dist/index.d.mts +80 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.js +737 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +695 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react/index.d.mts +3 -0
- package/dist/react/index.d.ts +3 -0
- package/dist/react/index.js +708 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/index.mjs +677 -0
- package/dist/react/index.mjs.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,708 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/react/index.ts
|
|
21
|
+
var react_exports = {};
|
|
22
|
+
__export(react_exports, {
|
|
23
|
+
AuthGuard: () => AuthGuard,
|
|
24
|
+
CallbackHandler: () => CallbackHandler,
|
|
25
|
+
useAccessToken: () => useAccessToken,
|
|
26
|
+
useAuth: () => useAuth,
|
|
27
|
+
withAuthGuard: () => withAuthGuard
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(react_exports);
|
|
30
|
+
|
|
31
|
+
// src/react/use-auth.ts
|
|
32
|
+
var import_react = require("react");
|
|
33
|
+
|
|
34
|
+
// src/core/types.ts
|
|
35
|
+
var DEFAULT_AUTH_CONFIG = {
|
|
36
|
+
portalUrl: typeof process !== "undefined" ? process.env.NEXT_PUBLIC_PORTAL_URL || "https://yiminlab.site" : "https://yiminlab.site",
|
|
37
|
+
apiBaseUrl: typeof process !== "undefined" ? process.env.NEXT_PUBLIC_API_BASE_URL || "https://api.yiminlab.site" : "https://api.yiminlab.site",
|
|
38
|
+
callbackPath: "/auth/callback",
|
|
39
|
+
tokenKeys: {
|
|
40
|
+
accessToken: "authkit_access_token",
|
|
41
|
+
refreshToken: "authkit_refresh_token",
|
|
42
|
+
tokenExpiry: "authkit_token_expiry"
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// src/core/token-manager.ts
|
|
47
|
+
var LocalStorageAdapter = class {
|
|
48
|
+
getItem(key) {
|
|
49
|
+
if (typeof window === "undefined") return null;
|
|
50
|
+
return localStorage.getItem(key);
|
|
51
|
+
}
|
|
52
|
+
setItem(key, value) {
|
|
53
|
+
if (typeof window === "undefined") return;
|
|
54
|
+
localStorage.setItem(key, value);
|
|
55
|
+
}
|
|
56
|
+
removeItem(key) {
|
|
57
|
+
if (typeof window === "undefined") return;
|
|
58
|
+
localStorage.removeItem(key);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
var TokenManager = class {
|
|
62
|
+
constructor(storage, config) {
|
|
63
|
+
this.storage = storage || new LocalStorageAdapter();
|
|
64
|
+
this.config = { ...DEFAULT_AUTH_CONFIG, ...config };
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 保存 tokens
|
|
68
|
+
*/
|
|
69
|
+
setTokens(accessToken, refreshToken, expiresIn) {
|
|
70
|
+
const expiryTime = Date.now() + expiresIn * 1e3;
|
|
71
|
+
this.storage.setItem(this.config.tokenKeys.accessToken, accessToken);
|
|
72
|
+
this.storage.setItem(this.config.tokenKeys.refreshToken, refreshToken);
|
|
73
|
+
this.storage.setItem(this.config.tokenKeys.tokenExpiry, expiryTime.toString());
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* 获取 Access Token
|
|
77
|
+
*/
|
|
78
|
+
getAccessToken() {
|
|
79
|
+
return this.storage.getItem(this.config.tokenKeys.accessToken);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 获取 Refresh Token
|
|
83
|
+
*/
|
|
84
|
+
getRefreshToken() {
|
|
85
|
+
return this.storage.getItem(this.config.tokenKeys.refreshToken);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 获取 Token 过期时间
|
|
89
|
+
*/
|
|
90
|
+
getTokenExpiry() {
|
|
91
|
+
const expiry = this.storage.getItem(this.config.tokenKeys.tokenExpiry);
|
|
92
|
+
return expiry ? parseInt(expiry, 10) : null;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 检查 Access Token 是否过期
|
|
96
|
+
* 提前 60 秒认为过期,留出刷新时间
|
|
97
|
+
*/
|
|
98
|
+
isAccessTokenExpired() {
|
|
99
|
+
const expiryTime = this.getTokenExpiry();
|
|
100
|
+
if (!expiryTime) return true;
|
|
101
|
+
const now = Date.now();
|
|
102
|
+
return now >= expiryTime - 6e4;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* 清除所有 tokens
|
|
106
|
+
*/
|
|
107
|
+
clearTokens() {
|
|
108
|
+
this.storage.removeItem(this.config.tokenKeys.accessToken);
|
|
109
|
+
this.storage.removeItem(this.config.tokenKeys.refreshToken);
|
|
110
|
+
this.storage.removeItem(this.config.tokenKeys.tokenExpiry);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 检查是否有有效的认证信息
|
|
114
|
+
*/
|
|
115
|
+
hasValidAuth() {
|
|
116
|
+
const accessToken = this.getAccessToken();
|
|
117
|
+
const refreshToken = this.getRefreshToken();
|
|
118
|
+
return !!(accessToken && !this.isAccessTokenExpired()) || !!refreshToken;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* 获取配置
|
|
122
|
+
*/
|
|
123
|
+
getConfig() {
|
|
124
|
+
return { ...this.config };
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
var instance = null;
|
|
128
|
+
function getTokenManager(config) {
|
|
129
|
+
if (!instance) {
|
|
130
|
+
instance = new TokenManager(void 0, config);
|
|
131
|
+
}
|
|
132
|
+
return instance;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/core/auth-redirect.ts
|
|
136
|
+
var ALLOWED_REDIRECT_DOMAINS = [
|
|
137
|
+
"yiminlab.site",
|
|
138
|
+
"jsontailor.yiminlab.site",
|
|
139
|
+
"ai.yiminlab.site",
|
|
140
|
+
"localhost"
|
|
141
|
+
];
|
|
142
|
+
var AuthRedirect = class {
|
|
143
|
+
constructor(config) {
|
|
144
|
+
this.config = {
|
|
145
|
+
portalUrl: config?.portalUrl || DEFAULT_AUTH_CONFIG.portalUrl,
|
|
146
|
+
callbackPath: config?.callbackPath || DEFAULT_AUTH_CONFIG.callbackPath
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* 获取当前页面的完整 URL
|
|
151
|
+
*/
|
|
152
|
+
getCurrentUrl() {
|
|
153
|
+
if (typeof window === "undefined") return "";
|
|
154
|
+
return window.location.href;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* 获取当前页面的 origin
|
|
158
|
+
*/
|
|
159
|
+
getCurrentOrigin() {
|
|
160
|
+
if (typeof window === "undefined") return "";
|
|
161
|
+
return window.location.origin;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* 构建回调 URL
|
|
165
|
+
*/
|
|
166
|
+
buildCallbackUrl() {
|
|
167
|
+
return `${this.getCurrentOrigin()}${this.config.callbackPath}`;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* 构建登录跳转 URL
|
|
171
|
+
* @param returnPath 登录成功后返回的路径(默认当前页面)
|
|
172
|
+
*/
|
|
173
|
+
buildLoginUrl(returnPath) {
|
|
174
|
+
const callbackUrl = this.buildCallbackUrl();
|
|
175
|
+
const loginUrl = new URL("/login", this.config.portalUrl);
|
|
176
|
+
loginUrl.searchParams.set("redirect", callbackUrl);
|
|
177
|
+
if (returnPath) {
|
|
178
|
+
loginUrl.searchParams.set("return", returnPath);
|
|
179
|
+
}
|
|
180
|
+
return loginUrl.toString();
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* 跳转到 Portal 登录页
|
|
184
|
+
* @param returnPath 登录成功后返回的路径
|
|
185
|
+
*/
|
|
186
|
+
redirectToLogin(returnPath) {
|
|
187
|
+
if (typeof window === "undefined") return;
|
|
188
|
+
const currentPath = window.location.pathname + window.location.search;
|
|
189
|
+
const loginUrl = this.buildLoginUrl(returnPath || currentPath);
|
|
190
|
+
window.location.href = loginUrl;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* 从 URL 参数中解析 Token
|
|
194
|
+
*/
|
|
195
|
+
parseTokensFromUrl() {
|
|
196
|
+
if (typeof window === "undefined") return null;
|
|
197
|
+
const params = new URLSearchParams(window.location.search);
|
|
198
|
+
const accessToken = params.get("token");
|
|
199
|
+
const refreshToken = params.get("refresh");
|
|
200
|
+
const expiresInStr = params.get("expires");
|
|
201
|
+
const returnPath = params.get("return");
|
|
202
|
+
if (!accessToken || !refreshToken) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
accessToken,
|
|
207
|
+
refreshToken,
|
|
208
|
+
expiresIn: expiresInStr ? parseInt(expiresInStr, 10) : 3600,
|
|
209
|
+
returnPath
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* 清除 URL 中的 Token 参数
|
|
214
|
+
*/
|
|
215
|
+
clearTokensFromUrl() {
|
|
216
|
+
if (typeof window === "undefined") return;
|
|
217
|
+
const url = new URL(window.location.href);
|
|
218
|
+
url.searchParams.delete("token");
|
|
219
|
+
url.searchParams.delete("refresh");
|
|
220
|
+
url.searchParams.delete("expires");
|
|
221
|
+
url.searchParams.delete("return");
|
|
222
|
+
window.history.replaceState({}, "", url.pathname + url.search);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* 验证跳转 URL 是否在白名单中
|
|
226
|
+
*/
|
|
227
|
+
isAllowedRedirectUrl(url) {
|
|
228
|
+
try {
|
|
229
|
+
const { hostname } = new URL(url);
|
|
230
|
+
return ALLOWED_REDIRECT_DOMAINS.some(
|
|
231
|
+
(domain) => hostname === domain || hostname.endsWith("." + domain)
|
|
232
|
+
);
|
|
233
|
+
} catch {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
var instance2 = null;
|
|
239
|
+
function getAuthRedirect(config) {
|
|
240
|
+
if (!instance2) {
|
|
241
|
+
instance2 = new AuthRedirect(config);
|
|
242
|
+
}
|
|
243
|
+
return instance2;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/core/authkit-client.ts
|
|
247
|
+
var AuthKitClient = class {
|
|
248
|
+
constructor(config) {
|
|
249
|
+
this.config = {
|
|
250
|
+
baseUrl: config.baseUrl || DEFAULT_AUTH_CONFIG.apiBaseUrl,
|
|
251
|
+
getAccessToken: config.getAccessToken
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* 构建请求头
|
|
256
|
+
*/
|
|
257
|
+
buildHeaders() {
|
|
258
|
+
const headers = {
|
|
259
|
+
"Content-Type": "application/json"
|
|
260
|
+
};
|
|
261
|
+
const token = this.config.getAccessToken();
|
|
262
|
+
if (token) {
|
|
263
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
264
|
+
}
|
|
265
|
+
return headers;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* 解析 API 响应为标准格式
|
|
269
|
+
*/
|
|
270
|
+
parseTokenResponse(data) {
|
|
271
|
+
if (data.access_token) {
|
|
272
|
+
const expiresAt = new Date(Date.now() + data.expires_in * 1e3).toISOString();
|
|
273
|
+
return {
|
|
274
|
+
token: data.access_token,
|
|
275
|
+
expires_at: expiresAt,
|
|
276
|
+
scopes: data.scopes
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
throw new Error("Invalid response format");
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* 获取当前用户信息
|
|
283
|
+
*/
|
|
284
|
+
async getMe() {
|
|
285
|
+
const response = await fetch(`${this.config.baseUrl}/api/auth/me`, {
|
|
286
|
+
method: "GET",
|
|
287
|
+
headers: this.buildHeaders()
|
|
288
|
+
});
|
|
289
|
+
if (!response.ok) {
|
|
290
|
+
const error = await response.json().catch(() => ({}));
|
|
291
|
+
throw new Error(error.error || error.message || `HTTP ${response.status}`);
|
|
292
|
+
}
|
|
293
|
+
return await response.json();
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* 获取全局 Scoped Token(自动创建/续期)
|
|
297
|
+
*
|
|
298
|
+
* @param scope 权限范围,如 'streamock:stream:read'
|
|
299
|
+
* @returns 短 token,如 'st_7kB2xM9pQr3n'
|
|
300
|
+
*/
|
|
301
|
+
async getScopedToken(scope) {
|
|
302
|
+
const response = await fetch(
|
|
303
|
+
`${this.config.baseUrl}/api/auth/token/scoped?scope=${encodeURIComponent(scope)}`,
|
|
304
|
+
{
|
|
305
|
+
method: "GET",
|
|
306
|
+
headers: this.buildHeaders()
|
|
307
|
+
}
|
|
308
|
+
);
|
|
309
|
+
if (!response.ok) {
|
|
310
|
+
const error = await response.json().catch(() => ({}));
|
|
311
|
+
throw new Error(error.error || error.message || `HTTP ${response.status}`);
|
|
312
|
+
}
|
|
313
|
+
const data = await response.json();
|
|
314
|
+
return this.parseTokenResponse(data);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* 轮换 Scoped Token(废弃旧的,生成新的)
|
|
318
|
+
*
|
|
319
|
+
* @param scope 权限范围,如 'streamock:stream:read'
|
|
320
|
+
* @returns 新的短 token
|
|
321
|
+
*/
|
|
322
|
+
async rotateScopedToken(scope) {
|
|
323
|
+
const response = await fetch(
|
|
324
|
+
`${this.config.baseUrl}/api/auth/token/scoped/rotate?scope=${encodeURIComponent(scope)}`,
|
|
325
|
+
{
|
|
326
|
+
method: "POST",
|
|
327
|
+
headers: this.buildHeaders()
|
|
328
|
+
}
|
|
329
|
+
);
|
|
330
|
+
if (!response.ok) {
|
|
331
|
+
const error = await response.json().catch(() => ({}));
|
|
332
|
+
throw new Error(error.error || error.message || `HTTP ${response.status}`);
|
|
333
|
+
}
|
|
334
|
+
const data = await response.json();
|
|
335
|
+
return this.parseTokenResponse(data);
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* 创建 Scoped Token(短 token)- 保留兼容性
|
|
339
|
+
*
|
|
340
|
+
* @param scopes 权限范围,如 ['streamock:stream:read']
|
|
341
|
+
* @param expiresIn 过期时间(秒),默认 24 小时
|
|
342
|
+
* @returns 短 token,如 'st_7kB2xM9pQr3n'
|
|
343
|
+
* @deprecated 请使用 getScopedToken 获取全局 token
|
|
344
|
+
*/
|
|
345
|
+
async createScopedToken(scopes, expiresIn = 86400) {
|
|
346
|
+
const response = await fetch(`${this.config.baseUrl}/api/auth/token/scoped`, {
|
|
347
|
+
method: "POST",
|
|
348
|
+
headers: this.buildHeaders(),
|
|
349
|
+
body: JSON.stringify({
|
|
350
|
+
scopes,
|
|
351
|
+
expires_in: expiresIn
|
|
352
|
+
})
|
|
353
|
+
});
|
|
354
|
+
if (!response.ok) {
|
|
355
|
+
const error = await response.json().catch(() => ({}));
|
|
356
|
+
throw new Error(error.error || error.message || `HTTP ${response.status}`);
|
|
357
|
+
}
|
|
358
|
+
const data = await response.json();
|
|
359
|
+
return this.parseTokenResponse(data);
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
var instance3 = null;
|
|
363
|
+
function getAuthKitClient(getAccessToken) {
|
|
364
|
+
if (!instance3) {
|
|
365
|
+
instance3 = new AuthKitClient({ getAccessToken });
|
|
366
|
+
}
|
|
367
|
+
return instance3;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// src/react/use-auth.ts
|
|
371
|
+
function useAuth(options = {}) {
|
|
372
|
+
const {
|
|
373
|
+
tokenManager = getTokenManager(options.config),
|
|
374
|
+
authRedirect = getAuthRedirect(options.config),
|
|
375
|
+
fetchUser = true
|
|
376
|
+
} = options;
|
|
377
|
+
const [authState, setAuthState] = (0, import_react.useState)({
|
|
378
|
+
isAuthenticated: false,
|
|
379
|
+
isLoading: true,
|
|
380
|
+
user: null,
|
|
381
|
+
error: null
|
|
382
|
+
});
|
|
383
|
+
const fetchUserInfo = (0, import_react.useCallback)(async () => {
|
|
384
|
+
try {
|
|
385
|
+
const client = getAuthKitClient(() => tokenManager.getAccessToken());
|
|
386
|
+
const user = await client.getMe();
|
|
387
|
+
setAuthState((prev) => ({
|
|
388
|
+
...prev,
|
|
389
|
+
user,
|
|
390
|
+
error: null
|
|
391
|
+
}));
|
|
392
|
+
} catch (error) {
|
|
393
|
+
console.error("Failed to fetch user info:", error);
|
|
394
|
+
}
|
|
395
|
+
}, [tokenManager]);
|
|
396
|
+
const checkAuth = (0, import_react.useCallback)(async () => {
|
|
397
|
+
const hasValidAuth = tokenManager.hasValidAuth();
|
|
398
|
+
const accessToken = tokenManager.getAccessToken();
|
|
399
|
+
if (hasValidAuth && accessToken) {
|
|
400
|
+
setAuthState({
|
|
401
|
+
isAuthenticated: true,
|
|
402
|
+
isLoading: false,
|
|
403
|
+
user: null,
|
|
404
|
+
error: null
|
|
405
|
+
});
|
|
406
|
+
if (fetchUser) {
|
|
407
|
+
await fetchUserInfo();
|
|
408
|
+
}
|
|
409
|
+
} else {
|
|
410
|
+
setAuthState({
|
|
411
|
+
isAuthenticated: false,
|
|
412
|
+
isLoading: false,
|
|
413
|
+
user: null,
|
|
414
|
+
error: null
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}, [tokenManager, fetchUser, fetchUserInfo]);
|
|
418
|
+
const login = (0, import_react.useCallback)(
|
|
419
|
+
(returnPath) => {
|
|
420
|
+
authRedirect.redirectToLogin(returnPath);
|
|
421
|
+
},
|
|
422
|
+
[authRedirect]
|
|
423
|
+
);
|
|
424
|
+
const logout = (0, import_react.useCallback)(() => {
|
|
425
|
+
tokenManager.clearTokens();
|
|
426
|
+
setAuthState({
|
|
427
|
+
isAuthenticated: false,
|
|
428
|
+
isLoading: false,
|
|
429
|
+
user: null,
|
|
430
|
+
error: null
|
|
431
|
+
});
|
|
432
|
+
}, [tokenManager]);
|
|
433
|
+
const getAccessToken = (0, import_react.useCallback)(() => {
|
|
434
|
+
return tokenManager.getAccessToken();
|
|
435
|
+
}, [tokenManager]);
|
|
436
|
+
const refresh = (0, import_react.useCallback)(() => {
|
|
437
|
+
checkAuth();
|
|
438
|
+
}, [checkAuth]);
|
|
439
|
+
const refreshUser = (0, import_react.useCallback)(async () => {
|
|
440
|
+
await fetchUserInfo();
|
|
441
|
+
}, [fetchUserInfo]);
|
|
442
|
+
(0, import_react.useEffect)(() => {
|
|
443
|
+
checkAuth();
|
|
444
|
+
}, [checkAuth]);
|
|
445
|
+
(0, import_react.useEffect)(() => {
|
|
446
|
+
const handleStorageChange = (event) => {
|
|
447
|
+
const config = tokenManager.getConfig();
|
|
448
|
+
if (event.key === config.tokenKeys.accessToken || event.key === config.tokenKeys.refreshToken) {
|
|
449
|
+
checkAuth();
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
window.addEventListener("storage", handleStorageChange);
|
|
453
|
+
return () => {
|
|
454
|
+
window.removeEventListener("storage", handleStorageChange);
|
|
455
|
+
};
|
|
456
|
+
}, [tokenManager, checkAuth]);
|
|
457
|
+
return (0, import_react.useMemo)(
|
|
458
|
+
() => ({
|
|
459
|
+
...authState,
|
|
460
|
+
login,
|
|
461
|
+
logout,
|
|
462
|
+
getAccessToken,
|
|
463
|
+
refresh,
|
|
464
|
+
refreshUser
|
|
465
|
+
}),
|
|
466
|
+
[authState, login, logout, getAccessToken, refresh, refreshUser]
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
function useAccessToken() {
|
|
470
|
+
const { getAccessToken } = useAuth({ fetchUser: false });
|
|
471
|
+
return getAccessToken();
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// src/react/auth-guard.tsx
|
|
475
|
+
var import_react2 = require("react");
|
|
476
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
477
|
+
function DefaultLoading() {
|
|
478
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: {
|
|
479
|
+
display: "flex",
|
|
480
|
+
height: "50vh",
|
|
481
|
+
width: "100%",
|
|
482
|
+
alignItems: "center",
|
|
483
|
+
justifyContent: "center"
|
|
484
|
+
}, children: [
|
|
485
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
|
|
486
|
+
width: "32px",
|
|
487
|
+
height: "32px",
|
|
488
|
+
border: "3px solid #e5e7eb",
|
|
489
|
+
borderTopColor: "#3b82f6",
|
|
490
|
+
borderRadius: "50%",
|
|
491
|
+
animation: "spin 1s linear infinite"
|
|
492
|
+
} }),
|
|
493
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `
|
|
494
|
+
@keyframes spin {
|
|
495
|
+
to { transform: rotate(360deg); }
|
|
496
|
+
}
|
|
497
|
+
` })
|
|
498
|
+
] });
|
|
499
|
+
}
|
|
500
|
+
function DefaultLoginPrompt({
|
|
501
|
+
onLogin,
|
|
502
|
+
title = "\u9700\u8981\u767B\u5F55",
|
|
503
|
+
description = "\u8BF7\u767B\u5F55\u4EE5\u7EE7\u7EED\u4F7F\u7528",
|
|
504
|
+
buttonText = "\u767B\u5F55"
|
|
505
|
+
}) {
|
|
506
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: {
|
|
507
|
+
display: "flex",
|
|
508
|
+
height: "50vh",
|
|
509
|
+
width: "100%",
|
|
510
|
+
flexDirection: "column",
|
|
511
|
+
alignItems: "center",
|
|
512
|
+
justifyContent: "center",
|
|
513
|
+
gap: "16px"
|
|
514
|
+
}, children: [
|
|
515
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { textAlign: "center" }, children: [
|
|
516
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { style: {
|
|
517
|
+
fontSize: "1.5rem",
|
|
518
|
+
fontWeight: 600,
|
|
519
|
+
margin: 0
|
|
520
|
+
}, children: title }),
|
|
521
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: {
|
|
522
|
+
marginTop: "8px",
|
|
523
|
+
color: "#6b7280"
|
|
524
|
+
}, children: description })
|
|
525
|
+
] }),
|
|
526
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
527
|
+
"button",
|
|
528
|
+
{
|
|
529
|
+
onClick: onLogin,
|
|
530
|
+
style: {
|
|
531
|
+
display: "inline-flex",
|
|
532
|
+
alignItems: "center",
|
|
533
|
+
justifyContent: "center",
|
|
534
|
+
padding: "10px 20px",
|
|
535
|
+
fontSize: "1rem",
|
|
536
|
+
fontWeight: 500,
|
|
537
|
+
backgroundColor: "#3b82f6",
|
|
538
|
+
color: "white",
|
|
539
|
+
border: "none",
|
|
540
|
+
borderRadius: "6px",
|
|
541
|
+
cursor: "pointer",
|
|
542
|
+
transition: "background-color 0.2s"
|
|
543
|
+
},
|
|
544
|
+
onMouseOver: (e) => e.currentTarget.style.backgroundColor = "#2563eb",
|
|
545
|
+
onMouseOut: (e) => e.currentTarget.style.backgroundColor = "#3b82f6",
|
|
546
|
+
children: buttonText
|
|
547
|
+
}
|
|
548
|
+
)
|
|
549
|
+
] });
|
|
550
|
+
}
|
|
551
|
+
function AuthGuard({
|
|
552
|
+
children,
|
|
553
|
+
fallback = "prompt",
|
|
554
|
+
returnPath,
|
|
555
|
+
loadingContent,
|
|
556
|
+
promptContent,
|
|
557
|
+
promptTitle,
|
|
558
|
+
promptDescription,
|
|
559
|
+
loginButtonText,
|
|
560
|
+
authOptions
|
|
561
|
+
}) {
|
|
562
|
+
const { isAuthenticated, isLoading, login } = useAuth(authOptions);
|
|
563
|
+
(0, import_react2.useEffect)(() => {
|
|
564
|
+
if (!isLoading && !isAuthenticated && fallback === "redirect") {
|
|
565
|
+
login(returnPath);
|
|
566
|
+
}
|
|
567
|
+
}, [isLoading, isAuthenticated, fallback, login, returnPath]);
|
|
568
|
+
if (isLoading) {
|
|
569
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: loadingContent || /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DefaultLoading, {}) });
|
|
570
|
+
}
|
|
571
|
+
if (!isAuthenticated) {
|
|
572
|
+
if (fallback === "redirect") {
|
|
573
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: loadingContent || /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DefaultLoading, {}) });
|
|
574
|
+
}
|
|
575
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: promptContent || /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
576
|
+
DefaultLoginPrompt,
|
|
577
|
+
{
|
|
578
|
+
onLogin: () => login(returnPath),
|
|
579
|
+
title: promptTitle,
|
|
580
|
+
description: promptDescription,
|
|
581
|
+
buttonText: loginButtonText
|
|
582
|
+
}
|
|
583
|
+
) });
|
|
584
|
+
}
|
|
585
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children });
|
|
586
|
+
}
|
|
587
|
+
function withAuthGuard(Component, guardProps) {
|
|
588
|
+
return function WrappedComponent(props) {
|
|
589
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AuthGuard, { ...guardProps, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Component, { ...props }) });
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// src/react/callback-handler.tsx
|
|
594
|
+
var import_react3 = require("react");
|
|
595
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
596
|
+
function DefaultLoading2() {
|
|
597
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
|
|
598
|
+
display: "flex",
|
|
599
|
+
height: "100vh",
|
|
600
|
+
width: "100%",
|
|
601
|
+
alignItems: "center",
|
|
602
|
+
justifyContent: "center",
|
|
603
|
+
flexDirection: "column",
|
|
604
|
+
gap: "16px"
|
|
605
|
+
}, children: [
|
|
606
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
|
|
607
|
+
width: "40px",
|
|
608
|
+
height: "40px",
|
|
609
|
+
border: "3px solid #e5e7eb",
|
|
610
|
+
borderTopColor: "#3b82f6",
|
|
611
|
+
borderRadius: "50%",
|
|
612
|
+
animation: "spin 1s linear infinite"
|
|
613
|
+
} }),
|
|
614
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { color: "#6b7280" }, children: "\u6B63\u5728\u5904\u7406\u767B\u5F55..." }),
|
|
615
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `
|
|
616
|
+
@keyframes spin {
|
|
617
|
+
to { transform: rotate(360deg); }
|
|
618
|
+
}
|
|
619
|
+
` })
|
|
620
|
+
] });
|
|
621
|
+
}
|
|
622
|
+
function DefaultError({ error, onRetry }) {
|
|
623
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
|
|
624
|
+
display: "flex",
|
|
625
|
+
height: "100vh",
|
|
626
|
+
width: "100%",
|
|
627
|
+
alignItems: "center",
|
|
628
|
+
justifyContent: "center",
|
|
629
|
+
flexDirection: "column",
|
|
630
|
+
gap: "16px"
|
|
631
|
+
}, children: [
|
|
632
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
|
|
633
|
+
color: "#ef4444",
|
|
634
|
+
fontSize: "48px"
|
|
635
|
+
}, children: "\u26A0\uFE0F" }),
|
|
636
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { style: { margin: 0 }, children: "\u767B\u5F55\u5931\u8D25" }),
|
|
637
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { color: "#6b7280", margin: 0 }, children: error }),
|
|
638
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
639
|
+
"button",
|
|
640
|
+
{
|
|
641
|
+
onClick: onRetry,
|
|
642
|
+
style: {
|
|
643
|
+
padding: "10px 20px",
|
|
644
|
+
fontSize: "1rem",
|
|
645
|
+
backgroundColor: "#3b82f6",
|
|
646
|
+
color: "white",
|
|
647
|
+
border: "none",
|
|
648
|
+
borderRadius: "6px",
|
|
649
|
+
cursor: "pointer"
|
|
650
|
+
},
|
|
651
|
+
children: "\u91CD\u8BD5"
|
|
652
|
+
}
|
|
653
|
+
)
|
|
654
|
+
] });
|
|
655
|
+
}
|
|
656
|
+
function CallbackHandler({
|
|
657
|
+
defaultRedirectPath = "/",
|
|
658
|
+
loadingContent,
|
|
659
|
+
errorContent,
|
|
660
|
+
config,
|
|
661
|
+
onSuccess,
|
|
662
|
+
onError
|
|
663
|
+
}) {
|
|
664
|
+
const [error, setError] = (0, import_react3.useState)(null);
|
|
665
|
+
(0, import_react3.useEffect)(() => {
|
|
666
|
+
const handleCallback = () => {
|
|
667
|
+
const tokenManager = getTokenManager(config);
|
|
668
|
+
const authRedirect = getAuthRedirect(config);
|
|
669
|
+
const tokens = authRedirect.parseTokensFromUrl();
|
|
670
|
+
if (!tokens || !tokens.accessToken || !tokens.refreshToken) {
|
|
671
|
+
const errorMsg = "\u672A\u627E\u5230\u8BA4\u8BC1\u4FE1\u606F";
|
|
672
|
+
setError(errorMsg);
|
|
673
|
+
onError?.(errorMsg);
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
tokenManager.setTokens(
|
|
677
|
+
tokens.accessToken,
|
|
678
|
+
tokens.refreshToken,
|
|
679
|
+
tokens.expiresIn || 3600
|
|
680
|
+
);
|
|
681
|
+
authRedirect.clearTokensFromUrl();
|
|
682
|
+
onSuccess?.();
|
|
683
|
+
const redirectPath = tokens.returnPath || defaultRedirectPath;
|
|
684
|
+
window.location.href = redirectPath;
|
|
685
|
+
};
|
|
686
|
+
handleCallback();
|
|
687
|
+
}, [defaultRedirectPath, config, onSuccess, onError]);
|
|
688
|
+
const handleRetry = () => {
|
|
689
|
+
const authRedirect = getAuthRedirect(config);
|
|
690
|
+
authRedirect.redirectToLogin(defaultRedirectPath);
|
|
691
|
+
};
|
|
692
|
+
if (error) {
|
|
693
|
+
if (typeof errorContent === "function") {
|
|
694
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: errorContent(error) });
|
|
695
|
+
}
|
|
696
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: errorContent || /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DefaultError, { error, onRetry: handleRetry }) });
|
|
697
|
+
}
|
|
698
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: loadingContent || /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DefaultLoading2, {}) });
|
|
699
|
+
}
|
|
700
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
701
|
+
0 && (module.exports = {
|
|
702
|
+
AuthGuard,
|
|
703
|
+
CallbackHandler,
|
|
704
|
+
useAccessToken,
|
|
705
|
+
useAuth,
|
|
706
|
+
withAuthGuard
|
|
707
|
+
});
|
|
708
|
+
//# sourceMappingURL=index.js.map
|