antaeus.keycloak.react 1.0.6
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/LICENSE +21 -0
- package/README.md +652 -0
- package/dist/index.d.mts +1193 -0
- package/dist/index.d.ts +1193 -0
- package/dist/index.js +956 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +937 -0
- package/dist/index.mjs.map +1 -0
- package/dist/styles.css +601 -0
- package/package.json +72 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,937 @@
|
|
|
1
|
+
import { useCallback, useMemo, useEffect, useState, useRef } from 'react';
|
|
2
|
+
import { useAuth as useAuth$1, AuthProvider } from 'react-oidc-context';
|
|
3
|
+
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
4
|
+
import { useLocation } from 'react-router-dom';
|
|
5
|
+
import { QRCodeSVG } from 'qrcode.react';
|
|
6
|
+
import { User } from 'oidc-client-ts';
|
|
7
|
+
|
|
8
|
+
// src/components/KeycloakProvider.tsx
|
|
9
|
+
|
|
10
|
+
// src/config/defaults.ts
|
|
11
|
+
var defaultConfig = {
|
|
12
|
+
redirectUri: typeof window !== "undefined" ? window.location.origin + "/" : "/",
|
|
13
|
+
postLogoutRedirectUri: typeof window !== "undefined" ? window.location.origin + "/" : "/",
|
|
14
|
+
scope: "openid profile email offline_access",
|
|
15
|
+
features: {
|
|
16
|
+
deviceFlow: false,
|
|
17
|
+
sessionMonitor: false,
|
|
18
|
+
autoRefresh: true
|
|
19
|
+
},
|
|
20
|
+
silentRenew: {
|
|
21
|
+
enabled: true,
|
|
22
|
+
silentRedirectUri: typeof window !== "undefined" ? window.location.origin + "/silent-renew.html" : "/silent-renew.html",
|
|
23
|
+
thresholdSeconds: 300
|
|
24
|
+
// 5 minutes
|
|
25
|
+
},
|
|
26
|
+
advanced: {
|
|
27
|
+
responseType: "code",
|
|
28
|
+
codeChallengeMethod: "S256",
|
|
29
|
+
loadUserInfo: true,
|
|
30
|
+
automaticSilentSignIn: false
|
|
31
|
+
},
|
|
32
|
+
events: {}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// src/config/KeycloakConfig.ts
|
|
36
|
+
var KeycloakConfigBuilder = class {
|
|
37
|
+
/**
|
|
38
|
+
* Builds OIDC client configuration from Keycloak config
|
|
39
|
+
*
|
|
40
|
+
* @param userConfig - User-provided Keycloak configuration
|
|
41
|
+
* @returns OIDC client settings ready for react-oidc-context
|
|
42
|
+
* @throws Error if required fields are missing or invalid
|
|
43
|
+
*/
|
|
44
|
+
static build(userConfig) {
|
|
45
|
+
this.validate(userConfig);
|
|
46
|
+
const config = this.mergeWithDefaults(userConfig);
|
|
47
|
+
const oidcSettings = {
|
|
48
|
+
authority: config.authority,
|
|
49
|
+
client_id: config.clientId,
|
|
50
|
+
redirect_uri: config.redirectUri,
|
|
51
|
+
post_logout_redirect_uri: config.postLogoutRedirectUri,
|
|
52
|
+
response_type: config.advanced.responseType,
|
|
53
|
+
scope: config.scope,
|
|
54
|
+
automaticSilentRenew: config.silentRenew.enabled,
|
|
55
|
+
loadUserInfo: config.advanced.loadUserInfo,
|
|
56
|
+
silent_redirect_uri: config.silentRenew.silentRedirectUri
|
|
57
|
+
};
|
|
58
|
+
return oidcSettings;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Validates required configuration fields
|
|
62
|
+
*
|
|
63
|
+
* @param config - Configuration to validate
|
|
64
|
+
* @throws Error if validation fails
|
|
65
|
+
*/
|
|
66
|
+
static validate(config) {
|
|
67
|
+
if (!config.authority) {
|
|
68
|
+
throw new Error("Keycloak authority is required");
|
|
69
|
+
}
|
|
70
|
+
if (!config.clientId) {
|
|
71
|
+
throw new Error("Keycloak clientId is required");
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
new URL(config.authority);
|
|
75
|
+
} catch {
|
|
76
|
+
throw new Error("Keycloak authority must be a valid URL");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Merges user configuration with defaults
|
|
81
|
+
*
|
|
82
|
+
* @param config - User configuration
|
|
83
|
+
* @returns Merged configuration with all defaults applied
|
|
84
|
+
*/
|
|
85
|
+
static mergeWithDefaults(config) {
|
|
86
|
+
return {
|
|
87
|
+
authority: config.authority,
|
|
88
|
+
clientId: config.clientId,
|
|
89
|
+
redirectUri: config.redirectUri ?? defaultConfig.redirectUri,
|
|
90
|
+
postLogoutRedirectUri: config.postLogoutRedirectUri ?? defaultConfig.postLogoutRedirectUri,
|
|
91
|
+
scope: config.scope ?? defaultConfig.scope,
|
|
92
|
+
features: {
|
|
93
|
+
...defaultConfig.features,
|
|
94
|
+
...config.features
|
|
95
|
+
},
|
|
96
|
+
silentRenew: {
|
|
97
|
+
...defaultConfig.silentRenew,
|
|
98
|
+
...config.silentRenew
|
|
99
|
+
},
|
|
100
|
+
advanced: {
|
|
101
|
+
...defaultConfig.advanced,
|
|
102
|
+
...config.advanced
|
|
103
|
+
},
|
|
104
|
+
events: {
|
|
105
|
+
...defaultConfig.events,
|
|
106
|
+
...config.events
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
function getConfigFromEnv() {
|
|
112
|
+
if (typeof import.meta === "undefined") {
|
|
113
|
+
return {};
|
|
114
|
+
}
|
|
115
|
+
const env = import.meta.env;
|
|
116
|
+
if (!env) {
|
|
117
|
+
return {};
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
authority: env.VITE_KEYCLOAK_AUTHORITY,
|
|
121
|
+
clientId: env.VITE_KEYCLOAK_CLIENT_ID,
|
|
122
|
+
features: {
|
|
123
|
+
deviceFlow: env.VITE_ENABLE_DEVICE_FLOW === "true",
|
|
124
|
+
sessionMonitor: env.VITE_ENABLE_SESSION_MONITOR === "true"
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
var KeycloakProvider = ({
|
|
129
|
+
config,
|
|
130
|
+
children,
|
|
131
|
+
debug = false
|
|
132
|
+
}) => {
|
|
133
|
+
const oidcConfig = KeycloakConfigBuilder.build(config);
|
|
134
|
+
const log = useCallback(
|
|
135
|
+
(level, message, ...args) => {
|
|
136
|
+
if (debug) {
|
|
137
|
+
const prefix = "[Keycloak]";
|
|
138
|
+
switch (level) {
|
|
139
|
+
case "info":
|
|
140
|
+
console.log(prefix, message, ...args);
|
|
141
|
+
break;
|
|
142
|
+
case "warn":
|
|
143
|
+
console.warn(prefix, message, ...args);
|
|
144
|
+
break;
|
|
145
|
+
case "error":
|
|
146
|
+
console.error(prefix, message, ...args);
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
[debug]
|
|
152
|
+
);
|
|
153
|
+
const onSigninCallback = useCallback(
|
|
154
|
+
(_user) => {
|
|
155
|
+
log("info", "Sign in successful");
|
|
156
|
+
window.history.replaceState({}, document.title, window.location.pathname);
|
|
157
|
+
if (_user) {
|
|
158
|
+
config.events?.onLogin?.(_user);
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
[config.events, log]
|
|
162
|
+
);
|
|
163
|
+
const onSignoutCallback = useCallback(() => {
|
|
164
|
+
log("info", "Sign out successful");
|
|
165
|
+
config.events?.onLogout?.();
|
|
166
|
+
}, [config.events, log]);
|
|
167
|
+
const onRemoveUser = useCallback(() => {
|
|
168
|
+
log("info", "User removed");
|
|
169
|
+
}, [log]);
|
|
170
|
+
return /* @__PURE__ */ jsx(
|
|
171
|
+
AuthProvider,
|
|
172
|
+
{
|
|
173
|
+
...oidcConfig,
|
|
174
|
+
onSigninCallback,
|
|
175
|
+
onSignoutCallback,
|
|
176
|
+
onRemoveUser,
|
|
177
|
+
children
|
|
178
|
+
}
|
|
179
|
+
);
|
|
180
|
+
};
|
|
181
|
+
var useAuth = useAuth$1;
|
|
182
|
+
function useRoles() {
|
|
183
|
+
const auth = useAuth();
|
|
184
|
+
const roles = useMemo(() => {
|
|
185
|
+
if (!auth.user?.profile) {
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
const realmAccess = auth.user.profile.realm_access;
|
|
189
|
+
return realmAccess?.roles || [];
|
|
190
|
+
}, [auth.user]);
|
|
191
|
+
const hasRole = useMemo(
|
|
192
|
+
() => (role) => {
|
|
193
|
+
return roles.includes(role);
|
|
194
|
+
},
|
|
195
|
+
[roles]
|
|
196
|
+
);
|
|
197
|
+
const hasAnyRole = useMemo(
|
|
198
|
+
() => (requiredRoles) => {
|
|
199
|
+
return requiredRoles.some((role) => roles.includes(role));
|
|
200
|
+
},
|
|
201
|
+
[roles]
|
|
202
|
+
);
|
|
203
|
+
const hasAllRoles = useMemo(
|
|
204
|
+
() => (requiredRoles) => {
|
|
205
|
+
return requiredRoles.every((role) => roles.includes(role));
|
|
206
|
+
},
|
|
207
|
+
[roles]
|
|
208
|
+
);
|
|
209
|
+
const getClientRoles = useMemo(
|
|
210
|
+
() => (clientId) => {
|
|
211
|
+
if (!auth.user?.profile) {
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
214
|
+
const resourceAccess = auth.user.profile.resource_access;
|
|
215
|
+
return resourceAccess?.[clientId]?.roles || [];
|
|
216
|
+
},
|
|
217
|
+
[auth.user]
|
|
218
|
+
);
|
|
219
|
+
return {
|
|
220
|
+
roles,
|
|
221
|
+
hasRole,
|
|
222
|
+
hasAnyRole,
|
|
223
|
+
hasAllRoles,
|
|
224
|
+
getClientRoles
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function useScopes() {
|
|
228
|
+
const auth = useAuth();
|
|
229
|
+
const scopes = useMemo(() => {
|
|
230
|
+
if (!auth.user?.scope) return [];
|
|
231
|
+
return auth.user.scope.split(" ").filter((s) => s.length > 0);
|
|
232
|
+
}, [auth.user]);
|
|
233
|
+
const hasScope = useMemo(
|
|
234
|
+
() => (scope) => {
|
|
235
|
+
return scopes.includes(scope);
|
|
236
|
+
},
|
|
237
|
+
[scopes]
|
|
238
|
+
);
|
|
239
|
+
const hasAnyScope = useMemo(
|
|
240
|
+
() => (requiredScopes) => {
|
|
241
|
+
return requiredScopes.some((scope) => scopes.includes(scope));
|
|
242
|
+
},
|
|
243
|
+
[scopes]
|
|
244
|
+
);
|
|
245
|
+
const hasAllScopes = useMemo(
|
|
246
|
+
() => (requiredScopes) => {
|
|
247
|
+
return requiredScopes.every((scope) => scopes.includes(scope));
|
|
248
|
+
},
|
|
249
|
+
[scopes]
|
|
250
|
+
);
|
|
251
|
+
return {
|
|
252
|
+
scopes,
|
|
253
|
+
hasScope,
|
|
254
|
+
hasAnyScope,
|
|
255
|
+
hasAllScopes
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
var ProtectedRoute = ({
|
|
259
|
+
children,
|
|
260
|
+
requiredRoles = [],
|
|
261
|
+
requireAllRoles = false,
|
|
262
|
+
requiredScopes = [],
|
|
263
|
+
requireAllScopes = false,
|
|
264
|
+
policy,
|
|
265
|
+
loadingComponent,
|
|
266
|
+
unauthorizedComponent,
|
|
267
|
+
onUnauthorized
|
|
268
|
+
}) => {
|
|
269
|
+
const auth = useAuth();
|
|
270
|
+
const { hasAnyRole, hasAllRoles } = useRoles();
|
|
271
|
+
const { hasAnyScope, hasAllScopes } = useScopes();
|
|
272
|
+
const location = useLocation();
|
|
273
|
+
const isAuthenticated = !auth.isLoading && auth.isAuthenticated;
|
|
274
|
+
const policyAccess = policy && auth.user ? policy(auth.user, { location }) : null;
|
|
275
|
+
const roleAccess = requiredRoles.length > 0 ? requireAllRoles ? hasAllRoles(requiredRoles) : hasAnyRole(requiredRoles) : null;
|
|
276
|
+
const scopeAccess = requiredScopes.length > 0 ? requireAllScopes ? hasAllScopes(requiredScopes) : hasAnyScope(requiredScopes) : null;
|
|
277
|
+
const unauthorizedReason = !isAuthenticated ? "not-authenticated" : policyAccess === false ? "policy-denied" : roleAccess === false ? "insufficient-roles" : scopeAccess === false ? "insufficient-scopes" : null;
|
|
278
|
+
useEffect(() => {
|
|
279
|
+
if (unauthorizedReason) {
|
|
280
|
+
onUnauthorized?.(unauthorizedReason);
|
|
281
|
+
}
|
|
282
|
+
}, [unauthorizedReason, onUnauthorized]);
|
|
283
|
+
useEffect(() => {
|
|
284
|
+
if (!auth.isLoading && !auth.isAuthenticated) {
|
|
285
|
+
auth.signinRedirect({
|
|
286
|
+
redirect_uri: window.location.origin + location.pathname + location.search
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}, [auth.isLoading, auth.isAuthenticated, auth, location.pathname, location.search]);
|
|
290
|
+
if (auth.isLoading) {
|
|
291
|
+
return /* @__PURE__ */ jsx(Fragment, { children: loadingComponent || /* @__PURE__ */ jsx(DefaultLoadingComponent, {}) });
|
|
292
|
+
}
|
|
293
|
+
if (!isAuthenticated) {
|
|
294
|
+
return /* @__PURE__ */ jsx(Fragment, { children: loadingComponent || /* @__PURE__ */ jsx(DefaultLoadingComponent, {}) });
|
|
295
|
+
}
|
|
296
|
+
if (unauthorizedReason) {
|
|
297
|
+
return /* @__PURE__ */ jsx(Fragment, { children: unauthorizedComponent || /* @__PURE__ */ jsx(DefaultUnauthorizedComponent, {}) });
|
|
298
|
+
}
|
|
299
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
300
|
+
};
|
|
301
|
+
var DefaultLoadingComponent = () => /* @__PURE__ */ jsxs(
|
|
302
|
+
"div",
|
|
303
|
+
{
|
|
304
|
+
style: {
|
|
305
|
+
display: "flex",
|
|
306
|
+
justifyContent: "center",
|
|
307
|
+
alignItems: "center",
|
|
308
|
+
minHeight: "200px",
|
|
309
|
+
flexDirection: "column",
|
|
310
|
+
gap: "1rem"
|
|
311
|
+
},
|
|
312
|
+
children: [
|
|
313
|
+
/* @__PURE__ */ jsx(
|
|
314
|
+
"div",
|
|
315
|
+
{
|
|
316
|
+
style: {
|
|
317
|
+
border: "3px solid #f3f3f3",
|
|
318
|
+
borderTop: "3px solid #3498db",
|
|
319
|
+
borderRadius: "50%",
|
|
320
|
+
width: "40px",
|
|
321
|
+
height: "40px",
|
|
322
|
+
animation: "spin 1s linear infinite"
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
),
|
|
326
|
+
/* @__PURE__ */ jsx("p", { style: { color: "#666", margin: 0 }, children: "Loading..." }),
|
|
327
|
+
/* @__PURE__ */ jsx("style", { children: `
|
|
328
|
+
@keyframes spin {
|
|
329
|
+
0% { transform: rotate(0deg); }
|
|
330
|
+
100% { transform: rotate(360deg); }
|
|
331
|
+
}
|
|
332
|
+
` })
|
|
333
|
+
]
|
|
334
|
+
}
|
|
335
|
+
);
|
|
336
|
+
var DefaultUnauthorizedComponent = () => /* @__PURE__ */ jsxs(
|
|
337
|
+
"div",
|
|
338
|
+
{
|
|
339
|
+
style: {
|
|
340
|
+
display: "flex",
|
|
341
|
+
justifyContent: "center",
|
|
342
|
+
alignItems: "center",
|
|
343
|
+
minHeight: "200px",
|
|
344
|
+
flexDirection: "column",
|
|
345
|
+
gap: "1rem",
|
|
346
|
+
padding: "2rem",
|
|
347
|
+
textAlign: "center"
|
|
348
|
+
},
|
|
349
|
+
children: [
|
|
350
|
+
/* @__PURE__ */ jsx(
|
|
351
|
+
"div",
|
|
352
|
+
{
|
|
353
|
+
style: {
|
|
354
|
+
fontSize: "4rem",
|
|
355
|
+
color: "#e74c3c"
|
|
356
|
+
},
|
|
357
|
+
children: "\u{1F512}"
|
|
358
|
+
}
|
|
359
|
+
),
|
|
360
|
+
/* @__PURE__ */ jsx("h1", { style: { margin: 0, color: "#333" }, children: "Access Denied" }),
|
|
361
|
+
/* @__PURE__ */ jsx("p", { style: { color: "#666", margin: 0 }, children: "You do not have permission to access this page." })
|
|
362
|
+
]
|
|
363
|
+
}
|
|
364
|
+
);
|
|
365
|
+
var LoginButton = ({
|
|
366
|
+
children = "Login",
|
|
367
|
+
className = "",
|
|
368
|
+
variant = "primary",
|
|
369
|
+
size = "medium",
|
|
370
|
+
showIcon = true,
|
|
371
|
+
onLoginStart,
|
|
372
|
+
style,
|
|
373
|
+
disabled = false
|
|
374
|
+
}) => {
|
|
375
|
+
const auth = useAuth();
|
|
376
|
+
if (auth.isAuthenticated) {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
const handleClick = () => {
|
|
380
|
+
onLoginStart?.();
|
|
381
|
+
auth.signinRedirect();
|
|
382
|
+
};
|
|
383
|
+
const baseClass = "kc-button kc-login-button";
|
|
384
|
+
const variantClass = `kc-button--${variant}`;
|
|
385
|
+
const sizeClass = `kc-button--${size}`;
|
|
386
|
+
const classes = `${baseClass} ${variantClass} ${sizeClass} ${className}`.trim();
|
|
387
|
+
return /* @__PURE__ */ jsxs(
|
|
388
|
+
"button",
|
|
389
|
+
{
|
|
390
|
+
type: "button",
|
|
391
|
+
className: classes,
|
|
392
|
+
onClick: handleClick,
|
|
393
|
+
disabled: disabled || auth.isLoading,
|
|
394
|
+
style,
|
|
395
|
+
children: [
|
|
396
|
+
showIcon && /* @__PURE__ */ jsx("span", { className: "kc-button__icon", children: "\u{1F510}" }),
|
|
397
|
+
/* @__PURE__ */ jsx("span", { className: "kc-button__text", children })
|
|
398
|
+
]
|
|
399
|
+
}
|
|
400
|
+
);
|
|
401
|
+
};
|
|
402
|
+
var LogoutButton = ({
|
|
403
|
+
children = "Logout",
|
|
404
|
+
className = "",
|
|
405
|
+
variant = "secondary",
|
|
406
|
+
size = "medium",
|
|
407
|
+
showIcon = true,
|
|
408
|
+
onLogoutStart,
|
|
409
|
+
style,
|
|
410
|
+
disabled = false
|
|
411
|
+
}) => {
|
|
412
|
+
const auth = useAuth();
|
|
413
|
+
if (!auth.isAuthenticated) {
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
const handleClick = () => {
|
|
417
|
+
onLogoutStart?.();
|
|
418
|
+
auth.signoutRedirect();
|
|
419
|
+
};
|
|
420
|
+
const baseClass = "kc-button kc-logout-button";
|
|
421
|
+
const variantClass = `kc-button--${variant}`;
|
|
422
|
+
const sizeClass = `kc-button--${size}`;
|
|
423
|
+
const classes = `${baseClass} ${variantClass} ${sizeClass} ${className}`.trim();
|
|
424
|
+
return /* @__PURE__ */ jsxs(
|
|
425
|
+
"button",
|
|
426
|
+
{
|
|
427
|
+
type: "button",
|
|
428
|
+
className: classes,
|
|
429
|
+
onClick: handleClick,
|
|
430
|
+
disabled: disabled || auth.isLoading,
|
|
431
|
+
style,
|
|
432
|
+
children: [
|
|
433
|
+
showIcon && /* @__PURE__ */ jsx("span", { className: "kc-button__icon", children: "\u{1F6AA}" }),
|
|
434
|
+
/* @__PURE__ */ jsx("span", { className: "kc-button__text", children })
|
|
435
|
+
]
|
|
436
|
+
}
|
|
437
|
+
);
|
|
438
|
+
};
|
|
439
|
+
var UserProfile = ({
|
|
440
|
+
className = "",
|
|
441
|
+
showAvatar = true,
|
|
442
|
+
showEmail = true,
|
|
443
|
+
showRoles = false,
|
|
444
|
+
avatarSize = 40,
|
|
445
|
+
style,
|
|
446
|
+
onClick
|
|
447
|
+
}) => {
|
|
448
|
+
const auth = useAuth();
|
|
449
|
+
const { roles } = useRoles();
|
|
450
|
+
if (!auth.isAuthenticated || !auth.user) {
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
const profile = auth.user.profile;
|
|
454
|
+
const name = profile.name || profile.preferred_username || "User";
|
|
455
|
+
const email = profile.email;
|
|
456
|
+
const initials = getInitials(name);
|
|
457
|
+
const baseClass = "kc-user-profile";
|
|
458
|
+
const clickableClass = onClick ? "kc-user-profile--clickable" : "";
|
|
459
|
+
const classes = `${baseClass} ${clickableClass} ${className}`.trim();
|
|
460
|
+
const handleClick = () => {
|
|
461
|
+
if (onClick) {
|
|
462
|
+
onClick();
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
return /* @__PURE__ */ jsxs(
|
|
466
|
+
"div",
|
|
467
|
+
{
|
|
468
|
+
className: classes,
|
|
469
|
+
style,
|
|
470
|
+
onClick: handleClick,
|
|
471
|
+
role: onClick ? "button" : void 0,
|
|
472
|
+
tabIndex: onClick ? 0 : void 0,
|
|
473
|
+
onKeyPress: onClick ? (e) => e.key === "Enter" && handleClick() : void 0,
|
|
474
|
+
children: [
|
|
475
|
+
showAvatar && /* @__PURE__ */ jsx(
|
|
476
|
+
"div",
|
|
477
|
+
{
|
|
478
|
+
className: "kc-user-profile__avatar",
|
|
479
|
+
style: {
|
|
480
|
+
width: avatarSize,
|
|
481
|
+
height: avatarSize,
|
|
482
|
+
fontSize: avatarSize * 0.4
|
|
483
|
+
},
|
|
484
|
+
children: initials
|
|
485
|
+
}
|
|
486
|
+
),
|
|
487
|
+
/* @__PURE__ */ jsxs("div", { className: "kc-user-profile__info", children: [
|
|
488
|
+
/* @__PURE__ */ jsx("div", { className: "kc-user-profile__name", children: name }),
|
|
489
|
+
showEmail && email && /* @__PURE__ */ jsx("div", { className: "kc-user-profile__email", children: email }),
|
|
490
|
+
showRoles && roles.length > 0 && /* @__PURE__ */ jsxs("div", { className: "kc-user-profile__roles", children: [
|
|
491
|
+
roles.slice(0, 3).map((role) => /* @__PURE__ */ jsx("span", { className: "kc-user-profile__role-badge", children: role }, role)),
|
|
492
|
+
roles.length > 3 && /* @__PURE__ */ jsxs("span", { className: "kc-user-profile__role-badge", children: [
|
|
493
|
+
"+",
|
|
494
|
+
roles.length - 3
|
|
495
|
+
] })
|
|
496
|
+
] })
|
|
497
|
+
] })
|
|
498
|
+
]
|
|
499
|
+
}
|
|
500
|
+
);
|
|
501
|
+
};
|
|
502
|
+
function getInitials(name) {
|
|
503
|
+
const parts = name.trim().split(" ");
|
|
504
|
+
if (parts.length === 1) {
|
|
505
|
+
return parts[0].substring(0, 2).toUpperCase();
|
|
506
|
+
}
|
|
507
|
+
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
|
508
|
+
}
|
|
509
|
+
var SessionMonitor = ({
|
|
510
|
+
position = "top-right",
|
|
511
|
+
warningThreshold = 300,
|
|
512
|
+
// 5 minutes
|
|
513
|
+
criticalThreshold = 60,
|
|
514
|
+
// 1 minute
|
|
515
|
+
showProgressBar = true,
|
|
516
|
+
minimizable = true,
|
|
517
|
+
startMinimized = false,
|
|
518
|
+
className = "",
|
|
519
|
+
onWarning,
|
|
520
|
+
onCritical,
|
|
521
|
+
onExpired,
|
|
522
|
+
style
|
|
523
|
+
}) => {
|
|
524
|
+
const auth = useAuth();
|
|
525
|
+
const [timeRemaining, setTimeRemaining] = useState(null);
|
|
526
|
+
const [minimized, setMinimized] = useState(startMinimized);
|
|
527
|
+
const [warningTriggered, setWarningTriggered] = useState(false);
|
|
528
|
+
const [criticalTriggered, setCriticalTriggered] = useState(false);
|
|
529
|
+
useEffect(() => {
|
|
530
|
+
if (!auth.isAuthenticated || !auth.user?.expires_at) {
|
|
531
|
+
setTimeRemaining(null);
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
const updateTimeRemaining = () => {
|
|
535
|
+
const expiresAt = auth.user.expires_at;
|
|
536
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
537
|
+
const remaining = expiresAt - now;
|
|
538
|
+
if (remaining <= 0) {
|
|
539
|
+
setTimeRemaining(0);
|
|
540
|
+
onExpired?.();
|
|
541
|
+
setTimeout(() => {
|
|
542
|
+
auth.signoutRedirect();
|
|
543
|
+
}, 2e3);
|
|
544
|
+
} else {
|
|
545
|
+
setTimeRemaining(remaining);
|
|
546
|
+
if (remaining <= criticalThreshold && !criticalTriggered) {
|
|
547
|
+
setCriticalTriggered(true);
|
|
548
|
+
onCritical?.(remaining);
|
|
549
|
+
} else if (remaining <= warningThreshold && !warningTriggered) {
|
|
550
|
+
setWarningTriggered(true);
|
|
551
|
+
onWarning?.(remaining);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
updateTimeRemaining();
|
|
556
|
+
const interval = setInterval(updateTimeRemaining, 1e3);
|
|
557
|
+
return () => clearInterval(interval);
|
|
558
|
+
}, [
|
|
559
|
+
auth,
|
|
560
|
+
warningThreshold,
|
|
561
|
+
criticalThreshold,
|
|
562
|
+
warningTriggered,
|
|
563
|
+
criticalTriggered,
|
|
564
|
+
onWarning,
|
|
565
|
+
onCritical,
|
|
566
|
+
onExpired
|
|
567
|
+
]);
|
|
568
|
+
useEffect(() => {
|
|
569
|
+
if (timeRemaining && timeRemaining > warningThreshold) {
|
|
570
|
+
setWarningTriggered(false);
|
|
571
|
+
setCriticalTriggered(false);
|
|
572
|
+
}
|
|
573
|
+
}, [timeRemaining, warningThreshold]);
|
|
574
|
+
const formatTime = useCallback((seconds) => {
|
|
575
|
+
const mins = Math.floor(seconds / 60);
|
|
576
|
+
const secs = seconds % 60;
|
|
577
|
+
return `${mins}:${secs.toString().padStart(2, "0")}`;
|
|
578
|
+
}, []);
|
|
579
|
+
const getStatusClass = useCallback(() => {
|
|
580
|
+
if (!timeRemaining) return "normal";
|
|
581
|
+
if (timeRemaining <= criticalThreshold) return "critical";
|
|
582
|
+
if (timeRemaining <= warningThreshold) return "warning";
|
|
583
|
+
return "normal";
|
|
584
|
+
}, [timeRemaining, warningThreshold, criticalThreshold]);
|
|
585
|
+
const getProgress = useCallback(() => {
|
|
586
|
+
if (!timeRemaining || !auth.user?.expires_in) return 100;
|
|
587
|
+
return timeRemaining / auth.user.expires_in * 100;
|
|
588
|
+
}, [timeRemaining, auth.user]);
|
|
589
|
+
if (!auth.isAuthenticated || timeRemaining === null) {
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
const baseClass = "kc-session-monitor";
|
|
593
|
+
const positionClass = `kc-session-monitor--${position}`;
|
|
594
|
+
const statusClass = `kc-session-monitor--${getStatusClass()}`;
|
|
595
|
+
const minimizedClass = minimized ? "kc-session-monitor--minimized" : "";
|
|
596
|
+
const classes = `${baseClass} ${positionClass} ${statusClass} ${minimizedClass} ${className}`.trim();
|
|
597
|
+
const progress = getProgress();
|
|
598
|
+
return /* @__PURE__ */ jsxs("div", { className: classes, style, children: [
|
|
599
|
+
minimizable && /* @__PURE__ */ jsx(
|
|
600
|
+
"button",
|
|
601
|
+
{
|
|
602
|
+
className: "kc-session-monitor__toggle",
|
|
603
|
+
onClick: () => setMinimized(!minimized),
|
|
604
|
+
"aria-label": minimized ? "Expand session monitor" : "Minimize session monitor",
|
|
605
|
+
title: minimized ? "Expand" : "Minimize",
|
|
606
|
+
children: minimized ? "\u25B2" : "\u25BC"
|
|
607
|
+
}
|
|
608
|
+
),
|
|
609
|
+
!minimized && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
610
|
+
/* @__PURE__ */ jsxs("div", { className: "kc-session-monitor__content", children: [
|
|
611
|
+
/* @__PURE__ */ jsx("span", { className: "kc-session-monitor__label", children: timeRemaining === 0 ? "Session expired" : "Session expires in:" }),
|
|
612
|
+
/* @__PURE__ */ jsx("span", { className: "kc-session-monitor__time", children: formatTime(timeRemaining) })
|
|
613
|
+
] }),
|
|
614
|
+
showProgressBar && /* @__PURE__ */ jsx("div", { className: "kc-session-monitor__progress-bar", children: /* @__PURE__ */ jsx(
|
|
615
|
+
"div",
|
|
616
|
+
{
|
|
617
|
+
className: "kc-session-monitor__progress-fill",
|
|
618
|
+
style: { width: `${progress}%` },
|
|
619
|
+
role: "progressbar",
|
|
620
|
+
"aria-valuenow": progress,
|
|
621
|
+
"aria-valuemin": 0,
|
|
622
|
+
"aria-valuemax": 100
|
|
623
|
+
}
|
|
624
|
+
) })
|
|
625
|
+
] })
|
|
626
|
+
] });
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
// src/utils/deviceFlow.ts
|
|
630
|
+
async function requestDeviceCode(apiBaseUrl) {
|
|
631
|
+
const url = `${apiBaseUrl}/api/keycloak/device/authorize`;
|
|
632
|
+
try {
|
|
633
|
+
const response = await fetch(url, {
|
|
634
|
+
method: "POST",
|
|
635
|
+
headers: {
|
|
636
|
+
"Content-Type": "application/json"
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
if (!response.ok) {
|
|
640
|
+
const errorText = await response.text();
|
|
641
|
+
throw new Error(
|
|
642
|
+
`Device authorization failed: ${response.status} ${response.statusText}. ${errorText}`
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
const data = await response.json();
|
|
646
|
+
return data;
|
|
647
|
+
} catch (error) {
|
|
648
|
+
if (error instanceof Error) {
|
|
649
|
+
throw new Error(`Failed to request device code: ${error.message}`);
|
|
650
|
+
}
|
|
651
|
+
throw error;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
async function pollForToken(apiBaseUrl, deviceCode) {
|
|
655
|
+
const url = `${apiBaseUrl}/api/keycloak/device/token`;
|
|
656
|
+
try {
|
|
657
|
+
const response = await fetch(url, {
|
|
658
|
+
method: "POST",
|
|
659
|
+
headers: {
|
|
660
|
+
"Content-Type": "application/json"
|
|
661
|
+
},
|
|
662
|
+
body: JSON.stringify({ device_code: deviceCode })
|
|
663
|
+
});
|
|
664
|
+
const data = await response.json();
|
|
665
|
+
if ("error" in data) {
|
|
666
|
+
const errorData = data;
|
|
667
|
+
if (errorData.error === "authorization_pending" || errorData.error === "slow_down") {
|
|
668
|
+
return null;
|
|
669
|
+
}
|
|
670
|
+
throw new Error(
|
|
671
|
+
errorData.error_description || errorData.error || "Token polling failed"
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
if (!response.ok) {
|
|
675
|
+
throw new Error(`Token polling failed with status ${response.status}`);
|
|
676
|
+
}
|
|
677
|
+
return data;
|
|
678
|
+
} catch (error) {
|
|
679
|
+
if (error instanceof Error) {
|
|
680
|
+
throw error;
|
|
681
|
+
}
|
|
682
|
+
throw new Error("Token polling failed");
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
async function fetchUserInfo(apiBaseUrl, accessToken) {
|
|
686
|
+
const url = `${apiBaseUrl}/api/keycloak/device/userinfo`;
|
|
687
|
+
try {
|
|
688
|
+
const response = await fetch(url, {
|
|
689
|
+
method: "POST",
|
|
690
|
+
headers: {
|
|
691
|
+
"Content-Type": "application/json"
|
|
692
|
+
},
|
|
693
|
+
body: JSON.stringify({ access_token: accessToken })
|
|
694
|
+
});
|
|
695
|
+
if (!response.ok) {
|
|
696
|
+
throw new Error(`Failed to fetch user info: ${response.statusText}`);
|
|
697
|
+
}
|
|
698
|
+
return await response.json();
|
|
699
|
+
} catch (error) {
|
|
700
|
+
if (error instanceof Error) {
|
|
701
|
+
throw new Error(`Failed to fetch user info: ${error.message}`);
|
|
702
|
+
}
|
|
703
|
+
throw error;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
var DeviceLogin = ({
|
|
707
|
+
apiBaseUrl,
|
|
708
|
+
qrCodeSize = 256,
|
|
709
|
+
pollingInterval = 5,
|
|
710
|
+
className = "",
|
|
711
|
+
onSuccess,
|
|
712
|
+
onError,
|
|
713
|
+
style
|
|
714
|
+
}) => {
|
|
715
|
+
const auth = useAuth();
|
|
716
|
+
const [deviceData, setDeviceData] = useState(null);
|
|
717
|
+
const [error, setError] = useState(null);
|
|
718
|
+
const [loading, setLoading] = useState(true);
|
|
719
|
+
const [timeRemaining, setTimeRemaining] = useState(null);
|
|
720
|
+
const pollTimerRef = useRef(null);
|
|
721
|
+
const userManagerRef = useRef(null);
|
|
722
|
+
useEffect(() => {
|
|
723
|
+
const initDeviceFlow = async () => {
|
|
724
|
+
setLoading(true);
|
|
725
|
+
setError(null);
|
|
726
|
+
try {
|
|
727
|
+
const response = await requestDeviceCode(apiBaseUrl);
|
|
728
|
+
setDeviceData(response);
|
|
729
|
+
setTimeRemaining(response.expires_in);
|
|
730
|
+
} catch (err) {
|
|
731
|
+
const error2 = err;
|
|
732
|
+
setError(error2.message);
|
|
733
|
+
onError?.(error2);
|
|
734
|
+
} finally {
|
|
735
|
+
setLoading(false);
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
initDeviceFlow();
|
|
739
|
+
}, [apiBaseUrl, onError]);
|
|
740
|
+
useEffect(() => {
|
|
741
|
+
if (!deviceData || timeRemaining === null) return;
|
|
742
|
+
const interval = setInterval(() => {
|
|
743
|
+
setTimeRemaining((prev) => {
|
|
744
|
+
if (prev === null || prev <= 1) {
|
|
745
|
+
clearInterval(interval);
|
|
746
|
+
setError("Device code expired. Please refresh to try again.");
|
|
747
|
+
return 0;
|
|
748
|
+
}
|
|
749
|
+
return prev - 1;
|
|
750
|
+
});
|
|
751
|
+
}, 1e3);
|
|
752
|
+
return () => clearInterval(interval);
|
|
753
|
+
}, [deviceData, timeRemaining]);
|
|
754
|
+
useEffect(() => {
|
|
755
|
+
if (!deviceData) return;
|
|
756
|
+
userManagerRef.current = auth.userManager;
|
|
757
|
+
const poll = async () => {
|
|
758
|
+
try {
|
|
759
|
+
const tokenResponse = await pollForToken(apiBaseUrl, deviceData.device_code);
|
|
760
|
+
if (tokenResponse) {
|
|
761
|
+
if (pollTimerRef.current) {
|
|
762
|
+
clearInterval(pollTimerRef.current);
|
|
763
|
+
}
|
|
764
|
+
const userInfo = await fetchUserInfo(apiBaseUrl, tokenResponse.access_token);
|
|
765
|
+
const user = new User({
|
|
766
|
+
id_token: tokenResponse.id_token || "",
|
|
767
|
+
access_token: tokenResponse.access_token,
|
|
768
|
+
token_type: tokenResponse.token_type,
|
|
769
|
+
profile: userInfo,
|
|
770
|
+
expires_at: Math.floor(Date.now() / 1e3) + tokenResponse.expires_in,
|
|
771
|
+
scope: tokenResponse.scope,
|
|
772
|
+
refresh_token: tokenResponse.refresh_token
|
|
773
|
+
});
|
|
774
|
+
if (userManagerRef.current) {
|
|
775
|
+
await userManagerRef.current.storeUser(user);
|
|
776
|
+
}
|
|
777
|
+
onSuccess?.(user);
|
|
778
|
+
}
|
|
779
|
+
} catch (err) {
|
|
780
|
+
if (!err.message.includes("authorization_pending")) {
|
|
781
|
+
if (pollTimerRef.current) {
|
|
782
|
+
clearInterval(pollTimerRef.current);
|
|
783
|
+
}
|
|
784
|
+
const error2 = err;
|
|
785
|
+
setError(error2.message);
|
|
786
|
+
onError?.(error2);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
pollTimerRef.current = setInterval(poll, pollingInterval * 1e3);
|
|
791
|
+
return () => {
|
|
792
|
+
if (pollTimerRef.current) {
|
|
793
|
+
clearInterval(pollTimerRef.current);
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
}, [deviceData, apiBaseUrl, pollingInterval, onSuccess, onError, auth]);
|
|
797
|
+
if (loading) {
|
|
798
|
+
return /* @__PURE__ */ jsxs("div", { className: `kc-device-login kc-device-login--loading ${className}`, style, children: [
|
|
799
|
+
/* @__PURE__ */ jsx("div", { className: "kc-device-login__spinner" }),
|
|
800
|
+
/* @__PURE__ */ jsx("p", { children: "Initializing device flow..." })
|
|
801
|
+
] });
|
|
802
|
+
}
|
|
803
|
+
if (error) {
|
|
804
|
+
return /* @__PURE__ */ jsxs("div", { className: `kc-device-login kc-device-login--error ${className}`, style, children: [
|
|
805
|
+
/* @__PURE__ */ jsx("div", { className: "kc-device-login__error-icon", children: "\u26A0\uFE0F" }),
|
|
806
|
+
/* @__PURE__ */ jsx("h2", { children: "Error" }),
|
|
807
|
+
/* @__PURE__ */ jsx("p", { children: error }),
|
|
808
|
+
/* @__PURE__ */ jsx(
|
|
809
|
+
"button",
|
|
810
|
+
{
|
|
811
|
+
className: "kc-button kc-button--primary",
|
|
812
|
+
onClick: () => window.location.reload(),
|
|
813
|
+
children: "Retry"
|
|
814
|
+
}
|
|
815
|
+
)
|
|
816
|
+
] });
|
|
817
|
+
}
|
|
818
|
+
if (!deviceData) {
|
|
819
|
+
return null;
|
|
820
|
+
}
|
|
821
|
+
const formatTime = (seconds) => {
|
|
822
|
+
const mins = Math.floor(seconds / 60);
|
|
823
|
+
const secs = seconds % 60;
|
|
824
|
+
return `${mins}:${secs.toString().padStart(2, "0")}`;
|
|
825
|
+
};
|
|
826
|
+
return /* @__PURE__ */ jsxs("div", { className: `kc-device-login ${className}`, style, children: [
|
|
827
|
+
/* @__PURE__ */ jsx("h2", { className: "kc-device-login__title", children: "Device Login" }),
|
|
828
|
+
/* @__PURE__ */ jsx("div", { className: "kc-device-login__qr", children: /* @__PURE__ */ jsx(QRCodeSVG, { value: deviceData.verification_uri_complete, size: qrCodeSize }) }),
|
|
829
|
+
/* @__PURE__ */ jsxs("div", { className: "kc-device-login__instructions", children: [
|
|
830
|
+
/* @__PURE__ */ jsx("p", { children: "Scan the QR code with your phone or visit:" }),
|
|
831
|
+
/* @__PURE__ */ jsx("code", { className: "kc-device-login__url", children: deviceData.verification_uri }),
|
|
832
|
+
/* @__PURE__ */ jsx("p", { children: "And enter code:" }),
|
|
833
|
+
/* @__PURE__ */ jsx("strong", { className: "kc-device-login__user-code", children: deviceData.user_code })
|
|
834
|
+
] }),
|
|
835
|
+
timeRemaining !== null && timeRemaining > 0 && /* @__PURE__ */ jsxs("p", { className: "kc-device-login__expiry", children: [
|
|
836
|
+
"Code expires in ",
|
|
837
|
+
formatTime(timeRemaining)
|
|
838
|
+
] }),
|
|
839
|
+
/* @__PURE__ */ jsxs("div", { className: "kc-device-login__status", children: [
|
|
840
|
+
/* @__PURE__ */ jsx("div", { className: "kc-device-login__spinner kc-device-login__spinner--small" }),
|
|
841
|
+
/* @__PURE__ */ jsx("p", { children: "Waiting for authorization..." })
|
|
842
|
+
] })
|
|
843
|
+
] });
|
|
844
|
+
};
|
|
845
|
+
function useProtectedFetch(options = {}) {
|
|
846
|
+
const auth = useAuth();
|
|
847
|
+
const {
|
|
848
|
+
baseUrl = "",
|
|
849
|
+
onUnauthorized,
|
|
850
|
+
retryOn401 = true,
|
|
851
|
+
headers: customHeaders = {}
|
|
852
|
+
} = options;
|
|
853
|
+
const buildUrl = useCallback(
|
|
854
|
+
(url) => {
|
|
855
|
+
if (url.startsWith("http://") || url.startsWith("https://")) {
|
|
856
|
+
return url;
|
|
857
|
+
}
|
|
858
|
+
const base = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
859
|
+
const path = url.startsWith("/") ? url : `/${url}`;
|
|
860
|
+
return base + path;
|
|
861
|
+
},
|
|
862
|
+
[baseUrl]
|
|
863
|
+
);
|
|
864
|
+
const getAuthHeader = useCallback(() => {
|
|
865
|
+
if (!auth.user?.access_token) {
|
|
866
|
+
return {};
|
|
867
|
+
}
|
|
868
|
+
return {
|
|
869
|
+
Authorization: `Bearer ${auth.user.access_token}`
|
|
870
|
+
};
|
|
871
|
+
}, [auth.user]);
|
|
872
|
+
const buildHeaders = useCallback(
|
|
873
|
+
(requestHeaders) => {
|
|
874
|
+
const headers = new Headers(requestHeaders);
|
|
875
|
+
const authHeader = getAuthHeader();
|
|
876
|
+
if (authHeader.Authorization) {
|
|
877
|
+
headers.set("Authorization", authHeader.Authorization);
|
|
878
|
+
}
|
|
879
|
+
Object.entries(customHeaders).forEach(([key, value]) => {
|
|
880
|
+
headers.set(key, value);
|
|
881
|
+
});
|
|
882
|
+
return headers;
|
|
883
|
+
},
|
|
884
|
+
[getAuthHeader, customHeaders]
|
|
885
|
+
);
|
|
886
|
+
const protectedFetch = useCallback(
|
|
887
|
+
async (url, init) => {
|
|
888
|
+
const fullUrl = buildUrl(url);
|
|
889
|
+
const headers = buildHeaders(init?.headers);
|
|
890
|
+
const response = await fetch(fullUrl, {
|
|
891
|
+
...init,
|
|
892
|
+
headers
|
|
893
|
+
});
|
|
894
|
+
if (response.status === 401) {
|
|
895
|
+
onUnauthorized?.();
|
|
896
|
+
if (retryOn401 && auth.user) {
|
|
897
|
+
try {
|
|
898
|
+
await auth.signinSilent();
|
|
899
|
+
const newHeaders = buildHeaders(init?.headers);
|
|
900
|
+
const retryResponse = await fetch(fullUrl, {
|
|
901
|
+
...init,
|
|
902
|
+
headers: newHeaders
|
|
903
|
+
});
|
|
904
|
+
return retryResponse;
|
|
905
|
+
} catch (error) {
|
|
906
|
+
return response;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
return response;
|
|
911
|
+
},
|
|
912
|
+
[buildUrl, buildHeaders, onUnauthorized, retryOn401, auth]
|
|
913
|
+
);
|
|
914
|
+
const fetchJson = useCallback(
|
|
915
|
+
async (url, init) => {
|
|
916
|
+
const response = await protectedFetch(url, init);
|
|
917
|
+
if (!response.ok) {
|
|
918
|
+
const errorText = await response.text();
|
|
919
|
+
throw new Error(
|
|
920
|
+
`HTTP ${response.status}: ${response.statusText}. ${errorText}`
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
const data = await response.json();
|
|
924
|
+
return data;
|
|
925
|
+
},
|
|
926
|
+
[protectedFetch]
|
|
927
|
+
);
|
|
928
|
+
return {
|
|
929
|
+
fetchJson,
|
|
930
|
+
fetch: protectedFetch,
|
|
931
|
+
isLoading: auth.isLoading
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
export { DeviceLogin, KeycloakConfigBuilder, KeycloakProvider, LoginButton, LogoutButton, ProtectedRoute, SessionMonitor, UserProfile, defaultConfig, fetchUserInfo, getConfigFromEnv, pollForToken, requestDeviceCode, useAuth, useProtectedFetch, useRoles, useScopes };
|
|
936
|
+
//# sourceMappingURL=index.mjs.map
|
|
937
|
+
//# sourceMappingURL=index.mjs.map
|