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/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 supportsRefreshTokens = config.silentRenew?.useRefreshToken !== false;
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 (supportsRefreshTokens && !hasRefreshToken) {
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
- supportsRefreshTokens,
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 (cancelled) {
457
+ if (!isMountedRef.current) {
422
458
  return;
423
459
  }
424
- if (isRefreshing) {
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
- }, 5e3);
494
+ window.setTimeout(executeRetry, 0);
495
+ }, retryDelay);
456
496
  return () => {
457
497
  cancelled = true;
458
498
  window.clearTimeout(timeoutId);
459
499
  };
460
- }, [pendingRetry, refreshToken, isRefreshing, log]);
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 (!supportsRefreshTokens) {
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 && supportsRefreshTokens
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
- supportsRefreshTokens,
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 && (!supportsRefreshTokens || !hasUser || !hasRefreshToken)) {
571
+ if (!auth.isAuthenticated && (!hasUser || !hasRefreshToken && !allowIframeFallback)) {
535
572
  hasCheckedOnStartup.current = false;
536
573
  }
537
574
  }
538
- }, [auth.isAuthenticated, auth.user, supportsRefreshTokens]);
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