antaeus.keycloak.react 2.2.3 → 2.4.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
@@ -1,5 +1,5 @@
1
- import { createContext, useState, useRef, useCallback, useEffect, useMemo, useContext } from 'react';
2
- import { useAuth as useAuth$1, AuthProvider } from 'react-oidc-context';
1
+ import { createContext, useContext, useState, useRef, useCallback, useEffect, useMemo } from 'react';
2
+ import { useAuth as useAuth$1, AuthContext, AuthProvider } from 'react-oidc-context';
3
3
  import { WebStorageStateStore, User } from 'oidc-client-ts';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
  import { useLocation } from 'react-router-dom';
@@ -27,7 +27,8 @@ var defaultConfig = {
27
27
  maxRefreshTokenLifetime: 2592e3,
28
28
  // 30 days in seconds
29
29
  showLoadingIndicator: false,
30
- loadingMessage: "Refreshing session..."
30
+ loadingMessage: "Refreshing session...",
31
+ useRefreshToken: true
31
32
  },
32
33
  advanced: {
33
34
  responseType: "code",
@@ -53,6 +54,7 @@ var KeycloakConfigBuilder = class {
53
54
  const config = this.mergeWithDefaults(userConfig);
54
55
  const storageType = config.advanced.storageType || "session";
55
56
  const storage = storageType === "local" ? window.localStorage : window.sessionStorage;
57
+ const { useRefreshToken } = config.silentRenew;
56
58
  const oidcSettings = {
57
59
  authority: config.authority,
58
60
  client_id: config.clientId,
@@ -70,6 +72,9 @@ var KeycloakConfigBuilder = class {
70
72
  response_mode: "query"
71
73
  }
72
74
  };
75
+ if (useRefreshToken !== void 0) {
76
+ oidcSettings.useRefreshToken = useRefreshToken;
77
+ }
73
78
  return oidcSettings;
74
79
  }
75
80
  /**
@@ -157,6 +162,7 @@ function useTokenLifecycle(options) {
157
162
  const [isRefreshing, setIsRefreshing] = useState(false);
158
163
  const [isRefreshTokenExpired, setIsRefreshTokenExpired] = useState(false);
159
164
  const hasCheckedOnStartup = useRef(false);
165
+ const supportsRefreshTokens = config.silentRenew?.useRefreshToken !== false;
160
166
  const log = useCallback(
161
167
  (message, ...args) => {
162
168
  if (debug) {
@@ -293,24 +299,49 @@ function useTokenLifecycle(options) {
293
299
  useEffect(() => {
294
300
  const strategy = config.silentRenew?.refreshStrategy || "both";
295
301
  const shouldCheckOnStartup = strategy === "startup" || strategy === "both";
302
+ const hasRefreshToken = Boolean(auth.user?.refresh_token);
296
303
  if (!shouldCheckOnStartup || hasCheckedOnStartup.current) {
297
304
  return;
298
305
  }
299
- if (!auth.isAuthenticated || auth.isLoading) {
306
+ if (auth.isLoading) {
300
307
  return;
301
308
  }
309
+ if (!auth.isAuthenticated) {
310
+ if (!supportsRefreshTokens) {
311
+ return;
312
+ }
313
+ if (!auth.user || !hasRefreshToken) {
314
+ return;
315
+ }
316
+ }
302
317
  hasCheckedOnStartup.current = true;
303
- log("Running startup token check with strategy:", strategy);
318
+ log(
319
+ "Running startup token check with strategy:",
320
+ strategy,
321
+ "using refresh token:",
322
+ !auth.isAuthenticated && supportsRefreshTokens
323
+ );
304
324
  checkAndRefreshTokens().catch((error) => {
305
325
  log("Startup token check failed:", error);
306
326
  });
307
327
  }, [
308
328
  auth.isAuthenticated,
309
329
  auth.isLoading,
330
+ auth.user,
310
331
  config.silentRenew?.refreshStrategy,
332
+ supportsRefreshTokens,
311
333
  checkAndRefreshTokens,
312
334
  log
313
335
  ]);
336
+ useEffect(() => {
337
+ if (hasCheckedOnStartup.current) {
338
+ const hasUser = Boolean(auth.user);
339
+ const hasRefreshToken = Boolean(auth.user?.refresh_token);
340
+ if (auth.isLoading || !auth.isAuthenticated && (!supportsRefreshTokens || !hasUser || !hasRefreshToken)) {
341
+ hasCheckedOnStartup.current = false;
342
+ }
343
+ }
344
+ }, [auth.isAuthenticated, auth.isLoading, auth.user, supportsRefreshTokens]);
314
345
  useEffect(() => {
315
346
  if (auth.user && !auth.isLoading) {
316
347
  storeFirstLoginTime();
@@ -625,6 +656,71 @@ var SessionMonitor = ({
625
656
  ] });
626
657
  };
627
658
 
659
+ // src/utils/tokenBridge.ts
660
+ var TokenBridge = class {
661
+ constructor() {
662
+ this.currentToken = null;
663
+ this.listeners = /* @__PURE__ */ new Set();
664
+ }
665
+ /**
666
+ * Update the tracked token and notify subscribers when it changes.
667
+ */
668
+ setToken(token) {
669
+ if (this.currentToken === token) {
670
+ return;
671
+ }
672
+ this.currentToken = token;
673
+ for (const listener of this.listeners) {
674
+ try {
675
+ listener(token);
676
+ } catch (error) {
677
+ console.error("[TokenBridge] Listener threw error:", error);
678
+ }
679
+ }
680
+ }
681
+ /**
682
+ * Read the most recent token value.
683
+ */
684
+ getToken() {
685
+ return this.currentToken;
686
+ }
687
+ /**
688
+ * Convenience helper for async callers.
689
+ */
690
+ async getTokenAsync() {
691
+ return this.currentToken;
692
+ }
693
+ /**
694
+ * Subscribe to token changes. Returns an unsubscribe callback.
695
+ */
696
+ subscribe(listener) {
697
+ this.listeners.add(listener);
698
+ listener(this.currentToken);
699
+ return () => {
700
+ this.listeners.delete(listener);
701
+ };
702
+ }
703
+ };
704
+ var createTokenBridge = () => new TokenBridge();
705
+ var defaultTokenBridge = createTokenBridge();
706
+
707
+ // src/components/TokenBridge.tsx
708
+ var TokenBridgeSync = ({
709
+ bridge = defaultTokenBridge
710
+ }) => {
711
+ const auth = useAuth();
712
+ useEffect(() => {
713
+ const token = auth.user?.access_token ?? null;
714
+ bridge.setToken(token);
715
+ }, [auth.user, bridge]);
716
+ useEffect(() => {
717
+ if (!auth.isAuthenticated) {
718
+ bridge.setToken(null);
719
+ }
720
+ }, [auth.isAuthenticated, bridge]);
721
+ return null;
722
+ };
723
+
628
724
  // src/config/presets.ts
629
725
  var CONFIG_PRESETS = {
630
726
  /**
@@ -843,11 +939,97 @@ var StorageConfigProvider = ({ storageType, children }) => {
843
939
  return /* @__PURE__ */ jsx(StorageConfigContext.Provider, { value: { storageType, storage }, children });
844
940
  };
845
941
  var useStorageConfig = () => useContext(StorageConfigContext);
942
+ var defaultStatus = {
943
+ isEnabled: true,
944
+ isConfigured: true
945
+ };
946
+ var KeycloakStatusContext = createContext(defaultStatus);
947
+ var KeycloakStatusProvider = KeycloakStatusContext.Provider;
948
+ var useKeycloakStatus = () => useContext(KeycloakStatusContext);
949
+ var createDisabledEvents = () => {
950
+ const noop = () => void 0;
951
+ const noopSubscription = () => () => void 0;
952
+ const noopAsync = async () => void 0;
953
+ const events = {
954
+ addAccessTokenExpiring: noopSubscription,
955
+ removeAccessTokenExpiring: noop,
956
+ addAccessTokenExpired: noopSubscription,
957
+ removeAccessTokenExpired: noop,
958
+ addSilentRenewError: noopSubscription,
959
+ removeSilentRenewError: noop,
960
+ addUserLoaded: noopSubscription,
961
+ removeUserLoaded: noop,
962
+ addUserUnloaded: noopSubscription,
963
+ removeUserUnloaded: noop,
964
+ addUserSignedIn: noopSubscription,
965
+ removeUserSignedIn: noop,
966
+ addUserSignedOut: noopSubscription,
967
+ removeUserSignedOut: noop,
968
+ addUserSessionChanged: noopSubscription,
969
+ removeUserSessionChanged: noop,
970
+ load: noopAsync,
971
+ unload: noopAsync
972
+ };
973
+ return events;
974
+ };
975
+ var disabledEvents = createDisabledEvents();
976
+ var disabledSettings = {
977
+ authority: "",
978
+ client_id: "",
979
+ redirect_uri: "http://localhost/disabled",
980
+ response_type: "code",
981
+ scope: ""
982
+ };
983
+ var disabledError = () => new Error("Keycloak integration is disabled for this environment.");
984
+ var disabledAsync = async () => {
985
+ throw disabledError();
986
+ };
987
+ var disabledAuthContext = {
988
+ settings: disabledSettings,
989
+ events: disabledEvents,
990
+ clearStaleState: async () => void 0,
991
+ removeUser: async () => void 0,
992
+ signinPopup: (_args) => disabledAsync(),
993
+ signinSilent: async (_args) => null,
994
+ signinRedirect: (_args) => disabledAsync(),
995
+ signinResourceOwnerCredentials: (_args) => disabledAsync(),
996
+ signoutRedirect: async (_args) => void 0,
997
+ signoutPopup: async (_args) => void 0,
998
+ signoutSilent: async (_args) => void 0,
999
+ querySessionStatus: async (_args) => null,
1000
+ revokeTokens: async (_types) => void 0,
1001
+ startSilentRenew: () => void 0,
1002
+ stopSilentRenew: () => void 0,
1003
+ user: null,
1004
+ isLoading: false,
1005
+ isAuthenticated: false,
1006
+ activeNavigator: void 0,
1007
+ error: void 0
1008
+ };
846
1009
  function isZeroConfig(props) {
847
1010
  return "authority" in props && "clientId" in props;
848
1011
  }
849
1012
  var KeycloakProvider = (props) => {
850
1013
  const { children, debug = false } = props;
1014
+ const status = useMemo(() => {
1015
+ const enabledFlag = props.enabled ?? true;
1016
+ const hasAuthority = isZeroConfig(props) ? Boolean(props.authority) : Boolean(props.config.authority);
1017
+ const hasClientId = isZeroConfig(props) ? Boolean(props.clientId) : Boolean(props.config.clientId);
1018
+ return {
1019
+ isEnabled: enabledFlag,
1020
+ isConfigured: hasAuthority && hasClientId,
1021
+ reason: !enabledFlag ? "disabled" : !hasAuthority || !hasClientId ? "missing-config" : void 0
1022
+ };
1023
+ }, [props]);
1024
+ const effectiveEnabled = status.isEnabled && status.isConfigured;
1025
+ useEffect(() => {
1026
+ if (!effectiveEnabled && props.tokenBridge) {
1027
+ props.tokenBridge.setToken(null);
1028
+ }
1029
+ }, [effectiveEnabled, props.tokenBridge]);
1030
+ if (!effectiveEnabled) {
1031
+ return /* @__PURE__ */ jsx(KeycloakStatusProvider, { value: status, children: /* @__PURE__ */ jsx(AuthContext.Provider, { value: disabledAuthContext, children: /* @__PURE__ */ jsx(StorageConfigProvider, { storageType: "session", children }) }) });
1032
+ }
851
1033
  const config = isZeroConfig(props) ? buildZeroConfig(props) : props.config;
852
1034
  const oidcConfig = KeycloakConfigBuilder.build(config);
853
1035
  const log = useCallback(
@@ -889,7 +1071,7 @@ var KeycloakProvider = (props) => {
889
1071
  }, [log]);
890
1072
  const showSessionMonitor = config.features?.sessionMonitor !== false;
891
1073
  const storageType = config.advanced?.storageType || "session";
892
- return /* @__PURE__ */ jsx(
1074
+ return /* @__PURE__ */ jsx(KeycloakStatusProvider, { value: status, children: /* @__PURE__ */ jsx(
893
1075
  AuthProvider,
894
1076
  {
895
1077
  ...oidcConfig,
@@ -897,11 +1079,12 @@ var KeycloakProvider = (props) => {
897
1079
  onSignoutCallback,
898
1080
  onRemoveUser,
899
1081
  children: /* @__PURE__ */ jsx(StorageConfigProvider, { storageType, children: /* @__PURE__ */ jsxs(TokenLifecycleManager, { config, debug, children: [
1082
+ props.tokenBridge && /* @__PURE__ */ jsx(TokenBridgeSync, { bridge: props.tokenBridge }),
900
1083
  showSessionMonitor && /* @__PURE__ */ jsx(SessionMonitor, {}),
901
1084
  children
902
1085
  ] }) })
903
1086
  }
904
- );
1087
+ ) });
905
1088
  };
906
1089
  function buildZeroConfig(props) {
907
1090
  const { authority, clientId, preset = "default", config: overrides = {} } = props;
@@ -1605,6 +1788,6 @@ function useProtectedFetch(options = {}) {
1605
1788
  };
1606
1789
  }
1607
1790
 
1608
- export { CONFIG_PRESETS, DeviceLogin, KeycloakConfigBuilder, KeycloakProvider, LoginButton, LogoutButton, ProtectedRoute, SessionMonitor, TokenLifecycleManager, UserProfile, defaultConfig, fetchUserInfo, getConfigFromEnv, getConfigPreset, mergeWithPreset, pollForToken, requestDeviceCode, useAuth, useProtectedFetch, useRoles, useScopes, useTokenLifecycle };
1791
+ 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, useTokenLifecycle };
1609
1792
  //# sourceMappingURL=index.mjs.map
1610
1793
  //# sourceMappingURL=index.mjs.map