antaeus.keycloak.react 2.6.2 → 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 +80 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +80 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -30,6 +30,7 @@ That's it! You now have full authentication with automatic token refresh.
|
|
|
30
30
|
- Zero-config setup
|
|
31
31
|
- Automatic token lifecycle management
|
|
32
32
|
- Token refresh on app startup
|
|
33
|
+
- Token refresh when suspended mobile/PWA sessions resume
|
|
33
34
|
- Silent token renewal
|
|
34
35
|
- Session monitoring UI
|
|
35
36
|
- Protected routes
|
|
@@ -152,6 +153,8 @@ const { isEnabled, isConfigured, reason } = useKeycloakStatus();
|
|
|
152
153
|
- **on-demand**: Only when API returns 401
|
|
153
154
|
- **both** (recommended): Startup + 401 retry
|
|
154
155
|
|
|
156
|
+
For mobile browsers and installed PWAs, token freshness is also checked when the app resumes from the background by default. Set `silentRenew.refreshOnResume` to `false` to disable this behavior.
|
|
157
|
+
|
|
155
158
|
## React Hooks
|
|
156
159
|
|
|
157
160
|
### useAuth()
|
package/dist/index.d.mts
CHANGED
|
@@ -121,6 +121,12 @@ interface SilentRenewConfig {
|
|
|
121
121
|
* @default true
|
|
122
122
|
*/
|
|
123
123
|
useRefreshToken?: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* Re-check token freshness when a suspended tab/PWA resumes.
|
|
126
|
+
* Useful for mobile browsers and installed PWAs where timers pause in the background.
|
|
127
|
+
* @default true
|
|
128
|
+
*/
|
|
129
|
+
refreshOnResume?: boolean;
|
|
124
130
|
}
|
|
125
131
|
/**
|
|
126
132
|
* Event callbacks for authentication lifecycle
|
package/dist/index.d.ts
CHANGED
|
@@ -121,6 +121,12 @@ interface SilentRenewConfig {
|
|
|
121
121
|
* @default true
|
|
122
122
|
*/
|
|
123
123
|
useRefreshToken?: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* Re-check token freshness when a suspended tab/PWA resumes.
|
|
126
|
+
* Useful for mobile browsers and installed PWAs where timers pause in the background.
|
|
127
|
+
* @default true
|
|
128
|
+
*/
|
|
129
|
+
refreshOnResume?: boolean;
|
|
124
130
|
}
|
|
125
131
|
/**
|
|
126
132
|
* Event callbacks for authentication lifecycle
|
package/dist/index.js
CHANGED
|
@@ -36,7 +36,8 @@ var defaultConfig = {
|
|
|
36
36
|
// 30 days in seconds
|
|
37
37
|
showLoadingIndicator: false,
|
|
38
38
|
loadingMessage: "Refreshing session...",
|
|
39
|
-
useRefreshToken: true
|
|
39
|
+
useRefreshToken: true,
|
|
40
|
+
refreshOnResume: true
|
|
40
41
|
},
|
|
41
42
|
advanced: {
|
|
42
43
|
responseType: "code",
|
|
@@ -191,6 +192,7 @@ function useTokenLifecycle(options) {
|
|
|
191
192
|
const auth = useAuth();
|
|
192
193
|
const [isRefreshing, setIsRefreshing] = react.useState(false);
|
|
193
194
|
const [isRefreshTokenExpired, setIsRefreshTokenExpired] = react.useState(false);
|
|
195
|
+
const isRefreshingRef = react.useRef(false);
|
|
194
196
|
const hasCheckedOnStartup = react.useRef(false);
|
|
195
197
|
const supportsRefreshTokens = config.silentRenew?.useRefreshToken !== false;
|
|
196
198
|
const [pendingRetry, setPendingRetry] = react.useState(null);
|
|
@@ -243,10 +245,11 @@ function useTokenLifecycle(options) {
|
|
|
243
245
|
return elapsedSeconds >= maxLifetime;
|
|
244
246
|
}, [config.silentRenew?.maxRefreshTokenLifetime, getStorageKey, log, storeFirstLoginTime]);
|
|
245
247
|
const refreshToken = react.useCallback(async () => {
|
|
246
|
-
if (
|
|
248
|
+
if (isRefreshingRef.current) {
|
|
247
249
|
log("Refresh already in progress, skipping");
|
|
248
250
|
return;
|
|
249
251
|
}
|
|
252
|
+
isRefreshingRef.current = true;
|
|
250
253
|
setIsRefreshing(true);
|
|
251
254
|
deferredRefreshReasonRef.current = null;
|
|
252
255
|
try {
|
|
@@ -302,10 +305,10 @@ function useTokenLifecycle(options) {
|
|
|
302
305
|
onRefreshError?.(err);
|
|
303
306
|
throw err;
|
|
304
307
|
} finally {
|
|
308
|
+
isRefreshingRef.current = false;
|
|
305
309
|
setIsRefreshing(false);
|
|
306
310
|
}
|
|
307
311
|
}, [
|
|
308
|
-
isRefreshing,
|
|
309
312
|
auth,
|
|
310
313
|
checkRefreshTokenLifetime,
|
|
311
314
|
onRefreshStart,
|
|
@@ -316,6 +319,38 @@ function useTokenLifecycle(options) {
|
|
|
316
319
|
getStorageKey,
|
|
317
320
|
log
|
|
318
321
|
]);
|
|
322
|
+
const shouldRefreshOnResume = react.useCallback((reason) => {
|
|
323
|
+
const silentRenewEnabled = config.silentRenew?.enabled !== false;
|
|
324
|
+
const refreshOnResume = config.silentRenew?.refreshOnResume !== false;
|
|
325
|
+
const strategy = config.silentRenew?.refreshStrategy || "both";
|
|
326
|
+
const strategyAllowsResume = strategy === "startup" || strategy === "both";
|
|
327
|
+
const hasRefreshToken = Boolean(auth.user?.refresh_token);
|
|
328
|
+
if (!silentRenewEnabled || !refreshOnResume || !strategyAllowsResume) {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
if (auth.isLoading) {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
if (!auth.user) {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
if (supportsRefreshTokens && !hasRefreshToken) {
|
|
338
|
+
log("Skipping resume token check because no refresh token is available:", reason);
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
if (reason !== "online" && typeof document !== "undefined" && document.visibilityState === "hidden") {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
return true;
|
|
345
|
+
}, [
|
|
346
|
+
auth.isLoading,
|
|
347
|
+
auth.user,
|
|
348
|
+
config.silentRenew?.enabled,
|
|
349
|
+
config.silentRenew?.refreshOnResume,
|
|
350
|
+
config.silentRenew?.refreshStrategy,
|
|
351
|
+
supportsRefreshTokens,
|
|
352
|
+
log
|
|
353
|
+
]);
|
|
319
354
|
const checkAndRefreshTokens = react.useCallback(async () => {
|
|
320
355
|
if (!auth.user) {
|
|
321
356
|
log("No user session, skipping token check");
|
|
@@ -463,6 +498,38 @@ function useTokenLifecycle(options) {
|
|
|
463
498
|
checkAndRefreshTokens,
|
|
464
499
|
log
|
|
465
500
|
]);
|
|
501
|
+
react.useEffect(() => {
|
|
502
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
const runResumeCheck = (reason) => {
|
|
506
|
+
if (!shouldRefreshOnResume(reason)) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
log("Running resume token check after:", reason);
|
|
510
|
+
checkAndRefreshTokens().catch((error) => {
|
|
511
|
+
log("Resume token check failed:", error);
|
|
512
|
+
});
|
|
513
|
+
};
|
|
514
|
+
const handleVisibilityChange = () => {
|
|
515
|
+
if (document.visibilityState === "visible") {
|
|
516
|
+
runResumeCheck("visibilitychange");
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
const handlePageShow = () => runResumeCheck("pageshow");
|
|
520
|
+
const handleFocus = () => runResumeCheck("focus");
|
|
521
|
+
const handleOnline = () => runResumeCheck("online");
|
|
522
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
523
|
+
window.addEventListener("pageshow", handlePageShow);
|
|
524
|
+
window.addEventListener("focus", handleFocus);
|
|
525
|
+
window.addEventListener("online", handleOnline);
|
|
526
|
+
return () => {
|
|
527
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
528
|
+
window.removeEventListener("pageshow", handlePageShow);
|
|
529
|
+
window.removeEventListener("focus", handleFocus);
|
|
530
|
+
window.removeEventListener("online", handleOnline);
|
|
531
|
+
};
|
|
532
|
+
}, [checkAndRefreshTokens, log, shouldRefreshOnResume]);
|
|
466
533
|
react.useEffect(() => {
|
|
467
534
|
if (hasCheckedOnStartup.current) {
|
|
468
535
|
const hasUser = Boolean(auth.user);
|
|
@@ -885,7 +952,8 @@ var CONFIG_PRESETS = {
|
|
|
885
952
|
maxRefreshTokenLifetime: 2592e3,
|
|
886
953
|
// 30 days
|
|
887
954
|
showLoadingIndicator: false,
|
|
888
|
-
loadingMessage: "Refreshing session..."
|
|
955
|
+
loadingMessage: "Refreshing session...",
|
|
956
|
+
refreshOnResume: true
|
|
889
957
|
},
|
|
890
958
|
advanced: {
|
|
891
959
|
responseType: "code",
|
|
@@ -921,7 +989,8 @@ var CONFIG_PRESETS = {
|
|
|
921
989
|
// 30 days
|
|
922
990
|
showLoadingIndicator: true,
|
|
923
991
|
// Show loading for better UX
|
|
924
|
-
loadingMessage: "Restoring your session..."
|
|
992
|
+
loadingMessage: "Restoring your session...",
|
|
993
|
+
refreshOnResume: true
|
|
925
994
|
},
|
|
926
995
|
advanced: {
|
|
927
996
|
responseType: "code",
|
|
@@ -958,7 +1027,8 @@ var CONFIG_PRESETS = {
|
|
|
958
1027
|
// 24 hours only
|
|
959
1028
|
showLoadingIndicator: false,
|
|
960
1029
|
// Silent for security
|
|
961
|
-
loadingMessage: "Validating session..."
|
|
1030
|
+
loadingMessage: "Validating session...",
|
|
1031
|
+
refreshOnResume: true
|
|
962
1032
|
},
|
|
963
1033
|
advanced: {
|
|
964
1034
|
responseType: "code",
|
|
@@ -992,7 +1062,8 @@ var CONFIG_PRESETS = {
|
|
|
992
1062
|
maxRefreshTokenLifetime: 604800,
|
|
993
1063
|
// 7 days
|
|
994
1064
|
showLoadingIndicator: false,
|
|
995
|
-
loadingMessage: "Refreshing session..."
|
|
1065
|
+
loadingMessage: "Refreshing session...",
|
|
1066
|
+
refreshOnResume: true
|
|
996
1067
|
},
|
|
997
1068
|
advanced: {
|
|
998
1069
|
responseType: "code",
|
|
@@ -1029,7 +1100,8 @@ var CONFIG_PRESETS = {
|
|
|
1029
1100
|
// No limit (for convenience)
|
|
1030
1101
|
showLoadingIndicator: true,
|
|
1031
1102
|
// Show for debugging
|
|
1032
|
-
loadingMessage: "[DEV] Refreshing tokens..."
|
|
1103
|
+
loadingMessage: "[DEV] Refreshing tokens...",
|
|
1104
|
+
refreshOnResume: true
|
|
1033
1105
|
},
|
|
1034
1106
|
advanced: {
|
|
1035
1107
|
responseType: "code",
|