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