@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.
@@ -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");
@@ -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
- throw new Error(`Authentication failed: ${errorDescription}`);
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
- throw new Error("No access token found in callback URL");
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
- console.error("\u274C Token refresh failed:", response.status, errorText);
477
- throw new Error(`Refresh failed: ${response.status}`);
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 = ((_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"));
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, import_react3.useCallback)(() => {
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, import_react3.useEffect)(() => {
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, import_react3.useEffect)(() => {
1314
+ (0, import_react4.useEffect)(() => {
998
1315
  if (validateOnMount && enabled && auth.getToken()) {
999
1316
  queryClient.invalidateQueries({ queryKey: ["session-validation"] });
1000
1317
  }