@spidy092/auth-client 3.0.8 โ 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.cjs +87 -4
- package/dist/api.cjs.map +1 -1
- package/dist/api.js +87 -4
- package/dist/api.js.map +1 -1
- package/dist/index.cjs +465 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +452 -33
- package/dist/index.js.map +1 -1
- package/dist/preferences.cjs +657 -0
- package/dist/preferences.cjs.map +1 -0
- package/dist/preferences.js +608 -0
- package/dist/preferences.js.map +1 -0
- package/dist/react/AuthProvider.cjs +143 -30
- package/dist/react/AuthProvider.cjs.map +1 -1
- package/dist/react/AuthProvider.js +143 -30
- package/dist/react/AuthProvider.js.map +1 -1
- package/dist/react/PreferencesProvider.cjs +621 -0
- package/dist/react/PreferencesProvider.cjs.map +1 -0
- package/dist/react/PreferencesProvider.js +586 -0
- package/dist/react/PreferencesProvider.js.map +1 -0
- package/dist/react/useAuth.cjs +17 -0
- package/dist/react/useAuth.cjs.map +1 -1
- package/dist/react/useAuth.js +17 -0
- package/dist/react/useAuth.js.map +1 -1
- package/dist/react/useSessionMonitor.cjs +381 -31
- package/dist/react/useSessionMonitor.cjs.map +1 -1
- package/dist/react/useSessionMonitor.js +380 -30
- package/dist/react/useSessionMonitor.js.map +1 -1
- package/dist/utils/jwt.cjs +16 -0
- package/dist/utils/jwt.cjs.map +1 -1
- package/dist/utils/jwt.js +16 -0
- package/dist/utils/jwt.js.map +1 -1
- package/package.json +10 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// react/useSessionMonitor.js
|
|
2
2
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
3
|
-
import { useEffect as
|
|
3
|
+
import { useEffect as useEffect3, useCallback } from "react";
|
|
4
4
|
|
|
5
5
|
// token.js
|
|
6
6
|
import { jwtDecode } from "jwt-decode";
|
|
@@ -173,6 +173,21 @@ function removeTokenListener(listener) {
|
|
|
173
173
|
function getListenerCount() {
|
|
174
174
|
return listeners.size;
|
|
175
175
|
}
|
|
176
|
+
if (typeof window !== "undefined" && window.addEventListener) {
|
|
177
|
+
window.addEventListener("storage", (event) => {
|
|
178
|
+
if (event.key !== "authToken") return;
|
|
179
|
+
const previousToken = accessToken;
|
|
180
|
+
accessToken = event.newValue || null;
|
|
181
|
+
if (previousToken === accessToken) return;
|
|
182
|
+
listeners.forEach((listener) => {
|
|
183
|
+
try {
|
|
184
|
+
listener(accessToken, previousToken);
|
|
185
|
+
} catch (err) {
|
|
186
|
+
console.warn("Token listener error:", err);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
}
|
|
176
191
|
|
|
177
192
|
// config.js
|
|
178
193
|
var config = {
|
|
@@ -217,8 +232,8 @@ function setConfig(customConfig = {}) {
|
|
|
217
232
|
// โ
Auto-detect router mode
|
|
218
233
|
isRouter: customConfig.isRouter || customConfig.clientKey === "account-ui"
|
|
219
234
|
};
|
|
235
|
+
enableRefreshTokenPersistence(config.persistRefreshToken);
|
|
220
236
|
if (config.persistRefreshToken) {
|
|
221
|
-
enableRefreshTokenPersistence(true);
|
|
222
237
|
console.log("\u{1F4E6} Refresh token persistence ENABLED (localStorage on HTTPS)");
|
|
223
238
|
}
|
|
224
239
|
console.log(`\u{1F527} Auth Client Mode: ${config.isRouter ? "ROUTER" : "CLIENT"}`, {
|
|
@@ -234,6 +249,82 @@ function isRouterMode() {
|
|
|
234
249
|
return config.isRouter;
|
|
235
250
|
}
|
|
236
251
|
|
|
252
|
+
// diagnostics.js
|
|
253
|
+
var STORAGE_KEY = "auth_diagnostic_context";
|
|
254
|
+
var LOGIN_LOCK_KEY = "auth_login_lock";
|
|
255
|
+
var randomId = () => {
|
|
256
|
+
var _a;
|
|
257
|
+
if ((_a = globalThis.crypto) == null ? void 0 : _a.randomUUID) return globalThis.crypto.randomUUID();
|
|
258
|
+
return `auth-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
259
|
+
};
|
|
260
|
+
var fingerprint = async (value) => {
|
|
261
|
+
var _a;
|
|
262
|
+
if (!value || !((_a = globalThis.crypto) == null ? void 0 : _a.subtle)) return null;
|
|
263
|
+
const bytes = new TextEncoder().encode(String(value));
|
|
264
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
|
|
265
|
+
return [...new Uint8Array(digest)].slice(0, 6).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
266
|
+
};
|
|
267
|
+
function getDiagnosticContext() {
|
|
268
|
+
try {
|
|
269
|
+
const existing = JSON.parse(sessionStorage.getItem(STORAGE_KEY) || "null");
|
|
270
|
+
if (existing == null ? void 0 : existing.correlationId) return existing;
|
|
271
|
+
} catch {
|
|
272
|
+
}
|
|
273
|
+
const context = { correlationId: randomId(), startedAt: Date.now() };
|
|
274
|
+
try {
|
|
275
|
+
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(context));
|
|
276
|
+
} catch {
|
|
277
|
+
}
|
|
278
|
+
return context;
|
|
279
|
+
}
|
|
280
|
+
function resetDiagnosticContext() {
|
|
281
|
+
const context = { correlationId: randomId(), startedAt: Date.now() };
|
|
282
|
+
try {
|
|
283
|
+
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(context));
|
|
284
|
+
} catch {
|
|
285
|
+
}
|
|
286
|
+
return context;
|
|
287
|
+
}
|
|
288
|
+
function diagnosticHeaders() {
|
|
289
|
+
const { correlationId } = getDiagnosticContext();
|
|
290
|
+
return { "X-Correlation-ID": correlationId, "X-Request-ID": correlationId };
|
|
291
|
+
}
|
|
292
|
+
function acquireLoginLock(clientKey, redirectUri, ttlMs = 5e3) {
|
|
293
|
+
const now = Date.now();
|
|
294
|
+
const signature = `${clientKey}|${redirectUri}`;
|
|
295
|
+
try {
|
|
296
|
+
const current = JSON.parse(sessionStorage.getItem(LOGIN_LOCK_KEY) || "null");
|
|
297
|
+
if ((current == null ? void 0 : current.signature) === signature && now - current.createdAt < ttlMs) return false;
|
|
298
|
+
sessionStorage.setItem(LOGIN_LOCK_KEY, JSON.stringify({ signature, createdAt: now }));
|
|
299
|
+
} catch {
|
|
300
|
+
}
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
function clearLoginLock() {
|
|
304
|
+
try {
|
|
305
|
+
sessionStorage.removeItem(LOGIN_LOCK_KEY);
|
|
306
|
+
} catch {
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
async function emitAuthDiagnostic(event, outcome, reasonCode, details = {}) {
|
|
310
|
+
const context = getDiagnosticContext();
|
|
311
|
+
const safe = {
|
|
312
|
+
eventType: "auth_client_diagnostic",
|
|
313
|
+
event,
|
|
314
|
+
outcome,
|
|
315
|
+
reasonCode,
|
|
316
|
+
correlationId: context.correlationId,
|
|
317
|
+
elapsedMs: Date.now() - context.startedAt,
|
|
318
|
+
clientKey: details.clientKey || null,
|
|
319
|
+
status: details.status || null,
|
|
320
|
+
stateFingerprint: await fingerprint(details.state),
|
|
321
|
+
online: typeof navigator === "undefined" ? null : navigator.onLine
|
|
322
|
+
};
|
|
323
|
+
const method = outcome === "FAILURE" ? "error" : outcome === "WARNING" ? "warn" : "info";
|
|
324
|
+
console[method]("[auth-client]", safe);
|
|
325
|
+
return safe;
|
|
326
|
+
}
|
|
327
|
+
|
|
237
328
|
// core.js
|
|
238
329
|
var callbackProcessed = false;
|
|
239
330
|
function login(clientKeyArg, redirectUriArg) {
|
|
@@ -246,14 +337,16 @@ function login(clientKeyArg, redirectUriArg) {
|
|
|
246
337
|
} = getConfig();
|
|
247
338
|
const clientKey = clientKeyArg || defaultClientKey;
|
|
248
339
|
const redirectUri = redirectUriArg || defaultRedirectUri;
|
|
249
|
-
console.log("\u{1F504} Smart Login initiated:", {
|
|
250
|
-
mode: isRouterMode() ? "ROUTER" : "CLIENT",
|
|
251
|
-
clientKey,
|
|
252
|
-
redirectUri
|
|
253
|
-
});
|
|
254
340
|
if (!clientKey || !redirectUri) {
|
|
341
|
+
emitAuthDiagnostic("LOGIN_REJECTED", "FAILURE", "CLIENT_CONFIG_MISSING", { clientKey });
|
|
255
342
|
throw new Error("Missing clientKey or redirectUri");
|
|
256
343
|
}
|
|
344
|
+
if (!acquireLoginLock(clientKey, redirectUri)) {
|
|
345
|
+
emitAuthDiagnostic("LOGIN_DUPLICATE_SUPPRESSED", "WARNING", "LOGIN_ALREADY_IN_PROGRESS", { clientKey });
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
resetDiagnosticContext();
|
|
349
|
+
emitAuthDiagnostic("LOGIN_INITIATED", "PENDING", "NONE", { clientKey });
|
|
257
350
|
sessionStorage.setItem("originalApp", clientKey);
|
|
258
351
|
sessionStorage.setItem("returnUrl", redirectUri);
|
|
259
352
|
if (isRouterMode()) {
|
|
@@ -268,13 +361,9 @@ function routerLogin(clientKey, redirectUri) {
|
|
|
268
361
|
if (redirectUri) {
|
|
269
362
|
params.append("redirect_uri", redirectUri);
|
|
270
363
|
}
|
|
364
|
+
params.append("correlation_id", getDiagnosticContext().correlationId);
|
|
271
365
|
const query = params.toString();
|
|
272
366
|
const backendLoginUrl = `${authBaseUrl}/login/${clientKey}${query ? `?${query}` : ""}`;
|
|
273
|
-
console.log("\u{1F3ED} Router Login: Direct backend authentication", {
|
|
274
|
-
clientKey,
|
|
275
|
-
redirectUri,
|
|
276
|
-
backendUrl: backendLoginUrl
|
|
277
|
-
});
|
|
278
367
|
window.location.href = backendLoginUrl;
|
|
279
368
|
}
|
|
280
369
|
function clientLogin(clientKey, redirectUri) {
|
|
@@ -286,19 +375,19 @@ function clientLogin(clientKey, redirectUri) {
|
|
|
286
375
|
params.append("redirect_uri", redirectUri);
|
|
287
376
|
}
|
|
288
377
|
const centralizedLoginUrl = `${accountUiUrl}/login?${params.toString()}`;
|
|
289
|
-
console.log("\u{1F504} Client Login: Redirecting to centralized login", {
|
|
290
|
-
clientKey,
|
|
291
|
-
redirectUri,
|
|
292
|
-
centralizedUrl: centralizedLoginUrl
|
|
293
|
-
});
|
|
294
378
|
window.location.href = centralizedLoginUrl;
|
|
295
379
|
}
|
|
296
|
-
async function logout() {
|
|
380
|
+
async function logout(options = {}) {
|
|
297
381
|
resetCallbackState();
|
|
298
382
|
const { clientKey, authBaseUrl, accountUiUrl } = getConfig();
|
|
383
|
+
const scope = options.scope === "client" ? "client" : "sso";
|
|
299
384
|
const token = getToken();
|
|
300
385
|
const refreshToken2 = getRefreshToken();
|
|
301
|
-
console.log("\u{1F6AA} Smart Logout initiated", {
|
|
386
|
+
console.log("\u{1F6AA} Smart Logout initiated", {
|
|
387
|
+
mode: isRouterMode() ? "ROUTER" : "CLIENT",
|
|
388
|
+
clientKey,
|
|
389
|
+
scope
|
|
390
|
+
});
|
|
302
391
|
clearToken();
|
|
303
392
|
clearRefreshToken();
|
|
304
393
|
sessionStorage.removeItem("originalApp");
|
|
@@ -308,21 +397,33 @@ async function logout() {
|
|
|
308
397
|
method: "POST",
|
|
309
398
|
credentials: "include",
|
|
310
399
|
headers: {
|
|
400
|
+
...diagnosticHeaders(),
|
|
311
401
|
"Authorization": token ? `Bearer ${token}` : "",
|
|
312
402
|
"Content-Type": "application/json"
|
|
313
403
|
},
|
|
314
|
-
body: JSON.stringify({ refreshToken: refreshToken2 })
|
|
404
|
+
body: JSON.stringify({ refreshToken: refreshToken2, scope })
|
|
315
405
|
});
|
|
406
|
+
if (!response.ok) {
|
|
407
|
+
throw new Error(`Logout failed: ${response.status}`);
|
|
408
|
+
}
|
|
316
409
|
const data = await response.json();
|
|
317
410
|
console.log("\u2705 Logout response:", data);
|
|
411
|
+
if (data == null ? void 0 : data.logoutRedirectUrl) {
|
|
412
|
+
window.location.replace(data.logoutRedirectUrl);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
318
415
|
if (data == null ? void 0 : data.keycloakLogoutUrl) {
|
|
319
|
-
window.location.
|
|
416
|
+
window.location.replace(data.keycloakLogoutUrl);
|
|
320
417
|
return;
|
|
321
418
|
}
|
|
322
419
|
} catch (error) {
|
|
323
420
|
console.warn("\u26A0\uFE0F Logout backend call failed:", error);
|
|
324
421
|
}
|
|
325
|
-
|
|
422
|
+
const fallbackUrl = isRouterMode() ? new URL("/login", window.location.origin) : new URL("/login", accountUiUrl);
|
|
423
|
+
fallbackUrl.searchParams.set("logged_out", "true");
|
|
424
|
+
fallbackUrl.searchParams.set("client", clientKey);
|
|
425
|
+
fallbackUrl.searchParams.set("scope", scope);
|
|
426
|
+
window.location.replace(fallbackUrl.toString());
|
|
326
427
|
}
|
|
327
428
|
function handleCallback() {
|
|
328
429
|
var _a;
|
|
@@ -342,11 +443,19 @@ function handleCallback() {
|
|
|
342
443
|
callbackProcessed = false;
|
|
343
444
|
}
|
|
344
445
|
callbackProcessed = true;
|
|
446
|
+
clearLoginLock();
|
|
345
447
|
sessionStorage.removeItem("originalApp");
|
|
346
448
|
sessionStorage.removeItem("returnUrl");
|
|
347
449
|
if (error) {
|
|
348
450
|
const errorDescription = params.get("error_description") || error;
|
|
349
|
-
|
|
451
|
+
const authError = new Error(`Authentication failed: ${errorDescription}`);
|
|
452
|
+
authError.code = error;
|
|
453
|
+
authError.correlationId = getDiagnosticContext().correlationId;
|
|
454
|
+
emitAuthDiagnostic("CALLBACK_REJECTED", "FAILURE", error.toUpperCase(), {
|
|
455
|
+
clientKey: getConfig().clientKey,
|
|
456
|
+
state: params.get("state")
|
|
457
|
+
});
|
|
458
|
+
throw authError;
|
|
350
459
|
}
|
|
351
460
|
if (accessToken2) {
|
|
352
461
|
setToken(accessToken2);
|
|
@@ -369,9 +478,19 @@ function handleCallback() {
|
|
|
369
478
|
url.searchParams.delete("error_description");
|
|
370
479
|
window.history.replaceState({}, "", url);
|
|
371
480
|
console.log("\u2705 Callback processed successfully, token stored");
|
|
481
|
+
emitAuthDiagnostic("CALLBACK_COMPLETED", "SUCCESS", "NONE", {
|
|
482
|
+
clientKey: getConfig().clientKey,
|
|
483
|
+
state: params.get("state")
|
|
484
|
+
});
|
|
372
485
|
return accessToken2;
|
|
373
486
|
}
|
|
374
|
-
|
|
487
|
+
emitAuthDiagnostic("CALLBACK_REJECTED", "FAILURE", "ACCESS_TOKEN_MISSING", {
|
|
488
|
+
clientKey: getConfig().clientKey,
|
|
489
|
+
state: params.get("state")
|
|
490
|
+
});
|
|
491
|
+
const missingTokenError = new Error("No access token found in callback URL");
|
|
492
|
+
missingTokenError.code = "ACCESS_TOKEN_MISSING";
|
|
493
|
+
throw missingTokenError;
|
|
375
494
|
}
|
|
376
495
|
function resetCallbackState() {
|
|
377
496
|
callbackProcessed = false;
|
|
@@ -386,7 +505,7 @@ async function refreshToken() {
|
|
|
386
505
|
}
|
|
387
506
|
refreshInProgress = true;
|
|
388
507
|
refreshPromise = (async () => {
|
|
389
|
-
var _a, _b, _c, _d;
|
|
508
|
+
var _a, _b, _c, _d, _e, _f;
|
|
390
509
|
try {
|
|
391
510
|
const storedRefreshToken = getRefreshToken();
|
|
392
511
|
console.log("\u{1F504} Refreshing token:", {
|
|
@@ -399,6 +518,7 @@ async function refreshToken() {
|
|
|
399
518
|
credentials: "include",
|
|
400
519
|
// โ
Include httpOnly cookies (for HTTPS)
|
|
401
520
|
headers: {
|
|
521
|
+
...diagnosticHeaders(),
|
|
402
522
|
"Content-Type": "application/json"
|
|
403
523
|
}
|
|
404
524
|
};
|
|
@@ -409,8 +529,19 @@ async function refreshToken() {
|
|
|
409
529
|
const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
|
|
410
530
|
if (!response.ok) {
|
|
411
531
|
const errorText = await response.text();
|
|
412
|
-
|
|
413
|
-
|
|
532
|
+
let serverCode = null;
|
|
533
|
+
try {
|
|
534
|
+
serverCode = ((_a = JSON.parse(errorText)) == null ? void 0 : _a.error) || ((_b = JSON.parse(errorText)) == null ? void 0 : _b.code);
|
|
535
|
+
} catch {
|
|
536
|
+
}
|
|
537
|
+
emitAuthDiagnostic("TOKEN_REFRESH_REJECTED", "FAILURE", serverCode || `HTTP_${response.status}`, {
|
|
538
|
+
clientKey,
|
|
539
|
+
status: response.status
|
|
540
|
+
});
|
|
541
|
+
const refreshError = new Error(`Refresh failed: ${response.status}`);
|
|
542
|
+
refreshError.code = serverCode || `HTTP_${response.status}`;
|
|
543
|
+
refreshError.status = response.status;
|
|
544
|
+
throw refreshError;
|
|
414
545
|
}
|
|
415
546
|
const data = await response.json();
|
|
416
547
|
const { access_token, refresh_token: new_refresh_token } = data;
|
|
@@ -423,10 +554,11 @@ async function refreshToken() {
|
|
|
423
554
|
console.log("\u{1F504} New refresh token stored from rotation");
|
|
424
555
|
}
|
|
425
556
|
console.log("\u2705 Token refresh successful, listeners notified");
|
|
557
|
+
emitAuthDiagnostic("TOKEN_REFRESH_COMPLETED", "SUCCESS", "NONE", { clientKey });
|
|
426
558
|
return access_token;
|
|
427
559
|
} catch (err) {
|
|
428
560
|
console.error("\u274C Token refresh error:", err);
|
|
429
|
-
const isAuthRejection = ((
|
|
561
|
+
const isAuthRejection = ((_c = err.message) == null ? void 0 : _c.includes("401")) || ((_d = err.message) == null ? void 0 : _d.includes("403")) || ((_e = err.message) == null ? void 0 : _e.includes("invalid_grant")) || ((_f = err.message) == null ? void 0 : _f.includes("Refresh failed: 4"));
|
|
430
562
|
if (isAuthRejection) {
|
|
431
563
|
clearToken();
|
|
432
564
|
clearRefreshToken();
|
|
@@ -449,6 +581,7 @@ async function validateCurrentSession() {
|
|
|
449
581
|
const response = await fetch(`${authBaseUrl}/account/validate-session`, {
|
|
450
582
|
method: "GET",
|
|
451
583
|
headers: {
|
|
584
|
+
...diagnosticHeaders(),
|
|
452
585
|
"Authorization": `Bearer ${token}`,
|
|
453
586
|
"Content-Type": "application/json"
|
|
454
587
|
},
|
|
@@ -701,6 +834,7 @@ api.interceptors.request.use((config2) => {
|
|
|
701
834
|
if ((runtimeConfig == null ? void 0 : runtimeConfig.clientKey) && !config2.headers["X-Client-Key"]) {
|
|
702
835
|
config2.headers["X-Client-Key"] = runtimeConfig.clientKey;
|
|
703
836
|
}
|
|
837
|
+
Object.assign(config2.headers, diagnosticHeaders());
|
|
704
838
|
const token = getToken();
|
|
705
839
|
if (token) {
|
|
706
840
|
config2.headers.Authorization = `Bearer ${token}`;
|
|
@@ -719,6 +853,10 @@ api.interceptors.response.use(
|
|
|
719
853
|
return Promise.reject(error);
|
|
720
854
|
}
|
|
721
855
|
config2._retry = true;
|
|
856
|
+
emitAuthDiagnostic("API_401_REFRESH_STARTED", "PENDING", "HTTP_401", {
|
|
857
|
+
clientKey: getConfig().clientKey,
|
|
858
|
+
status: 401
|
|
859
|
+
});
|
|
722
860
|
if (!refreshPromise2) {
|
|
723
861
|
refreshPromise2 = refreshToken().then((newToken) => {
|
|
724
862
|
refreshPromise2 = null;
|
|
@@ -778,6 +916,208 @@ function isAuthenticated() {
|
|
|
778
916
|
return !!token && !isTokenExpired(token, 10);
|
|
779
917
|
}
|
|
780
918
|
|
|
919
|
+
// preferences.js
|
|
920
|
+
var PREFERENCE_DEFAULTS = Object.freeze({
|
|
921
|
+
theme: "system",
|
|
922
|
+
colorScheme: "blue",
|
|
923
|
+
density: "comfortable",
|
|
924
|
+
reducedMotion: false,
|
|
925
|
+
language: "en",
|
|
926
|
+
timezone: "Etc/UTC",
|
|
927
|
+
dateFormat: "MM/DD/YYYY",
|
|
928
|
+
timeFormat: "12h",
|
|
929
|
+
highContrast: false,
|
|
930
|
+
largeText: false
|
|
931
|
+
});
|
|
932
|
+
var ALLOWED = {
|
|
933
|
+
theme: /* @__PURE__ */ new Set(["light", "dark", "system"]),
|
|
934
|
+
density: /* @__PURE__ */ new Set(["compact", "comfortable", "spacious"]),
|
|
935
|
+
timeFormat: /* @__PURE__ */ new Set(["12h", "24h"])
|
|
936
|
+
};
|
|
937
|
+
var listeners2 = /* @__PURE__ */ new Set();
|
|
938
|
+
var cachedPreferences = null;
|
|
939
|
+
var pendingRequest = null;
|
|
940
|
+
var syncCleanup = null;
|
|
941
|
+
function extractResponseData(response) {
|
|
942
|
+
var _a;
|
|
943
|
+
return ((_a = response == null ? void 0 : response.data) == null ? void 0 : _a.data) ?? (response == null ? void 0 : response.data) ?? response ?? {};
|
|
944
|
+
}
|
|
945
|
+
function normalizePreferences(value = {}) {
|
|
946
|
+
const next = { ...PREFERENCE_DEFAULTS };
|
|
947
|
+
for (const key of Object.keys(PREFERENCE_DEFAULTS)) {
|
|
948
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
949
|
+
const candidate = value[key];
|
|
950
|
+
if (ALLOWED[key] && !ALLOWED[key].has(candidate)) continue;
|
|
951
|
+
if (typeof PREFERENCE_DEFAULTS[key] !== typeof candidate) continue;
|
|
952
|
+
next[key] = candidate;
|
|
953
|
+
}
|
|
954
|
+
return next;
|
|
955
|
+
}
|
|
956
|
+
function resolveTheme(theme, matchMedia2 = globalThis.matchMedia) {
|
|
957
|
+
if (theme === "dark" || theme === "light") return theme;
|
|
958
|
+
return typeof matchMedia2 === "function" && matchMedia2("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
959
|
+
}
|
|
960
|
+
function announce(preferences2) {
|
|
961
|
+
cachedPreferences = normalizePreferences(preferences2);
|
|
962
|
+
listeners2.forEach((listener) => listener(cachedPreferences));
|
|
963
|
+
return cachedPreferences;
|
|
964
|
+
}
|
|
965
|
+
async function getPreferences({ force = false } = {}) {
|
|
966
|
+
if (cachedPreferences && !force) return cachedPreferences;
|
|
967
|
+
if (pendingRequest) return pendingRequest;
|
|
968
|
+
pendingRequest = api_default.get("/account/preferences").then((response) => announce(extractResponseData(response))).finally(() => {
|
|
969
|
+
pendingRequest = null;
|
|
970
|
+
});
|
|
971
|
+
return pendingRequest;
|
|
972
|
+
}
|
|
973
|
+
async function updatePreferences(patch) {
|
|
974
|
+
const allowedPatch = {};
|
|
975
|
+
for (const key of Object.keys(PREFERENCE_DEFAULTS)) {
|
|
976
|
+
if (Object.hasOwn(patch, key)) allowedPatch[key] = patch[key];
|
|
977
|
+
}
|
|
978
|
+
const candidate = normalizePreferences({ ...cachedPreferences || PREFERENCE_DEFAULTS, ...allowedPatch });
|
|
979
|
+
const response = await api_default.put("/account/preferences", allowedPatch);
|
|
980
|
+
const saved = extractResponseData(response);
|
|
981
|
+
return announce(Object.keys(saved).length ? saved : candidate);
|
|
982
|
+
}
|
|
983
|
+
function subscribePreferences(listener) {
|
|
984
|
+
listeners2.add(listener);
|
|
985
|
+
if (cachedPreferences) listener(cachedPreferences);
|
|
986
|
+
return () => listeners2.delete(listener);
|
|
987
|
+
}
|
|
988
|
+
function clearPreferenceCache() {
|
|
989
|
+
cachedPreferences = null;
|
|
990
|
+
pendingRequest = null;
|
|
991
|
+
}
|
|
992
|
+
function applyPreferences(preferences2, documentRef = globalThis.document) {
|
|
993
|
+
var _a, _b, _c;
|
|
994
|
+
if (!(documentRef == null ? void 0 : documentRef.documentElement)) return;
|
|
995
|
+
const value = normalizePreferences(preferences2);
|
|
996
|
+
const root = documentRef.documentElement;
|
|
997
|
+
const resolvedTheme = resolveTheme(value.theme, (_b = (_a = documentRef.defaultView) == null ? void 0 : _a.matchMedia) == null ? void 0 : _b.bind(documentRef.defaultView));
|
|
998
|
+
root.dataset.theme = resolvedTheme;
|
|
999
|
+
root.dataset.themePreference = value.theme;
|
|
1000
|
+
root.dataset.colorScheme = value.colorScheme;
|
|
1001
|
+
root.dataset.density = value.density;
|
|
1002
|
+
root.lang = value.language;
|
|
1003
|
+
root.style.colorScheme = resolvedTheme;
|
|
1004
|
+
root.style.setProperty("--user-accent-color", {
|
|
1005
|
+
blue: "#1976d2",
|
|
1006
|
+
green: "#2e7d32",
|
|
1007
|
+
purple: "#7b1fa2",
|
|
1008
|
+
orange: "#c65d00",
|
|
1009
|
+
red: "#c62828"
|
|
1010
|
+
}[value.colorScheme] || "#1976d2");
|
|
1011
|
+
root.classList.toggle("user-reduced-motion", value.reducedMotion);
|
|
1012
|
+
root.classList.toggle("user-high-contrast", value.highContrast);
|
|
1013
|
+
root.classList.toggle("user-large-text", value.largeText);
|
|
1014
|
+
const styleId = "sso-user-preference-styles";
|
|
1015
|
+
if (!documentRef.getElementById(styleId)) {
|
|
1016
|
+
const style = documentRef.createElement("style");
|
|
1017
|
+
style.id = styleId;
|
|
1018
|
+
style.textContent = `
|
|
1019
|
+
html.user-large-text { font-size: 112.5%; }
|
|
1020
|
+
html.user-high-contrast { filter: contrast(1.15); }
|
|
1021
|
+
html[data-density="compact"] { --user-density-padding: 6px; }
|
|
1022
|
+
html[data-density="comfortable"] { --user-density-padding: 10px; }
|
|
1023
|
+
html[data-density="spacious"] { --user-density-padding: 14px; }
|
|
1024
|
+
html[data-density] .MuiListItem-root { padding-top: var(--user-density-padding); padding-bottom: var(--user-density-padding); }
|
|
1025
|
+
html.user-reduced-motion *, html.user-reduced-motion *::before, html.user-reduced-motion *::after {
|
|
1026
|
+
animation-duration: 0.01ms !important;
|
|
1027
|
+
animation-iteration-count: 1 !important;
|
|
1028
|
+
scroll-behavior: auto !important;
|
|
1029
|
+
transition-duration: 0.01ms !important;
|
|
1030
|
+
}
|
|
1031
|
+
`;
|
|
1032
|
+
(_c = documentRef.head) == null ? void 0 : _c.appendChild(style);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
function startPreferenceSync({ refreshOnFocus = true } = {}) {
|
|
1036
|
+
var _a, _b;
|
|
1037
|
+
if (syncCleanup || typeof globalThis.addEventListener !== "function") return syncCleanup || (() => {
|
|
1038
|
+
});
|
|
1039
|
+
const refresh = () => {
|
|
1040
|
+
if (!globalThis.document || globalThis.document.visibilityState === "visible") {
|
|
1041
|
+
getPreferences({ force: true }).catch(() => {
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
const onVisibility = () => refresh();
|
|
1046
|
+
(_b = (_a = globalThis.document) == null ? void 0 : _a.addEventListener) == null ? void 0 : _b.call(_a, "visibilitychange", onVisibility);
|
|
1047
|
+
if (refreshOnFocus) globalThis.addEventListener("focus", refresh);
|
|
1048
|
+
syncCleanup = () => {
|
|
1049
|
+
var _a2, _b2;
|
|
1050
|
+
(_b2 = (_a2 = globalThis.document) == null ? void 0 : _a2.removeEventListener) == null ? void 0 : _b2.call(_a2, "visibilitychange", onVisibility);
|
|
1051
|
+
if (refreshOnFocus) globalThis.removeEventListener("focus", refresh);
|
|
1052
|
+
syncCleanup = null;
|
|
1053
|
+
};
|
|
1054
|
+
return syncCleanup;
|
|
1055
|
+
}
|
|
1056
|
+
function localeFor(language) {
|
|
1057
|
+
const supported = Intl.DateTimeFormat.supportedLocalesOf([language]);
|
|
1058
|
+
return supported[0] || "en";
|
|
1059
|
+
}
|
|
1060
|
+
function fixedOffset(value) {
|
|
1061
|
+
const match = /^UTC([+-])(\d{2}):(\d{2})$/.exec(value || "");
|
|
1062
|
+
if (!match) return null;
|
|
1063
|
+
const minutes = Number(match[2]) * 60 + Number(match[3]);
|
|
1064
|
+
return (match[1] === "-" ? -1 : 1) * minutes;
|
|
1065
|
+
}
|
|
1066
|
+
function formatUserDate(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, options = {}) {
|
|
1067
|
+
const value = normalizePreferences(preferences2);
|
|
1068
|
+
const date = input instanceof Date ? input : new Date(input);
|
|
1069
|
+
const offset = fixedOffset(value.timezone);
|
|
1070
|
+
const adjusted = offset === null ? date : new Date(date.getTime() + offset * 6e4);
|
|
1071
|
+
const timeZone = offset === null ? value.timezone : "UTC";
|
|
1072
|
+
const hasExplicitParts = ["year", "month", "day", "weekday", "hour", "minute", "second", "dateStyle", "timeStyle"].some((key) => options[key] !== void 0);
|
|
1073
|
+
const dateOptions = hasExplicitParts ? options : { year: "numeric", month: "2-digit", day: "2-digit" };
|
|
1074
|
+
const formatter = new Intl.DateTimeFormat(localeFor(value.language), {
|
|
1075
|
+
...dateOptions,
|
|
1076
|
+
timeZone,
|
|
1077
|
+
...dateOptions.hour || dateOptions.timeStyle ? { hour12: value.timeFormat === "12h" } : {}
|
|
1078
|
+
});
|
|
1079
|
+
if (!hasExplicitParts) {
|
|
1080
|
+
const parts = Object.fromEntries(formatter.formatToParts(adjusted).map((part) => [part.type, part.value]));
|
|
1081
|
+
if (value.dateFormat === "DD/MM/YYYY") return `${parts.day}/${parts.month}/${parts.year}`;
|
|
1082
|
+
if (value.dateFormat === "YYYY-MM-DD") return `${parts.year}-${parts.month}-${parts.day}`;
|
|
1083
|
+
return `${parts.month}/${parts.day}/${parts.year}`;
|
|
1084
|
+
}
|
|
1085
|
+
return formatter.format(adjusted);
|
|
1086
|
+
}
|
|
1087
|
+
function formatUserNumber(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, options = {}) {
|
|
1088
|
+
const value = normalizePreferences(preferences2);
|
|
1089
|
+
return new Intl.NumberFormat(localeFor(value.language), options).format(input);
|
|
1090
|
+
}
|
|
1091
|
+
function formatUserRelativeTime(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, now = /* @__PURE__ */ new Date()) {
|
|
1092
|
+
const value = normalizePreferences(preferences2);
|
|
1093
|
+
const deltaSeconds = (new Date(input).getTime() - now.getTime()) / 1e3;
|
|
1094
|
+
const units = [
|
|
1095
|
+
["year", 31536e3],
|
|
1096
|
+
["month", 2592e3],
|
|
1097
|
+
["week", 604800],
|
|
1098
|
+
["day", 86400],
|
|
1099
|
+
["hour", 3600],
|
|
1100
|
+
["minute", 60],
|
|
1101
|
+
["second", 1]
|
|
1102
|
+
];
|
|
1103
|
+
const [unit, seconds] = units.find(([, size]) => Math.abs(deltaSeconds) >= size) || units.at(-1);
|
|
1104
|
+
return new Intl.RelativeTimeFormat(localeFor(value.language), { numeric: "auto" }).format(Math.round(deltaSeconds / seconds), unit);
|
|
1105
|
+
}
|
|
1106
|
+
var preferences = {
|
|
1107
|
+
defaults: PREFERENCE_DEFAULTS,
|
|
1108
|
+
get: getPreferences,
|
|
1109
|
+
update: updatePreferences,
|
|
1110
|
+
subscribe: subscribePreferences,
|
|
1111
|
+
clearCache: clearPreferenceCache,
|
|
1112
|
+
apply: applyPreferences,
|
|
1113
|
+
startSync: startPreferenceSync,
|
|
1114
|
+
formatDate: formatUserDate,
|
|
1115
|
+
formatNumber: formatUserNumber,
|
|
1116
|
+
formatRelativeTime: formatUserRelativeTime,
|
|
1117
|
+
normalize: normalizePreferences,
|
|
1118
|
+
resolveTheme
|
|
1119
|
+
};
|
|
1120
|
+
|
|
781
1121
|
// react/AuthProvider.jsx
|
|
782
1122
|
import React, { createContext, useState, useEffect, useRef } from "react";
|
|
783
1123
|
var AuthContext = createContext();
|
|
@@ -785,6 +1125,10 @@ var AuthContext = createContext();
|
|
|
785
1125
|
// react/useAuth.js
|
|
786
1126
|
import { useContext } from "react";
|
|
787
1127
|
|
|
1128
|
+
// react/PreferencesProvider.jsx
|
|
1129
|
+
import React2, { createContext as createContext2, useContext as useContext2, useEffect as useEffect2, useMemo, useState as useState2 } from "react";
|
|
1130
|
+
var PreferencesContext = createContext2(null);
|
|
1131
|
+
|
|
788
1132
|
// index.js
|
|
789
1133
|
var auth = {
|
|
790
1134
|
// ๐ง Config
|
|
@@ -794,6 +1138,8 @@ var auth = {
|
|
|
794
1138
|
// ๐ Core flows
|
|
795
1139
|
login,
|
|
796
1140
|
logout,
|
|
1141
|
+
logoutClient: () => logout({ scope: "client" }),
|
|
1142
|
+
logoutSso: () => logout({ scope: "sso" }),
|
|
797
1143
|
handleCallback,
|
|
798
1144
|
refreshToken,
|
|
799
1145
|
resetCallbackState,
|
|
@@ -813,6 +1159,10 @@ var auth = {
|
|
|
813
1159
|
// โ
Debug function
|
|
814
1160
|
// ๐ Authenticated API client
|
|
815
1161
|
api: api_default,
|
|
1162
|
+
preferences,
|
|
1163
|
+
// ๐ Safe authentication diagnostics
|
|
1164
|
+
getDiagnosticContext,
|
|
1165
|
+
emitAuthDiagnostic,
|
|
816
1166
|
// ๐งช Utilities
|
|
817
1167
|
decodeToken,
|
|
818
1168
|
isTokenExpired,
|
|
@@ -915,7 +1265,7 @@ var useSessionMonitor = (options = {}) => {
|
|
|
915
1265
|
staleTime: refetchInterval / 2
|
|
916
1266
|
// Consider stale at half the interval
|
|
917
1267
|
});
|
|
918
|
-
|
|
1268
|
+
useEffect3(() => {
|
|
919
1269
|
if (!enabled) return;
|
|
920
1270
|
const handleVisibilityChange = () => {
|
|
921
1271
|
if (document.visibilityState === "visible" && auth.getToken()) {
|
|
@@ -928,7 +1278,7 @@ var useSessionMonitor = (options = {}) => {
|
|
|
928
1278
|
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
929
1279
|
};
|
|
930
1280
|
}, [enabled, queryClient]);
|
|
931
|
-
|
|
1281
|
+
useEffect3(() => {
|
|
932
1282
|
if (validateOnMount && enabled && auth.getToken()) {
|
|
933
1283
|
queryClient.invalidateQueries({ queryKey: ["session-validation"] });
|
|
934
1284
|
}
|