@spidy092/auth-client 3.0.9 โ 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 +72 -4
- package/dist/api.cjs.map +1 -1
- package/dist/api.js +72 -4
- package/dist/api.js.map +1 -1
- package/dist/index.cjs +419 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +406 -23
- 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 +99 -20
- package/dist/react/AuthProvider.cjs.map +1 -1
- package/dist/react/AuthProvider.js +99 -20
- 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.map +1 -1
- package/dist/react/useAuth.js.map +1 -1
- package/dist/react/useSessionMonitor.cjs +343 -26
- package/dist/react/useSessionMonitor.cjs.map +1 -1
- package/dist/react/useSessionMonitor.js +342 -25
- 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");
|
|
@@ -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 = {}) {
|
|
@@ -361,6 +430,7 @@ async function logout(options = {}) {
|
|
|
361
430
|
method: "POST",
|
|
362
431
|
credentials: "include",
|
|
363
432
|
headers: {
|
|
433
|
+
...diagnosticHeaders(),
|
|
364
434
|
"Authorization": token ? `Bearer ${token}` : "",
|
|
365
435
|
"Content-Type": "application/json"
|
|
366
436
|
},
|
|
@@ -406,11 +476,19 @@ function handleCallback() {
|
|
|
406
476
|
callbackProcessed = false;
|
|
407
477
|
}
|
|
408
478
|
callbackProcessed = true;
|
|
479
|
+
clearLoginLock();
|
|
409
480
|
sessionStorage.removeItem("originalApp");
|
|
410
481
|
sessionStorage.removeItem("returnUrl");
|
|
411
482
|
if (error) {
|
|
412
483
|
const errorDescription = params.get("error_description") || error;
|
|
413
|
-
|
|
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;
|
|
414
492
|
}
|
|
415
493
|
if (accessToken2) {
|
|
416
494
|
setToken(accessToken2);
|
|
@@ -433,9 +511,19 @@ function handleCallback() {
|
|
|
433
511
|
url.searchParams.delete("error_description");
|
|
434
512
|
window.history.replaceState({}, "", url);
|
|
435
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
|
+
});
|
|
436
518
|
return accessToken2;
|
|
437
519
|
}
|
|
438
|
-
|
|
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;
|
|
439
527
|
}
|
|
440
528
|
function resetCallbackState() {
|
|
441
529
|
callbackProcessed = false;
|
|
@@ -450,7 +538,7 @@ async function refreshToken() {
|
|
|
450
538
|
}
|
|
451
539
|
refreshInProgress = true;
|
|
452
540
|
refreshPromise = (async () => {
|
|
453
|
-
var _a, _b, _c, _d;
|
|
541
|
+
var _a, _b, _c, _d, _e, _f;
|
|
454
542
|
try {
|
|
455
543
|
const storedRefreshToken = getRefreshToken();
|
|
456
544
|
console.log("\u{1F504} Refreshing token:", {
|
|
@@ -463,6 +551,7 @@ async function refreshToken() {
|
|
|
463
551
|
credentials: "include",
|
|
464
552
|
// โ
Include httpOnly cookies (for HTTPS)
|
|
465
553
|
headers: {
|
|
554
|
+
...diagnosticHeaders(),
|
|
466
555
|
"Content-Type": "application/json"
|
|
467
556
|
}
|
|
468
557
|
};
|
|
@@ -473,8 +562,19 @@ async function refreshToken() {
|
|
|
473
562
|
const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
|
|
474
563
|
if (!response.ok) {
|
|
475
564
|
const errorText = await response.text();
|
|
476
|
-
|
|
477
|
-
|
|
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;
|
|
478
578
|
}
|
|
479
579
|
const data = await response.json();
|
|
480
580
|
const { access_token, refresh_token: new_refresh_token } = data;
|
|
@@ -487,10 +587,11 @@ async function refreshToken() {
|
|
|
487
587
|
console.log("\u{1F504} New refresh token stored from rotation");
|
|
488
588
|
}
|
|
489
589
|
console.log("\u2705 Token refresh successful, listeners notified");
|
|
590
|
+
emitAuthDiagnostic("TOKEN_REFRESH_COMPLETED", "SUCCESS", "NONE", { clientKey });
|
|
490
591
|
return access_token;
|
|
491
592
|
} catch (err) {
|
|
492
593
|
console.error("\u274C Token refresh error:", err);
|
|
493
|
-
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"));
|
|
494
595
|
if (isAuthRejection) {
|
|
495
596
|
clearToken();
|
|
496
597
|
clearRefreshToken();
|
|
@@ -513,6 +614,7 @@ async function validateCurrentSession() {
|
|
|
513
614
|
const response = await fetch(`${authBaseUrl}/account/validate-session`, {
|
|
514
615
|
method: "GET",
|
|
515
616
|
headers: {
|
|
617
|
+
...diagnosticHeaders(),
|
|
516
618
|
"Authorization": `Bearer ${token}`,
|
|
517
619
|
"Content-Type": "application/json"
|
|
518
620
|
},
|
|
@@ -765,6 +867,7 @@ api.interceptors.request.use((config2) => {
|
|
|
765
867
|
if ((runtimeConfig == null ? void 0 : runtimeConfig.clientKey) && !config2.headers["X-Client-Key"]) {
|
|
766
868
|
config2.headers["X-Client-Key"] = runtimeConfig.clientKey;
|
|
767
869
|
}
|
|
870
|
+
Object.assign(config2.headers, diagnosticHeaders());
|
|
768
871
|
const token = getToken();
|
|
769
872
|
if (token) {
|
|
770
873
|
config2.headers.Authorization = `Bearer ${token}`;
|
|
@@ -783,6 +886,10 @@ api.interceptors.response.use(
|
|
|
783
886
|
return Promise.reject(error);
|
|
784
887
|
}
|
|
785
888
|
config2._retry = true;
|
|
889
|
+
emitAuthDiagnostic("API_401_REFRESH_STARTED", "PENDING", "HTTP_401", {
|
|
890
|
+
clientKey: getConfig().clientKey,
|
|
891
|
+
status: 401
|
|
892
|
+
});
|
|
786
893
|
if (!refreshPromise2) {
|
|
787
894
|
refreshPromise2 = refreshToken().then((newToken) => {
|
|
788
895
|
refreshPromise2 = null;
|
|
@@ -842,6 +949,208 @@ function isAuthenticated() {
|
|
|
842
949
|
return !!token && !isTokenExpired(token, 10);
|
|
843
950
|
}
|
|
844
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
|
+
|
|
845
1154
|
// react/AuthProvider.jsx
|
|
846
1155
|
var import_react = __toESM(require("react"), 1);
|
|
847
1156
|
var AuthContext = (0, import_react.createContext)();
|
|
@@ -849,6 +1158,10 @@ var AuthContext = (0, import_react.createContext)();
|
|
|
849
1158
|
// react/useAuth.js
|
|
850
1159
|
var import_react2 = require("react");
|
|
851
1160
|
|
|
1161
|
+
// react/PreferencesProvider.jsx
|
|
1162
|
+
var import_react3 = __toESM(require("react"), 1);
|
|
1163
|
+
var PreferencesContext = (0, import_react3.createContext)(null);
|
|
1164
|
+
|
|
852
1165
|
// index.js
|
|
853
1166
|
var auth = {
|
|
854
1167
|
// ๐ง Config
|
|
@@ -879,6 +1192,10 @@ var auth = {
|
|
|
879
1192
|
// โ
Debug function
|
|
880
1193
|
// ๐ Authenticated API client
|
|
881
1194
|
api: api_default,
|
|
1195
|
+
preferences,
|
|
1196
|
+
// ๐ Safe authentication diagnostics
|
|
1197
|
+
getDiagnosticContext,
|
|
1198
|
+
emitAuthDiagnostic,
|
|
882
1199
|
// ๐งช Utilities
|
|
883
1200
|
decodeToken,
|
|
884
1201
|
isTokenExpired,
|
|
@@ -937,7 +1254,7 @@ var useSessionMonitor = (options = {}) => {
|
|
|
937
1254
|
autoLogout = true,
|
|
938
1255
|
validateOnMount = true
|
|
939
1256
|
} = options;
|
|
940
|
-
const handleInvalid = (0,
|
|
1257
|
+
const handleInvalid = (0, import_react4.useCallback)(() => {
|
|
941
1258
|
console.log("\u{1F6A8} useSessionMonitor: Session invalid detected");
|
|
942
1259
|
queryClient.clear();
|
|
943
1260
|
if (autoLogout) {
|
|
@@ -981,7 +1298,7 @@ var useSessionMonitor = (options = {}) => {
|
|
|
981
1298
|
staleTime: refetchInterval / 2
|
|
982
1299
|
// Consider stale at half the interval
|
|
983
1300
|
});
|
|
984
|
-
(0,
|
|
1301
|
+
(0, import_react4.useEffect)(() => {
|
|
985
1302
|
if (!enabled) return;
|
|
986
1303
|
const handleVisibilityChange = () => {
|
|
987
1304
|
if (document.visibilityState === "visible" && auth.getToken()) {
|
|
@@ -994,7 +1311,7 @@ var useSessionMonitor = (options = {}) => {
|
|
|
994
1311
|
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
995
1312
|
};
|
|
996
1313
|
}, [enabled, queryClient]);
|
|
997
|
-
(0,
|
|
1314
|
+
(0, import_react4.useEffect)(() => {
|
|
998
1315
|
if (validateOnMount && enabled && auth.getToken()) {
|
|
999
1316
|
queryClient.invalidateQueries({ queryKey: ["session-validation"] });
|
|
1000
1317
|
}
|