antaeus.keycloak.react 2.6.4 → 2.7.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 +70 -0
- package/dist/index.d.mts +48 -3
- package/dist/index.d.ts +48 -3
- package/dist/index.js +116 -21
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +112 -22
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -34,6 +34,12 @@ var defaultConfig = {
|
|
|
34
34
|
showLoadingIndicator: false,
|
|
35
35
|
loadingMessage: "Refreshing session...",
|
|
36
36
|
useRefreshToken: true,
|
|
37
|
+
allowIframeFallback: false,
|
|
38
|
+
transientRetry: {
|
|
39
|
+
maxAttempts: 3,
|
|
40
|
+
initialDelayMs: 1e3,
|
|
41
|
+
maxDelayMs: 3e4
|
|
42
|
+
},
|
|
37
43
|
refreshOnResume: true
|
|
38
44
|
},
|
|
39
45
|
advanced: {
|
|
@@ -60,7 +66,6 @@ var KeycloakConfigBuilder = class {
|
|
|
60
66
|
const config = this.mergeWithDefaults(userConfig);
|
|
61
67
|
const storageType = config.advanced.storageType || "session";
|
|
62
68
|
const storage = storageType === "local" ? window.localStorage : window.sessionStorage;
|
|
63
|
-
const { useRefreshToken } = config.silentRenew;
|
|
64
69
|
const oidcSettings = {
|
|
65
70
|
authority: config.authority,
|
|
66
71
|
client_id: config.clientId,
|
|
@@ -78,9 +83,6 @@ var KeycloakConfigBuilder = class {
|
|
|
78
83
|
response_mode: "query"
|
|
79
84
|
}
|
|
80
85
|
};
|
|
81
|
-
if (useRefreshToken !== void 0) {
|
|
82
|
-
oidcSettings.useRefreshToken = useRefreshToken;
|
|
83
|
-
}
|
|
84
86
|
return oidcSettings;
|
|
85
87
|
}
|
|
86
88
|
/**
|
|
@@ -155,6 +157,9 @@ var useAuth = useAuth$1;
|
|
|
155
157
|
|
|
156
158
|
// src/hooks/useTokenLifecycle.ts
|
|
157
159
|
var STORAGE_KEY_PREFIX = "keycloak_first_login_";
|
|
160
|
+
var DEFAULT_MAX_TRANSIENT_RETRIES = 3;
|
|
161
|
+
var DEFAULT_RETRY_INITIAL_DELAY_MS = 1e3;
|
|
162
|
+
var DEFAULT_RETRY_MAX_DELAY_MS = 3e4;
|
|
158
163
|
var isNavigatorOffline = () => typeof navigator !== "undefined" && "onLine" in navigator && navigator.onLine === false;
|
|
159
164
|
var isTransientNetworkError = (error) => {
|
|
160
165
|
const message = error.message?.toLowerCase?.() ?? "";
|
|
@@ -190,13 +195,34 @@ function useTokenLifecycle(options) {
|
|
|
190
195
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
191
196
|
const [isRefreshTokenExpired, setIsRefreshTokenExpired] = useState(false);
|
|
192
197
|
const isRefreshingRef = useRef(false);
|
|
198
|
+
const isMountedRef = useRef(true);
|
|
193
199
|
const hasCheckedOnStartup = useRef(false);
|
|
194
|
-
const
|
|
200
|
+
const allowIframeFallback = config.silentRenew?.allowIframeFallback === true;
|
|
195
201
|
const [pendingRetry, setPendingRetry] = useState(null);
|
|
196
202
|
const deferredRefreshReasonRef = useRef(null);
|
|
203
|
+
const transientRetryAttemptsRef = useRef(0);
|
|
197
204
|
const enqueueRetry = useCallback((reason) => {
|
|
198
205
|
setPendingRetry((current) => current === reason ? current : reason);
|
|
199
206
|
}, []);
|
|
207
|
+
const transientRetry = config.silentRenew?.transientRetry;
|
|
208
|
+
const maxTransientRetries = Math.max(
|
|
209
|
+
0,
|
|
210
|
+
transientRetry?.maxAttempts ?? DEFAULT_MAX_TRANSIENT_RETRIES
|
|
211
|
+
);
|
|
212
|
+
const retryInitialDelayMs = Math.max(
|
|
213
|
+
0,
|
|
214
|
+
transientRetry?.initialDelayMs ?? DEFAULT_RETRY_INITIAL_DELAY_MS
|
|
215
|
+
);
|
|
216
|
+
const retryMaxDelayMs = Math.max(
|
|
217
|
+
retryInitialDelayMs,
|
|
218
|
+
transientRetry?.maxDelayMs ?? DEFAULT_RETRY_MAX_DELAY_MS
|
|
219
|
+
);
|
|
220
|
+
useEffect(() => {
|
|
221
|
+
isMountedRef.current = true;
|
|
222
|
+
return () => {
|
|
223
|
+
isMountedRef.current = false;
|
|
224
|
+
};
|
|
225
|
+
}, []);
|
|
200
226
|
const log = useCallback(
|
|
201
227
|
(message, ...args) => {
|
|
202
228
|
if (debug) {
|
|
@@ -266,6 +292,7 @@ function useTokenLifecycle(options) {
|
|
|
266
292
|
log("Starting token refresh");
|
|
267
293
|
await auth.signinSilent();
|
|
268
294
|
log("Token refresh successful");
|
|
295
|
+
transientRetryAttemptsRef.current = 0;
|
|
269
296
|
onRefreshSuccess?.();
|
|
270
297
|
} catch (error) {
|
|
271
298
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
@@ -288,7 +315,15 @@ function useTokenLifecycle(options) {
|
|
|
288
315
|
deferredRefreshReasonRef.current = reason;
|
|
289
316
|
if (reason === "offline") {
|
|
290
317
|
log("Token refresh deferred because device is offline");
|
|
318
|
+
transientRetryAttemptsRef.current = 0;
|
|
291
319
|
} else {
|
|
320
|
+
if (transientRetryAttemptsRef.current >= maxTransientRetries) {
|
|
321
|
+
deferredRefreshReasonRef.current = null;
|
|
322
|
+
log("Token refresh retry limit reached:", err.message);
|
|
323
|
+
onRefreshError?.(err);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
transientRetryAttemptsRef.current += 1;
|
|
292
327
|
log(
|
|
293
328
|
"Token refresh failed due to transient network issue, will retry automatically:",
|
|
294
329
|
err.message
|
|
@@ -314,7 +349,8 @@ function useTokenLifecycle(options) {
|
|
|
314
349
|
onRefreshTokenExpired,
|
|
315
350
|
enqueueRetry,
|
|
316
351
|
getStorageKey,
|
|
317
|
-
log
|
|
352
|
+
log,
|
|
353
|
+
maxTransientRetries
|
|
318
354
|
]);
|
|
319
355
|
const shouldRefreshOnResume = useCallback((reason) => {
|
|
320
356
|
const silentRenewEnabled = config.silentRenew?.enabled !== false;
|
|
@@ -331,7 +367,7 @@ function useTokenLifecycle(options) {
|
|
|
331
367
|
if (!auth.user) {
|
|
332
368
|
return false;
|
|
333
369
|
}
|
|
334
|
-
if (
|
|
370
|
+
if (!hasRefreshToken && !allowIframeFallback) {
|
|
335
371
|
log("Skipping resume token check because no refresh token is available:", reason);
|
|
336
372
|
return false;
|
|
337
373
|
}
|
|
@@ -345,7 +381,7 @@ function useTokenLifecycle(options) {
|
|
|
345
381
|
config.silentRenew?.enabled,
|
|
346
382
|
config.silentRenew?.refreshOnResume,
|
|
347
383
|
config.silentRenew?.refreshStrategy,
|
|
348
|
-
|
|
384
|
+
allowIframeFallback,
|
|
349
385
|
log
|
|
350
386
|
]);
|
|
351
387
|
const checkAndRefreshTokens = useCallback(async () => {
|
|
@@ -418,10 +454,10 @@ function useTokenLifecycle(options) {
|
|
|
418
454
|
}
|
|
419
455
|
let cancelled = false;
|
|
420
456
|
const executeRetry = () => {
|
|
421
|
-
if (
|
|
457
|
+
if (!isMountedRef.current) {
|
|
422
458
|
return;
|
|
423
459
|
}
|
|
424
|
-
if (
|
|
460
|
+
if (isRefreshingRef.current) {
|
|
425
461
|
log("Skipping scheduled token refresh retry because a refresh is already in progress");
|
|
426
462
|
return;
|
|
427
463
|
}
|
|
@@ -445,19 +481,23 @@ function useTokenLifecycle(options) {
|
|
|
445
481
|
window.removeEventListener("online", handleOnline);
|
|
446
482
|
};
|
|
447
483
|
}
|
|
484
|
+
const retryDelay = Math.min(
|
|
485
|
+
retryInitialDelayMs * 2 ** Math.max(0, transientRetryAttemptsRef.current - 1),
|
|
486
|
+
retryMaxDelayMs
|
|
487
|
+
);
|
|
448
488
|
const timeoutId = window.setTimeout(() => {
|
|
449
489
|
if (cancelled) {
|
|
450
490
|
return;
|
|
451
491
|
}
|
|
452
492
|
log("Retrying token refresh after transient network failure delay");
|
|
453
493
|
setPendingRetry(null);
|
|
454
|
-
executeRetry
|
|
455
|
-
},
|
|
494
|
+
window.setTimeout(executeRetry, 0);
|
|
495
|
+
}, retryDelay);
|
|
456
496
|
return () => {
|
|
457
497
|
cancelled = true;
|
|
458
498
|
window.clearTimeout(timeoutId);
|
|
459
499
|
};
|
|
460
|
-
}, [pendingRetry, refreshToken,
|
|
500
|
+
}, [pendingRetry, refreshToken, log, retryInitialDelayMs, retryMaxDelayMs]);
|
|
461
501
|
useEffect(() => {
|
|
462
502
|
const strategy = config.silentRenew?.refreshStrategy || "both";
|
|
463
503
|
const shouldCheckOnStartup = strategy === "startup" || strategy === "both";
|
|
@@ -469,10 +509,7 @@ function useTokenLifecycle(options) {
|
|
|
469
509
|
return;
|
|
470
510
|
}
|
|
471
511
|
if (!auth.isAuthenticated) {
|
|
472
|
-
if (!
|
|
473
|
-
return;
|
|
474
|
-
}
|
|
475
|
-
if (!auth.user || !hasRefreshToken) {
|
|
512
|
+
if (!auth.user || !hasRefreshToken && !allowIframeFallback) {
|
|
476
513
|
return;
|
|
477
514
|
}
|
|
478
515
|
}
|
|
@@ -481,7 +518,7 @@ function useTokenLifecycle(options) {
|
|
|
481
518
|
"Running startup token check with strategy:",
|
|
482
519
|
strategy,
|
|
483
520
|
"using refresh token:",
|
|
484
|
-
!auth.isAuthenticated &&
|
|
521
|
+
!auth.isAuthenticated && hasRefreshToken
|
|
485
522
|
);
|
|
486
523
|
checkAndRefreshTokens().catch((error) => {
|
|
487
524
|
log("Startup token check failed:", error);
|
|
@@ -491,7 +528,7 @@ function useTokenLifecycle(options) {
|
|
|
491
528
|
auth.isLoading,
|
|
492
529
|
auth.user,
|
|
493
530
|
config.silentRenew?.refreshStrategy,
|
|
494
|
-
|
|
531
|
+
allowIframeFallback,
|
|
495
532
|
checkAndRefreshTokens,
|
|
496
533
|
log
|
|
497
534
|
]);
|
|
@@ -531,11 +568,11 @@ function useTokenLifecycle(options) {
|
|
|
531
568
|
if (hasCheckedOnStartup.current) {
|
|
532
569
|
const hasUser = Boolean(auth.user);
|
|
533
570
|
const hasRefreshToken = Boolean(auth.user?.refresh_token);
|
|
534
|
-
if (!auth.isAuthenticated && (!
|
|
571
|
+
if (!auth.isAuthenticated && (!hasUser || !hasRefreshToken && !allowIframeFallback)) {
|
|
535
572
|
hasCheckedOnStartup.current = false;
|
|
536
573
|
}
|
|
537
574
|
}
|
|
538
|
-
}, [auth.isAuthenticated, auth.user,
|
|
575
|
+
}, [auth.isAuthenticated, auth.user, allowIframeFallback]);
|
|
539
576
|
useEffect(() => {
|
|
540
577
|
if (auth.user && !auth.isLoading) {
|
|
541
578
|
storeFirstLoginTime();
|
|
@@ -1992,6 +2029,59 @@ function useProtectedFetch(options = {}) {
|
|
|
1992
2029
|
};
|
|
1993
2030
|
}
|
|
1994
2031
|
|
|
2032
|
+
// src/utils/storedOidcSession.ts
|
|
2033
|
+
var getOidcUserStorageKey = (authority, clientId) => {
|
|
2034
|
+
if (!authority || !clientId) {
|
|
2035
|
+
return null;
|
|
2036
|
+
}
|
|
2037
|
+
return `oidc.user:${authority}:${clientId}`;
|
|
2038
|
+
};
|
|
2039
|
+
var getStoredOidcUser = ({
|
|
2040
|
+
authority,
|
|
2041
|
+
clientId,
|
|
2042
|
+
storage
|
|
2043
|
+
}) => {
|
|
2044
|
+
const storageKey = getOidcUserStorageKey(authority, clientId);
|
|
2045
|
+
if (!storageKey || !storage) {
|
|
2046
|
+
return null;
|
|
2047
|
+
}
|
|
2048
|
+
try {
|
|
2049
|
+
const storedUser = storage.getItem(storageKey);
|
|
2050
|
+
if (!storedUser) {
|
|
2051
|
+
return null;
|
|
2052
|
+
}
|
|
2053
|
+
return JSON.parse(storedUser);
|
|
2054
|
+
} catch {
|
|
2055
|
+
return null;
|
|
2056
|
+
}
|
|
2057
|
+
};
|
|
2058
|
+
var hasStoredOidcRefreshToken = (args) => {
|
|
2059
|
+
const storedUser = getStoredOidcUser(args);
|
|
2060
|
+
return typeof storedUser?.refresh_token === "string" && storedUser.refresh_token.length > 0;
|
|
2061
|
+
};
|
|
2062
|
+
var getStoredOidcSession = (args) => {
|
|
2063
|
+
const storageKey = getOidcUserStorageKey(args.authority, args.clientId);
|
|
2064
|
+
const storedUser = getStoredOidcUser(args);
|
|
2065
|
+
return {
|
|
2066
|
+
storageKey,
|
|
2067
|
+
storedUser,
|
|
2068
|
+
hasStoredUser: storedUser !== null,
|
|
2069
|
+
hasRefreshToken: typeof storedUser?.refresh_token === "string" && storedUser.refresh_token.length > 0
|
|
2070
|
+
};
|
|
2071
|
+
};
|
|
2072
|
+
|
|
2073
|
+
// src/hooks/useStoredOidcSession.ts
|
|
2074
|
+
var useStoredOidcSession = () => {
|
|
2075
|
+
const auth = useAuth();
|
|
2076
|
+
const { storage } = useStorageConfig();
|
|
2077
|
+
const authority = auth.settings.authority;
|
|
2078
|
+
const clientId = auth.settings.client_id;
|
|
2079
|
+
return useMemo(
|
|
2080
|
+
() => getStoredOidcSession({ authority, clientId, storage }),
|
|
2081
|
+
[authority, clientId, storage]
|
|
2082
|
+
);
|
|
2083
|
+
};
|
|
2084
|
+
|
|
1995
2085
|
// node_modules/@microsoft/signalr/dist/esm/Errors.js
|
|
1996
2086
|
var HttpError = class extends Error {
|
|
1997
2087
|
/** Constructs a new instance of {@link @microsoft/signalr.HttpError}.
|
|
@@ -5030,6 +5120,6 @@ function useSignalRConnection(hubUrl, options = {}) {
|
|
|
5030
5120
|
};
|
|
5031
5121
|
}
|
|
5032
5122
|
|
|
5033
|
-
export { CONFIG_PRESETS, DeviceLogin, KeycloakConfigBuilder, KeycloakProvider, KeycloakStatusProvider, LoginButton, LogoutButton, ProtectedRoute, SessionMonitor, TokenBridge, TokenBridgeSync, TokenLifecycleManager, UserProfile, createTokenBridge, defaultConfig, defaultTokenBridge, fetchUserInfo, getConfigFromEnv, getConfigPreset, mergeWithPreset, pollForToken, requestDeviceCode, useAuth, useKeycloakStatus, useProtectedFetch, useRoles, useScopes, useSignalRConnection, useTokenLifecycle };
|
|
5123
|
+
export { CONFIG_PRESETS, DeviceLogin, KeycloakConfigBuilder, KeycloakProvider, KeycloakStatusProvider, LoginButton, LogoutButton, ProtectedRoute, SessionMonitor, TokenBridge, TokenBridgeSync, TokenLifecycleManager, UserProfile, createTokenBridge, defaultConfig, defaultTokenBridge, fetchUserInfo, getConfigFromEnv, getConfigPreset, getOidcUserStorageKey, getStoredOidcSession, getStoredOidcUser, hasStoredOidcRefreshToken, mergeWithPreset, pollForToken, requestDeviceCode, useAuth, useKeycloakStatus, useProtectedFetch, useRoles, useScopes, useSignalRConnection, useStoredOidcSession, useTokenLifecycle };
|
|
5034
5124
|
//# sourceMappingURL=index.mjs.map
|
|
5035
5125
|
//# sourceMappingURL=index.mjs.map
|