antaeus.keycloak.react 2.0.0 → 2.1.0

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
  }
@@ -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
  }
@@ -1442,14 +1489,14 @@ function useProtectedFetch(options = {}) {
1442
1489
  },
1443
1490
  [baseUrl]
1444
1491
  );
1445
- const getAuthHeader = useCallback(() => {
1492
+ const getAuthHeader = () => {
1446
1493
  if (!auth.user?.access_token) {
1447
1494
  return {};
1448
1495
  }
1449
1496
  return {
1450
1497
  Authorization: `Bearer ${auth.user.access_token}`
1451
1498
  };
1452
- }, [auth.user]);
1499
+ };
1453
1500
  const buildHeaders = useCallback(
1454
1501
  (requestHeaders) => {
1455
1502
  const headers = new Headers(requestHeaders);
@@ -1462,7 +1509,7 @@ function useProtectedFetch(options = {}) {
1462
1509
  });
1463
1510
  return headers;
1464
1511
  },
1465
- [getAuthHeader, customHeaders]
1512
+ [customHeaders]
1466
1513
  );
1467
1514
  const protectedFetch = useCallback(
1468
1515
  async (url, init) => {