@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
|
@@ -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";
|
|
@@ -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 = {}) {
|
|
@@ -328,6 +397,7 @@ async function logout(options = {}) {
|
|
|
328
397
|
method: "POST",
|
|
329
398
|
credentials: "include",
|
|
330
399
|
headers: {
|
|
400
|
+
...diagnosticHeaders(),
|
|
331
401
|
"Authorization": token ? `Bearer ${token}` : "",
|
|
332
402
|
"Content-Type": "application/json"
|
|
333
403
|
},
|
|
@@ -373,11 +443,19 @@ function handleCallback() {
|
|
|
373
443
|
callbackProcessed = false;
|
|
374
444
|
}
|
|
375
445
|
callbackProcessed = true;
|
|
446
|
+
clearLoginLock();
|
|
376
447
|
sessionStorage.removeItem("originalApp");
|
|
377
448
|
sessionStorage.removeItem("returnUrl");
|
|
378
449
|
if (error) {
|
|
379
450
|
const errorDescription = params.get("error_description") || error;
|
|
380
|
-
|
|
451
|
+
const authError = new Error(`Authentication failed: ${errorDescription}`);
|
|
452
|
+
authError.code = error;
|
|
453
|
+
authError.correlationId = getDiagnosticContext().correlationId;
|
|
454
|
+
emitAuthDiagnostic("CALLBACK_REJECTED", "FAILURE", error.toUpperCase(), {
|
|
455
|
+
clientKey: getConfig().clientKey,
|
|
456
|
+
state: params.get("state")
|
|
457
|
+
});
|
|
458
|
+
throw authError;
|
|
381
459
|
}
|
|
382
460
|
if (accessToken2) {
|
|
383
461
|
setToken(accessToken2);
|
|
@@ -400,9 +478,19 @@ function handleCallback() {
|
|
|
400
478
|
url.searchParams.delete("error_description");
|
|
401
479
|
window.history.replaceState({}, "", url);
|
|
402
480
|
console.log("\u2705 Callback processed successfully, token stored");
|
|
481
|
+
emitAuthDiagnostic("CALLBACK_COMPLETED", "SUCCESS", "NONE", {
|
|
482
|
+
clientKey: getConfig().clientKey,
|
|
483
|
+
state: params.get("state")
|
|
484
|
+
});
|
|
403
485
|
return accessToken2;
|
|
404
486
|
}
|
|
405
|
-
|
|
487
|
+
emitAuthDiagnostic("CALLBACK_REJECTED", "FAILURE", "ACCESS_TOKEN_MISSING", {
|
|
488
|
+
clientKey: getConfig().clientKey,
|
|
489
|
+
state: params.get("state")
|
|
490
|
+
});
|
|
491
|
+
const missingTokenError = new Error("No access token found in callback URL");
|
|
492
|
+
missingTokenError.code = "ACCESS_TOKEN_MISSING";
|
|
493
|
+
throw missingTokenError;
|
|
406
494
|
}
|
|
407
495
|
function resetCallbackState() {
|
|
408
496
|
callbackProcessed = false;
|
|
@@ -417,7 +505,7 @@ async function refreshToken() {
|
|
|
417
505
|
}
|
|
418
506
|
refreshInProgress = true;
|
|
419
507
|
refreshPromise = (async () => {
|
|
420
|
-
var _a, _b, _c, _d;
|
|
508
|
+
var _a, _b, _c, _d, _e, _f;
|
|
421
509
|
try {
|
|
422
510
|
const storedRefreshToken = getRefreshToken();
|
|
423
511
|
console.log("\u{1F504} Refreshing token:", {
|
|
@@ -430,6 +518,7 @@ async function refreshToken() {
|
|
|
430
518
|
credentials: "include",
|
|
431
519
|
// โ
Include httpOnly cookies (for HTTPS)
|
|
432
520
|
headers: {
|
|
521
|
+
...diagnosticHeaders(),
|
|
433
522
|
"Content-Type": "application/json"
|
|
434
523
|
}
|
|
435
524
|
};
|
|
@@ -440,8 +529,19 @@ async function refreshToken() {
|
|
|
440
529
|
const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
|
|
441
530
|
if (!response.ok) {
|
|
442
531
|
const errorText = await response.text();
|
|
443
|
-
|
|
444
|
-
|
|
532
|
+
let serverCode = null;
|
|
533
|
+
try {
|
|
534
|
+
serverCode = ((_a = JSON.parse(errorText)) == null ? void 0 : _a.error) || ((_b = JSON.parse(errorText)) == null ? void 0 : _b.code);
|
|
535
|
+
} catch {
|
|
536
|
+
}
|
|
537
|
+
emitAuthDiagnostic("TOKEN_REFRESH_REJECTED", "FAILURE", serverCode || `HTTP_${response.status}`, {
|
|
538
|
+
clientKey,
|
|
539
|
+
status: response.status
|
|
540
|
+
});
|
|
541
|
+
const refreshError = new Error(`Refresh failed: ${response.status}`);
|
|
542
|
+
refreshError.code = serverCode || `HTTP_${response.status}`;
|
|
543
|
+
refreshError.status = response.status;
|
|
544
|
+
throw refreshError;
|
|
445
545
|
}
|
|
446
546
|
const data = await response.json();
|
|
447
547
|
const { access_token, refresh_token: new_refresh_token } = data;
|
|
@@ -454,10 +554,11 @@ async function refreshToken() {
|
|
|
454
554
|
console.log("\u{1F504} New refresh token stored from rotation");
|
|
455
555
|
}
|
|
456
556
|
console.log("\u2705 Token refresh successful, listeners notified");
|
|
557
|
+
emitAuthDiagnostic("TOKEN_REFRESH_COMPLETED", "SUCCESS", "NONE", { clientKey });
|
|
457
558
|
return access_token;
|
|
458
559
|
} catch (err) {
|
|
459
560
|
console.error("\u274C Token refresh error:", err);
|
|
460
|
-
const isAuthRejection = ((
|
|
561
|
+
const isAuthRejection = ((_c = err.message) == null ? void 0 : _c.includes("401")) || ((_d = err.message) == null ? void 0 : _d.includes("403")) || ((_e = err.message) == null ? void 0 : _e.includes("invalid_grant")) || ((_f = err.message) == null ? void 0 : _f.includes("Refresh failed: 4"));
|
|
461
562
|
if (isAuthRejection) {
|
|
462
563
|
clearToken();
|
|
463
564
|
clearRefreshToken();
|
|
@@ -480,6 +581,7 @@ async function validateCurrentSession() {
|
|
|
480
581
|
const response = await fetch(`${authBaseUrl}/account/validate-session`, {
|
|
481
582
|
method: "GET",
|
|
482
583
|
headers: {
|
|
584
|
+
...diagnosticHeaders(),
|
|
483
585
|
"Authorization": `Bearer ${token}`,
|
|
484
586
|
"Content-Type": "application/json"
|
|
485
587
|
},
|
|
@@ -732,6 +834,7 @@ api.interceptors.request.use((config2) => {
|
|
|
732
834
|
if ((runtimeConfig == null ? void 0 : runtimeConfig.clientKey) && !config2.headers["X-Client-Key"]) {
|
|
733
835
|
config2.headers["X-Client-Key"] = runtimeConfig.clientKey;
|
|
734
836
|
}
|
|
837
|
+
Object.assign(config2.headers, diagnosticHeaders());
|
|
735
838
|
const token = getToken();
|
|
736
839
|
if (token) {
|
|
737
840
|
config2.headers.Authorization = `Bearer ${token}`;
|
|
@@ -750,6 +853,10 @@ api.interceptors.response.use(
|
|
|
750
853
|
return Promise.reject(error);
|
|
751
854
|
}
|
|
752
855
|
config2._retry = true;
|
|
856
|
+
emitAuthDiagnostic("API_401_REFRESH_STARTED", "PENDING", "HTTP_401", {
|
|
857
|
+
clientKey: getConfig().clientKey,
|
|
858
|
+
status: 401
|
|
859
|
+
});
|
|
753
860
|
if (!refreshPromise2) {
|
|
754
861
|
refreshPromise2 = refreshToken().then((newToken) => {
|
|
755
862
|
refreshPromise2 = null;
|
|
@@ -809,6 +916,208 @@ function isAuthenticated() {
|
|
|
809
916
|
return !!token && !isTokenExpired(token, 10);
|
|
810
917
|
}
|
|
811
918
|
|
|
919
|
+
// preferences.js
|
|
920
|
+
var PREFERENCE_DEFAULTS = Object.freeze({
|
|
921
|
+
theme: "system",
|
|
922
|
+
colorScheme: "blue",
|
|
923
|
+
density: "comfortable",
|
|
924
|
+
reducedMotion: false,
|
|
925
|
+
language: "en",
|
|
926
|
+
timezone: "Etc/UTC",
|
|
927
|
+
dateFormat: "MM/DD/YYYY",
|
|
928
|
+
timeFormat: "12h",
|
|
929
|
+
highContrast: false,
|
|
930
|
+
largeText: false
|
|
931
|
+
});
|
|
932
|
+
var ALLOWED = {
|
|
933
|
+
theme: /* @__PURE__ */ new Set(["light", "dark", "system"]),
|
|
934
|
+
density: /* @__PURE__ */ new Set(["compact", "comfortable", "spacious"]),
|
|
935
|
+
timeFormat: /* @__PURE__ */ new Set(["12h", "24h"])
|
|
936
|
+
};
|
|
937
|
+
var listeners2 = /* @__PURE__ */ new Set();
|
|
938
|
+
var cachedPreferences = null;
|
|
939
|
+
var pendingRequest = null;
|
|
940
|
+
var syncCleanup = null;
|
|
941
|
+
function extractResponseData(response) {
|
|
942
|
+
var _a;
|
|
943
|
+
return ((_a = response == null ? void 0 : response.data) == null ? void 0 : _a.data) ?? (response == null ? void 0 : response.data) ?? response ?? {};
|
|
944
|
+
}
|
|
945
|
+
function normalizePreferences(value = {}) {
|
|
946
|
+
const next = { ...PREFERENCE_DEFAULTS };
|
|
947
|
+
for (const key of Object.keys(PREFERENCE_DEFAULTS)) {
|
|
948
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
949
|
+
const candidate = value[key];
|
|
950
|
+
if (ALLOWED[key] && !ALLOWED[key].has(candidate)) continue;
|
|
951
|
+
if (typeof PREFERENCE_DEFAULTS[key] !== typeof candidate) continue;
|
|
952
|
+
next[key] = candidate;
|
|
953
|
+
}
|
|
954
|
+
return next;
|
|
955
|
+
}
|
|
956
|
+
function resolveTheme(theme, matchMedia2 = globalThis.matchMedia) {
|
|
957
|
+
if (theme === "dark" || theme === "light") return theme;
|
|
958
|
+
return typeof matchMedia2 === "function" && matchMedia2("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
959
|
+
}
|
|
960
|
+
function announce(preferences2) {
|
|
961
|
+
cachedPreferences = normalizePreferences(preferences2);
|
|
962
|
+
listeners2.forEach((listener) => listener(cachedPreferences));
|
|
963
|
+
return cachedPreferences;
|
|
964
|
+
}
|
|
965
|
+
async function getPreferences({ force = false } = {}) {
|
|
966
|
+
if (cachedPreferences && !force) return cachedPreferences;
|
|
967
|
+
if (pendingRequest) return pendingRequest;
|
|
968
|
+
pendingRequest = api_default.get("/account/preferences").then((response) => announce(extractResponseData(response))).finally(() => {
|
|
969
|
+
pendingRequest = null;
|
|
970
|
+
});
|
|
971
|
+
return pendingRequest;
|
|
972
|
+
}
|
|
973
|
+
async function updatePreferences(patch) {
|
|
974
|
+
const allowedPatch = {};
|
|
975
|
+
for (const key of Object.keys(PREFERENCE_DEFAULTS)) {
|
|
976
|
+
if (Object.hasOwn(patch, key)) allowedPatch[key] = patch[key];
|
|
977
|
+
}
|
|
978
|
+
const candidate = normalizePreferences({ ...cachedPreferences || PREFERENCE_DEFAULTS, ...allowedPatch });
|
|
979
|
+
const response = await api_default.put("/account/preferences", allowedPatch);
|
|
980
|
+
const saved = extractResponseData(response);
|
|
981
|
+
return announce(Object.keys(saved).length ? saved : candidate);
|
|
982
|
+
}
|
|
983
|
+
function subscribePreferences(listener) {
|
|
984
|
+
listeners2.add(listener);
|
|
985
|
+
if (cachedPreferences) listener(cachedPreferences);
|
|
986
|
+
return () => listeners2.delete(listener);
|
|
987
|
+
}
|
|
988
|
+
function clearPreferenceCache() {
|
|
989
|
+
cachedPreferences = null;
|
|
990
|
+
pendingRequest = null;
|
|
991
|
+
}
|
|
992
|
+
function applyPreferences(preferences2, documentRef = globalThis.document) {
|
|
993
|
+
var _a, _b, _c;
|
|
994
|
+
if (!(documentRef == null ? void 0 : documentRef.documentElement)) return;
|
|
995
|
+
const value = normalizePreferences(preferences2);
|
|
996
|
+
const root = documentRef.documentElement;
|
|
997
|
+
const resolvedTheme = resolveTheme(value.theme, (_b = (_a = documentRef.defaultView) == null ? void 0 : _a.matchMedia) == null ? void 0 : _b.bind(documentRef.defaultView));
|
|
998
|
+
root.dataset.theme = resolvedTheme;
|
|
999
|
+
root.dataset.themePreference = value.theme;
|
|
1000
|
+
root.dataset.colorScheme = value.colorScheme;
|
|
1001
|
+
root.dataset.density = value.density;
|
|
1002
|
+
root.lang = value.language;
|
|
1003
|
+
root.style.colorScheme = resolvedTheme;
|
|
1004
|
+
root.style.setProperty("--user-accent-color", {
|
|
1005
|
+
blue: "#1976d2",
|
|
1006
|
+
green: "#2e7d32",
|
|
1007
|
+
purple: "#7b1fa2",
|
|
1008
|
+
orange: "#c65d00",
|
|
1009
|
+
red: "#c62828"
|
|
1010
|
+
}[value.colorScheme] || "#1976d2");
|
|
1011
|
+
root.classList.toggle("user-reduced-motion", value.reducedMotion);
|
|
1012
|
+
root.classList.toggle("user-high-contrast", value.highContrast);
|
|
1013
|
+
root.classList.toggle("user-large-text", value.largeText);
|
|
1014
|
+
const styleId = "sso-user-preference-styles";
|
|
1015
|
+
if (!documentRef.getElementById(styleId)) {
|
|
1016
|
+
const style = documentRef.createElement("style");
|
|
1017
|
+
style.id = styleId;
|
|
1018
|
+
style.textContent = `
|
|
1019
|
+
html.user-large-text { font-size: 112.5%; }
|
|
1020
|
+
html.user-high-contrast { filter: contrast(1.15); }
|
|
1021
|
+
html[data-density="compact"] { --user-density-padding: 6px; }
|
|
1022
|
+
html[data-density="comfortable"] { --user-density-padding: 10px; }
|
|
1023
|
+
html[data-density="spacious"] { --user-density-padding: 14px; }
|
|
1024
|
+
html[data-density] .MuiListItem-root { padding-top: var(--user-density-padding); padding-bottom: var(--user-density-padding); }
|
|
1025
|
+
html.user-reduced-motion *, html.user-reduced-motion *::before, html.user-reduced-motion *::after {
|
|
1026
|
+
animation-duration: 0.01ms !important;
|
|
1027
|
+
animation-iteration-count: 1 !important;
|
|
1028
|
+
scroll-behavior: auto !important;
|
|
1029
|
+
transition-duration: 0.01ms !important;
|
|
1030
|
+
}
|
|
1031
|
+
`;
|
|
1032
|
+
(_c = documentRef.head) == null ? void 0 : _c.appendChild(style);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
function startPreferenceSync({ refreshOnFocus = true } = {}) {
|
|
1036
|
+
var _a, _b;
|
|
1037
|
+
if (syncCleanup || typeof globalThis.addEventListener !== "function") return syncCleanup || (() => {
|
|
1038
|
+
});
|
|
1039
|
+
const refresh = () => {
|
|
1040
|
+
if (!globalThis.document || globalThis.document.visibilityState === "visible") {
|
|
1041
|
+
getPreferences({ force: true }).catch(() => {
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
const onVisibility = () => refresh();
|
|
1046
|
+
(_b = (_a = globalThis.document) == null ? void 0 : _a.addEventListener) == null ? void 0 : _b.call(_a, "visibilitychange", onVisibility);
|
|
1047
|
+
if (refreshOnFocus) globalThis.addEventListener("focus", refresh);
|
|
1048
|
+
syncCleanup = () => {
|
|
1049
|
+
var _a2, _b2;
|
|
1050
|
+
(_b2 = (_a2 = globalThis.document) == null ? void 0 : _a2.removeEventListener) == null ? void 0 : _b2.call(_a2, "visibilitychange", onVisibility);
|
|
1051
|
+
if (refreshOnFocus) globalThis.removeEventListener("focus", refresh);
|
|
1052
|
+
syncCleanup = null;
|
|
1053
|
+
};
|
|
1054
|
+
return syncCleanup;
|
|
1055
|
+
}
|
|
1056
|
+
function localeFor(language) {
|
|
1057
|
+
const supported = Intl.DateTimeFormat.supportedLocalesOf([language]);
|
|
1058
|
+
return supported[0] || "en";
|
|
1059
|
+
}
|
|
1060
|
+
function fixedOffset(value) {
|
|
1061
|
+
const match = /^UTC([+-])(\d{2}):(\d{2})$/.exec(value || "");
|
|
1062
|
+
if (!match) return null;
|
|
1063
|
+
const minutes = Number(match[2]) * 60 + Number(match[3]);
|
|
1064
|
+
return (match[1] === "-" ? -1 : 1) * minutes;
|
|
1065
|
+
}
|
|
1066
|
+
function formatUserDate(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, options = {}) {
|
|
1067
|
+
const value = normalizePreferences(preferences2);
|
|
1068
|
+
const date = input instanceof Date ? input : new Date(input);
|
|
1069
|
+
const offset = fixedOffset(value.timezone);
|
|
1070
|
+
const adjusted = offset === null ? date : new Date(date.getTime() + offset * 6e4);
|
|
1071
|
+
const timeZone = offset === null ? value.timezone : "UTC";
|
|
1072
|
+
const hasExplicitParts = ["year", "month", "day", "weekday", "hour", "minute", "second", "dateStyle", "timeStyle"].some((key) => options[key] !== void 0);
|
|
1073
|
+
const dateOptions = hasExplicitParts ? options : { year: "numeric", month: "2-digit", day: "2-digit" };
|
|
1074
|
+
const formatter = new Intl.DateTimeFormat(localeFor(value.language), {
|
|
1075
|
+
...dateOptions,
|
|
1076
|
+
timeZone,
|
|
1077
|
+
...dateOptions.hour || dateOptions.timeStyle ? { hour12: value.timeFormat === "12h" } : {}
|
|
1078
|
+
});
|
|
1079
|
+
if (!hasExplicitParts) {
|
|
1080
|
+
const parts = Object.fromEntries(formatter.formatToParts(adjusted).map((part) => [part.type, part.value]));
|
|
1081
|
+
if (value.dateFormat === "DD/MM/YYYY") return `${parts.day}/${parts.month}/${parts.year}`;
|
|
1082
|
+
if (value.dateFormat === "YYYY-MM-DD") return `${parts.year}-${parts.month}-${parts.day}`;
|
|
1083
|
+
return `${parts.month}/${parts.day}/${parts.year}`;
|
|
1084
|
+
}
|
|
1085
|
+
return formatter.format(adjusted);
|
|
1086
|
+
}
|
|
1087
|
+
function formatUserNumber(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, options = {}) {
|
|
1088
|
+
const value = normalizePreferences(preferences2);
|
|
1089
|
+
return new Intl.NumberFormat(localeFor(value.language), options).format(input);
|
|
1090
|
+
}
|
|
1091
|
+
function formatUserRelativeTime(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, now = /* @__PURE__ */ new Date()) {
|
|
1092
|
+
const value = normalizePreferences(preferences2);
|
|
1093
|
+
const deltaSeconds = (new Date(input).getTime() - now.getTime()) / 1e3;
|
|
1094
|
+
const units = [
|
|
1095
|
+
["year", 31536e3],
|
|
1096
|
+
["month", 2592e3],
|
|
1097
|
+
["week", 604800],
|
|
1098
|
+
["day", 86400],
|
|
1099
|
+
["hour", 3600],
|
|
1100
|
+
["minute", 60],
|
|
1101
|
+
["second", 1]
|
|
1102
|
+
];
|
|
1103
|
+
const [unit, seconds] = units.find(([, size]) => Math.abs(deltaSeconds) >= size) || units.at(-1);
|
|
1104
|
+
return new Intl.RelativeTimeFormat(localeFor(value.language), { numeric: "auto" }).format(Math.round(deltaSeconds / seconds), unit);
|
|
1105
|
+
}
|
|
1106
|
+
var preferences = {
|
|
1107
|
+
defaults: PREFERENCE_DEFAULTS,
|
|
1108
|
+
get: getPreferences,
|
|
1109
|
+
update: updatePreferences,
|
|
1110
|
+
subscribe: subscribePreferences,
|
|
1111
|
+
clearCache: clearPreferenceCache,
|
|
1112
|
+
apply: applyPreferences,
|
|
1113
|
+
startSync: startPreferenceSync,
|
|
1114
|
+
formatDate: formatUserDate,
|
|
1115
|
+
formatNumber: formatUserNumber,
|
|
1116
|
+
formatRelativeTime: formatUserRelativeTime,
|
|
1117
|
+
normalize: normalizePreferences,
|
|
1118
|
+
resolveTheme
|
|
1119
|
+
};
|
|
1120
|
+
|
|
812
1121
|
// react/AuthProvider.jsx
|
|
813
1122
|
import React, { createContext, useState, useEffect, useRef } from "react";
|
|
814
1123
|
var AuthContext = createContext();
|
|
@@ -816,6 +1125,10 @@ var AuthContext = createContext();
|
|
|
816
1125
|
// react/useAuth.js
|
|
817
1126
|
import { useContext } from "react";
|
|
818
1127
|
|
|
1128
|
+
// react/PreferencesProvider.jsx
|
|
1129
|
+
import React2, { createContext as createContext2, useContext as useContext2, useEffect as useEffect2, useMemo, useState as useState2 } from "react";
|
|
1130
|
+
var PreferencesContext = createContext2(null);
|
|
1131
|
+
|
|
819
1132
|
// index.js
|
|
820
1133
|
var auth = {
|
|
821
1134
|
// ๐ง Config
|
|
@@ -846,6 +1159,10 @@ var auth = {
|
|
|
846
1159
|
// โ
Debug function
|
|
847
1160
|
// ๐ Authenticated API client
|
|
848
1161
|
api: api_default,
|
|
1162
|
+
preferences,
|
|
1163
|
+
// ๐ Safe authentication diagnostics
|
|
1164
|
+
getDiagnosticContext,
|
|
1165
|
+
emitAuthDiagnostic,
|
|
849
1166
|
// ๐งช Utilities
|
|
850
1167
|
decodeToken,
|
|
851
1168
|
isTokenExpired,
|
|
@@ -948,7 +1265,7 @@ var useSessionMonitor = (options = {}) => {
|
|
|
948
1265
|
staleTime: refetchInterval / 2
|
|
949
1266
|
// Consider stale at half the interval
|
|
950
1267
|
});
|
|
951
|
-
|
|
1268
|
+
useEffect3(() => {
|
|
952
1269
|
if (!enabled) return;
|
|
953
1270
|
const handleVisibilityChange = () => {
|
|
954
1271
|
if (document.visibilityState === "visible" && auth.getToken()) {
|
|
@@ -961,7 +1278,7 @@ var useSessionMonitor = (options = {}) => {
|
|
|
961
1278
|
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
962
1279
|
};
|
|
963
1280
|
}, [enabled, queryClient]);
|
|
964
|
-
|
|
1281
|
+
useEffect3(() => {
|
|
965
1282
|
if (validateOnMount && enabled && auth.getToken()) {
|
|
966
1283
|
queryClient.invalidateQueries({ queryKey: ["session-validation"] });
|
|
967
1284
|
}
|