@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
|
@@ -33,7 +33,7 @@ __export(useSessionMonitor_exports, {
|
|
|
33
33
|
});
|
|
34
34
|
module.exports = __toCommonJS(useSessionMonitor_exports);
|
|
35
35
|
var import_react_query = require("@tanstack/react-query");
|
|
36
|
-
var
|
|
36
|
+
var import_react4 = require("react");
|
|
37
37
|
|
|
38
38
|
// token.js
|
|
39
39
|
var import_jwt_decode = require("jwt-decode");
|
|
@@ -148,7 +148,7 @@ function shouldUseLocalStorage() {
|
|
|
148
148
|
return false;
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
|
-
function
|
|
151
|
+
function setRefreshToken2(token) {
|
|
152
152
|
if (!token) {
|
|
153
153
|
clearRefreshToken();
|
|
154
154
|
return;
|
|
@@ -265,8 +265,8 @@ function setConfig(customConfig = {}) {
|
|
|
265
265
|
// โ
Auto-detect router mode
|
|
266
266
|
isRouter: customConfig.isRouter || customConfig.clientKey === "account-ui"
|
|
267
267
|
};
|
|
268
|
+
enableRefreshTokenPersistence(config.persistRefreshToken);
|
|
268
269
|
if (config.persistRefreshToken) {
|
|
269
|
-
enableRefreshTokenPersistence(true);
|
|
270
270
|
console.log("\u{1F4E6} Refresh token persistence ENABLED (localStorage on HTTPS)");
|
|
271
271
|
}
|
|
272
272
|
console.log(`\u{1F527} Auth Client Mode: ${config.isRouter ? "ROUTER" : "CLIENT"}`, {
|
|
@@ -282,6 +282,82 @@ function isRouterMode() {
|
|
|
282
282
|
return config.isRouter;
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
+
// diagnostics.js
|
|
286
|
+
var STORAGE_KEY = "auth_diagnostic_context";
|
|
287
|
+
var LOGIN_LOCK_KEY = "auth_login_lock";
|
|
288
|
+
var randomId = () => {
|
|
289
|
+
var _a;
|
|
290
|
+
if ((_a = globalThis.crypto) == null ? void 0 : _a.randomUUID) return globalThis.crypto.randomUUID();
|
|
291
|
+
return `auth-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
292
|
+
};
|
|
293
|
+
var fingerprint = async (value) => {
|
|
294
|
+
var _a;
|
|
295
|
+
if (!value || !((_a = globalThis.crypto) == null ? void 0 : _a.subtle)) return null;
|
|
296
|
+
const bytes = new TextEncoder().encode(String(value));
|
|
297
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
|
|
298
|
+
return [...new Uint8Array(digest)].slice(0, 6).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
299
|
+
};
|
|
300
|
+
function getDiagnosticContext() {
|
|
301
|
+
try {
|
|
302
|
+
const existing = JSON.parse(sessionStorage.getItem(STORAGE_KEY) || "null");
|
|
303
|
+
if (existing == null ? void 0 : existing.correlationId) return existing;
|
|
304
|
+
} catch {
|
|
305
|
+
}
|
|
306
|
+
const context = { correlationId: randomId(), startedAt: Date.now() };
|
|
307
|
+
try {
|
|
308
|
+
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(context));
|
|
309
|
+
} catch {
|
|
310
|
+
}
|
|
311
|
+
return context;
|
|
312
|
+
}
|
|
313
|
+
function resetDiagnosticContext() {
|
|
314
|
+
const context = { correlationId: randomId(), startedAt: Date.now() };
|
|
315
|
+
try {
|
|
316
|
+
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(context));
|
|
317
|
+
} catch {
|
|
318
|
+
}
|
|
319
|
+
return context;
|
|
320
|
+
}
|
|
321
|
+
function diagnosticHeaders() {
|
|
322
|
+
const { correlationId } = getDiagnosticContext();
|
|
323
|
+
return { "X-Correlation-ID": correlationId, "X-Request-ID": correlationId };
|
|
324
|
+
}
|
|
325
|
+
function acquireLoginLock(clientKey, redirectUri, ttlMs = 5e3) {
|
|
326
|
+
const now = Date.now();
|
|
327
|
+
const signature = `${clientKey}|${redirectUri}`;
|
|
328
|
+
try {
|
|
329
|
+
const current = JSON.parse(sessionStorage.getItem(LOGIN_LOCK_KEY) || "null");
|
|
330
|
+
if ((current == null ? void 0 : current.signature) === signature && now - current.createdAt < ttlMs) return false;
|
|
331
|
+
sessionStorage.setItem(LOGIN_LOCK_KEY, JSON.stringify({ signature, createdAt: now }));
|
|
332
|
+
} catch {
|
|
333
|
+
}
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
function clearLoginLock() {
|
|
337
|
+
try {
|
|
338
|
+
sessionStorage.removeItem(LOGIN_LOCK_KEY);
|
|
339
|
+
} catch {
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
async function emitAuthDiagnostic(event, outcome, reasonCode, details = {}) {
|
|
343
|
+
const context = getDiagnosticContext();
|
|
344
|
+
const safe = {
|
|
345
|
+
eventType: "auth_client_diagnostic",
|
|
346
|
+
event,
|
|
347
|
+
outcome,
|
|
348
|
+
reasonCode,
|
|
349
|
+
correlationId: context.correlationId,
|
|
350
|
+
elapsedMs: Date.now() - context.startedAt,
|
|
351
|
+
clientKey: details.clientKey || null,
|
|
352
|
+
status: details.status || null,
|
|
353
|
+
stateFingerprint: await fingerprint(details.state),
|
|
354
|
+
online: typeof navigator === "undefined" ? null : navigator.onLine
|
|
355
|
+
};
|
|
356
|
+
const method = outcome === "FAILURE" ? "error" : outcome === "WARNING" ? "warn" : "info";
|
|
357
|
+
console[method]("[auth-client]", safe);
|
|
358
|
+
return safe;
|
|
359
|
+
}
|
|
360
|
+
|
|
285
361
|
// core.js
|
|
286
362
|
var callbackProcessed = false;
|
|
287
363
|
function login(clientKeyArg, redirectUriArg) {
|
|
@@ -294,14 +370,16 @@ function login(clientKeyArg, redirectUriArg) {
|
|
|
294
370
|
} = getConfig();
|
|
295
371
|
const clientKey = clientKeyArg || defaultClientKey;
|
|
296
372
|
const redirectUri = redirectUriArg || defaultRedirectUri;
|
|
297
|
-
console.log("\u{1F504} Smart Login initiated:", {
|
|
298
|
-
mode: isRouterMode() ? "ROUTER" : "CLIENT",
|
|
299
|
-
clientKey,
|
|
300
|
-
redirectUri
|
|
301
|
-
});
|
|
302
373
|
if (!clientKey || !redirectUri) {
|
|
374
|
+
emitAuthDiagnostic("LOGIN_REJECTED", "FAILURE", "CLIENT_CONFIG_MISSING", { clientKey });
|
|
303
375
|
throw new Error("Missing clientKey or redirectUri");
|
|
304
376
|
}
|
|
377
|
+
if (!acquireLoginLock(clientKey, redirectUri)) {
|
|
378
|
+
emitAuthDiagnostic("LOGIN_DUPLICATE_SUPPRESSED", "WARNING", "LOGIN_ALREADY_IN_PROGRESS", { clientKey });
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
resetDiagnosticContext();
|
|
382
|
+
emitAuthDiagnostic("LOGIN_INITIATED", "PENDING", "NONE", { clientKey });
|
|
305
383
|
sessionStorage.setItem("originalApp", clientKey);
|
|
306
384
|
sessionStorage.setItem("returnUrl", redirectUri);
|
|
307
385
|
if (isRouterMode()) {
|
|
@@ -316,13 +394,9 @@ function routerLogin(clientKey, redirectUri) {
|
|
|
316
394
|
if (redirectUri) {
|
|
317
395
|
params.append("redirect_uri", redirectUri);
|
|
318
396
|
}
|
|
397
|
+
params.append("correlation_id", getDiagnosticContext().correlationId);
|
|
319
398
|
const query = params.toString();
|
|
320
399
|
const backendLoginUrl = `${authBaseUrl}/login/${clientKey}${query ? `?${query}` : ""}`;
|
|
321
|
-
console.log("\u{1F3ED} Router Login: Direct backend authentication", {
|
|
322
|
-
clientKey,
|
|
323
|
-
redirectUri,
|
|
324
|
-
backendUrl: backendLoginUrl
|
|
325
|
-
});
|
|
326
400
|
window.location.href = backendLoginUrl;
|
|
327
401
|
}
|
|
328
402
|
function clientLogin(clientKey, redirectUri) {
|
|
@@ -334,11 +408,6 @@ function clientLogin(clientKey, redirectUri) {
|
|
|
334
408
|
params.append("redirect_uri", redirectUri);
|
|
335
409
|
}
|
|
336
410
|
const centralizedLoginUrl = `${accountUiUrl}/login?${params.toString()}`;
|
|
337
|
-
console.log("\u{1F504} Client Login: Redirecting to centralized login", {
|
|
338
|
-
clientKey,
|
|
339
|
-
redirectUri,
|
|
340
|
-
centralizedUrl: centralizedLoginUrl
|
|
341
|
-
});
|
|
342
411
|
window.location.href = centralizedLoginUrl;
|
|
343
412
|
}
|
|
344
413
|
async function logout(options = {}) {
|
|
@@ -352,6 +421,7 @@ async function logout(options = {}) {
|
|
|
352
421
|
clientKey,
|
|
353
422
|
scope
|
|
354
423
|
});
|
|
424
|
+
emitAuthDiagnostic("LOGOUT_INITIATED", "PENDING", "NONE", { clientKey });
|
|
355
425
|
clearToken();
|
|
356
426
|
clearRefreshToken();
|
|
357
427
|
sessionStorage.removeItem("originalApp");
|
|
@@ -361,6 +431,7 @@ async function logout(options = {}) {
|
|
|
361
431
|
method: "POST",
|
|
362
432
|
credentials: "include",
|
|
363
433
|
headers: {
|
|
434
|
+
...diagnosticHeaders(),
|
|
364
435
|
"Authorization": token ? `Bearer ${token}` : "",
|
|
365
436
|
"Content-Type": "application/json"
|
|
366
437
|
},
|
|
@@ -371,16 +442,23 @@ async function logout(options = {}) {
|
|
|
371
442
|
}
|
|
372
443
|
const data = await response.json();
|
|
373
444
|
console.log("\u2705 Logout response:", data);
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
}
|
|
445
|
+
emitAuthDiagnostic("LOGOUT_COMPLETED", "SUCCESS", "NONE", {
|
|
446
|
+
clientKey,
|
|
447
|
+
status: response.status
|
|
448
|
+
});
|
|
378
449
|
if (data == null ? void 0 : data.keycloakLogoutUrl) {
|
|
379
450
|
window.location.replace(data.keycloakLogoutUrl);
|
|
380
451
|
return;
|
|
381
452
|
}
|
|
453
|
+
if (data == null ? void 0 : data.logoutRedirectUrl) {
|
|
454
|
+
window.location.replace(data.logoutRedirectUrl);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
382
457
|
} catch (error) {
|
|
383
458
|
console.warn("\u26A0\uFE0F Logout backend call failed:", error);
|
|
459
|
+
emitAuthDiagnostic("LOGOUT_BACKEND_FAILED", "FAILURE", "LOGOUT_REQUEST_FAILED", {
|
|
460
|
+
clientKey
|
|
461
|
+
});
|
|
384
462
|
}
|
|
385
463
|
const fallbackUrl = isRouterMode() ? new URL("/login", window.location.origin) : new URL("/login", accountUiUrl);
|
|
386
464
|
fallbackUrl.searchParams.set("logged_out", "true");
|
|
@@ -389,7 +467,6 @@ async function logout(options = {}) {
|
|
|
389
467
|
window.location.replace(fallbackUrl.toString());
|
|
390
468
|
}
|
|
391
469
|
function handleCallback() {
|
|
392
|
-
var _a;
|
|
393
470
|
const params = new URLSearchParams(window.location.search);
|
|
394
471
|
const accessToken2 = params.get("access_token");
|
|
395
472
|
const error = params.get("error");
|
|
@@ -406,24 +483,27 @@ function handleCallback() {
|
|
|
406
483
|
callbackProcessed = false;
|
|
407
484
|
}
|
|
408
485
|
callbackProcessed = true;
|
|
486
|
+
clearLoginLock();
|
|
409
487
|
sessionStorage.removeItem("originalApp");
|
|
410
488
|
sessionStorage.removeItem("returnUrl");
|
|
411
489
|
if (error) {
|
|
412
490
|
const errorDescription = params.get("error_description") || error;
|
|
413
|
-
|
|
491
|
+
const authError = new Error(`Authentication failed: ${errorDescription}`);
|
|
492
|
+
authError.code = error;
|
|
493
|
+
authError.correlationId = getDiagnosticContext().correlationId;
|
|
494
|
+
emitAuthDiagnostic("CALLBACK_REJECTED", "FAILURE", error.toUpperCase(), {
|
|
495
|
+
clientKey: getConfig().clientKey,
|
|
496
|
+
state: params.get("state")
|
|
497
|
+
});
|
|
498
|
+
throw authError;
|
|
414
499
|
}
|
|
415
500
|
if (accessToken2) {
|
|
416
501
|
setToken(accessToken2);
|
|
417
502
|
const refreshTokenInUrl = params.get("refresh_token");
|
|
418
503
|
if (refreshTokenInUrl) {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
console.log(`\u{1F4E6} Storing refresh token from callback URL (${persistRefreshToken ? "persistence enabled" : "HTTP dev mode"})`);
|
|
423
|
-
setRefreshToken(refreshTokenInUrl);
|
|
424
|
-
} else {
|
|
425
|
-
console.log("\u{1F512} HTTPS mode: Refresh token is in httpOnly cookie (ignoring URL param)");
|
|
426
|
-
}
|
|
504
|
+
emitAuthDiagnostic("CALLBACK_REFRESH_TOKEN_IGNORED", "WARNING", "REFRESH_TOKEN_IN_URL", {
|
|
505
|
+
clientKey: getConfig().clientKey
|
|
506
|
+
});
|
|
427
507
|
}
|
|
428
508
|
const url = new URL(window.location);
|
|
429
509
|
url.searchParams.delete("access_token");
|
|
@@ -433,9 +513,19 @@ function handleCallback() {
|
|
|
433
513
|
url.searchParams.delete("error_description");
|
|
434
514
|
window.history.replaceState({}, "", url);
|
|
435
515
|
console.log("\u2705 Callback processed successfully, token stored");
|
|
516
|
+
emitAuthDiagnostic("CALLBACK_COMPLETED", "SUCCESS", "NONE", {
|
|
517
|
+
clientKey: getConfig().clientKey,
|
|
518
|
+
state: params.get("state")
|
|
519
|
+
});
|
|
436
520
|
return accessToken2;
|
|
437
521
|
}
|
|
438
|
-
|
|
522
|
+
emitAuthDiagnostic("CALLBACK_REJECTED", "FAILURE", "ACCESS_TOKEN_MISSING", {
|
|
523
|
+
clientKey: getConfig().clientKey,
|
|
524
|
+
state: params.get("state")
|
|
525
|
+
});
|
|
526
|
+
const missingTokenError = new Error("No access token found in callback URL");
|
|
527
|
+
missingTokenError.code = "ACCESS_TOKEN_MISSING";
|
|
528
|
+
throw missingTokenError;
|
|
439
529
|
}
|
|
440
530
|
function resetCallbackState() {
|
|
441
531
|
callbackProcessed = false;
|
|
@@ -450,7 +540,7 @@ async function refreshToken() {
|
|
|
450
540
|
}
|
|
451
541
|
refreshInProgress = true;
|
|
452
542
|
refreshPromise = (async () => {
|
|
453
|
-
var _a, _b, _c, _d;
|
|
543
|
+
var _a, _b, _c, _d, _e, _f;
|
|
454
544
|
try {
|
|
455
545
|
const storedRefreshToken = getRefreshToken();
|
|
456
546
|
console.log("\u{1F504} Refreshing token:", {
|
|
@@ -463,6 +553,7 @@ async function refreshToken() {
|
|
|
463
553
|
credentials: "include",
|
|
464
554
|
// โ
Include httpOnly cookies (for HTTPS)
|
|
465
555
|
headers: {
|
|
556
|
+
...diagnosticHeaders(),
|
|
466
557
|
"Content-Type": "application/json"
|
|
467
558
|
}
|
|
468
559
|
};
|
|
@@ -473,8 +564,19 @@ async function refreshToken() {
|
|
|
473
564
|
const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
|
|
474
565
|
if (!response.ok) {
|
|
475
566
|
const errorText = await response.text();
|
|
476
|
-
|
|
477
|
-
|
|
567
|
+
let serverCode = null;
|
|
568
|
+
try {
|
|
569
|
+
serverCode = ((_a = JSON.parse(errorText)) == null ? void 0 : _a.error) || ((_b = JSON.parse(errorText)) == null ? void 0 : _b.code);
|
|
570
|
+
} catch {
|
|
571
|
+
}
|
|
572
|
+
emitAuthDiagnostic("TOKEN_REFRESH_REJECTED", "FAILURE", serverCode || `HTTP_${response.status}`, {
|
|
573
|
+
clientKey,
|
|
574
|
+
status: response.status
|
|
575
|
+
});
|
|
576
|
+
const refreshError = new Error(`Refresh failed: ${response.status}`);
|
|
577
|
+
refreshError.code = serverCode || `HTTP_${response.status}`;
|
|
578
|
+
refreshError.status = response.status;
|
|
579
|
+
throw refreshError;
|
|
478
580
|
}
|
|
479
581
|
const data = await response.json();
|
|
480
582
|
const { access_token, refresh_token: new_refresh_token } = data;
|
|
@@ -487,10 +589,11 @@ async function refreshToken() {
|
|
|
487
589
|
console.log("\u{1F504} New refresh token stored from rotation");
|
|
488
590
|
}
|
|
489
591
|
console.log("\u2705 Token refresh successful, listeners notified");
|
|
592
|
+
emitAuthDiagnostic("TOKEN_REFRESH_COMPLETED", "SUCCESS", "NONE", { clientKey });
|
|
490
593
|
return access_token;
|
|
491
594
|
} catch (err) {
|
|
492
595
|
console.error("\u274C Token refresh error:", err);
|
|
493
|
-
const isAuthRejection = ((
|
|
596
|
+
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"));
|
|
494
597
|
if (isAuthRejection) {
|
|
495
598
|
clearToken();
|
|
496
599
|
clearRefreshToken();
|
|
@@ -513,6 +616,7 @@ async function validateCurrentSession() {
|
|
|
513
616
|
const response = await fetch(`${authBaseUrl}/account/validate-session`, {
|
|
514
617
|
method: "GET",
|
|
515
618
|
headers: {
|
|
619
|
+
...diagnosticHeaders(),
|
|
516
620
|
"Authorization": `Bearer ${token}`,
|
|
517
621
|
"Content-Type": "application/json"
|
|
518
622
|
},
|
|
@@ -765,6 +869,7 @@ api.interceptors.request.use((config2) => {
|
|
|
765
869
|
if ((runtimeConfig == null ? void 0 : runtimeConfig.clientKey) && !config2.headers["X-Client-Key"]) {
|
|
766
870
|
config2.headers["X-Client-Key"] = runtimeConfig.clientKey;
|
|
767
871
|
}
|
|
872
|
+
Object.assign(config2.headers, diagnosticHeaders());
|
|
768
873
|
const token = getToken();
|
|
769
874
|
if (token) {
|
|
770
875
|
config2.headers.Authorization = `Bearer ${token}`;
|
|
@@ -783,6 +888,10 @@ api.interceptors.response.use(
|
|
|
783
888
|
return Promise.reject(error);
|
|
784
889
|
}
|
|
785
890
|
config2._retry = true;
|
|
891
|
+
emitAuthDiagnostic("API_401_REFRESH_STARTED", "PENDING", "HTTP_401", {
|
|
892
|
+
clientKey: getConfig().clientKey,
|
|
893
|
+
status: 401
|
|
894
|
+
});
|
|
786
895
|
if (!refreshPromise2) {
|
|
787
896
|
refreshPromise2 = refreshToken().then((newToken) => {
|
|
788
897
|
refreshPromise2 = null;
|
|
@@ -842,6 +951,208 @@ function isAuthenticated() {
|
|
|
842
951
|
return !!token && !isTokenExpired(token, 10);
|
|
843
952
|
}
|
|
844
953
|
|
|
954
|
+
// preferences.js
|
|
955
|
+
var PREFERENCE_DEFAULTS = Object.freeze({
|
|
956
|
+
theme: "system",
|
|
957
|
+
colorScheme: "blue",
|
|
958
|
+
density: "comfortable",
|
|
959
|
+
reducedMotion: false,
|
|
960
|
+
language: "en",
|
|
961
|
+
timezone: "Etc/UTC",
|
|
962
|
+
dateFormat: "MM/DD/YYYY",
|
|
963
|
+
timeFormat: "12h",
|
|
964
|
+
highContrast: false,
|
|
965
|
+
largeText: false
|
|
966
|
+
});
|
|
967
|
+
var ALLOWED = {
|
|
968
|
+
theme: /* @__PURE__ */ new Set(["light", "dark", "system"]),
|
|
969
|
+
density: /* @__PURE__ */ new Set(["compact", "comfortable", "spacious"]),
|
|
970
|
+
timeFormat: /* @__PURE__ */ new Set(["12h", "24h"])
|
|
971
|
+
};
|
|
972
|
+
var listeners2 = /* @__PURE__ */ new Set();
|
|
973
|
+
var cachedPreferences = null;
|
|
974
|
+
var pendingRequest = null;
|
|
975
|
+
var syncCleanup = null;
|
|
976
|
+
function extractResponseData(response) {
|
|
977
|
+
var _a;
|
|
978
|
+
return ((_a = response == null ? void 0 : response.data) == null ? void 0 : _a.data) ?? (response == null ? void 0 : response.data) ?? response ?? {};
|
|
979
|
+
}
|
|
980
|
+
function normalizePreferences(value = {}) {
|
|
981
|
+
const next = { ...PREFERENCE_DEFAULTS };
|
|
982
|
+
for (const key of Object.keys(PREFERENCE_DEFAULTS)) {
|
|
983
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
984
|
+
const candidate = value[key];
|
|
985
|
+
if (ALLOWED[key] && !ALLOWED[key].has(candidate)) continue;
|
|
986
|
+
if (typeof PREFERENCE_DEFAULTS[key] !== typeof candidate) continue;
|
|
987
|
+
next[key] = candidate;
|
|
988
|
+
}
|
|
989
|
+
return next;
|
|
990
|
+
}
|
|
991
|
+
function resolveTheme(theme, matchMedia2 = globalThis.matchMedia) {
|
|
992
|
+
if (theme === "dark" || theme === "light") return theme;
|
|
993
|
+
return typeof matchMedia2 === "function" && matchMedia2("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
994
|
+
}
|
|
995
|
+
function announce(preferences2) {
|
|
996
|
+
cachedPreferences = normalizePreferences(preferences2);
|
|
997
|
+
listeners2.forEach((listener) => listener(cachedPreferences));
|
|
998
|
+
return cachedPreferences;
|
|
999
|
+
}
|
|
1000
|
+
async function getPreferences({ force = false } = {}) {
|
|
1001
|
+
if (cachedPreferences && !force) return cachedPreferences;
|
|
1002
|
+
if (pendingRequest) return pendingRequest;
|
|
1003
|
+
pendingRequest = api_default.get("/account/preferences").then((response) => announce(extractResponseData(response))).finally(() => {
|
|
1004
|
+
pendingRequest = null;
|
|
1005
|
+
});
|
|
1006
|
+
return pendingRequest;
|
|
1007
|
+
}
|
|
1008
|
+
async function updatePreferences(patch) {
|
|
1009
|
+
const allowedPatch = {};
|
|
1010
|
+
for (const key of Object.keys(PREFERENCE_DEFAULTS)) {
|
|
1011
|
+
if (Object.hasOwn(patch, key)) allowedPatch[key] = patch[key];
|
|
1012
|
+
}
|
|
1013
|
+
const candidate = normalizePreferences({ ...cachedPreferences || PREFERENCE_DEFAULTS, ...allowedPatch });
|
|
1014
|
+
const response = await api_default.put("/account/preferences", allowedPatch);
|
|
1015
|
+
const saved = extractResponseData(response);
|
|
1016
|
+
return announce(Object.keys(saved).length ? saved : candidate);
|
|
1017
|
+
}
|
|
1018
|
+
function subscribePreferences(listener) {
|
|
1019
|
+
listeners2.add(listener);
|
|
1020
|
+
if (cachedPreferences) listener(cachedPreferences);
|
|
1021
|
+
return () => listeners2.delete(listener);
|
|
1022
|
+
}
|
|
1023
|
+
function clearPreferenceCache() {
|
|
1024
|
+
cachedPreferences = null;
|
|
1025
|
+
pendingRequest = null;
|
|
1026
|
+
}
|
|
1027
|
+
function applyPreferences(preferences2, documentRef = globalThis.document) {
|
|
1028
|
+
var _a, _b, _c;
|
|
1029
|
+
if (!(documentRef == null ? void 0 : documentRef.documentElement)) return;
|
|
1030
|
+
const value = normalizePreferences(preferences2);
|
|
1031
|
+
const root = documentRef.documentElement;
|
|
1032
|
+
const resolvedTheme = resolveTheme(value.theme, (_b = (_a = documentRef.defaultView) == null ? void 0 : _a.matchMedia) == null ? void 0 : _b.bind(documentRef.defaultView));
|
|
1033
|
+
root.dataset.theme = resolvedTheme;
|
|
1034
|
+
root.dataset.themePreference = value.theme;
|
|
1035
|
+
root.dataset.colorScheme = value.colorScheme;
|
|
1036
|
+
root.dataset.density = value.density;
|
|
1037
|
+
root.lang = value.language;
|
|
1038
|
+
root.style.colorScheme = resolvedTheme;
|
|
1039
|
+
root.style.setProperty("--user-accent-color", {
|
|
1040
|
+
blue: "#1976d2",
|
|
1041
|
+
green: "#2e7d32",
|
|
1042
|
+
purple: "#7b1fa2",
|
|
1043
|
+
orange: "#c65d00",
|
|
1044
|
+
red: "#c62828"
|
|
1045
|
+
}[value.colorScheme] || "#1976d2");
|
|
1046
|
+
root.classList.toggle("user-reduced-motion", value.reducedMotion);
|
|
1047
|
+
root.classList.toggle("user-high-contrast", value.highContrast);
|
|
1048
|
+
root.classList.toggle("user-large-text", value.largeText);
|
|
1049
|
+
const styleId = "sso-user-preference-styles";
|
|
1050
|
+
if (!documentRef.getElementById(styleId)) {
|
|
1051
|
+
const style = documentRef.createElement("style");
|
|
1052
|
+
style.id = styleId;
|
|
1053
|
+
style.textContent = `
|
|
1054
|
+
html.user-large-text { font-size: 112.5%; }
|
|
1055
|
+
html.user-high-contrast { filter: contrast(1.15); }
|
|
1056
|
+
html[data-density="compact"] { --user-density-padding: 6px; }
|
|
1057
|
+
html[data-density="comfortable"] { --user-density-padding: 10px; }
|
|
1058
|
+
html[data-density="spacious"] { --user-density-padding: 14px; }
|
|
1059
|
+
html[data-density] .MuiListItem-root { padding-top: var(--user-density-padding); padding-bottom: var(--user-density-padding); }
|
|
1060
|
+
html.user-reduced-motion *, html.user-reduced-motion *::before, html.user-reduced-motion *::after {
|
|
1061
|
+
animation-duration: 0.01ms !important;
|
|
1062
|
+
animation-iteration-count: 1 !important;
|
|
1063
|
+
scroll-behavior: auto !important;
|
|
1064
|
+
transition-duration: 0.01ms !important;
|
|
1065
|
+
}
|
|
1066
|
+
`;
|
|
1067
|
+
(_c = documentRef.head) == null ? void 0 : _c.appendChild(style);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
function startPreferenceSync({ refreshOnFocus = true } = {}) {
|
|
1071
|
+
var _a, _b;
|
|
1072
|
+
if (syncCleanup || typeof globalThis.addEventListener !== "function") return syncCleanup || (() => {
|
|
1073
|
+
});
|
|
1074
|
+
const refresh = () => {
|
|
1075
|
+
if (!globalThis.document || globalThis.document.visibilityState === "visible") {
|
|
1076
|
+
getPreferences({ force: true }).catch(() => {
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
const onVisibility = () => refresh();
|
|
1081
|
+
(_b = (_a = globalThis.document) == null ? void 0 : _a.addEventListener) == null ? void 0 : _b.call(_a, "visibilitychange", onVisibility);
|
|
1082
|
+
if (refreshOnFocus) globalThis.addEventListener("focus", refresh);
|
|
1083
|
+
syncCleanup = () => {
|
|
1084
|
+
var _a2, _b2;
|
|
1085
|
+
(_b2 = (_a2 = globalThis.document) == null ? void 0 : _a2.removeEventListener) == null ? void 0 : _b2.call(_a2, "visibilitychange", onVisibility);
|
|
1086
|
+
if (refreshOnFocus) globalThis.removeEventListener("focus", refresh);
|
|
1087
|
+
syncCleanup = null;
|
|
1088
|
+
};
|
|
1089
|
+
return syncCleanup;
|
|
1090
|
+
}
|
|
1091
|
+
function localeFor(language) {
|
|
1092
|
+
const supported = Intl.DateTimeFormat.supportedLocalesOf([language]);
|
|
1093
|
+
return supported[0] || "en";
|
|
1094
|
+
}
|
|
1095
|
+
function fixedOffset(value) {
|
|
1096
|
+
const match = /^UTC([+-])(\d{2}):(\d{2})$/.exec(value || "");
|
|
1097
|
+
if (!match) return null;
|
|
1098
|
+
const minutes = Number(match[2]) * 60 + Number(match[3]);
|
|
1099
|
+
return (match[1] === "-" ? -1 : 1) * minutes;
|
|
1100
|
+
}
|
|
1101
|
+
function formatUserDate(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, options = {}) {
|
|
1102
|
+
const value = normalizePreferences(preferences2);
|
|
1103
|
+
const date = input instanceof Date ? input : new Date(input);
|
|
1104
|
+
const offset = fixedOffset(value.timezone);
|
|
1105
|
+
const adjusted = offset === null ? date : new Date(date.getTime() + offset * 6e4);
|
|
1106
|
+
const timeZone = offset === null ? value.timezone : "UTC";
|
|
1107
|
+
const hasExplicitParts = ["year", "month", "day", "weekday", "hour", "minute", "second", "dateStyle", "timeStyle"].some((key) => options[key] !== void 0);
|
|
1108
|
+
const dateOptions = hasExplicitParts ? options : { year: "numeric", month: "2-digit", day: "2-digit" };
|
|
1109
|
+
const formatter = new Intl.DateTimeFormat(localeFor(value.language), {
|
|
1110
|
+
...dateOptions,
|
|
1111
|
+
timeZone,
|
|
1112
|
+
...dateOptions.hour || dateOptions.timeStyle ? { hour12: value.timeFormat === "12h" } : {}
|
|
1113
|
+
});
|
|
1114
|
+
if (!hasExplicitParts) {
|
|
1115
|
+
const parts = Object.fromEntries(formatter.formatToParts(adjusted).map((part) => [part.type, part.value]));
|
|
1116
|
+
if (value.dateFormat === "DD/MM/YYYY") return `${parts.day}/${parts.month}/${parts.year}`;
|
|
1117
|
+
if (value.dateFormat === "YYYY-MM-DD") return `${parts.year}-${parts.month}-${parts.day}`;
|
|
1118
|
+
return `${parts.month}/${parts.day}/${parts.year}`;
|
|
1119
|
+
}
|
|
1120
|
+
return formatter.format(adjusted);
|
|
1121
|
+
}
|
|
1122
|
+
function formatUserNumber(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, options = {}) {
|
|
1123
|
+
const value = normalizePreferences(preferences2);
|
|
1124
|
+
return new Intl.NumberFormat(localeFor(value.language), options).format(input);
|
|
1125
|
+
}
|
|
1126
|
+
function formatUserRelativeTime(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, now = /* @__PURE__ */ new Date()) {
|
|
1127
|
+
const value = normalizePreferences(preferences2);
|
|
1128
|
+
const deltaSeconds = (new Date(input).getTime() - now.getTime()) / 1e3;
|
|
1129
|
+
const units = [
|
|
1130
|
+
["year", 31536e3],
|
|
1131
|
+
["month", 2592e3],
|
|
1132
|
+
["week", 604800],
|
|
1133
|
+
["day", 86400],
|
|
1134
|
+
["hour", 3600],
|
|
1135
|
+
["minute", 60],
|
|
1136
|
+
["second", 1]
|
|
1137
|
+
];
|
|
1138
|
+
const [unit, seconds] = units.find(([, size]) => Math.abs(deltaSeconds) >= size) || units.at(-1);
|
|
1139
|
+
return new Intl.RelativeTimeFormat(localeFor(value.language), { numeric: "auto" }).format(Math.round(deltaSeconds / seconds), unit);
|
|
1140
|
+
}
|
|
1141
|
+
var preferences = {
|
|
1142
|
+
defaults: PREFERENCE_DEFAULTS,
|
|
1143
|
+
get: getPreferences,
|
|
1144
|
+
update: updatePreferences,
|
|
1145
|
+
subscribe: subscribePreferences,
|
|
1146
|
+
clearCache: clearPreferenceCache,
|
|
1147
|
+
apply: applyPreferences,
|
|
1148
|
+
startSync: startPreferenceSync,
|
|
1149
|
+
formatDate: formatUserDate,
|
|
1150
|
+
formatNumber: formatUserNumber,
|
|
1151
|
+
formatRelativeTime: formatUserRelativeTime,
|
|
1152
|
+
normalize: normalizePreferences,
|
|
1153
|
+
resolveTheme
|
|
1154
|
+
};
|
|
1155
|
+
|
|
845
1156
|
// react/AuthProvider.jsx
|
|
846
1157
|
var import_react = __toESM(require("react"), 1);
|
|
847
1158
|
var AuthContext = (0, import_react.createContext)();
|
|
@@ -849,6 +1160,10 @@ var AuthContext = (0, import_react.createContext)();
|
|
|
849
1160
|
// react/useAuth.js
|
|
850
1161
|
var import_react2 = require("react");
|
|
851
1162
|
|
|
1163
|
+
// react/PreferencesProvider.jsx
|
|
1164
|
+
var import_react3 = __toESM(require("react"), 1);
|
|
1165
|
+
var PreferencesContext = (0, import_react3.createContext)(null);
|
|
1166
|
+
|
|
852
1167
|
// index.js
|
|
853
1168
|
var auth = {
|
|
854
1169
|
// ๐ง Config
|
|
@@ -868,7 +1183,7 @@ var auth = {
|
|
|
868
1183
|
getToken,
|
|
869
1184
|
setToken,
|
|
870
1185
|
clearToken,
|
|
871
|
-
setRefreshToken,
|
|
1186
|
+
setRefreshToken: setRefreshToken2,
|
|
872
1187
|
// โ
Refresh token for HTTP dev
|
|
873
1188
|
getRefreshToken,
|
|
874
1189
|
clearRefreshToken,
|
|
@@ -879,6 +1194,10 @@ var auth = {
|
|
|
879
1194
|
// โ
Debug function
|
|
880
1195
|
// ๐ Authenticated API client
|
|
881
1196
|
api: api_default,
|
|
1197
|
+
preferences,
|
|
1198
|
+
// ๐ Safe authentication diagnostics
|
|
1199
|
+
getDiagnosticContext,
|
|
1200
|
+
emitAuthDiagnostic,
|
|
882
1201
|
// ๐งช Utilities
|
|
883
1202
|
decodeToken,
|
|
884
1203
|
isTokenExpired,
|
|
@@ -937,7 +1256,7 @@ var useSessionMonitor = (options = {}) => {
|
|
|
937
1256
|
autoLogout = true,
|
|
938
1257
|
validateOnMount = true
|
|
939
1258
|
} = options;
|
|
940
|
-
const handleInvalid = (0,
|
|
1259
|
+
const handleInvalid = (0, import_react4.useCallback)(() => {
|
|
941
1260
|
console.log("\u{1F6A8} useSessionMonitor: Session invalid detected");
|
|
942
1261
|
queryClient.clear();
|
|
943
1262
|
if (autoLogout) {
|
|
@@ -981,7 +1300,7 @@ var useSessionMonitor = (options = {}) => {
|
|
|
981
1300
|
staleTime: refetchInterval / 2
|
|
982
1301
|
// Consider stale at half the interval
|
|
983
1302
|
});
|
|
984
|
-
(0,
|
|
1303
|
+
(0, import_react4.useEffect)(() => {
|
|
985
1304
|
if (!enabled) return;
|
|
986
1305
|
const handleVisibilityChange = () => {
|
|
987
1306
|
if (document.visibilityState === "visible" && auth.getToken()) {
|
|
@@ -994,7 +1313,7 @@ var useSessionMonitor = (options = {}) => {
|
|
|
994
1313
|
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
995
1314
|
};
|
|
996
1315
|
}, [enabled, queryClient]);
|
|
997
|
-
(0,
|
|
1316
|
+
(0, import_react4.useEffect)(() => {
|
|
998
1317
|
if (validateOnMount && enabled && auth.getToken()) {
|
|
999
1318
|
queryClient.invalidateQueries({ queryKey: ["session-validation"] });
|
|
1000
1319
|
}
|