@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.
@@ -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 import_react3 = require("react");
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 setRefreshToken(token) {
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
- if (data == null ? void 0 : data.logoutRedirectUrl) {
375
- window.location.replace(data.logoutRedirectUrl);
376
- return;
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
- throw new Error(`Authentication failed: ${errorDescription}`);
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
- const { persistRefreshToken } = getConfig();
420
- const isHttpDev = typeof window !== "undefined" && ((_a = window.location) == null ? void 0 : _a.protocol) === "http:";
421
- if (persistRefreshToken || isHttpDev) {
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
- throw new Error("No access token found in callback URL");
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
- console.error("\u274C Token refresh failed:", response.status, errorText);
477
- throw new Error(`Refresh failed: ${response.status}`);
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 = ((_a = err.message) == null ? void 0 : _a.includes("401")) || ((_b = err.message) == null ? void 0 : _b.includes("403")) || ((_c = err.message) == null ? void 0 : _c.includes("invalid_grant")) || ((_d = err.message) == null ? void 0 : _d.includes("Refresh failed: 4"));
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, import_react3.useCallback)(() => {
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, import_react3.useEffect)(() => {
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, import_react3.useEffect)(() => {
1316
+ (0, import_react4.useEffect)(() => {
998
1317
  if (validateOnMount && enabled && auth.getToken()) {
999
1318
  queryClient.invalidateQueries({ queryKey: ["session-validation"] });
1000
1319
  }