@tidecloak/react 0.13.11 → 0.13.14
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 +18 -298
- package/dist/cjs/components/AuthCallback.js +90 -0
- package/dist/cjs/contexts/TideCloakContextProvider.js +534 -73
- package/dist/cjs/hooks/useAuthCallback.js +161 -0
- package/dist/cjs/index.js +81 -2
- package/dist/esm/components/AuthCallback.js +90 -0
- package/dist/esm/contexts/TideCloakContextProvider.js +534 -73
- package/dist/esm/hooks/useAuthCallback.js +161 -0
- package/dist/esm/index.js +81 -2
- package/dist/types/components/AuthCallback.d.ts +90 -0
- package/dist/types/components/AuthCallback.d.ts.map +1 -0
- package/dist/types/contexts/TideCloakContextProvider.d.ts +92 -6
- package/dist/types/contexts/TideCloakContextProvider.d.ts.map +1 -1
- package/dist/types/hooks/useAuthCallback.d.ts +80 -0
- package/dist/types/hooks/useAuthCallback.d.ts.map +1 -0
- package/dist/types/index.d.ts +63 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -1,28 +1,133 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import React from "react";
|
|
3
|
-
import { IAMService } from '@tidecloak/js';
|
|
3
|
+
import { IAMService, AdminAPI } from '@tidecloak/js';
|
|
4
4
|
const TideCloakContext = React.createContext(undefined);
|
|
5
|
-
export function TideCloakContextProvider({ config, children }) {
|
|
5
|
+
export function TideCloakContextProvider({ config: configProp, configUrl = '/adapter.json', authMode, adapter, children, onAuthSuccess, onAuthError, onLogout, onReauthRequired, onActionNotification }) {
|
|
6
|
+
// Bootstrap state
|
|
6
7
|
const [isInitializing, setIsInitializing] = React.useState(true);
|
|
7
8
|
const [initError, setInitError] = React.useState(null);
|
|
9
|
+
// Session state
|
|
8
10
|
const [authenticated, setAuthenticated] = React.useState(false);
|
|
9
11
|
const [sessionExpired, setSessionExpired] = React.useState(false);
|
|
10
12
|
const [isRefreshing, setIsRefreshing] = React.useState(false);
|
|
13
|
+
const [isLoading, setIsLoading] = React.useState(false);
|
|
14
|
+
// Network state
|
|
15
|
+
const [isOffline, setIsOffline] = React.useState(typeof navigator !== 'undefined' ? !navigator.onLine : false);
|
|
16
|
+
const [wasOffline, setWasOffline] = React.useState(false);
|
|
17
|
+
// Re-auth state
|
|
18
|
+
const [needsReauth, setNeedsReauth] = React.useState(false);
|
|
19
|
+
// Token state
|
|
11
20
|
const [token, setToken] = React.useState(null);
|
|
12
21
|
const [idToken, setIdToken] = React.useState(null);
|
|
13
22
|
const [tokenExp, setTokenExp] = React.useState(null);
|
|
23
|
+
// Config state
|
|
14
24
|
const [baseURL, setBaseURL] = React.useState("");
|
|
15
25
|
const [reloadKey, setReloadKey] = React.useState(0);
|
|
26
|
+
const [resolvedConfig, setResolvedConfig] = React.useState(null);
|
|
27
|
+
// Store callbacks in refs to avoid re-subscriptions
|
|
28
|
+
const onAuthSuccessRef = React.useRef(onAuthSuccess);
|
|
29
|
+
const onAuthErrorRef = React.useRef(onAuthError);
|
|
30
|
+
const onLogoutRef = React.useRef(onLogout);
|
|
31
|
+
const onReauthRequiredRef = React.useRef(onReauthRequired);
|
|
32
|
+
const onActionNotificationRef = React.useRef(onActionNotification);
|
|
16
33
|
React.useEffect(() => {
|
|
34
|
+
onAuthSuccessRef.current = onAuthSuccess;
|
|
35
|
+
onAuthErrorRef.current = onAuthError;
|
|
36
|
+
onLogoutRef.current = onLogout;
|
|
37
|
+
onReauthRequiredRef.current = onReauthRequired;
|
|
38
|
+
onActionNotificationRef.current = onActionNotification;
|
|
39
|
+
}, [onAuthSuccess, onAuthError, onLogout, onReauthRequired, onActionNotification]);
|
|
40
|
+
// Network status tracking
|
|
41
|
+
React.useEffect(() => {
|
|
42
|
+
if (typeof window === 'undefined')
|
|
43
|
+
return;
|
|
44
|
+
const handleOnline = () => setIsOffline(false);
|
|
45
|
+
const handleOffline = () => {
|
|
46
|
+
setIsOffline(true);
|
|
47
|
+
setWasOffline(true);
|
|
48
|
+
};
|
|
49
|
+
window.addEventListener('online', handleOnline);
|
|
50
|
+
window.addEventListener('offline', handleOffline);
|
|
51
|
+
return () => {
|
|
52
|
+
window.removeEventListener('online', handleOnline);
|
|
53
|
+
window.removeEventListener('offline', handleOffline);
|
|
54
|
+
};
|
|
55
|
+
}, []);
|
|
56
|
+
// Resolve config - either use provided config or fetch from URL
|
|
57
|
+
React.useEffect(() => {
|
|
58
|
+
let mounted = true;
|
|
59
|
+
const resolveConfig = async () => {
|
|
60
|
+
// Validate native mode requirements
|
|
61
|
+
if (authMode === 'native' && !adapter) {
|
|
62
|
+
const err = new Error('[TideCloak] authMode="native" requires an adapter prop');
|
|
63
|
+
if (mounted) {
|
|
64
|
+
setInitError(err);
|
|
65
|
+
setIsInitializing(false);
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// If config is provided directly, use it
|
|
70
|
+
if (configProp) {
|
|
71
|
+
const finalConfig = {
|
|
72
|
+
...configProp,
|
|
73
|
+
...(authMode && { authMode }),
|
|
74
|
+
...(adapter && { adapter }),
|
|
75
|
+
};
|
|
76
|
+
if (mounted) {
|
|
77
|
+
setResolvedConfig(finalConfig);
|
|
78
|
+
}
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// Otherwise, fetch from configUrl
|
|
82
|
+
try {
|
|
83
|
+
console.debug(`[TideCloak] Fetching config from ${configUrl}`);
|
|
84
|
+
const response = await fetch(configUrl);
|
|
85
|
+
if (!response.ok) {
|
|
86
|
+
throw new Error(`Failed to fetch config from ${configUrl}: ${response.status}`);
|
|
87
|
+
}
|
|
88
|
+
const fetchedConfig = await response.json();
|
|
89
|
+
const finalConfig = {
|
|
90
|
+
...fetchedConfig,
|
|
91
|
+
...(authMode && { authMode }),
|
|
92
|
+
...(adapter && { adapter }),
|
|
93
|
+
};
|
|
94
|
+
if (mounted) {
|
|
95
|
+
console.debug('[TideCloak] Config loaded:', {
|
|
96
|
+
realm: finalConfig.realm,
|
|
97
|
+
authMode: finalConfig.authMode || 'frontchannel',
|
|
98
|
+
hasAdapter: !!finalConfig.adapter,
|
|
99
|
+
});
|
|
100
|
+
setResolvedConfig(finalConfig);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
if (mounted) {
|
|
105
|
+
console.error('[TideCloak] Failed to load config:', err);
|
|
106
|
+
setInitError(err instanceof Error ? err : new Error(String(err)));
|
|
107
|
+
setIsInitializing(false);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
resolveConfig();
|
|
112
|
+
return () => {
|
|
113
|
+
mounted = false;
|
|
114
|
+
};
|
|
115
|
+
}, [configProp, configUrl, authMode, adapter]);
|
|
116
|
+
// Main IAMService initialization and event handling
|
|
117
|
+
React.useEffect(() => {
|
|
118
|
+
// Wait for config to be resolved
|
|
119
|
+
if (!resolvedConfig)
|
|
120
|
+
return;
|
|
17
121
|
let mounted = true;
|
|
18
122
|
const updateAuthState = async (eventName) => {
|
|
19
|
-
console.debug(`[TideCloak
|
|
123
|
+
console.debug(`[TideCloak] Auth state update from: ${eventName}`);
|
|
20
124
|
if (!mounted)
|
|
21
125
|
return;
|
|
22
126
|
const logged = IAMService.isLoggedIn();
|
|
23
127
|
setAuthenticated(logged);
|
|
24
128
|
if (logged) {
|
|
25
129
|
setSessionExpired(false);
|
|
130
|
+
setNeedsReauth(false);
|
|
26
131
|
try {
|
|
27
132
|
const t = await IAMService.getToken();
|
|
28
133
|
const idt = IAMService.getIDToken();
|
|
@@ -31,7 +136,7 @@ export function TideCloakContextProvider({ config, children }) {
|
|
|
31
136
|
setTokenExp(IAMService.getTokenExp());
|
|
32
137
|
}
|
|
33
138
|
catch (e) {
|
|
34
|
-
console.error("[TideCloak
|
|
139
|
+
console.error("[TideCloak] Failed to get tokens:", e);
|
|
35
140
|
}
|
|
36
141
|
}
|
|
37
142
|
else {
|
|
@@ -40,106 +145,462 @@ export function TideCloakContextProvider({ config, children }) {
|
|
|
40
145
|
setIdToken(null);
|
|
41
146
|
}
|
|
42
147
|
};
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
148
|
+
const handleAuthSuccess = async () => {
|
|
149
|
+
var _a;
|
|
150
|
+
if (!mounted)
|
|
151
|
+
return;
|
|
152
|
+
setIsLoading(false);
|
|
153
|
+
await updateAuthState('authSuccess');
|
|
154
|
+
// Send notification
|
|
155
|
+
(_a = onActionNotificationRef.current) === null || _a === void 0 ? void 0 : _a.call(onActionNotificationRef, {
|
|
156
|
+
type: 'success',
|
|
157
|
+
title: 'Logged In',
|
|
158
|
+
message: 'Successfully authenticated',
|
|
159
|
+
action: 'login'
|
|
160
|
+
});
|
|
161
|
+
// Call user's callback
|
|
162
|
+
if (onAuthSuccessRef.current) {
|
|
163
|
+
try {
|
|
164
|
+
await onAuthSuccessRef.current();
|
|
165
|
+
}
|
|
166
|
+
catch (e) {
|
|
167
|
+
console.error("[TideCloak] onAuthSuccess callback error:", e);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
const handleAuthError = (_event, error) => {
|
|
172
|
+
var _a;
|
|
173
|
+
if (!mounted)
|
|
174
|
+
return;
|
|
175
|
+
setIsLoading(false);
|
|
176
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
177
|
+
// Send notification
|
|
178
|
+
(_a = onActionNotificationRef.current) === null || _a === void 0 ? void 0 : _a.call(onActionNotificationRef, {
|
|
179
|
+
type: 'error',
|
|
180
|
+
title: 'Login Failed',
|
|
181
|
+
message: err.message,
|
|
182
|
+
action: 'login'
|
|
183
|
+
});
|
|
184
|
+
// Call user's callback
|
|
185
|
+
if (onAuthErrorRef.current) {
|
|
186
|
+
onAuthErrorRef.current(err);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
const handleLogout = () => {
|
|
190
|
+
var _a;
|
|
191
|
+
if (!mounted)
|
|
192
|
+
return;
|
|
193
|
+
setIsLoading(false);
|
|
194
|
+
setAuthenticated(false);
|
|
195
|
+
setToken(null);
|
|
196
|
+
setIdToken(null);
|
|
197
|
+
setNeedsReauth(false);
|
|
198
|
+
// Send notification
|
|
199
|
+
(_a = onActionNotificationRef.current) === null || _a === void 0 ? void 0 : _a.call(onActionNotificationRef, {
|
|
200
|
+
type: 'info',
|
|
201
|
+
title: 'Logged Out',
|
|
202
|
+
message: 'You have been logged out',
|
|
203
|
+
action: 'logout'
|
|
204
|
+
});
|
|
205
|
+
// Call user's callback
|
|
206
|
+
if (onLogoutRef.current) {
|
|
207
|
+
onLogoutRef.current();
|
|
47
208
|
}
|
|
48
209
|
};
|
|
49
|
-
const
|
|
210
|
+
const handleInitError = (err) => {
|
|
211
|
+
var _a;
|
|
212
|
+
if (!mounted)
|
|
213
|
+
return;
|
|
214
|
+
setInitError(err);
|
|
215
|
+
setIsInitializing(false);
|
|
216
|
+
// Send notification
|
|
217
|
+
(_a = onActionNotificationRef.current) === null || _a === void 0 ? void 0 : _a.call(onActionNotificationRef, {
|
|
218
|
+
type: 'error',
|
|
219
|
+
title: 'Initialization Failed',
|
|
220
|
+
message: err.message,
|
|
221
|
+
action: 'init'
|
|
222
|
+
});
|
|
223
|
+
};
|
|
224
|
+
const handleTokenExpired = async () => {
|
|
225
|
+
var _a;
|
|
50
226
|
if (!mounted)
|
|
51
227
|
return;
|
|
52
|
-
console.debug("[TideCloak
|
|
228
|
+
console.debug("[TideCloak] Token expired, attempting refresh...");
|
|
53
229
|
setSessionExpired(true);
|
|
230
|
+
// Send notification
|
|
231
|
+
(_a = onActionNotificationRef.current) === null || _a === void 0 ? void 0 : _a.call(onActionNotificationRef, {
|
|
232
|
+
type: 'warning',
|
|
233
|
+
title: 'Session Expiring',
|
|
234
|
+
message: 'Refreshing your session...',
|
|
235
|
+
action: 'token'
|
|
236
|
+
});
|
|
54
237
|
try {
|
|
55
238
|
await IAMService.updateIAMToken();
|
|
56
239
|
}
|
|
57
240
|
catch (refreshError) {
|
|
58
|
-
console.error("[TideCloak
|
|
241
|
+
console.error("[TideCloak] Token refresh failed:", refreshError);
|
|
59
242
|
}
|
|
60
243
|
};
|
|
244
|
+
const handleAuthRefreshSuccess = async () => {
|
|
245
|
+
var _a;
|
|
246
|
+
if (!mounted)
|
|
247
|
+
return;
|
|
248
|
+
await updateAuthState('authRefreshSuccess');
|
|
249
|
+
// Send notification
|
|
250
|
+
(_a = onActionNotificationRef.current) === null || _a === void 0 ? void 0 : _a.call(onActionNotificationRef, {
|
|
251
|
+
type: 'success',
|
|
252
|
+
title: 'Session Refreshed',
|
|
253
|
+
message: 'Your session has been refreshed',
|
|
254
|
+
action: 'token'
|
|
255
|
+
});
|
|
256
|
+
};
|
|
257
|
+
const handleAuthRefreshError = async () => {
|
|
258
|
+
var _a;
|
|
259
|
+
if (!mounted)
|
|
260
|
+
return;
|
|
261
|
+
await updateAuthState('authRefreshError');
|
|
262
|
+
// Send notification
|
|
263
|
+
(_a = onActionNotificationRef.current) === null || _a === void 0 ? void 0 : _a.call(onActionNotificationRef, {
|
|
264
|
+
type: 'error',
|
|
265
|
+
title: 'Session Refresh Failed',
|
|
266
|
+
message: 'Please log in again',
|
|
267
|
+
action: 'token'
|
|
268
|
+
});
|
|
269
|
+
};
|
|
270
|
+
// Subscribe to IAMService events
|
|
61
271
|
IAMService
|
|
62
|
-
.on('authSuccess',
|
|
63
|
-
.on('authError',
|
|
64
|
-
.on('authRefreshSuccess',
|
|
65
|
-
.on('authRefreshError',
|
|
66
|
-
.on('logout',
|
|
67
|
-
.on('tokenExpired',
|
|
68
|
-
.on('initError',
|
|
272
|
+
.on('authSuccess', handleAuthSuccess)
|
|
273
|
+
.on('authError', handleAuthError)
|
|
274
|
+
.on('authRefreshSuccess', handleAuthRefreshSuccess)
|
|
275
|
+
.on('authRefreshError', handleAuthRefreshError)
|
|
276
|
+
.on('logout', handleLogout)
|
|
277
|
+
.on('tokenExpired', handleTokenExpired)
|
|
278
|
+
.on('initError', handleInitError);
|
|
69
279
|
setIsInitializing(true);
|
|
280
|
+
// Initialize
|
|
70
281
|
(async () => {
|
|
71
282
|
try {
|
|
72
|
-
const loaded = await IAMService.loadConfig(
|
|
73
|
-
;
|
|
283
|
+
const loaded = await IAMService.loadConfig(resolvedConfig);
|
|
74
284
|
if (!loaded)
|
|
75
285
|
throw new Error("Invalid config");
|
|
76
|
-
setBaseURL(loaded['auth-server-url'].replace(/\/+$/, ''));
|
|
77
|
-
await IAMService.initIAM(
|
|
286
|
+
setBaseURL((loaded['auth-server-url'] || '').replace(/\/+$/, ''));
|
|
287
|
+
await IAMService.initIAM(resolvedConfig, updateAuthState);
|
|
78
288
|
if (!mounted)
|
|
79
289
|
return;
|
|
80
290
|
setIsInitializing(false);
|
|
81
291
|
}
|
|
82
292
|
catch (err) {
|
|
83
|
-
|
|
293
|
+
handleInitError(err);
|
|
84
294
|
}
|
|
85
295
|
})();
|
|
86
296
|
return () => {
|
|
87
297
|
mounted = false;
|
|
88
|
-
IAMService
|
|
89
|
-
.off('authSuccess',
|
|
90
|
-
.off('authError',
|
|
91
|
-
.off('authRefreshSuccess',
|
|
92
|
-
.off('authRefreshError',
|
|
93
|
-
.off('logout',
|
|
94
|
-
.off('tokenExpired',
|
|
95
|
-
.off('initError',
|
|
298
|
+
IAMService
|
|
299
|
+
.off('authSuccess', handleAuthSuccess)
|
|
300
|
+
.off('authError', handleAuthError)
|
|
301
|
+
.off('authRefreshSuccess', handleAuthRefreshSuccess)
|
|
302
|
+
.off('authRefreshError', handleAuthRefreshError)
|
|
303
|
+
.off('logout', handleLogout)
|
|
304
|
+
.off('tokenExpired', handleTokenExpired)
|
|
305
|
+
.off('initError', handleInitError);
|
|
96
306
|
};
|
|
97
|
-
}, [
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
307
|
+
}, [resolvedConfig, reloadKey]);
|
|
308
|
+
// Actions
|
|
309
|
+
const login = React.useCallback(async () => {
|
|
310
|
+
setIsLoading(true);
|
|
311
|
+
try {
|
|
312
|
+
await IAMService.doLogin();
|
|
313
|
+
}
|
|
314
|
+
catch (error) {
|
|
315
|
+
setIsLoading(false);
|
|
316
|
+
throw error;
|
|
317
|
+
}
|
|
318
|
+
}, []);
|
|
319
|
+
const logout = React.useCallback(async () => {
|
|
320
|
+
setIsLoading(true);
|
|
321
|
+
try {
|
|
322
|
+
await IAMService.doLogout();
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
setIsLoading(false);
|
|
326
|
+
throw error;
|
|
327
|
+
}
|
|
328
|
+
}, []);
|
|
329
|
+
const getToken = React.useCallback(async () => {
|
|
330
|
+
try {
|
|
331
|
+
return await IAMService.getToken();
|
|
332
|
+
}
|
|
333
|
+
catch (error) {
|
|
334
|
+
console.error("[TideCloak] getToken error:", error);
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
}, []);
|
|
338
|
+
const refreshToken = React.useCallback(async () => {
|
|
339
|
+
setIsRefreshing(true);
|
|
340
|
+
try {
|
|
341
|
+
return await IAMService.updateIAMToken();
|
|
342
|
+
}
|
|
343
|
+
finally {
|
|
344
|
+
setIsRefreshing(false);
|
|
345
|
+
}
|
|
346
|
+
}, []);
|
|
347
|
+
const forceRefreshToken = React.useCallback(async () => {
|
|
348
|
+
setIsRefreshing(true);
|
|
349
|
+
try {
|
|
350
|
+
return await IAMService.forceUpdateToken();
|
|
351
|
+
}
|
|
352
|
+
finally {
|
|
353
|
+
setIsRefreshing(false);
|
|
354
|
+
}
|
|
355
|
+
}, []);
|
|
356
|
+
// Re-auth actions
|
|
357
|
+
const triggerReauth = React.useCallback(() => {
|
|
358
|
+
var _a;
|
|
359
|
+
setNeedsReauth(true);
|
|
360
|
+
// Send notification
|
|
361
|
+
(_a = onActionNotificationRef.current) === null || _a === void 0 ? void 0 : _a.call(onActionNotificationRef, {
|
|
362
|
+
type: 'warning',
|
|
363
|
+
title: 'Re-authentication Required',
|
|
364
|
+
message: 'Please log in again to continue',
|
|
365
|
+
action: 'reauth'
|
|
366
|
+
});
|
|
367
|
+
if (onReauthRequiredRef.current) {
|
|
368
|
+
onReauthRequiredRef.current();
|
|
369
|
+
}
|
|
370
|
+
}, []);
|
|
371
|
+
const clearReauth = React.useCallback(() => {
|
|
372
|
+
setNeedsReauth(false);
|
|
373
|
+
}, []);
|
|
374
|
+
const resetWasOffline = React.useCallback(() => {
|
|
375
|
+
setWasOffline(false);
|
|
376
|
+
}, []);
|
|
377
|
+
// Context value - provide safe defaults during initialization
|
|
378
|
+
const contextValue = {
|
|
379
|
+
// Bootstrap
|
|
380
|
+
isInitializing,
|
|
381
|
+
initError,
|
|
382
|
+
// Session
|
|
383
|
+
authenticated,
|
|
384
|
+
sessionExpired,
|
|
385
|
+
isRefreshing,
|
|
386
|
+
isLoading,
|
|
387
|
+
// Network
|
|
388
|
+
isOffline,
|
|
389
|
+
wasOffline,
|
|
390
|
+
// Re-auth
|
|
391
|
+
needsReauth,
|
|
392
|
+
// Tokens
|
|
393
|
+
token,
|
|
394
|
+
idToken,
|
|
395
|
+
tokenExp,
|
|
396
|
+
// Config
|
|
397
|
+
baseURL,
|
|
398
|
+
// Direct access to service instances
|
|
399
|
+
IAMService,
|
|
400
|
+
AdminAPI,
|
|
401
|
+
getConfig: () => isInitializing ? {} : IAMService.getConfig(),
|
|
402
|
+
reload: () => setReloadKey((k) => k + 1),
|
|
403
|
+
// Auth actions
|
|
404
|
+
login,
|
|
405
|
+
logout,
|
|
406
|
+
getToken,
|
|
407
|
+
refreshToken,
|
|
408
|
+
forceRefreshToken,
|
|
409
|
+
// Role checks - return false during initialization
|
|
410
|
+
hasRealmRole: (role) => isInitializing ? false : IAMService.hasRealmRole(role),
|
|
411
|
+
hasClientRole: (role, resource) => isInitializing ? false : IAMService.hasClientRole(role, resource),
|
|
412
|
+
// Token claims - return undefined during initialization or when not authenticated
|
|
413
|
+
getValueFromToken: (key) => (isInitializing || !authenticated) ? undefined : IAMService.getValueFromToken(key),
|
|
414
|
+
getValueFromIdToken: (key) => (isInitializing || !authenticated) ? undefined : IAMService.getValueFromIDToken(key),
|
|
415
|
+
// Re-auth actions
|
|
416
|
+
triggerReauth,
|
|
417
|
+
clearReauth,
|
|
418
|
+
resetWasOffline,
|
|
419
|
+
// Tide encryption - return null during initialization
|
|
420
|
+
doEncrypt: async (data) => {
|
|
421
|
+
var _a, _b;
|
|
422
|
+
if (isInitializing)
|
|
423
|
+
return null;
|
|
424
|
+
try {
|
|
425
|
+
const result = await IAMService.doEncrypt(data);
|
|
426
|
+
(_a = onActionNotificationRef.current) === null || _a === void 0 ? void 0 : _a.call(onActionNotificationRef, {
|
|
427
|
+
type: 'success',
|
|
428
|
+
title: 'Encrypted',
|
|
429
|
+
message: `${Array.isArray(data) ? data.length : 1} item(s) encrypted`,
|
|
430
|
+
action: 'encrypt'
|
|
431
|
+
});
|
|
432
|
+
return result;
|
|
433
|
+
}
|
|
434
|
+
catch (error) {
|
|
435
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
436
|
+
(_b = onActionNotificationRef.current) === null || _b === void 0 ? void 0 : _b.call(onActionNotificationRef, {
|
|
437
|
+
type: 'error',
|
|
438
|
+
title: 'Encryption Failed',
|
|
439
|
+
message: err.message,
|
|
440
|
+
action: 'encrypt'
|
|
441
|
+
});
|
|
442
|
+
throw error;
|
|
443
|
+
}
|
|
444
|
+
},
|
|
445
|
+
doDecrypt: async (data) => {
|
|
446
|
+
var _a, _b;
|
|
447
|
+
if (isInitializing)
|
|
448
|
+
return null;
|
|
449
|
+
try {
|
|
450
|
+
const result = await IAMService.doDecrypt(data);
|
|
451
|
+
(_a = onActionNotificationRef.current) === null || _a === void 0 ? void 0 : _a.call(onActionNotificationRef, {
|
|
452
|
+
type: 'success',
|
|
453
|
+
title: 'Decrypted',
|
|
454
|
+
message: `${Array.isArray(data) ? data.length : 1} item(s) decrypted`,
|
|
455
|
+
action: 'decrypt'
|
|
456
|
+
});
|
|
457
|
+
return result;
|
|
458
|
+
}
|
|
459
|
+
catch (error) {
|
|
460
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
461
|
+
(_b = onActionNotificationRef.current) === null || _b === void 0 ? void 0 : _b.call(onActionNotificationRef, {
|
|
462
|
+
type: 'error',
|
|
463
|
+
title: 'Decryption Failed',
|
|
464
|
+
message: err.message,
|
|
465
|
+
action: 'decrypt'
|
|
466
|
+
});
|
|
467
|
+
throw error;
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
// Tide request signing (for policy creation)
|
|
471
|
+
initializeTideRequest: async (request) => {
|
|
472
|
+
const tc = IAMService._tc;
|
|
473
|
+
if (!(tc === null || tc === void 0 ? void 0 : tc.createTideRequest)) {
|
|
474
|
+
throw new Error("TideCloak createTideRequest not available");
|
|
475
|
+
}
|
|
476
|
+
const encodedRequest = request.encode();
|
|
477
|
+
const initializedBytes = await tc.createTideRequest(encodedRequest);
|
|
478
|
+
const RequestClass = request.constructor;
|
|
479
|
+
if (typeof RequestClass.decode === "function") {
|
|
480
|
+
return RequestClass.decode(initializedBytes);
|
|
481
|
+
}
|
|
482
|
+
return request;
|
|
483
|
+
},
|
|
484
|
+
// Get vendor ID from config
|
|
485
|
+
getVendorId: () => {
|
|
486
|
+
const cfg = IAMService.getConfig();
|
|
487
|
+
return (cfg === null || cfg === void 0 ? void 0 : cfg.vendorId) || (cfg === null || cfg === void 0 ? void 0 : cfg["vendor-id"]) || "";
|
|
488
|
+
},
|
|
489
|
+
// Get resource (client ID) from config
|
|
490
|
+
getResource: () => {
|
|
491
|
+
const cfg = IAMService.getConfig();
|
|
492
|
+
return (cfg === null || cfg === void 0 ? void 0 : cfg.resource) || (cfg === null || cfg === void 0 ? void 0 : cfg.clientId) || "";
|
|
493
|
+
},
|
|
494
|
+
// Tide approval enclave (for change set approvals)
|
|
495
|
+
approveTideRequests: async (requests) => {
|
|
496
|
+
var _a, _b, _c, _d;
|
|
497
|
+
const tc = IAMService._tc;
|
|
498
|
+
if (!(tc === null || tc === void 0 ? void 0 : tc.requestTideOperatorApproval)) {
|
|
499
|
+
const error = new Error("TideCloak approval enclave not available");
|
|
500
|
+
(_a = onActionNotificationRef.current) === null || _a === void 0 ? void 0 : _a.call(onActionNotificationRef, {
|
|
501
|
+
type: 'error',
|
|
502
|
+
title: 'Approval Failed',
|
|
503
|
+
message: error.message,
|
|
504
|
+
action: 'approval'
|
|
505
|
+
});
|
|
506
|
+
throw error;
|
|
507
|
+
}
|
|
508
|
+
try {
|
|
509
|
+
const response = await tc.requestTideOperatorApproval(requests);
|
|
510
|
+
const results = response.map((res) => {
|
|
511
|
+
if (res.status === "approved") {
|
|
512
|
+
return { id: res.id, approved: { request: res.request } };
|
|
513
|
+
}
|
|
514
|
+
else if (res.status === "denied") {
|
|
515
|
+
return { id: res.id, denied: true };
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
return { id: res.id, pending: true };
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
// Send notifications for each result
|
|
522
|
+
const approved = results.filter((r) => r.approved).length;
|
|
523
|
+
const denied = results.filter((r) => r.denied).length;
|
|
524
|
+
if (approved > 0) {
|
|
525
|
+
(_b = onActionNotificationRef.current) === null || _b === void 0 ? void 0 : _b.call(onActionNotificationRef, {
|
|
526
|
+
type: 'success',
|
|
527
|
+
title: 'Approved',
|
|
528
|
+
message: `${approved} request${approved > 1 ? 's' : ''} approved successfully`,
|
|
529
|
+
action: 'approval'
|
|
530
|
+
});
|
|
127
531
|
}
|
|
128
|
-
|
|
129
|
-
|
|
532
|
+
if (denied > 0) {
|
|
533
|
+
(_c = onActionNotificationRef.current) === null || _c === void 0 ? void 0 : _c.call(onActionNotificationRef, {
|
|
534
|
+
type: 'warning',
|
|
535
|
+
title: 'Denied',
|
|
536
|
+
message: `${denied} request${denied > 1 ? 's' : ''} denied`,
|
|
537
|
+
action: 'approval'
|
|
538
|
+
});
|
|
130
539
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
540
|
+
// Trigger reload to refresh data after approvals
|
|
541
|
+
setReloadKey(k => k + 1);
|
|
542
|
+
return results;
|
|
543
|
+
}
|
|
544
|
+
catch (error) {
|
|
545
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
546
|
+
(_d = onActionNotificationRef.current) === null || _d === void 0 ? void 0 : _d.call(onActionNotificationRef, {
|
|
547
|
+
type: 'error',
|
|
548
|
+
title: 'Approval Failed',
|
|
549
|
+
message: err.message,
|
|
550
|
+
action: 'approval'
|
|
551
|
+
});
|
|
552
|
+
throw error;
|
|
553
|
+
}
|
|
554
|
+
},
|
|
555
|
+
};
|
|
556
|
+
return (_jsx(TideCloakContext.Provider, { value: contextValue, children: children }));
|
|
139
557
|
}
|
|
558
|
+
// Default context value for when provider is not available
|
|
559
|
+
// This allows graceful degradation in iframes or other contexts
|
|
560
|
+
const defaultContextValue = {
|
|
561
|
+
isInitializing: true,
|
|
562
|
+
initError: null,
|
|
563
|
+
authenticated: false,
|
|
564
|
+
sessionExpired: false,
|
|
565
|
+
isRefreshing: false,
|
|
566
|
+
isLoading: false,
|
|
567
|
+
isOffline: false,
|
|
568
|
+
wasOffline: false,
|
|
569
|
+
needsReauth: false,
|
|
570
|
+
token: null,
|
|
571
|
+
idToken: null,
|
|
572
|
+
tokenExp: null,
|
|
573
|
+
baseURL: '',
|
|
574
|
+
IAMService,
|
|
575
|
+
AdminAPI,
|
|
576
|
+
getConfig: () => ({}),
|
|
577
|
+
reload: () => { },
|
|
578
|
+
login: async () => { },
|
|
579
|
+
logout: async () => { },
|
|
580
|
+
getToken: async () => null,
|
|
581
|
+
refreshToken: async () => false,
|
|
582
|
+
forceRefreshToken: async () => false,
|
|
583
|
+
hasRealmRole: () => false,
|
|
584
|
+
hasClientRole: () => false,
|
|
585
|
+
getValueFromToken: () => undefined,
|
|
586
|
+
getValueFromIdToken: () => undefined,
|
|
587
|
+
triggerReauth: () => { },
|
|
588
|
+
clearReauth: () => { },
|
|
589
|
+
resetWasOffline: () => { },
|
|
590
|
+
doEncrypt: async () => null,
|
|
591
|
+
doDecrypt: async () => null,
|
|
592
|
+
initializeTideRequest: async () => { throw new Error("TideCloakContextProvider not available"); },
|
|
593
|
+
getVendorId: () => "",
|
|
594
|
+
getResource: () => "",
|
|
595
|
+
approveTideRequests: async () => { throw new Error("TideCloakContextProvider not available"); },
|
|
596
|
+
};
|
|
140
597
|
export function useTideCloakContext() {
|
|
141
598
|
const ctx = React.useContext(TideCloakContext);
|
|
142
|
-
if (
|
|
143
|
-
|
|
599
|
+
// Return default context if not within provider (e.g., in iframes)
|
|
600
|
+
// This provides graceful degradation instead of throwing
|
|
601
|
+
if (!ctx) {
|
|
602
|
+
console.warn("useTideCloakContext called outside TideCloakContextProvider - returning defaults");
|
|
603
|
+
return defaultContextValue;
|
|
604
|
+
}
|
|
144
605
|
return ctx;
|
|
145
606
|
}
|