antaeus.keycloak.react 2.6.1 → 2.6.4
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 +3 -0
- package/dist/index.d.mts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +114 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +115 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -5
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createContext, useContext, useState, useRef, useCallback, useEffect, useMemo } from 'react';
|
|
2
2
|
import { useAuth as useAuth$1, AuthContext, AuthProvider } from 'react-oidc-context';
|
|
3
|
-
import { WebStorageStateStore, User } from 'oidc-client-ts';
|
|
3
|
+
import { WebStorageStateStore, User, ErrorResponse } from 'oidc-client-ts';
|
|
4
4
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
5
|
import { useLocation } from 'react-router-dom';
|
|
6
6
|
import { QRCodeSVG } from 'qrcode.react';
|
|
@@ -33,7 +33,8 @@ var defaultConfig = {
|
|
|
33
33
|
// 30 days in seconds
|
|
34
34
|
showLoadingIndicator: false,
|
|
35
35
|
loadingMessage: "Refreshing session...",
|
|
36
|
-
useRefreshToken: true
|
|
36
|
+
useRefreshToken: true,
|
|
37
|
+
refreshOnResume: true
|
|
37
38
|
},
|
|
38
39
|
advanced: {
|
|
39
40
|
responseType: "code",
|
|
@@ -162,6 +163,20 @@ var isTransientNetworkError = (error) => {
|
|
|
162
163
|
}
|
|
163
164
|
return message.includes("network") || message.includes("failed to fetch") || message.includes("fetch") || message.includes("timeout") || message.includes("temporarily unavailable") || message.includes("dns") || message.includes("offline");
|
|
164
165
|
};
|
|
166
|
+
var isInvalidRefreshTokenError = (error) => {
|
|
167
|
+
if (error instanceof ErrorResponse) {
|
|
168
|
+
const code = error.error?.toLowerCase?.();
|
|
169
|
+
if (code === "invalid_grant" || code === "invalid_token") {
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
const description = error.error_description?.toLowerCase?.();
|
|
173
|
+
if (description?.includes("refresh token")) {
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const message = error.message?.toLowerCase?.() ?? "";
|
|
178
|
+
return message.includes("invalid_grant") || message.includes("refresh token") || message.includes("invalid token");
|
|
179
|
+
};
|
|
165
180
|
function useTokenLifecycle(options) {
|
|
166
181
|
const {
|
|
167
182
|
config,
|
|
@@ -174,6 +189,7 @@ function useTokenLifecycle(options) {
|
|
|
174
189
|
const auth = useAuth();
|
|
175
190
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
176
191
|
const [isRefreshTokenExpired, setIsRefreshTokenExpired] = useState(false);
|
|
192
|
+
const isRefreshingRef = useRef(false);
|
|
177
193
|
const hasCheckedOnStartup = useRef(false);
|
|
178
194
|
const supportsRefreshTokens = config.silentRenew?.useRefreshToken !== false;
|
|
179
195
|
const [pendingRetry, setPendingRetry] = useState(null);
|
|
@@ -226,10 +242,11 @@ function useTokenLifecycle(options) {
|
|
|
226
242
|
return elapsedSeconds >= maxLifetime;
|
|
227
243
|
}, [config.silentRenew?.maxRefreshTokenLifetime, getStorageKey, log, storeFirstLoginTime]);
|
|
228
244
|
const refreshToken = useCallback(async () => {
|
|
229
|
-
if (
|
|
245
|
+
if (isRefreshingRef.current) {
|
|
230
246
|
log("Refresh already in progress, skipping");
|
|
231
247
|
return;
|
|
232
248
|
}
|
|
249
|
+
isRefreshingRef.current = true;
|
|
233
250
|
setIsRefreshing(true);
|
|
234
251
|
deferredRefreshReasonRef.current = null;
|
|
235
252
|
try {
|
|
@@ -252,6 +269,20 @@ function useTokenLifecycle(options) {
|
|
|
252
269
|
onRefreshSuccess?.();
|
|
253
270
|
} catch (error) {
|
|
254
271
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
272
|
+
if (isInvalidRefreshTokenError(err)) {
|
|
273
|
+
log("Refresh token rejected by the identity provider, clearing stored session");
|
|
274
|
+
setIsRefreshTokenExpired(true);
|
|
275
|
+
onRefreshTokenExpired?.();
|
|
276
|
+
const key = getStorageKey();
|
|
277
|
+
localStorage.removeItem(key);
|
|
278
|
+
try {
|
|
279
|
+
await auth.removeUser();
|
|
280
|
+
} catch (removeError) {
|
|
281
|
+
log("Error removing user after invalid refresh token:", removeError);
|
|
282
|
+
}
|
|
283
|
+
onRefreshError?.(err);
|
|
284
|
+
throw err;
|
|
285
|
+
}
|
|
255
286
|
if (isTransientNetworkError(err)) {
|
|
256
287
|
const reason = isNavigatorOffline() ? "offline" : "network";
|
|
257
288
|
deferredRefreshReasonRef.current = reason;
|
|
@@ -271,10 +302,10 @@ function useTokenLifecycle(options) {
|
|
|
271
302
|
onRefreshError?.(err);
|
|
272
303
|
throw err;
|
|
273
304
|
} finally {
|
|
305
|
+
isRefreshingRef.current = false;
|
|
274
306
|
setIsRefreshing(false);
|
|
275
307
|
}
|
|
276
308
|
}, [
|
|
277
|
-
isRefreshing,
|
|
278
309
|
auth,
|
|
279
310
|
checkRefreshTokenLifetime,
|
|
280
311
|
onRefreshStart,
|
|
@@ -282,6 +313,39 @@ function useTokenLifecycle(options) {
|
|
|
282
313
|
onRefreshError,
|
|
283
314
|
onRefreshTokenExpired,
|
|
284
315
|
enqueueRetry,
|
|
316
|
+
getStorageKey,
|
|
317
|
+
log
|
|
318
|
+
]);
|
|
319
|
+
const shouldRefreshOnResume = useCallback((reason) => {
|
|
320
|
+
const silentRenewEnabled = config.silentRenew?.enabled !== false;
|
|
321
|
+
const refreshOnResume = config.silentRenew?.refreshOnResume !== false;
|
|
322
|
+
const strategy = config.silentRenew?.refreshStrategy || "both";
|
|
323
|
+
const strategyAllowsResume = strategy === "startup" || strategy === "both";
|
|
324
|
+
const hasRefreshToken = Boolean(auth.user?.refresh_token);
|
|
325
|
+
if (!silentRenewEnabled || !refreshOnResume || !strategyAllowsResume) {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
if (auth.isLoading) {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
if (!auth.user) {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
if (supportsRefreshTokens && !hasRefreshToken) {
|
|
335
|
+
log("Skipping resume token check because no refresh token is available:", reason);
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
if (reason !== "online" && typeof document !== "undefined" && document.visibilityState === "hidden") {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
return true;
|
|
342
|
+
}, [
|
|
343
|
+
auth.isLoading,
|
|
344
|
+
auth.user,
|
|
345
|
+
config.silentRenew?.enabled,
|
|
346
|
+
config.silentRenew?.refreshOnResume,
|
|
347
|
+
config.silentRenew?.refreshStrategy,
|
|
348
|
+
supportsRefreshTokens,
|
|
285
349
|
log
|
|
286
350
|
]);
|
|
287
351
|
const checkAndRefreshTokens = useCallback(async () => {
|
|
@@ -431,6 +495,38 @@ function useTokenLifecycle(options) {
|
|
|
431
495
|
checkAndRefreshTokens,
|
|
432
496
|
log
|
|
433
497
|
]);
|
|
498
|
+
useEffect(() => {
|
|
499
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
const runResumeCheck = (reason) => {
|
|
503
|
+
if (!shouldRefreshOnResume(reason)) {
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
log("Running resume token check after:", reason);
|
|
507
|
+
checkAndRefreshTokens().catch((error) => {
|
|
508
|
+
log("Resume token check failed:", error);
|
|
509
|
+
});
|
|
510
|
+
};
|
|
511
|
+
const handleVisibilityChange = () => {
|
|
512
|
+
if (document.visibilityState === "visible") {
|
|
513
|
+
runResumeCheck("visibilitychange");
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
const handlePageShow = () => runResumeCheck("pageshow");
|
|
517
|
+
const handleFocus = () => runResumeCheck("focus");
|
|
518
|
+
const handleOnline = () => runResumeCheck("online");
|
|
519
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
520
|
+
window.addEventListener("pageshow", handlePageShow);
|
|
521
|
+
window.addEventListener("focus", handleFocus);
|
|
522
|
+
window.addEventListener("online", handleOnline);
|
|
523
|
+
return () => {
|
|
524
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
525
|
+
window.removeEventListener("pageshow", handlePageShow);
|
|
526
|
+
window.removeEventListener("focus", handleFocus);
|
|
527
|
+
window.removeEventListener("online", handleOnline);
|
|
528
|
+
};
|
|
529
|
+
}, [checkAndRefreshTokens, log, shouldRefreshOnResume]);
|
|
434
530
|
useEffect(() => {
|
|
435
531
|
if (hasCheckedOnStartup.current) {
|
|
436
532
|
const hasUser = Boolean(auth.user);
|
|
@@ -445,6 +541,11 @@ function useTokenLifecycle(options) {
|
|
|
445
541
|
storeFirstLoginTime();
|
|
446
542
|
}
|
|
447
543
|
}, [auth.user, auth.isLoading, storeFirstLoginTime]);
|
|
544
|
+
useEffect(() => {
|
|
545
|
+
if (auth.isAuthenticated && isRefreshTokenExpired) {
|
|
546
|
+
setIsRefreshTokenExpired(false);
|
|
547
|
+
}
|
|
548
|
+
}, [auth.isAuthenticated, isRefreshTokenExpired]);
|
|
448
549
|
useEffect(() => {
|
|
449
550
|
if (!auth.isAuthenticated || auth.isLoading) {
|
|
450
551
|
return;
|
|
@@ -848,7 +949,8 @@ var CONFIG_PRESETS = {
|
|
|
848
949
|
maxRefreshTokenLifetime: 2592e3,
|
|
849
950
|
// 30 days
|
|
850
951
|
showLoadingIndicator: false,
|
|
851
|
-
loadingMessage: "Refreshing session..."
|
|
952
|
+
loadingMessage: "Refreshing session...",
|
|
953
|
+
refreshOnResume: true
|
|
852
954
|
},
|
|
853
955
|
advanced: {
|
|
854
956
|
responseType: "code",
|
|
@@ -884,7 +986,8 @@ var CONFIG_PRESETS = {
|
|
|
884
986
|
// 30 days
|
|
885
987
|
showLoadingIndicator: true,
|
|
886
988
|
// Show loading for better UX
|
|
887
|
-
loadingMessage: "Restoring your session..."
|
|
989
|
+
loadingMessage: "Restoring your session...",
|
|
990
|
+
refreshOnResume: true
|
|
888
991
|
},
|
|
889
992
|
advanced: {
|
|
890
993
|
responseType: "code",
|
|
@@ -921,7 +1024,8 @@ var CONFIG_PRESETS = {
|
|
|
921
1024
|
// 24 hours only
|
|
922
1025
|
showLoadingIndicator: false,
|
|
923
1026
|
// Silent for security
|
|
924
|
-
loadingMessage: "Validating session..."
|
|
1027
|
+
loadingMessage: "Validating session...",
|
|
1028
|
+
refreshOnResume: true
|
|
925
1029
|
},
|
|
926
1030
|
advanced: {
|
|
927
1031
|
responseType: "code",
|
|
@@ -955,7 +1059,8 @@ var CONFIG_PRESETS = {
|
|
|
955
1059
|
maxRefreshTokenLifetime: 604800,
|
|
956
1060
|
// 7 days
|
|
957
1061
|
showLoadingIndicator: false,
|
|
958
|
-
loadingMessage: "Refreshing session..."
|
|
1062
|
+
loadingMessage: "Refreshing session...",
|
|
1063
|
+
refreshOnResume: true
|
|
959
1064
|
},
|
|
960
1065
|
advanced: {
|
|
961
1066
|
responseType: "code",
|
|
@@ -992,7 +1097,8 @@ var CONFIG_PRESETS = {
|
|
|
992
1097
|
// No limit (for convenience)
|
|
993
1098
|
showLoadingIndicator: true,
|
|
994
1099
|
// Show for debugging
|
|
995
|
-
loadingMessage: "[DEV] Refreshing tokens..."
|
|
1100
|
+
loadingMessage: "[DEV] Refreshing tokens...",
|
|
1101
|
+
refreshOnResume: true
|
|
996
1102
|
},
|
|
997
1103
|
advanced: {
|
|
998
1104
|
responseType: "code",
|