@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/index.cjs CHANGED
@@ -30,8 +30,21 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  var index_exports = {};
31
31
  __export(index_exports, {
32
32
  AuthProvider: () => AuthProvider,
33
+ PREFERENCE_DEFAULTS: () => PREFERENCE_DEFAULTS,
34
+ PreferencesProvider: () => PreferencesProvider,
35
+ applyPreferences: () => applyPreferences,
33
36
  auth: () => auth,
37
+ formatUserDate: () => formatUserDate,
38
+ formatUserNumber: () => formatUserNumber,
39
+ formatUserRelativeTime: () => formatUserRelativeTime,
40
+ getPreferences: () => getPreferences,
41
+ normalizePreferences: () => normalizePreferences,
42
+ resolveTheme: () => resolveTheme,
43
+ startPreferenceSync: () => startPreferenceSync,
44
+ subscribePreferences: () => subscribePreferences,
45
+ updatePreferences: () => updatePreferences,
34
46
  useAuth: () => useAuth,
47
+ usePreferences: () => usePreferences,
35
48
  useSessionMonitor: () => useSessionMonitor
36
49
  });
37
50
  module.exports = __toCommonJS(index_exports);
@@ -266,8 +279,8 @@ function setConfig(customConfig = {}) {
266
279
  // โœ… Auto-detect router mode
267
280
  isRouter: customConfig.isRouter || customConfig.clientKey === "account-ui"
268
281
  };
282
+ enableRefreshTokenPersistence(config.persistRefreshToken);
269
283
  if (config.persistRefreshToken) {
270
- enableRefreshTokenPersistence(true);
271
284
  console.log("\u{1F4E6} Refresh token persistence ENABLED (localStorage on HTTPS)");
272
285
  }
273
286
  console.log(`\u{1F527} Auth Client Mode: ${config.isRouter ? "ROUTER" : "CLIENT"}`, {
@@ -283,6 +296,82 @@ function isRouterMode() {
283
296
  return config.isRouter;
284
297
  }
285
298
 
299
+ // diagnostics.js
300
+ var STORAGE_KEY = "auth_diagnostic_context";
301
+ var LOGIN_LOCK_KEY = "auth_login_lock";
302
+ var randomId = () => {
303
+ var _a;
304
+ if ((_a = globalThis.crypto) == null ? void 0 : _a.randomUUID) return globalThis.crypto.randomUUID();
305
+ return `auth-${Date.now()}-${Math.random().toString(36).slice(2)}`;
306
+ };
307
+ var fingerprint = async (value) => {
308
+ var _a;
309
+ if (!value || !((_a = globalThis.crypto) == null ? void 0 : _a.subtle)) return null;
310
+ const bytes = new TextEncoder().encode(String(value));
311
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
312
+ return [...new Uint8Array(digest)].slice(0, 6).map((byte) => byte.toString(16).padStart(2, "0")).join("");
313
+ };
314
+ function getDiagnosticContext() {
315
+ try {
316
+ const existing = JSON.parse(sessionStorage.getItem(STORAGE_KEY) || "null");
317
+ if (existing == null ? void 0 : existing.correlationId) return existing;
318
+ } catch {
319
+ }
320
+ const context = { correlationId: randomId(), startedAt: Date.now() };
321
+ try {
322
+ sessionStorage.setItem(STORAGE_KEY, JSON.stringify(context));
323
+ } catch {
324
+ }
325
+ return context;
326
+ }
327
+ function resetDiagnosticContext() {
328
+ const context = { correlationId: randomId(), startedAt: Date.now() };
329
+ try {
330
+ sessionStorage.setItem(STORAGE_KEY, JSON.stringify(context));
331
+ } catch {
332
+ }
333
+ return context;
334
+ }
335
+ function diagnosticHeaders() {
336
+ const { correlationId } = getDiagnosticContext();
337
+ return { "X-Correlation-ID": correlationId, "X-Request-ID": correlationId };
338
+ }
339
+ function acquireLoginLock(clientKey, redirectUri, ttlMs = 5e3) {
340
+ const now = Date.now();
341
+ const signature = `${clientKey}|${redirectUri}`;
342
+ try {
343
+ const current = JSON.parse(sessionStorage.getItem(LOGIN_LOCK_KEY) || "null");
344
+ if ((current == null ? void 0 : current.signature) === signature && now - current.createdAt < ttlMs) return false;
345
+ sessionStorage.setItem(LOGIN_LOCK_KEY, JSON.stringify({ signature, createdAt: now }));
346
+ } catch {
347
+ }
348
+ return true;
349
+ }
350
+ function clearLoginLock() {
351
+ try {
352
+ sessionStorage.removeItem(LOGIN_LOCK_KEY);
353
+ } catch {
354
+ }
355
+ }
356
+ async function emitAuthDiagnostic(event, outcome, reasonCode, details = {}) {
357
+ const context = getDiagnosticContext();
358
+ const safe = {
359
+ eventType: "auth_client_diagnostic",
360
+ event,
361
+ outcome,
362
+ reasonCode,
363
+ correlationId: context.correlationId,
364
+ elapsedMs: Date.now() - context.startedAt,
365
+ clientKey: details.clientKey || null,
366
+ status: details.status || null,
367
+ stateFingerprint: await fingerprint(details.state),
368
+ online: typeof navigator === "undefined" ? null : navigator.onLine
369
+ };
370
+ const method = outcome === "FAILURE" ? "error" : outcome === "WARNING" ? "warn" : "info";
371
+ console[method]("[auth-client]", safe);
372
+ return safe;
373
+ }
374
+
286
375
  // core.js
287
376
  var callbackProcessed = false;
288
377
  function login(clientKeyArg, redirectUriArg) {
@@ -295,14 +384,16 @@ function login(clientKeyArg, redirectUriArg) {
295
384
  } = getConfig();
296
385
  const clientKey = clientKeyArg || defaultClientKey;
297
386
  const redirectUri = redirectUriArg || defaultRedirectUri;
298
- console.log("\u{1F504} Smart Login initiated:", {
299
- mode: isRouterMode() ? "ROUTER" : "CLIENT",
300
- clientKey,
301
- redirectUri
302
- });
303
387
  if (!clientKey || !redirectUri) {
388
+ emitAuthDiagnostic("LOGIN_REJECTED", "FAILURE", "CLIENT_CONFIG_MISSING", { clientKey });
304
389
  throw new Error("Missing clientKey or redirectUri");
305
390
  }
391
+ if (!acquireLoginLock(clientKey, redirectUri)) {
392
+ emitAuthDiagnostic("LOGIN_DUPLICATE_SUPPRESSED", "WARNING", "LOGIN_ALREADY_IN_PROGRESS", { clientKey });
393
+ return false;
394
+ }
395
+ resetDiagnosticContext();
396
+ emitAuthDiagnostic("LOGIN_INITIATED", "PENDING", "NONE", { clientKey });
306
397
  sessionStorage.setItem("originalApp", clientKey);
307
398
  sessionStorage.setItem("returnUrl", redirectUri);
308
399
  if (isRouterMode()) {
@@ -317,13 +408,9 @@ function routerLogin(clientKey, redirectUri) {
317
408
  if (redirectUri) {
318
409
  params.append("redirect_uri", redirectUri);
319
410
  }
411
+ params.append("correlation_id", getDiagnosticContext().correlationId);
320
412
  const query = params.toString();
321
413
  const backendLoginUrl = `${authBaseUrl}/login/${clientKey}${query ? `?${query}` : ""}`;
322
- console.log("\u{1F3ED} Router Login: Direct backend authentication", {
323
- clientKey,
324
- redirectUri,
325
- backendUrl: backendLoginUrl
326
- });
327
414
  window.location.href = backendLoginUrl;
328
415
  }
329
416
  function clientLogin(clientKey, redirectUri) {
@@ -335,11 +422,6 @@ function clientLogin(clientKey, redirectUri) {
335
422
  params.append("redirect_uri", redirectUri);
336
423
  }
337
424
  const centralizedLoginUrl = `${accountUiUrl}/login?${params.toString()}`;
338
- console.log("\u{1F504} Client Login: Redirecting to centralized login", {
339
- clientKey,
340
- redirectUri,
341
- centralizedUrl: centralizedLoginUrl
342
- });
343
425
  window.location.href = centralizedLoginUrl;
344
426
  }
345
427
  async function logout(options = {}) {
@@ -362,6 +444,7 @@ async function logout(options = {}) {
362
444
  method: "POST",
363
445
  credentials: "include",
364
446
  headers: {
447
+ ...diagnosticHeaders(),
365
448
  "Authorization": token ? `Bearer ${token}` : "",
366
449
  "Content-Type": "application/json"
367
450
  },
@@ -407,11 +490,19 @@ function handleCallback() {
407
490
  callbackProcessed = false;
408
491
  }
409
492
  callbackProcessed = true;
493
+ clearLoginLock();
410
494
  sessionStorage.removeItem("originalApp");
411
495
  sessionStorage.removeItem("returnUrl");
412
496
  if (error) {
413
497
  const errorDescription = params.get("error_description") || error;
414
- throw new Error(`Authentication failed: ${errorDescription}`);
498
+ const authError = new Error(`Authentication failed: ${errorDescription}`);
499
+ authError.code = error;
500
+ authError.correlationId = getDiagnosticContext().correlationId;
501
+ emitAuthDiagnostic("CALLBACK_REJECTED", "FAILURE", error.toUpperCase(), {
502
+ clientKey: getConfig().clientKey,
503
+ state: params.get("state")
504
+ });
505
+ throw authError;
415
506
  }
416
507
  if (accessToken2) {
417
508
  setToken(accessToken2);
@@ -434,9 +525,19 @@ function handleCallback() {
434
525
  url.searchParams.delete("error_description");
435
526
  window.history.replaceState({}, "", url);
436
527
  console.log("\u2705 Callback processed successfully, token stored");
528
+ emitAuthDiagnostic("CALLBACK_COMPLETED", "SUCCESS", "NONE", {
529
+ clientKey: getConfig().clientKey,
530
+ state: params.get("state")
531
+ });
437
532
  return accessToken2;
438
533
  }
439
- throw new Error("No access token found in callback URL");
534
+ emitAuthDiagnostic("CALLBACK_REJECTED", "FAILURE", "ACCESS_TOKEN_MISSING", {
535
+ clientKey: getConfig().clientKey,
536
+ state: params.get("state")
537
+ });
538
+ const missingTokenError = new Error("No access token found in callback URL");
539
+ missingTokenError.code = "ACCESS_TOKEN_MISSING";
540
+ throw missingTokenError;
440
541
  }
441
542
  function resetCallbackState() {
442
543
  callbackProcessed = false;
@@ -451,7 +552,7 @@ async function refreshToken() {
451
552
  }
452
553
  refreshInProgress = true;
453
554
  refreshPromise = (async () => {
454
- var _a, _b, _c, _d;
555
+ var _a, _b, _c, _d, _e, _f;
455
556
  try {
456
557
  const storedRefreshToken = getRefreshToken();
457
558
  console.log("\u{1F504} Refreshing token:", {
@@ -464,6 +565,7 @@ async function refreshToken() {
464
565
  credentials: "include",
465
566
  // โœ… Include httpOnly cookies (for HTTPS)
466
567
  headers: {
568
+ ...diagnosticHeaders(),
467
569
  "Content-Type": "application/json"
468
570
  }
469
571
  };
@@ -474,8 +576,19 @@ async function refreshToken() {
474
576
  const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
475
577
  if (!response.ok) {
476
578
  const errorText = await response.text();
477
- console.error("\u274C Token refresh failed:", response.status, errorText);
478
- throw new Error(`Refresh failed: ${response.status}`);
579
+ let serverCode = null;
580
+ try {
581
+ serverCode = ((_a = JSON.parse(errorText)) == null ? void 0 : _a.error) || ((_b = JSON.parse(errorText)) == null ? void 0 : _b.code);
582
+ } catch {
583
+ }
584
+ emitAuthDiagnostic("TOKEN_REFRESH_REJECTED", "FAILURE", serverCode || `HTTP_${response.status}`, {
585
+ clientKey,
586
+ status: response.status
587
+ });
588
+ const refreshError = new Error(`Refresh failed: ${response.status}`);
589
+ refreshError.code = serverCode || `HTTP_${response.status}`;
590
+ refreshError.status = response.status;
591
+ throw refreshError;
479
592
  }
480
593
  const data = await response.json();
481
594
  const { access_token, refresh_token: new_refresh_token } = data;
@@ -488,10 +601,11 @@ async function refreshToken() {
488
601
  console.log("\u{1F504} New refresh token stored from rotation");
489
602
  }
490
603
  console.log("\u2705 Token refresh successful, listeners notified");
604
+ emitAuthDiagnostic("TOKEN_REFRESH_COMPLETED", "SUCCESS", "NONE", { clientKey });
491
605
  return access_token;
492
606
  } catch (err) {
493
607
  console.error("\u274C Token refresh error:", err);
494
- 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"));
608
+ 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"));
495
609
  if (isAuthRejection) {
496
610
  clearToken();
497
611
  clearRefreshToken();
@@ -514,6 +628,7 @@ async function validateCurrentSession() {
514
628
  const response = await fetch(`${authBaseUrl}/account/validate-session`, {
515
629
  method: "GET",
516
630
  headers: {
631
+ ...diagnosticHeaders(),
517
632
  "Authorization": `Bearer ${token}`,
518
633
  "Content-Type": "application/json"
519
634
  },
@@ -766,6 +881,7 @@ api.interceptors.request.use((config2) => {
766
881
  if ((runtimeConfig == null ? void 0 : runtimeConfig.clientKey) && !config2.headers["X-Client-Key"]) {
767
882
  config2.headers["X-Client-Key"] = runtimeConfig.clientKey;
768
883
  }
884
+ Object.assign(config2.headers, diagnosticHeaders());
769
885
  const token = getToken();
770
886
  if (token) {
771
887
  config2.headers.Authorization = `Bearer ${token}`;
@@ -784,6 +900,10 @@ api.interceptors.response.use(
784
900
  return Promise.reject(error);
785
901
  }
786
902
  config2._retry = true;
903
+ emitAuthDiagnostic("API_401_REFRESH_STARTED", "PENDING", "HTTP_401", {
904
+ clientKey: getConfig().clientKey,
905
+ status: 401
906
+ });
787
907
  if (!refreshPromise2) {
788
908
  refreshPromise2 = refreshToken().then((newToken) => {
789
909
  refreshPromise2 = null;
@@ -843,6 +963,208 @@ function isAuthenticated() {
843
963
  return !!token && !isTokenExpired(token, 10);
844
964
  }
845
965
 
966
+ // preferences.js
967
+ var PREFERENCE_DEFAULTS = Object.freeze({
968
+ theme: "system",
969
+ colorScheme: "blue",
970
+ density: "comfortable",
971
+ reducedMotion: false,
972
+ language: "en",
973
+ timezone: "Etc/UTC",
974
+ dateFormat: "MM/DD/YYYY",
975
+ timeFormat: "12h",
976
+ highContrast: false,
977
+ largeText: false
978
+ });
979
+ var ALLOWED = {
980
+ theme: /* @__PURE__ */ new Set(["light", "dark", "system"]),
981
+ density: /* @__PURE__ */ new Set(["compact", "comfortable", "spacious"]),
982
+ timeFormat: /* @__PURE__ */ new Set(["12h", "24h"])
983
+ };
984
+ var listeners2 = /* @__PURE__ */ new Set();
985
+ var cachedPreferences = null;
986
+ var pendingRequest = null;
987
+ var syncCleanup = null;
988
+ function extractResponseData(response) {
989
+ var _a;
990
+ return ((_a = response == null ? void 0 : response.data) == null ? void 0 : _a.data) ?? (response == null ? void 0 : response.data) ?? response ?? {};
991
+ }
992
+ function normalizePreferences(value = {}) {
993
+ const next = { ...PREFERENCE_DEFAULTS };
994
+ for (const key of Object.keys(PREFERENCE_DEFAULTS)) {
995
+ if (!Object.hasOwn(value, key)) continue;
996
+ const candidate = value[key];
997
+ if (ALLOWED[key] && !ALLOWED[key].has(candidate)) continue;
998
+ if (typeof PREFERENCE_DEFAULTS[key] !== typeof candidate) continue;
999
+ next[key] = candidate;
1000
+ }
1001
+ return next;
1002
+ }
1003
+ function resolveTheme(theme, matchMedia2 = globalThis.matchMedia) {
1004
+ if (theme === "dark" || theme === "light") return theme;
1005
+ return typeof matchMedia2 === "function" && matchMedia2("(prefers-color-scheme: dark)").matches ? "dark" : "light";
1006
+ }
1007
+ function announce(preferences2) {
1008
+ cachedPreferences = normalizePreferences(preferences2);
1009
+ listeners2.forEach((listener) => listener(cachedPreferences));
1010
+ return cachedPreferences;
1011
+ }
1012
+ async function getPreferences({ force = false } = {}) {
1013
+ if (cachedPreferences && !force) return cachedPreferences;
1014
+ if (pendingRequest) return pendingRequest;
1015
+ pendingRequest = api_default.get("/account/preferences").then((response) => announce(extractResponseData(response))).finally(() => {
1016
+ pendingRequest = null;
1017
+ });
1018
+ return pendingRequest;
1019
+ }
1020
+ async function updatePreferences(patch) {
1021
+ const allowedPatch = {};
1022
+ for (const key of Object.keys(PREFERENCE_DEFAULTS)) {
1023
+ if (Object.hasOwn(patch, key)) allowedPatch[key] = patch[key];
1024
+ }
1025
+ const candidate = normalizePreferences({ ...cachedPreferences || PREFERENCE_DEFAULTS, ...allowedPatch });
1026
+ const response = await api_default.put("/account/preferences", allowedPatch);
1027
+ const saved = extractResponseData(response);
1028
+ return announce(Object.keys(saved).length ? saved : candidate);
1029
+ }
1030
+ function subscribePreferences(listener) {
1031
+ listeners2.add(listener);
1032
+ if (cachedPreferences) listener(cachedPreferences);
1033
+ return () => listeners2.delete(listener);
1034
+ }
1035
+ function clearPreferenceCache() {
1036
+ cachedPreferences = null;
1037
+ pendingRequest = null;
1038
+ }
1039
+ function applyPreferences(preferences2, documentRef = globalThis.document) {
1040
+ var _a, _b, _c;
1041
+ if (!(documentRef == null ? void 0 : documentRef.documentElement)) return;
1042
+ const value = normalizePreferences(preferences2);
1043
+ const root = documentRef.documentElement;
1044
+ const resolvedTheme = resolveTheme(value.theme, (_b = (_a = documentRef.defaultView) == null ? void 0 : _a.matchMedia) == null ? void 0 : _b.bind(documentRef.defaultView));
1045
+ root.dataset.theme = resolvedTheme;
1046
+ root.dataset.themePreference = value.theme;
1047
+ root.dataset.colorScheme = value.colorScheme;
1048
+ root.dataset.density = value.density;
1049
+ root.lang = value.language;
1050
+ root.style.colorScheme = resolvedTheme;
1051
+ root.style.setProperty("--user-accent-color", {
1052
+ blue: "#1976d2",
1053
+ green: "#2e7d32",
1054
+ purple: "#7b1fa2",
1055
+ orange: "#c65d00",
1056
+ red: "#c62828"
1057
+ }[value.colorScheme] || "#1976d2");
1058
+ root.classList.toggle("user-reduced-motion", value.reducedMotion);
1059
+ root.classList.toggle("user-high-contrast", value.highContrast);
1060
+ root.classList.toggle("user-large-text", value.largeText);
1061
+ const styleId = "sso-user-preference-styles";
1062
+ if (!documentRef.getElementById(styleId)) {
1063
+ const style = documentRef.createElement("style");
1064
+ style.id = styleId;
1065
+ style.textContent = `
1066
+ html.user-large-text { font-size: 112.5%; }
1067
+ html.user-high-contrast { filter: contrast(1.15); }
1068
+ html[data-density="compact"] { --user-density-padding: 6px; }
1069
+ html[data-density="comfortable"] { --user-density-padding: 10px; }
1070
+ html[data-density="spacious"] { --user-density-padding: 14px; }
1071
+ html[data-density] .MuiListItem-root { padding-top: var(--user-density-padding); padding-bottom: var(--user-density-padding); }
1072
+ html.user-reduced-motion *, html.user-reduced-motion *::before, html.user-reduced-motion *::after {
1073
+ animation-duration: 0.01ms !important;
1074
+ animation-iteration-count: 1 !important;
1075
+ scroll-behavior: auto !important;
1076
+ transition-duration: 0.01ms !important;
1077
+ }
1078
+ `;
1079
+ (_c = documentRef.head) == null ? void 0 : _c.appendChild(style);
1080
+ }
1081
+ }
1082
+ function startPreferenceSync({ refreshOnFocus = true } = {}) {
1083
+ var _a, _b;
1084
+ if (syncCleanup || typeof globalThis.addEventListener !== "function") return syncCleanup || (() => {
1085
+ });
1086
+ const refresh = () => {
1087
+ if (!globalThis.document || globalThis.document.visibilityState === "visible") {
1088
+ getPreferences({ force: true }).catch(() => {
1089
+ });
1090
+ }
1091
+ };
1092
+ const onVisibility = () => refresh();
1093
+ (_b = (_a = globalThis.document) == null ? void 0 : _a.addEventListener) == null ? void 0 : _b.call(_a, "visibilitychange", onVisibility);
1094
+ if (refreshOnFocus) globalThis.addEventListener("focus", refresh);
1095
+ syncCleanup = () => {
1096
+ var _a2, _b2;
1097
+ (_b2 = (_a2 = globalThis.document) == null ? void 0 : _a2.removeEventListener) == null ? void 0 : _b2.call(_a2, "visibilitychange", onVisibility);
1098
+ if (refreshOnFocus) globalThis.removeEventListener("focus", refresh);
1099
+ syncCleanup = null;
1100
+ };
1101
+ return syncCleanup;
1102
+ }
1103
+ function localeFor(language) {
1104
+ const supported = Intl.DateTimeFormat.supportedLocalesOf([language]);
1105
+ return supported[0] || "en";
1106
+ }
1107
+ function fixedOffset(value) {
1108
+ const match = /^UTC([+-])(\d{2}):(\d{2})$/.exec(value || "");
1109
+ if (!match) return null;
1110
+ const minutes = Number(match[2]) * 60 + Number(match[3]);
1111
+ return (match[1] === "-" ? -1 : 1) * minutes;
1112
+ }
1113
+ function formatUserDate(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, options = {}) {
1114
+ const value = normalizePreferences(preferences2);
1115
+ const date = input instanceof Date ? input : new Date(input);
1116
+ const offset = fixedOffset(value.timezone);
1117
+ const adjusted = offset === null ? date : new Date(date.getTime() + offset * 6e4);
1118
+ const timeZone = offset === null ? value.timezone : "UTC";
1119
+ const hasExplicitParts = ["year", "month", "day", "weekday", "hour", "minute", "second", "dateStyle", "timeStyle"].some((key) => options[key] !== void 0);
1120
+ const dateOptions = hasExplicitParts ? options : { year: "numeric", month: "2-digit", day: "2-digit" };
1121
+ const formatter = new Intl.DateTimeFormat(localeFor(value.language), {
1122
+ ...dateOptions,
1123
+ timeZone,
1124
+ ...dateOptions.hour || dateOptions.timeStyle ? { hour12: value.timeFormat === "12h" } : {}
1125
+ });
1126
+ if (!hasExplicitParts) {
1127
+ const parts = Object.fromEntries(formatter.formatToParts(adjusted).map((part) => [part.type, part.value]));
1128
+ if (value.dateFormat === "DD/MM/YYYY") return `${parts.day}/${parts.month}/${parts.year}`;
1129
+ if (value.dateFormat === "YYYY-MM-DD") return `${parts.year}-${parts.month}-${parts.day}`;
1130
+ return `${parts.month}/${parts.day}/${parts.year}`;
1131
+ }
1132
+ return formatter.format(adjusted);
1133
+ }
1134
+ function formatUserNumber(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, options = {}) {
1135
+ const value = normalizePreferences(preferences2);
1136
+ return new Intl.NumberFormat(localeFor(value.language), options).format(input);
1137
+ }
1138
+ function formatUserRelativeTime(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, now = /* @__PURE__ */ new Date()) {
1139
+ const value = normalizePreferences(preferences2);
1140
+ const deltaSeconds = (new Date(input).getTime() - now.getTime()) / 1e3;
1141
+ const units = [
1142
+ ["year", 31536e3],
1143
+ ["month", 2592e3],
1144
+ ["week", 604800],
1145
+ ["day", 86400],
1146
+ ["hour", 3600],
1147
+ ["minute", 60],
1148
+ ["second", 1]
1149
+ ];
1150
+ const [unit, seconds] = units.find(([, size]) => Math.abs(deltaSeconds) >= size) || units.at(-1);
1151
+ return new Intl.RelativeTimeFormat(localeFor(value.language), { numeric: "auto" }).format(Math.round(deltaSeconds / seconds), unit);
1152
+ }
1153
+ var preferences = {
1154
+ defaults: PREFERENCE_DEFAULTS,
1155
+ get: getPreferences,
1156
+ update: updatePreferences,
1157
+ subscribe: subscribePreferences,
1158
+ clearCache: clearPreferenceCache,
1159
+ apply: applyPreferences,
1160
+ startSync: startPreferenceSync,
1161
+ formatDate: formatUserDate,
1162
+ formatNumber: formatUserNumber,
1163
+ formatRelativeTime: formatUserRelativeTime,
1164
+ normalize: normalizePreferences,
1165
+ resolveTheme
1166
+ };
1167
+
846
1168
  // react/AuthProvider.jsx
847
1169
  var import_react = __toESM(require("react"), 1);
848
1170
  var AuthContext = (0, import_react.createContext)();
@@ -906,7 +1228,8 @@ function AuthProvider({ children, onSessionExpired }) {
906
1228
  console.log("\u{1F4E5} Profile response status:", res.status);
907
1229
  if (!res.ok) throw new Error("Failed to fetch user");
908
1230
  return res.json();
909
- }).then((userData) => {
1231
+ }).then((responseBody) => {
1232
+ const userData = (responseBody == null ? void 0 : responseBody.data) ?? responseBody;
910
1233
  console.log("\u2705 Profile fetched successfully:", userData.email);
911
1234
  setUser(userData);
912
1235
  setSessionValid(true);
@@ -1053,6 +1376,62 @@ var useSessionMonitor = (options = {}) => {
1053
1376
  };
1054
1377
  };
1055
1378
 
1379
+ // react/PreferencesProvider.jsx
1380
+ var import_react4 = __toESM(require("react"), 1);
1381
+ var PreferencesContext = (0, import_react4.createContext)(null);
1382
+ function PreferencesProvider({ children, initialPreferences, applyToDocument = true }) {
1383
+ const [preferences2, setPreferences] = (0, import_react4.useState)(() => ({
1384
+ ...PREFERENCE_DEFAULTS,
1385
+ ...initialPreferences
1386
+ }));
1387
+ const [loading, setLoading] = (0, import_react4.useState)(true);
1388
+ const [error, setError] = (0, import_react4.useState)(null);
1389
+ (0, import_react4.useEffect)(() => {
1390
+ const unsubscribe = subscribePreferences((next) => {
1391
+ setPreferences(next);
1392
+ setLoading(false);
1393
+ setError(null);
1394
+ });
1395
+ const stopSync = startPreferenceSync();
1396
+ getPreferences().catch((nextError) => {
1397
+ setError(nextError);
1398
+ setLoading(false);
1399
+ });
1400
+ return () => {
1401
+ unsubscribe();
1402
+ stopSync();
1403
+ };
1404
+ }, []);
1405
+ (0, import_react4.useEffect)(() => {
1406
+ if (applyToDocument) applyPreferences(preferences2);
1407
+ }, [applyToDocument, preferences2]);
1408
+ (0, import_react4.useEffect)(() => {
1409
+ var _a;
1410
+ if (preferences2.theme !== "system" || typeof matchMedia !== "function") return void 0;
1411
+ const media = matchMedia("(prefers-color-scheme: dark)");
1412
+ const applySystemTheme = () => applyPreferences(preferences2);
1413
+ (_a = media.addEventListener) == null ? void 0 : _a.call(media, "change", applySystemTheme);
1414
+ return () => {
1415
+ var _a2;
1416
+ return (_a2 = media.removeEventListener) == null ? void 0 : _a2.call(media, "change", applySystemTheme);
1417
+ };
1418
+ }, [preferences2]);
1419
+ const value = (0, import_react4.useMemo)(() => ({
1420
+ preferences: preferences2,
1421
+ resolvedTheme: resolveTheme(preferences2.theme),
1422
+ loading,
1423
+ error,
1424
+ refresh: () => getPreferences({ force: true }),
1425
+ update: updatePreferences
1426
+ }), [error, loading, preferences2]);
1427
+ return /* @__PURE__ */ import_react4.default.createElement(PreferencesContext.Provider, { value }, children);
1428
+ }
1429
+ function usePreferences() {
1430
+ const context = (0, import_react4.useContext)(PreferencesContext);
1431
+ if (!context) throw new Error("usePreferences must be used within a PreferencesProvider");
1432
+ return context;
1433
+ }
1434
+
1056
1435
  // index.js
1057
1436
  var auth = {
1058
1437
  // ๐Ÿ”ง Config
@@ -1083,6 +1462,10 @@ var auth = {
1083
1462
  // โœ… Debug function
1084
1463
  // ๐ŸŒ Authenticated API client
1085
1464
  api: api_default,
1465
+ preferences,
1466
+ // ๐Ÿ”Ž Safe authentication diagnostics
1467
+ getDiagnosticContext,
1468
+ emitAuthDiagnostic,
1086
1469
  // ๐Ÿงช Utilities
1087
1470
  decodeToken,
1088
1471
  isTokenExpired,
@@ -1130,8 +1513,21 @@ var auth = {
1130
1513
  // Annotate the CommonJS export names for ESM import in node:
1131
1514
  0 && (module.exports = {
1132
1515
  AuthProvider,
1516
+ PREFERENCE_DEFAULTS,
1517
+ PreferencesProvider,
1518
+ applyPreferences,
1133
1519
  auth,
1520
+ formatUserDate,
1521
+ formatUserNumber,
1522
+ formatUserRelativeTime,
1523
+ getPreferences,
1524
+ normalizePreferences,
1525
+ resolveTheme,
1526
+ startPreferenceSync,
1527
+ subscribePreferences,
1528
+ updatePreferences,
1134
1529
  useAuth,
1530
+ usePreferences,
1135
1531
  useSessionMonitor
1136
1532
  });
1137
1533
  //# sourceMappingURL=index.cjs.map