@spidy092/auth-client 3.0.9 โ 3.1.1
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 +72 -20
- package/dist/api.cjs.map +1 -1
- package/dist/api.js +72 -20
- package/dist/api.js.map +1 -1
- package/dist/index.cjs +436 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +423 -38
- package/dist/index.js.map +1 -1
- package/dist/preferences.cjs +641 -0
- package/dist/preferences.cjs.map +1 -0
- package/dist/preferences.js +592 -0
- package/dist/preferences.js.map +1 -0
- package/dist/react/AuthProvider.cjs +111 -40
- package/dist/react/AuthProvider.cjs.map +1 -1
- package/dist/react/AuthProvider.js +111 -40
- package/dist/react/AuthProvider.js.map +1 -1
- package/dist/react/PreferencesProvider.cjs +605 -0
- package/dist/react/PreferencesProvider.cjs.map +1 -0
- package/dist/react/PreferencesProvider.js +570 -0
- package/dist/react/PreferencesProvider.js.map +1 -0
- package/dist/react/useAuth.cjs.map +1 -1
- package/dist/react/useAuth.js.map +1 -1
- package/dist/react/useSessionMonitor.cjs +360 -41
- package/dist/react/useSessionMonitor.cjs.map +1 -1
- package/dist/react/useSessionMonitor.js +359 -40
- package/dist/react/useSessionMonitor.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";
|
|
@@ -115,7 +115,7 @@ function shouldUseLocalStorage() {
|
|
|
115
115
|
return false;
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
-
function
|
|
118
|
+
function setRefreshToken2(token) {
|
|
119
119
|
if (!token) {
|
|
120
120
|
clearRefreshToken();
|
|
121
121
|
return;
|
|
@@ -232,8 +232,8 @@ function setConfig(customConfig = {}) {
|
|
|
232
232
|
// โ
Auto-detect router mode
|
|
233
233
|
isRouter: customConfig.isRouter || customConfig.clientKey === "account-ui"
|
|
234
234
|
};
|
|
235
|
+
enableRefreshTokenPersistence(config.persistRefreshToken);
|
|
235
236
|
if (config.persistRefreshToken) {
|
|
236
|
-
enableRefreshTokenPersistence(true);
|
|
237
237
|
console.log("\u{1F4E6} Refresh token persistence ENABLED (localStorage on HTTPS)");
|
|
238
238
|
}
|
|
239
239
|
console.log(`\u{1F527} Auth Client Mode: ${config.isRouter ? "ROUTER" : "CLIENT"}`, {
|
|
@@ -249,6 +249,82 @@ function isRouterMode() {
|
|
|
249
249
|
return config.isRouter;
|
|
250
250
|
}
|
|
251
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
|
+
|
|
252
328
|
// core.js
|
|
253
329
|
var callbackProcessed = false;
|
|
254
330
|
function login(clientKeyArg, redirectUriArg) {
|
|
@@ -261,14 +337,16 @@ function login(clientKeyArg, redirectUriArg) {
|
|
|
261
337
|
} = getConfig();
|
|
262
338
|
const clientKey = clientKeyArg || defaultClientKey;
|
|
263
339
|
const redirectUri = redirectUriArg || defaultRedirectUri;
|
|
264
|
-
console.log("\u{1F504} Smart Login initiated:", {
|
|
265
|
-
mode: isRouterMode() ? "ROUTER" : "CLIENT",
|
|
266
|
-
clientKey,
|
|
267
|
-
redirectUri
|
|
268
|
-
});
|
|
269
340
|
if (!clientKey || !redirectUri) {
|
|
341
|
+
emitAuthDiagnostic("LOGIN_REJECTED", "FAILURE", "CLIENT_CONFIG_MISSING", { clientKey });
|
|
270
342
|
throw new Error("Missing clientKey or redirectUri");
|
|
271
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 });
|
|
272
350
|
sessionStorage.setItem("originalApp", clientKey);
|
|
273
351
|
sessionStorage.setItem("returnUrl", redirectUri);
|
|
274
352
|
if (isRouterMode()) {
|
|
@@ -283,13 +361,9 @@ function routerLogin(clientKey, redirectUri) {
|
|
|
283
361
|
if (redirectUri) {
|
|
284
362
|
params.append("redirect_uri", redirectUri);
|
|
285
363
|
}
|
|
364
|
+
params.append("correlation_id", getDiagnosticContext().correlationId);
|
|
286
365
|
const query = params.toString();
|
|
287
366
|
const backendLoginUrl = `${authBaseUrl}/login/${clientKey}${query ? `?${query}` : ""}`;
|
|
288
|
-
console.log("\u{1F3ED} Router Login: Direct backend authentication", {
|
|
289
|
-
clientKey,
|
|
290
|
-
redirectUri,
|
|
291
|
-
backendUrl: backendLoginUrl
|
|
292
|
-
});
|
|
293
367
|
window.location.href = backendLoginUrl;
|
|
294
368
|
}
|
|
295
369
|
function clientLogin(clientKey, redirectUri) {
|
|
@@ -301,11 +375,6 @@ function clientLogin(clientKey, redirectUri) {
|
|
|
301
375
|
params.append("redirect_uri", redirectUri);
|
|
302
376
|
}
|
|
303
377
|
const centralizedLoginUrl = `${accountUiUrl}/login?${params.toString()}`;
|
|
304
|
-
console.log("\u{1F504} Client Login: Redirecting to centralized login", {
|
|
305
|
-
clientKey,
|
|
306
|
-
redirectUri,
|
|
307
|
-
centralizedUrl: centralizedLoginUrl
|
|
308
|
-
});
|
|
309
378
|
window.location.href = centralizedLoginUrl;
|
|
310
379
|
}
|
|
311
380
|
async function logout(options = {}) {
|
|
@@ -319,6 +388,7 @@ async function logout(options = {}) {
|
|
|
319
388
|
clientKey,
|
|
320
389
|
scope
|
|
321
390
|
});
|
|
391
|
+
emitAuthDiagnostic("LOGOUT_INITIATED", "PENDING", "NONE", { clientKey });
|
|
322
392
|
clearToken();
|
|
323
393
|
clearRefreshToken();
|
|
324
394
|
sessionStorage.removeItem("originalApp");
|
|
@@ -328,6 +398,7 @@ async function logout(options = {}) {
|
|
|
328
398
|
method: "POST",
|
|
329
399
|
credentials: "include",
|
|
330
400
|
headers: {
|
|
401
|
+
...diagnosticHeaders(),
|
|
331
402
|
"Authorization": token ? `Bearer ${token}` : "",
|
|
332
403
|
"Content-Type": "application/json"
|
|
333
404
|
},
|
|
@@ -338,16 +409,23 @@ async function logout(options = {}) {
|
|
|
338
409
|
}
|
|
339
410
|
const data = await response.json();
|
|
340
411
|
console.log("\u2705 Logout response:", data);
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
}
|
|
412
|
+
emitAuthDiagnostic("LOGOUT_COMPLETED", "SUCCESS", "NONE", {
|
|
413
|
+
clientKey,
|
|
414
|
+
status: response.status
|
|
415
|
+
});
|
|
345
416
|
if (data == null ? void 0 : data.keycloakLogoutUrl) {
|
|
346
417
|
window.location.replace(data.keycloakLogoutUrl);
|
|
347
418
|
return;
|
|
348
419
|
}
|
|
420
|
+
if (data == null ? void 0 : data.logoutRedirectUrl) {
|
|
421
|
+
window.location.replace(data.logoutRedirectUrl);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
349
424
|
} catch (error) {
|
|
350
425
|
console.warn("\u26A0\uFE0F Logout backend call failed:", error);
|
|
426
|
+
emitAuthDiagnostic("LOGOUT_BACKEND_FAILED", "FAILURE", "LOGOUT_REQUEST_FAILED", {
|
|
427
|
+
clientKey
|
|
428
|
+
});
|
|
351
429
|
}
|
|
352
430
|
const fallbackUrl = isRouterMode() ? new URL("/login", window.location.origin) : new URL("/login", accountUiUrl);
|
|
353
431
|
fallbackUrl.searchParams.set("logged_out", "true");
|
|
@@ -356,7 +434,6 @@ async function logout(options = {}) {
|
|
|
356
434
|
window.location.replace(fallbackUrl.toString());
|
|
357
435
|
}
|
|
358
436
|
function handleCallback() {
|
|
359
|
-
var _a;
|
|
360
437
|
const params = new URLSearchParams(window.location.search);
|
|
361
438
|
const accessToken2 = params.get("access_token");
|
|
362
439
|
const error = params.get("error");
|
|
@@ -373,24 +450,27 @@ function handleCallback() {
|
|
|
373
450
|
callbackProcessed = false;
|
|
374
451
|
}
|
|
375
452
|
callbackProcessed = true;
|
|
453
|
+
clearLoginLock();
|
|
376
454
|
sessionStorage.removeItem("originalApp");
|
|
377
455
|
sessionStorage.removeItem("returnUrl");
|
|
378
456
|
if (error) {
|
|
379
457
|
const errorDescription = params.get("error_description") || error;
|
|
380
|
-
|
|
458
|
+
const authError = new Error(`Authentication failed: ${errorDescription}`);
|
|
459
|
+
authError.code = error;
|
|
460
|
+
authError.correlationId = getDiagnosticContext().correlationId;
|
|
461
|
+
emitAuthDiagnostic("CALLBACK_REJECTED", "FAILURE", error.toUpperCase(), {
|
|
462
|
+
clientKey: getConfig().clientKey,
|
|
463
|
+
state: params.get("state")
|
|
464
|
+
});
|
|
465
|
+
throw authError;
|
|
381
466
|
}
|
|
382
467
|
if (accessToken2) {
|
|
383
468
|
setToken(accessToken2);
|
|
384
469
|
const refreshTokenInUrl = params.get("refresh_token");
|
|
385
470
|
if (refreshTokenInUrl) {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
console.log(`\u{1F4E6} Storing refresh token from callback URL (${persistRefreshToken ? "persistence enabled" : "HTTP dev mode"})`);
|
|
390
|
-
setRefreshToken(refreshTokenInUrl);
|
|
391
|
-
} else {
|
|
392
|
-
console.log("\u{1F512} HTTPS mode: Refresh token is in httpOnly cookie (ignoring URL param)");
|
|
393
|
-
}
|
|
471
|
+
emitAuthDiagnostic("CALLBACK_REFRESH_TOKEN_IGNORED", "WARNING", "REFRESH_TOKEN_IN_URL", {
|
|
472
|
+
clientKey: getConfig().clientKey
|
|
473
|
+
});
|
|
394
474
|
}
|
|
395
475
|
const url = new URL(window.location);
|
|
396
476
|
url.searchParams.delete("access_token");
|
|
@@ -400,9 +480,19 @@ function handleCallback() {
|
|
|
400
480
|
url.searchParams.delete("error_description");
|
|
401
481
|
window.history.replaceState({}, "", url);
|
|
402
482
|
console.log("\u2705 Callback processed successfully, token stored");
|
|
483
|
+
emitAuthDiagnostic("CALLBACK_COMPLETED", "SUCCESS", "NONE", {
|
|
484
|
+
clientKey: getConfig().clientKey,
|
|
485
|
+
state: params.get("state")
|
|
486
|
+
});
|
|
403
487
|
return accessToken2;
|
|
404
488
|
}
|
|
405
|
-
|
|
489
|
+
emitAuthDiagnostic("CALLBACK_REJECTED", "FAILURE", "ACCESS_TOKEN_MISSING", {
|
|
490
|
+
clientKey: getConfig().clientKey,
|
|
491
|
+
state: params.get("state")
|
|
492
|
+
});
|
|
493
|
+
const missingTokenError = new Error("No access token found in callback URL");
|
|
494
|
+
missingTokenError.code = "ACCESS_TOKEN_MISSING";
|
|
495
|
+
throw missingTokenError;
|
|
406
496
|
}
|
|
407
497
|
function resetCallbackState() {
|
|
408
498
|
callbackProcessed = false;
|
|
@@ -417,7 +507,7 @@ async function refreshToken() {
|
|
|
417
507
|
}
|
|
418
508
|
refreshInProgress = true;
|
|
419
509
|
refreshPromise = (async () => {
|
|
420
|
-
var _a, _b, _c, _d;
|
|
510
|
+
var _a, _b, _c, _d, _e, _f;
|
|
421
511
|
try {
|
|
422
512
|
const storedRefreshToken = getRefreshToken();
|
|
423
513
|
console.log("\u{1F504} Refreshing token:", {
|
|
@@ -430,6 +520,7 @@ async function refreshToken() {
|
|
|
430
520
|
credentials: "include",
|
|
431
521
|
// โ
Include httpOnly cookies (for HTTPS)
|
|
432
522
|
headers: {
|
|
523
|
+
...diagnosticHeaders(),
|
|
433
524
|
"Content-Type": "application/json"
|
|
434
525
|
}
|
|
435
526
|
};
|
|
@@ -440,8 +531,19 @@ async function refreshToken() {
|
|
|
440
531
|
const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
|
|
441
532
|
if (!response.ok) {
|
|
442
533
|
const errorText = await response.text();
|
|
443
|
-
|
|
444
|
-
|
|
534
|
+
let serverCode = null;
|
|
535
|
+
try {
|
|
536
|
+
serverCode = ((_a = JSON.parse(errorText)) == null ? void 0 : _a.error) || ((_b = JSON.parse(errorText)) == null ? void 0 : _b.code);
|
|
537
|
+
} catch {
|
|
538
|
+
}
|
|
539
|
+
emitAuthDiagnostic("TOKEN_REFRESH_REJECTED", "FAILURE", serverCode || `HTTP_${response.status}`, {
|
|
540
|
+
clientKey,
|
|
541
|
+
status: response.status
|
|
542
|
+
});
|
|
543
|
+
const refreshError = new Error(`Refresh failed: ${response.status}`);
|
|
544
|
+
refreshError.code = serverCode || `HTTP_${response.status}`;
|
|
545
|
+
refreshError.status = response.status;
|
|
546
|
+
throw refreshError;
|
|
445
547
|
}
|
|
446
548
|
const data = await response.json();
|
|
447
549
|
const { access_token, refresh_token: new_refresh_token } = data;
|
|
@@ -454,10 +556,11 @@ async function refreshToken() {
|
|
|
454
556
|
console.log("\u{1F504} New refresh token stored from rotation");
|
|
455
557
|
}
|
|
456
558
|
console.log("\u2705 Token refresh successful, listeners notified");
|
|
559
|
+
emitAuthDiagnostic("TOKEN_REFRESH_COMPLETED", "SUCCESS", "NONE", { clientKey });
|
|
457
560
|
return access_token;
|
|
458
561
|
} catch (err) {
|
|
459
562
|
console.error("\u274C Token refresh error:", err);
|
|
460
|
-
const isAuthRejection = ((
|
|
563
|
+
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"));
|
|
461
564
|
if (isAuthRejection) {
|
|
462
565
|
clearToken();
|
|
463
566
|
clearRefreshToken();
|
|
@@ -480,6 +583,7 @@ async function validateCurrentSession() {
|
|
|
480
583
|
const response = await fetch(`${authBaseUrl}/account/validate-session`, {
|
|
481
584
|
method: "GET",
|
|
482
585
|
headers: {
|
|
586
|
+
...diagnosticHeaders(),
|
|
483
587
|
"Authorization": `Bearer ${token}`,
|
|
484
588
|
"Content-Type": "application/json"
|
|
485
589
|
},
|
|
@@ -732,6 +836,7 @@ api.interceptors.request.use((config2) => {
|
|
|
732
836
|
if ((runtimeConfig == null ? void 0 : runtimeConfig.clientKey) && !config2.headers["X-Client-Key"]) {
|
|
733
837
|
config2.headers["X-Client-Key"] = runtimeConfig.clientKey;
|
|
734
838
|
}
|
|
839
|
+
Object.assign(config2.headers, diagnosticHeaders());
|
|
735
840
|
const token = getToken();
|
|
736
841
|
if (token) {
|
|
737
842
|
config2.headers.Authorization = `Bearer ${token}`;
|
|
@@ -750,6 +855,10 @@ api.interceptors.response.use(
|
|
|
750
855
|
return Promise.reject(error);
|
|
751
856
|
}
|
|
752
857
|
config2._retry = true;
|
|
858
|
+
emitAuthDiagnostic("API_401_REFRESH_STARTED", "PENDING", "HTTP_401", {
|
|
859
|
+
clientKey: getConfig().clientKey,
|
|
860
|
+
status: 401
|
|
861
|
+
});
|
|
753
862
|
if (!refreshPromise2) {
|
|
754
863
|
refreshPromise2 = refreshToken().then((newToken) => {
|
|
755
864
|
refreshPromise2 = null;
|
|
@@ -809,6 +918,208 @@ function isAuthenticated() {
|
|
|
809
918
|
return !!token && !isTokenExpired(token, 10);
|
|
810
919
|
}
|
|
811
920
|
|
|
921
|
+
// preferences.js
|
|
922
|
+
var PREFERENCE_DEFAULTS = Object.freeze({
|
|
923
|
+
theme: "system",
|
|
924
|
+
colorScheme: "blue",
|
|
925
|
+
density: "comfortable",
|
|
926
|
+
reducedMotion: false,
|
|
927
|
+
language: "en",
|
|
928
|
+
timezone: "Etc/UTC",
|
|
929
|
+
dateFormat: "MM/DD/YYYY",
|
|
930
|
+
timeFormat: "12h",
|
|
931
|
+
highContrast: false,
|
|
932
|
+
largeText: false
|
|
933
|
+
});
|
|
934
|
+
var ALLOWED = {
|
|
935
|
+
theme: /* @__PURE__ */ new Set(["light", "dark", "system"]),
|
|
936
|
+
density: /* @__PURE__ */ new Set(["compact", "comfortable", "spacious"]),
|
|
937
|
+
timeFormat: /* @__PURE__ */ new Set(["12h", "24h"])
|
|
938
|
+
};
|
|
939
|
+
var listeners2 = /* @__PURE__ */ new Set();
|
|
940
|
+
var cachedPreferences = null;
|
|
941
|
+
var pendingRequest = null;
|
|
942
|
+
var syncCleanup = null;
|
|
943
|
+
function extractResponseData(response) {
|
|
944
|
+
var _a;
|
|
945
|
+
return ((_a = response == null ? void 0 : response.data) == null ? void 0 : _a.data) ?? (response == null ? void 0 : response.data) ?? response ?? {};
|
|
946
|
+
}
|
|
947
|
+
function normalizePreferences(value = {}) {
|
|
948
|
+
const next = { ...PREFERENCE_DEFAULTS };
|
|
949
|
+
for (const key of Object.keys(PREFERENCE_DEFAULTS)) {
|
|
950
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
951
|
+
const candidate = value[key];
|
|
952
|
+
if (ALLOWED[key] && !ALLOWED[key].has(candidate)) continue;
|
|
953
|
+
if (typeof PREFERENCE_DEFAULTS[key] !== typeof candidate) continue;
|
|
954
|
+
next[key] = candidate;
|
|
955
|
+
}
|
|
956
|
+
return next;
|
|
957
|
+
}
|
|
958
|
+
function resolveTheme(theme, matchMedia2 = globalThis.matchMedia) {
|
|
959
|
+
if (theme === "dark" || theme === "light") return theme;
|
|
960
|
+
return typeof matchMedia2 === "function" && matchMedia2("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
961
|
+
}
|
|
962
|
+
function announce(preferences2) {
|
|
963
|
+
cachedPreferences = normalizePreferences(preferences2);
|
|
964
|
+
listeners2.forEach((listener) => listener(cachedPreferences));
|
|
965
|
+
return cachedPreferences;
|
|
966
|
+
}
|
|
967
|
+
async function getPreferences({ force = false } = {}) {
|
|
968
|
+
if (cachedPreferences && !force) return cachedPreferences;
|
|
969
|
+
if (pendingRequest) return pendingRequest;
|
|
970
|
+
pendingRequest = api_default.get("/account/preferences").then((response) => announce(extractResponseData(response))).finally(() => {
|
|
971
|
+
pendingRequest = null;
|
|
972
|
+
});
|
|
973
|
+
return pendingRequest;
|
|
974
|
+
}
|
|
975
|
+
async function updatePreferences(patch) {
|
|
976
|
+
const allowedPatch = {};
|
|
977
|
+
for (const key of Object.keys(PREFERENCE_DEFAULTS)) {
|
|
978
|
+
if (Object.hasOwn(patch, key)) allowedPatch[key] = patch[key];
|
|
979
|
+
}
|
|
980
|
+
const candidate = normalizePreferences({ ...cachedPreferences || PREFERENCE_DEFAULTS, ...allowedPatch });
|
|
981
|
+
const response = await api_default.put("/account/preferences", allowedPatch);
|
|
982
|
+
const saved = extractResponseData(response);
|
|
983
|
+
return announce(Object.keys(saved).length ? saved : candidate);
|
|
984
|
+
}
|
|
985
|
+
function subscribePreferences(listener) {
|
|
986
|
+
listeners2.add(listener);
|
|
987
|
+
if (cachedPreferences) listener(cachedPreferences);
|
|
988
|
+
return () => listeners2.delete(listener);
|
|
989
|
+
}
|
|
990
|
+
function clearPreferenceCache() {
|
|
991
|
+
cachedPreferences = null;
|
|
992
|
+
pendingRequest = null;
|
|
993
|
+
}
|
|
994
|
+
function applyPreferences(preferences2, documentRef = globalThis.document) {
|
|
995
|
+
var _a, _b, _c;
|
|
996
|
+
if (!(documentRef == null ? void 0 : documentRef.documentElement)) return;
|
|
997
|
+
const value = normalizePreferences(preferences2);
|
|
998
|
+
const root = documentRef.documentElement;
|
|
999
|
+
const resolvedTheme = resolveTheme(value.theme, (_b = (_a = documentRef.defaultView) == null ? void 0 : _a.matchMedia) == null ? void 0 : _b.bind(documentRef.defaultView));
|
|
1000
|
+
root.dataset.theme = resolvedTheme;
|
|
1001
|
+
root.dataset.themePreference = value.theme;
|
|
1002
|
+
root.dataset.colorScheme = value.colorScheme;
|
|
1003
|
+
root.dataset.density = value.density;
|
|
1004
|
+
root.lang = value.language;
|
|
1005
|
+
root.style.colorScheme = resolvedTheme;
|
|
1006
|
+
root.style.setProperty("--user-accent-color", {
|
|
1007
|
+
blue: "#1976d2",
|
|
1008
|
+
green: "#2e7d32",
|
|
1009
|
+
purple: "#7b1fa2",
|
|
1010
|
+
orange: "#c65d00",
|
|
1011
|
+
red: "#c62828"
|
|
1012
|
+
}[value.colorScheme] || "#1976d2");
|
|
1013
|
+
root.classList.toggle("user-reduced-motion", value.reducedMotion);
|
|
1014
|
+
root.classList.toggle("user-high-contrast", value.highContrast);
|
|
1015
|
+
root.classList.toggle("user-large-text", value.largeText);
|
|
1016
|
+
const styleId = "sso-user-preference-styles";
|
|
1017
|
+
if (!documentRef.getElementById(styleId)) {
|
|
1018
|
+
const style = documentRef.createElement("style");
|
|
1019
|
+
style.id = styleId;
|
|
1020
|
+
style.textContent = `
|
|
1021
|
+
html.user-large-text { font-size: 112.5%; }
|
|
1022
|
+
html.user-high-contrast { filter: contrast(1.15); }
|
|
1023
|
+
html[data-density="compact"] { --user-density-padding: 6px; }
|
|
1024
|
+
html[data-density="comfortable"] { --user-density-padding: 10px; }
|
|
1025
|
+
html[data-density="spacious"] { --user-density-padding: 14px; }
|
|
1026
|
+
html[data-density] .MuiListItem-root { padding-top: var(--user-density-padding); padding-bottom: var(--user-density-padding); }
|
|
1027
|
+
html.user-reduced-motion *, html.user-reduced-motion *::before, html.user-reduced-motion *::after {
|
|
1028
|
+
animation-duration: 0.01ms !important;
|
|
1029
|
+
animation-iteration-count: 1 !important;
|
|
1030
|
+
scroll-behavior: auto !important;
|
|
1031
|
+
transition-duration: 0.01ms !important;
|
|
1032
|
+
}
|
|
1033
|
+
`;
|
|
1034
|
+
(_c = documentRef.head) == null ? void 0 : _c.appendChild(style);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
function startPreferenceSync({ refreshOnFocus = true } = {}) {
|
|
1038
|
+
var _a, _b;
|
|
1039
|
+
if (syncCleanup || typeof globalThis.addEventListener !== "function") return syncCleanup || (() => {
|
|
1040
|
+
});
|
|
1041
|
+
const refresh = () => {
|
|
1042
|
+
if (!globalThis.document || globalThis.document.visibilityState === "visible") {
|
|
1043
|
+
getPreferences({ force: true }).catch(() => {
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
const onVisibility = () => refresh();
|
|
1048
|
+
(_b = (_a = globalThis.document) == null ? void 0 : _a.addEventListener) == null ? void 0 : _b.call(_a, "visibilitychange", onVisibility);
|
|
1049
|
+
if (refreshOnFocus) globalThis.addEventListener("focus", refresh);
|
|
1050
|
+
syncCleanup = () => {
|
|
1051
|
+
var _a2, _b2;
|
|
1052
|
+
(_b2 = (_a2 = globalThis.document) == null ? void 0 : _a2.removeEventListener) == null ? void 0 : _b2.call(_a2, "visibilitychange", onVisibility);
|
|
1053
|
+
if (refreshOnFocus) globalThis.removeEventListener("focus", refresh);
|
|
1054
|
+
syncCleanup = null;
|
|
1055
|
+
};
|
|
1056
|
+
return syncCleanup;
|
|
1057
|
+
}
|
|
1058
|
+
function localeFor(language) {
|
|
1059
|
+
const supported = Intl.DateTimeFormat.supportedLocalesOf([language]);
|
|
1060
|
+
return supported[0] || "en";
|
|
1061
|
+
}
|
|
1062
|
+
function fixedOffset(value) {
|
|
1063
|
+
const match = /^UTC([+-])(\d{2}):(\d{2})$/.exec(value || "");
|
|
1064
|
+
if (!match) return null;
|
|
1065
|
+
const minutes = Number(match[2]) * 60 + Number(match[3]);
|
|
1066
|
+
return (match[1] === "-" ? -1 : 1) * minutes;
|
|
1067
|
+
}
|
|
1068
|
+
function formatUserDate(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, options = {}) {
|
|
1069
|
+
const value = normalizePreferences(preferences2);
|
|
1070
|
+
const date = input instanceof Date ? input : new Date(input);
|
|
1071
|
+
const offset = fixedOffset(value.timezone);
|
|
1072
|
+
const adjusted = offset === null ? date : new Date(date.getTime() + offset * 6e4);
|
|
1073
|
+
const timeZone = offset === null ? value.timezone : "UTC";
|
|
1074
|
+
const hasExplicitParts = ["year", "month", "day", "weekday", "hour", "minute", "second", "dateStyle", "timeStyle"].some((key) => options[key] !== void 0);
|
|
1075
|
+
const dateOptions = hasExplicitParts ? options : { year: "numeric", month: "2-digit", day: "2-digit" };
|
|
1076
|
+
const formatter = new Intl.DateTimeFormat(localeFor(value.language), {
|
|
1077
|
+
...dateOptions,
|
|
1078
|
+
timeZone,
|
|
1079
|
+
...dateOptions.hour || dateOptions.timeStyle ? { hour12: value.timeFormat === "12h" } : {}
|
|
1080
|
+
});
|
|
1081
|
+
if (!hasExplicitParts) {
|
|
1082
|
+
const parts = Object.fromEntries(formatter.formatToParts(adjusted).map((part) => [part.type, part.value]));
|
|
1083
|
+
if (value.dateFormat === "DD/MM/YYYY") return `${parts.day}/${parts.month}/${parts.year}`;
|
|
1084
|
+
if (value.dateFormat === "YYYY-MM-DD") return `${parts.year}-${parts.month}-${parts.day}`;
|
|
1085
|
+
return `${parts.month}/${parts.day}/${parts.year}`;
|
|
1086
|
+
}
|
|
1087
|
+
return formatter.format(adjusted);
|
|
1088
|
+
}
|
|
1089
|
+
function formatUserNumber(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, options = {}) {
|
|
1090
|
+
const value = normalizePreferences(preferences2);
|
|
1091
|
+
return new Intl.NumberFormat(localeFor(value.language), options).format(input);
|
|
1092
|
+
}
|
|
1093
|
+
function formatUserRelativeTime(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, now = /* @__PURE__ */ new Date()) {
|
|
1094
|
+
const value = normalizePreferences(preferences2);
|
|
1095
|
+
const deltaSeconds = (new Date(input).getTime() - now.getTime()) / 1e3;
|
|
1096
|
+
const units = [
|
|
1097
|
+
["year", 31536e3],
|
|
1098
|
+
["month", 2592e3],
|
|
1099
|
+
["week", 604800],
|
|
1100
|
+
["day", 86400],
|
|
1101
|
+
["hour", 3600],
|
|
1102
|
+
["minute", 60],
|
|
1103
|
+
["second", 1]
|
|
1104
|
+
];
|
|
1105
|
+
const [unit, seconds] = units.find(([, size]) => Math.abs(deltaSeconds) >= size) || units.at(-1);
|
|
1106
|
+
return new Intl.RelativeTimeFormat(localeFor(value.language), { numeric: "auto" }).format(Math.round(deltaSeconds / seconds), unit);
|
|
1107
|
+
}
|
|
1108
|
+
var preferences = {
|
|
1109
|
+
defaults: PREFERENCE_DEFAULTS,
|
|
1110
|
+
get: getPreferences,
|
|
1111
|
+
update: updatePreferences,
|
|
1112
|
+
subscribe: subscribePreferences,
|
|
1113
|
+
clearCache: clearPreferenceCache,
|
|
1114
|
+
apply: applyPreferences,
|
|
1115
|
+
startSync: startPreferenceSync,
|
|
1116
|
+
formatDate: formatUserDate,
|
|
1117
|
+
formatNumber: formatUserNumber,
|
|
1118
|
+
formatRelativeTime: formatUserRelativeTime,
|
|
1119
|
+
normalize: normalizePreferences,
|
|
1120
|
+
resolveTheme
|
|
1121
|
+
};
|
|
1122
|
+
|
|
812
1123
|
// react/AuthProvider.jsx
|
|
813
1124
|
import React, { createContext, useState, useEffect, useRef } from "react";
|
|
814
1125
|
var AuthContext = createContext();
|
|
@@ -816,6 +1127,10 @@ var AuthContext = createContext();
|
|
|
816
1127
|
// react/useAuth.js
|
|
817
1128
|
import { useContext } from "react";
|
|
818
1129
|
|
|
1130
|
+
// react/PreferencesProvider.jsx
|
|
1131
|
+
import React2, { createContext as createContext2, useContext as useContext2, useEffect as useEffect2, useMemo, useState as useState2 } from "react";
|
|
1132
|
+
var PreferencesContext = createContext2(null);
|
|
1133
|
+
|
|
819
1134
|
// index.js
|
|
820
1135
|
var auth = {
|
|
821
1136
|
// ๐ง Config
|
|
@@ -835,7 +1150,7 @@ var auth = {
|
|
|
835
1150
|
getToken,
|
|
836
1151
|
setToken,
|
|
837
1152
|
clearToken,
|
|
838
|
-
setRefreshToken,
|
|
1153
|
+
setRefreshToken: setRefreshToken2,
|
|
839
1154
|
// โ
Refresh token for HTTP dev
|
|
840
1155
|
getRefreshToken,
|
|
841
1156
|
clearRefreshToken,
|
|
@@ -846,6 +1161,10 @@ var auth = {
|
|
|
846
1161
|
// โ
Debug function
|
|
847
1162
|
// ๐ Authenticated API client
|
|
848
1163
|
api: api_default,
|
|
1164
|
+
preferences,
|
|
1165
|
+
// ๐ Safe authentication diagnostics
|
|
1166
|
+
getDiagnosticContext,
|
|
1167
|
+
emitAuthDiagnostic,
|
|
849
1168
|
// ๐งช Utilities
|
|
850
1169
|
decodeToken,
|
|
851
1170
|
isTokenExpired,
|
|
@@ -948,7 +1267,7 @@ var useSessionMonitor = (options = {}) => {
|
|
|
948
1267
|
staleTime: refetchInterval / 2
|
|
949
1268
|
// Consider stale at half the interval
|
|
950
1269
|
});
|
|
951
|
-
|
|
1270
|
+
useEffect3(() => {
|
|
952
1271
|
if (!enabled) return;
|
|
953
1272
|
const handleVisibilityChange = () => {
|
|
954
1273
|
if (document.visibilityState === "visible" && auth.getToken()) {
|
|
@@ -961,7 +1280,7 @@ var useSessionMonitor = (options = {}) => {
|
|
|
961
1280
|
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
962
1281
|
};
|
|
963
1282
|
}, [enabled, queryClient]);
|
|
964
|
-
|
|
1283
|
+
useEffect3(() => {
|
|
965
1284
|
if (validateOnMount && enabled && auth.getToken()) {
|
|
966
1285
|
queryClient.invalidateQueries({ queryKey: ["session-validation"] });
|
|
967
1286
|
}
|