@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.
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);
@@ -207,6 +220,21 @@ function removeTokenListener(listener) {
207
220
  function getListenerCount() {
208
221
  return listeners.size;
209
222
  }
223
+ if (typeof window !== "undefined" && window.addEventListener) {
224
+ window.addEventListener("storage", (event) => {
225
+ if (event.key !== "authToken") return;
226
+ const previousToken = accessToken;
227
+ accessToken = event.newValue || null;
228
+ if (previousToken === accessToken) return;
229
+ listeners.forEach((listener) => {
230
+ try {
231
+ listener(accessToken, previousToken);
232
+ } catch (err) {
233
+ console.warn("Token listener error:", err);
234
+ }
235
+ });
236
+ });
237
+ }
210
238
 
211
239
  // config.js
212
240
  var config = {
@@ -251,8 +279,8 @@ function setConfig(customConfig = {}) {
251
279
  // โœ… Auto-detect router mode
252
280
  isRouter: customConfig.isRouter || customConfig.clientKey === "account-ui"
253
281
  };
282
+ enableRefreshTokenPersistence(config.persistRefreshToken);
254
283
  if (config.persistRefreshToken) {
255
- enableRefreshTokenPersistence(true);
256
284
  console.log("\u{1F4E6} Refresh token persistence ENABLED (localStorage on HTTPS)");
257
285
  }
258
286
  console.log(`\u{1F527} Auth Client Mode: ${config.isRouter ? "ROUTER" : "CLIENT"}`, {
@@ -268,6 +296,82 @@ function isRouterMode() {
268
296
  return config.isRouter;
269
297
  }
270
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
+
271
375
  // core.js
272
376
  var callbackProcessed = false;
273
377
  function login(clientKeyArg, redirectUriArg) {
@@ -280,14 +384,16 @@ function login(clientKeyArg, redirectUriArg) {
280
384
  } = getConfig();
281
385
  const clientKey = clientKeyArg || defaultClientKey;
282
386
  const redirectUri = redirectUriArg || defaultRedirectUri;
283
- console.log("\u{1F504} Smart Login initiated:", {
284
- mode: isRouterMode() ? "ROUTER" : "CLIENT",
285
- clientKey,
286
- redirectUri
287
- });
288
387
  if (!clientKey || !redirectUri) {
388
+ emitAuthDiagnostic("LOGIN_REJECTED", "FAILURE", "CLIENT_CONFIG_MISSING", { clientKey });
289
389
  throw new Error("Missing clientKey or redirectUri");
290
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 });
291
397
  sessionStorage.setItem("originalApp", clientKey);
292
398
  sessionStorage.setItem("returnUrl", redirectUri);
293
399
  if (isRouterMode()) {
@@ -302,13 +408,9 @@ function routerLogin(clientKey, redirectUri) {
302
408
  if (redirectUri) {
303
409
  params.append("redirect_uri", redirectUri);
304
410
  }
411
+ params.append("correlation_id", getDiagnosticContext().correlationId);
305
412
  const query = params.toString();
306
413
  const backendLoginUrl = `${authBaseUrl}/login/${clientKey}${query ? `?${query}` : ""}`;
307
- console.log("\u{1F3ED} Router Login: Direct backend authentication", {
308
- clientKey,
309
- redirectUri,
310
- backendUrl: backendLoginUrl
311
- });
312
414
  window.location.href = backendLoginUrl;
313
415
  }
314
416
  function clientLogin(clientKey, redirectUri) {
@@ -320,19 +422,19 @@ function clientLogin(clientKey, redirectUri) {
320
422
  params.append("redirect_uri", redirectUri);
321
423
  }
322
424
  const centralizedLoginUrl = `${accountUiUrl}/login?${params.toString()}`;
323
- console.log("\u{1F504} Client Login: Redirecting to centralized login", {
324
- clientKey,
325
- redirectUri,
326
- centralizedUrl: centralizedLoginUrl
327
- });
328
425
  window.location.href = centralizedLoginUrl;
329
426
  }
330
- async function logout() {
427
+ async function logout(options = {}) {
331
428
  resetCallbackState();
332
429
  const { clientKey, authBaseUrl, accountUiUrl } = getConfig();
430
+ const scope = options.scope === "client" ? "client" : "sso";
333
431
  const token = getToken();
334
432
  const refreshToken2 = getRefreshToken();
335
- console.log("\u{1F6AA} Smart Logout initiated", { mode: isRouterMode() ? "ROUTER" : "CLIENT", clientKey });
433
+ console.log("\u{1F6AA} Smart Logout initiated", {
434
+ mode: isRouterMode() ? "ROUTER" : "CLIENT",
435
+ clientKey,
436
+ scope
437
+ });
336
438
  clearToken();
337
439
  clearRefreshToken();
338
440
  sessionStorage.removeItem("originalApp");
@@ -342,21 +444,33 @@ async function logout() {
342
444
  method: "POST",
343
445
  credentials: "include",
344
446
  headers: {
447
+ ...diagnosticHeaders(),
345
448
  "Authorization": token ? `Bearer ${token}` : "",
346
449
  "Content-Type": "application/json"
347
450
  },
348
- body: JSON.stringify({ refreshToken: refreshToken2 })
451
+ body: JSON.stringify({ refreshToken: refreshToken2, scope })
349
452
  });
453
+ if (!response.ok) {
454
+ throw new Error(`Logout failed: ${response.status}`);
455
+ }
350
456
  const data = await response.json();
351
457
  console.log("\u2705 Logout response:", data);
458
+ if (data == null ? void 0 : data.logoutRedirectUrl) {
459
+ window.location.replace(data.logoutRedirectUrl);
460
+ return;
461
+ }
352
462
  if (data == null ? void 0 : data.keycloakLogoutUrl) {
353
- window.location.href = data.keycloakLogoutUrl;
463
+ window.location.replace(data.keycloakLogoutUrl);
354
464
  return;
355
465
  }
356
466
  } catch (error) {
357
467
  console.warn("\u26A0\uFE0F Logout backend call failed:", error);
358
468
  }
359
- window.location.href = isRouterMode() ? "/login" : `${accountUiUrl}/login?client=${clientKey}&logout=true`;
469
+ const fallbackUrl = isRouterMode() ? new URL("/login", window.location.origin) : new URL("/login", accountUiUrl);
470
+ fallbackUrl.searchParams.set("logged_out", "true");
471
+ fallbackUrl.searchParams.set("client", clientKey);
472
+ fallbackUrl.searchParams.set("scope", scope);
473
+ window.location.replace(fallbackUrl.toString());
360
474
  }
361
475
  function handleCallback() {
362
476
  var _a;
@@ -376,11 +490,19 @@ function handleCallback() {
376
490
  callbackProcessed = false;
377
491
  }
378
492
  callbackProcessed = true;
493
+ clearLoginLock();
379
494
  sessionStorage.removeItem("originalApp");
380
495
  sessionStorage.removeItem("returnUrl");
381
496
  if (error) {
382
497
  const errorDescription = params.get("error_description") || error;
383
- 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;
384
506
  }
385
507
  if (accessToken2) {
386
508
  setToken(accessToken2);
@@ -403,9 +525,19 @@ function handleCallback() {
403
525
  url.searchParams.delete("error_description");
404
526
  window.history.replaceState({}, "", url);
405
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
+ });
406
532
  return accessToken2;
407
533
  }
408
- 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;
409
541
  }
410
542
  function resetCallbackState() {
411
543
  callbackProcessed = false;
@@ -420,7 +552,7 @@ async function refreshToken() {
420
552
  }
421
553
  refreshInProgress = true;
422
554
  refreshPromise = (async () => {
423
- var _a, _b, _c, _d;
555
+ var _a, _b, _c, _d, _e, _f;
424
556
  try {
425
557
  const storedRefreshToken = getRefreshToken();
426
558
  console.log("\u{1F504} Refreshing token:", {
@@ -433,6 +565,7 @@ async function refreshToken() {
433
565
  credentials: "include",
434
566
  // โœ… Include httpOnly cookies (for HTTPS)
435
567
  headers: {
568
+ ...diagnosticHeaders(),
436
569
  "Content-Type": "application/json"
437
570
  }
438
571
  };
@@ -443,8 +576,19 @@ async function refreshToken() {
443
576
  const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
444
577
  if (!response.ok) {
445
578
  const errorText = await response.text();
446
- console.error("\u274C Token refresh failed:", response.status, errorText);
447
- 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;
448
592
  }
449
593
  const data = await response.json();
450
594
  const { access_token, refresh_token: new_refresh_token } = data;
@@ -457,10 +601,11 @@ async function refreshToken() {
457
601
  console.log("\u{1F504} New refresh token stored from rotation");
458
602
  }
459
603
  console.log("\u2705 Token refresh successful, listeners notified");
604
+ emitAuthDiagnostic("TOKEN_REFRESH_COMPLETED", "SUCCESS", "NONE", { clientKey });
460
605
  return access_token;
461
606
  } catch (err) {
462
607
  console.error("\u274C Token refresh error:", err);
463
- 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"));
464
609
  if (isAuthRejection) {
465
610
  clearToken();
466
611
  clearRefreshToken();
@@ -483,6 +628,7 @@ async function validateCurrentSession() {
483
628
  const response = await fetch(`${authBaseUrl}/account/validate-session`, {
484
629
  method: "GET",
485
630
  headers: {
631
+ ...diagnosticHeaders(),
486
632
  "Authorization": `Bearer ${token}`,
487
633
  "Content-Type": "application/json"
488
634
  },
@@ -735,6 +881,7 @@ api.interceptors.request.use((config2) => {
735
881
  if ((runtimeConfig == null ? void 0 : runtimeConfig.clientKey) && !config2.headers["X-Client-Key"]) {
736
882
  config2.headers["X-Client-Key"] = runtimeConfig.clientKey;
737
883
  }
884
+ Object.assign(config2.headers, diagnosticHeaders());
738
885
  const token = getToken();
739
886
  if (token) {
740
887
  config2.headers.Authorization = `Bearer ${token}`;
@@ -753,6 +900,10 @@ api.interceptors.response.use(
753
900
  return Promise.reject(error);
754
901
  }
755
902
  config2._retry = true;
903
+ emitAuthDiagnostic("API_401_REFRESH_STARTED", "PENDING", "HTTP_401", {
904
+ clientKey: getConfig().clientKey,
905
+ status: 401
906
+ });
756
907
  if (!refreshPromise2) {
757
908
  refreshPromise2 = refreshToken().then((newToken) => {
758
909
  refreshPromise2 = null;
@@ -812,6 +963,208 @@ function isAuthenticated() {
812
963
  return !!token && !isTokenExpired(token, 10);
813
964
  }
814
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
+
815
1168
  // react/AuthProvider.jsx
816
1169
  var import_react = __toESM(require("react"), 1);
817
1170
  var AuthContext = (0, import_react.createContext)();
@@ -875,7 +1228,8 @@ function AuthProvider({ children, onSessionExpired }) {
875
1228
  console.log("\u{1F4E5} Profile response status:", res.status);
876
1229
  if (!res.ok) throw new Error("Failed to fetch user");
877
1230
  return res.json();
878
- }).then((userData) => {
1231
+ }).then((responseBody) => {
1232
+ const userData = (responseBody == null ? void 0 : responseBody.data) ?? responseBody;
879
1233
  console.log("\u2705 Profile fetched successfully:", userData.email);
880
1234
  setUser(userData);
881
1235
  setSessionValid(true);
@@ -891,13 +1245,16 @@ function AuthProvider({ children, onSessionExpired }) {
891
1245
  const login2 = (clientKey, redirectUri, state) => {
892
1246
  login(clientKey, redirectUri, state);
893
1247
  };
894
- const logout2 = () => {
1248
+ const logout2 = async (options) => {
895
1249
  stopSessionSecurity();
896
1250
  sessionSecurityRef.current = null;
897
- logout();
898
- setUser(null);
899
- setTokenState(null);
900
- setSessionValid(true);
1251
+ try {
1252
+ await logout(options);
1253
+ } finally {
1254
+ setUser(null);
1255
+ setTokenState(null);
1256
+ setSessionValid(true);
1257
+ }
901
1258
  };
902
1259
  const value = {
903
1260
  token,
@@ -1019,6 +1376,62 @@ var useSessionMonitor = (options = {}) => {
1019
1376
  };
1020
1377
  };
1021
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
+
1022
1435
  // index.js
1023
1436
  var auth = {
1024
1437
  // ๐Ÿ”ง Config
@@ -1028,6 +1441,8 @@ var auth = {
1028
1441
  // ๐Ÿ” Core flows
1029
1442
  login,
1030
1443
  logout,
1444
+ logoutClient: () => logout({ scope: "client" }),
1445
+ logoutSso: () => logout({ scope: "sso" }),
1031
1446
  handleCallback,
1032
1447
  refreshToken,
1033
1448
  resetCallbackState,
@@ -1047,6 +1462,10 @@ var auth = {
1047
1462
  // โœ… Debug function
1048
1463
  // ๐ŸŒ Authenticated API client
1049
1464
  api: api_default,
1465
+ preferences,
1466
+ // ๐Ÿ”Ž Safe authentication diagnostics
1467
+ getDiagnosticContext,
1468
+ emitAuthDiagnostic,
1050
1469
  // ๐Ÿงช Utilities
1051
1470
  decodeToken,
1052
1471
  isTokenExpired,
@@ -1094,8 +1513,21 @@ var auth = {
1094
1513
  // Annotate the CommonJS export names for ESM import in node:
1095
1514
  0 && (module.exports = {
1096
1515
  AuthProvider,
1516
+ PREFERENCE_DEFAULTS,
1517
+ PreferencesProvider,
1518
+ applyPreferences,
1097
1519
  auth,
1520
+ formatUserDate,
1521
+ formatUserNumber,
1522
+ formatUserRelativeTime,
1523
+ getPreferences,
1524
+ normalizePreferences,
1525
+ resolveTheme,
1526
+ startPreferenceSync,
1527
+ subscribePreferences,
1528
+ updatePreferences,
1098
1529
  useAuth,
1530
+ usePreferences,
1099
1531
  useSessionMonitor
1100
1532
  });
1101
1533
  //# sourceMappingURL=index.cjs.map