antaeus.keycloak.react 2.0.0 → 2.1.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
@@ -14,7 +14,7 @@ var defaultConfig = {
14
14
  scope: "openid profile email offline_access",
15
15
  features: {
16
16
  deviceFlow: false,
17
- sessionMonitor: false,
17
+ sessionMonitor: true,
18
18
  autoRefresh: true
19
19
  },
20
20
  silentRenew: {
@@ -59,7 +59,11 @@ var KeycloakConfigBuilder = class {
59
59
  scope: config.scope,
60
60
  automaticSilentRenew: config.silentRenew.enabled,
61
61
  loadUserInfo: config.advanced.loadUserInfo,
62
- silent_redirect_uri: config.silentRenew.silentRedirectUri
62
+ silent_redirect_uri: config.silentRenew.silentRedirectUri,
63
+ // Add PKCE support (required for public clients)
64
+ ...config.advanced.codeChallengeMethod && {
65
+ response_mode: "query"
66
+ }
63
67
  };
64
68
  return oidcSettings;
65
69
  }
@@ -197,14 +201,14 @@ function useTokenLifecycle(options) {
197
201
  log("Refresh already in progress, skipping");
198
202
  return;
199
203
  }
200
- if (checkRefreshTokenLifetime()) {
201
- log("Refresh token exceeded max lifetime, cannot refresh");
202
- setIsRefreshTokenExpired(true);
203
- onRefreshTokenExpired?.();
204
- return;
205
- }
204
+ setIsRefreshing(true);
206
205
  try {
207
- setIsRefreshing(true);
206
+ if (checkRefreshTokenLifetime()) {
207
+ log("Refresh token exceeded max lifetime, cannot refresh");
208
+ setIsRefreshTokenExpired(true);
209
+ onRefreshTokenExpired?.();
210
+ return;
211
+ }
208
212
  onRefreshStart?.();
209
213
  log("Starting token refresh");
210
214
  await auth.signinSilent();
@@ -239,7 +243,11 @@ function useTokenLifecycle(options) {
239
243
  onRefreshTokenExpired?.();
240
244
  const key = getStorageKey();
241
245
  localStorage.removeItem(key);
242
- await auth.removeUser();
246
+ try {
247
+ await auth.removeUser();
248
+ } catch (error) {
249
+ log("Error removing user after max lifetime:", error);
250
+ }
243
251
  return false;
244
252
  }
245
253
  const expiresAt = auth.user.expires_at;
@@ -318,7 +326,9 @@ function useTokenLifecycle(options) {
318
326
  onRefreshTokenExpired?.();
319
327
  const key = getStorageKey();
320
328
  localStorage.removeItem(key);
321
- auth.removeUser();
329
+ auth.removeUser().catch((error) => {
330
+ log("Error removing user during periodic check:", error);
331
+ });
322
332
  }
323
333
  }, 5 * 60 * 1e3);
324
334
  return () => clearInterval(intervalId);
@@ -495,22 +505,28 @@ var SessionMonitor = ({
495
505
  }) => {
496
506
  const auth = useAuth();
497
507
  const [timeRemaining, setTimeRemaining] = useState(null);
508
+ const [initialTimeRemaining, setInitialTimeRemaining] = useState(null);
498
509
  const [minimized, setMinimized] = useState(startMinimized);
499
510
  const [warningTriggered, setWarningTriggered] = useState(false);
500
511
  const [criticalTriggered, setCriticalTriggered] = useState(false);
501
512
  useEffect(() => {
502
513
  if (!auth.isAuthenticated || !auth.user?.expires_at) {
503
514
  setTimeRemaining(null);
515
+ setInitialTimeRemaining(null);
504
516
  return;
505
517
  }
518
+ let logoutTimeoutId = null;
506
519
  const updateTimeRemaining = () => {
507
520
  const expiresAt = auth.user.expires_at;
508
521
  const now = Math.floor(Date.now() / 1e3);
509
522
  const remaining = expiresAt - now;
523
+ if (initialTimeRemaining === null && remaining > 0) {
524
+ setInitialTimeRemaining(remaining);
525
+ }
510
526
  if (remaining <= 0) {
511
527
  setTimeRemaining(0);
512
528
  onExpired?.();
513
- setTimeout(() => {
529
+ logoutTimeoutId = setTimeout(() => {
514
530
  auth.signoutRedirect();
515
531
  }, 2e3);
516
532
  } else {
@@ -526,13 +542,19 @@ var SessionMonitor = ({
526
542
  };
527
543
  updateTimeRemaining();
528
544
  const interval = setInterval(updateTimeRemaining, 1e3);
529
- return () => clearInterval(interval);
545
+ return () => {
546
+ clearInterval(interval);
547
+ if (logoutTimeoutId) {
548
+ clearTimeout(logoutTimeoutId);
549
+ }
550
+ };
530
551
  }, [
531
552
  auth,
532
553
  warningThreshold,
533
554
  criticalThreshold,
534
555
  warningTriggered,
535
556
  criticalTriggered,
557
+ initialTimeRemaining,
536
558
  onWarning,
537
559
  onCritical,
538
560
  onExpired
@@ -555,9 +577,9 @@ var SessionMonitor = ({
555
577
  return "normal";
556
578
  }, [timeRemaining, warningThreshold, criticalThreshold]);
557
579
  const getProgress = useCallback(() => {
558
- if (!timeRemaining || !auth.user?.expires_in) return 100;
559
- return timeRemaining / auth.user.expires_in * 100;
560
- }, [timeRemaining, auth.user]);
580
+ if (!timeRemaining || !initialTimeRemaining) return 100;
581
+ return Math.max(0, Math.min(100, timeRemaining / initialTimeRemaining * 100));
582
+ }, [timeRemaining, initialTimeRemaining]);
561
583
  if (!auth.isAuthenticated || timeRemaining === null) {
562
584
  return null;
563
585
  }
@@ -870,12 +892,27 @@ function buildZeroConfig(props) {
870
892
  const { authority, clientId, preset = "default", config: overrides = {} } = props;
871
893
  const presetConfig = mergeWithPreset(preset, overrides);
872
894
  const fullConfig = {
873
- ...defaultConfig,
874
- ...presetConfig,
875
895
  authority,
876
896
  clientId,
877
- redirectUri: presetConfig.redirectUri || (typeof window !== "undefined" ? window.location.origin + "/" : "/"),
878
- postLogoutRedirectUri: presetConfig.postLogoutRedirectUri || (typeof window !== "undefined" ? window.location.origin + "/" : "/")
897
+ redirectUri: presetConfig.redirectUri || defaultConfig.redirectUri || (typeof window !== "undefined" ? window.location.origin + "/" : "/"),
898
+ postLogoutRedirectUri: presetConfig.postLogoutRedirectUri || defaultConfig.postLogoutRedirectUri || (typeof window !== "undefined" ? window.location.origin + "/" : "/"),
899
+ scope: presetConfig.scope || defaultConfig.scope,
900
+ features: {
901
+ ...defaultConfig.features,
902
+ ...presetConfig.features
903
+ },
904
+ silentRenew: {
905
+ ...defaultConfig.silentRenew,
906
+ ...presetConfig.silentRenew
907
+ },
908
+ advanced: {
909
+ ...defaultConfig.advanced,
910
+ ...presetConfig.advanced
911
+ },
912
+ events: {
913
+ ...defaultConfig.events,
914
+ ...presetConfig.events
915
+ }
879
916
  };
880
917
  return fullConfig;
881
918
  }
@@ -970,6 +1007,7 @@ var ProtectedRoute = ({
970
1007
  const { hasAnyRole, hasAllRoles } = useRoles();
971
1008
  const { hasAnyScope, hasAllScopes } = useScopes();
972
1009
  const location = useLocation();
1010
+ const hasRedirected = useRef(false);
973
1011
  const isAuthenticated = !auth.isLoading && auth.isAuthenticated;
974
1012
  const policyAccess = policy && auth.user ? policy(auth.user, { location }) : null;
975
1013
  const roleAccess = requiredRoles.length > 0 ? requireAllRoles ? hasAllRoles(requiredRoles) : hasAnyRole(requiredRoles) : null;
@@ -981,12 +1019,21 @@ var ProtectedRoute = ({
981
1019
  }
982
1020
  }, [unauthorizedReason, onUnauthorized]);
983
1021
  useEffect(() => {
984
- if (!auth.isLoading && !auth.isAuthenticated) {
1022
+ if (!auth.isLoading && !auth.isAuthenticated && !hasRedirected.current) {
1023
+ hasRedirected.current = true;
985
1024
  auth.signinRedirect({
986
1025
  redirect_uri: window.location.origin + location.pathname + location.search
1026
+ }).catch((error) => {
1027
+ hasRedirected.current = false;
1028
+ console.error("[ProtectedRoute] Signin redirect failed:", error);
987
1029
  });
988
1030
  }
989
- }, [auth.isLoading, auth.isAuthenticated, auth, location.pathname, location.search]);
1031
+ }, [auth.isLoading, auth.isAuthenticated, location.pathname, location.search]);
1032
+ useEffect(() => {
1033
+ if (auth.isAuthenticated) {
1034
+ hasRedirected.current = false;
1035
+ }
1036
+ }, [auth.isAuthenticated]);
990
1037
  if (auth.isLoading) {
991
1038
  return /* @__PURE__ */ jsx(Fragment, { children: loadingComponent || /* @__PURE__ */ jsx(DefaultLoadingComponent, {}) });
992
1039
  }
@@ -1354,8 +1401,17 @@ var DeviceLogin = ({
1354
1401
  });
1355
1402
  if (userManagerRef.current) {
1356
1403
  await userManagerRef.current.storeUser(user);
1404
+ const events = userManagerRef.current.events;
1405
+ if (events && events._userLoaded) {
1406
+ events._userLoaded.raise(user);
1407
+ }
1408
+ }
1409
+ if (onSuccess) {
1410
+ await new Promise((resolve) => setTimeout(resolve, 100));
1411
+ onSuccess(user);
1412
+ } else {
1413
+ window.location.reload();
1357
1414
  }
1358
- onSuccess?.(user);
1359
1415
  }
1360
1416
  } catch (err) {
1361
1417
  if (!err.message.includes("authorization_pending")) {
@@ -1442,14 +1498,14 @@ function useProtectedFetch(options = {}) {
1442
1498
  },
1443
1499
  [baseUrl]
1444
1500
  );
1445
- const getAuthHeader = useCallback(() => {
1501
+ const getAuthHeader = () => {
1446
1502
  if (!auth.user?.access_token) {
1447
1503
  return {};
1448
1504
  }
1449
1505
  return {
1450
1506
  Authorization: `Bearer ${auth.user.access_token}`
1451
1507
  };
1452
- }, [auth.user]);
1508
+ };
1453
1509
  const buildHeaders = useCallback(
1454
1510
  (requestHeaders) => {
1455
1511
  const headers = new Headers(requestHeaders);
@@ -1462,7 +1518,7 @@ function useProtectedFetch(options = {}) {
1462
1518
  });
1463
1519
  return headers;
1464
1520
  },
1465
- [getAuthHeader, customHeaders]
1521
+ [customHeaders]
1466
1522
  );
1467
1523
  const protectedFetch = useCallback(
1468
1524
  async (url, init) => {