@spidy092/auth-client 3.0.8 โ†’ 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");
@@ -206,6 +206,21 @@ function removeTokenListener(listener) {
206
206
  function getListenerCount() {
207
207
  return listeners.size;
208
208
  }
209
+ if (typeof window !== "undefined" && window.addEventListener) {
210
+ window.addEventListener("storage", (event) => {
211
+ if (event.key !== "authToken") return;
212
+ const previousToken = accessToken;
213
+ accessToken = event.newValue || null;
214
+ if (previousToken === accessToken) return;
215
+ listeners.forEach((listener) => {
216
+ try {
217
+ listener(accessToken, previousToken);
218
+ } catch (err) {
219
+ console.warn("Token listener error:", err);
220
+ }
221
+ });
222
+ });
223
+ }
209
224
 
210
225
  // config.js
211
226
  var config = {
@@ -250,8 +265,8 @@ function setConfig(customConfig = {}) {
250
265
  // โœ… Auto-detect router mode
251
266
  isRouter: customConfig.isRouter || customConfig.clientKey === "account-ui"
252
267
  };
268
+ enableRefreshTokenPersistence(config.persistRefreshToken);
253
269
  if (config.persistRefreshToken) {
254
- enableRefreshTokenPersistence(true);
255
270
  console.log("\u{1F4E6} Refresh token persistence ENABLED (localStorage on HTTPS)");
256
271
  }
257
272
  console.log(`\u{1F527} Auth Client Mode: ${config.isRouter ? "ROUTER" : "CLIENT"}`, {
@@ -267,6 +282,82 @@ function isRouterMode() {
267
282
  return config.isRouter;
268
283
  }
269
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
+
270
361
  // core.js
271
362
  var callbackProcessed = false;
272
363
  function login(clientKeyArg, redirectUriArg) {
@@ -279,14 +370,16 @@ function login(clientKeyArg, redirectUriArg) {
279
370
  } = getConfig();
280
371
  const clientKey = clientKeyArg || defaultClientKey;
281
372
  const redirectUri = redirectUriArg || defaultRedirectUri;
282
- console.log("\u{1F504} Smart Login initiated:", {
283
- mode: isRouterMode() ? "ROUTER" : "CLIENT",
284
- clientKey,
285
- redirectUri
286
- });
287
373
  if (!clientKey || !redirectUri) {
374
+ emitAuthDiagnostic("LOGIN_REJECTED", "FAILURE", "CLIENT_CONFIG_MISSING", { clientKey });
288
375
  throw new Error("Missing clientKey or redirectUri");
289
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 });
290
383
  sessionStorage.setItem("originalApp", clientKey);
291
384
  sessionStorage.setItem("returnUrl", redirectUri);
292
385
  if (isRouterMode()) {
@@ -301,13 +394,9 @@ function routerLogin(clientKey, redirectUri) {
301
394
  if (redirectUri) {
302
395
  params.append("redirect_uri", redirectUri);
303
396
  }
397
+ params.append("correlation_id", getDiagnosticContext().correlationId);
304
398
  const query = params.toString();
305
399
  const backendLoginUrl = `${authBaseUrl}/login/${clientKey}${query ? `?${query}` : ""}`;
306
- console.log("\u{1F3ED} Router Login: Direct backend authentication", {
307
- clientKey,
308
- redirectUri,
309
- backendUrl: backendLoginUrl
310
- });
311
400
  window.location.href = backendLoginUrl;
312
401
  }
313
402
  function clientLogin(clientKey, redirectUri) {
@@ -319,19 +408,19 @@ function clientLogin(clientKey, redirectUri) {
319
408
  params.append("redirect_uri", redirectUri);
320
409
  }
321
410
  const centralizedLoginUrl = `${accountUiUrl}/login?${params.toString()}`;
322
- console.log("\u{1F504} Client Login: Redirecting to centralized login", {
323
- clientKey,
324
- redirectUri,
325
- centralizedUrl: centralizedLoginUrl
326
- });
327
411
  window.location.href = centralizedLoginUrl;
328
412
  }
329
- async function logout() {
413
+ async function logout(options = {}) {
330
414
  resetCallbackState();
331
415
  const { clientKey, authBaseUrl, accountUiUrl } = getConfig();
416
+ const scope = options.scope === "client" ? "client" : "sso";
332
417
  const token = getToken();
333
418
  const refreshToken2 = getRefreshToken();
334
- console.log("\u{1F6AA} Smart Logout initiated", { mode: isRouterMode() ? "ROUTER" : "CLIENT", clientKey });
419
+ console.log("\u{1F6AA} Smart Logout initiated", {
420
+ mode: isRouterMode() ? "ROUTER" : "CLIENT",
421
+ clientKey,
422
+ scope
423
+ });
335
424
  clearToken();
336
425
  clearRefreshToken();
337
426
  sessionStorage.removeItem("originalApp");
@@ -341,21 +430,33 @@ async function logout() {
341
430
  method: "POST",
342
431
  credentials: "include",
343
432
  headers: {
433
+ ...diagnosticHeaders(),
344
434
  "Authorization": token ? `Bearer ${token}` : "",
345
435
  "Content-Type": "application/json"
346
436
  },
347
- body: JSON.stringify({ refreshToken: refreshToken2 })
437
+ body: JSON.stringify({ refreshToken: refreshToken2, scope })
348
438
  });
439
+ if (!response.ok) {
440
+ throw new Error(`Logout failed: ${response.status}`);
441
+ }
349
442
  const data = await response.json();
350
443
  console.log("\u2705 Logout response:", data);
444
+ if (data == null ? void 0 : data.logoutRedirectUrl) {
445
+ window.location.replace(data.logoutRedirectUrl);
446
+ return;
447
+ }
351
448
  if (data == null ? void 0 : data.keycloakLogoutUrl) {
352
- window.location.href = data.keycloakLogoutUrl;
449
+ window.location.replace(data.keycloakLogoutUrl);
353
450
  return;
354
451
  }
355
452
  } catch (error) {
356
453
  console.warn("\u26A0\uFE0F Logout backend call failed:", error);
357
454
  }
358
- window.location.href = isRouterMode() ? "/login" : `${accountUiUrl}/login?client=${clientKey}&logout=true`;
455
+ const fallbackUrl = isRouterMode() ? new URL("/login", window.location.origin) : new URL("/login", accountUiUrl);
456
+ fallbackUrl.searchParams.set("logged_out", "true");
457
+ fallbackUrl.searchParams.set("client", clientKey);
458
+ fallbackUrl.searchParams.set("scope", scope);
459
+ window.location.replace(fallbackUrl.toString());
359
460
  }
360
461
  function handleCallback() {
361
462
  var _a;
@@ -375,11 +476,19 @@ function handleCallback() {
375
476
  callbackProcessed = false;
376
477
  }
377
478
  callbackProcessed = true;
479
+ clearLoginLock();
378
480
  sessionStorage.removeItem("originalApp");
379
481
  sessionStorage.removeItem("returnUrl");
380
482
  if (error) {
381
483
  const errorDescription = params.get("error_description") || error;
382
- 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;
383
492
  }
384
493
  if (accessToken2) {
385
494
  setToken(accessToken2);
@@ -402,9 +511,19 @@ function handleCallback() {
402
511
  url.searchParams.delete("error_description");
403
512
  window.history.replaceState({}, "", url);
404
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
+ });
405
518
  return accessToken2;
406
519
  }
407
- 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;
408
527
  }
409
528
  function resetCallbackState() {
410
529
  callbackProcessed = false;
@@ -419,7 +538,7 @@ async function refreshToken() {
419
538
  }
420
539
  refreshInProgress = true;
421
540
  refreshPromise = (async () => {
422
- var _a, _b, _c, _d;
541
+ var _a, _b, _c, _d, _e, _f;
423
542
  try {
424
543
  const storedRefreshToken = getRefreshToken();
425
544
  console.log("\u{1F504} Refreshing token:", {
@@ -432,6 +551,7 @@ async function refreshToken() {
432
551
  credentials: "include",
433
552
  // โœ… Include httpOnly cookies (for HTTPS)
434
553
  headers: {
554
+ ...diagnosticHeaders(),
435
555
  "Content-Type": "application/json"
436
556
  }
437
557
  };
@@ -442,8 +562,19 @@ async function refreshToken() {
442
562
  const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
443
563
  if (!response.ok) {
444
564
  const errorText = await response.text();
445
- console.error("\u274C Token refresh failed:", response.status, errorText);
446
- 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;
447
578
  }
448
579
  const data = await response.json();
449
580
  const { access_token, refresh_token: new_refresh_token } = data;
@@ -456,10 +587,11 @@ async function refreshToken() {
456
587
  console.log("\u{1F504} New refresh token stored from rotation");
457
588
  }
458
589
  console.log("\u2705 Token refresh successful, listeners notified");
590
+ emitAuthDiagnostic("TOKEN_REFRESH_COMPLETED", "SUCCESS", "NONE", { clientKey });
459
591
  return access_token;
460
592
  } catch (err) {
461
593
  console.error("\u274C Token refresh error:", err);
462
- 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"));
463
595
  if (isAuthRejection) {
464
596
  clearToken();
465
597
  clearRefreshToken();
@@ -482,6 +614,7 @@ async function validateCurrentSession() {
482
614
  const response = await fetch(`${authBaseUrl}/account/validate-session`, {
483
615
  method: "GET",
484
616
  headers: {
617
+ ...diagnosticHeaders(),
485
618
  "Authorization": `Bearer ${token}`,
486
619
  "Content-Type": "application/json"
487
620
  },
@@ -734,6 +867,7 @@ api.interceptors.request.use((config2) => {
734
867
  if ((runtimeConfig == null ? void 0 : runtimeConfig.clientKey) && !config2.headers["X-Client-Key"]) {
735
868
  config2.headers["X-Client-Key"] = runtimeConfig.clientKey;
736
869
  }
870
+ Object.assign(config2.headers, diagnosticHeaders());
737
871
  const token = getToken();
738
872
  if (token) {
739
873
  config2.headers.Authorization = `Bearer ${token}`;
@@ -752,6 +886,10 @@ api.interceptors.response.use(
752
886
  return Promise.reject(error);
753
887
  }
754
888
  config2._retry = true;
889
+ emitAuthDiagnostic("API_401_REFRESH_STARTED", "PENDING", "HTTP_401", {
890
+ clientKey: getConfig().clientKey,
891
+ status: 401
892
+ });
755
893
  if (!refreshPromise2) {
756
894
  refreshPromise2 = refreshToken().then((newToken) => {
757
895
  refreshPromise2 = null;
@@ -811,6 +949,208 @@ function isAuthenticated() {
811
949
  return !!token && !isTokenExpired(token, 10);
812
950
  }
813
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
+
814
1154
  // react/AuthProvider.jsx
815
1155
  var import_react = __toESM(require("react"), 1);
816
1156
  var AuthContext = (0, import_react.createContext)();
@@ -818,6 +1158,10 @@ var AuthContext = (0, import_react.createContext)();
818
1158
  // react/useAuth.js
819
1159
  var import_react2 = require("react");
820
1160
 
1161
+ // react/PreferencesProvider.jsx
1162
+ var import_react3 = __toESM(require("react"), 1);
1163
+ var PreferencesContext = (0, import_react3.createContext)(null);
1164
+
821
1165
  // index.js
822
1166
  var auth = {
823
1167
  // ๐Ÿ”ง Config
@@ -827,6 +1171,8 @@ var auth = {
827
1171
  // ๐Ÿ” Core flows
828
1172
  login,
829
1173
  logout,
1174
+ logoutClient: () => logout({ scope: "client" }),
1175
+ logoutSso: () => logout({ scope: "sso" }),
830
1176
  handleCallback,
831
1177
  refreshToken,
832
1178
  resetCallbackState,
@@ -846,6 +1192,10 @@ var auth = {
846
1192
  // โœ… Debug function
847
1193
  // ๐ŸŒ Authenticated API client
848
1194
  api: api_default,
1195
+ preferences,
1196
+ // ๐Ÿ”Ž Safe authentication diagnostics
1197
+ getDiagnosticContext,
1198
+ emitAuthDiagnostic,
849
1199
  // ๐Ÿงช Utilities
850
1200
  decodeToken,
851
1201
  isTokenExpired,
@@ -904,7 +1254,7 @@ var useSessionMonitor = (options = {}) => {
904
1254
  autoLogout = true,
905
1255
  validateOnMount = true
906
1256
  } = options;
907
- const handleInvalid = (0, import_react3.useCallback)(() => {
1257
+ const handleInvalid = (0, import_react4.useCallback)(() => {
908
1258
  console.log("\u{1F6A8} useSessionMonitor: Session invalid detected");
909
1259
  queryClient.clear();
910
1260
  if (autoLogout) {
@@ -948,7 +1298,7 @@ var useSessionMonitor = (options = {}) => {
948
1298
  staleTime: refetchInterval / 2
949
1299
  // Consider stale at half the interval
950
1300
  });
951
- (0, import_react3.useEffect)(() => {
1301
+ (0, import_react4.useEffect)(() => {
952
1302
  if (!enabled) return;
953
1303
  const handleVisibilityChange = () => {
954
1304
  if (document.visibilityState === "visible" && auth.getToken()) {
@@ -961,7 +1311,7 @@ var useSessionMonitor = (options = {}) => {
961
1311
  document.removeEventListener("visibilitychange", handleVisibilityChange);
962
1312
  };
963
1313
  }, [enabled, queryClient]);
964
- (0, import_react3.useEffect)(() => {
1314
+ (0, import_react4.useEffect)(() => {
965
1315
  if (validateOnMount && enabled && auth.getToken()) {
966
1316
  queryClient.invalidateQueries({ queryKey: ["session-validation"] });
967
1317
  }