@spidy092/auth-client 3.0.9 โ 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/api.cjs +72 -4
- package/dist/api.cjs.map +1 -1
- package/dist/api.js +72 -4
- package/dist/api.js.map +1 -1
- package/dist/index.cjs +419 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +406 -23
- package/dist/index.js.map +1 -1
- package/dist/preferences.cjs +657 -0
- package/dist/preferences.cjs.map +1 -0
- package/dist/preferences.js +608 -0
- package/dist/preferences.js.map +1 -0
- package/dist/react/AuthProvider.cjs +99 -20
- package/dist/react/AuthProvider.cjs.map +1 -1
- package/dist/react/AuthProvider.js +99 -20
- package/dist/react/AuthProvider.js.map +1 -1
- package/dist/react/PreferencesProvider.cjs +621 -0
- package/dist/react/PreferencesProvider.cjs.map +1 -0
- package/dist/react/PreferencesProvider.js +586 -0
- package/dist/react/PreferencesProvider.js.map +1 -0
- package/dist/react/useAuth.cjs.map +1 -1
- package/dist/react/useAuth.js.map +1 -1
- package/dist/react/useSessionMonitor.cjs +343 -26
- package/dist/react/useSessionMonitor.cjs.map +1 -1
- package/dist/react/useSessionMonitor.js +342 -25
- package/dist/react/useSessionMonitor.js.map +1 -1
- package/package.json +10 -2
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);
|
|
@@ -266,8 +279,8 @@ function setConfig(customConfig = {}) {
|
|
|
266
279
|
// โ
Auto-detect router mode
|
|
267
280
|
isRouter: customConfig.isRouter || customConfig.clientKey === "account-ui"
|
|
268
281
|
};
|
|
282
|
+
enableRefreshTokenPersistence(config.persistRefreshToken);
|
|
269
283
|
if (config.persistRefreshToken) {
|
|
270
|
-
enableRefreshTokenPersistence(true);
|
|
271
284
|
console.log("\u{1F4E6} Refresh token persistence ENABLED (localStorage on HTTPS)");
|
|
272
285
|
}
|
|
273
286
|
console.log(`\u{1F527} Auth Client Mode: ${config.isRouter ? "ROUTER" : "CLIENT"}`, {
|
|
@@ -283,6 +296,82 @@ function isRouterMode() {
|
|
|
283
296
|
return config.isRouter;
|
|
284
297
|
}
|
|
285
298
|
|
|
299
|
+
// diagnostics.js
|
|
300
|
+
var STORAGE_KEY = "auth_diagnostic_context";
|
|
301
|
+
var LOGIN_LOCK_KEY = "auth_login_lock";
|
|
302
|
+
var randomId = () => {
|
|
303
|
+
var _a;
|
|
304
|
+
if ((_a = globalThis.crypto) == null ? void 0 : _a.randomUUID) return globalThis.crypto.randomUUID();
|
|
305
|
+
return `auth-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
306
|
+
};
|
|
307
|
+
var fingerprint = async (value) => {
|
|
308
|
+
var _a;
|
|
309
|
+
if (!value || !((_a = globalThis.crypto) == null ? void 0 : _a.subtle)) return null;
|
|
310
|
+
const bytes = new TextEncoder().encode(String(value));
|
|
311
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
|
|
312
|
+
return [...new Uint8Array(digest)].slice(0, 6).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
313
|
+
};
|
|
314
|
+
function getDiagnosticContext() {
|
|
315
|
+
try {
|
|
316
|
+
const existing = JSON.parse(sessionStorage.getItem(STORAGE_KEY) || "null");
|
|
317
|
+
if (existing == null ? void 0 : existing.correlationId) return existing;
|
|
318
|
+
} catch {
|
|
319
|
+
}
|
|
320
|
+
const context = { correlationId: randomId(), startedAt: Date.now() };
|
|
321
|
+
try {
|
|
322
|
+
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(context));
|
|
323
|
+
} catch {
|
|
324
|
+
}
|
|
325
|
+
return context;
|
|
326
|
+
}
|
|
327
|
+
function resetDiagnosticContext() {
|
|
328
|
+
const context = { correlationId: randomId(), startedAt: Date.now() };
|
|
329
|
+
try {
|
|
330
|
+
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(context));
|
|
331
|
+
} catch {
|
|
332
|
+
}
|
|
333
|
+
return context;
|
|
334
|
+
}
|
|
335
|
+
function diagnosticHeaders() {
|
|
336
|
+
const { correlationId } = getDiagnosticContext();
|
|
337
|
+
return { "X-Correlation-ID": correlationId, "X-Request-ID": correlationId };
|
|
338
|
+
}
|
|
339
|
+
function acquireLoginLock(clientKey, redirectUri, ttlMs = 5e3) {
|
|
340
|
+
const now = Date.now();
|
|
341
|
+
const signature = `${clientKey}|${redirectUri}`;
|
|
342
|
+
try {
|
|
343
|
+
const current = JSON.parse(sessionStorage.getItem(LOGIN_LOCK_KEY) || "null");
|
|
344
|
+
if ((current == null ? void 0 : current.signature) === signature && now - current.createdAt < ttlMs) return false;
|
|
345
|
+
sessionStorage.setItem(LOGIN_LOCK_KEY, JSON.stringify({ signature, createdAt: now }));
|
|
346
|
+
} catch {
|
|
347
|
+
}
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
function clearLoginLock() {
|
|
351
|
+
try {
|
|
352
|
+
sessionStorage.removeItem(LOGIN_LOCK_KEY);
|
|
353
|
+
} catch {
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
async function emitAuthDiagnostic(event, outcome, reasonCode, details = {}) {
|
|
357
|
+
const context = getDiagnosticContext();
|
|
358
|
+
const safe = {
|
|
359
|
+
eventType: "auth_client_diagnostic",
|
|
360
|
+
event,
|
|
361
|
+
outcome,
|
|
362
|
+
reasonCode,
|
|
363
|
+
correlationId: context.correlationId,
|
|
364
|
+
elapsedMs: Date.now() - context.startedAt,
|
|
365
|
+
clientKey: details.clientKey || null,
|
|
366
|
+
status: details.status || null,
|
|
367
|
+
stateFingerprint: await fingerprint(details.state),
|
|
368
|
+
online: typeof navigator === "undefined" ? null : navigator.onLine
|
|
369
|
+
};
|
|
370
|
+
const method = outcome === "FAILURE" ? "error" : outcome === "WARNING" ? "warn" : "info";
|
|
371
|
+
console[method]("[auth-client]", safe);
|
|
372
|
+
return safe;
|
|
373
|
+
}
|
|
374
|
+
|
|
286
375
|
// core.js
|
|
287
376
|
var callbackProcessed = false;
|
|
288
377
|
function login(clientKeyArg, redirectUriArg) {
|
|
@@ -295,14 +384,16 @@ function login(clientKeyArg, redirectUriArg) {
|
|
|
295
384
|
} = getConfig();
|
|
296
385
|
const clientKey = clientKeyArg || defaultClientKey;
|
|
297
386
|
const redirectUri = redirectUriArg || defaultRedirectUri;
|
|
298
|
-
console.log("\u{1F504} Smart Login initiated:", {
|
|
299
|
-
mode: isRouterMode() ? "ROUTER" : "CLIENT",
|
|
300
|
-
clientKey,
|
|
301
|
-
redirectUri
|
|
302
|
-
});
|
|
303
387
|
if (!clientKey || !redirectUri) {
|
|
388
|
+
emitAuthDiagnostic("LOGIN_REJECTED", "FAILURE", "CLIENT_CONFIG_MISSING", { clientKey });
|
|
304
389
|
throw new Error("Missing clientKey or redirectUri");
|
|
305
390
|
}
|
|
391
|
+
if (!acquireLoginLock(clientKey, redirectUri)) {
|
|
392
|
+
emitAuthDiagnostic("LOGIN_DUPLICATE_SUPPRESSED", "WARNING", "LOGIN_ALREADY_IN_PROGRESS", { clientKey });
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
resetDiagnosticContext();
|
|
396
|
+
emitAuthDiagnostic("LOGIN_INITIATED", "PENDING", "NONE", { clientKey });
|
|
306
397
|
sessionStorage.setItem("originalApp", clientKey);
|
|
307
398
|
sessionStorage.setItem("returnUrl", redirectUri);
|
|
308
399
|
if (isRouterMode()) {
|
|
@@ -317,13 +408,9 @@ function routerLogin(clientKey, redirectUri) {
|
|
|
317
408
|
if (redirectUri) {
|
|
318
409
|
params.append("redirect_uri", redirectUri);
|
|
319
410
|
}
|
|
411
|
+
params.append("correlation_id", getDiagnosticContext().correlationId);
|
|
320
412
|
const query = params.toString();
|
|
321
413
|
const backendLoginUrl = `${authBaseUrl}/login/${clientKey}${query ? `?${query}` : ""}`;
|
|
322
|
-
console.log("\u{1F3ED} Router Login: Direct backend authentication", {
|
|
323
|
-
clientKey,
|
|
324
|
-
redirectUri,
|
|
325
|
-
backendUrl: backendLoginUrl
|
|
326
|
-
});
|
|
327
414
|
window.location.href = backendLoginUrl;
|
|
328
415
|
}
|
|
329
416
|
function clientLogin(clientKey, redirectUri) {
|
|
@@ -335,11 +422,6 @@ function clientLogin(clientKey, redirectUri) {
|
|
|
335
422
|
params.append("redirect_uri", redirectUri);
|
|
336
423
|
}
|
|
337
424
|
const centralizedLoginUrl = `${accountUiUrl}/login?${params.toString()}`;
|
|
338
|
-
console.log("\u{1F504} Client Login: Redirecting to centralized login", {
|
|
339
|
-
clientKey,
|
|
340
|
-
redirectUri,
|
|
341
|
-
centralizedUrl: centralizedLoginUrl
|
|
342
|
-
});
|
|
343
425
|
window.location.href = centralizedLoginUrl;
|
|
344
426
|
}
|
|
345
427
|
async function logout(options = {}) {
|
|
@@ -362,6 +444,7 @@ async function logout(options = {}) {
|
|
|
362
444
|
method: "POST",
|
|
363
445
|
credentials: "include",
|
|
364
446
|
headers: {
|
|
447
|
+
...diagnosticHeaders(),
|
|
365
448
|
"Authorization": token ? `Bearer ${token}` : "",
|
|
366
449
|
"Content-Type": "application/json"
|
|
367
450
|
},
|
|
@@ -407,11 +490,19 @@ function handleCallback() {
|
|
|
407
490
|
callbackProcessed = false;
|
|
408
491
|
}
|
|
409
492
|
callbackProcessed = true;
|
|
493
|
+
clearLoginLock();
|
|
410
494
|
sessionStorage.removeItem("originalApp");
|
|
411
495
|
sessionStorage.removeItem("returnUrl");
|
|
412
496
|
if (error) {
|
|
413
497
|
const errorDescription = params.get("error_description") || error;
|
|
414
|
-
|
|
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;
|
|
415
506
|
}
|
|
416
507
|
if (accessToken2) {
|
|
417
508
|
setToken(accessToken2);
|
|
@@ -434,9 +525,19 @@ function handleCallback() {
|
|
|
434
525
|
url.searchParams.delete("error_description");
|
|
435
526
|
window.history.replaceState({}, "", url);
|
|
436
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
|
+
});
|
|
437
532
|
return accessToken2;
|
|
438
533
|
}
|
|
439
|
-
|
|
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;
|
|
440
541
|
}
|
|
441
542
|
function resetCallbackState() {
|
|
442
543
|
callbackProcessed = false;
|
|
@@ -451,7 +552,7 @@ async function refreshToken() {
|
|
|
451
552
|
}
|
|
452
553
|
refreshInProgress = true;
|
|
453
554
|
refreshPromise = (async () => {
|
|
454
|
-
var _a, _b, _c, _d;
|
|
555
|
+
var _a, _b, _c, _d, _e, _f;
|
|
455
556
|
try {
|
|
456
557
|
const storedRefreshToken = getRefreshToken();
|
|
457
558
|
console.log("\u{1F504} Refreshing token:", {
|
|
@@ -464,6 +565,7 @@ async function refreshToken() {
|
|
|
464
565
|
credentials: "include",
|
|
465
566
|
// โ
Include httpOnly cookies (for HTTPS)
|
|
466
567
|
headers: {
|
|
568
|
+
...diagnosticHeaders(),
|
|
467
569
|
"Content-Type": "application/json"
|
|
468
570
|
}
|
|
469
571
|
};
|
|
@@ -474,8 +576,19 @@ async function refreshToken() {
|
|
|
474
576
|
const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
|
|
475
577
|
if (!response.ok) {
|
|
476
578
|
const errorText = await response.text();
|
|
477
|
-
|
|
478
|
-
|
|
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;
|
|
479
592
|
}
|
|
480
593
|
const data = await response.json();
|
|
481
594
|
const { access_token, refresh_token: new_refresh_token } = data;
|
|
@@ -488,10 +601,11 @@ async function refreshToken() {
|
|
|
488
601
|
console.log("\u{1F504} New refresh token stored from rotation");
|
|
489
602
|
}
|
|
490
603
|
console.log("\u2705 Token refresh successful, listeners notified");
|
|
604
|
+
emitAuthDiagnostic("TOKEN_REFRESH_COMPLETED", "SUCCESS", "NONE", { clientKey });
|
|
491
605
|
return access_token;
|
|
492
606
|
} catch (err) {
|
|
493
607
|
console.error("\u274C Token refresh error:", err);
|
|
494
|
-
const isAuthRejection = ((
|
|
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"));
|
|
495
609
|
if (isAuthRejection) {
|
|
496
610
|
clearToken();
|
|
497
611
|
clearRefreshToken();
|
|
@@ -514,6 +628,7 @@ async function validateCurrentSession() {
|
|
|
514
628
|
const response = await fetch(`${authBaseUrl}/account/validate-session`, {
|
|
515
629
|
method: "GET",
|
|
516
630
|
headers: {
|
|
631
|
+
...diagnosticHeaders(),
|
|
517
632
|
"Authorization": `Bearer ${token}`,
|
|
518
633
|
"Content-Type": "application/json"
|
|
519
634
|
},
|
|
@@ -766,6 +881,7 @@ api.interceptors.request.use((config2) => {
|
|
|
766
881
|
if ((runtimeConfig == null ? void 0 : runtimeConfig.clientKey) && !config2.headers["X-Client-Key"]) {
|
|
767
882
|
config2.headers["X-Client-Key"] = runtimeConfig.clientKey;
|
|
768
883
|
}
|
|
884
|
+
Object.assign(config2.headers, diagnosticHeaders());
|
|
769
885
|
const token = getToken();
|
|
770
886
|
if (token) {
|
|
771
887
|
config2.headers.Authorization = `Bearer ${token}`;
|
|
@@ -784,6 +900,10 @@ api.interceptors.response.use(
|
|
|
784
900
|
return Promise.reject(error);
|
|
785
901
|
}
|
|
786
902
|
config2._retry = true;
|
|
903
|
+
emitAuthDiagnostic("API_401_REFRESH_STARTED", "PENDING", "HTTP_401", {
|
|
904
|
+
clientKey: getConfig().clientKey,
|
|
905
|
+
status: 401
|
|
906
|
+
});
|
|
787
907
|
if (!refreshPromise2) {
|
|
788
908
|
refreshPromise2 = refreshToken().then((newToken) => {
|
|
789
909
|
refreshPromise2 = null;
|
|
@@ -843,6 +963,208 @@ function isAuthenticated() {
|
|
|
843
963
|
return !!token && !isTokenExpired(token, 10);
|
|
844
964
|
}
|
|
845
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
|
+
|
|
846
1168
|
// react/AuthProvider.jsx
|
|
847
1169
|
var import_react = __toESM(require("react"), 1);
|
|
848
1170
|
var AuthContext = (0, import_react.createContext)();
|
|
@@ -906,7 +1228,8 @@ function AuthProvider({ children, onSessionExpired }) {
|
|
|
906
1228
|
console.log("\u{1F4E5} Profile response status:", res.status);
|
|
907
1229
|
if (!res.ok) throw new Error("Failed to fetch user");
|
|
908
1230
|
return res.json();
|
|
909
|
-
}).then((
|
|
1231
|
+
}).then((responseBody) => {
|
|
1232
|
+
const userData = (responseBody == null ? void 0 : responseBody.data) ?? responseBody;
|
|
910
1233
|
console.log("\u2705 Profile fetched successfully:", userData.email);
|
|
911
1234
|
setUser(userData);
|
|
912
1235
|
setSessionValid(true);
|
|
@@ -1053,6 +1376,62 @@ var useSessionMonitor = (options = {}) => {
|
|
|
1053
1376
|
};
|
|
1054
1377
|
};
|
|
1055
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
|
+
|
|
1056
1435
|
// index.js
|
|
1057
1436
|
var auth = {
|
|
1058
1437
|
// ๐ง Config
|
|
@@ -1083,6 +1462,10 @@ var auth = {
|
|
|
1083
1462
|
// โ
Debug function
|
|
1084
1463
|
// ๐ Authenticated API client
|
|
1085
1464
|
api: api_default,
|
|
1465
|
+
preferences,
|
|
1466
|
+
// ๐ Safe authentication diagnostics
|
|
1467
|
+
getDiagnosticContext,
|
|
1468
|
+
emitAuthDiagnostic,
|
|
1086
1469
|
// ๐งช Utilities
|
|
1087
1470
|
decodeToken,
|
|
1088
1471
|
isTokenExpired,
|
|
@@ -1130,8 +1513,21 @@ var auth = {
|
|
|
1130
1513
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1131
1514
|
0 && (module.exports = {
|
|
1132
1515
|
AuthProvider,
|
|
1516
|
+
PREFERENCE_DEFAULTS,
|
|
1517
|
+
PreferencesProvider,
|
|
1518
|
+
applyPreferences,
|
|
1133
1519
|
auth,
|
|
1520
|
+
formatUserDate,
|
|
1521
|
+
formatUserNumber,
|
|
1522
|
+
formatUserRelativeTime,
|
|
1523
|
+
getPreferences,
|
|
1524
|
+
normalizePreferences,
|
|
1525
|
+
resolveTheme,
|
|
1526
|
+
startPreferenceSync,
|
|
1527
|
+
subscribePreferences,
|
|
1528
|
+
updatePreferences,
|
|
1134
1529
|
useAuth,
|
|
1530
|
+
usePreferences,
|
|
1135
1531
|
useSessionMonitor
|
|
1136
1532
|
});
|
|
1137
1533
|
//# sourceMappingURL=index.cjs.map
|