@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.js CHANGED
@@ -111,7 +111,7 @@ function shouldUseLocalStorage() {
111
111
  return false;
112
112
  }
113
113
  }
114
- function setRefreshToken(token) {
114
+ function setRefreshToken2(token) {
115
115
  if (!token) {
116
116
  clearRefreshToken();
117
117
  return;
@@ -228,8 +228,8 @@ function setConfig(customConfig = {}) {
228
228
  // โœ… Auto-detect router mode
229
229
  isRouter: customConfig.isRouter || customConfig.clientKey === "account-ui"
230
230
  };
231
+ enableRefreshTokenPersistence(config.persistRefreshToken);
231
232
  if (config.persistRefreshToken) {
232
- enableRefreshTokenPersistence(true);
233
233
  console.log("\u{1F4E6} Refresh token persistence ENABLED (localStorage on HTTPS)");
234
234
  }
235
235
  console.log(`\u{1F527} Auth Client Mode: ${config.isRouter ? "ROUTER" : "CLIENT"}`, {
@@ -245,6 +245,82 @@ function isRouterMode() {
245
245
  return config.isRouter;
246
246
  }
247
247
 
248
+ // diagnostics.js
249
+ var STORAGE_KEY = "auth_diagnostic_context";
250
+ var LOGIN_LOCK_KEY = "auth_login_lock";
251
+ var randomId = () => {
252
+ var _a;
253
+ if ((_a = globalThis.crypto) == null ? void 0 : _a.randomUUID) return globalThis.crypto.randomUUID();
254
+ return `auth-${Date.now()}-${Math.random().toString(36).slice(2)}`;
255
+ };
256
+ var fingerprint = async (value) => {
257
+ var _a;
258
+ if (!value || !((_a = globalThis.crypto) == null ? void 0 : _a.subtle)) return null;
259
+ const bytes = new TextEncoder().encode(String(value));
260
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
261
+ return [...new Uint8Array(digest)].slice(0, 6).map((byte) => byte.toString(16).padStart(2, "0")).join("");
262
+ };
263
+ function getDiagnosticContext() {
264
+ try {
265
+ const existing = JSON.parse(sessionStorage.getItem(STORAGE_KEY) || "null");
266
+ if (existing == null ? void 0 : existing.correlationId) return existing;
267
+ } catch {
268
+ }
269
+ const context = { correlationId: randomId(), startedAt: Date.now() };
270
+ try {
271
+ sessionStorage.setItem(STORAGE_KEY, JSON.stringify(context));
272
+ } catch {
273
+ }
274
+ return context;
275
+ }
276
+ function resetDiagnosticContext() {
277
+ const context = { correlationId: randomId(), startedAt: Date.now() };
278
+ try {
279
+ sessionStorage.setItem(STORAGE_KEY, JSON.stringify(context));
280
+ } catch {
281
+ }
282
+ return context;
283
+ }
284
+ function diagnosticHeaders() {
285
+ const { correlationId } = getDiagnosticContext();
286
+ return { "X-Correlation-ID": correlationId, "X-Request-ID": correlationId };
287
+ }
288
+ function acquireLoginLock(clientKey, redirectUri, ttlMs = 5e3) {
289
+ const now = Date.now();
290
+ const signature = `${clientKey}|${redirectUri}`;
291
+ try {
292
+ const current = JSON.parse(sessionStorage.getItem(LOGIN_LOCK_KEY) || "null");
293
+ if ((current == null ? void 0 : current.signature) === signature && now - current.createdAt < ttlMs) return false;
294
+ sessionStorage.setItem(LOGIN_LOCK_KEY, JSON.stringify({ signature, createdAt: now }));
295
+ } catch {
296
+ }
297
+ return true;
298
+ }
299
+ function clearLoginLock() {
300
+ try {
301
+ sessionStorage.removeItem(LOGIN_LOCK_KEY);
302
+ } catch {
303
+ }
304
+ }
305
+ async function emitAuthDiagnostic(event, outcome, reasonCode, details = {}) {
306
+ const context = getDiagnosticContext();
307
+ const safe = {
308
+ eventType: "auth_client_diagnostic",
309
+ event,
310
+ outcome,
311
+ reasonCode,
312
+ correlationId: context.correlationId,
313
+ elapsedMs: Date.now() - context.startedAt,
314
+ clientKey: details.clientKey || null,
315
+ status: details.status || null,
316
+ stateFingerprint: await fingerprint(details.state),
317
+ online: typeof navigator === "undefined" ? null : navigator.onLine
318
+ };
319
+ const method = outcome === "FAILURE" ? "error" : outcome === "WARNING" ? "warn" : "info";
320
+ console[method]("[auth-client]", safe);
321
+ return safe;
322
+ }
323
+
248
324
  // core.js
249
325
  var callbackProcessed = false;
250
326
  function login(clientKeyArg, redirectUriArg) {
@@ -257,14 +333,16 @@ function login(clientKeyArg, redirectUriArg) {
257
333
  } = getConfig();
258
334
  const clientKey = clientKeyArg || defaultClientKey;
259
335
  const redirectUri = redirectUriArg || defaultRedirectUri;
260
- console.log("\u{1F504} Smart Login initiated:", {
261
- mode: isRouterMode() ? "ROUTER" : "CLIENT",
262
- clientKey,
263
- redirectUri
264
- });
265
336
  if (!clientKey || !redirectUri) {
337
+ emitAuthDiagnostic("LOGIN_REJECTED", "FAILURE", "CLIENT_CONFIG_MISSING", { clientKey });
266
338
  throw new Error("Missing clientKey or redirectUri");
267
339
  }
340
+ if (!acquireLoginLock(clientKey, redirectUri)) {
341
+ emitAuthDiagnostic("LOGIN_DUPLICATE_SUPPRESSED", "WARNING", "LOGIN_ALREADY_IN_PROGRESS", { clientKey });
342
+ return false;
343
+ }
344
+ resetDiagnosticContext();
345
+ emitAuthDiagnostic("LOGIN_INITIATED", "PENDING", "NONE", { clientKey });
268
346
  sessionStorage.setItem("originalApp", clientKey);
269
347
  sessionStorage.setItem("returnUrl", redirectUri);
270
348
  if (isRouterMode()) {
@@ -279,13 +357,9 @@ function routerLogin(clientKey, redirectUri) {
279
357
  if (redirectUri) {
280
358
  params.append("redirect_uri", redirectUri);
281
359
  }
360
+ params.append("correlation_id", getDiagnosticContext().correlationId);
282
361
  const query = params.toString();
283
362
  const backendLoginUrl = `${authBaseUrl}/login/${clientKey}${query ? `?${query}` : ""}`;
284
- console.log("\u{1F3ED} Router Login: Direct backend authentication", {
285
- clientKey,
286
- redirectUri,
287
- backendUrl: backendLoginUrl
288
- });
289
363
  window.location.href = backendLoginUrl;
290
364
  }
291
365
  function clientLogin(clientKey, redirectUri) {
@@ -297,11 +371,6 @@ function clientLogin(clientKey, redirectUri) {
297
371
  params.append("redirect_uri", redirectUri);
298
372
  }
299
373
  const centralizedLoginUrl = `${accountUiUrl}/login?${params.toString()}`;
300
- console.log("\u{1F504} Client Login: Redirecting to centralized login", {
301
- clientKey,
302
- redirectUri,
303
- centralizedUrl: centralizedLoginUrl
304
- });
305
374
  window.location.href = centralizedLoginUrl;
306
375
  }
307
376
  async function logout(options = {}) {
@@ -315,6 +384,7 @@ async function logout(options = {}) {
315
384
  clientKey,
316
385
  scope
317
386
  });
387
+ emitAuthDiagnostic("LOGOUT_INITIATED", "PENDING", "NONE", { clientKey });
318
388
  clearToken();
319
389
  clearRefreshToken();
320
390
  sessionStorage.removeItem("originalApp");
@@ -324,6 +394,7 @@ async function logout(options = {}) {
324
394
  method: "POST",
325
395
  credentials: "include",
326
396
  headers: {
397
+ ...diagnosticHeaders(),
327
398
  "Authorization": token ? `Bearer ${token}` : "",
328
399
  "Content-Type": "application/json"
329
400
  },
@@ -334,16 +405,23 @@ async function logout(options = {}) {
334
405
  }
335
406
  const data = await response.json();
336
407
  console.log("\u2705 Logout response:", data);
337
- if (data == null ? void 0 : data.logoutRedirectUrl) {
338
- window.location.replace(data.logoutRedirectUrl);
339
- return;
340
- }
408
+ emitAuthDiagnostic("LOGOUT_COMPLETED", "SUCCESS", "NONE", {
409
+ clientKey,
410
+ status: response.status
411
+ });
341
412
  if (data == null ? void 0 : data.keycloakLogoutUrl) {
342
413
  window.location.replace(data.keycloakLogoutUrl);
343
414
  return;
344
415
  }
416
+ if (data == null ? void 0 : data.logoutRedirectUrl) {
417
+ window.location.replace(data.logoutRedirectUrl);
418
+ return;
419
+ }
345
420
  } catch (error) {
346
421
  console.warn("\u26A0\uFE0F Logout backend call failed:", error);
422
+ emitAuthDiagnostic("LOGOUT_BACKEND_FAILED", "FAILURE", "LOGOUT_REQUEST_FAILED", {
423
+ clientKey
424
+ });
347
425
  }
348
426
  const fallbackUrl = isRouterMode() ? new URL("/login", window.location.origin) : new URL("/login", accountUiUrl);
349
427
  fallbackUrl.searchParams.set("logged_out", "true");
@@ -352,7 +430,6 @@ async function logout(options = {}) {
352
430
  window.location.replace(fallbackUrl.toString());
353
431
  }
354
432
  function handleCallback() {
355
- var _a;
356
433
  const params = new URLSearchParams(window.location.search);
357
434
  const accessToken2 = params.get("access_token");
358
435
  const error = params.get("error");
@@ -369,24 +446,27 @@ function handleCallback() {
369
446
  callbackProcessed = false;
370
447
  }
371
448
  callbackProcessed = true;
449
+ clearLoginLock();
372
450
  sessionStorage.removeItem("originalApp");
373
451
  sessionStorage.removeItem("returnUrl");
374
452
  if (error) {
375
453
  const errorDescription = params.get("error_description") || error;
376
- throw new Error(`Authentication failed: ${errorDescription}`);
454
+ const authError = new Error(`Authentication failed: ${errorDescription}`);
455
+ authError.code = error;
456
+ authError.correlationId = getDiagnosticContext().correlationId;
457
+ emitAuthDiagnostic("CALLBACK_REJECTED", "FAILURE", error.toUpperCase(), {
458
+ clientKey: getConfig().clientKey,
459
+ state: params.get("state")
460
+ });
461
+ throw authError;
377
462
  }
378
463
  if (accessToken2) {
379
464
  setToken(accessToken2);
380
465
  const refreshTokenInUrl = params.get("refresh_token");
381
466
  if (refreshTokenInUrl) {
382
- const { persistRefreshToken } = getConfig();
383
- const isHttpDev = typeof window !== "undefined" && ((_a = window.location) == null ? void 0 : _a.protocol) === "http:";
384
- if (persistRefreshToken || isHttpDev) {
385
- console.log(`\u{1F4E6} Storing refresh token from callback URL (${persistRefreshToken ? "persistence enabled" : "HTTP dev mode"})`);
386
- setRefreshToken(refreshTokenInUrl);
387
- } else {
388
- console.log("\u{1F512} HTTPS mode: Refresh token is in httpOnly cookie (ignoring URL param)");
389
- }
467
+ emitAuthDiagnostic("CALLBACK_REFRESH_TOKEN_IGNORED", "WARNING", "REFRESH_TOKEN_IN_URL", {
468
+ clientKey: getConfig().clientKey
469
+ });
390
470
  }
391
471
  const url = new URL(window.location);
392
472
  url.searchParams.delete("access_token");
@@ -396,9 +476,19 @@ function handleCallback() {
396
476
  url.searchParams.delete("error_description");
397
477
  window.history.replaceState({}, "", url);
398
478
  console.log("\u2705 Callback processed successfully, token stored");
479
+ emitAuthDiagnostic("CALLBACK_COMPLETED", "SUCCESS", "NONE", {
480
+ clientKey: getConfig().clientKey,
481
+ state: params.get("state")
482
+ });
399
483
  return accessToken2;
400
484
  }
401
- throw new Error("No access token found in callback URL");
485
+ emitAuthDiagnostic("CALLBACK_REJECTED", "FAILURE", "ACCESS_TOKEN_MISSING", {
486
+ clientKey: getConfig().clientKey,
487
+ state: params.get("state")
488
+ });
489
+ const missingTokenError = new Error("No access token found in callback URL");
490
+ missingTokenError.code = "ACCESS_TOKEN_MISSING";
491
+ throw missingTokenError;
402
492
  }
403
493
  function resetCallbackState() {
404
494
  callbackProcessed = false;
@@ -413,7 +503,7 @@ async function refreshToken() {
413
503
  }
414
504
  refreshInProgress = true;
415
505
  refreshPromise = (async () => {
416
- var _a, _b, _c, _d;
506
+ var _a, _b, _c, _d, _e, _f;
417
507
  try {
418
508
  const storedRefreshToken = getRefreshToken();
419
509
  console.log("\u{1F504} Refreshing token:", {
@@ -426,6 +516,7 @@ async function refreshToken() {
426
516
  credentials: "include",
427
517
  // โœ… Include httpOnly cookies (for HTTPS)
428
518
  headers: {
519
+ ...diagnosticHeaders(),
429
520
  "Content-Type": "application/json"
430
521
  }
431
522
  };
@@ -436,8 +527,19 @@ async function refreshToken() {
436
527
  const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
437
528
  if (!response.ok) {
438
529
  const errorText = await response.text();
439
- console.error("\u274C Token refresh failed:", response.status, errorText);
440
- throw new Error(`Refresh failed: ${response.status}`);
530
+ let serverCode = null;
531
+ try {
532
+ serverCode = ((_a = JSON.parse(errorText)) == null ? void 0 : _a.error) || ((_b = JSON.parse(errorText)) == null ? void 0 : _b.code);
533
+ } catch {
534
+ }
535
+ emitAuthDiagnostic("TOKEN_REFRESH_REJECTED", "FAILURE", serverCode || `HTTP_${response.status}`, {
536
+ clientKey,
537
+ status: response.status
538
+ });
539
+ const refreshError = new Error(`Refresh failed: ${response.status}`);
540
+ refreshError.code = serverCode || `HTTP_${response.status}`;
541
+ refreshError.status = response.status;
542
+ throw refreshError;
441
543
  }
442
544
  const data = await response.json();
443
545
  const { access_token, refresh_token: new_refresh_token } = data;
@@ -450,10 +552,11 @@ async function refreshToken() {
450
552
  console.log("\u{1F504} New refresh token stored from rotation");
451
553
  }
452
554
  console.log("\u2705 Token refresh successful, listeners notified");
555
+ emitAuthDiagnostic("TOKEN_REFRESH_COMPLETED", "SUCCESS", "NONE", { clientKey });
453
556
  return access_token;
454
557
  } catch (err) {
455
558
  console.error("\u274C Token refresh error:", err);
456
- 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"));
559
+ 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"));
457
560
  if (isAuthRejection) {
458
561
  clearToken();
459
562
  clearRefreshToken();
@@ -476,6 +579,7 @@ async function validateCurrentSession() {
476
579
  const response = await fetch(`${authBaseUrl}/account/validate-session`, {
477
580
  method: "GET",
478
581
  headers: {
582
+ ...diagnosticHeaders(),
479
583
  "Authorization": `Bearer ${token}`,
480
584
  "Content-Type": "application/json"
481
585
  },
@@ -728,6 +832,7 @@ api.interceptors.request.use((config2) => {
728
832
  if ((runtimeConfig == null ? void 0 : runtimeConfig.clientKey) && !config2.headers["X-Client-Key"]) {
729
833
  config2.headers["X-Client-Key"] = runtimeConfig.clientKey;
730
834
  }
835
+ Object.assign(config2.headers, diagnosticHeaders());
731
836
  const token = getToken();
732
837
  if (token) {
733
838
  config2.headers.Authorization = `Bearer ${token}`;
@@ -746,6 +851,10 @@ api.interceptors.response.use(
746
851
  return Promise.reject(error);
747
852
  }
748
853
  config2._retry = true;
854
+ emitAuthDiagnostic("API_401_REFRESH_STARTED", "PENDING", "HTTP_401", {
855
+ clientKey: getConfig().clientKey,
856
+ status: 401
857
+ });
749
858
  if (!refreshPromise2) {
750
859
  refreshPromise2 = refreshToken().then((newToken) => {
751
860
  refreshPromise2 = null;
@@ -805,6 +914,208 @@ function isAuthenticated() {
805
914
  return !!token && !isTokenExpired(token, 10);
806
915
  }
807
916
 
917
+ // preferences.js
918
+ var PREFERENCE_DEFAULTS = Object.freeze({
919
+ theme: "system",
920
+ colorScheme: "blue",
921
+ density: "comfortable",
922
+ reducedMotion: false,
923
+ language: "en",
924
+ timezone: "Etc/UTC",
925
+ dateFormat: "MM/DD/YYYY",
926
+ timeFormat: "12h",
927
+ highContrast: false,
928
+ largeText: false
929
+ });
930
+ var ALLOWED = {
931
+ theme: /* @__PURE__ */ new Set(["light", "dark", "system"]),
932
+ density: /* @__PURE__ */ new Set(["compact", "comfortable", "spacious"]),
933
+ timeFormat: /* @__PURE__ */ new Set(["12h", "24h"])
934
+ };
935
+ var listeners2 = /* @__PURE__ */ new Set();
936
+ var cachedPreferences = null;
937
+ var pendingRequest = null;
938
+ var syncCleanup = null;
939
+ function extractResponseData(response) {
940
+ var _a;
941
+ return ((_a = response == null ? void 0 : response.data) == null ? void 0 : _a.data) ?? (response == null ? void 0 : response.data) ?? response ?? {};
942
+ }
943
+ function normalizePreferences(value = {}) {
944
+ const next = { ...PREFERENCE_DEFAULTS };
945
+ for (const key of Object.keys(PREFERENCE_DEFAULTS)) {
946
+ if (!Object.hasOwn(value, key)) continue;
947
+ const candidate = value[key];
948
+ if (ALLOWED[key] && !ALLOWED[key].has(candidate)) continue;
949
+ if (typeof PREFERENCE_DEFAULTS[key] !== typeof candidate) continue;
950
+ next[key] = candidate;
951
+ }
952
+ return next;
953
+ }
954
+ function resolveTheme(theme, matchMedia2 = globalThis.matchMedia) {
955
+ if (theme === "dark" || theme === "light") return theme;
956
+ return typeof matchMedia2 === "function" && matchMedia2("(prefers-color-scheme: dark)").matches ? "dark" : "light";
957
+ }
958
+ function announce(preferences2) {
959
+ cachedPreferences = normalizePreferences(preferences2);
960
+ listeners2.forEach((listener) => listener(cachedPreferences));
961
+ return cachedPreferences;
962
+ }
963
+ async function getPreferences({ force = false } = {}) {
964
+ if (cachedPreferences && !force) return cachedPreferences;
965
+ if (pendingRequest) return pendingRequest;
966
+ pendingRequest = api_default.get("/account/preferences").then((response) => announce(extractResponseData(response))).finally(() => {
967
+ pendingRequest = null;
968
+ });
969
+ return pendingRequest;
970
+ }
971
+ async function updatePreferences(patch) {
972
+ const allowedPatch = {};
973
+ for (const key of Object.keys(PREFERENCE_DEFAULTS)) {
974
+ if (Object.hasOwn(patch, key)) allowedPatch[key] = patch[key];
975
+ }
976
+ const candidate = normalizePreferences({ ...cachedPreferences || PREFERENCE_DEFAULTS, ...allowedPatch });
977
+ const response = await api_default.put("/account/preferences", allowedPatch);
978
+ const saved = extractResponseData(response);
979
+ return announce(Object.keys(saved).length ? saved : candidate);
980
+ }
981
+ function subscribePreferences(listener) {
982
+ listeners2.add(listener);
983
+ if (cachedPreferences) listener(cachedPreferences);
984
+ return () => listeners2.delete(listener);
985
+ }
986
+ function clearPreferenceCache() {
987
+ cachedPreferences = null;
988
+ pendingRequest = null;
989
+ }
990
+ function applyPreferences(preferences2, documentRef = globalThis.document) {
991
+ var _a, _b, _c;
992
+ if (!(documentRef == null ? void 0 : documentRef.documentElement)) return;
993
+ const value = normalizePreferences(preferences2);
994
+ const root = documentRef.documentElement;
995
+ const resolvedTheme = resolveTheme(value.theme, (_b = (_a = documentRef.defaultView) == null ? void 0 : _a.matchMedia) == null ? void 0 : _b.bind(documentRef.defaultView));
996
+ root.dataset.theme = resolvedTheme;
997
+ root.dataset.themePreference = value.theme;
998
+ root.dataset.colorScheme = value.colorScheme;
999
+ root.dataset.density = value.density;
1000
+ root.lang = value.language;
1001
+ root.style.colorScheme = resolvedTheme;
1002
+ root.style.setProperty("--user-accent-color", {
1003
+ blue: "#1976d2",
1004
+ green: "#2e7d32",
1005
+ purple: "#7b1fa2",
1006
+ orange: "#c65d00",
1007
+ red: "#c62828"
1008
+ }[value.colorScheme] || "#1976d2");
1009
+ root.classList.toggle("user-reduced-motion", value.reducedMotion);
1010
+ root.classList.toggle("user-high-contrast", value.highContrast);
1011
+ root.classList.toggle("user-large-text", value.largeText);
1012
+ const styleId = "sso-user-preference-styles";
1013
+ if (!documentRef.getElementById(styleId)) {
1014
+ const style = documentRef.createElement("style");
1015
+ style.id = styleId;
1016
+ style.textContent = `
1017
+ html.user-large-text { font-size: 112.5%; }
1018
+ html.user-high-contrast { filter: contrast(1.15); }
1019
+ html[data-density="compact"] { --user-density-padding: 6px; }
1020
+ html[data-density="comfortable"] { --user-density-padding: 10px; }
1021
+ html[data-density="spacious"] { --user-density-padding: 14px; }
1022
+ html[data-density] .MuiListItem-root { padding-top: var(--user-density-padding); padding-bottom: var(--user-density-padding); }
1023
+ html.user-reduced-motion *, html.user-reduced-motion *::before, html.user-reduced-motion *::after {
1024
+ animation-duration: 0.01ms !important;
1025
+ animation-iteration-count: 1 !important;
1026
+ scroll-behavior: auto !important;
1027
+ transition-duration: 0.01ms !important;
1028
+ }
1029
+ `;
1030
+ (_c = documentRef.head) == null ? void 0 : _c.appendChild(style);
1031
+ }
1032
+ }
1033
+ function startPreferenceSync({ refreshOnFocus = true } = {}) {
1034
+ var _a, _b;
1035
+ if (syncCleanup || typeof globalThis.addEventListener !== "function") return syncCleanup || (() => {
1036
+ });
1037
+ const refresh = () => {
1038
+ if (!globalThis.document || globalThis.document.visibilityState === "visible") {
1039
+ getPreferences({ force: true }).catch(() => {
1040
+ });
1041
+ }
1042
+ };
1043
+ const onVisibility = () => refresh();
1044
+ (_b = (_a = globalThis.document) == null ? void 0 : _a.addEventListener) == null ? void 0 : _b.call(_a, "visibilitychange", onVisibility);
1045
+ if (refreshOnFocus) globalThis.addEventListener("focus", refresh);
1046
+ syncCleanup = () => {
1047
+ var _a2, _b2;
1048
+ (_b2 = (_a2 = globalThis.document) == null ? void 0 : _a2.removeEventListener) == null ? void 0 : _b2.call(_a2, "visibilitychange", onVisibility);
1049
+ if (refreshOnFocus) globalThis.removeEventListener("focus", refresh);
1050
+ syncCleanup = null;
1051
+ };
1052
+ return syncCleanup;
1053
+ }
1054
+ function localeFor(language) {
1055
+ const supported = Intl.DateTimeFormat.supportedLocalesOf([language]);
1056
+ return supported[0] || "en";
1057
+ }
1058
+ function fixedOffset(value) {
1059
+ const match = /^UTC([+-])(\d{2}):(\d{2})$/.exec(value || "");
1060
+ if (!match) return null;
1061
+ const minutes = Number(match[2]) * 60 + Number(match[3]);
1062
+ return (match[1] === "-" ? -1 : 1) * minutes;
1063
+ }
1064
+ function formatUserDate(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, options = {}) {
1065
+ const value = normalizePreferences(preferences2);
1066
+ const date = input instanceof Date ? input : new Date(input);
1067
+ const offset = fixedOffset(value.timezone);
1068
+ const adjusted = offset === null ? date : new Date(date.getTime() + offset * 6e4);
1069
+ const timeZone = offset === null ? value.timezone : "UTC";
1070
+ const hasExplicitParts = ["year", "month", "day", "weekday", "hour", "minute", "second", "dateStyle", "timeStyle"].some((key) => options[key] !== void 0);
1071
+ const dateOptions = hasExplicitParts ? options : { year: "numeric", month: "2-digit", day: "2-digit" };
1072
+ const formatter = new Intl.DateTimeFormat(localeFor(value.language), {
1073
+ ...dateOptions,
1074
+ timeZone,
1075
+ ...dateOptions.hour || dateOptions.timeStyle ? { hour12: value.timeFormat === "12h" } : {}
1076
+ });
1077
+ if (!hasExplicitParts) {
1078
+ const parts = Object.fromEntries(formatter.formatToParts(adjusted).map((part) => [part.type, part.value]));
1079
+ if (value.dateFormat === "DD/MM/YYYY") return `${parts.day}/${parts.month}/${parts.year}`;
1080
+ if (value.dateFormat === "YYYY-MM-DD") return `${parts.year}-${parts.month}-${parts.day}`;
1081
+ return `${parts.month}/${parts.day}/${parts.year}`;
1082
+ }
1083
+ return formatter.format(adjusted);
1084
+ }
1085
+ function formatUserNumber(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, options = {}) {
1086
+ const value = normalizePreferences(preferences2);
1087
+ return new Intl.NumberFormat(localeFor(value.language), options).format(input);
1088
+ }
1089
+ function formatUserRelativeTime(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, now = /* @__PURE__ */ new Date()) {
1090
+ const value = normalizePreferences(preferences2);
1091
+ const deltaSeconds = (new Date(input).getTime() - now.getTime()) / 1e3;
1092
+ const units = [
1093
+ ["year", 31536e3],
1094
+ ["month", 2592e3],
1095
+ ["week", 604800],
1096
+ ["day", 86400],
1097
+ ["hour", 3600],
1098
+ ["minute", 60],
1099
+ ["second", 1]
1100
+ ];
1101
+ const [unit, seconds] = units.find(([, size]) => Math.abs(deltaSeconds) >= size) || units.at(-1);
1102
+ return new Intl.RelativeTimeFormat(localeFor(value.language), { numeric: "auto" }).format(Math.round(deltaSeconds / seconds), unit);
1103
+ }
1104
+ var preferences = {
1105
+ defaults: PREFERENCE_DEFAULTS,
1106
+ get: getPreferences,
1107
+ update: updatePreferences,
1108
+ subscribe: subscribePreferences,
1109
+ clearCache: clearPreferenceCache,
1110
+ apply: applyPreferences,
1111
+ startSync: startPreferenceSync,
1112
+ formatDate: formatUserDate,
1113
+ formatNumber: formatUserNumber,
1114
+ formatRelativeTime: formatUserRelativeTime,
1115
+ normalize: normalizePreferences,
1116
+ resolveTheme
1117
+ };
1118
+
808
1119
  // react/AuthProvider.jsx
809
1120
  import React, { createContext, useState, useEffect, useRef } from "react";
810
1121
  var AuthContext = createContext();
@@ -868,7 +1179,8 @@ function AuthProvider({ children, onSessionExpired }) {
868
1179
  console.log("\u{1F4E5} Profile response status:", res.status);
869
1180
  if (!res.ok) throw new Error("Failed to fetch user");
870
1181
  return res.json();
871
- }).then((userData) => {
1182
+ }).then((responseBody) => {
1183
+ const userData = (responseBody == null ? void 0 : responseBody.data) ?? responseBody;
872
1184
  console.log("\u2705 Profile fetched successfully:", userData.email);
873
1185
  setUser(userData);
874
1186
  setSessionValid(true);
@@ -1015,6 +1327,62 @@ var useSessionMonitor = (options = {}) => {
1015
1327
  };
1016
1328
  };
1017
1329
 
1330
+ // react/PreferencesProvider.jsx
1331
+ import React2, { createContext as createContext2, useContext as useContext2, useEffect as useEffect3, useMemo, useState as useState2 } from "react";
1332
+ var PreferencesContext = createContext2(null);
1333
+ function PreferencesProvider({ children, initialPreferences, applyToDocument = true }) {
1334
+ const [preferences2, setPreferences] = useState2(() => ({
1335
+ ...PREFERENCE_DEFAULTS,
1336
+ ...initialPreferences
1337
+ }));
1338
+ const [loading, setLoading] = useState2(true);
1339
+ const [error, setError] = useState2(null);
1340
+ useEffect3(() => {
1341
+ const unsubscribe = subscribePreferences((next) => {
1342
+ setPreferences(next);
1343
+ setLoading(false);
1344
+ setError(null);
1345
+ });
1346
+ const stopSync = startPreferenceSync();
1347
+ getPreferences().catch((nextError) => {
1348
+ setError(nextError);
1349
+ setLoading(false);
1350
+ });
1351
+ return () => {
1352
+ unsubscribe();
1353
+ stopSync();
1354
+ };
1355
+ }, []);
1356
+ useEffect3(() => {
1357
+ if (applyToDocument) applyPreferences(preferences2);
1358
+ }, [applyToDocument, preferences2]);
1359
+ useEffect3(() => {
1360
+ var _a;
1361
+ if (preferences2.theme !== "system" || typeof matchMedia !== "function") return void 0;
1362
+ const media = matchMedia("(prefers-color-scheme: dark)");
1363
+ const applySystemTheme = () => applyPreferences(preferences2);
1364
+ (_a = media.addEventListener) == null ? void 0 : _a.call(media, "change", applySystemTheme);
1365
+ return () => {
1366
+ var _a2;
1367
+ return (_a2 = media.removeEventListener) == null ? void 0 : _a2.call(media, "change", applySystemTheme);
1368
+ };
1369
+ }, [preferences2]);
1370
+ const value = useMemo(() => ({
1371
+ preferences: preferences2,
1372
+ resolvedTheme: resolveTheme(preferences2.theme),
1373
+ loading,
1374
+ error,
1375
+ refresh: () => getPreferences({ force: true }),
1376
+ update: updatePreferences
1377
+ }), [error, loading, preferences2]);
1378
+ return /* @__PURE__ */ React2.createElement(PreferencesContext.Provider, { value }, children);
1379
+ }
1380
+ function usePreferences() {
1381
+ const context = useContext2(PreferencesContext);
1382
+ if (!context) throw new Error("usePreferences must be used within a PreferencesProvider");
1383
+ return context;
1384
+ }
1385
+
1018
1386
  // index.js
1019
1387
  var auth = {
1020
1388
  // ๐Ÿ”ง Config
@@ -1034,7 +1402,7 @@ var auth = {
1034
1402
  getToken,
1035
1403
  setToken,
1036
1404
  clearToken,
1037
- setRefreshToken,
1405
+ setRefreshToken: setRefreshToken2,
1038
1406
  // โœ… Refresh token for HTTP dev
1039
1407
  getRefreshToken,
1040
1408
  clearRefreshToken,
@@ -1045,6 +1413,10 @@ var auth = {
1045
1413
  // โœ… Debug function
1046
1414
  // ๐ŸŒ Authenticated API client
1047
1415
  api: api_default,
1416
+ preferences,
1417
+ // ๐Ÿ”Ž Safe authentication diagnostics
1418
+ getDiagnosticContext,
1419
+ emitAuthDiagnostic,
1048
1420
  // ๐Ÿงช Utilities
1049
1421
  decodeToken,
1050
1422
  isTokenExpired,
@@ -1091,8 +1463,21 @@ var auth = {
1091
1463
  };
1092
1464
  export {
1093
1465
  AuthProvider,
1466
+ PREFERENCE_DEFAULTS,
1467
+ PreferencesProvider,
1468
+ applyPreferences,
1094
1469
  auth,
1470
+ formatUserDate,
1471
+ formatUserNumber,
1472
+ formatUserRelativeTime,
1473
+ getPreferences,
1474
+ normalizePreferences,
1475
+ resolveTheme,
1476
+ startPreferenceSync,
1477
+ subscribePreferences,
1478
+ updatePreferences,
1095
1479
  useAuth,
1480
+ usePreferences,
1096
1481
  useSessionMonitor
1097
1482
  };
1098
1483
  //# sourceMappingURL=index.js.map