@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.
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);
@@ -149,7 +162,7 @@ function shouldUseLocalStorage() {
149
162
  return false;
150
163
  }
151
164
  }
152
- function setRefreshToken(token) {
165
+ function setRefreshToken2(token) {
153
166
  if (!token) {
154
167
  clearRefreshToken();
155
168
  return;
@@ -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 = {}) {
@@ -353,6 +435,7 @@ async function logout(options = {}) {
353
435
  clientKey,
354
436
  scope
355
437
  });
438
+ emitAuthDiagnostic("LOGOUT_INITIATED", "PENDING", "NONE", { clientKey });
356
439
  clearToken();
357
440
  clearRefreshToken();
358
441
  sessionStorage.removeItem("originalApp");
@@ -362,6 +445,7 @@ async function logout(options = {}) {
362
445
  method: "POST",
363
446
  credentials: "include",
364
447
  headers: {
448
+ ...diagnosticHeaders(),
365
449
  "Authorization": token ? `Bearer ${token}` : "",
366
450
  "Content-Type": "application/json"
367
451
  },
@@ -372,16 +456,23 @@ async function logout(options = {}) {
372
456
  }
373
457
  const data = await response.json();
374
458
  console.log("\u2705 Logout response:", data);
375
- if (data == null ? void 0 : data.logoutRedirectUrl) {
376
- window.location.replace(data.logoutRedirectUrl);
377
- return;
378
- }
459
+ emitAuthDiagnostic("LOGOUT_COMPLETED", "SUCCESS", "NONE", {
460
+ clientKey,
461
+ status: response.status
462
+ });
379
463
  if (data == null ? void 0 : data.keycloakLogoutUrl) {
380
464
  window.location.replace(data.keycloakLogoutUrl);
381
465
  return;
382
466
  }
467
+ if (data == null ? void 0 : data.logoutRedirectUrl) {
468
+ window.location.replace(data.logoutRedirectUrl);
469
+ return;
470
+ }
383
471
  } catch (error) {
384
472
  console.warn("\u26A0\uFE0F Logout backend call failed:", error);
473
+ emitAuthDiagnostic("LOGOUT_BACKEND_FAILED", "FAILURE", "LOGOUT_REQUEST_FAILED", {
474
+ clientKey
475
+ });
385
476
  }
386
477
  const fallbackUrl = isRouterMode() ? new URL("/login", window.location.origin) : new URL("/login", accountUiUrl);
387
478
  fallbackUrl.searchParams.set("logged_out", "true");
@@ -390,7 +481,6 @@ async function logout(options = {}) {
390
481
  window.location.replace(fallbackUrl.toString());
391
482
  }
392
483
  function handleCallback() {
393
- var _a;
394
484
  const params = new URLSearchParams(window.location.search);
395
485
  const accessToken2 = params.get("access_token");
396
486
  const error = params.get("error");
@@ -407,24 +497,27 @@ function handleCallback() {
407
497
  callbackProcessed = false;
408
498
  }
409
499
  callbackProcessed = true;
500
+ clearLoginLock();
410
501
  sessionStorage.removeItem("originalApp");
411
502
  sessionStorage.removeItem("returnUrl");
412
503
  if (error) {
413
504
  const errorDescription = params.get("error_description") || error;
414
- throw new Error(`Authentication failed: ${errorDescription}`);
505
+ const authError = new Error(`Authentication failed: ${errorDescription}`);
506
+ authError.code = error;
507
+ authError.correlationId = getDiagnosticContext().correlationId;
508
+ emitAuthDiagnostic("CALLBACK_REJECTED", "FAILURE", error.toUpperCase(), {
509
+ clientKey: getConfig().clientKey,
510
+ state: params.get("state")
511
+ });
512
+ throw authError;
415
513
  }
416
514
  if (accessToken2) {
417
515
  setToken(accessToken2);
418
516
  const refreshTokenInUrl = params.get("refresh_token");
419
517
  if (refreshTokenInUrl) {
420
- const { persistRefreshToken } = getConfig();
421
- const isHttpDev = typeof window !== "undefined" && ((_a = window.location) == null ? void 0 : _a.protocol) === "http:";
422
- if (persistRefreshToken || isHttpDev) {
423
- console.log(`\u{1F4E6} Storing refresh token from callback URL (${persistRefreshToken ? "persistence enabled" : "HTTP dev mode"})`);
424
- setRefreshToken(refreshTokenInUrl);
425
- } else {
426
- console.log("\u{1F512} HTTPS mode: Refresh token is in httpOnly cookie (ignoring URL param)");
427
- }
518
+ emitAuthDiagnostic("CALLBACK_REFRESH_TOKEN_IGNORED", "WARNING", "REFRESH_TOKEN_IN_URL", {
519
+ clientKey: getConfig().clientKey
520
+ });
428
521
  }
429
522
  const url = new URL(window.location);
430
523
  url.searchParams.delete("access_token");
@@ -434,9 +527,19 @@ function handleCallback() {
434
527
  url.searchParams.delete("error_description");
435
528
  window.history.replaceState({}, "", url);
436
529
  console.log("\u2705 Callback processed successfully, token stored");
530
+ emitAuthDiagnostic("CALLBACK_COMPLETED", "SUCCESS", "NONE", {
531
+ clientKey: getConfig().clientKey,
532
+ state: params.get("state")
533
+ });
437
534
  return accessToken2;
438
535
  }
439
- throw new Error("No access token found in callback URL");
536
+ emitAuthDiagnostic("CALLBACK_REJECTED", "FAILURE", "ACCESS_TOKEN_MISSING", {
537
+ clientKey: getConfig().clientKey,
538
+ state: params.get("state")
539
+ });
540
+ const missingTokenError = new Error("No access token found in callback URL");
541
+ missingTokenError.code = "ACCESS_TOKEN_MISSING";
542
+ throw missingTokenError;
440
543
  }
441
544
  function resetCallbackState() {
442
545
  callbackProcessed = false;
@@ -451,7 +554,7 @@ async function refreshToken() {
451
554
  }
452
555
  refreshInProgress = true;
453
556
  refreshPromise = (async () => {
454
- var _a, _b, _c, _d;
557
+ var _a, _b, _c, _d, _e, _f;
455
558
  try {
456
559
  const storedRefreshToken = getRefreshToken();
457
560
  console.log("\u{1F504} Refreshing token:", {
@@ -464,6 +567,7 @@ async function refreshToken() {
464
567
  credentials: "include",
465
568
  // โœ… Include httpOnly cookies (for HTTPS)
466
569
  headers: {
570
+ ...diagnosticHeaders(),
467
571
  "Content-Type": "application/json"
468
572
  }
469
573
  };
@@ -474,8 +578,19 @@ async function refreshToken() {
474
578
  const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
475
579
  if (!response.ok) {
476
580
  const errorText = await response.text();
477
- console.error("\u274C Token refresh failed:", response.status, errorText);
478
- throw new Error(`Refresh failed: ${response.status}`);
581
+ let serverCode = null;
582
+ try {
583
+ serverCode = ((_a = JSON.parse(errorText)) == null ? void 0 : _a.error) || ((_b = JSON.parse(errorText)) == null ? void 0 : _b.code);
584
+ } catch {
585
+ }
586
+ emitAuthDiagnostic("TOKEN_REFRESH_REJECTED", "FAILURE", serverCode || `HTTP_${response.status}`, {
587
+ clientKey,
588
+ status: response.status
589
+ });
590
+ const refreshError = new Error(`Refresh failed: ${response.status}`);
591
+ refreshError.code = serverCode || `HTTP_${response.status}`;
592
+ refreshError.status = response.status;
593
+ throw refreshError;
479
594
  }
480
595
  const data = await response.json();
481
596
  const { access_token, refresh_token: new_refresh_token } = data;
@@ -488,10 +603,11 @@ async function refreshToken() {
488
603
  console.log("\u{1F504} New refresh token stored from rotation");
489
604
  }
490
605
  console.log("\u2705 Token refresh successful, listeners notified");
606
+ emitAuthDiagnostic("TOKEN_REFRESH_COMPLETED", "SUCCESS", "NONE", { clientKey });
491
607
  return access_token;
492
608
  } catch (err) {
493
609
  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"));
610
+ 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
611
  if (isAuthRejection) {
496
612
  clearToken();
497
613
  clearRefreshToken();
@@ -514,6 +630,7 @@ async function validateCurrentSession() {
514
630
  const response = await fetch(`${authBaseUrl}/account/validate-session`, {
515
631
  method: "GET",
516
632
  headers: {
633
+ ...diagnosticHeaders(),
517
634
  "Authorization": `Bearer ${token}`,
518
635
  "Content-Type": "application/json"
519
636
  },
@@ -766,6 +883,7 @@ api.interceptors.request.use((config2) => {
766
883
  if ((runtimeConfig == null ? void 0 : runtimeConfig.clientKey) && !config2.headers["X-Client-Key"]) {
767
884
  config2.headers["X-Client-Key"] = runtimeConfig.clientKey;
768
885
  }
886
+ Object.assign(config2.headers, diagnosticHeaders());
769
887
  const token = getToken();
770
888
  if (token) {
771
889
  config2.headers.Authorization = `Bearer ${token}`;
@@ -784,6 +902,10 @@ api.interceptors.response.use(
784
902
  return Promise.reject(error);
785
903
  }
786
904
  config2._retry = true;
905
+ emitAuthDiagnostic("API_401_REFRESH_STARTED", "PENDING", "HTTP_401", {
906
+ clientKey: getConfig().clientKey,
907
+ status: 401
908
+ });
787
909
  if (!refreshPromise2) {
788
910
  refreshPromise2 = refreshToken().then((newToken) => {
789
911
  refreshPromise2 = null;
@@ -843,6 +965,208 @@ function isAuthenticated() {
843
965
  return !!token && !isTokenExpired(token, 10);
844
966
  }
845
967
 
968
+ // preferences.js
969
+ var PREFERENCE_DEFAULTS = Object.freeze({
970
+ theme: "system",
971
+ colorScheme: "blue",
972
+ density: "comfortable",
973
+ reducedMotion: false,
974
+ language: "en",
975
+ timezone: "Etc/UTC",
976
+ dateFormat: "MM/DD/YYYY",
977
+ timeFormat: "12h",
978
+ highContrast: false,
979
+ largeText: false
980
+ });
981
+ var ALLOWED = {
982
+ theme: /* @__PURE__ */ new Set(["light", "dark", "system"]),
983
+ density: /* @__PURE__ */ new Set(["compact", "comfortable", "spacious"]),
984
+ timeFormat: /* @__PURE__ */ new Set(["12h", "24h"])
985
+ };
986
+ var listeners2 = /* @__PURE__ */ new Set();
987
+ var cachedPreferences = null;
988
+ var pendingRequest = null;
989
+ var syncCleanup = null;
990
+ function extractResponseData(response) {
991
+ var _a;
992
+ return ((_a = response == null ? void 0 : response.data) == null ? void 0 : _a.data) ?? (response == null ? void 0 : response.data) ?? response ?? {};
993
+ }
994
+ function normalizePreferences(value = {}) {
995
+ const next = { ...PREFERENCE_DEFAULTS };
996
+ for (const key of Object.keys(PREFERENCE_DEFAULTS)) {
997
+ if (!Object.hasOwn(value, key)) continue;
998
+ const candidate = value[key];
999
+ if (ALLOWED[key] && !ALLOWED[key].has(candidate)) continue;
1000
+ if (typeof PREFERENCE_DEFAULTS[key] !== typeof candidate) continue;
1001
+ next[key] = candidate;
1002
+ }
1003
+ return next;
1004
+ }
1005
+ function resolveTheme(theme, matchMedia2 = globalThis.matchMedia) {
1006
+ if (theme === "dark" || theme === "light") return theme;
1007
+ return typeof matchMedia2 === "function" && matchMedia2("(prefers-color-scheme: dark)").matches ? "dark" : "light";
1008
+ }
1009
+ function announce(preferences2) {
1010
+ cachedPreferences = normalizePreferences(preferences2);
1011
+ listeners2.forEach((listener) => listener(cachedPreferences));
1012
+ return cachedPreferences;
1013
+ }
1014
+ async function getPreferences({ force = false } = {}) {
1015
+ if (cachedPreferences && !force) return cachedPreferences;
1016
+ if (pendingRequest) return pendingRequest;
1017
+ pendingRequest = api_default.get("/account/preferences").then((response) => announce(extractResponseData(response))).finally(() => {
1018
+ pendingRequest = null;
1019
+ });
1020
+ return pendingRequest;
1021
+ }
1022
+ async function updatePreferences(patch) {
1023
+ const allowedPatch = {};
1024
+ for (const key of Object.keys(PREFERENCE_DEFAULTS)) {
1025
+ if (Object.hasOwn(patch, key)) allowedPatch[key] = patch[key];
1026
+ }
1027
+ const candidate = normalizePreferences({ ...cachedPreferences || PREFERENCE_DEFAULTS, ...allowedPatch });
1028
+ const response = await api_default.put("/account/preferences", allowedPatch);
1029
+ const saved = extractResponseData(response);
1030
+ return announce(Object.keys(saved).length ? saved : candidate);
1031
+ }
1032
+ function subscribePreferences(listener) {
1033
+ listeners2.add(listener);
1034
+ if (cachedPreferences) listener(cachedPreferences);
1035
+ return () => listeners2.delete(listener);
1036
+ }
1037
+ function clearPreferenceCache() {
1038
+ cachedPreferences = null;
1039
+ pendingRequest = null;
1040
+ }
1041
+ function applyPreferences(preferences2, documentRef = globalThis.document) {
1042
+ var _a, _b, _c;
1043
+ if (!(documentRef == null ? void 0 : documentRef.documentElement)) return;
1044
+ const value = normalizePreferences(preferences2);
1045
+ const root = documentRef.documentElement;
1046
+ const resolvedTheme = resolveTheme(value.theme, (_b = (_a = documentRef.defaultView) == null ? void 0 : _a.matchMedia) == null ? void 0 : _b.bind(documentRef.defaultView));
1047
+ root.dataset.theme = resolvedTheme;
1048
+ root.dataset.themePreference = value.theme;
1049
+ root.dataset.colorScheme = value.colorScheme;
1050
+ root.dataset.density = value.density;
1051
+ root.lang = value.language;
1052
+ root.style.colorScheme = resolvedTheme;
1053
+ root.style.setProperty("--user-accent-color", {
1054
+ blue: "#1976d2",
1055
+ green: "#2e7d32",
1056
+ purple: "#7b1fa2",
1057
+ orange: "#c65d00",
1058
+ red: "#c62828"
1059
+ }[value.colorScheme] || "#1976d2");
1060
+ root.classList.toggle("user-reduced-motion", value.reducedMotion);
1061
+ root.classList.toggle("user-high-contrast", value.highContrast);
1062
+ root.classList.toggle("user-large-text", value.largeText);
1063
+ const styleId = "sso-user-preference-styles";
1064
+ if (!documentRef.getElementById(styleId)) {
1065
+ const style = documentRef.createElement("style");
1066
+ style.id = styleId;
1067
+ style.textContent = `
1068
+ html.user-large-text { font-size: 112.5%; }
1069
+ html.user-high-contrast { filter: contrast(1.15); }
1070
+ html[data-density="compact"] { --user-density-padding: 6px; }
1071
+ html[data-density="comfortable"] { --user-density-padding: 10px; }
1072
+ html[data-density="spacious"] { --user-density-padding: 14px; }
1073
+ html[data-density] .MuiListItem-root { padding-top: var(--user-density-padding); padding-bottom: var(--user-density-padding); }
1074
+ html.user-reduced-motion *, html.user-reduced-motion *::before, html.user-reduced-motion *::after {
1075
+ animation-duration: 0.01ms !important;
1076
+ animation-iteration-count: 1 !important;
1077
+ scroll-behavior: auto !important;
1078
+ transition-duration: 0.01ms !important;
1079
+ }
1080
+ `;
1081
+ (_c = documentRef.head) == null ? void 0 : _c.appendChild(style);
1082
+ }
1083
+ }
1084
+ function startPreferenceSync({ refreshOnFocus = true } = {}) {
1085
+ var _a, _b;
1086
+ if (syncCleanup || typeof globalThis.addEventListener !== "function") return syncCleanup || (() => {
1087
+ });
1088
+ const refresh = () => {
1089
+ if (!globalThis.document || globalThis.document.visibilityState === "visible") {
1090
+ getPreferences({ force: true }).catch(() => {
1091
+ });
1092
+ }
1093
+ };
1094
+ const onVisibility = () => refresh();
1095
+ (_b = (_a = globalThis.document) == null ? void 0 : _a.addEventListener) == null ? void 0 : _b.call(_a, "visibilitychange", onVisibility);
1096
+ if (refreshOnFocus) globalThis.addEventListener("focus", refresh);
1097
+ syncCleanup = () => {
1098
+ var _a2, _b2;
1099
+ (_b2 = (_a2 = globalThis.document) == null ? void 0 : _a2.removeEventListener) == null ? void 0 : _b2.call(_a2, "visibilitychange", onVisibility);
1100
+ if (refreshOnFocus) globalThis.removeEventListener("focus", refresh);
1101
+ syncCleanup = null;
1102
+ };
1103
+ return syncCleanup;
1104
+ }
1105
+ function localeFor(language) {
1106
+ const supported = Intl.DateTimeFormat.supportedLocalesOf([language]);
1107
+ return supported[0] || "en";
1108
+ }
1109
+ function fixedOffset(value) {
1110
+ const match = /^UTC([+-])(\d{2}):(\d{2})$/.exec(value || "");
1111
+ if (!match) return null;
1112
+ const minutes = Number(match[2]) * 60 + Number(match[3]);
1113
+ return (match[1] === "-" ? -1 : 1) * minutes;
1114
+ }
1115
+ function formatUserDate(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, options = {}) {
1116
+ const value = normalizePreferences(preferences2);
1117
+ const date = input instanceof Date ? input : new Date(input);
1118
+ const offset = fixedOffset(value.timezone);
1119
+ const adjusted = offset === null ? date : new Date(date.getTime() + offset * 6e4);
1120
+ const timeZone = offset === null ? value.timezone : "UTC";
1121
+ const hasExplicitParts = ["year", "month", "day", "weekday", "hour", "minute", "second", "dateStyle", "timeStyle"].some((key) => options[key] !== void 0);
1122
+ const dateOptions = hasExplicitParts ? options : { year: "numeric", month: "2-digit", day: "2-digit" };
1123
+ const formatter = new Intl.DateTimeFormat(localeFor(value.language), {
1124
+ ...dateOptions,
1125
+ timeZone,
1126
+ ...dateOptions.hour || dateOptions.timeStyle ? { hour12: value.timeFormat === "12h" } : {}
1127
+ });
1128
+ if (!hasExplicitParts) {
1129
+ const parts = Object.fromEntries(formatter.formatToParts(adjusted).map((part) => [part.type, part.value]));
1130
+ if (value.dateFormat === "DD/MM/YYYY") return `${parts.day}/${parts.month}/${parts.year}`;
1131
+ if (value.dateFormat === "YYYY-MM-DD") return `${parts.year}-${parts.month}-${parts.day}`;
1132
+ return `${parts.month}/${parts.day}/${parts.year}`;
1133
+ }
1134
+ return formatter.format(adjusted);
1135
+ }
1136
+ function formatUserNumber(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, options = {}) {
1137
+ const value = normalizePreferences(preferences2);
1138
+ return new Intl.NumberFormat(localeFor(value.language), options).format(input);
1139
+ }
1140
+ function formatUserRelativeTime(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, now = /* @__PURE__ */ new Date()) {
1141
+ const value = normalizePreferences(preferences2);
1142
+ const deltaSeconds = (new Date(input).getTime() - now.getTime()) / 1e3;
1143
+ const units = [
1144
+ ["year", 31536e3],
1145
+ ["month", 2592e3],
1146
+ ["week", 604800],
1147
+ ["day", 86400],
1148
+ ["hour", 3600],
1149
+ ["minute", 60],
1150
+ ["second", 1]
1151
+ ];
1152
+ const [unit, seconds] = units.find(([, size]) => Math.abs(deltaSeconds) >= size) || units.at(-1);
1153
+ return new Intl.RelativeTimeFormat(localeFor(value.language), { numeric: "auto" }).format(Math.round(deltaSeconds / seconds), unit);
1154
+ }
1155
+ var preferences = {
1156
+ defaults: PREFERENCE_DEFAULTS,
1157
+ get: getPreferences,
1158
+ update: updatePreferences,
1159
+ subscribe: subscribePreferences,
1160
+ clearCache: clearPreferenceCache,
1161
+ apply: applyPreferences,
1162
+ startSync: startPreferenceSync,
1163
+ formatDate: formatUserDate,
1164
+ formatNumber: formatUserNumber,
1165
+ formatRelativeTime: formatUserRelativeTime,
1166
+ normalize: normalizePreferences,
1167
+ resolveTheme
1168
+ };
1169
+
846
1170
  // react/AuthProvider.jsx
847
1171
  var import_react = __toESM(require("react"), 1);
848
1172
  var AuthContext = (0, import_react.createContext)();
@@ -906,7 +1230,8 @@ function AuthProvider({ children, onSessionExpired }) {
906
1230
  console.log("\u{1F4E5} Profile response status:", res.status);
907
1231
  if (!res.ok) throw new Error("Failed to fetch user");
908
1232
  return res.json();
909
- }).then((userData) => {
1233
+ }).then((responseBody) => {
1234
+ const userData = (responseBody == null ? void 0 : responseBody.data) ?? responseBody;
910
1235
  console.log("\u2705 Profile fetched successfully:", userData.email);
911
1236
  setUser(userData);
912
1237
  setSessionValid(true);
@@ -1053,6 +1378,62 @@ var useSessionMonitor = (options = {}) => {
1053
1378
  };
1054
1379
  };
1055
1380
 
1381
+ // react/PreferencesProvider.jsx
1382
+ var import_react4 = __toESM(require("react"), 1);
1383
+ var PreferencesContext = (0, import_react4.createContext)(null);
1384
+ function PreferencesProvider({ children, initialPreferences, applyToDocument = true }) {
1385
+ const [preferences2, setPreferences] = (0, import_react4.useState)(() => ({
1386
+ ...PREFERENCE_DEFAULTS,
1387
+ ...initialPreferences
1388
+ }));
1389
+ const [loading, setLoading] = (0, import_react4.useState)(true);
1390
+ const [error, setError] = (0, import_react4.useState)(null);
1391
+ (0, import_react4.useEffect)(() => {
1392
+ const unsubscribe = subscribePreferences((next) => {
1393
+ setPreferences(next);
1394
+ setLoading(false);
1395
+ setError(null);
1396
+ });
1397
+ const stopSync = startPreferenceSync();
1398
+ getPreferences().catch((nextError) => {
1399
+ setError(nextError);
1400
+ setLoading(false);
1401
+ });
1402
+ return () => {
1403
+ unsubscribe();
1404
+ stopSync();
1405
+ };
1406
+ }, []);
1407
+ (0, import_react4.useEffect)(() => {
1408
+ if (applyToDocument) applyPreferences(preferences2);
1409
+ }, [applyToDocument, preferences2]);
1410
+ (0, import_react4.useEffect)(() => {
1411
+ var _a;
1412
+ if (preferences2.theme !== "system" || typeof matchMedia !== "function") return void 0;
1413
+ const media = matchMedia("(prefers-color-scheme: dark)");
1414
+ const applySystemTheme = () => applyPreferences(preferences2);
1415
+ (_a = media.addEventListener) == null ? void 0 : _a.call(media, "change", applySystemTheme);
1416
+ return () => {
1417
+ var _a2;
1418
+ return (_a2 = media.removeEventListener) == null ? void 0 : _a2.call(media, "change", applySystemTheme);
1419
+ };
1420
+ }, [preferences2]);
1421
+ const value = (0, import_react4.useMemo)(() => ({
1422
+ preferences: preferences2,
1423
+ resolvedTheme: resolveTheme(preferences2.theme),
1424
+ loading,
1425
+ error,
1426
+ refresh: () => getPreferences({ force: true }),
1427
+ update: updatePreferences
1428
+ }), [error, loading, preferences2]);
1429
+ return /* @__PURE__ */ import_react4.default.createElement(PreferencesContext.Provider, { value }, children);
1430
+ }
1431
+ function usePreferences() {
1432
+ const context = (0, import_react4.useContext)(PreferencesContext);
1433
+ if (!context) throw new Error("usePreferences must be used within a PreferencesProvider");
1434
+ return context;
1435
+ }
1436
+
1056
1437
  // index.js
1057
1438
  var auth = {
1058
1439
  // ๐Ÿ”ง Config
@@ -1072,7 +1453,7 @@ var auth = {
1072
1453
  getToken,
1073
1454
  setToken,
1074
1455
  clearToken,
1075
- setRefreshToken,
1456
+ setRefreshToken: setRefreshToken2,
1076
1457
  // โœ… Refresh token for HTTP dev
1077
1458
  getRefreshToken,
1078
1459
  clearRefreshToken,
@@ -1083,6 +1464,10 @@ var auth = {
1083
1464
  // โœ… Debug function
1084
1465
  // ๐ŸŒ Authenticated API client
1085
1466
  api: api_default,
1467
+ preferences,
1468
+ // ๐Ÿ”Ž Safe authentication diagnostics
1469
+ getDiagnosticContext,
1470
+ emitAuthDiagnostic,
1086
1471
  // ๐Ÿงช Utilities
1087
1472
  decodeToken,
1088
1473
  isTokenExpired,
@@ -1130,8 +1515,21 @@ var auth = {
1130
1515
  // Annotate the CommonJS export names for ESM import in node:
1131
1516
  0 && (module.exports = {
1132
1517
  AuthProvider,
1518
+ PREFERENCE_DEFAULTS,
1519
+ PreferencesProvider,
1520
+ applyPreferences,
1133
1521
  auth,
1522
+ formatUserDate,
1523
+ formatUserNumber,
1524
+ formatUserRelativeTime,
1525
+ getPreferences,
1526
+ normalizePreferences,
1527
+ resolveTheme,
1528
+ startPreferenceSync,
1529
+ subscribePreferences,
1530
+ updatePreferences,
1134
1531
  useAuth,
1532
+ usePreferences,
1135
1533
  useSessionMonitor
1136
1534
  });
1137
1535
  //# sourceMappingURL=index.cjs.map