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/README.md +95 -1
- package/dist/index.d.mts +117 -23
- package/dist/index.d.ts +117 -23
- package/dist/index.js +194 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +191 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import * as React$1 from 'react';
|
|
2
|
+
import React__default from 'react';
|
|
2
3
|
import { User, UserManagerSettings } from 'oidc-client-ts';
|
|
3
4
|
export { User } from 'oidc-client-ts';
|
|
4
5
|
import { useLocation } from 'react-router-dom';
|
|
@@ -113,6 +114,12 @@ interface SilentRenewConfig {
|
|
|
113
114
|
* @default 'Refreshing session...'
|
|
114
115
|
*/
|
|
115
116
|
loadingMessage?: string;
|
|
117
|
+
/**
|
|
118
|
+
* Enable using the refresh token grant instead of iframe silent renew when available.
|
|
119
|
+
* Keeps long-lived sessions alive even after the app is closed.
|
|
120
|
+
* @default true
|
|
121
|
+
*/
|
|
122
|
+
useRefreshToken?: boolean;
|
|
116
123
|
}
|
|
117
124
|
/**
|
|
118
125
|
* Event callbacks for authentication lifecycle
|
|
@@ -217,6 +224,40 @@ declare function getConfigPreset(preset: ConfigPreset): Partial<KeycloakConfig>;
|
|
|
217
224
|
*/
|
|
218
225
|
declare function mergeWithPreset(preset: ConfigPreset, customConfig: Partial<KeycloakConfig>): Partial<KeycloakConfig>;
|
|
219
226
|
|
|
227
|
+
type TokenListener = (token: string | null) => void;
|
|
228
|
+
/**
|
|
229
|
+
* Minimal bridge to expose the current Keycloak access token outside React.
|
|
230
|
+
* Consumers can subscribe to token changes or fetch the latest value on demand.
|
|
231
|
+
*/
|
|
232
|
+
declare class TokenBridge {
|
|
233
|
+
private currentToken;
|
|
234
|
+
private listeners;
|
|
235
|
+
/**
|
|
236
|
+
* Update the tracked token and notify subscribers when it changes.
|
|
237
|
+
*/
|
|
238
|
+
setToken(token: string | null): void;
|
|
239
|
+
/**
|
|
240
|
+
* Read the most recent token value.
|
|
241
|
+
*/
|
|
242
|
+
getToken(): string | null;
|
|
243
|
+
/**
|
|
244
|
+
* Convenience helper for async callers.
|
|
245
|
+
*/
|
|
246
|
+
getTokenAsync(): Promise<string | null>;
|
|
247
|
+
/**
|
|
248
|
+
* Subscribe to token changes. Returns an unsubscribe callback.
|
|
249
|
+
*/
|
|
250
|
+
subscribe(listener: TokenListener): () => void;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Factory helper to create an isolated token bridge.
|
|
254
|
+
*/
|
|
255
|
+
declare const createTokenBridge: () => TokenBridge;
|
|
256
|
+
/**
|
|
257
|
+
* Default shared bridge for simple scenarios.
|
|
258
|
+
*/
|
|
259
|
+
declare const defaultTokenBridge: TokenBridge;
|
|
260
|
+
|
|
220
261
|
/**
|
|
221
262
|
* Props for zero-config mode (simplified setup)
|
|
222
263
|
*/
|
|
@@ -234,10 +275,16 @@ interface KeycloakProviderZeroConfigProps {
|
|
|
234
275
|
* @default 'default'
|
|
235
276
|
*/
|
|
236
277
|
preset?: ConfigPreset;
|
|
278
|
+
/**
|
|
279
|
+
* Toggle to disable Keycloak integration entirely.
|
|
280
|
+
* When false, children render with a stub auth context.
|
|
281
|
+
* @default true
|
|
282
|
+
*/
|
|
283
|
+
enabled?: boolean;
|
|
237
284
|
/**
|
|
238
285
|
* Child components to render
|
|
239
286
|
*/
|
|
240
|
-
children:
|
|
287
|
+
children: React__default.ReactNode;
|
|
241
288
|
/**
|
|
242
289
|
* Enable debug logging to console
|
|
243
290
|
* @default false
|
|
@@ -248,6 +295,10 @@ interface KeycloakProviderZeroConfigProps {
|
|
|
248
295
|
* Use this to customize preset or zero-config defaults
|
|
249
296
|
*/
|
|
250
297
|
config?: Partial<Omit<KeycloakConfig, 'authority' | 'clientId'>>;
|
|
298
|
+
/**
|
|
299
|
+
* Optional token bridge instance for exposing tokens outside React.
|
|
300
|
+
*/
|
|
301
|
+
tokenBridge?: TokenBridge;
|
|
251
302
|
}
|
|
252
303
|
/**
|
|
253
304
|
* Props for full-config mode (advanced setup)
|
|
@@ -257,15 +308,25 @@ interface KeycloakProviderFullConfigProps {
|
|
|
257
308
|
* Full Keycloak configuration object
|
|
258
309
|
*/
|
|
259
310
|
config: KeycloakConfig;
|
|
311
|
+
/**
|
|
312
|
+
* Toggle to disable Keycloak integration entirely.
|
|
313
|
+
* When false, children render with a stub auth context.
|
|
314
|
+
* @default true
|
|
315
|
+
*/
|
|
316
|
+
enabled?: boolean;
|
|
260
317
|
/**
|
|
261
318
|
* Child components to render
|
|
262
319
|
*/
|
|
263
|
-
children:
|
|
320
|
+
children: React__default.ReactNode;
|
|
264
321
|
/**
|
|
265
322
|
* Enable debug logging to console
|
|
266
323
|
* @default false
|
|
267
324
|
*/
|
|
268
325
|
debug?: boolean;
|
|
326
|
+
/**
|
|
327
|
+
* Optional token bridge instance for exposing tokens outside React.
|
|
328
|
+
*/
|
|
329
|
+
tokenBridge?: TokenBridge;
|
|
269
330
|
}
|
|
270
331
|
/**
|
|
271
332
|
* Combined props type
|
|
@@ -318,6 +379,7 @@ type KeycloakProviderProps = KeycloakProviderZeroConfigProps | KeycloakProviderF
|
|
|
318
379
|
* silentRenew: {
|
|
319
380
|
* maxRefreshTokenLifetime: 604800, // Override: 7 days instead of 30
|
|
320
381
|
* showLoadingIndicator: true,
|
|
382
|
+
* useRefreshToken: true, // Explicitly ensure refresh tokens are used when available
|
|
321
383
|
* }
|
|
322
384
|
* }}
|
|
323
385
|
* debug={true}
|
|
@@ -361,6 +423,7 @@ type KeycloakProviderProps = KeycloakProviderZeroConfigProps | KeycloakProviderF
|
|
|
361
423
|
* enabled: true,
|
|
362
424
|
* refreshStrategy: 'both',
|
|
363
425
|
* maxRefreshTokenLifetime: 2592000,
|
|
426
|
+
* useRefreshToken: true,
|
|
364
427
|
* },
|
|
365
428
|
* events: {
|
|
366
429
|
* onLogin: (user) => console.log('User logged in', user),
|
|
@@ -380,6 +443,7 @@ type KeycloakProviderProps = KeycloakProviderZeroConfigProps | KeycloakProviderF
|
|
|
380
443
|
* - ✅ Token lifecycle management (refresh on app startup)
|
|
381
444
|
* - ✅ Session monitoring UI (countdown timer)
|
|
382
445
|
* - ✅ Silent renew (background token refresh)
|
|
446
|
+
* - ✅ Refresh token grant fallback on app restart
|
|
383
447
|
* - ✅ Error notifications (user-friendly messages)
|
|
384
448
|
* - ✅ 401 retry logic (automatic token refresh on API errors)
|
|
385
449
|
* - ✅ Maximum refresh token lifetime (security control)
|
|
@@ -387,7 +451,7 @@ type KeycloakProviderProps = KeycloakProviderZeroConfigProps | KeycloakProviderF
|
|
|
387
451
|
*
|
|
388
452
|
* All features can be disabled or customized via configuration overrides.
|
|
389
453
|
*/
|
|
390
|
-
declare const KeycloakProvider:
|
|
454
|
+
declare const KeycloakProvider: React__default.FC<KeycloakProviderProps>;
|
|
391
455
|
|
|
392
456
|
/**
|
|
393
457
|
* Context object passed to custom policy function
|
|
@@ -410,7 +474,7 @@ interface ProtectedRouteProps {
|
|
|
410
474
|
/**
|
|
411
475
|
* Child components to render when user is authorized
|
|
412
476
|
*/
|
|
413
|
-
children:
|
|
477
|
+
children: React__default.ReactNode;
|
|
414
478
|
/**
|
|
415
479
|
* Required realm roles (OR logic - user needs at least one)
|
|
416
480
|
*/
|
|
@@ -437,11 +501,11 @@ interface ProtectedRouteProps {
|
|
|
437
501
|
/**
|
|
438
502
|
* Custom loading component to display while checking authentication
|
|
439
503
|
*/
|
|
440
|
-
loadingComponent?:
|
|
504
|
+
loadingComponent?: React__default.ReactNode;
|
|
441
505
|
/**
|
|
442
506
|
* Custom unauthorized component to display when user lacks required permissions
|
|
443
507
|
*/
|
|
444
|
-
unauthorizedComponent?:
|
|
508
|
+
unauthorizedComponent?: React__default.ReactNode;
|
|
445
509
|
/**
|
|
446
510
|
* Path to redirect to if not authenticated
|
|
447
511
|
* @default '/login'
|
|
@@ -568,14 +632,14 @@ interface ProtectedRouteProps {
|
|
|
568
632
|
* </ProtectedRoute>
|
|
569
633
|
* ```
|
|
570
634
|
*/
|
|
571
|
-
declare const ProtectedRoute:
|
|
635
|
+
declare const ProtectedRoute: React__default.FC<ProtectedRouteProps>;
|
|
572
636
|
|
|
573
637
|
interface LoginButtonProps {
|
|
574
638
|
/**
|
|
575
639
|
* Button text
|
|
576
640
|
* @default 'Login'
|
|
577
641
|
*/
|
|
578
|
-
children?:
|
|
642
|
+
children?: React__default.ReactNode;
|
|
579
643
|
/**
|
|
580
644
|
* Additional CSS class names
|
|
581
645
|
*/
|
|
@@ -602,7 +666,7 @@ interface LoginButtonProps {
|
|
|
602
666
|
/**
|
|
603
667
|
* Custom inline styles
|
|
604
668
|
*/
|
|
605
|
-
style?:
|
|
669
|
+
style?: React__default.CSSProperties;
|
|
606
670
|
/**
|
|
607
671
|
* Disable the button
|
|
608
672
|
*/
|
|
@@ -642,14 +706,14 @@ interface LoginButtonProps {
|
|
|
642
706
|
* />
|
|
643
707
|
* ```
|
|
644
708
|
*/
|
|
645
|
-
declare const LoginButton:
|
|
709
|
+
declare const LoginButton: React__default.FC<LoginButtonProps>;
|
|
646
710
|
|
|
647
711
|
interface LogoutButtonProps {
|
|
648
712
|
/**
|
|
649
713
|
* Button text
|
|
650
714
|
* @default 'Logout'
|
|
651
715
|
*/
|
|
652
|
-
children?:
|
|
716
|
+
children?: React__default.ReactNode;
|
|
653
717
|
/**
|
|
654
718
|
* Additional CSS class names
|
|
655
719
|
*/
|
|
@@ -676,7 +740,7 @@ interface LogoutButtonProps {
|
|
|
676
740
|
/**
|
|
677
741
|
* Custom inline styles
|
|
678
742
|
*/
|
|
679
|
-
style?:
|
|
743
|
+
style?: React__default.CSSProperties;
|
|
680
744
|
/**
|
|
681
745
|
* Disable the button
|
|
682
746
|
*/
|
|
@@ -719,7 +783,7 @@ interface LogoutButtonProps {
|
|
|
719
783
|
* />
|
|
720
784
|
* ```
|
|
721
785
|
*/
|
|
722
|
-
declare const LogoutButton:
|
|
786
|
+
declare const LogoutButton: React__default.FC<LogoutButtonProps>;
|
|
723
787
|
|
|
724
788
|
interface UserProfileProps {
|
|
725
789
|
/**
|
|
@@ -749,7 +813,7 @@ interface UserProfileProps {
|
|
|
749
813
|
/**
|
|
750
814
|
* Custom inline styles
|
|
751
815
|
*/
|
|
752
|
-
style?:
|
|
816
|
+
style?: React__default.CSSProperties;
|
|
753
817
|
/**
|
|
754
818
|
* Callback when profile is clicked
|
|
755
819
|
*/
|
|
@@ -793,7 +857,7 @@ interface UserProfileProps {
|
|
|
793
857
|
* />
|
|
794
858
|
* ```
|
|
795
859
|
*/
|
|
796
|
-
declare const UserProfile:
|
|
860
|
+
declare const UserProfile: React__default.FC<UserProfileProps>;
|
|
797
861
|
|
|
798
862
|
interface SessionMonitorProps {
|
|
799
863
|
/**
|
|
@@ -845,7 +909,7 @@ interface SessionMonitorProps {
|
|
|
845
909
|
/**
|
|
846
910
|
* Custom inline styles
|
|
847
911
|
*/
|
|
848
|
-
style?:
|
|
912
|
+
style?: React__default.CSSProperties;
|
|
849
913
|
}
|
|
850
914
|
/**
|
|
851
915
|
* Session monitoring component with countdown timer
|
|
@@ -888,7 +952,7 @@ interface SessionMonitorProps {
|
|
|
888
952
|
* />
|
|
889
953
|
* ```
|
|
890
954
|
*/
|
|
891
|
-
declare const SessionMonitor:
|
|
955
|
+
declare const SessionMonitor: React__default.FC<SessionMonitorProps>;
|
|
892
956
|
|
|
893
957
|
interface DeviceLoginProps {
|
|
894
958
|
/**
|
|
@@ -921,7 +985,7 @@ interface DeviceLoginProps {
|
|
|
921
985
|
/**
|
|
922
986
|
* Custom inline styles
|
|
923
987
|
*/
|
|
924
|
-
style?:
|
|
988
|
+
style?: React__default.CSSProperties;
|
|
925
989
|
}
|
|
926
990
|
/**
|
|
927
991
|
* Device flow login component
|
|
@@ -956,7 +1020,7 @@ interface DeviceLoginProps {
|
|
|
956
1020
|
* />
|
|
957
1021
|
* ```
|
|
958
1022
|
*/
|
|
959
|
-
declare const DeviceLogin:
|
|
1023
|
+
declare const DeviceLogin: React__default.FC<DeviceLoginProps>;
|
|
960
1024
|
|
|
961
1025
|
interface TokenLifecycleManagerProps {
|
|
962
1026
|
/**
|
|
@@ -966,7 +1030,7 @@ interface TokenLifecycleManagerProps {
|
|
|
966
1030
|
/**
|
|
967
1031
|
* Child components to render
|
|
968
1032
|
*/
|
|
969
|
-
children:
|
|
1033
|
+
children: React__default.ReactNode;
|
|
970
1034
|
/**
|
|
971
1035
|
* Enable debug logging
|
|
972
1036
|
* @default false
|
|
@@ -988,7 +1052,19 @@ interface TokenLifecycleManagerProps {
|
|
|
988
1052
|
* </TokenLifecycleManager>
|
|
989
1053
|
* ```
|
|
990
1054
|
*/
|
|
991
|
-
declare const TokenLifecycleManager:
|
|
1055
|
+
declare const TokenLifecycleManager: React__default.FC<TokenLifecycleManagerProps>;
|
|
1056
|
+
|
|
1057
|
+
interface TokenBridgeProps {
|
|
1058
|
+
/**
|
|
1059
|
+
* Optional custom bridge instance. Defaults to the shared global bridge.
|
|
1060
|
+
*/
|
|
1061
|
+
bridge?: TokenBridge;
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Synchronises the current Keycloak access token into an imperative TokenBridge.
|
|
1065
|
+
* Place this component inside your application tree (after KeycloakProvider).
|
|
1066
|
+
*/
|
|
1067
|
+
declare const TokenBridgeSync: React.FC<TokenBridgeProps>;
|
|
992
1068
|
|
|
993
1069
|
/**
|
|
994
1070
|
* Main authentication hook for accessing Keycloak authentication state and methods
|
|
@@ -1397,6 +1473,24 @@ declare class KeycloakConfigBuilder {
|
|
|
1397
1473
|
*/
|
|
1398
1474
|
declare function getConfigFromEnv(): Partial<KeycloakConfig>;
|
|
1399
1475
|
|
|
1476
|
+
type KeycloakDisabledReason = 'disabled' | 'missing-config';
|
|
1477
|
+
interface KeycloakStatus {
|
|
1478
|
+
/**
|
|
1479
|
+
* Whether the Keycloak integration is enabled by configuration.
|
|
1480
|
+
*/
|
|
1481
|
+
isEnabled: boolean;
|
|
1482
|
+
/**
|
|
1483
|
+
* Whether the minimum configuration (authority + clientId) is present.
|
|
1484
|
+
*/
|
|
1485
|
+
isConfigured: boolean;
|
|
1486
|
+
/**
|
|
1487
|
+
* Provides additional context when integration is disabled.
|
|
1488
|
+
*/
|
|
1489
|
+
reason?: KeycloakDisabledReason;
|
|
1490
|
+
}
|
|
1491
|
+
declare const KeycloakStatusProvider: React$1.Provider<KeycloakStatus>;
|
|
1492
|
+
declare const useKeycloakStatus: () => KeycloakStatus;
|
|
1493
|
+
|
|
1400
1494
|
/**
|
|
1401
1495
|
* Device authorization response from Keycloak
|
|
1402
1496
|
*/
|
|
@@ -1508,4 +1602,4 @@ declare function pollForToken(apiBaseUrl: string, deviceCode: string): Promise<T
|
|
|
1508
1602
|
*/
|
|
1509
1603
|
declare function fetchUserInfo(apiBaseUrl: string, accessToken: string): Promise<any>;
|
|
1510
1604
|
|
|
1511
|
-
export { type AdvancedOidcOptions, CONFIG_PRESETS, type ConfigPreset, type DeviceAuthorizationResponse, type DeviceFlowError, DeviceLogin, type DeviceLoginProps, type EventCallbacks, type FeatureFlags, type KeycloakConfig, KeycloakConfigBuilder, KeycloakProvider, type KeycloakProviderFullConfigProps, type KeycloakProviderProps, type KeycloakProviderZeroConfigProps, LoginButton, type LoginButtonProps, LogoutButton, type LogoutButtonProps, type PolicyContext, type PolicyFunction, ProtectedRoute, type ProtectedRouteProps, SessionMonitor, type SessionMonitorProps, type SilentRenewConfig, TokenLifecycleManager, type TokenLifecycleManagerProps, type TokenResponse, type UseProtectedFetchOptions, type UseProtectedFetchReturn, type UseRolesReturn, type UseScopesReturn, type UseTokenLifecycleOptions, type UseTokenLifecycleReturn, UserProfile, type UserProfileProps, defaultConfig, fetchUserInfo, getConfigFromEnv, getConfigPreset, mergeWithPreset, pollForToken, requestDeviceCode, useAuth, useProtectedFetch, useRoles, useScopes, useTokenLifecycle };
|
|
1605
|
+
export { type AdvancedOidcOptions, CONFIG_PRESETS, type ConfigPreset, type DeviceAuthorizationResponse, type DeviceFlowError, DeviceLogin, type DeviceLoginProps, type EventCallbacks, type FeatureFlags, type KeycloakConfig, KeycloakConfigBuilder, type KeycloakDisabledReason, KeycloakProvider, type KeycloakProviderFullConfigProps, type KeycloakProviderProps, type KeycloakProviderZeroConfigProps, type KeycloakStatus, KeycloakStatusProvider, LoginButton, type LoginButtonProps, LogoutButton, type LogoutButtonProps, type PolicyContext, type PolicyFunction, ProtectedRoute, type ProtectedRouteProps, SessionMonitor, type SessionMonitorProps, type SilentRenewConfig, TokenBridge, type TokenBridgeProps, TokenBridgeSync, TokenLifecycleManager, type TokenLifecycleManagerProps, type TokenListener, type TokenResponse, type UseProtectedFetchOptions, type UseProtectedFetchReturn, type UseRolesReturn, type UseScopesReturn, type UseTokenLifecycleOptions, type UseTokenLifecycleReturn, UserProfile, type UserProfileProps, createTokenBridge, defaultConfig, defaultTokenBridge, fetchUserInfo, getConfigFromEnv, getConfigPreset, mergeWithPreset, pollForToken, requestDeviceCode, useAuth, useKeycloakStatus, useProtectedFetch, useRoles, useScopes, useTokenLifecycle };
|
package/dist/index.js
CHANGED
|
@@ -30,7 +30,8 @@ var defaultConfig = {
|
|
|
30
30
|
maxRefreshTokenLifetime: 2592e3,
|
|
31
31
|
// 30 days in seconds
|
|
32
32
|
showLoadingIndicator: false,
|
|
33
|
-
loadingMessage: "Refreshing session..."
|
|
33
|
+
loadingMessage: "Refreshing session...",
|
|
34
|
+
useRefreshToken: true
|
|
34
35
|
},
|
|
35
36
|
advanced: {
|
|
36
37
|
responseType: "code",
|
|
@@ -56,6 +57,7 @@ var KeycloakConfigBuilder = class {
|
|
|
56
57
|
const config = this.mergeWithDefaults(userConfig);
|
|
57
58
|
const storageType = config.advanced.storageType || "session";
|
|
58
59
|
const storage = storageType === "local" ? window.localStorage : window.sessionStorage;
|
|
60
|
+
const { useRefreshToken } = config.silentRenew;
|
|
59
61
|
const oidcSettings = {
|
|
60
62
|
authority: config.authority,
|
|
61
63
|
client_id: config.clientId,
|
|
@@ -73,6 +75,9 @@ var KeycloakConfigBuilder = class {
|
|
|
73
75
|
response_mode: "query"
|
|
74
76
|
}
|
|
75
77
|
};
|
|
78
|
+
if (useRefreshToken !== void 0) {
|
|
79
|
+
oidcSettings.useRefreshToken = useRefreshToken;
|
|
80
|
+
}
|
|
76
81
|
return oidcSettings;
|
|
77
82
|
}
|
|
78
83
|
/**
|
|
@@ -160,6 +165,7 @@ function useTokenLifecycle(options) {
|
|
|
160
165
|
const [isRefreshing, setIsRefreshing] = react.useState(false);
|
|
161
166
|
const [isRefreshTokenExpired, setIsRefreshTokenExpired] = react.useState(false);
|
|
162
167
|
const hasCheckedOnStartup = react.useRef(false);
|
|
168
|
+
const supportsRefreshTokens = config.silentRenew?.useRefreshToken !== false;
|
|
163
169
|
const log = react.useCallback(
|
|
164
170
|
(message, ...args) => {
|
|
165
171
|
if (debug) {
|
|
@@ -296,24 +302,49 @@ function useTokenLifecycle(options) {
|
|
|
296
302
|
react.useEffect(() => {
|
|
297
303
|
const strategy = config.silentRenew?.refreshStrategy || "both";
|
|
298
304
|
const shouldCheckOnStartup = strategy === "startup" || strategy === "both";
|
|
305
|
+
const hasRefreshToken = Boolean(auth.user?.refresh_token);
|
|
299
306
|
if (!shouldCheckOnStartup || hasCheckedOnStartup.current) {
|
|
300
307
|
return;
|
|
301
308
|
}
|
|
302
|
-
if (
|
|
309
|
+
if (auth.isLoading) {
|
|
303
310
|
return;
|
|
304
311
|
}
|
|
312
|
+
if (!auth.isAuthenticated) {
|
|
313
|
+
if (!supportsRefreshTokens) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (!auth.user || !hasRefreshToken) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
305
320
|
hasCheckedOnStartup.current = true;
|
|
306
|
-
log(
|
|
321
|
+
log(
|
|
322
|
+
"Running startup token check with strategy:",
|
|
323
|
+
strategy,
|
|
324
|
+
"using refresh token:",
|
|
325
|
+
!auth.isAuthenticated && supportsRefreshTokens
|
|
326
|
+
);
|
|
307
327
|
checkAndRefreshTokens().catch((error) => {
|
|
308
328
|
log("Startup token check failed:", error);
|
|
309
329
|
});
|
|
310
330
|
}, [
|
|
311
331
|
auth.isAuthenticated,
|
|
312
332
|
auth.isLoading,
|
|
333
|
+
auth.user,
|
|
313
334
|
config.silentRenew?.refreshStrategy,
|
|
335
|
+
supportsRefreshTokens,
|
|
314
336
|
checkAndRefreshTokens,
|
|
315
337
|
log
|
|
316
338
|
]);
|
|
339
|
+
react.useEffect(() => {
|
|
340
|
+
if (hasCheckedOnStartup.current) {
|
|
341
|
+
const hasUser = Boolean(auth.user);
|
|
342
|
+
const hasRefreshToken = Boolean(auth.user?.refresh_token);
|
|
343
|
+
if (auth.isLoading || !auth.isAuthenticated && (!supportsRefreshTokens || !hasUser || !hasRefreshToken)) {
|
|
344
|
+
hasCheckedOnStartup.current = false;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}, [auth.isAuthenticated, auth.isLoading, auth.user, supportsRefreshTokens]);
|
|
317
348
|
react.useEffect(() => {
|
|
318
349
|
if (auth.user && !auth.isLoading) {
|
|
319
350
|
storeFirstLoginTime();
|
|
@@ -628,6 +659,71 @@ var SessionMonitor = ({
|
|
|
628
659
|
] });
|
|
629
660
|
};
|
|
630
661
|
|
|
662
|
+
// src/utils/tokenBridge.ts
|
|
663
|
+
var TokenBridge = class {
|
|
664
|
+
constructor() {
|
|
665
|
+
this.currentToken = null;
|
|
666
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Update the tracked token and notify subscribers when it changes.
|
|
670
|
+
*/
|
|
671
|
+
setToken(token) {
|
|
672
|
+
if (this.currentToken === token) {
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
this.currentToken = token;
|
|
676
|
+
for (const listener of this.listeners) {
|
|
677
|
+
try {
|
|
678
|
+
listener(token);
|
|
679
|
+
} catch (error) {
|
|
680
|
+
console.error("[TokenBridge] Listener threw error:", error);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Read the most recent token value.
|
|
686
|
+
*/
|
|
687
|
+
getToken() {
|
|
688
|
+
return this.currentToken;
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Convenience helper for async callers.
|
|
692
|
+
*/
|
|
693
|
+
async getTokenAsync() {
|
|
694
|
+
return this.currentToken;
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Subscribe to token changes. Returns an unsubscribe callback.
|
|
698
|
+
*/
|
|
699
|
+
subscribe(listener) {
|
|
700
|
+
this.listeners.add(listener);
|
|
701
|
+
listener(this.currentToken);
|
|
702
|
+
return () => {
|
|
703
|
+
this.listeners.delete(listener);
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
var createTokenBridge = () => new TokenBridge();
|
|
708
|
+
var defaultTokenBridge = createTokenBridge();
|
|
709
|
+
|
|
710
|
+
// src/components/TokenBridge.tsx
|
|
711
|
+
var TokenBridgeSync = ({
|
|
712
|
+
bridge = defaultTokenBridge
|
|
713
|
+
}) => {
|
|
714
|
+
const auth = useAuth();
|
|
715
|
+
react.useEffect(() => {
|
|
716
|
+
const token = auth.user?.access_token ?? null;
|
|
717
|
+
bridge.setToken(token);
|
|
718
|
+
}, [auth.user, bridge]);
|
|
719
|
+
react.useEffect(() => {
|
|
720
|
+
if (!auth.isAuthenticated) {
|
|
721
|
+
bridge.setToken(null);
|
|
722
|
+
}
|
|
723
|
+
}, [auth.isAuthenticated, bridge]);
|
|
724
|
+
return null;
|
|
725
|
+
};
|
|
726
|
+
|
|
631
727
|
// src/config/presets.ts
|
|
632
728
|
var CONFIG_PRESETS = {
|
|
633
729
|
/**
|
|
@@ -846,11 +942,97 @@ var StorageConfigProvider = ({ storageType, children }) => {
|
|
|
846
942
|
return /* @__PURE__ */ jsxRuntime.jsx(StorageConfigContext.Provider, { value: { storageType, storage }, children });
|
|
847
943
|
};
|
|
848
944
|
var useStorageConfig = () => react.useContext(StorageConfigContext);
|
|
945
|
+
var defaultStatus = {
|
|
946
|
+
isEnabled: true,
|
|
947
|
+
isConfigured: true
|
|
948
|
+
};
|
|
949
|
+
var KeycloakStatusContext = react.createContext(defaultStatus);
|
|
950
|
+
var KeycloakStatusProvider = KeycloakStatusContext.Provider;
|
|
951
|
+
var useKeycloakStatus = () => react.useContext(KeycloakStatusContext);
|
|
952
|
+
var createDisabledEvents = () => {
|
|
953
|
+
const noop = () => void 0;
|
|
954
|
+
const noopSubscription = () => () => void 0;
|
|
955
|
+
const noopAsync = async () => void 0;
|
|
956
|
+
const events = {
|
|
957
|
+
addAccessTokenExpiring: noopSubscription,
|
|
958
|
+
removeAccessTokenExpiring: noop,
|
|
959
|
+
addAccessTokenExpired: noopSubscription,
|
|
960
|
+
removeAccessTokenExpired: noop,
|
|
961
|
+
addSilentRenewError: noopSubscription,
|
|
962
|
+
removeSilentRenewError: noop,
|
|
963
|
+
addUserLoaded: noopSubscription,
|
|
964
|
+
removeUserLoaded: noop,
|
|
965
|
+
addUserUnloaded: noopSubscription,
|
|
966
|
+
removeUserUnloaded: noop,
|
|
967
|
+
addUserSignedIn: noopSubscription,
|
|
968
|
+
removeUserSignedIn: noop,
|
|
969
|
+
addUserSignedOut: noopSubscription,
|
|
970
|
+
removeUserSignedOut: noop,
|
|
971
|
+
addUserSessionChanged: noopSubscription,
|
|
972
|
+
removeUserSessionChanged: noop,
|
|
973
|
+
load: noopAsync,
|
|
974
|
+
unload: noopAsync
|
|
975
|
+
};
|
|
976
|
+
return events;
|
|
977
|
+
};
|
|
978
|
+
var disabledEvents = createDisabledEvents();
|
|
979
|
+
var disabledSettings = {
|
|
980
|
+
authority: "",
|
|
981
|
+
client_id: "",
|
|
982
|
+
redirect_uri: "http://localhost/disabled",
|
|
983
|
+
response_type: "code",
|
|
984
|
+
scope: ""
|
|
985
|
+
};
|
|
986
|
+
var disabledError = () => new Error("Keycloak integration is disabled for this environment.");
|
|
987
|
+
var disabledAsync = async () => {
|
|
988
|
+
throw disabledError();
|
|
989
|
+
};
|
|
990
|
+
var disabledAuthContext = {
|
|
991
|
+
settings: disabledSettings,
|
|
992
|
+
events: disabledEvents,
|
|
993
|
+
clearStaleState: async () => void 0,
|
|
994
|
+
removeUser: async () => void 0,
|
|
995
|
+
signinPopup: (_args) => disabledAsync(),
|
|
996
|
+
signinSilent: async (_args) => null,
|
|
997
|
+
signinRedirect: (_args) => disabledAsync(),
|
|
998
|
+
signinResourceOwnerCredentials: (_args) => disabledAsync(),
|
|
999
|
+
signoutRedirect: async (_args) => void 0,
|
|
1000
|
+
signoutPopup: async (_args) => void 0,
|
|
1001
|
+
signoutSilent: async (_args) => void 0,
|
|
1002
|
+
querySessionStatus: async (_args) => null,
|
|
1003
|
+
revokeTokens: async (_types) => void 0,
|
|
1004
|
+
startSilentRenew: () => void 0,
|
|
1005
|
+
stopSilentRenew: () => void 0,
|
|
1006
|
+
user: null,
|
|
1007
|
+
isLoading: false,
|
|
1008
|
+
isAuthenticated: false,
|
|
1009
|
+
activeNavigator: void 0,
|
|
1010
|
+
error: void 0
|
|
1011
|
+
};
|
|
849
1012
|
function isZeroConfig(props) {
|
|
850
1013
|
return "authority" in props && "clientId" in props;
|
|
851
1014
|
}
|
|
852
1015
|
var KeycloakProvider = (props) => {
|
|
853
1016
|
const { children, debug = false } = props;
|
|
1017
|
+
const status = react.useMemo(() => {
|
|
1018
|
+
const enabledFlag = props.enabled ?? true;
|
|
1019
|
+
const hasAuthority = isZeroConfig(props) ? Boolean(props.authority) : Boolean(props.config.authority);
|
|
1020
|
+
const hasClientId = isZeroConfig(props) ? Boolean(props.clientId) : Boolean(props.config.clientId);
|
|
1021
|
+
return {
|
|
1022
|
+
isEnabled: enabledFlag,
|
|
1023
|
+
isConfigured: hasAuthority && hasClientId,
|
|
1024
|
+
reason: !enabledFlag ? "disabled" : !hasAuthority || !hasClientId ? "missing-config" : void 0
|
|
1025
|
+
};
|
|
1026
|
+
}, [props]);
|
|
1027
|
+
const effectiveEnabled = status.isEnabled && status.isConfigured;
|
|
1028
|
+
react.useEffect(() => {
|
|
1029
|
+
if (!effectiveEnabled && props.tokenBridge) {
|
|
1030
|
+
props.tokenBridge.setToken(null);
|
|
1031
|
+
}
|
|
1032
|
+
}, [effectiveEnabled, props.tokenBridge]);
|
|
1033
|
+
if (!effectiveEnabled) {
|
|
1034
|
+
return /* @__PURE__ */ jsxRuntime.jsx(KeycloakStatusProvider, { value: status, children: /* @__PURE__ */ jsxRuntime.jsx(reactOidcContext.AuthContext.Provider, { value: disabledAuthContext, children: /* @__PURE__ */ jsxRuntime.jsx(StorageConfigProvider, { storageType: "session", children }) }) });
|
|
1035
|
+
}
|
|
854
1036
|
const config = isZeroConfig(props) ? buildZeroConfig(props) : props.config;
|
|
855
1037
|
const oidcConfig = KeycloakConfigBuilder.build(config);
|
|
856
1038
|
const log = react.useCallback(
|
|
@@ -892,7 +1074,7 @@ var KeycloakProvider = (props) => {
|
|
|
892
1074
|
}, [log]);
|
|
893
1075
|
const showSessionMonitor = config.features?.sessionMonitor !== false;
|
|
894
1076
|
const storageType = config.advanced?.storageType || "session";
|
|
895
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1077
|
+
return /* @__PURE__ */ jsxRuntime.jsx(KeycloakStatusProvider, { value: status, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
896
1078
|
reactOidcContext.AuthProvider,
|
|
897
1079
|
{
|
|
898
1080
|
...oidcConfig,
|
|
@@ -900,11 +1082,12 @@ var KeycloakProvider = (props) => {
|
|
|
900
1082
|
onSignoutCallback,
|
|
901
1083
|
onRemoveUser,
|
|
902
1084
|
children: /* @__PURE__ */ jsxRuntime.jsx(StorageConfigProvider, { storageType, children: /* @__PURE__ */ jsxRuntime.jsxs(TokenLifecycleManager, { config, debug, children: [
|
|
1085
|
+
props.tokenBridge && /* @__PURE__ */ jsxRuntime.jsx(TokenBridgeSync, { bridge: props.tokenBridge }),
|
|
903
1086
|
showSessionMonitor && /* @__PURE__ */ jsxRuntime.jsx(SessionMonitor, {}),
|
|
904
1087
|
children
|
|
905
1088
|
] }) })
|
|
906
1089
|
}
|
|
907
|
-
);
|
|
1090
|
+
) });
|
|
908
1091
|
};
|
|
909
1092
|
function buildZeroConfig(props) {
|
|
910
1093
|
const { authority, clientId, preset = "default", config: overrides = {} } = props;
|
|
@@ -1612,13 +1795,18 @@ exports.CONFIG_PRESETS = CONFIG_PRESETS;
|
|
|
1612
1795
|
exports.DeviceLogin = DeviceLogin;
|
|
1613
1796
|
exports.KeycloakConfigBuilder = KeycloakConfigBuilder;
|
|
1614
1797
|
exports.KeycloakProvider = KeycloakProvider;
|
|
1798
|
+
exports.KeycloakStatusProvider = KeycloakStatusProvider;
|
|
1615
1799
|
exports.LoginButton = LoginButton;
|
|
1616
1800
|
exports.LogoutButton = LogoutButton;
|
|
1617
1801
|
exports.ProtectedRoute = ProtectedRoute;
|
|
1618
1802
|
exports.SessionMonitor = SessionMonitor;
|
|
1803
|
+
exports.TokenBridge = TokenBridge;
|
|
1804
|
+
exports.TokenBridgeSync = TokenBridgeSync;
|
|
1619
1805
|
exports.TokenLifecycleManager = TokenLifecycleManager;
|
|
1620
1806
|
exports.UserProfile = UserProfile;
|
|
1807
|
+
exports.createTokenBridge = createTokenBridge;
|
|
1621
1808
|
exports.defaultConfig = defaultConfig;
|
|
1809
|
+
exports.defaultTokenBridge = defaultTokenBridge;
|
|
1622
1810
|
exports.fetchUserInfo = fetchUserInfo;
|
|
1623
1811
|
exports.getConfigFromEnv = getConfigFromEnv;
|
|
1624
1812
|
exports.getConfigPreset = getConfigPreset;
|
|
@@ -1626,6 +1814,7 @@ exports.mergeWithPreset = mergeWithPreset;
|
|
|
1626
1814
|
exports.pollForToken = pollForToken;
|
|
1627
1815
|
exports.requestDeviceCode = requestDeviceCode;
|
|
1628
1816
|
exports.useAuth = useAuth;
|
|
1817
|
+
exports.useKeycloakStatus = useKeycloakStatus;
|
|
1629
1818
|
exports.useProtectedFetch = useProtectedFetch;
|
|
1630
1819
|
exports.useRoles = useRoles;
|
|
1631
1820
|
exports.useScopes = useScopes;
|