antaeus.keycloak.react 1.0.6 → 2.0.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 +135 -546
- package/dist/index.d.mts +307 -11
- package/dist/index.d.ts +307 -11
- package/dist/index.js +711 -125
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +709 -128
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -3
package/dist/index.js
CHANGED
|
@@ -23,8 +23,14 @@ var defaultConfig = {
|
|
|
23
23
|
silentRenew: {
|
|
24
24
|
enabled: true,
|
|
25
25
|
silentRedirectUri: typeof window !== "undefined" ? window.location.origin + "/silent-renew.html" : "/silent-renew.html",
|
|
26
|
-
thresholdSeconds: 300
|
|
26
|
+
thresholdSeconds: 300,
|
|
27
27
|
// 5 minutes
|
|
28
|
+
refreshStrategy: "both",
|
|
29
|
+
// Check on startup AND retry on 401
|
|
30
|
+
maxRefreshTokenLifetime: 2592e3,
|
|
31
|
+
// 30 days in seconds
|
|
32
|
+
showLoadingIndicator: false,
|
|
33
|
+
loadingMessage: "Refreshing session..."
|
|
28
34
|
},
|
|
29
35
|
advanced: {
|
|
30
36
|
responseType: "code",
|
|
@@ -128,16 +134,693 @@ function getConfigFromEnv() {
|
|
|
128
134
|
}
|
|
129
135
|
};
|
|
130
136
|
}
|
|
131
|
-
var
|
|
137
|
+
var useAuth = reactOidcContext.useAuth;
|
|
138
|
+
|
|
139
|
+
// src/hooks/useTokenLifecycle.ts
|
|
140
|
+
var STORAGE_KEY_PREFIX = "keycloak_first_login_";
|
|
141
|
+
function useTokenLifecycle(options) {
|
|
142
|
+
const {
|
|
143
|
+
config,
|
|
144
|
+
debug = false,
|
|
145
|
+
onRefreshStart,
|
|
146
|
+
onRefreshSuccess,
|
|
147
|
+
onRefreshError,
|
|
148
|
+
onRefreshTokenExpired
|
|
149
|
+
} = options;
|
|
150
|
+
const auth = useAuth();
|
|
151
|
+
const [isRefreshing, setIsRefreshing] = react.useState(false);
|
|
152
|
+
const [isRefreshTokenExpired, setIsRefreshTokenExpired] = react.useState(false);
|
|
153
|
+
const hasCheckedOnStartup = react.useRef(false);
|
|
154
|
+
const log = react.useCallback(
|
|
155
|
+
(message, ...args) => {
|
|
156
|
+
if (debug) {
|
|
157
|
+
console.log("[TokenLifecycle]", message, ...args);
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
[debug]
|
|
161
|
+
);
|
|
162
|
+
const getStorageKey = react.useCallback(() => {
|
|
163
|
+
const authority = config.authority;
|
|
164
|
+
const clientId = config.clientId;
|
|
165
|
+
return `${STORAGE_KEY_PREFIX}${authority}_${clientId}`;
|
|
166
|
+
}, [config.authority, config.clientId]);
|
|
167
|
+
const storeFirstLoginTime = react.useCallback(() => {
|
|
168
|
+
const key = getStorageKey();
|
|
169
|
+
const existingTime = localStorage.getItem(key);
|
|
170
|
+
if (!existingTime) {
|
|
171
|
+
const now = Date.now();
|
|
172
|
+
localStorage.setItem(key, now.toString());
|
|
173
|
+
log("Stored first login time:", new Date(now).toISOString());
|
|
174
|
+
}
|
|
175
|
+
}, [getStorageKey, log]);
|
|
176
|
+
const checkRefreshTokenLifetime = react.useCallback(() => {
|
|
177
|
+
const maxLifetime = config.silentRenew?.maxRefreshTokenLifetime;
|
|
178
|
+
if (!maxLifetime || maxLifetime <= 0) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
const key = getStorageKey();
|
|
182
|
+
const firstLoginStr = localStorage.getItem(key);
|
|
183
|
+
if (!firstLoginStr) {
|
|
184
|
+
storeFirstLoginTime();
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
const firstLoginTime = parseInt(firstLoginStr, 10);
|
|
188
|
+
const now = Date.now();
|
|
189
|
+
const elapsedSeconds = (now - firstLoginTime) / 1e3;
|
|
190
|
+
log("Refresh token lifetime check:", {
|
|
191
|
+
firstLogin: new Date(firstLoginTime).toISOString(),
|
|
192
|
+
elapsedSeconds,
|
|
193
|
+
maxLifetime,
|
|
194
|
+
isExpired: elapsedSeconds >= maxLifetime
|
|
195
|
+
});
|
|
196
|
+
return elapsedSeconds >= maxLifetime;
|
|
197
|
+
}, [config.silentRenew?.maxRefreshTokenLifetime, getStorageKey, log, storeFirstLoginTime]);
|
|
198
|
+
const refreshToken = react.useCallback(async () => {
|
|
199
|
+
if (isRefreshing) {
|
|
200
|
+
log("Refresh already in progress, skipping");
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (checkRefreshTokenLifetime()) {
|
|
204
|
+
log("Refresh token exceeded max lifetime, cannot refresh");
|
|
205
|
+
setIsRefreshTokenExpired(true);
|
|
206
|
+
onRefreshTokenExpired?.();
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
setIsRefreshing(true);
|
|
211
|
+
onRefreshStart?.();
|
|
212
|
+
log("Starting token refresh");
|
|
213
|
+
await auth.signinSilent();
|
|
214
|
+
log("Token refresh successful");
|
|
215
|
+
onRefreshSuccess?.();
|
|
216
|
+
} catch (error) {
|
|
217
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
218
|
+
log("Token refresh failed:", err.message);
|
|
219
|
+
onRefreshError?.(err);
|
|
220
|
+
throw err;
|
|
221
|
+
} finally {
|
|
222
|
+
setIsRefreshing(false);
|
|
223
|
+
}
|
|
224
|
+
}, [
|
|
225
|
+
isRefreshing,
|
|
226
|
+
auth,
|
|
227
|
+
checkRefreshTokenLifetime,
|
|
228
|
+
onRefreshStart,
|
|
229
|
+
onRefreshSuccess,
|
|
230
|
+
onRefreshError,
|
|
231
|
+
onRefreshTokenExpired,
|
|
232
|
+
log
|
|
233
|
+
]);
|
|
234
|
+
const checkAndRefreshTokens = react.useCallback(async () => {
|
|
235
|
+
if (!auth.user) {
|
|
236
|
+
log("No user session, skipping token check");
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
if (checkRefreshTokenLifetime()) {
|
|
240
|
+
log("Refresh token exceeded max lifetime");
|
|
241
|
+
setIsRefreshTokenExpired(true);
|
|
242
|
+
onRefreshTokenExpired?.();
|
|
243
|
+
const key = getStorageKey();
|
|
244
|
+
localStorage.removeItem(key);
|
|
245
|
+
await auth.removeUser();
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
const expiresAt = auth.user.expires_at;
|
|
249
|
+
if (!expiresAt) {
|
|
250
|
+
log("No expiration time found, skipping token check");
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
254
|
+
const threshold = config.silentRenew?.thresholdSeconds || 300;
|
|
255
|
+
const timeUntilExpiry = expiresAt - now;
|
|
256
|
+
log("Token expiration check:", {
|
|
257
|
+
expiresAt: new Date(expiresAt * 1e3).toISOString(),
|
|
258
|
+
now: new Date(now * 1e3).toISOString(),
|
|
259
|
+
timeUntilExpiry,
|
|
260
|
+
threshold,
|
|
261
|
+
needsRefresh: timeUntilExpiry <= threshold
|
|
262
|
+
});
|
|
263
|
+
if (timeUntilExpiry <= threshold) {
|
|
264
|
+
try {
|
|
265
|
+
await refreshToken();
|
|
266
|
+
return true;
|
|
267
|
+
} catch (error) {
|
|
268
|
+
log("Failed to refresh tokens:", error);
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
log("Tokens are still valid, no refresh needed");
|
|
273
|
+
return false;
|
|
274
|
+
}, [
|
|
275
|
+
auth,
|
|
276
|
+
config.silentRenew?.thresholdSeconds,
|
|
277
|
+
checkRefreshTokenLifetime,
|
|
278
|
+
refreshToken,
|
|
279
|
+
onRefreshTokenExpired,
|
|
280
|
+
getStorageKey,
|
|
281
|
+
log
|
|
282
|
+
]);
|
|
283
|
+
react.useEffect(() => {
|
|
284
|
+
const strategy = config.silentRenew?.refreshStrategy || "both";
|
|
285
|
+
const shouldCheckOnStartup = strategy === "startup" || strategy === "both";
|
|
286
|
+
if (!shouldCheckOnStartup || hasCheckedOnStartup.current) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (!auth.isAuthenticated || auth.isLoading) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
hasCheckedOnStartup.current = true;
|
|
293
|
+
log("Running startup token check with strategy:", strategy);
|
|
294
|
+
checkAndRefreshTokens().catch((error) => {
|
|
295
|
+
log("Startup token check failed:", error);
|
|
296
|
+
});
|
|
297
|
+
}, [
|
|
298
|
+
auth.isAuthenticated,
|
|
299
|
+
auth.isLoading,
|
|
300
|
+
config.silentRenew?.refreshStrategy,
|
|
301
|
+
checkAndRefreshTokens,
|
|
302
|
+
log
|
|
303
|
+
]);
|
|
304
|
+
react.useEffect(() => {
|
|
305
|
+
if (auth.user && !auth.isLoading) {
|
|
306
|
+
storeFirstLoginTime();
|
|
307
|
+
}
|
|
308
|
+
}, [auth.user, auth.isLoading, storeFirstLoginTime]);
|
|
309
|
+
react.useEffect(() => {
|
|
310
|
+
if (!auth.isAuthenticated || auth.isLoading) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const maxLifetime = config.silentRenew?.maxRefreshTokenLifetime;
|
|
314
|
+
if (!maxLifetime || maxLifetime <= 0) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const intervalId = setInterval(() => {
|
|
318
|
+
if (checkRefreshTokenLifetime()) {
|
|
319
|
+
log("Refresh token expired during periodic check");
|
|
320
|
+
setIsRefreshTokenExpired(true);
|
|
321
|
+
onRefreshTokenExpired?.();
|
|
322
|
+
const key = getStorageKey();
|
|
323
|
+
localStorage.removeItem(key);
|
|
324
|
+
auth.removeUser();
|
|
325
|
+
}
|
|
326
|
+
}, 5 * 60 * 1e3);
|
|
327
|
+
return () => clearInterval(intervalId);
|
|
328
|
+
}, [
|
|
329
|
+
auth,
|
|
330
|
+
config.silentRenew?.maxRefreshTokenLifetime,
|
|
331
|
+
checkRefreshTokenLifetime,
|
|
332
|
+
onRefreshTokenExpired,
|
|
333
|
+
getStorageKey,
|
|
334
|
+
log
|
|
335
|
+
]);
|
|
336
|
+
return {
|
|
337
|
+
isRefreshing,
|
|
338
|
+
isRefreshTokenExpired,
|
|
339
|
+
refreshToken,
|
|
340
|
+
checkAndRefreshTokens
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
var TokenLifecycleManager = ({
|
|
132
344
|
config,
|
|
133
345
|
children,
|
|
134
346
|
debug = false
|
|
135
347
|
}) => {
|
|
348
|
+
const [showLoading, setShowLoading] = react.useState(false);
|
|
349
|
+
const handleRefreshStart = react.useCallback(() => {
|
|
350
|
+
if (config.silentRenew?.showLoadingIndicator) {
|
|
351
|
+
setShowLoading(true);
|
|
352
|
+
}
|
|
353
|
+
}, [config.silentRenew?.showLoadingIndicator]);
|
|
354
|
+
const handleRefreshSuccess = react.useCallback(() => {
|
|
355
|
+
setShowLoading(false);
|
|
356
|
+
}, []);
|
|
357
|
+
const handleRefreshError = react.useCallback((error) => {
|
|
358
|
+
setShowLoading(false);
|
|
359
|
+
if (debug) {
|
|
360
|
+
console.error("[TokenLifecycleManager] Token refresh failed:", error);
|
|
361
|
+
}
|
|
362
|
+
config.events?.onSilentRenewError?.(error);
|
|
363
|
+
}, [config.events, debug]);
|
|
364
|
+
const handleRefreshTokenExpired = react.useCallback(() => {
|
|
365
|
+
setShowLoading(false);
|
|
366
|
+
if (debug) {
|
|
367
|
+
console.warn("[TokenLifecycleManager] Refresh token exceeded max lifetime");
|
|
368
|
+
}
|
|
369
|
+
config.events?.onTokenExpired?.();
|
|
370
|
+
}, [config.events, debug]);
|
|
371
|
+
const { isRefreshing, isRefreshTokenExpired } = useTokenLifecycle({
|
|
372
|
+
config,
|
|
373
|
+
debug,
|
|
374
|
+
onRefreshStart: handleRefreshStart,
|
|
375
|
+
onRefreshSuccess: handleRefreshSuccess,
|
|
376
|
+
onRefreshError: handleRefreshError,
|
|
377
|
+
onRefreshTokenExpired: handleRefreshTokenExpired
|
|
378
|
+
});
|
|
379
|
+
if (isRefreshTokenExpired) {
|
|
380
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
381
|
+
"div",
|
|
382
|
+
{
|
|
383
|
+
style: {
|
|
384
|
+
display: "flex",
|
|
385
|
+
flexDirection: "column",
|
|
386
|
+
alignItems: "center",
|
|
387
|
+
justifyContent: "center",
|
|
388
|
+
height: "100vh",
|
|
389
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
390
|
+
backgroundColor: "#f5f5f5",
|
|
391
|
+
padding: "20px",
|
|
392
|
+
textAlign: "center"
|
|
393
|
+
},
|
|
394
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
395
|
+
"div",
|
|
396
|
+
{
|
|
397
|
+
style: {
|
|
398
|
+
backgroundColor: "white",
|
|
399
|
+
padding: "40px",
|
|
400
|
+
borderRadius: "8px",
|
|
401
|
+
boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
|
|
402
|
+
maxWidth: "400px"
|
|
403
|
+
},
|
|
404
|
+
children: [
|
|
405
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { style: { margin: "0 0 16px 0", color: "#333" }, children: "Session Expired" }),
|
|
406
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { style: { margin: "0 0 24px 0", color: "#666" }, children: "Your session has been inactive for too long. Please sign in again to continue." }),
|
|
407
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
408
|
+
"button",
|
|
409
|
+
{
|
|
410
|
+
onClick: () => window.location.reload(),
|
|
411
|
+
style: {
|
|
412
|
+
padding: "12px 24px",
|
|
413
|
+
backgroundColor: "#007bff",
|
|
414
|
+
color: "white",
|
|
415
|
+
border: "none",
|
|
416
|
+
borderRadius: "4px",
|
|
417
|
+
fontSize: "16px",
|
|
418
|
+
cursor: "pointer",
|
|
419
|
+
fontWeight: "500"
|
|
420
|
+
},
|
|
421
|
+
children: "Sign In Again"
|
|
422
|
+
}
|
|
423
|
+
)
|
|
424
|
+
]
|
|
425
|
+
}
|
|
426
|
+
)
|
|
427
|
+
}
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
if (showLoading && isRefreshing) {
|
|
431
|
+
const message = config.silentRenew?.loadingMessage || "Refreshing session...";
|
|
432
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
433
|
+
"div",
|
|
434
|
+
{
|
|
435
|
+
style: {
|
|
436
|
+
display: "flex",
|
|
437
|
+
flexDirection: "column",
|
|
438
|
+
alignItems: "center",
|
|
439
|
+
justifyContent: "center",
|
|
440
|
+
height: "100vh",
|
|
441
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
442
|
+
backgroundColor: "#f5f5f5"
|
|
443
|
+
},
|
|
444
|
+
children: [
|
|
445
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
446
|
+
"div",
|
|
447
|
+
{
|
|
448
|
+
style: {
|
|
449
|
+
display: "flex",
|
|
450
|
+
flexDirection: "column",
|
|
451
|
+
alignItems: "center",
|
|
452
|
+
gap: "16px"
|
|
453
|
+
},
|
|
454
|
+
children: [
|
|
455
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
456
|
+
"div",
|
|
457
|
+
{
|
|
458
|
+
style: {
|
|
459
|
+
width: "40px",
|
|
460
|
+
height: "40px",
|
|
461
|
+
border: "4px solid #e0e0e0",
|
|
462
|
+
borderTop: "4px solid #007bff",
|
|
463
|
+
borderRadius: "50%",
|
|
464
|
+
animation: "spin 1s linear infinite"
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
),
|
|
468
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { style: { margin: 0, color: "#666", fontSize: "16px" }, children: message })
|
|
469
|
+
]
|
|
470
|
+
}
|
|
471
|
+
),
|
|
472
|
+
/* @__PURE__ */ jsxRuntime.jsx("style", { children: `
|
|
473
|
+
@keyframes spin {
|
|
474
|
+
0% { transform: rotate(0deg); }
|
|
475
|
+
100% { transform: rotate(360deg); }
|
|
476
|
+
}
|
|
477
|
+
` })
|
|
478
|
+
]
|
|
479
|
+
}
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
483
|
+
};
|
|
484
|
+
var SessionMonitor = ({
|
|
485
|
+
position = "top-right",
|
|
486
|
+
warningThreshold = 300,
|
|
487
|
+
// 5 minutes
|
|
488
|
+
criticalThreshold = 60,
|
|
489
|
+
// 1 minute
|
|
490
|
+
showProgressBar = true,
|
|
491
|
+
minimizable = true,
|
|
492
|
+
startMinimized = false,
|
|
493
|
+
className = "",
|
|
494
|
+
onWarning,
|
|
495
|
+
onCritical,
|
|
496
|
+
onExpired,
|
|
497
|
+
style
|
|
498
|
+
}) => {
|
|
499
|
+
const auth = useAuth();
|
|
500
|
+
const [timeRemaining, setTimeRemaining] = react.useState(null);
|
|
501
|
+
const [minimized, setMinimized] = react.useState(startMinimized);
|
|
502
|
+
const [warningTriggered, setWarningTriggered] = react.useState(false);
|
|
503
|
+
const [criticalTriggered, setCriticalTriggered] = react.useState(false);
|
|
504
|
+
react.useEffect(() => {
|
|
505
|
+
if (!auth.isAuthenticated || !auth.user?.expires_at) {
|
|
506
|
+
setTimeRemaining(null);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const updateTimeRemaining = () => {
|
|
510
|
+
const expiresAt = auth.user.expires_at;
|
|
511
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
512
|
+
const remaining = expiresAt - now;
|
|
513
|
+
if (remaining <= 0) {
|
|
514
|
+
setTimeRemaining(0);
|
|
515
|
+
onExpired?.();
|
|
516
|
+
setTimeout(() => {
|
|
517
|
+
auth.signoutRedirect();
|
|
518
|
+
}, 2e3);
|
|
519
|
+
} else {
|
|
520
|
+
setTimeRemaining(remaining);
|
|
521
|
+
if (remaining <= criticalThreshold && !criticalTriggered) {
|
|
522
|
+
setCriticalTriggered(true);
|
|
523
|
+
onCritical?.(remaining);
|
|
524
|
+
} else if (remaining <= warningThreshold && !warningTriggered) {
|
|
525
|
+
setWarningTriggered(true);
|
|
526
|
+
onWarning?.(remaining);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
updateTimeRemaining();
|
|
531
|
+
const interval = setInterval(updateTimeRemaining, 1e3);
|
|
532
|
+
return () => clearInterval(interval);
|
|
533
|
+
}, [
|
|
534
|
+
auth,
|
|
535
|
+
warningThreshold,
|
|
536
|
+
criticalThreshold,
|
|
537
|
+
warningTriggered,
|
|
538
|
+
criticalTriggered,
|
|
539
|
+
onWarning,
|
|
540
|
+
onCritical,
|
|
541
|
+
onExpired
|
|
542
|
+
]);
|
|
543
|
+
react.useEffect(() => {
|
|
544
|
+
if (timeRemaining && timeRemaining > warningThreshold) {
|
|
545
|
+
setWarningTriggered(false);
|
|
546
|
+
setCriticalTriggered(false);
|
|
547
|
+
}
|
|
548
|
+
}, [timeRemaining, warningThreshold]);
|
|
549
|
+
const formatTime = react.useCallback((seconds) => {
|
|
550
|
+
const mins = Math.floor(seconds / 60);
|
|
551
|
+
const secs = seconds % 60;
|
|
552
|
+
return `${mins}:${secs.toString().padStart(2, "0")}`;
|
|
553
|
+
}, []);
|
|
554
|
+
const getStatusClass = react.useCallback(() => {
|
|
555
|
+
if (!timeRemaining) return "normal";
|
|
556
|
+
if (timeRemaining <= criticalThreshold) return "critical";
|
|
557
|
+
if (timeRemaining <= warningThreshold) return "warning";
|
|
558
|
+
return "normal";
|
|
559
|
+
}, [timeRemaining, warningThreshold, criticalThreshold]);
|
|
560
|
+
const getProgress = react.useCallback(() => {
|
|
561
|
+
if (!timeRemaining || !auth.user?.expires_in) return 100;
|
|
562
|
+
return timeRemaining / auth.user.expires_in * 100;
|
|
563
|
+
}, [timeRemaining, auth.user]);
|
|
564
|
+
if (!auth.isAuthenticated || timeRemaining === null) {
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
567
|
+
const baseClass = "kc-session-monitor";
|
|
568
|
+
const positionClass = `kc-session-monitor--${position}`;
|
|
569
|
+
const statusClass = `kc-session-monitor--${getStatusClass()}`;
|
|
570
|
+
const minimizedClass = minimized ? "kc-session-monitor--minimized" : "";
|
|
571
|
+
const classes = `${baseClass} ${positionClass} ${statusClass} ${minimizedClass} ${className}`.trim();
|
|
572
|
+
const progress = getProgress();
|
|
573
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classes, style, children: [
|
|
574
|
+
minimizable && /* @__PURE__ */ jsxRuntime.jsx(
|
|
575
|
+
"button",
|
|
576
|
+
{
|
|
577
|
+
className: "kc-session-monitor__toggle",
|
|
578
|
+
onClick: () => setMinimized(!minimized),
|
|
579
|
+
"aria-label": minimized ? "Expand session monitor" : "Minimize session monitor",
|
|
580
|
+
title: minimized ? "Expand" : "Minimize",
|
|
581
|
+
children: minimized ? "\u25B2" : "\u25BC"
|
|
582
|
+
}
|
|
583
|
+
),
|
|
584
|
+
!minimized && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
585
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "kc-session-monitor__content", children: [
|
|
586
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "kc-session-monitor__label", children: timeRemaining === 0 ? "Session expired" : "Session expires in:" }),
|
|
587
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "kc-session-monitor__time", children: formatTime(timeRemaining) })
|
|
588
|
+
] }),
|
|
589
|
+
showProgressBar && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "kc-session-monitor__progress-bar", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
590
|
+
"div",
|
|
591
|
+
{
|
|
592
|
+
className: "kc-session-monitor__progress-fill",
|
|
593
|
+
style: { width: `${progress}%` },
|
|
594
|
+
role: "progressbar",
|
|
595
|
+
"aria-valuenow": progress,
|
|
596
|
+
"aria-valuemin": 0,
|
|
597
|
+
"aria-valuemax": 100
|
|
598
|
+
}
|
|
599
|
+
) })
|
|
600
|
+
] })
|
|
601
|
+
] });
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
// src/config/presets.ts
|
|
605
|
+
var CONFIG_PRESETS = {
|
|
606
|
+
/**
|
|
607
|
+
* Default preset - Balanced configuration for most applications
|
|
608
|
+
*
|
|
609
|
+
* Features:
|
|
610
|
+
* - 30-day refresh token lifetime
|
|
611
|
+
* - Proactive token refresh on startup + reactive on 401
|
|
612
|
+
* - Silent background token refresh
|
|
613
|
+
* - Session monitoring enabled
|
|
614
|
+
* - Auto token refresh enabled
|
|
615
|
+
*
|
|
616
|
+
* Best for: Most web applications, dashboards, internal tools
|
|
617
|
+
*/
|
|
618
|
+
default: {
|
|
619
|
+
scope: "openid profile email offline_access",
|
|
620
|
+
features: {
|
|
621
|
+
autoRefresh: true,
|
|
622
|
+
sessionMonitor: true,
|
|
623
|
+
deviceFlow: false
|
|
624
|
+
},
|
|
625
|
+
silentRenew: {
|
|
626
|
+
enabled: true,
|
|
627
|
+
thresholdSeconds: 300,
|
|
628
|
+
// 5 minutes
|
|
629
|
+
refreshStrategy: "both",
|
|
630
|
+
maxRefreshTokenLifetime: 2592e3,
|
|
631
|
+
// 30 days
|
|
632
|
+
showLoadingIndicator: false,
|
|
633
|
+
loadingMessage: "Refreshing session..."
|
|
634
|
+
},
|
|
635
|
+
advanced: {
|
|
636
|
+
responseType: "code",
|
|
637
|
+
codeChallengeMethod: "S256",
|
|
638
|
+
loadUserInfo: true,
|
|
639
|
+
automaticSilentSignIn: false
|
|
640
|
+
}
|
|
641
|
+
},
|
|
642
|
+
/**
|
|
643
|
+
* Mobile/PWA preset - Optimized for mobile and progressive web apps
|
|
644
|
+
*
|
|
645
|
+
* Features:
|
|
646
|
+
* - 30-day refresh token lifetime
|
|
647
|
+
* - Proactive startup refresh (apps close frequently)
|
|
648
|
+
* - Loading indicator during refresh (better UX for app reopening)
|
|
649
|
+
* - Session monitoring enabled
|
|
650
|
+
*
|
|
651
|
+
* Best for: Mobile apps, PWAs, apps that frequently close/reopen
|
|
652
|
+
*/
|
|
653
|
+
mobile: {
|
|
654
|
+
scope: "openid profile email offline_access",
|
|
655
|
+
features: {
|
|
656
|
+
autoRefresh: true,
|
|
657
|
+
sessionMonitor: true,
|
|
658
|
+
deviceFlow: false
|
|
659
|
+
},
|
|
660
|
+
silentRenew: {
|
|
661
|
+
enabled: true,
|
|
662
|
+
thresholdSeconds: 300,
|
|
663
|
+
refreshStrategy: "startup",
|
|
664
|
+
// Focus on startup refresh
|
|
665
|
+
maxRefreshTokenLifetime: 2592e3,
|
|
666
|
+
// 30 days
|
|
667
|
+
showLoadingIndicator: true,
|
|
668
|
+
// Show loading for better UX
|
|
669
|
+
loadingMessage: "Restoring your session..."
|
|
670
|
+
},
|
|
671
|
+
advanced: {
|
|
672
|
+
responseType: "code",
|
|
673
|
+
codeChallengeMethod: "S256",
|
|
674
|
+
loadUserInfo: true,
|
|
675
|
+
automaticSilentSignIn: false
|
|
676
|
+
}
|
|
677
|
+
},
|
|
678
|
+
/**
|
|
679
|
+
* High Security preset - Strict security for sensitive applications
|
|
680
|
+
*
|
|
681
|
+
* Features:
|
|
682
|
+
* - 24-hour forced re-authentication
|
|
683
|
+
* - Proactive + reactive token refresh
|
|
684
|
+
* - 10-minute refresh threshold (earlier refresh)
|
|
685
|
+
* - Silent refresh (no UI hints about security measures)
|
|
686
|
+
* - Session monitoring enabled
|
|
687
|
+
*
|
|
688
|
+
* Best for: Banking, healthcare, admin panels, sensitive data applications
|
|
689
|
+
*/
|
|
690
|
+
"high-security": {
|
|
691
|
+
scope: "openid profile email offline_access",
|
|
692
|
+
features: {
|
|
693
|
+
autoRefresh: true,
|
|
694
|
+
sessionMonitor: true,
|
|
695
|
+
deviceFlow: false
|
|
696
|
+
},
|
|
697
|
+
silentRenew: {
|
|
698
|
+
enabled: true,
|
|
699
|
+
thresholdSeconds: 600,
|
|
700
|
+
// 10 minutes (more aggressive)
|
|
701
|
+
refreshStrategy: "both",
|
|
702
|
+
maxRefreshTokenLifetime: 86400,
|
|
703
|
+
// 24 hours only
|
|
704
|
+
showLoadingIndicator: false,
|
|
705
|
+
// Silent for security
|
|
706
|
+
loadingMessage: "Validating session..."
|
|
707
|
+
},
|
|
708
|
+
advanced: {
|
|
709
|
+
responseType: "code",
|
|
710
|
+
codeChallengeMethod: "S256",
|
|
711
|
+
loadUserInfo: true,
|
|
712
|
+
automaticSilentSignIn: false
|
|
713
|
+
}
|
|
714
|
+
},
|
|
715
|
+
/**
|
|
716
|
+
* Long-Running preset - Optimized for desktop apps and long sessions
|
|
717
|
+
*
|
|
718
|
+
* Features:
|
|
719
|
+
* - 7-day refresh token lifetime
|
|
720
|
+
* - Proactive + reactive token refresh
|
|
721
|
+
* - Silent background refresh
|
|
722
|
+
* - Session monitoring enabled
|
|
723
|
+
*
|
|
724
|
+
* Best for: Desktop applications, monitoring dashboards, always-on tools
|
|
725
|
+
*/
|
|
726
|
+
"long-running": {
|
|
727
|
+
scope: "openid profile email offline_access",
|
|
728
|
+
features: {
|
|
729
|
+
autoRefresh: true,
|
|
730
|
+
sessionMonitor: true,
|
|
731
|
+
deviceFlow: false
|
|
732
|
+
},
|
|
733
|
+
silentRenew: {
|
|
734
|
+
enabled: true,
|
|
735
|
+
thresholdSeconds: 300,
|
|
736
|
+
refreshStrategy: "both",
|
|
737
|
+
maxRefreshTokenLifetime: 604800,
|
|
738
|
+
// 7 days
|
|
739
|
+
showLoadingIndicator: false,
|
|
740
|
+
loadingMessage: "Refreshing session..."
|
|
741
|
+
},
|
|
742
|
+
advanced: {
|
|
743
|
+
responseType: "code",
|
|
744
|
+
codeChallengeMethod: "S256",
|
|
745
|
+
loadUserInfo: true,
|
|
746
|
+
automaticSilentSignIn: false
|
|
747
|
+
}
|
|
748
|
+
},
|
|
749
|
+
/**
|
|
750
|
+
* Development preset - Convenient settings for development
|
|
751
|
+
*
|
|
752
|
+
* Features:
|
|
753
|
+
* - No refresh token lifetime limit (never expires)
|
|
754
|
+
* - Proactive + reactive token refresh
|
|
755
|
+
* - Loading indicators visible (easier debugging)
|
|
756
|
+
* - Session monitoring enabled
|
|
757
|
+
* - More verbose feedback
|
|
758
|
+
*
|
|
759
|
+
* Best for: Local development, testing, debugging
|
|
760
|
+
* WARNING: Do not use in production!
|
|
761
|
+
*/
|
|
762
|
+
development: {
|
|
763
|
+
scope: "openid profile email offline_access",
|
|
764
|
+
features: {
|
|
765
|
+
autoRefresh: true,
|
|
766
|
+
sessionMonitor: true,
|
|
767
|
+
deviceFlow: false
|
|
768
|
+
},
|
|
769
|
+
silentRenew: {
|
|
770
|
+
enabled: true,
|
|
771
|
+
thresholdSeconds: 300,
|
|
772
|
+
refreshStrategy: "both",
|
|
773
|
+
maxRefreshTokenLifetime: 0,
|
|
774
|
+
// No limit (for convenience)
|
|
775
|
+
showLoadingIndicator: true,
|
|
776
|
+
// Show for debugging
|
|
777
|
+
loadingMessage: "[DEV] Refreshing tokens..."
|
|
778
|
+
},
|
|
779
|
+
advanced: {
|
|
780
|
+
responseType: "code",
|
|
781
|
+
codeChallengeMethod: "S256",
|
|
782
|
+
loadUserInfo: true,
|
|
783
|
+
automaticSilentSignIn: false
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
function getConfigPreset(preset) {
|
|
788
|
+
return CONFIG_PRESETS[preset];
|
|
789
|
+
}
|
|
790
|
+
function mergeWithPreset(preset, customConfig) {
|
|
791
|
+
const presetConfig = getConfigPreset(preset);
|
|
792
|
+
return {
|
|
793
|
+
...presetConfig,
|
|
794
|
+
...customConfig,
|
|
795
|
+
features: {
|
|
796
|
+
...presetConfig.features,
|
|
797
|
+
...customConfig.features
|
|
798
|
+
},
|
|
799
|
+
silentRenew: {
|
|
800
|
+
...presetConfig.silentRenew,
|
|
801
|
+
...customConfig.silentRenew
|
|
802
|
+
},
|
|
803
|
+
advanced: {
|
|
804
|
+
...presetConfig.advanced,
|
|
805
|
+
...customConfig.advanced
|
|
806
|
+
},
|
|
807
|
+
events: {
|
|
808
|
+
...presetConfig.events,
|
|
809
|
+
...customConfig.events
|
|
810
|
+
}
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
function isZeroConfig(props) {
|
|
814
|
+
return "authority" in props && "clientId" in props;
|
|
815
|
+
}
|
|
816
|
+
var KeycloakProvider = (props) => {
|
|
817
|
+
const { children, debug = false } = props;
|
|
818
|
+
const config = isZeroConfig(props) ? buildZeroConfig(props) : props.config;
|
|
136
819
|
const oidcConfig = KeycloakConfigBuilder.build(config);
|
|
137
820
|
const log = react.useCallback(
|
|
138
821
|
(level, message, ...args) => {
|
|
139
822
|
if (debug) {
|
|
140
|
-
const prefix = "[
|
|
823
|
+
const prefix = "[KeycloakProvider]";
|
|
141
824
|
switch (level) {
|
|
142
825
|
case "info":
|
|
143
826
|
console.log(prefix, message, ...args);
|
|
@@ -165,11 +848,13 @@ var KeycloakProvider = ({
|
|
|
165
848
|
);
|
|
166
849
|
const onSignoutCallback = react.useCallback(() => {
|
|
167
850
|
log("info", "Sign out successful");
|
|
851
|
+
window.history.replaceState({}, document.title, window.location.pathname);
|
|
168
852
|
config.events?.onLogout?.();
|
|
169
853
|
}, [config.events, log]);
|
|
170
854
|
const onRemoveUser = react.useCallback(() => {
|
|
171
|
-
log("
|
|
855
|
+
log("warn", "User removed from storage - token validation likely failed");
|
|
172
856
|
}, [log]);
|
|
857
|
+
const showSessionMonitor = config.features?.sessionMonitor !== false;
|
|
173
858
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
174
859
|
reactOidcContext.AuthProvider,
|
|
175
860
|
{
|
|
@@ -177,11 +862,26 @@ var KeycloakProvider = ({
|
|
|
177
862
|
onSigninCallback,
|
|
178
863
|
onSignoutCallback,
|
|
179
864
|
onRemoveUser,
|
|
180
|
-
children
|
|
865
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(TokenLifecycleManager, { config, debug, children: [
|
|
866
|
+
showSessionMonitor && /* @__PURE__ */ jsxRuntime.jsx(SessionMonitor, {}),
|
|
867
|
+
children
|
|
868
|
+
] })
|
|
181
869
|
}
|
|
182
870
|
);
|
|
183
871
|
};
|
|
184
|
-
|
|
872
|
+
function buildZeroConfig(props) {
|
|
873
|
+
const { authority, clientId, preset = "default", config: overrides = {} } = props;
|
|
874
|
+
const presetConfig = mergeWithPreset(preset, overrides);
|
|
875
|
+
const fullConfig = {
|
|
876
|
+
...defaultConfig,
|
|
877
|
+
...presetConfig,
|
|
878
|
+
authority,
|
|
879
|
+
clientId,
|
|
880
|
+
redirectUri: presetConfig.redirectUri || (typeof window !== "undefined" ? window.location.origin + "/" : "/"),
|
|
881
|
+
postLogoutRedirectUri: presetConfig.postLogoutRedirectUri || (typeof window !== "undefined" ? window.location.origin + "/" : "/")
|
|
882
|
+
};
|
|
883
|
+
return fullConfig;
|
|
884
|
+
}
|
|
185
885
|
function useRoles() {
|
|
186
886
|
const auth = useAuth();
|
|
187
887
|
const roles = react.useMemo(() => {
|
|
@@ -509,125 +1209,6 @@ function getInitials(name) {
|
|
|
509
1209
|
}
|
|
510
1210
|
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
|
511
1211
|
}
|
|
512
|
-
var SessionMonitor = ({
|
|
513
|
-
position = "top-right",
|
|
514
|
-
warningThreshold = 300,
|
|
515
|
-
// 5 minutes
|
|
516
|
-
criticalThreshold = 60,
|
|
517
|
-
// 1 minute
|
|
518
|
-
showProgressBar = true,
|
|
519
|
-
minimizable = true,
|
|
520
|
-
startMinimized = false,
|
|
521
|
-
className = "",
|
|
522
|
-
onWarning,
|
|
523
|
-
onCritical,
|
|
524
|
-
onExpired,
|
|
525
|
-
style
|
|
526
|
-
}) => {
|
|
527
|
-
const auth = useAuth();
|
|
528
|
-
const [timeRemaining, setTimeRemaining] = react.useState(null);
|
|
529
|
-
const [minimized, setMinimized] = react.useState(startMinimized);
|
|
530
|
-
const [warningTriggered, setWarningTriggered] = react.useState(false);
|
|
531
|
-
const [criticalTriggered, setCriticalTriggered] = react.useState(false);
|
|
532
|
-
react.useEffect(() => {
|
|
533
|
-
if (!auth.isAuthenticated || !auth.user?.expires_at) {
|
|
534
|
-
setTimeRemaining(null);
|
|
535
|
-
return;
|
|
536
|
-
}
|
|
537
|
-
const updateTimeRemaining = () => {
|
|
538
|
-
const expiresAt = auth.user.expires_at;
|
|
539
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
540
|
-
const remaining = expiresAt - now;
|
|
541
|
-
if (remaining <= 0) {
|
|
542
|
-
setTimeRemaining(0);
|
|
543
|
-
onExpired?.();
|
|
544
|
-
setTimeout(() => {
|
|
545
|
-
auth.signoutRedirect();
|
|
546
|
-
}, 2e3);
|
|
547
|
-
} else {
|
|
548
|
-
setTimeRemaining(remaining);
|
|
549
|
-
if (remaining <= criticalThreshold && !criticalTriggered) {
|
|
550
|
-
setCriticalTriggered(true);
|
|
551
|
-
onCritical?.(remaining);
|
|
552
|
-
} else if (remaining <= warningThreshold && !warningTriggered) {
|
|
553
|
-
setWarningTriggered(true);
|
|
554
|
-
onWarning?.(remaining);
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
};
|
|
558
|
-
updateTimeRemaining();
|
|
559
|
-
const interval = setInterval(updateTimeRemaining, 1e3);
|
|
560
|
-
return () => clearInterval(interval);
|
|
561
|
-
}, [
|
|
562
|
-
auth,
|
|
563
|
-
warningThreshold,
|
|
564
|
-
criticalThreshold,
|
|
565
|
-
warningTriggered,
|
|
566
|
-
criticalTriggered,
|
|
567
|
-
onWarning,
|
|
568
|
-
onCritical,
|
|
569
|
-
onExpired
|
|
570
|
-
]);
|
|
571
|
-
react.useEffect(() => {
|
|
572
|
-
if (timeRemaining && timeRemaining > warningThreshold) {
|
|
573
|
-
setWarningTriggered(false);
|
|
574
|
-
setCriticalTriggered(false);
|
|
575
|
-
}
|
|
576
|
-
}, [timeRemaining, warningThreshold]);
|
|
577
|
-
const formatTime = react.useCallback((seconds) => {
|
|
578
|
-
const mins = Math.floor(seconds / 60);
|
|
579
|
-
const secs = seconds % 60;
|
|
580
|
-
return `${mins}:${secs.toString().padStart(2, "0")}`;
|
|
581
|
-
}, []);
|
|
582
|
-
const getStatusClass = react.useCallback(() => {
|
|
583
|
-
if (!timeRemaining) return "normal";
|
|
584
|
-
if (timeRemaining <= criticalThreshold) return "critical";
|
|
585
|
-
if (timeRemaining <= warningThreshold) return "warning";
|
|
586
|
-
return "normal";
|
|
587
|
-
}, [timeRemaining, warningThreshold, criticalThreshold]);
|
|
588
|
-
const getProgress = react.useCallback(() => {
|
|
589
|
-
if (!timeRemaining || !auth.user?.expires_in) return 100;
|
|
590
|
-
return timeRemaining / auth.user.expires_in * 100;
|
|
591
|
-
}, [timeRemaining, auth.user]);
|
|
592
|
-
if (!auth.isAuthenticated || timeRemaining === null) {
|
|
593
|
-
return null;
|
|
594
|
-
}
|
|
595
|
-
const baseClass = "kc-session-monitor";
|
|
596
|
-
const positionClass = `kc-session-monitor--${position}`;
|
|
597
|
-
const statusClass = `kc-session-monitor--${getStatusClass()}`;
|
|
598
|
-
const minimizedClass = minimized ? "kc-session-monitor--minimized" : "";
|
|
599
|
-
const classes = `${baseClass} ${positionClass} ${statusClass} ${minimizedClass} ${className}`.trim();
|
|
600
|
-
const progress = getProgress();
|
|
601
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classes, style, children: [
|
|
602
|
-
minimizable && /* @__PURE__ */ jsxRuntime.jsx(
|
|
603
|
-
"button",
|
|
604
|
-
{
|
|
605
|
-
className: "kc-session-monitor__toggle",
|
|
606
|
-
onClick: () => setMinimized(!minimized),
|
|
607
|
-
"aria-label": minimized ? "Expand session monitor" : "Minimize session monitor",
|
|
608
|
-
title: minimized ? "Expand" : "Minimize",
|
|
609
|
-
children: minimized ? "\u25B2" : "\u25BC"
|
|
610
|
-
}
|
|
611
|
-
),
|
|
612
|
-
!minimized && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
613
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "kc-session-monitor__content", children: [
|
|
614
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "kc-session-monitor__label", children: timeRemaining === 0 ? "Session expired" : "Session expires in:" }),
|
|
615
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "kc-session-monitor__time", children: formatTime(timeRemaining) })
|
|
616
|
-
] }),
|
|
617
|
-
showProgressBar && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "kc-session-monitor__progress-bar", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
618
|
-
"div",
|
|
619
|
-
{
|
|
620
|
-
className: "kc-session-monitor__progress-fill",
|
|
621
|
-
style: { width: `${progress}%` },
|
|
622
|
-
role: "progressbar",
|
|
623
|
-
"aria-valuenow": progress,
|
|
624
|
-
"aria-valuemin": 0,
|
|
625
|
-
"aria-valuemax": 100
|
|
626
|
-
}
|
|
627
|
-
) })
|
|
628
|
-
] })
|
|
629
|
-
] });
|
|
630
|
-
};
|
|
631
1212
|
|
|
632
1213
|
// src/utils/deviceFlow.ts
|
|
633
1214
|
async function requestDeviceCode(apiBaseUrl) {
|
|
@@ -935,6 +1516,7 @@ function useProtectedFetch(options = {}) {
|
|
|
935
1516
|
};
|
|
936
1517
|
}
|
|
937
1518
|
|
|
1519
|
+
exports.CONFIG_PRESETS = CONFIG_PRESETS;
|
|
938
1520
|
exports.DeviceLogin = DeviceLogin;
|
|
939
1521
|
exports.KeycloakConfigBuilder = KeycloakConfigBuilder;
|
|
940
1522
|
exports.KeycloakProvider = KeycloakProvider;
|
|
@@ -942,15 +1524,19 @@ exports.LoginButton = LoginButton;
|
|
|
942
1524
|
exports.LogoutButton = LogoutButton;
|
|
943
1525
|
exports.ProtectedRoute = ProtectedRoute;
|
|
944
1526
|
exports.SessionMonitor = SessionMonitor;
|
|
1527
|
+
exports.TokenLifecycleManager = TokenLifecycleManager;
|
|
945
1528
|
exports.UserProfile = UserProfile;
|
|
946
1529
|
exports.defaultConfig = defaultConfig;
|
|
947
1530
|
exports.fetchUserInfo = fetchUserInfo;
|
|
948
1531
|
exports.getConfigFromEnv = getConfigFromEnv;
|
|
1532
|
+
exports.getConfigPreset = getConfigPreset;
|
|
1533
|
+
exports.mergeWithPreset = mergeWithPreset;
|
|
949
1534
|
exports.pollForToken = pollForToken;
|
|
950
1535
|
exports.requestDeviceCode = requestDeviceCode;
|
|
951
1536
|
exports.useAuth = useAuth;
|
|
952
1537
|
exports.useProtectedFetch = useProtectedFetch;
|
|
953
1538
|
exports.useRoles = useRoles;
|
|
954
1539
|
exports.useScopes = useScopes;
|
|
1540
|
+
exports.useTokenLifecycle = useTokenLifecycle;
|
|
955
1541
|
//# sourceMappingURL=index.js.map
|
|
956
1542
|
//# sourceMappingURL=index.js.map
|