@nihplod/sso-sdk 1.1.0 → 1.2.1
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 +73 -6
- package/dist/index.d.mts +51 -10
- package/dist/index.d.ts +51 -10
- package/dist/index.js +68 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +65 -9
- package/dist/index.mjs.map +1 -1
- package/dist/next/index.d.mts +38 -5
- package/dist/next/index.d.ts +38 -5
- package/dist/next/index.js +90 -49
- package/dist/next/index.js.map +1 -1
- package/dist/next/index.mjs +88 -48
- package/dist/next/index.mjs.map +1 -1
- package/dist/react/index.d.mts +113 -10
- package/dist/react/index.d.ts +113 -10
- package/dist/react/index.js +166 -55
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +169 -59
- package/dist/react/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/react/index.mjs
CHANGED
|
@@ -84,6 +84,7 @@ var TOKEN_KEY = "token";
|
|
|
84
84
|
var VERIFIER_KEY_PREFIX = "pkce_verifier_";
|
|
85
85
|
var STATE_KEY = "oauth_state";
|
|
86
86
|
var RETURN_URL_KEY = "return_url";
|
|
87
|
+
var LOGOUT_STATE_KEY = "logout_state";
|
|
87
88
|
function buildKey(base, clientId) {
|
|
88
89
|
return clientId ? `${base}:${clientId}` : base;
|
|
89
90
|
}
|
|
@@ -130,7 +131,7 @@ function createSessionStorageAdapter() {
|
|
|
130
131
|
};
|
|
131
132
|
}
|
|
132
133
|
var _transient = createSessionStorageAdapter();
|
|
133
|
-
var _storage =
|
|
134
|
+
var _storage = createSessionStorageAdapter();
|
|
134
135
|
function saveTokenData(data, clientId) {
|
|
135
136
|
_storage.set(buildKey(TOKEN_KEY, clientId), JSON.stringify(data));
|
|
136
137
|
}
|
|
@@ -164,6 +165,15 @@ function getOAuthState(clientId) {
|
|
|
164
165
|
function removeOAuthState(clientId) {
|
|
165
166
|
_transient.remove(buildKey(STATE_KEY, clientId));
|
|
166
167
|
}
|
|
168
|
+
function saveLogoutState(state, clientId) {
|
|
169
|
+
_transient.set(buildKey(LOGOUT_STATE_KEY, clientId), state);
|
|
170
|
+
}
|
|
171
|
+
function getLogoutState(clientId) {
|
|
172
|
+
return _transient.get(buildKey(LOGOUT_STATE_KEY, clientId));
|
|
173
|
+
}
|
|
174
|
+
function removeLogoutState(clientId) {
|
|
175
|
+
_transient.remove(buildKey(LOGOUT_STATE_KEY, clientId));
|
|
176
|
+
}
|
|
167
177
|
function saveReturnUrl(url, clientId) {
|
|
168
178
|
_transient.set(buildKey(RETURN_URL_KEY, clientId), url);
|
|
169
179
|
}
|
|
@@ -178,6 +188,7 @@ function clearAllSsoData(clientId) {
|
|
|
178
188
|
removeTokenData(clientId);
|
|
179
189
|
removeOAuthState(clientId);
|
|
180
190
|
removeReturnUrl(clientId);
|
|
191
|
+
removeLogoutState(clientId);
|
|
181
192
|
removePkceVerifier(clientId);
|
|
182
193
|
removePkceVerifier(`${clientId}_popup_nonce`);
|
|
183
194
|
return;
|
|
@@ -185,6 +196,7 @@ function clearAllSsoData(clientId) {
|
|
|
185
196
|
removeTokenData();
|
|
186
197
|
removeOAuthState();
|
|
187
198
|
removeReturnUrl();
|
|
199
|
+
removeLogoutState();
|
|
188
200
|
const prefix = STORAGE_PREFIX + VERIFIER_KEY_PREFIX;
|
|
189
201
|
const stores = [
|
|
190
202
|
typeof sessionStorage !== "undefined" ? sessionStorage : null,
|
|
@@ -207,6 +219,7 @@ function clearAllSsoData(clientId) {
|
|
|
207
219
|
// src/core/security.ts
|
|
208
220
|
function isTrustedReturnUrl(url, currentOrigin) {
|
|
209
221
|
if (!url) return false;
|
|
222
|
+
if (url.includes("\\")) return false;
|
|
210
223
|
if (url.startsWith("/") && !url.startsWith("//")) return true;
|
|
211
224
|
try {
|
|
212
225
|
return new URL(url).origin === currentOrigin;
|
|
@@ -412,7 +425,7 @@ async function validateIdToken(idToken, accessToken, expectedIssuer, expectedCli
|
|
|
412
425
|
if (typeof payload.exp !== "number") {
|
|
413
426
|
throw new SsoError("id_token_invalid", "ID Token \u7F3A\u5C11 exp \u58F0\u660E");
|
|
414
427
|
}
|
|
415
|
-
if (Date.now() >= payload.exp * 1e3) {
|
|
428
|
+
if (Date.now() >= payload.exp * 1e3 + 6e4) {
|
|
416
429
|
throw new SsoError("id_token_expired", "ID Token \u5DF2\u8FC7\u671F");
|
|
417
430
|
}
|
|
418
431
|
if (typeof payload.iat === "number" && payload.iat * 1e3 > Date.now() + 6e4) {
|
|
@@ -505,7 +518,7 @@ var _SsoClient = class _SsoClient {
|
|
|
505
518
|
*/
|
|
506
519
|
_saveReturnUrlIfTrusted(returnUrl) {
|
|
507
520
|
if (isTrustedReturnUrl(returnUrl, window.location.origin)) {
|
|
508
|
-
saveReturnUrl(returnUrl);
|
|
521
|
+
saveReturnUrl(returnUrl, this.config.clientId);
|
|
509
522
|
} else {
|
|
510
523
|
console.warn(`[SSO SDK] returnUrl \u672A\u901A\u8FC7\u540C\u6E90\u6821\u9A8C\uFF0C\u5DF2\u5FFD\u7565: ${returnUrl}`);
|
|
511
524
|
}
|
|
@@ -521,6 +534,10 @@ var _SsoClient = class _SsoClient {
|
|
|
521
534
|
*
|
|
522
535
|
* @param returnUrl - 登录成功后的返回地址(可选,保存到 sessionStorage;
|
|
523
536
|
* 仅允许相对路径或同源绝对 URL,否则忽略并告警)
|
|
537
|
+
*
|
|
538
|
+
* ⚠️ 不要与 getLoginUrl() 混用:两者都会重新生成并覆盖 sessionStorage 中的
|
|
539
|
+
* state / PKCE verifier,先调用的那次授权流程将因 state 不匹配而失败。
|
|
540
|
+
* 同一次登录只使用其中一个入口。
|
|
524
541
|
*/
|
|
525
542
|
async login(returnUrl) {
|
|
526
543
|
const verifier = generateCodeVerifier();
|
|
@@ -546,6 +563,10 @@ var _SsoClient = class _SsoClient {
|
|
|
546
563
|
* 构建登录 URL(不跳转,返回 URL 字符串)
|
|
547
564
|
*
|
|
548
565
|
* 适用于需要手动处理跳转的场景。
|
|
566
|
+
*
|
|
567
|
+
* ⚠️ 不要与 login() 混用:两者都会重新生成并覆盖 sessionStorage 中的
|
|
568
|
+
* state / PKCE verifier,先调用的那次授权流程将因 state 不匹配而失败。
|
|
569
|
+
* 同一次登录只使用其中一个入口。
|
|
549
570
|
*/
|
|
550
571
|
async getLoginUrl(returnUrl) {
|
|
551
572
|
const verifier = generateCodeVerifier();
|
|
@@ -698,7 +719,7 @@ var _SsoClient = class _SsoClient {
|
|
|
698
719
|
* 处理 OAuth 回调
|
|
699
720
|
*
|
|
700
721
|
* 解析回调 URL,校验 state 参数,用授权码交换 token。
|
|
701
|
-
* 成功后 token 自动保存到 token
|
|
722
|
+
* 成功后 token 自动保存到 token 存储(默认 sessionStorage,可通过 setTokenStorage 定制)。
|
|
702
723
|
*
|
|
703
724
|
* @param callbackUrl - 完整的回调 URL(window.location.href)
|
|
704
725
|
* @returns TokenData 或 null
|
|
@@ -708,12 +729,16 @@ var _SsoClient = class _SsoClient {
|
|
|
708
729
|
const params = url.searchParams;
|
|
709
730
|
const error = params.get("error");
|
|
710
731
|
if (error) {
|
|
732
|
+
removeOAuthState(this.config.clientId);
|
|
733
|
+
removePkceVerifier(this.config.clientId);
|
|
711
734
|
const desc = params.get("error_description") || error;
|
|
712
|
-
throw new SsoError(
|
|
735
|
+
throw new SsoError(mapOAuthErrorToSsoCode(error), `\u6388\u6743\u5931\u8D25: ${desc}`);
|
|
713
736
|
}
|
|
714
737
|
const code = params.get("code");
|
|
715
738
|
const returnedState = params.get("state");
|
|
716
739
|
if (!code) {
|
|
740
|
+
removeOAuthState(this.config.clientId);
|
|
741
|
+
removePkceVerifier(this.config.clientId);
|
|
717
742
|
throw new SsoError("token_request_failed", "\u56DE\u8C03 URL \u4E2D\u7F3A\u5C11 authorization code");
|
|
718
743
|
}
|
|
719
744
|
const savedState = getOAuthState(this.config.clientId);
|
|
@@ -765,8 +790,6 @@ var _SsoClient = class _SsoClient {
|
|
|
765
790
|
);
|
|
766
791
|
}
|
|
767
792
|
const data = await res.json();
|
|
768
|
-
removeOAuthState(this.config.clientId);
|
|
769
|
-
removePkceVerifier(this.config.clientId);
|
|
770
793
|
if (data.id_token) {
|
|
771
794
|
try {
|
|
772
795
|
await validateIdToken(
|
|
@@ -780,6 +803,8 @@ var _SsoClient = class _SsoClient {
|
|
|
780
803
|
throw err;
|
|
781
804
|
}
|
|
782
805
|
}
|
|
806
|
+
removeOAuthState(this.config.clientId);
|
|
807
|
+
removePkceVerifier(this.config.clientId);
|
|
783
808
|
const now = Date.now();
|
|
784
809
|
const tokenData = {
|
|
785
810
|
access_token: data.access_token,
|
|
@@ -948,7 +973,9 @@ var _SsoClient = class _SsoClient {
|
|
|
948
973
|
* 登出
|
|
949
974
|
*
|
|
950
975
|
* 清除本地所有 token 和临时数据,并尝试撤销服务端 refresh_token。
|
|
951
|
-
* @param redirectToSso - 是否重定向到 SSO 登出页(默认 false
|
|
976
|
+
* @param redirectToSso - 是否重定向到 SSO 登出页(默认 false)。
|
|
977
|
+
* 为 true 时携带 state 参数(已保存到 sessionStorage),
|
|
978
|
+
* 回跳页面应调用 validateLogoutState() 校验防登出 CSRF。
|
|
952
979
|
*/
|
|
953
980
|
async logout(redirectToSso = false) {
|
|
954
981
|
const tokenData = getTokenData(this.config.clientId);
|
|
@@ -991,10 +1018,36 @@ var _SsoClient = class _SsoClient {
|
|
|
991
1018
|
logoutUrl.searchParams.set("id_token_hint", idTokenHint);
|
|
992
1019
|
}
|
|
993
1020
|
const state = generateState();
|
|
1021
|
+
saveLogoutState(state, this.config.clientId);
|
|
994
1022
|
logoutUrl.searchParams.set("state", state);
|
|
995
1023
|
window.location.href = logoutUrl.toString();
|
|
996
1024
|
}
|
|
997
1025
|
}
|
|
1026
|
+
/**
|
|
1027
|
+
* 校验 RP-Initiated Logout 回跳的 state 参数(登出 CSRF 防护)
|
|
1028
|
+
*
|
|
1029
|
+
* 在 post_logout_redirect_uri 指向的页面加载时调用;
|
|
1030
|
+
* 仅在 URL 携带 state 且与 logout(redirectToSso=true) 保存的值一致时返回 true,
|
|
1031
|
+
* 校验后清除已保存的 logout state(一次性)。
|
|
1032
|
+
*
|
|
1033
|
+
* @param url - 当前页面完整 URL(window.location.href)
|
|
1034
|
+
*
|
|
1035
|
+
* @example
|
|
1036
|
+
* ```typescript
|
|
1037
|
+
* if (sso.validateLogoutState(window.location.href)) {
|
|
1038
|
+
* // 来自 SSO 登出的可信回跳
|
|
1039
|
+
* }
|
|
1040
|
+
* ```
|
|
1041
|
+
*/
|
|
1042
|
+
validateLogoutState(url) {
|
|
1043
|
+
const returnedState = new URL(url).searchParams.get("state");
|
|
1044
|
+
if (!returnedState) return false;
|
|
1045
|
+
const savedState = getLogoutState(this.config.clientId);
|
|
1046
|
+
if (!savedState) return false;
|
|
1047
|
+
if (!timingSafeEqualString(savedState, returnedState)) return false;
|
|
1048
|
+
removeLogoutState(this.config.clientId);
|
|
1049
|
+
return true;
|
|
1050
|
+
}
|
|
998
1051
|
/**
|
|
999
1052
|
* 获取 OIDC Discovery 文档
|
|
1000
1053
|
*
|
|
@@ -1078,6 +1131,7 @@ function SsoProvider({
|
|
|
1078
1131
|
const [user, setUser] = useState(null);
|
|
1079
1132
|
const [isLoading, setIsLoading] = useState(true);
|
|
1080
1133
|
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
1134
|
+
const [error, setError] = useState(null);
|
|
1081
1135
|
const [client] = useState(() => new SsoClient(config));
|
|
1082
1136
|
const refreshTimerRef = useRef(null);
|
|
1083
1137
|
const loadedRef = useRef(false);
|
|
@@ -1086,6 +1140,7 @@ function SsoProvider({
|
|
|
1086
1140
|
if (!tokenData) {
|
|
1087
1141
|
setUser(null);
|
|
1088
1142
|
setIsAuthenticated(false);
|
|
1143
|
+
setError(null);
|
|
1089
1144
|
setIsLoading(false);
|
|
1090
1145
|
return;
|
|
1091
1146
|
}
|
|
@@ -1093,9 +1148,13 @@ function SsoProvider({
|
|
|
1093
1148
|
const u = await client.getUserInfo();
|
|
1094
1149
|
setUser(u);
|
|
1095
1150
|
setIsAuthenticated(true);
|
|
1096
|
-
|
|
1151
|
+
setError(null);
|
|
1152
|
+
} catch (err) {
|
|
1097
1153
|
setUser(null);
|
|
1098
1154
|
setIsAuthenticated(false);
|
|
1155
|
+
setError(
|
|
1156
|
+
err instanceof SsoError ? err : new SsoError("userinfo_failed", err instanceof Error ? err.message : String(err))
|
|
1157
|
+
);
|
|
1099
1158
|
removeTokenData(client.config.clientId);
|
|
1100
1159
|
} finally {
|
|
1101
1160
|
setIsLoading(false);
|
|
@@ -1190,6 +1249,7 @@ function SsoProvider({
|
|
|
1190
1249
|
await client.logout(redirectToSso);
|
|
1191
1250
|
setUser(null);
|
|
1192
1251
|
setIsAuthenticated(false);
|
|
1252
|
+
setError(null);
|
|
1193
1253
|
},
|
|
1194
1254
|
[client]
|
|
1195
1255
|
);
|
|
@@ -1203,6 +1263,7 @@ function SsoProvider({
|
|
|
1203
1263
|
user,
|
|
1204
1264
|
isAuthenticated,
|
|
1205
1265
|
isLoading,
|
|
1266
|
+
error,
|
|
1206
1267
|
login,
|
|
1207
1268
|
loginPopup,
|
|
1208
1269
|
logout,
|
|
@@ -1221,34 +1282,67 @@ function useSso() {
|
|
|
1221
1282
|
}
|
|
1222
1283
|
|
|
1223
1284
|
// src/react/RequireAuth.tsx
|
|
1224
|
-
import React2, { useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
1285
|
+
import React2, { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
1225
1286
|
function RequireAuth({
|
|
1226
1287
|
children,
|
|
1227
1288
|
fallback,
|
|
1228
1289
|
autoLogin = true,
|
|
1229
|
-
usePopup = false
|
|
1290
|
+
usePopup = false,
|
|
1291
|
+
onError,
|
|
1292
|
+
renderLoginError
|
|
1230
1293
|
}) {
|
|
1231
1294
|
const { isAuthenticated, isLoading, login, loginPopup } = useSso();
|
|
1232
1295
|
const loginTriggeredRef = useRef2(false);
|
|
1296
|
+
const [loginError, setLoginError] = useState2(null);
|
|
1297
|
+
const triggerLogin = () => {
|
|
1298
|
+
loginTriggeredRef.current = true;
|
|
1299
|
+
const currentPath = window.location.pathname + window.location.search;
|
|
1300
|
+
const handleFailure = (err) => {
|
|
1301
|
+
loginTriggeredRef.current = false;
|
|
1302
|
+
setLoginError(err);
|
|
1303
|
+
onError?.(err);
|
|
1304
|
+
};
|
|
1305
|
+
if (usePopup) {
|
|
1306
|
+
loginPopup({ returnUrl: currentPath }).catch((err) => {
|
|
1307
|
+
if (err.code === "popup_blocked") {
|
|
1308
|
+
login(currentPath).catch(handleFailure);
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
handleFailure(err);
|
|
1312
|
+
});
|
|
1313
|
+
} else {
|
|
1314
|
+
login(currentPath).catch(handleFailure);
|
|
1315
|
+
}
|
|
1316
|
+
};
|
|
1317
|
+
const retry = () => {
|
|
1318
|
+
setLoginError(null);
|
|
1319
|
+
triggerLogin();
|
|
1320
|
+
};
|
|
1233
1321
|
useEffect2(() => {
|
|
1234
1322
|
if (!isLoading && !isAuthenticated && autoLogin && !loginTriggeredRef.current) {
|
|
1235
|
-
|
|
1236
|
-
const currentPath = window.location.pathname + window.location.search;
|
|
1237
|
-
if (usePopup) {
|
|
1238
|
-
loginPopup({ returnUrl: currentPath }).catch((err) => {
|
|
1239
|
-
if (err.code === "popup_blocked") {
|
|
1240
|
-
login(currentPath);
|
|
1241
|
-
}
|
|
1242
|
-
});
|
|
1243
|
-
} else {
|
|
1244
|
-
login(currentPath);
|
|
1245
|
-
}
|
|
1323
|
+
triggerLogin();
|
|
1246
1324
|
}
|
|
1247
1325
|
}, [isLoading, isAuthenticated, autoLogin, usePopup, login, loginPopup]);
|
|
1248
1326
|
if (isLoading) {
|
|
1249
1327
|
return fallback || React2.createElement("div", null, "\u6B63\u5728\u9A8C\u8BC1\u767B\u5F55\u72B6\u6001...");
|
|
1250
1328
|
}
|
|
1251
1329
|
if (!isAuthenticated) {
|
|
1330
|
+
if (loginError) {
|
|
1331
|
+
if (renderLoginError) {
|
|
1332
|
+
return React2.createElement(
|
|
1333
|
+
React2.Fragment,
|
|
1334
|
+
null,
|
|
1335
|
+
renderLoginError(loginError, retry)
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1338
|
+
const message = loginError instanceof Error ? loginError.message : "\u767B\u5F55\u5931\u8D25";
|
|
1339
|
+
return React2.createElement(
|
|
1340
|
+
"div",
|
|
1341
|
+
null,
|
|
1342
|
+
React2.createElement("p", null, `\u767B\u5F55\u672A\u5B8C\u6210\uFF1A${message}`),
|
|
1343
|
+
React2.createElement("button", { onClick: retry }, "\u91CD\u8BD5\u767B\u5F55")
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1252
1346
|
if (fallback) return React2.createElement(React2.Fragment, null, fallback);
|
|
1253
1347
|
return React2.createElement("div", null, "\u8BF7\u5148\u767B\u5F55");
|
|
1254
1348
|
}
|
|
@@ -1268,11 +1362,42 @@ function withAuth(Component) {
|
|
|
1268
1362
|
}
|
|
1269
1363
|
|
|
1270
1364
|
// src/react/CallbackPage.tsx
|
|
1271
|
-
import React3, { useEffect as useEffect3, useState as
|
|
1272
|
-
function
|
|
1365
|
+
import React3, { useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
|
|
1366
|
+
function DefaultCallbackError({ error }) {
|
|
1367
|
+
return React3.createElement(
|
|
1368
|
+
"div",
|
|
1369
|
+
{
|
|
1370
|
+
style: {
|
|
1371
|
+
display: "flex",
|
|
1372
|
+
flexDirection: "column",
|
|
1373
|
+
alignItems: "center",
|
|
1374
|
+
justifyContent: "center",
|
|
1375
|
+
minHeight: "100vh",
|
|
1376
|
+
fontFamily: "system-ui, sans-serif"
|
|
1377
|
+
}
|
|
1378
|
+
},
|
|
1379
|
+
React3.createElement(
|
|
1380
|
+
"p",
|
|
1381
|
+
{ style: { color: "#dc2626", marginBottom: "1rem" } },
|
|
1382
|
+
error
|
|
1383
|
+
),
|
|
1384
|
+
React3.createElement(
|
|
1385
|
+
"a",
|
|
1386
|
+
{ href: "/", style: { color: "#2563eb", textDecoration: "underline" } },
|
|
1387
|
+
"\u8FD4\u56DE\u9996\u9875"
|
|
1388
|
+
)
|
|
1389
|
+
);
|
|
1390
|
+
}
|
|
1391
|
+
function CallbackPage({ onSuccess, onError, renderError } = {}) {
|
|
1273
1392
|
const { client, refreshUser } = useSso();
|
|
1274
|
-
const [error, setError] =
|
|
1275
|
-
const [processing, setProcessing] =
|
|
1393
|
+
const [error, setError] = useState3(null);
|
|
1394
|
+
const [processing, setProcessing] = useState3(true);
|
|
1395
|
+
const onSuccessRef = useRef3(onSuccess);
|
|
1396
|
+
const onErrorRef = useRef3(onError);
|
|
1397
|
+
useEffect3(() => {
|
|
1398
|
+
onSuccessRef.current = onSuccess;
|
|
1399
|
+
onErrorRef.current = onError;
|
|
1400
|
+
});
|
|
1276
1401
|
useEffect3(() => {
|
|
1277
1402
|
if (window.opener && !window.opener.closed) {
|
|
1278
1403
|
const nonce = new URL(window.location.href).searchParams.get("popup_nonce");
|
|
@@ -1291,22 +1416,27 @@ function CallbackPage() {
|
|
|
1291
1416
|
let cancelled = false;
|
|
1292
1417
|
async function handleCallback() {
|
|
1293
1418
|
try {
|
|
1294
|
-
await client.handleCallback(window.location.href);
|
|
1419
|
+
const tokenData = await client.handleCallback(window.location.href);
|
|
1295
1420
|
if (cancelled) return;
|
|
1296
1421
|
await refreshUser();
|
|
1297
|
-
|
|
1298
|
-
|
|
1422
|
+
if (cancelled) return;
|
|
1423
|
+
const clientId = client.config.clientId;
|
|
1424
|
+
const returnUrl = getReturnUrl(clientId);
|
|
1425
|
+
removeReturnUrl(clientId);
|
|
1426
|
+
const onSuccessCb = onSuccessRef.current;
|
|
1427
|
+
if (onSuccessCb) {
|
|
1428
|
+
onSuccessCb(tokenData);
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1299
1431
|
window.location.href = returnUrl && isTrustedReturnUrl(returnUrl, window.location.origin) ? returnUrl : "/";
|
|
1300
1432
|
} catch (err) {
|
|
1301
1433
|
if (cancelled) return;
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
setError(
|
|
1306
|
-
} else if (err instanceof Error) {
|
|
1307
|
-
setError(`\u767B\u5F55\u56DE\u8C03\u5904\u7406\u5931\u8D25: ${err.message}`);
|
|
1434
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
1435
|
+
onErrorRef.current?.(errorObj);
|
|
1436
|
+
if (errorObj instanceof SsoError) {
|
|
1437
|
+
setError(errorObj.description || `SSO \u9519\u8BEF (${errorObj.code})`);
|
|
1308
1438
|
} else {
|
|
1309
|
-
setError(
|
|
1439
|
+
setError(`\u767B\u5F55\u56DE\u8C03\u5904\u7406\u5931\u8D25: ${errorObj.message}`);
|
|
1310
1440
|
}
|
|
1311
1441
|
setProcessing(false);
|
|
1312
1442
|
}
|
|
@@ -1317,29 +1447,8 @@ function CallbackPage() {
|
|
|
1317
1447
|
};
|
|
1318
1448
|
}, [client, refreshUser]);
|
|
1319
1449
|
if (error) {
|
|
1320
|
-
return React3.createElement(
|
|
1321
|
-
|
|
1322
|
-
{
|
|
1323
|
-
style: {
|
|
1324
|
-
display: "flex",
|
|
1325
|
-
flexDirection: "column",
|
|
1326
|
-
alignItems: "center",
|
|
1327
|
-
justifyContent: "center",
|
|
1328
|
-
minHeight: "100vh",
|
|
1329
|
-
fontFamily: "system-ui, sans-serif"
|
|
1330
|
-
}
|
|
1331
|
-
},
|
|
1332
|
-
React3.createElement(
|
|
1333
|
-
"p",
|
|
1334
|
-
{ style: { color: "#dc2626", marginBottom: "1rem" } },
|
|
1335
|
-
error
|
|
1336
|
-
),
|
|
1337
|
-
React3.createElement(
|
|
1338
|
-
"a",
|
|
1339
|
-
{ href: "/", style: { color: "#2563eb", textDecoration: "underline" } },
|
|
1340
|
-
"\u8FD4\u56DE\u9996\u9875"
|
|
1341
|
-
)
|
|
1342
|
-
);
|
|
1450
|
+
if (renderError) return React3.createElement(React3.Fragment, null, renderError(error));
|
|
1451
|
+
return React3.createElement(DefaultCallbackError, { error });
|
|
1343
1452
|
}
|
|
1344
1453
|
if (processing) {
|
|
1345
1454
|
return React3.createElement(
|
|
@@ -1360,6 +1469,7 @@ function CallbackPage() {
|
|
|
1360
1469
|
}
|
|
1361
1470
|
export {
|
|
1362
1471
|
CallbackPage,
|
|
1472
|
+
DefaultCallbackError,
|
|
1363
1473
|
RequireAuth,
|
|
1364
1474
|
SsoProvider,
|
|
1365
1475
|
useSso,
|