@spidy092/auth-client 3.0.9 โ 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.cjs +72 -20
- package/dist/api.cjs.map +1 -1
- package/dist/api.js +72 -20
- package/dist/api.js.map +1 -1
- package/dist/index.cjs +436 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +423 -38
- package/dist/index.js.map +1 -1
- package/dist/preferences.cjs +641 -0
- package/dist/preferences.cjs.map +1 -0
- package/dist/preferences.js +592 -0
- package/dist/preferences.js.map +1 -0
- package/dist/react/AuthProvider.cjs +111 -40
- package/dist/react/AuthProvider.cjs.map +1 -1
- package/dist/react/AuthProvider.js +111 -40
- package/dist/react/AuthProvider.js.map +1 -1
- package/dist/react/PreferencesProvider.cjs +605 -0
- package/dist/react/PreferencesProvider.cjs.map +1 -0
- package/dist/react/PreferencesProvider.js +570 -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 +360 -41
- package/dist/react/useSessionMonitor.cjs.map +1 -1
- package/dist/react/useSessionMonitor.js +359 -40
- 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);
|
|
@@ -149,7 +162,7 @@ function shouldUseLocalStorage() {
|
|
|
149
162
|
return false;
|
|
150
163
|
}
|
|
151
164
|
}
|
|
152
|
-
function
|
|
165
|
+
function setRefreshToken2(token) {
|
|
153
166
|
if (!token) {
|
|
154
167
|
clearRefreshToken();
|
|
155
168
|
return;
|
|
@@ -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 = {}) {
|
|
@@ -353,6 +435,7 @@ async function logout(options = {}) {
|
|
|
353
435
|
clientKey,
|
|
354
436
|
scope
|
|
355
437
|
});
|
|
438
|
+
emitAuthDiagnostic("LOGOUT_INITIATED", "PENDING", "NONE", { clientKey });
|
|
356
439
|
clearToken();
|
|
357
440
|
clearRefreshToken();
|
|
358
441
|
sessionStorage.removeItem("originalApp");
|
|
@@ -362,6 +445,7 @@ async function logout(options = {}) {
|
|
|
362
445
|
method: "POST",
|
|
363
446
|
credentials: "include",
|
|
364
447
|
headers: {
|
|
448
|
+
...diagnosticHeaders(),
|
|
365
449
|
"Authorization": token ? `Bearer ${token}` : "",
|
|
366
450
|
"Content-Type": "application/json"
|
|
367
451
|
},
|
|
@@ -372,16 +456,23 @@ async function logout(options = {}) {
|
|
|
372
456
|
}
|
|
373
457
|
const data = await response.json();
|
|
374
458
|
console.log("\u2705 Logout response:", data);
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
}
|
|
459
|
+
emitAuthDiagnostic("LOGOUT_COMPLETED", "SUCCESS", "NONE", {
|
|
460
|
+
clientKey,
|
|
461
|
+
status: response.status
|
|
462
|
+
});
|
|
379
463
|
if (data == null ? void 0 : data.keycloakLogoutUrl) {
|
|
380
464
|
window.location.replace(data.keycloakLogoutUrl);
|
|
381
465
|
return;
|
|
382
466
|
}
|
|
467
|
+
if (data == null ? void 0 : data.logoutRedirectUrl) {
|
|
468
|
+
window.location.replace(data.logoutRedirectUrl);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
383
471
|
} catch (error) {
|
|
384
472
|
console.warn("\u26A0\uFE0F Logout backend call failed:", error);
|
|
473
|
+
emitAuthDiagnostic("LOGOUT_BACKEND_FAILED", "FAILURE", "LOGOUT_REQUEST_FAILED", {
|
|
474
|
+
clientKey
|
|
475
|
+
});
|
|
385
476
|
}
|
|
386
477
|
const fallbackUrl = isRouterMode() ? new URL("/login", window.location.origin) : new URL("/login", accountUiUrl);
|
|
387
478
|
fallbackUrl.searchParams.set("logged_out", "true");
|
|
@@ -390,7 +481,6 @@ async function logout(options = {}) {
|
|
|
390
481
|
window.location.replace(fallbackUrl.toString());
|
|
391
482
|
}
|
|
392
483
|
function handleCallback() {
|
|
393
|
-
var _a;
|
|
394
484
|
const params = new URLSearchParams(window.location.search);
|
|
395
485
|
const accessToken2 = params.get("access_token");
|
|
396
486
|
const error = params.get("error");
|
|
@@ -407,24 +497,27 @@ function handleCallback() {
|
|
|
407
497
|
callbackProcessed = false;
|
|
408
498
|
}
|
|
409
499
|
callbackProcessed = true;
|
|
500
|
+
clearLoginLock();
|
|
410
501
|
sessionStorage.removeItem("originalApp");
|
|
411
502
|
sessionStorage.removeItem("returnUrl");
|
|
412
503
|
if (error) {
|
|
413
504
|
const errorDescription = params.get("error_description") || error;
|
|
414
|
-
|
|
505
|
+
const authError = new Error(`Authentication failed: ${errorDescription}`);
|
|
506
|
+
authError.code = error;
|
|
507
|
+
authError.correlationId = getDiagnosticContext().correlationId;
|
|
508
|
+
emitAuthDiagnostic("CALLBACK_REJECTED", "FAILURE", error.toUpperCase(), {
|
|
509
|
+
clientKey: getConfig().clientKey,
|
|
510
|
+
state: params.get("state")
|
|
511
|
+
});
|
|
512
|
+
throw authError;
|
|
415
513
|
}
|
|
416
514
|
if (accessToken2) {
|
|
417
515
|
setToken(accessToken2);
|
|
418
516
|
const refreshTokenInUrl = params.get("refresh_token");
|
|
419
517
|
if (refreshTokenInUrl) {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
console.log(`\u{1F4E6} Storing refresh token from callback URL (${persistRefreshToken ? "persistence enabled" : "HTTP dev mode"})`);
|
|
424
|
-
setRefreshToken(refreshTokenInUrl);
|
|
425
|
-
} else {
|
|
426
|
-
console.log("\u{1F512} HTTPS mode: Refresh token is in httpOnly cookie (ignoring URL param)");
|
|
427
|
-
}
|
|
518
|
+
emitAuthDiagnostic("CALLBACK_REFRESH_TOKEN_IGNORED", "WARNING", "REFRESH_TOKEN_IN_URL", {
|
|
519
|
+
clientKey: getConfig().clientKey
|
|
520
|
+
});
|
|
428
521
|
}
|
|
429
522
|
const url = new URL(window.location);
|
|
430
523
|
url.searchParams.delete("access_token");
|
|
@@ -434,9 +527,19 @@ function handleCallback() {
|
|
|
434
527
|
url.searchParams.delete("error_description");
|
|
435
528
|
window.history.replaceState({}, "", url);
|
|
436
529
|
console.log("\u2705 Callback processed successfully, token stored");
|
|
530
|
+
emitAuthDiagnostic("CALLBACK_COMPLETED", "SUCCESS", "NONE", {
|
|
531
|
+
clientKey: getConfig().clientKey,
|
|
532
|
+
state: params.get("state")
|
|
533
|
+
});
|
|
437
534
|
return accessToken2;
|
|
438
535
|
}
|
|
439
|
-
|
|
536
|
+
emitAuthDiagnostic("CALLBACK_REJECTED", "FAILURE", "ACCESS_TOKEN_MISSING", {
|
|
537
|
+
clientKey: getConfig().clientKey,
|
|
538
|
+
state: params.get("state")
|
|
539
|
+
});
|
|
540
|
+
const missingTokenError = new Error("No access token found in callback URL");
|
|
541
|
+
missingTokenError.code = "ACCESS_TOKEN_MISSING";
|
|
542
|
+
throw missingTokenError;
|
|
440
543
|
}
|
|
441
544
|
function resetCallbackState() {
|
|
442
545
|
callbackProcessed = false;
|
|
@@ -451,7 +554,7 @@ async function refreshToken() {
|
|
|
451
554
|
}
|
|
452
555
|
refreshInProgress = true;
|
|
453
556
|
refreshPromise = (async () => {
|
|
454
|
-
var _a, _b, _c, _d;
|
|
557
|
+
var _a, _b, _c, _d, _e, _f;
|
|
455
558
|
try {
|
|
456
559
|
const storedRefreshToken = getRefreshToken();
|
|
457
560
|
console.log("\u{1F504} Refreshing token:", {
|
|
@@ -464,6 +567,7 @@ async function refreshToken() {
|
|
|
464
567
|
credentials: "include",
|
|
465
568
|
// โ
Include httpOnly cookies (for HTTPS)
|
|
466
569
|
headers: {
|
|
570
|
+
...diagnosticHeaders(),
|
|
467
571
|
"Content-Type": "application/json"
|
|
468
572
|
}
|
|
469
573
|
};
|
|
@@ -474,8 +578,19 @@ async function refreshToken() {
|
|
|
474
578
|
const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
|
|
475
579
|
if (!response.ok) {
|
|
476
580
|
const errorText = await response.text();
|
|
477
|
-
|
|
478
|
-
|
|
581
|
+
let serverCode = null;
|
|
582
|
+
try {
|
|
583
|
+
serverCode = ((_a = JSON.parse(errorText)) == null ? void 0 : _a.error) || ((_b = JSON.parse(errorText)) == null ? void 0 : _b.code);
|
|
584
|
+
} catch {
|
|
585
|
+
}
|
|
586
|
+
emitAuthDiagnostic("TOKEN_REFRESH_REJECTED", "FAILURE", serverCode || `HTTP_${response.status}`, {
|
|
587
|
+
clientKey,
|
|
588
|
+
status: response.status
|
|
589
|
+
});
|
|
590
|
+
const refreshError = new Error(`Refresh failed: ${response.status}`);
|
|
591
|
+
refreshError.code = serverCode || `HTTP_${response.status}`;
|
|
592
|
+
refreshError.status = response.status;
|
|
593
|
+
throw refreshError;
|
|
479
594
|
}
|
|
480
595
|
const data = await response.json();
|
|
481
596
|
const { access_token, refresh_token: new_refresh_token } = data;
|
|
@@ -488,10 +603,11 @@ async function refreshToken() {
|
|
|
488
603
|
console.log("\u{1F504} New refresh token stored from rotation");
|
|
489
604
|
}
|
|
490
605
|
console.log("\u2705 Token refresh successful, listeners notified");
|
|
606
|
+
emitAuthDiagnostic("TOKEN_REFRESH_COMPLETED", "SUCCESS", "NONE", { clientKey });
|
|
491
607
|
return access_token;
|
|
492
608
|
} catch (err) {
|
|
493
609
|
console.error("\u274C Token refresh error:", err);
|
|
494
|
-
const isAuthRejection = ((
|
|
610
|
+
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
611
|
if (isAuthRejection) {
|
|
496
612
|
clearToken();
|
|
497
613
|
clearRefreshToken();
|
|
@@ -514,6 +630,7 @@ async function validateCurrentSession() {
|
|
|
514
630
|
const response = await fetch(`${authBaseUrl}/account/validate-session`, {
|
|
515
631
|
method: "GET",
|
|
516
632
|
headers: {
|
|
633
|
+
...diagnosticHeaders(),
|
|
517
634
|
"Authorization": `Bearer ${token}`,
|
|
518
635
|
"Content-Type": "application/json"
|
|
519
636
|
},
|
|
@@ -766,6 +883,7 @@ api.interceptors.request.use((config2) => {
|
|
|
766
883
|
if ((runtimeConfig == null ? void 0 : runtimeConfig.clientKey) && !config2.headers["X-Client-Key"]) {
|
|
767
884
|
config2.headers["X-Client-Key"] = runtimeConfig.clientKey;
|
|
768
885
|
}
|
|
886
|
+
Object.assign(config2.headers, diagnosticHeaders());
|
|
769
887
|
const token = getToken();
|
|
770
888
|
if (token) {
|
|
771
889
|
config2.headers.Authorization = `Bearer ${token}`;
|
|
@@ -784,6 +902,10 @@ api.interceptors.response.use(
|
|
|
784
902
|
return Promise.reject(error);
|
|
785
903
|
}
|
|
786
904
|
config2._retry = true;
|
|
905
|
+
emitAuthDiagnostic("API_401_REFRESH_STARTED", "PENDING", "HTTP_401", {
|
|
906
|
+
clientKey: getConfig().clientKey,
|
|
907
|
+
status: 401
|
|
908
|
+
});
|
|
787
909
|
if (!refreshPromise2) {
|
|
788
910
|
refreshPromise2 = refreshToken().then((newToken) => {
|
|
789
911
|
refreshPromise2 = null;
|
|
@@ -843,6 +965,208 @@ function isAuthenticated() {
|
|
|
843
965
|
return !!token && !isTokenExpired(token, 10);
|
|
844
966
|
}
|
|
845
967
|
|
|
968
|
+
// preferences.js
|
|
969
|
+
var PREFERENCE_DEFAULTS = Object.freeze({
|
|
970
|
+
theme: "system",
|
|
971
|
+
colorScheme: "blue",
|
|
972
|
+
density: "comfortable",
|
|
973
|
+
reducedMotion: false,
|
|
974
|
+
language: "en",
|
|
975
|
+
timezone: "Etc/UTC",
|
|
976
|
+
dateFormat: "MM/DD/YYYY",
|
|
977
|
+
timeFormat: "12h",
|
|
978
|
+
highContrast: false,
|
|
979
|
+
largeText: false
|
|
980
|
+
});
|
|
981
|
+
var ALLOWED = {
|
|
982
|
+
theme: /* @__PURE__ */ new Set(["light", "dark", "system"]),
|
|
983
|
+
density: /* @__PURE__ */ new Set(["compact", "comfortable", "spacious"]),
|
|
984
|
+
timeFormat: /* @__PURE__ */ new Set(["12h", "24h"])
|
|
985
|
+
};
|
|
986
|
+
var listeners2 = /* @__PURE__ */ new Set();
|
|
987
|
+
var cachedPreferences = null;
|
|
988
|
+
var pendingRequest = null;
|
|
989
|
+
var syncCleanup = null;
|
|
990
|
+
function extractResponseData(response) {
|
|
991
|
+
var _a;
|
|
992
|
+
return ((_a = response == null ? void 0 : response.data) == null ? void 0 : _a.data) ?? (response == null ? void 0 : response.data) ?? response ?? {};
|
|
993
|
+
}
|
|
994
|
+
function normalizePreferences(value = {}) {
|
|
995
|
+
const next = { ...PREFERENCE_DEFAULTS };
|
|
996
|
+
for (const key of Object.keys(PREFERENCE_DEFAULTS)) {
|
|
997
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
998
|
+
const candidate = value[key];
|
|
999
|
+
if (ALLOWED[key] && !ALLOWED[key].has(candidate)) continue;
|
|
1000
|
+
if (typeof PREFERENCE_DEFAULTS[key] !== typeof candidate) continue;
|
|
1001
|
+
next[key] = candidate;
|
|
1002
|
+
}
|
|
1003
|
+
return next;
|
|
1004
|
+
}
|
|
1005
|
+
function resolveTheme(theme, matchMedia2 = globalThis.matchMedia) {
|
|
1006
|
+
if (theme === "dark" || theme === "light") return theme;
|
|
1007
|
+
return typeof matchMedia2 === "function" && matchMedia2("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
1008
|
+
}
|
|
1009
|
+
function announce(preferences2) {
|
|
1010
|
+
cachedPreferences = normalizePreferences(preferences2);
|
|
1011
|
+
listeners2.forEach((listener) => listener(cachedPreferences));
|
|
1012
|
+
return cachedPreferences;
|
|
1013
|
+
}
|
|
1014
|
+
async function getPreferences({ force = false } = {}) {
|
|
1015
|
+
if (cachedPreferences && !force) return cachedPreferences;
|
|
1016
|
+
if (pendingRequest) return pendingRequest;
|
|
1017
|
+
pendingRequest = api_default.get("/account/preferences").then((response) => announce(extractResponseData(response))).finally(() => {
|
|
1018
|
+
pendingRequest = null;
|
|
1019
|
+
});
|
|
1020
|
+
return pendingRequest;
|
|
1021
|
+
}
|
|
1022
|
+
async function updatePreferences(patch) {
|
|
1023
|
+
const allowedPatch = {};
|
|
1024
|
+
for (const key of Object.keys(PREFERENCE_DEFAULTS)) {
|
|
1025
|
+
if (Object.hasOwn(patch, key)) allowedPatch[key] = patch[key];
|
|
1026
|
+
}
|
|
1027
|
+
const candidate = normalizePreferences({ ...cachedPreferences || PREFERENCE_DEFAULTS, ...allowedPatch });
|
|
1028
|
+
const response = await api_default.put("/account/preferences", allowedPatch);
|
|
1029
|
+
const saved = extractResponseData(response);
|
|
1030
|
+
return announce(Object.keys(saved).length ? saved : candidate);
|
|
1031
|
+
}
|
|
1032
|
+
function subscribePreferences(listener) {
|
|
1033
|
+
listeners2.add(listener);
|
|
1034
|
+
if (cachedPreferences) listener(cachedPreferences);
|
|
1035
|
+
return () => listeners2.delete(listener);
|
|
1036
|
+
}
|
|
1037
|
+
function clearPreferenceCache() {
|
|
1038
|
+
cachedPreferences = null;
|
|
1039
|
+
pendingRequest = null;
|
|
1040
|
+
}
|
|
1041
|
+
function applyPreferences(preferences2, documentRef = globalThis.document) {
|
|
1042
|
+
var _a, _b, _c;
|
|
1043
|
+
if (!(documentRef == null ? void 0 : documentRef.documentElement)) return;
|
|
1044
|
+
const value = normalizePreferences(preferences2);
|
|
1045
|
+
const root = documentRef.documentElement;
|
|
1046
|
+
const resolvedTheme = resolveTheme(value.theme, (_b = (_a = documentRef.defaultView) == null ? void 0 : _a.matchMedia) == null ? void 0 : _b.bind(documentRef.defaultView));
|
|
1047
|
+
root.dataset.theme = resolvedTheme;
|
|
1048
|
+
root.dataset.themePreference = value.theme;
|
|
1049
|
+
root.dataset.colorScheme = value.colorScheme;
|
|
1050
|
+
root.dataset.density = value.density;
|
|
1051
|
+
root.lang = value.language;
|
|
1052
|
+
root.style.colorScheme = resolvedTheme;
|
|
1053
|
+
root.style.setProperty("--user-accent-color", {
|
|
1054
|
+
blue: "#1976d2",
|
|
1055
|
+
green: "#2e7d32",
|
|
1056
|
+
purple: "#7b1fa2",
|
|
1057
|
+
orange: "#c65d00",
|
|
1058
|
+
red: "#c62828"
|
|
1059
|
+
}[value.colorScheme] || "#1976d2");
|
|
1060
|
+
root.classList.toggle("user-reduced-motion", value.reducedMotion);
|
|
1061
|
+
root.classList.toggle("user-high-contrast", value.highContrast);
|
|
1062
|
+
root.classList.toggle("user-large-text", value.largeText);
|
|
1063
|
+
const styleId = "sso-user-preference-styles";
|
|
1064
|
+
if (!documentRef.getElementById(styleId)) {
|
|
1065
|
+
const style = documentRef.createElement("style");
|
|
1066
|
+
style.id = styleId;
|
|
1067
|
+
style.textContent = `
|
|
1068
|
+
html.user-large-text { font-size: 112.5%; }
|
|
1069
|
+
html.user-high-contrast { filter: contrast(1.15); }
|
|
1070
|
+
html[data-density="compact"] { --user-density-padding: 6px; }
|
|
1071
|
+
html[data-density="comfortable"] { --user-density-padding: 10px; }
|
|
1072
|
+
html[data-density="spacious"] { --user-density-padding: 14px; }
|
|
1073
|
+
html[data-density] .MuiListItem-root { padding-top: var(--user-density-padding); padding-bottom: var(--user-density-padding); }
|
|
1074
|
+
html.user-reduced-motion *, html.user-reduced-motion *::before, html.user-reduced-motion *::after {
|
|
1075
|
+
animation-duration: 0.01ms !important;
|
|
1076
|
+
animation-iteration-count: 1 !important;
|
|
1077
|
+
scroll-behavior: auto !important;
|
|
1078
|
+
transition-duration: 0.01ms !important;
|
|
1079
|
+
}
|
|
1080
|
+
`;
|
|
1081
|
+
(_c = documentRef.head) == null ? void 0 : _c.appendChild(style);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
function startPreferenceSync({ refreshOnFocus = true } = {}) {
|
|
1085
|
+
var _a, _b;
|
|
1086
|
+
if (syncCleanup || typeof globalThis.addEventListener !== "function") return syncCleanup || (() => {
|
|
1087
|
+
});
|
|
1088
|
+
const refresh = () => {
|
|
1089
|
+
if (!globalThis.document || globalThis.document.visibilityState === "visible") {
|
|
1090
|
+
getPreferences({ force: true }).catch(() => {
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
};
|
|
1094
|
+
const onVisibility = () => refresh();
|
|
1095
|
+
(_b = (_a = globalThis.document) == null ? void 0 : _a.addEventListener) == null ? void 0 : _b.call(_a, "visibilitychange", onVisibility);
|
|
1096
|
+
if (refreshOnFocus) globalThis.addEventListener("focus", refresh);
|
|
1097
|
+
syncCleanup = () => {
|
|
1098
|
+
var _a2, _b2;
|
|
1099
|
+
(_b2 = (_a2 = globalThis.document) == null ? void 0 : _a2.removeEventListener) == null ? void 0 : _b2.call(_a2, "visibilitychange", onVisibility);
|
|
1100
|
+
if (refreshOnFocus) globalThis.removeEventListener("focus", refresh);
|
|
1101
|
+
syncCleanup = null;
|
|
1102
|
+
};
|
|
1103
|
+
return syncCleanup;
|
|
1104
|
+
}
|
|
1105
|
+
function localeFor(language) {
|
|
1106
|
+
const supported = Intl.DateTimeFormat.supportedLocalesOf([language]);
|
|
1107
|
+
return supported[0] || "en";
|
|
1108
|
+
}
|
|
1109
|
+
function fixedOffset(value) {
|
|
1110
|
+
const match = /^UTC([+-])(\d{2}):(\d{2})$/.exec(value || "");
|
|
1111
|
+
if (!match) return null;
|
|
1112
|
+
const minutes = Number(match[2]) * 60 + Number(match[3]);
|
|
1113
|
+
return (match[1] === "-" ? -1 : 1) * minutes;
|
|
1114
|
+
}
|
|
1115
|
+
function formatUserDate(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, options = {}) {
|
|
1116
|
+
const value = normalizePreferences(preferences2);
|
|
1117
|
+
const date = input instanceof Date ? input : new Date(input);
|
|
1118
|
+
const offset = fixedOffset(value.timezone);
|
|
1119
|
+
const adjusted = offset === null ? date : new Date(date.getTime() + offset * 6e4);
|
|
1120
|
+
const timeZone = offset === null ? value.timezone : "UTC";
|
|
1121
|
+
const hasExplicitParts = ["year", "month", "day", "weekday", "hour", "minute", "second", "dateStyle", "timeStyle"].some((key) => options[key] !== void 0);
|
|
1122
|
+
const dateOptions = hasExplicitParts ? options : { year: "numeric", month: "2-digit", day: "2-digit" };
|
|
1123
|
+
const formatter = new Intl.DateTimeFormat(localeFor(value.language), {
|
|
1124
|
+
...dateOptions,
|
|
1125
|
+
timeZone,
|
|
1126
|
+
...dateOptions.hour || dateOptions.timeStyle ? { hour12: value.timeFormat === "12h" } : {}
|
|
1127
|
+
});
|
|
1128
|
+
if (!hasExplicitParts) {
|
|
1129
|
+
const parts = Object.fromEntries(formatter.formatToParts(adjusted).map((part) => [part.type, part.value]));
|
|
1130
|
+
if (value.dateFormat === "DD/MM/YYYY") return `${parts.day}/${parts.month}/${parts.year}`;
|
|
1131
|
+
if (value.dateFormat === "YYYY-MM-DD") return `${parts.year}-${parts.month}-${parts.day}`;
|
|
1132
|
+
return `${parts.month}/${parts.day}/${parts.year}`;
|
|
1133
|
+
}
|
|
1134
|
+
return formatter.format(adjusted);
|
|
1135
|
+
}
|
|
1136
|
+
function formatUserNumber(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, options = {}) {
|
|
1137
|
+
const value = normalizePreferences(preferences2);
|
|
1138
|
+
return new Intl.NumberFormat(localeFor(value.language), options).format(input);
|
|
1139
|
+
}
|
|
1140
|
+
function formatUserRelativeTime(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, now = /* @__PURE__ */ new Date()) {
|
|
1141
|
+
const value = normalizePreferences(preferences2);
|
|
1142
|
+
const deltaSeconds = (new Date(input).getTime() - now.getTime()) / 1e3;
|
|
1143
|
+
const units = [
|
|
1144
|
+
["year", 31536e3],
|
|
1145
|
+
["month", 2592e3],
|
|
1146
|
+
["week", 604800],
|
|
1147
|
+
["day", 86400],
|
|
1148
|
+
["hour", 3600],
|
|
1149
|
+
["minute", 60],
|
|
1150
|
+
["second", 1]
|
|
1151
|
+
];
|
|
1152
|
+
const [unit, seconds] = units.find(([, size]) => Math.abs(deltaSeconds) >= size) || units.at(-1);
|
|
1153
|
+
return new Intl.RelativeTimeFormat(localeFor(value.language), { numeric: "auto" }).format(Math.round(deltaSeconds / seconds), unit);
|
|
1154
|
+
}
|
|
1155
|
+
var preferences = {
|
|
1156
|
+
defaults: PREFERENCE_DEFAULTS,
|
|
1157
|
+
get: getPreferences,
|
|
1158
|
+
update: updatePreferences,
|
|
1159
|
+
subscribe: subscribePreferences,
|
|
1160
|
+
clearCache: clearPreferenceCache,
|
|
1161
|
+
apply: applyPreferences,
|
|
1162
|
+
startSync: startPreferenceSync,
|
|
1163
|
+
formatDate: formatUserDate,
|
|
1164
|
+
formatNumber: formatUserNumber,
|
|
1165
|
+
formatRelativeTime: formatUserRelativeTime,
|
|
1166
|
+
normalize: normalizePreferences,
|
|
1167
|
+
resolveTheme
|
|
1168
|
+
};
|
|
1169
|
+
|
|
846
1170
|
// react/AuthProvider.jsx
|
|
847
1171
|
var import_react = __toESM(require("react"), 1);
|
|
848
1172
|
var AuthContext = (0, import_react.createContext)();
|
|
@@ -906,7 +1230,8 @@ function AuthProvider({ children, onSessionExpired }) {
|
|
|
906
1230
|
console.log("\u{1F4E5} Profile response status:", res.status);
|
|
907
1231
|
if (!res.ok) throw new Error("Failed to fetch user");
|
|
908
1232
|
return res.json();
|
|
909
|
-
}).then((
|
|
1233
|
+
}).then((responseBody) => {
|
|
1234
|
+
const userData = (responseBody == null ? void 0 : responseBody.data) ?? responseBody;
|
|
910
1235
|
console.log("\u2705 Profile fetched successfully:", userData.email);
|
|
911
1236
|
setUser(userData);
|
|
912
1237
|
setSessionValid(true);
|
|
@@ -1053,6 +1378,62 @@ var useSessionMonitor = (options = {}) => {
|
|
|
1053
1378
|
};
|
|
1054
1379
|
};
|
|
1055
1380
|
|
|
1381
|
+
// react/PreferencesProvider.jsx
|
|
1382
|
+
var import_react4 = __toESM(require("react"), 1);
|
|
1383
|
+
var PreferencesContext = (0, import_react4.createContext)(null);
|
|
1384
|
+
function PreferencesProvider({ children, initialPreferences, applyToDocument = true }) {
|
|
1385
|
+
const [preferences2, setPreferences] = (0, import_react4.useState)(() => ({
|
|
1386
|
+
...PREFERENCE_DEFAULTS,
|
|
1387
|
+
...initialPreferences
|
|
1388
|
+
}));
|
|
1389
|
+
const [loading, setLoading] = (0, import_react4.useState)(true);
|
|
1390
|
+
const [error, setError] = (0, import_react4.useState)(null);
|
|
1391
|
+
(0, import_react4.useEffect)(() => {
|
|
1392
|
+
const unsubscribe = subscribePreferences((next) => {
|
|
1393
|
+
setPreferences(next);
|
|
1394
|
+
setLoading(false);
|
|
1395
|
+
setError(null);
|
|
1396
|
+
});
|
|
1397
|
+
const stopSync = startPreferenceSync();
|
|
1398
|
+
getPreferences().catch((nextError) => {
|
|
1399
|
+
setError(nextError);
|
|
1400
|
+
setLoading(false);
|
|
1401
|
+
});
|
|
1402
|
+
return () => {
|
|
1403
|
+
unsubscribe();
|
|
1404
|
+
stopSync();
|
|
1405
|
+
};
|
|
1406
|
+
}, []);
|
|
1407
|
+
(0, import_react4.useEffect)(() => {
|
|
1408
|
+
if (applyToDocument) applyPreferences(preferences2);
|
|
1409
|
+
}, [applyToDocument, preferences2]);
|
|
1410
|
+
(0, import_react4.useEffect)(() => {
|
|
1411
|
+
var _a;
|
|
1412
|
+
if (preferences2.theme !== "system" || typeof matchMedia !== "function") return void 0;
|
|
1413
|
+
const media = matchMedia("(prefers-color-scheme: dark)");
|
|
1414
|
+
const applySystemTheme = () => applyPreferences(preferences2);
|
|
1415
|
+
(_a = media.addEventListener) == null ? void 0 : _a.call(media, "change", applySystemTheme);
|
|
1416
|
+
return () => {
|
|
1417
|
+
var _a2;
|
|
1418
|
+
return (_a2 = media.removeEventListener) == null ? void 0 : _a2.call(media, "change", applySystemTheme);
|
|
1419
|
+
};
|
|
1420
|
+
}, [preferences2]);
|
|
1421
|
+
const value = (0, import_react4.useMemo)(() => ({
|
|
1422
|
+
preferences: preferences2,
|
|
1423
|
+
resolvedTheme: resolveTheme(preferences2.theme),
|
|
1424
|
+
loading,
|
|
1425
|
+
error,
|
|
1426
|
+
refresh: () => getPreferences({ force: true }),
|
|
1427
|
+
update: updatePreferences
|
|
1428
|
+
}), [error, loading, preferences2]);
|
|
1429
|
+
return /* @__PURE__ */ import_react4.default.createElement(PreferencesContext.Provider, { value }, children);
|
|
1430
|
+
}
|
|
1431
|
+
function usePreferences() {
|
|
1432
|
+
const context = (0, import_react4.useContext)(PreferencesContext);
|
|
1433
|
+
if (!context) throw new Error("usePreferences must be used within a PreferencesProvider");
|
|
1434
|
+
return context;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1056
1437
|
// index.js
|
|
1057
1438
|
var auth = {
|
|
1058
1439
|
// ๐ง Config
|
|
@@ -1072,7 +1453,7 @@ var auth = {
|
|
|
1072
1453
|
getToken,
|
|
1073
1454
|
setToken,
|
|
1074
1455
|
clearToken,
|
|
1075
|
-
setRefreshToken,
|
|
1456
|
+
setRefreshToken: setRefreshToken2,
|
|
1076
1457
|
// โ
Refresh token for HTTP dev
|
|
1077
1458
|
getRefreshToken,
|
|
1078
1459
|
clearRefreshToken,
|
|
@@ -1083,6 +1464,10 @@ var auth = {
|
|
|
1083
1464
|
// โ
Debug function
|
|
1084
1465
|
// ๐ Authenticated API client
|
|
1085
1466
|
api: api_default,
|
|
1467
|
+
preferences,
|
|
1468
|
+
// ๐ Safe authentication diagnostics
|
|
1469
|
+
getDiagnosticContext,
|
|
1470
|
+
emitAuthDiagnostic,
|
|
1086
1471
|
// ๐งช Utilities
|
|
1087
1472
|
decodeToken,
|
|
1088
1473
|
isTokenExpired,
|
|
@@ -1130,8 +1515,21 @@ var auth = {
|
|
|
1130
1515
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1131
1516
|
0 && (module.exports = {
|
|
1132
1517
|
AuthProvider,
|
|
1518
|
+
PREFERENCE_DEFAULTS,
|
|
1519
|
+
PreferencesProvider,
|
|
1520
|
+
applyPreferences,
|
|
1133
1521
|
auth,
|
|
1522
|
+
formatUserDate,
|
|
1523
|
+
formatUserNumber,
|
|
1524
|
+
formatUserRelativeTime,
|
|
1525
|
+
getPreferences,
|
|
1526
|
+
normalizePreferences,
|
|
1527
|
+
resolveTheme,
|
|
1528
|
+
startPreferenceSync,
|
|
1529
|
+
subscribePreferences,
|
|
1530
|
+
updatePreferences,
|
|
1134
1531
|
useAuth,
|
|
1532
|
+
usePreferences,
|
|
1135
1533
|
useSessionMonitor
|
|
1136
1534
|
});
|
|
1137
1535
|
//# sourceMappingURL=index.cjs.map
|