@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
|
@@ -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");
|
|
@@ -206,6 +206,21 @@ function removeTokenListener(listener) {
|
|
|
206
206
|
function getListenerCount() {
|
|
207
207
|
return listeners.size;
|
|
208
208
|
}
|
|
209
|
+
if (typeof window !== "undefined" && window.addEventListener) {
|
|
210
|
+
window.addEventListener("storage", (event) => {
|
|
211
|
+
if (event.key !== "authToken") return;
|
|
212
|
+
const previousToken = accessToken;
|
|
213
|
+
accessToken = event.newValue || null;
|
|
214
|
+
if (previousToken === accessToken) return;
|
|
215
|
+
listeners.forEach((listener) => {
|
|
216
|
+
try {
|
|
217
|
+
listener(accessToken, previousToken);
|
|
218
|
+
} catch (err) {
|
|
219
|
+
console.warn("Token listener error:", err);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
}
|
|
209
224
|
|
|
210
225
|
// config.js
|
|
211
226
|
var config = {
|
|
@@ -250,8 +265,8 @@ function setConfig(customConfig = {}) {
|
|
|
250
265
|
// โ
Auto-detect router mode
|
|
251
266
|
isRouter: customConfig.isRouter || customConfig.clientKey === "account-ui"
|
|
252
267
|
};
|
|
268
|
+
enableRefreshTokenPersistence(config.persistRefreshToken);
|
|
253
269
|
if (config.persistRefreshToken) {
|
|
254
|
-
enableRefreshTokenPersistence(true);
|
|
255
270
|
console.log("\u{1F4E6} Refresh token persistence ENABLED (localStorage on HTTPS)");
|
|
256
271
|
}
|
|
257
272
|
console.log(`\u{1F527} Auth Client Mode: ${config.isRouter ? "ROUTER" : "CLIENT"}`, {
|
|
@@ -267,6 +282,82 @@ function isRouterMode() {
|
|
|
267
282
|
return config.isRouter;
|
|
268
283
|
}
|
|
269
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
|
+
|
|
270
361
|
// core.js
|
|
271
362
|
var callbackProcessed = false;
|
|
272
363
|
function login(clientKeyArg, redirectUriArg) {
|
|
@@ -279,14 +370,16 @@ function login(clientKeyArg, redirectUriArg) {
|
|
|
279
370
|
} = getConfig();
|
|
280
371
|
const clientKey = clientKeyArg || defaultClientKey;
|
|
281
372
|
const redirectUri = redirectUriArg || defaultRedirectUri;
|
|
282
|
-
console.log("\u{1F504} Smart Login initiated:", {
|
|
283
|
-
mode: isRouterMode() ? "ROUTER" : "CLIENT",
|
|
284
|
-
clientKey,
|
|
285
|
-
redirectUri
|
|
286
|
-
});
|
|
287
373
|
if (!clientKey || !redirectUri) {
|
|
374
|
+
emitAuthDiagnostic("LOGIN_REJECTED", "FAILURE", "CLIENT_CONFIG_MISSING", { clientKey });
|
|
288
375
|
throw new Error("Missing clientKey or redirectUri");
|
|
289
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 });
|
|
290
383
|
sessionStorage.setItem("originalApp", clientKey);
|
|
291
384
|
sessionStorage.setItem("returnUrl", redirectUri);
|
|
292
385
|
if (isRouterMode()) {
|
|
@@ -301,13 +394,9 @@ function routerLogin(clientKey, redirectUri) {
|
|
|
301
394
|
if (redirectUri) {
|
|
302
395
|
params.append("redirect_uri", redirectUri);
|
|
303
396
|
}
|
|
397
|
+
params.append("correlation_id", getDiagnosticContext().correlationId);
|
|
304
398
|
const query = params.toString();
|
|
305
399
|
const backendLoginUrl = `${authBaseUrl}/login/${clientKey}${query ? `?${query}` : ""}`;
|
|
306
|
-
console.log("\u{1F3ED} Router Login: Direct backend authentication", {
|
|
307
|
-
clientKey,
|
|
308
|
-
redirectUri,
|
|
309
|
-
backendUrl: backendLoginUrl
|
|
310
|
-
});
|
|
311
400
|
window.location.href = backendLoginUrl;
|
|
312
401
|
}
|
|
313
402
|
function clientLogin(clientKey, redirectUri) {
|
|
@@ -319,19 +408,19 @@ function clientLogin(clientKey, redirectUri) {
|
|
|
319
408
|
params.append("redirect_uri", redirectUri);
|
|
320
409
|
}
|
|
321
410
|
const centralizedLoginUrl = `${accountUiUrl}/login?${params.toString()}`;
|
|
322
|
-
console.log("\u{1F504} Client Login: Redirecting to centralized login", {
|
|
323
|
-
clientKey,
|
|
324
|
-
redirectUri,
|
|
325
|
-
centralizedUrl: centralizedLoginUrl
|
|
326
|
-
});
|
|
327
411
|
window.location.href = centralizedLoginUrl;
|
|
328
412
|
}
|
|
329
|
-
async function logout() {
|
|
413
|
+
async function logout(options = {}) {
|
|
330
414
|
resetCallbackState();
|
|
331
415
|
const { clientKey, authBaseUrl, accountUiUrl } = getConfig();
|
|
416
|
+
const scope = options.scope === "client" ? "client" : "sso";
|
|
332
417
|
const token = getToken();
|
|
333
418
|
const refreshToken2 = getRefreshToken();
|
|
334
|
-
console.log("\u{1F6AA} Smart Logout initiated", {
|
|
419
|
+
console.log("\u{1F6AA} Smart Logout initiated", {
|
|
420
|
+
mode: isRouterMode() ? "ROUTER" : "CLIENT",
|
|
421
|
+
clientKey,
|
|
422
|
+
scope
|
|
423
|
+
});
|
|
335
424
|
clearToken();
|
|
336
425
|
clearRefreshToken();
|
|
337
426
|
sessionStorage.removeItem("originalApp");
|
|
@@ -341,21 +430,33 @@ async function logout() {
|
|
|
341
430
|
method: "POST",
|
|
342
431
|
credentials: "include",
|
|
343
432
|
headers: {
|
|
433
|
+
...diagnosticHeaders(),
|
|
344
434
|
"Authorization": token ? `Bearer ${token}` : "",
|
|
345
435
|
"Content-Type": "application/json"
|
|
346
436
|
},
|
|
347
|
-
body: JSON.stringify({ refreshToken: refreshToken2 })
|
|
437
|
+
body: JSON.stringify({ refreshToken: refreshToken2, scope })
|
|
348
438
|
});
|
|
439
|
+
if (!response.ok) {
|
|
440
|
+
throw new Error(`Logout failed: ${response.status}`);
|
|
441
|
+
}
|
|
349
442
|
const data = await response.json();
|
|
350
443
|
console.log("\u2705 Logout response:", data);
|
|
444
|
+
if (data == null ? void 0 : data.logoutRedirectUrl) {
|
|
445
|
+
window.location.replace(data.logoutRedirectUrl);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
351
448
|
if (data == null ? void 0 : data.keycloakLogoutUrl) {
|
|
352
|
-
window.location.
|
|
449
|
+
window.location.replace(data.keycloakLogoutUrl);
|
|
353
450
|
return;
|
|
354
451
|
}
|
|
355
452
|
} catch (error) {
|
|
356
453
|
console.warn("\u26A0\uFE0F Logout backend call failed:", error);
|
|
357
454
|
}
|
|
358
|
-
|
|
455
|
+
const fallbackUrl = isRouterMode() ? new URL("/login", window.location.origin) : new URL("/login", accountUiUrl);
|
|
456
|
+
fallbackUrl.searchParams.set("logged_out", "true");
|
|
457
|
+
fallbackUrl.searchParams.set("client", clientKey);
|
|
458
|
+
fallbackUrl.searchParams.set("scope", scope);
|
|
459
|
+
window.location.replace(fallbackUrl.toString());
|
|
359
460
|
}
|
|
360
461
|
function handleCallback() {
|
|
361
462
|
var _a;
|
|
@@ -375,11 +476,19 @@ function handleCallback() {
|
|
|
375
476
|
callbackProcessed = false;
|
|
376
477
|
}
|
|
377
478
|
callbackProcessed = true;
|
|
479
|
+
clearLoginLock();
|
|
378
480
|
sessionStorage.removeItem("originalApp");
|
|
379
481
|
sessionStorage.removeItem("returnUrl");
|
|
380
482
|
if (error) {
|
|
381
483
|
const errorDescription = params.get("error_description") || error;
|
|
382
|
-
|
|
484
|
+
const authError = new Error(`Authentication failed: ${errorDescription}`);
|
|
485
|
+
authError.code = error;
|
|
486
|
+
authError.correlationId = getDiagnosticContext().correlationId;
|
|
487
|
+
emitAuthDiagnostic("CALLBACK_REJECTED", "FAILURE", error.toUpperCase(), {
|
|
488
|
+
clientKey: getConfig().clientKey,
|
|
489
|
+
state: params.get("state")
|
|
490
|
+
});
|
|
491
|
+
throw authError;
|
|
383
492
|
}
|
|
384
493
|
if (accessToken2) {
|
|
385
494
|
setToken(accessToken2);
|
|
@@ -402,9 +511,19 @@ function handleCallback() {
|
|
|
402
511
|
url.searchParams.delete("error_description");
|
|
403
512
|
window.history.replaceState({}, "", url);
|
|
404
513
|
console.log("\u2705 Callback processed successfully, token stored");
|
|
514
|
+
emitAuthDiagnostic("CALLBACK_COMPLETED", "SUCCESS", "NONE", {
|
|
515
|
+
clientKey: getConfig().clientKey,
|
|
516
|
+
state: params.get("state")
|
|
517
|
+
});
|
|
405
518
|
return accessToken2;
|
|
406
519
|
}
|
|
407
|
-
|
|
520
|
+
emitAuthDiagnostic("CALLBACK_REJECTED", "FAILURE", "ACCESS_TOKEN_MISSING", {
|
|
521
|
+
clientKey: getConfig().clientKey,
|
|
522
|
+
state: params.get("state")
|
|
523
|
+
});
|
|
524
|
+
const missingTokenError = new Error("No access token found in callback URL");
|
|
525
|
+
missingTokenError.code = "ACCESS_TOKEN_MISSING";
|
|
526
|
+
throw missingTokenError;
|
|
408
527
|
}
|
|
409
528
|
function resetCallbackState() {
|
|
410
529
|
callbackProcessed = false;
|
|
@@ -419,7 +538,7 @@ async function refreshToken() {
|
|
|
419
538
|
}
|
|
420
539
|
refreshInProgress = true;
|
|
421
540
|
refreshPromise = (async () => {
|
|
422
|
-
var _a, _b, _c, _d;
|
|
541
|
+
var _a, _b, _c, _d, _e, _f;
|
|
423
542
|
try {
|
|
424
543
|
const storedRefreshToken = getRefreshToken();
|
|
425
544
|
console.log("\u{1F504} Refreshing token:", {
|
|
@@ -432,6 +551,7 @@ async function refreshToken() {
|
|
|
432
551
|
credentials: "include",
|
|
433
552
|
// โ
Include httpOnly cookies (for HTTPS)
|
|
434
553
|
headers: {
|
|
554
|
+
...diagnosticHeaders(),
|
|
435
555
|
"Content-Type": "application/json"
|
|
436
556
|
}
|
|
437
557
|
};
|
|
@@ -442,8 +562,19 @@ async function refreshToken() {
|
|
|
442
562
|
const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
|
|
443
563
|
if (!response.ok) {
|
|
444
564
|
const errorText = await response.text();
|
|
445
|
-
|
|
446
|
-
|
|
565
|
+
let serverCode = null;
|
|
566
|
+
try {
|
|
567
|
+
serverCode = ((_a = JSON.parse(errorText)) == null ? void 0 : _a.error) || ((_b = JSON.parse(errorText)) == null ? void 0 : _b.code);
|
|
568
|
+
} catch {
|
|
569
|
+
}
|
|
570
|
+
emitAuthDiagnostic("TOKEN_REFRESH_REJECTED", "FAILURE", serverCode || `HTTP_${response.status}`, {
|
|
571
|
+
clientKey,
|
|
572
|
+
status: response.status
|
|
573
|
+
});
|
|
574
|
+
const refreshError = new Error(`Refresh failed: ${response.status}`);
|
|
575
|
+
refreshError.code = serverCode || `HTTP_${response.status}`;
|
|
576
|
+
refreshError.status = response.status;
|
|
577
|
+
throw refreshError;
|
|
447
578
|
}
|
|
448
579
|
const data = await response.json();
|
|
449
580
|
const { access_token, refresh_token: new_refresh_token } = data;
|
|
@@ -456,10 +587,11 @@ async function refreshToken() {
|
|
|
456
587
|
console.log("\u{1F504} New refresh token stored from rotation");
|
|
457
588
|
}
|
|
458
589
|
console.log("\u2705 Token refresh successful, listeners notified");
|
|
590
|
+
emitAuthDiagnostic("TOKEN_REFRESH_COMPLETED", "SUCCESS", "NONE", { clientKey });
|
|
459
591
|
return access_token;
|
|
460
592
|
} catch (err) {
|
|
461
593
|
console.error("\u274C Token refresh error:", err);
|
|
462
|
-
const isAuthRejection = ((
|
|
594
|
+
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"));
|
|
463
595
|
if (isAuthRejection) {
|
|
464
596
|
clearToken();
|
|
465
597
|
clearRefreshToken();
|
|
@@ -482,6 +614,7 @@ async function validateCurrentSession() {
|
|
|
482
614
|
const response = await fetch(`${authBaseUrl}/account/validate-session`, {
|
|
483
615
|
method: "GET",
|
|
484
616
|
headers: {
|
|
617
|
+
...diagnosticHeaders(),
|
|
485
618
|
"Authorization": `Bearer ${token}`,
|
|
486
619
|
"Content-Type": "application/json"
|
|
487
620
|
},
|
|
@@ -734,6 +867,7 @@ api.interceptors.request.use((config2) => {
|
|
|
734
867
|
if ((runtimeConfig == null ? void 0 : runtimeConfig.clientKey) && !config2.headers["X-Client-Key"]) {
|
|
735
868
|
config2.headers["X-Client-Key"] = runtimeConfig.clientKey;
|
|
736
869
|
}
|
|
870
|
+
Object.assign(config2.headers, diagnosticHeaders());
|
|
737
871
|
const token = getToken();
|
|
738
872
|
if (token) {
|
|
739
873
|
config2.headers.Authorization = `Bearer ${token}`;
|
|
@@ -752,6 +886,10 @@ api.interceptors.response.use(
|
|
|
752
886
|
return Promise.reject(error);
|
|
753
887
|
}
|
|
754
888
|
config2._retry = true;
|
|
889
|
+
emitAuthDiagnostic("API_401_REFRESH_STARTED", "PENDING", "HTTP_401", {
|
|
890
|
+
clientKey: getConfig().clientKey,
|
|
891
|
+
status: 401
|
|
892
|
+
});
|
|
755
893
|
if (!refreshPromise2) {
|
|
756
894
|
refreshPromise2 = refreshToken().then((newToken) => {
|
|
757
895
|
refreshPromise2 = null;
|
|
@@ -811,6 +949,208 @@ function isAuthenticated() {
|
|
|
811
949
|
return !!token && !isTokenExpired(token, 10);
|
|
812
950
|
}
|
|
813
951
|
|
|
952
|
+
// preferences.js
|
|
953
|
+
var PREFERENCE_DEFAULTS = Object.freeze({
|
|
954
|
+
theme: "system",
|
|
955
|
+
colorScheme: "blue",
|
|
956
|
+
density: "comfortable",
|
|
957
|
+
reducedMotion: false,
|
|
958
|
+
language: "en",
|
|
959
|
+
timezone: "Etc/UTC",
|
|
960
|
+
dateFormat: "MM/DD/YYYY",
|
|
961
|
+
timeFormat: "12h",
|
|
962
|
+
highContrast: false,
|
|
963
|
+
largeText: false
|
|
964
|
+
});
|
|
965
|
+
var ALLOWED = {
|
|
966
|
+
theme: /* @__PURE__ */ new Set(["light", "dark", "system"]),
|
|
967
|
+
density: /* @__PURE__ */ new Set(["compact", "comfortable", "spacious"]),
|
|
968
|
+
timeFormat: /* @__PURE__ */ new Set(["12h", "24h"])
|
|
969
|
+
};
|
|
970
|
+
var listeners2 = /* @__PURE__ */ new Set();
|
|
971
|
+
var cachedPreferences = null;
|
|
972
|
+
var pendingRequest = null;
|
|
973
|
+
var syncCleanup = null;
|
|
974
|
+
function extractResponseData(response) {
|
|
975
|
+
var _a;
|
|
976
|
+
return ((_a = response == null ? void 0 : response.data) == null ? void 0 : _a.data) ?? (response == null ? void 0 : response.data) ?? response ?? {};
|
|
977
|
+
}
|
|
978
|
+
function normalizePreferences(value = {}) {
|
|
979
|
+
const next = { ...PREFERENCE_DEFAULTS };
|
|
980
|
+
for (const key of Object.keys(PREFERENCE_DEFAULTS)) {
|
|
981
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
982
|
+
const candidate = value[key];
|
|
983
|
+
if (ALLOWED[key] && !ALLOWED[key].has(candidate)) continue;
|
|
984
|
+
if (typeof PREFERENCE_DEFAULTS[key] !== typeof candidate) continue;
|
|
985
|
+
next[key] = candidate;
|
|
986
|
+
}
|
|
987
|
+
return next;
|
|
988
|
+
}
|
|
989
|
+
function resolveTheme(theme, matchMedia2 = globalThis.matchMedia) {
|
|
990
|
+
if (theme === "dark" || theme === "light") return theme;
|
|
991
|
+
return typeof matchMedia2 === "function" && matchMedia2("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
992
|
+
}
|
|
993
|
+
function announce(preferences2) {
|
|
994
|
+
cachedPreferences = normalizePreferences(preferences2);
|
|
995
|
+
listeners2.forEach((listener) => listener(cachedPreferences));
|
|
996
|
+
return cachedPreferences;
|
|
997
|
+
}
|
|
998
|
+
async function getPreferences({ force = false } = {}) {
|
|
999
|
+
if (cachedPreferences && !force) return cachedPreferences;
|
|
1000
|
+
if (pendingRequest) return pendingRequest;
|
|
1001
|
+
pendingRequest = api_default.get("/account/preferences").then((response) => announce(extractResponseData(response))).finally(() => {
|
|
1002
|
+
pendingRequest = null;
|
|
1003
|
+
});
|
|
1004
|
+
return pendingRequest;
|
|
1005
|
+
}
|
|
1006
|
+
async function updatePreferences(patch) {
|
|
1007
|
+
const allowedPatch = {};
|
|
1008
|
+
for (const key of Object.keys(PREFERENCE_DEFAULTS)) {
|
|
1009
|
+
if (Object.hasOwn(patch, key)) allowedPatch[key] = patch[key];
|
|
1010
|
+
}
|
|
1011
|
+
const candidate = normalizePreferences({ ...cachedPreferences || PREFERENCE_DEFAULTS, ...allowedPatch });
|
|
1012
|
+
const response = await api_default.put("/account/preferences", allowedPatch);
|
|
1013
|
+
const saved = extractResponseData(response);
|
|
1014
|
+
return announce(Object.keys(saved).length ? saved : candidate);
|
|
1015
|
+
}
|
|
1016
|
+
function subscribePreferences(listener) {
|
|
1017
|
+
listeners2.add(listener);
|
|
1018
|
+
if (cachedPreferences) listener(cachedPreferences);
|
|
1019
|
+
return () => listeners2.delete(listener);
|
|
1020
|
+
}
|
|
1021
|
+
function clearPreferenceCache() {
|
|
1022
|
+
cachedPreferences = null;
|
|
1023
|
+
pendingRequest = null;
|
|
1024
|
+
}
|
|
1025
|
+
function applyPreferences(preferences2, documentRef = globalThis.document) {
|
|
1026
|
+
var _a, _b, _c;
|
|
1027
|
+
if (!(documentRef == null ? void 0 : documentRef.documentElement)) return;
|
|
1028
|
+
const value = normalizePreferences(preferences2);
|
|
1029
|
+
const root = documentRef.documentElement;
|
|
1030
|
+
const resolvedTheme = resolveTheme(value.theme, (_b = (_a = documentRef.defaultView) == null ? void 0 : _a.matchMedia) == null ? void 0 : _b.bind(documentRef.defaultView));
|
|
1031
|
+
root.dataset.theme = resolvedTheme;
|
|
1032
|
+
root.dataset.themePreference = value.theme;
|
|
1033
|
+
root.dataset.colorScheme = value.colorScheme;
|
|
1034
|
+
root.dataset.density = value.density;
|
|
1035
|
+
root.lang = value.language;
|
|
1036
|
+
root.style.colorScheme = resolvedTheme;
|
|
1037
|
+
root.style.setProperty("--user-accent-color", {
|
|
1038
|
+
blue: "#1976d2",
|
|
1039
|
+
green: "#2e7d32",
|
|
1040
|
+
purple: "#7b1fa2",
|
|
1041
|
+
orange: "#c65d00",
|
|
1042
|
+
red: "#c62828"
|
|
1043
|
+
}[value.colorScheme] || "#1976d2");
|
|
1044
|
+
root.classList.toggle("user-reduced-motion", value.reducedMotion);
|
|
1045
|
+
root.classList.toggle("user-high-contrast", value.highContrast);
|
|
1046
|
+
root.classList.toggle("user-large-text", value.largeText);
|
|
1047
|
+
const styleId = "sso-user-preference-styles";
|
|
1048
|
+
if (!documentRef.getElementById(styleId)) {
|
|
1049
|
+
const style = documentRef.createElement("style");
|
|
1050
|
+
style.id = styleId;
|
|
1051
|
+
style.textContent = `
|
|
1052
|
+
html.user-large-text { font-size: 112.5%; }
|
|
1053
|
+
html.user-high-contrast { filter: contrast(1.15); }
|
|
1054
|
+
html[data-density="compact"] { --user-density-padding: 6px; }
|
|
1055
|
+
html[data-density="comfortable"] { --user-density-padding: 10px; }
|
|
1056
|
+
html[data-density="spacious"] { --user-density-padding: 14px; }
|
|
1057
|
+
html[data-density] .MuiListItem-root { padding-top: var(--user-density-padding); padding-bottom: var(--user-density-padding); }
|
|
1058
|
+
html.user-reduced-motion *, html.user-reduced-motion *::before, html.user-reduced-motion *::after {
|
|
1059
|
+
animation-duration: 0.01ms !important;
|
|
1060
|
+
animation-iteration-count: 1 !important;
|
|
1061
|
+
scroll-behavior: auto !important;
|
|
1062
|
+
transition-duration: 0.01ms !important;
|
|
1063
|
+
}
|
|
1064
|
+
`;
|
|
1065
|
+
(_c = documentRef.head) == null ? void 0 : _c.appendChild(style);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
function startPreferenceSync({ refreshOnFocus = true } = {}) {
|
|
1069
|
+
var _a, _b;
|
|
1070
|
+
if (syncCleanup || typeof globalThis.addEventListener !== "function") return syncCleanup || (() => {
|
|
1071
|
+
});
|
|
1072
|
+
const refresh = () => {
|
|
1073
|
+
if (!globalThis.document || globalThis.document.visibilityState === "visible") {
|
|
1074
|
+
getPreferences({ force: true }).catch(() => {
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
const onVisibility = () => refresh();
|
|
1079
|
+
(_b = (_a = globalThis.document) == null ? void 0 : _a.addEventListener) == null ? void 0 : _b.call(_a, "visibilitychange", onVisibility);
|
|
1080
|
+
if (refreshOnFocus) globalThis.addEventListener("focus", refresh);
|
|
1081
|
+
syncCleanup = () => {
|
|
1082
|
+
var _a2, _b2;
|
|
1083
|
+
(_b2 = (_a2 = globalThis.document) == null ? void 0 : _a2.removeEventListener) == null ? void 0 : _b2.call(_a2, "visibilitychange", onVisibility);
|
|
1084
|
+
if (refreshOnFocus) globalThis.removeEventListener("focus", refresh);
|
|
1085
|
+
syncCleanup = null;
|
|
1086
|
+
};
|
|
1087
|
+
return syncCleanup;
|
|
1088
|
+
}
|
|
1089
|
+
function localeFor(language) {
|
|
1090
|
+
const supported = Intl.DateTimeFormat.supportedLocalesOf([language]);
|
|
1091
|
+
return supported[0] || "en";
|
|
1092
|
+
}
|
|
1093
|
+
function fixedOffset(value) {
|
|
1094
|
+
const match = /^UTC([+-])(\d{2}):(\d{2})$/.exec(value || "");
|
|
1095
|
+
if (!match) return null;
|
|
1096
|
+
const minutes = Number(match[2]) * 60 + Number(match[3]);
|
|
1097
|
+
return (match[1] === "-" ? -1 : 1) * minutes;
|
|
1098
|
+
}
|
|
1099
|
+
function formatUserDate(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, options = {}) {
|
|
1100
|
+
const value = normalizePreferences(preferences2);
|
|
1101
|
+
const date = input instanceof Date ? input : new Date(input);
|
|
1102
|
+
const offset = fixedOffset(value.timezone);
|
|
1103
|
+
const adjusted = offset === null ? date : new Date(date.getTime() + offset * 6e4);
|
|
1104
|
+
const timeZone = offset === null ? value.timezone : "UTC";
|
|
1105
|
+
const hasExplicitParts = ["year", "month", "day", "weekday", "hour", "minute", "second", "dateStyle", "timeStyle"].some((key) => options[key] !== void 0);
|
|
1106
|
+
const dateOptions = hasExplicitParts ? options : { year: "numeric", month: "2-digit", day: "2-digit" };
|
|
1107
|
+
const formatter = new Intl.DateTimeFormat(localeFor(value.language), {
|
|
1108
|
+
...dateOptions,
|
|
1109
|
+
timeZone,
|
|
1110
|
+
...dateOptions.hour || dateOptions.timeStyle ? { hour12: value.timeFormat === "12h" } : {}
|
|
1111
|
+
});
|
|
1112
|
+
if (!hasExplicitParts) {
|
|
1113
|
+
const parts = Object.fromEntries(formatter.formatToParts(adjusted).map((part) => [part.type, part.value]));
|
|
1114
|
+
if (value.dateFormat === "DD/MM/YYYY") return `${parts.day}/${parts.month}/${parts.year}`;
|
|
1115
|
+
if (value.dateFormat === "YYYY-MM-DD") return `${parts.year}-${parts.month}-${parts.day}`;
|
|
1116
|
+
return `${parts.month}/${parts.day}/${parts.year}`;
|
|
1117
|
+
}
|
|
1118
|
+
return formatter.format(adjusted);
|
|
1119
|
+
}
|
|
1120
|
+
function formatUserNumber(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, options = {}) {
|
|
1121
|
+
const value = normalizePreferences(preferences2);
|
|
1122
|
+
return new Intl.NumberFormat(localeFor(value.language), options).format(input);
|
|
1123
|
+
}
|
|
1124
|
+
function formatUserRelativeTime(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, now = /* @__PURE__ */ new Date()) {
|
|
1125
|
+
const value = normalizePreferences(preferences2);
|
|
1126
|
+
const deltaSeconds = (new Date(input).getTime() - now.getTime()) / 1e3;
|
|
1127
|
+
const units = [
|
|
1128
|
+
["year", 31536e3],
|
|
1129
|
+
["month", 2592e3],
|
|
1130
|
+
["week", 604800],
|
|
1131
|
+
["day", 86400],
|
|
1132
|
+
["hour", 3600],
|
|
1133
|
+
["minute", 60],
|
|
1134
|
+
["second", 1]
|
|
1135
|
+
];
|
|
1136
|
+
const [unit, seconds] = units.find(([, size]) => Math.abs(deltaSeconds) >= size) || units.at(-1);
|
|
1137
|
+
return new Intl.RelativeTimeFormat(localeFor(value.language), { numeric: "auto" }).format(Math.round(deltaSeconds / seconds), unit);
|
|
1138
|
+
}
|
|
1139
|
+
var preferences = {
|
|
1140
|
+
defaults: PREFERENCE_DEFAULTS,
|
|
1141
|
+
get: getPreferences,
|
|
1142
|
+
update: updatePreferences,
|
|
1143
|
+
subscribe: subscribePreferences,
|
|
1144
|
+
clearCache: clearPreferenceCache,
|
|
1145
|
+
apply: applyPreferences,
|
|
1146
|
+
startSync: startPreferenceSync,
|
|
1147
|
+
formatDate: formatUserDate,
|
|
1148
|
+
formatNumber: formatUserNumber,
|
|
1149
|
+
formatRelativeTime: formatUserRelativeTime,
|
|
1150
|
+
normalize: normalizePreferences,
|
|
1151
|
+
resolveTheme
|
|
1152
|
+
};
|
|
1153
|
+
|
|
814
1154
|
// react/AuthProvider.jsx
|
|
815
1155
|
var import_react = __toESM(require("react"), 1);
|
|
816
1156
|
var AuthContext = (0, import_react.createContext)();
|
|
@@ -818,6 +1158,10 @@ var AuthContext = (0, import_react.createContext)();
|
|
|
818
1158
|
// react/useAuth.js
|
|
819
1159
|
var import_react2 = require("react");
|
|
820
1160
|
|
|
1161
|
+
// react/PreferencesProvider.jsx
|
|
1162
|
+
var import_react3 = __toESM(require("react"), 1);
|
|
1163
|
+
var PreferencesContext = (0, import_react3.createContext)(null);
|
|
1164
|
+
|
|
821
1165
|
// index.js
|
|
822
1166
|
var auth = {
|
|
823
1167
|
// ๐ง Config
|
|
@@ -827,6 +1171,8 @@ var auth = {
|
|
|
827
1171
|
// ๐ Core flows
|
|
828
1172
|
login,
|
|
829
1173
|
logout,
|
|
1174
|
+
logoutClient: () => logout({ scope: "client" }),
|
|
1175
|
+
logoutSso: () => logout({ scope: "sso" }),
|
|
830
1176
|
handleCallback,
|
|
831
1177
|
refreshToken,
|
|
832
1178
|
resetCallbackState,
|
|
@@ -846,6 +1192,10 @@ var auth = {
|
|
|
846
1192
|
// โ
Debug function
|
|
847
1193
|
// ๐ Authenticated API client
|
|
848
1194
|
api: api_default,
|
|
1195
|
+
preferences,
|
|
1196
|
+
// ๐ Safe authentication diagnostics
|
|
1197
|
+
getDiagnosticContext,
|
|
1198
|
+
emitAuthDiagnostic,
|
|
849
1199
|
// ๐งช Utilities
|
|
850
1200
|
decodeToken,
|
|
851
1201
|
isTokenExpired,
|
|
@@ -904,7 +1254,7 @@ var useSessionMonitor = (options = {}) => {
|
|
|
904
1254
|
autoLogout = true,
|
|
905
1255
|
validateOnMount = true
|
|
906
1256
|
} = options;
|
|
907
|
-
const handleInvalid = (0,
|
|
1257
|
+
const handleInvalid = (0, import_react4.useCallback)(() => {
|
|
908
1258
|
console.log("\u{1F6A8} useSessionMonitor: Session invalid detected");
|
|
909
1259
|
queryClient.clear();
|
|
910
1260
|
if (autoLogout) {
|
|
@@ -948,7 +1298,7 @@ var useSessionMonitor = (options = {}) => {
|
|
|
948
1298
|
staleTime: refetchInterval / 2
|
|
949
1299
|
// Consider stale at half the interval
|
|
950
1300
|
});
|
|
951
|
-
(0,
|
|
1301
|
+
(0, import_react4.useEffect)(() => {
|
|
952
1302
|
if (!enabled) return;
|
|
953
1303
|
const handleVisibilityChange = () => {
|
|
954
1304
|
if (document.visibilityState === "visible" && auth.getToken()) {
|
|
@@ -961,7 +1311,7 @@ var useSessionMonitor = (options = {}) => {
|
|
|
961
1311
|
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
962
1312
|
};
|
|
963
1313
|
}, [enabled, queryClient]);
|
|
964
|
-
(0,
|
|
1314
|
+
(0, import_react4.useEffect)(() => {
|
|
965
1315
|
if (validateOnMount && enabled && auth.getToken()) {
|
|
966
1316
|
queryClient.invalidateQueries({ queryKey: ["session-validation"] });
|
|
967
1317
|
}
|