@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.js CHANGED
@@ -169,6 +169,21 @@ function removeTokenListener(listener) {
169
169
  function getListenerCount() {
170
170
  return listeners.size;
171
171
  }
172
+ if (typeof window !== "undefined" && window.addEventListener) {
173
+ window.addEventListener("storage", (event) => {
174
+ if (event.key !== "authToken") return;
175
+ const previousToken = accessToken;
176
+ accessToken = event.newValue || null;
177
+ if (previousToken === accessToken) return;
178
+ listeners.forEach((listener) => {
179
+ try {
180
+ listener(accessToken, previousToken);
181
+ } catch (err) {
182
+ console.warn("Token listener error:", err);
183
+ }
184
+ });
185
+ });
186
+ }
172
187
 
173
188
  // config.js
174
189
  var config = {
@@ -213,8 +228,8 @@ function setConfig(customConfig = {}) {
213
228
  // โœ… Auto-detect router mode
214
229
  isRouter: customConfig.isRouter || customConfig.clientKey === "account-ui"
215
230
  };
231
+ enableRefreshTokenPersistence(config.persistRefreshToken);
216
232
  if (config.persistRefreshToken) {
217
- enableRefreshTokenPersistence(true);
218
233
  console.log("\u{1F4E6} Refresh token persistence ENABLED (localStorage on HTTPS)");
219
234
  }
220
235
  console.log(`\u{1F527} Auth Client Mode: ${config.isRouter ? "ROUTER" : "CLIENT"}`, {
@@ -230,6 +245,82 @@ function isRouterMode() {
230
245
  return config.isRouter;
231
246
  }
232
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
+
233
324
  // core.js
234
325
  var callbackProcessed = false;
235
326
  function login(clientKeyArg, redirectUriArg) {
@@ -242,14 +333,16 @@ function login(clientKeyArg, redirectUriArg) {
242
333
  } = getConfig();
243
334
  const clientKey = clientKeyArg || defaultClientKey;
244
335
  const redirectUri = redirectUriArg || defaultRedirectUri;
245
- console.log("\u{1F504} Smart Login initiated:", {
246
- mode: isRouterMode() ? "ROUTER" : "CLIENT",
247
- clientKey,
248
- redirectUri
249
- });
250
336
  if (!clientKey || !redirectUri) {
337
+ emitAuthDiagnostic("LOGIN_REJECTED", "FAILURE", "CLIENT_CONFIG_MISSING", { clientKey });
251
338
  throw new Error("Missing clientKey or redirectUri");
252
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 });
253
346
  sessionStorage.setItem("originalApp", clientKey);
254
347
  sessionStorage.setItem("returnUrl", redirectUri);
255
348
  if (isRouterMode()) {
@@ -264,13 +357,9 @@ function routerLogin(clientKey, redirectUri) {
264
357
  if (redirectUri) {
265
358
  params.append("redirect_uri", redirectUri);
266
359
  }
360
+ params.append("correlation_id", getDiagnosticContext().correlationId);
267
361
  const query = params.toString();
268
362
  const backendLoginUrl = `${authBaseUrl}/login/${clientKey}${query ? `?${query}` : ""}`;
269
- console.log("\u{1F3ED} Router Login: Direct backend authentication", {
270
- clientKey,
271
- redirectUri,
272
- backendUrl: backendLoginUrl
273
- });
274
363
  window.location.href = backendLoginUrl;
275
364
  }
276
365
  function clientLogin(clientKey, redirectUri) {
@@ -282,19 +371,19 @@ function clientLogin(clientKey, redirectUri) {
282
371
  params.append("redirect_uri", redirectUri);
283
372
  }
284
373
  const centralizedLoginUrl = `${accountUiUrl}/login?${params.toString()}`;
285
- console.log("\u{1F504} Client Login: Redirecting to centralized login", {
286
- clientKey,
287
- redirectUri,
288
- centralizedUrl: centralizedLoginUrl
289
- });
290
374
  window.location.href = centralizedLoginUrl;
291
375
  }
292
- async function logout() {
376
+ async function logout(options = {}) {
293
377
  resetCallbackState();
294
378
  const { clientKey, authBaseUrl, accountUiUrl } = getConfig();
379
+ const scope = options.scope === "client" ? "client" : "sso";
295
380
  const token = getToken();
296
381
  const refreshToken2 = getRefreshToken();
297
- console.log("\u{1F6AA} Smart Logout initiated", { mode: isRouterMode() ? "ROUTER" : "CLIENT", clientKey });
382
+ console.log("\u{1F6AA} Smart Logout initiated", {
383
+ mode: isRouterMode() ? "ROUTER" : "CLIENT",
384
+ clientKey,
385
+ scope
386
+ });
298
387
  clearToken();
299
388
  clearRefreshToken();
300
389
  sessionStorage.removeItem("originalApp");
@@ -304,21 +393,33 @@ async function logout() {
304
393
  method: "POST",
305
394
  credentials: "include",
306
395
  headers: {
396
+ ...diagnosticHeaders(),
307
397
  "Authorization": token ? `Bearer ${token}` : "",
308
398
  "Content-Type": "application/json"
309
399
  },
310
- body: JSON.stringify({ refreshToken: refreshToken2 })
400
+ body: JSON.stringify({ refreshToken: refreshToken2, scope })
311
401
  });
402
+ if (!response.ok) {
403
+ throw new Error(`Logout failed: ${response.status}`);
404
+ }
312
405
  const data = await response.json();
313
406
  console.log("\u2705 Logout response:", data);
407
+ if (data == null ? void 0 : data.logoutRedirectUrl) {
408
+ window.location.replace(data.logoutRedirectUrl);
409
+ return;
410
+ }
314
411
  if (data == null ? void 0 : data.keycloakLogoutUrl) {
315
- window.location.href = data.keycloakLogoutUrl;
412
+ window.location.replace(data.keycloakLogoutUrl);
316
413
  return;
317
414
  }
318
415
  } catch (error) {
319
416
  console.warn("\u26A0\uFE0F Logout backend call failed:", error);
320
417
  }
321
- window.location.href = isRouterMode() ? "/login" : `${accountUiUrl}/login?client=${clientKey}&logout=true`;
418
+ const fallbackUrl = isRouterMode() ? new URL("/login", window.location.origin) : new URL("/login", accountUiUrl);
419
+ fallbackUrl.searchParams.set("logged_out", "true");
420
+ fallbackUrl.searchParams.set("client", clientKey);
421
+ fallbackUrl.searchParams.set("scope", scope);
422
+ window.location.replace(fallbackUrl.toString());
322
423
  }
323
424
  function handleCallback() {
324
425
  var _a;
@@ -338,11 +439,19 @@ function handleCallback() {
338
439
  callbackProcessed = false;
339
440
  }
340
441
  callbackProcessed = true;
442
+ clearLoginLock();
341
443
  sessionStorage.removeItem("originalApp");
342
444
  sessionStorage.removeItem("returnUrl");
343
445
  if (error) {
344
446
  const errorDescription = params.get("error_description") || error;
345
- throw new Error(`Authentication failed: ${errorDescription}`);
447
+ const authError = new Error(`Authentication failed: ${errorDescription}`);
448
+ authError.code = error;
449
+ authError.correlationId = getDiagnosticContext().correlationId;
450
+ emitAuthDiagnostic("CALLBACK_REJECTED", "FAILURE", error.toUpperCase(), {
451
+ clientKey: getConfig().clientKey,
452
+ state: params.get("state")
453
+ });
454
+ throw authError;
346
455
  }
347
456
  if (accessToken2) {
348
457
  setToken(accessToken2);
@@ -365,9 +474,19 @@ function handleCallback() {
365
474
  url.searchParams.delete("error_description");
366
475
  window.history.replaceState({}, "", url);
367
476
  console.log("\u2705 Callback processed successfully, token stored");
477
+ emitAuthDiagnostic("CALLBACK_COMPLETED", "SUCCESS", "NONE", {
478
+ clientKey: getConfig().clientKey,
479
+ state: params.get("state")
480
+ });
368
481
  return accessToken2;
369
482
  }
370
- throw new Error("No access token found in callback URL");
483
+ emitAuthDiagnostic("CALLBACK_REJECTED", "FAILURE", "ACCESS_TOKEN_MISSING", {
484
+ clientKey: getConfig().clientKey,
485
+ state: params.get("state")
486
+ });
487
+ const missingTokenError = new Error("No access token found in callback URL");
488
+ missingTokenError.code = "ACCESS_TOKEN_MISSING";
489
+ throw missingTokenError;
371
490
  }
372
491
  function resetCallbackState() {
373
492
  callbackProcessed = false;
@@ -382,7 +501,7 @@ async function refreshToken() {
382
501
  }
383
502
  refreshInProgress = true;
384
503
  refreshPromise = (async () => {
385
- var _a, _b, _c, _d;
504
+ var _a, _b, _c, _d, _e, _f;
386
505
  try {
387
506
  const storedRefreshToken = getRefreshToken();
388
507
  console.log("\u{1F504} Refreshing token:", {
@@ -395,6 +514,7 @@ async function refreshToken() {
395
514
  credentials: "include",
396
515
  // โœ… Include httpOnly cookies (for HTTPS)
397
516
  headers: {
517
+ ...diagnosticHeaders(),
398
518
  "Content-Type": "application/json"
399
519
  }
400
520
  };
@@ -405,8 +525,19 @@ async function refreshToken() {
405
525
  const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
406
526
  if (!response.ok) {
407
527
  const errorText = await response.text();
408
- console.error("\u274C Token refresh failed:", response.status, errorText);
409
- throw new Error(`Refresh failed: ${response.status}`);
528
+ let serverCode = null;
529
+ try {
530
+ serverCode = ((_a = JSON.parse(errorText)) == null ? void 0 : _a.error) || ((_b = JSON.parse(errorText)) == null ? void 0 : _b.code);
531
+ } catch {
532
+ }
533
+ emitAuthDiagnostic("TOKEN_REFRESH_REJECTED", "FAILURE", serverCode || `HTTP_${response.status}`, {
534
+ clientKey,
535
+ status: response.status
536
+ });
537
+ const refreshError = new Error(`Refresh failed: ${response.status}`);
538
+ refreshError.code = serverCode || `HTTP_${response.status}`;
539
+ refreshError.status = response.status;
540
+ throw refreshError;
410
541
  }
411
542
  const data = await response.json();
412
543
  const { access_token, refresh_token: new_refresh_token } = data;
@@ -419,10 +550,11 @@ async function refreshToken() {
419
550
  console.log("\u{1F504} New refresh token stored from rotation");
420
551
  }
421
552
  console.log("\u2705 Token refresh successful, listeners notified");
553
+ emitAuthDiagnostic("TOKEN_REFRESH_COMPLETED", "SUCCESS", "NONE", { clientKey });
422
554
  return access_token;
423
555
  } catch (err) {
424
556
  console.error("\u274C Token refresh error:", err);
425
- 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"));
557
+ 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"));
426
558
  if (isAuthRejection) {
427
559
  clearToken();
428
560
  clearRefreshToken();
@@ -445,6 +577,7 @@ async function validateCurrentSession() {
445
577
  const response = await fetch(`${authBaseUrl}/account/validate-session`, {
446
578
  method: "GET",
447
579
  headers: {
580
+ ...diagnosticHeaders(),
448
581
  "Authorization": `Bearer ${token}`,
449
582
  "Content-Type": "application/json"
450
583
  },
@@ -697,6 +830,7 @@ api.interceptors.request.use((config2) => {
697
830
  if ((runtimeConfig == null ? void 0 : runtimeConfig.clientKey) && !config2.headers["X-Client-Key"]) {
698
831
  config2.headers["X-Client-Key"] = runtimeConfig.clientKey;
699
832
  }
833
+ Object.assign(config2.headers, diagnosticHeaders());
700
834
  const token = getToken();
701
835
  if (token) {
702
836
  config2.headers.Authorization = `Bearer ${token}`;
@@ -715,6 +849,10 @@ api.interceptors.response.use(
715
849
  return Promise.reject(error);
716
850
  }
717
851
  config2._retry = true;
852
+ emitAuthDiagnostic("API_401_REFRESH_STARTED", "PENDING", "HTTP_401", {
853
+ clientKey: getConfig().clientKey,
854
+ status: 401
855
+ });
718
856
  if (!refreshPromise2) {
719
857
  refreshPromise2 = refreshToken().then((newToken) => {
720
858
  refreshPromise2 = null;
@@ -774,6 +912,208 @@ function isAuthenticated() {
774
912
  return !!token && !isTokenExpired(token, 10);
775
913
  }
776
914
 
915
+ // preferences.js
916
+ var PREFERENCE_DEFAULTS = Object.freeze({
917
+ theme: "system",
918
+ colorScheme: "blue",
919
+ density: "comfortable",
920
+ reducedMotion: false,
921
+ language: "en",
922
+ timezone: "Etc/UTC",
923
+ dateFormat: "MM/DD/YYYY",
924
+ timeFormat: "12h",
925
+ highContrast: false,
926
+ largeText: false
927
+ });
928
+ var ALLOWED = {
929
+ theme: /* @__PURE__ */ new Set(["light", "dark", "system"]),
930
+ density: /* @__PURE__ */ new Set(["compact", "comfortable", "spacious"]),
931
+ timeFormat: /* @__PURE__ */ new Set(["12h", "24h"])
932
+ };
933
+ var listeners2 = /* @__PURE__ */ new Set();
934
+ var cachedPreferences = null;
935
+ var pendingRequest = null;
936
+ var syncCleanup = null;
937
+ function extractResponseData(response) {
938
+ var _a;
939
+ return ((_a = response == null ? void 0 : response.data) == null ? void 0 : _a.data) ?? (response == null ? void 0 : response.data) ?? response ?? {};
940
+ }
941
+ function normalizePreferences(value = {}) {
942
+ const next = { ...PREFERENCE_DEFAULTS };
943
+ for (const key of Object.keys(PREFERENCE_DEFAULTS)) {
944
+ if (!Object.hasOwn(value, key)) continue;
945
+ const candidate = value[key];
946
+ if (ALLOWED[key] && !ALLOWED[key].has(candidate)) continue;
947
+ if (typeof PREFERENCE_DEFAULTS[key] !== typeof candidate) continue;
948
+ next[key] = candidate;
949
+ }
950
+ return next;
951
+ }
952
+ function resolveTheme(theme, matchMedia2 = globalThis.matchMedia) {
953
+ if (theme === "dark" || theme === "light") return theme;
954
+ return typeof matchMedia2 === "function" && matchMedia2("(prefers-color-scheme: dark)").matches ? "dark" : "light";
955
+ }
956
+ function announce(preferences2) {
957
+ cachedPreferences = normalizePreferences(preferences2);
958
+ listeners2.forEach((listener) => listener(cachedPreferences));
959
+ return cachedPreferences;
960
+ }
961
+ async function getPreferences({ force = false } = {}) {
962
+ if (cachedPreferences && !force) return cachedPreferences;
963
+ if (pendingRequest) return pendingRequest;
964
+ pendingRequest = api_default.get("/account/preferences").then((response) => announce(extractResponseData(response))).finally(() => {
965
+ pendingRequest = null;
966
+ });
967
+ return pendingRequest;
968
+ }
969
+ async function updatePreferences(patch) {
970
+ const allowedPatch = {};
971
+ for (const key of Object.keys(PREFERENCE_DEFAULTS)) {
972
+ if (Object.hasOwn(patch, key)) allowedPatch[key] = patch[key];
973
+ }
974
+ const candidate = normalizePreferences({ ...cachedPreferences || PREFERENCE_DEFAULTS, ...allowedPatch });
975
+ const response = await api_default.put("/account/preferences", allowedPatch);
976
+ const saved = extractResponseData(response);
977
+ return announce(Object.keys(saved).length ? saved : candidate);
978
+ }
979
+ function subscribePreferences(listener) {
980
+ listeners2.add(listener);
981
+ if (cachedPreferences) listener(cachedPreferences);
982
+ return () => listeners2.delete(listener);
983
+ }
984
+ function clearPreferenceCache() {
985
+ cachedPreferences = null;
986
+ pendingRequest = null;
987
+ }
988
+ function applyPreferences(preferences2, documentRef = globalThis.document) {
989
+ var _a, _b, _c;
990
+ if (!(documentRef == null ? void 0 : documentRef.documentElement)) return;
991
+ const value = normalizePreferences(preferences2);
992
+ const root = documentRef.documentElement;
993
+ const resolvedTheme = resolveTheme(value.theme, (_b = (_a = documentRef.defaultView) == null ? void 0 : _a.matchMedia) == null ? void 0 : _b.bind(documentRef.defaultView));
994
+ root.dataset.theme = resolvedTheme;
995
+ root.dataset.themePreference = value.theme;
996
+ root.dataset.colorScheme = value.colorScheme;
997
+ root.dataset.density = value.density;
998
+ root.lang = value.language;
999
+ root.style.colorScheme = resolvedTheme;
1000
+ root.style.setProperty("--user-accent-color", {
1001
+ blue: "#1976d2",
1002
+ green: "#2e7d32",
1003
+ purple: "#7b1fa2",
1004
+ orange: "#c65d00",
1005
+ red: "#c62828"
1006
+ }[value.colorScheme] || "#1976d2");
1007
+ root.classList.toggle("user-reduced-motion", value.reducedMotion);
1008
+ root.classList.toggle("user-high-contrast", value.highContrast);
1009
+ root.classList.toggle("user-large-text", value.largeText);
1010
+ const styleId = "sso-user-preference-styles";
1011
+ if (!documentRef.getElementById(styleId)) {
1012
+ const style = documentRef.createElement("style");
1013
+ style.id = styleId;
1014
+ style.textContent = `
1015
+ html.user-large-text { font-size: 112.5%; }
1016
+ html.user-high-contrast { filter: contrast(1.15); }
1017
+ html[data-density="compact"] { --user-density-padding: 6px; }
1018
+ html[data-density="comfortable"] { --user-density-padding: 10px; }
1019
+ html[data-density="spacious"] { --user-density-padding: 14px; }
1020
+ html[data-density] .MuiListItem-root { padding-top: var(--user-density-padding); padding-bottom: var(--user-density-padding); }
1021
+ html.user-reduced-motion *, html.user-reduced-motion *::before, html.user-reduced-motion *::after {
1022
+ animation-duration: 0.01ms !important;
1023
+ animation-iteration-count: 1 !important;
1024
+ scroll-behavior: auto !important;
1025
+ transition-duration: 0.01ms !important;
1026
+ }
1027
+ `;
1028
+ (_c = documentRef.head) == null ? void 0 : _c.appendChild(style);
1029
+ }
1030
+ }
1031
+ function startPreferenceSync({ refreshOnFocus = true } = {}) {
1032
+ var _a, _b;
1033
+ if (syncCleanup || typeof globalThis.addEventListener !== "function") return syncCleanup || (() => {
1034
+ });
1035
+ const refresh = () => {
1036
+ if (!globalThis.document || globalThis.document.visibilityState === "visible") {
1037
+ getPreferences({ force: true }).catch(() => {
1038
+ });
1039
+ }
1040
+ };
1041
+ const onVisibility = () => refresh();
1042
+ (_b = (_a = globalThis.document) == null ? void 0 : _a.addEventListener) == null ? void 0 : _b.call(_a, "visibilitychange", onVisibility);
1043
+ if (refreshOnFocus) globalThis.addEventListener("focus", refresh);
1044
+ syncCleanup = () => {
1045
+ var _a2, _b2;
1046
+ (_b2 = (_a2 = globalThis.document) == null ? void 0 : _a2.removeEventListener) == null ? void 0 : _b2.call(_a2, "visibilitychange", onVisibility);
1047
+ if (refreshOnFocus) globalThis.removeEventListener("focus", refresh);
1048
+ syncCleanup = null;
1049
+ };
1050
+ return syncCleanup;
1051
+ }
1052
+ function localeFor(language) {
1053
+ const supported = Intl.DateTimeFormat.supportedLocalesOf([language]);
1054
+ return supported[0] || "en";
1055
+ }
1056
+ function fixedOffset(value) {
1057
+ const match = /^UTC([+-])(\d{2}):(\d{2})$/.exec(value || "");
1058
+ if (!match) return null;
1059
+ const minutes = Number(match[2]) * 60 + Number(match[3]);
1060
+ return (match[1] === "-" ? -1 : 1) * minutes;
1061
+ }
1062
+ function formatUserDate(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, options = {}) {
1063
+ const value = normalizePreferences(preferences2);
1064
+ const date = input instanceof Date ? input : new Date(input);
1065
+ const offset = fixedOffset(value.timezone);
1066
+ const adjusted = offset === null ? date : new Date(date.getTime() + offset * 6e4);
1067
+ const timeZone = offset === null ? value.timezone : "UTC";
1068
+ const hasExplicitParts = ["year", "month", "day", "weekday", "hour", "minute", "second", "dateStyle", "timeStyle"].some((key) => options[key] !== void 0);
1069
+ const dateOptions = hasExplicitParts ? options : { year: "numeric", month: "2-digit", day: "2-digit" };
1070
+ const formatter = new Intl.DateTimeFormat(localeFor(value.language), {
1071
+ ...dateOptions,
1072
+ timeZone,
1073
+ ...dateOptions.hour || dateOptions.timeStyle ? { hour12: value.timeFormat === "12h" } : {}
1074
+ });
1075
+ if (!hasExplicitParts) {
1076
+ const parts = Object.fromEntries(formatter.formatToParts(adjusted).map((part) => [part.type, part.value]));
1077
+ if (value.dateFormat === "DD/MM/YYYY") return `${parts.day}/${parts.month}/${parts.year}`;
1078
+ if (value.dateFormat === "YYYY-MM-DD") return `${parts.year}-${parts.month}-${parts.day}`;
1079
+ return `${parts.month}/${parts.day}/${parts.year}`;
1080
+ }
1081
+ return formatter.format(adjusted);
1082
+ }
1083
+ function formatUserNumber(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, options = {}) {
1084
+ const value = normalizePreferences(preferences2);
1085
+ return new Intl.NumberFormat(localeFor(value.language), options).format(input);
1086
+ }
1087
+ function formatUserRelativeTime(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, now = /* @__PURE__ */ new Date()) {
1088
+ const value = normalizePreferences(preferences2);
1089
+ const deltaSeconds = (new Date(input).getTime() - now.getTime()) / 1e3;
1090
+ const units = [
1091
+ ["year", 31536e3],
1092
+ ["month", 2592e3],
1093
+ ["week", 604800],
1094
+ ["day", 86400],
1095
+ ["hour", 3600],
1096
+ ["minute", 60],
1097
+ ["second", 1]
1098
+ ];
1099
+ const [unit, seconds] = units.find(([, size]) => Math.abs(deltaSeconds) >= size) || units.at(-1);
1100
+ return new Intl.RelativeTimeFormat(localeFor(value.language), { numeric: "auto" }).format(Math.round(deltaSeconds / seconds), unit);
1101
+ }
1102
+ var preferences = {
1103
+ defaults: PREFERENCE_DEFAULTS,
1104
+ get: getPreferences,
1105
+ update: updatePreferences,
1106
+ subscribe: subscribePreferences,
1107
+ clearCache: clearPreferenceCache,
1108
+ apply: applyPreferences,
1109
+ startSync: startPreferenceSync,
1110
+ formatDate: formatUserDate,
1111
+ formatNumber: formatUserNumber,
1112
+ formatRelativeTime: formatUserRelativeTime,
1113
+ normalize: normalizePreferences,
1114
+ resolveTheme
1115
+ };
1116
+
777
1117
  // react/AuthProvider.jsx
778
1118
  import React, { createContext, useState, useEffect, useRef } from "react";
779
1119
  var AuthContext = createContext();
@@ -837,7 +1177,8 @@ function AuthProvider({ children, onSessionExpired }) {
837
1177
  console.log("\u{1F4E5} Profile response status:", res.status);
838
1178
  if (!res.ok) throw new Error("Failed to fetch user");
839
1179
  return res.json();
840
- }).then((userData) => {
1180
+ }).then((responseBody) => {
1181
+ const userData = (responseBody == null ? void 0 : responseBody.data) ?? responseBody;
841
1182
  console.log("\u2705 Profile fetched successfully:", userData.email);
842
1183
  setUser(userData);
843
1184
  setSessionValid(true);
@@ -853,13 +1194,16 @@ function AuthProvider({ children, onSessionExpired }) {
853
1194
  const login2 = (clientKey, redirectUri, state) => {
854
1195
  login(clientKey, redirectUri, state);
855
1196
  };
856
- const logout2 = () => {
1197
+ const logout2 = async (options) => {
857
1198
  stopSessionSecurity();
858
1199
  sessionSecurityRef.current = null;
859
- logout();
860
- setUser(null);
861
- setTokenState(null);
862
- setSessionValid(true);
1200
+ try {
1201
+ await logout(options);
1202
+ } finally {
1203
+ setUser(null);
1204
+ setTokenState(null);
1205
+ setSessionValid(true);
1206
+ }
863
1207
  };
864
1208
  const value = {
865
1209
  token,
@@ -981,6 +1325,62 @@ var useSessionMonitor = (options = {}) => {
981
1325
  };
982
1326
  };
983
1327
 
1328
+ // react/PreferencesProvider.jsx
1329
+ import React2, { createContext as createContext2, useContext as useContext2, useEffect as useEffect3, useMemo, useState as useState2 } from "react";
1330
+ var PreferencesContext = createContext2(null);
1331
+ function PreferencesProvider({ children, initialPreferences, applyToDocument = true }) {
1332
+ const [preferences2, setPreferences] = useState2(() => ({
1333
+ ...PREFERENCE_DEFAULTS,
1334
+ ...initialPreferences
1335
+ }));
1336
+ const [loading, setLoading] = useState2(true);
1337
+ const [error, setError] = useState2(null);
1338
+ useEffect3(() => {
1339
+ const unsubscribe = subscribePreferences((next) => {
1340
+ setPreferences(next);
1341
+ setLoading(false);
1342
+ setError(null);
1343
+ });
1344
+ const stopSync = startPreferenceSync();
1345
+ getPreferences().catch((nextError) => {
1346
+ setError(nextError);
1347
+ setLoading(false);
1348
+ });
1349
+ return () => {
1350
+ unsubscribe();
1351
+ stopSync();
1352
+ };
1353
+ }, []);
1354
+ useEffect3(() => {
1355
+ if (applyToDocument) applyPreferences(preferences2);
1356
+ }, [applyToDocument, preferences2]);
1357
+ useEffect3(() => {
1358
+ var _a;
1359
+ if (preferences2.theme !== "system" || typeof matchMedia !== "function") return void 0;
1360
+ const media = matchMedia("(prefers-color-scheme: dark)");
1361
+ const applySystemTheme = () => applyPreferences(preferences2);
1362
+ (_a = media.addEventListener) == null ? void 0 : _a.call(media, "change", applySystemTheme);
1363
+ return () => {
1364
+ var _a2;
1365
+ return (_a2 = media.removeEventListener) == null ? void 0 : _a2.call(media, "change", applySystemTheme);
1366
+ };
1367
+ }, [preferences2]);
1368
+ const value = useMemo(() => ({
1369
+ preferences: preferences2,
1370
+ resolvedTheme: resolveTheme(preferences2.theme),
1371
+ loading,
1372
+ error,
1373
+ refresh: () => getPreferences({ force: true }),
1374
+ update: updatePreferences
1375
+ }), [error, loading, preferences2]);
1376
+ return /* @__PURE__ */ React2.createElement(PreferencesContext.Provider, { value }, children);
1377
+ }
1378
+ function usePreferences() {
1379
+ const context = useContext2(PreferencesContext);
1380
+ if (!context) throw new Error("usePreferences must be used within a PreferencesProvider");
1381
+ return context;
1382
+ }
1383
+
984
1384
  // index.js
985
1385
  var auth = {
986
1386
  // ๐Ÿ”ง Config
@@ -990,6 +1390,8 @@ var auth = {
990
1390
  // ๐Ÿ” Core flows
991
1391
  login,
992
1392
  logout,
1393
+ logoutClient: () => logout({ scope: "client" }),
1394
+ logoutSso: () => logout({ scope: "sso" }),
993
1395
  handleCallback,
994
1396
  refreshToken,
995
1397
  resetCallbackState,
@@ -1009,6 +1411,10 @@ var auth = {
1009
1411
  // โœ… Debug function
1010
1412
  // ๐ŸŒ Authenticated API client
1011
1413
  api: api_default,
1414
+ preferences,
1415
+ // ๐Ÿ”Ž Safe authentication diagnostics
1416
+ getDiagnosticContext,
1417
+ emitAuthDiagnostic,
1012
1418
  // ๐Ÿงช Utilities
1013
1419
  decodeToken,
1014
1420
  isTokenExpired,
@@ -1055,8 +1461,21 @@ var auth = {
1055
1461
  };
1056
1462
  export {
1057
1463
  AuthProvider,
1464
+ PREFERENCE_DEFAULTS,
1465
+ PreferencesProvider,
1466
+ applyPreferences,
1058
1467
  auth,
1468
+ formatUserDate,
1469
+ formatUserNumber,
1470
+ formatUserRelativeTime,
1471
+ getPreferences,
1472
+ normalizePreferences,
1473
+ resolveTheme,
1474
+ startPreferenceSync,
1475
+ subscribePreferences,
1476
+ updatePreferences,
1059
1477
  useAuth,
1478
+ usePreferences,
1060
1479
  useSessionMonitor
1061
1480
  };
1062
1481
  //# sourceMappingURL=index.js.map