@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.js
CHANGED
|
@@ -111,7 +111,7 @@ function shouldUseLocalStorage() {
|
|
|
111
111
|
return false;
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
|
-
function
|
|
114
|
+
function setRefreshToken2(token) {
|
|
115
115
|
if (!token) {
|
|
116
116
|
clearRefreshToken();
|
|
117
117
|
return;
|
|
@@ -228,8 +228,8 @@ function setConfig(customConfig = {}) {
|
|
|
228
228
|
// โ
Auto-detect router mode
|
|
229
229
|
isRouter: customConfig.isRouter || customConfig.clientKey === "account-ui"
|
|
230
230
|
};
|
|
231
|
+
enableRefreshTokenPersistence(config.persistRefreshToken);
|
|
231
232
|
if (config.persistRefreshToken) {
|
|
232
|
-
enableRefreshTokenPersistence(true);
|
|
233
233
|
console.log("\u{1F4E6} Refresh token persistence ENABLED (localStorage on HTTPS)");
|
|
234
234
|
}
|
|
235
235
|
console.log(`\u{1F527} Auth Client Mode: ${config.isRouter ? "ROUTER" : "CLIENT"}`, {
|
|
@@ -245,6 +245,82 @@ function isRouterMode() {
|
|
|
245
245
|
return config.isRouter;
|
|
246
246
|
}
|
|
247
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
|
+
|
|
248
324
|
// core.js
|
|
249
325
|
var callbackProcessed = false;
|
|
250
326
|
function login(clientKeyArg, redirectUriArg) {
|
|
@@ -257,14 +333,16 @@ function login(clientKeyArg, redirectUriArg) {
|
|
|
257
333
|
} = getConfig();
|
|
258
334
|
const clientKey = clientKeyArg || defaultClientKey;
|
|
259
335
|
const redirectUri = redirectUriArg || defaultRedirectUri;
|
|
260
|
-
console.log("\u{1F504} Smart Login initiated:", {
|
|
261
|
-
mode: isRouterMode() ? "ROUTER" : "CLIENT",
|
|
262
|
-
clientKey,
|
|
263
|
-
redirectUri
|
|
264
|
-
});
|
|
265
336
|
if (!clientKey || !redirectUri) {
|
|
337
|
+
emitAuthDiagnostic("LOGIN_REJECTED", "FAILURE", "CLIENT_CONFIG_MISSING", { clientKey });
|
|
266
338
|
throw new Error("Missing clientKey or redirectUri");
|
|
267
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 });
|
|
268
346
|
sessionStorage.setItem("originalApp", clientKey);
|
|
269
347
|
sessionStorage.setItem("returnUrl", redirectUri);
|
|
270
348
|
if (isRouterMode()) {
|
|
@@ -279,13 +357,9 @@ function routerLogin(clientKey, redirectUri) {
|
|
|
279
357
|
if (redirectUri) {
|
|
280
358
|
params.append("redirect_uri", redirectUri);
|
|
281
359
|
}
|
|
360
|
+
params.append("correlation_id", getDiagnosticContext().correlationId);
|
|
282
361
|
const query = params.toString();
|
|
283
362
|
const backendLoginUrl = `${authBaseUrl}/login/${clientKey}${query ? `?${query}` : ""}`;
|
|
284
|
-
console.log("\u{1F3ED} Router Login: Direct backend authentication", {
|
|
285
|
-
clientKey,
|
|
286
|
-
redirectUri,
|
|
287
|
-
backendUrl: backendLoginUrl
|
|
288
|
-
});
|
|
289
363
|
window.location.href = backendLoginUrl;
|
|
290
364
|
}
|
|
291
365
|
function clientLogin(clientKey, redirectUri) {
|
|
@@ -297,11 +371,6 @@ function clientLogin(clientKey, redirectUri) {
|
|
|
297
371
|
params.append("redirect_uri", redirectUri);
|
|
298
372
|
}
|
|
299
373
|
const centralizedLoginUrl = `${accountUiUrl}/login?${params.toString()}`;
|
|
300
|
-
console.log("\u{1F504} Client Login: Redirecting to centralized login", {
|
|
301
|
-
clientKey,
|
|
302
|
-
redirectUri,
|
|
303
|
-
centralizedUrl: centralizedLoginUrl
|
|
304
|
-
});
|
|
305
374
|
window.location.href = centralizedLoginUrl;
|
|
306
375
|
}
|
|
307
376
|
async function logout(options = {}) {
|
|
@@ -315,6 +384,7 @@ async function logout(options = {}) {
|
|
|
315
384
|
clientKey,
|
|
316
385
|
scope
|
|
317
386
|
});
|
|
387
|
+
emitAuthDiagnostic("LOGOUT_INITIATED", "PENDING", "NONE", { clientKey });
|
|
318
388
|
clearToken();
|
|
319
389
|
clearRefreshToken();
|
|
320
390
|
sessionStorage.removeItem("originalApp");
|
|
@@ -324,6 +394,7 @@ async function logout(options = {}) {
|
|
|
324
394
|
method: "POST",
|
|
325
395
|
credentials: "include",
|
|
326
396
|
headers: {
|
|
397
|
+
...diagnosticHeaders(),
|
|
327
398
|
"Authorization": token ? `Bearer ${token}` : "",
|
|
328
399
|
"Content-Type": "application/json"
|
|
329
400
|
},
|
|
@@ -334,16 +405,23 @@ async function logout(options = {}) {
|
|
|
334
405
|
}
|
|
335
406
|
const data = await response.json();
|
|
336
407
|
console.log("\u2705 Logout response:", data);
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
408
|
+
emitAuthDiagnostic("LOGOUT_COMPLETED", "SUCCESS", "NONE", {
|
|
409
|
+
clientKey,
|
|
410
|
+
status: response.status
|
|
411
|
+
});
|
|
341
412
|
if (data == null ? void 0 : data.keycloakLogoutUrl) {
|
|
342
413
|
window.location.replace(data.keycloakLogoutUrl);
|
|
343
414
|
return;
|
|
344
415
|
}
|
|
416
|
+
if (data == null ? void 0 : data.logoutRedirectUrl) {
|
|
417
|
+
window.location.replace(data.logoutRedirectUrl);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
345
420
|
} catch (error) {
|
|
346
421
|
console.warn("\u26A0\uFE0F Logout backend call failed:", error);
|
|
422
|
+
emitAuthDiagnostic("LOGOUT_BACKEND_FAILED", "FAILURE", "LOGOUT_REQUEST_FAILED", {
|
|
423
|
+
clientKey
|
|
424
|
+
});
|
|
347
425
|
}
|
|
348
426
|
const fallbackUrl = isRouterMode() ? new URL("/login", window.location.origin) : new URL("/login", accountUiUrl);
|
|
349
427
|
fallbackUrl.searchParams.set("logged_out", "true");
|
|
@@ -352,7 +430,6 @@ async function logout(options = {}) {
|
|
|
352
430
|
window.location.replace(fallbackUrl.toString());
|
|
353
431
|
}
|
|
354
432
|
function handleCallback() {
|
|
355
|
-
var _a;
|
|
356
433
|
const params = new URLSearchParams(window.location.search);
|
|
357
434
|
const accessToken2 = params.get("access_token");
|
|
358
435
|
const error = params.get("error");
|
|
@@ -369,24 +446,27 @@ function handleCallback() {
|
|
|
369
446
|
callbackProcessed = false;
|
|
370
447
|
}
|
|
371
448
|
callbackProcessed = true;
|
|
449
|
+
clearLoginLock();
|
|
372
450
|
sessionStorage.removeItem("originalApp");
|
|
373
451
|
sessionStorage.removeItem("returnUrl");
|
|
374
452
|
if (error) {
|
|
375
453
|
const errorDescription = params.get("error_description") || error;
|
|
376
|
-
|
|
454
|
+
const authError = new Error(`Authentication failed: ${errorDescription}`);
|
|
455
|
+
authError.code = error;
|
|
456
|
+
authError.correlationId = getDiagnosticContext().correlationId;
|
|
457
|
+
emitAuthDiagnostic("CALLBACK_REJECTED", "FAILURE", error.toUpperCase(), {
|
|
458
|
+
clientKey: getConfig().clientKey,
|
|
459
|
+
state: params.get("state")
|
|
460
|
+
});
|
|
461
|
+
throw authError;
|
|
377
462
|
}
|
|
378
463
|
if (accessToken2) {
|
|
379
464
|
setToken(accessToken2);
|
|
380
465
|
const refreshTokenInUrl = params.get("refresh_token");
|
|
381
466
|
if (refreshTokenInUrl) {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
console.log(`\u{1F4E6} Storing refresh token from callback URL (${persistRefreshToken ? "persistence enabled" : "HTTP dev mode"})`);
|
|
386
|
-
setRefreshToken(refreshTokenInUrl);
|
|
387
|
-
} else {
|
|
388
|
-
console.log("\u{1F512} HTTPS mode: Refresh token is in httpOnly cookie (ignoring URL param)");
|
|
389
|
-
}
|
|
467
|
+
emitAuthDiagnostic("CALLBACK_REFRESH_TOKEN_IGNORED", "WARNING", "REFRESH_TOKEN_IN_URL", {
|
|
468
|
+
clientKey: getConfig().clientKey
|
|
469
|
+
});
|
|
390
470
|
}
|
|
391
471
|
const url = new URL(window.location);
|
|
392
472
|
url.searchParams.delete("access_token");
|
|
@@ -396,9 +476,19 @@ function handleCallback() {
|
|
|
396
476
|
url.searchParams.delete("error_description");
|
|
397
477
|
window.history.replaceState({}, "", url);
|
|
398
478
|
console.log("\u2705 Callback processed successfully, token stored");
|
|
479
|
+
emitAuthDiagnostic("CALLBACK_COMPLETED", "SUCCESS", "NONE", {
|
|
480
|
+
clientKey: getConfig().clientKey,
|
|
481
|
+
state: params.get("state")
|
|
482
|
+
});
|
|
399
483
|
return accessToken2;
|
|
400
484
|
}
|
|
401
|
-
|
|
485
|
+
emitAuthDiagnostic("CALLBACK_REJECTED", "FAILURE", "ACCESS_TOKEN_MISSING", {
|
|
486
|
+
clientKey: getConfig().clientKey,
|
|
487
|
+
state: params.get("state")
|
|
488
|
+
});
|
|
489
|
+
const missingTokenError = new Error("No access token found in callback URL");
|
|
490
|
+
missingTokenError.code = "ACCESS_TOKEN_MISSING";
|
|
491
|
+
throw missingTokenError;
|
|
402
492
|
}
|
|
403
493
|
function resetCallbackState() {
|
|
404
494
|
callbackProcessed = false;
|
|
@@ -413,7 +503,7 @@ async function refreshToken() {
|
|
|
413
503
|
}
|
|
414
504
|
refreshInProgress = true;
|
|
415
505
|
refreshPromise = (async () => {
|
|
416
|
-
var _a, _b, _c, _d;
|
|
506
|
+
var _a, _b, _c, _d, _e, _f;
|
|
417
507
|
try {
|
|
418
508
|
const storedRefreshToken = getRefreshToken();
|
|
419
509
|
console.log("\u{1F504} Refreshing token:", {
|
|
@@ -426,6 +516,7 @@ async function refreshToken() {
|
|
|
426
516
|
credentials: "include",
|
|
427
517
|
// โ
Include httpOnly cookies (for HTTPS)
|
|
428
518
|
headers: {
|
|
519
|
+
...diagnosticHeaders(),
|
|
429
520
|
"Content-Type": "application/json"
|
|
430
521
|
}
|
|
431
522
|
};
|
|
@@ -436,8 +527,19 @@ async function refreshToken() {
|
|
|
436
527
|
const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
|
|
437
528
|
if (!response.ok) {
|
|
438
529
|
const errorText = await response.text();
|
|
439
|
-
|
|
440
|
-
|
|
530
|
+
let serverCode = null;
|
|
531
|
+
try {
|
|
532
|
+
serverCode = ((_a = JSON.parse(errorText)) == null ? void 0 : _a.error) || ((_b = JSON.parse(errorText)) == null ? void 0 : _b.code);
|
|
533
|
+
} catch {
|
|
534
|
+
}
|
|
535
|
+
emitAuthDiagnostic("TOKEN_REFRESH_REJECTED", "FAILURE", serverCode || `HTTP_${response.status}`, {
|
|
536
|
+
clientKey,
|
|
537
|
+
status: response.status
|
|
538
|
+
});
|
|
539
|
+
const refreshError = new Error(`Refresh failed: ${response.status}`);
|
|
540
|
+
refreshError.code = serverCode || `HTTP_${response.status}`;
|
|
541
|
+
refreshError.status = response.status;
|
|
542
|
+
throw refreshError;
|
|
441
543
|
}
|
|
442
544
|
const data = await response.json();
|
|
443
545
|
const { access_token, refresh_token: new_refresh_token } = data;
|
|
@@ -450,10 +552,11 @@ async function refreshToken() {
|
|
|
450
552
|
console.log("\u{1F504} New refresh token stored from rotation");
|
|
451
553
|
}
|
|
452
554
|
console.log("\u2705 Token refresh successful, listeners notified");
|
|
555
|
+
emitAuthDiagnostic("TOKEN_REFRESH_COMPLETED", "SUCCESS", "NONE", { clientKey });
|
|
453
556
|
return access_token;
|
|
454
557
|
} catch (err) {
|
|
455
558
|
console.error("\u274C Token refresh error:", err);
|
|
456
|
-
const isAuthRejection = ((
|
|
559
|
+
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"));
|
|
457
560
|
if (isAuthRejection) {
|
|
458
561
|
clearToken();
|
|
459
562
|
clearRefreshToken();
|
|
@@ -476,6 +579,7 @@ async function validateCurrentSession() {
|
|
|
476
579
|
const response = await fetch(`${authBaseUrl}/account/validate-session`, {
|
|
477
580
|
method: "GET",
|
|
478
581
|
headers: {
|
|
582
|
+
...diagnosticHeaders(),
|
|
479
583
|
"Authorization": `Bearer ${token}`,
|
|
480
584
|
"Content-Type": "application/json"
|
|
481
585
|
},
|
|
@@ -728,6 +832,7 @@ api.interceptors.request.use((config2) => {
|
|
|
728
832
|
if ((runtimeConfig == null ? void 0 : runtimeConfig.clientKey) && !config2.headers["X-Client-Key"]) {
|
|
729
833
|
config2.headers["X-Client-Key"] = runtimeConfig.clientKey;
|
|
730
834
|
}
|
|
835
|
+
Object.assign(config2.headers, diagnosticHeaders());
|
|
731
836
|
const token = getToken();
|
|
732
837
|
if (token) {
|
|
733
838
|
config2.headers.Authorization = `Bearer ${token}`;
|
|
@@ -746,6 +851,10 @@ api.interceptors.response.use(
|
|
|
746
851
|
return Promise.reject(error);
|
|
747
852
|
}
|
|
748
853
|
config2._retry = true;
|
|
854
|
+
emitAuthDiagnostic("API_401_REFRESH_STARTED", "PENDING", "HTTP_401", {
|
|
855
|
+
clientKey: getConfig().clientKey,
|
|
856
|
+
status: 401
|
|
857
|
+
});
|
|
749
858
|
if (!refreshPromise2) {
|
|
750
859
|
refreshPromise2 = refreshToken().then((newToken) => {
|
|
751
860
|
refreshPromise2 = null;
|
|
@@ -805,6 +914,208 @@ function isAuthenticated() {
|
|
|
805
914
|
return !!token && !isTokenExpired(token, 10);
|
|
806
915
|
}
|
|
807
916
|
|
|
917
|
+
// preferences.js
|
|
918
|
+
var PREFERENCE_DEFAULTS = Object.freeze({
|
|
919
|
+
theme: "system",
|
|
920
|
+
colorScheme: "blue",
|
|
921
|
+
density: "comfortable",
|
|
922
|
+
reducedMotion: false,
|
|
923
|
+
language: "en",
|
|
924
|
+
timezone: "Etc/UTC",
|
|
925
|
+
dateFormat: "MM/DD/YYYY",
|
|
926
|
+
timeFormat: "12h",
|
|
927
|
+
highContrast: false,
|
|
928
|
+
largeText: false
|
|
929
|
+
});
|
|
930
|
+
var ALLOWED = {
|
|
931
|
+
theme: /* @__PURE__ */ new Set(["light", "dark", "system"]),
|
|
932
|
+
density: /* @__PURE__ */ new Set(["compact", "comfortable", "spacious"]),
|
|
933
|
+
timeFormat: /* @__PURE__ */ new Set(["12h", "24h"])
|
|
934
|
+
};
|
|
935
|
+
var listeners2 = /* @__PURE__ */ new Set();
|
|
936
|
+
var cachedPreferences = null;
|
|
937
|
+
var pendingRequest = null;
|
|
938
|
+
var syncCleanup = null;
|
|
939
|
+
function extractResponseData(response) {
|
|
940
|
+
var _a;
|
|
941
|
+
return ((_a = response == null ? void 0 : response.data) == null ? void 0 : _a.data) ?? (response == null ? void 0 : response.data) ?? response ?? {};
|
|
942
|
+
}
|
|
943
|
+
function normalizePreferences(value = {}) {
|
|
944
|
+
const next = { ...PREFERENCE_DEFAULTS };
|
|
945
|
+
for (const key of Object.keys(PREFERENCE_DEFAULTS)) {
|
|
946
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
947
|
+
const candidate = value[key];
|
|
948
|
+
if (ALLOWED[key] && !ALLOWED[key].has(candidate)) continue;
|
|
949
|
+
if (typeof PREFERENCE_DEFAULTS[key] !== typeof candidate) continue;
|
|
950
|
+
next[key] = candidate;
|
|
951
|
+
}
|
|
952
|
+
return next;
|
|
953
|
+
}
|
|
954
|
+
function resolveTheme(theme, matchMedia2 = globalThis.matchMedia) {
|
|
955
|
+
if (theme === "dark" || theme === "light") return theme;
|
|
956
|
+
return typeof matchMedia2 === "function" && matchMedia2("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
957
|
+
}
|
|
958
|
+
function announce(preferences2) {
|
|
959
|
+
cachedPreferences = normalizePreferences(preferences2);
|
|
960
|
+
listeners2.forEach((listener) => listener(cachedPreferences));
|
|
961
|
+
return cachedPreferences;
|
|
962
|
+
}
|
|
963
|
+
async function getPreferences({ force = false } = {}) {
|
|
964
|
+
if (cachedPreferences && !force) return cachedPreferences;
|
|
965
|
+
if (pendingRequest) return pendingRequest;
|
|
966
|
+
pendingRequest = api_default.get("/account/preferences").then((response) => announce(extractResponseData(response))).finally(() => {
|
|
967
|
+
pendingRequest = null;
|
|
968
|
+
});
|
|
969
|
+
return pendingRequest;
|
|
970
|
+
}
|
|
971
|
+
async function updatePreferences(patch) {
|
|
972
|
+
const allowedPatch = {};
|
|
973
|
+
for (const key of Object.keys(PREFERENCE_DEFAULTS)) {
|
|
974
|
+
if (Object.hasOwn(patch, key)) allowedPatch[key] = patch[key];
|
|
975
|
+
}
|
|
976
|
+
const candidate = normalizePreferences({ ...cachedPreferences || PREFERENCE_DEFAULTS, ...allowedPatch });
|
|
977
|
+
const response = await api_default.put("/account/preferences", allowedPatch);
|
|
978
|
+
const saved = extractResponseData(response);
|
|
979
|
+
return announce(Object.keys(saved).length ? saved : candidate);
|
|
980
|
+
}
|
|
981
|
+
function subscribePreferences(listener) {
|
|
982
|
+
listeners2.add(listener);
|
|
983
|
+
if (cachedPreferences) listener(cachedPreferences);
|
|
984
|
+
return () => listeners2.delete(listener);
|
|
985
|
+
}
|
|
986
|
+
function clearPreferenceCache() {
|
|
987
|
+
cachedPreferences = null;
|
|
988
|
+
pendingRequest = null;
|
|
989
|
+
}
|
|
990
|
+
function applyPreferences(preferences2, documentRef = globalThis.document) {
|
|
991
|
+
var _a, _b, _c;
|
|
992
|
+
if (!(documentRef == null ? void 0 : documentRef.documentElement)) return;
|
|
993
|
+
const value = normalizePreferences(preferences2);
|
|
994
|
+
const root = documentRef.documentElement;
|
|
995
|
+
const resolvedTheme = resolveTheme(value.theme, (_b = (_a = documentRef.defaultView) == null ? void 0 : _a.matchMedia) == null ? void 0 : _b.bind(documentRef.defaultView));
|
|
996
|
+
root.dataset.theme = resolvedTheme;
|
|
997
|
+
root.dataset.themePreference = value.theme;
|
|
998
|
+
root.dataset.colorScheme = value.colorScheme;
|
|
999
|
+
root.dataset.density = value.density;
|
|
1000
|
+
root.lang = value.language;
|
|
1001
|
+
root.style.colorScheme = resolvedTheme;
|
|
1002
|
+
root.style.setProperty("--user-accent-color", {
|
|
1003
|
+
blue: "#1976d2",
|
|
1004
|
+
green: "#2e7d32",
|
|
1005
|
+
purple: "#7b1fa2",
|
|
1006
|
+
orange: "#c65d00",
|
|
1007
|
+
red: "#c62828"
|
|
1008
|
+
}[value.colorScheme] || "#1976d2");
|
|
1009
|
+
root.classList.toggle("user-reduced-motion", value.reducedMotion);
|
|
1010
|
+
root.classList.toggle("user-high-contrast", value.highContrast);
|
|
1011
|
+
root.classList.toggle("user-large-text", value.largeText);
|
|
1012
|
+
const styleId = "sso-user-preference-styles";
|
|
1013
|
+
if (!documentRef.getElementById(styleId)) {
|
|
1014
|
+
const style = documentRef.createElement("style");
|
|
1015
|
+
style.id = styleId;
|
|
1016
|
+
style.textContent = `
|
|
1017
|
+
html.user-large-text { font-size: 112.5%; }
|
|
1018
|
+
html.user-high-contrast { filter: contrast(1.15); }
|
|
1019
|
+
html[data-density="compact"] { --user-density-padding: 6px; }
|
|
1020
|
+
html[data-density="comfortable"] { --user-density-padding: 10px; }
|
|
1021
|
+
html[data-density="spacious"] { --user-density-padding: 14px; }
|
|
1022
|
+
html[data-density] .MuiListItem-root { padding-top: var(--user-density-padding); padding-bottom: var(--user-density-padding); }
|
|
1023
|
+
html.user-reduced-motion *, html.user-reduced-motion *::before, html.user-reduced-motion *::after {
|
|
1024
|
+
animation-duration: 0.01ms !important;
|
|
1025
|
+
animation-iteration-count: 1 !important;
|
|
1026
|
+
scroll-behavior: auto !important;
|
|
1027
|
+
transition-duration: 0.01ms !important;
|
|
1028
|
+
}
|
|
1029
|
+
`;
|
|
1030
|
+
(_c = documentRef.head) == null ? void 0 : _c.appendChild(style);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
function startPreferenceSync({ refreshOnFocus = true } = {}) {
|
|
1034
|
+
var _a, _b;
|
|
1035
|
+
if (syncCleanup || typeof globalThis.addEventListener !== "function") return syncCleanup || (() => {
|
|
1036
|
+
});
|
|
1037
|
+
const refresh = () => {
|
|
1038
|
+
if (!globalThis.document || globalThis.document.visibilityState === "visible") {
|
|
1039
|
+
getPreferences({ force: true }).catch(() => {
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
};
|
|
1043
|
+
const onVisibility = () => refresh();
|
|
1044
|
+
(_b = (_a = globalThis.document) == null ? void 0 : _a.addEventListener) == null ? void 0 : _b.call(_a, "visibilitychange", onVisibility);
|
|
1045
|
+
if (refreshOnFocus) globalThis.addEventListener("focus", refresh);
|
|
1046
|
+
syncCleanup = () => {
|
|
1047
|
+
var _a2, _b2;
|
|
1048
|
+
(_b2 = (_a2 = globalThis.document) == null ? void 0 : _a2.removeEventListener) == null ? void 0 : _b2.call(_a2, "visibilitychange", onVisibility);
|
|
1049
|
+
if (refreshOnFocus) globalThis.removeEventListener("focus", refresh);
|
|
1050
|
+
syncCleanup = null;
|
|
1051
|
+
};
|
|
1052
|
+
return syncCleanup;
|
|
1053
|
+
}
|
|
1054
|
+
function localeFor(language) {
|
|
1055
|
+
const supported = Intl.DateTimeFormat.supportedLocalesOf([language]);
|
|
1056
|
+
return supported[0] || "en";
|
|
1057
|
+
}
|
|
1058
|
+
function fixedOffset(value) {
|
|
1059
|
+
const match = /^UTC([+-])(\d{2}):(\d{2})$/.exec(value || "");
|
|
1060
|
+
if (!match) return null;
|
|
1061
|
+
const minutes = Number(match[2]) * 60 + Number(match[3]);
|
|
1062
|
+
return (match[1] === "-" ? -1 : 1) * minutes;
|
|
1063
|
+
}
|
|
1064
|
+
function formatUserDate(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, options = {}) {
|
|
1065
|
+
const value = normalizePreferences(preferences2);
|
|
1066
|
+
const date = input instanceof Date ? input : new Date(input);
|
|
1067
|
+
const offset = fixedOffset(value.timezone);
|
|
1068
|
+
const adjusted = offset === null ? date : new Date(date.getTime() + offset * 6e4);
|
|
1069
|
+
const timeZone = offset === null ? value.timezone : "UTC";
|
|
1070
|
+
const hasExplicitParts = ["year", "month", "day", "weekday", "hour", "minute", "second", "dateStyle", "timeStyle"].some((key) => options[key] !== void 0);
|
|
1071
|
+
const dateOptions = hasExplicitParts ? options : { year: "numeric", month: "2-digit", day: "2-digit" };
|
|
1072
|
+
const formatter = new Intl.DateTimeFormat(localeFor(value.language), {
|
|
1073
|
+
...dateOptions,
|
|
1074
|
+
timeZone,
|
|
1075
|
+
...dateOptions.hour || dateOptions.timeStyle ? { hour12: value.timeFormat === "12h" } : {}
|
|
1076
|
+
});
|
|
1077
|
+
if (!hasExplicitParts) {
|
|
1078
|
+
const parts = Object.fromEntries(formatter.formatToParts(adjusted).map((part) => [part.type, part.value]));
|
|
1079
|
+
if (value.dateFormat === "DD/MM/YYYY") return `${parts.day}/${parts.month}/${parts.year}`;
|
|
1080
|
+
if (value.dateFormat === "YYYY-MM-DD") return `${parts.year}-${parts.month}-${parts.day}`;
|
|
1081
|
+
return `${parts.month}/${parts.day}/${parts.year}`;
|
|
1082
|
+
}
|
|
1083
|
+
return formatter.format(adjusted);
|
|
1084
|
+
}
|
|
1085
|
+
function formatUserNumber(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, options = {}) {
|
|
1086
|
+
const value = normalizePreferences(preferences2);
|
|
1087
|
+
return new Intl.NumberFormat(localeFor(value.language), options).format(input);
|
|
1088
|
+
}
|
|
1089
|
+
function formatUserRelativeTime(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, now = /* @__PURE__ */ new Date()) {
|
|
1090
|
+
const value = normalizePreferences(preferences2);
|
|
1091
|
+
const deltaSeconds = (new Date(input).getTime() - now.getTime()) / 1e3;
|
|
1092
|
+
const units = [
|
|
1093
|
+
["year", 31536e3],
|
|
1094
|
+
["month", 2592e3],
|
|
1095
|
+
["week", 604800],
|
|
1096
|
+
["day", 86400],
|
|
1097
|
+
["hour", 3600],
|
|
1098
|
+
["minute", 60],
|
|
1099
|
+
["second", 1]
|
|
1100
|
+
];
|
|
1101
|
+
const [unit, seconds] = units.find(([, size]) => Math.abs(deltaSeconds) >= size) || units.at(-1);
|
|
1102
|
+
return new Intl.RelativeTimeFormat(localeFor(value.language), { numeric: "auto" }).format(Math.round(deltaSeconds / seconds), unit);
|
|
1103
|
+
}
|
|
1104
|
+
var preferences = {
|
|
1105
|
+
defaults: PREFERENCE_DEFAULTS,
|
|
1106
|
+
get: getPreferences,
|
|
1107
|
+
update: updatePreferences,
|
|
1108
|
+
subscribe: subscribePreferences,
|
|
1109
|
+
clearCache: clearPreferenceCache,
|
|
1110
|
+
apply: applyPreferences,
|
|
1111
|
+
startSync: startPreferenceSync,
|
|
1112
|
+
formatDate: formatUserDate,
|
|
1113
|
+
formatNumber: formatUserNumber,
|
|
1114
|
+
formatRelativeTime: formatUserRelativeTime,
|
|
1115
|
+
normalize: normalizePreferences,
|
|
1116
|
+
resolveTheme
|
|
1117
|
+
};
|
|
1118
|
+
|
|
808
1119
|
// react/AuthProvider.jsx
|
|
809
1120
|
import React, { createContext, useState, useEffect, useRef } from "react";
|
|
810
1121
|
var AuthContext = createContext();
|
|
@@ -868,7 +1179,8 @@ function AuthProvider({ children, onSessionExpired }) {
|
|
|
868
1179
|
console.log("\u{1F4E5} Profile response status:", res.status);
|
|
869
1180
|
if (!res.ok) throw new Error("Failed to fetch user");
|
|
870
1181
|
return res.json();
|
|
871
|
-
}).then((
|
|
1182
|
+
}).then((responseBody) => {
|
|
1183
|
+
const userData = (responseBody == null ? void 0 : responseBody.data) ?? responseBody;
|
|
872
1184
|
console.log("\u2705 Profile fetched successfully:", userData.email);
|
|
873
1185
|
setUser(userData);
|
|
874
1186
|
setSessionValid(true);
|
|
@@ -1015,6 +1327,62 @@ var useSessionMonitor = (options = {}) => {
|
|
|
1015
1327
|
};
|
|
1016
1328
|
};
|
|
1017
1329
|
|
|
1330
|
+
// react/PreferencesProvider.jsx
|
|
1331
|
+
import React2, { createContext as createContext2, useContext as useContext2, useEffect as useEffect3, useMemo, useState as useState2 } from "react";
|
|
1332
|
+
var PreferencesContext = createContext2(null);
|
|
1333
|
+
function PreferencesProvider({ children, initialPreferences, applyToDocument = true }) {
|
|
1334
|
+
const [preferences2, setPreferences] = useState2(() => ({
|
|
1335
|
+
...PREFERENCE_DEFAULTS,
|
|
1336
|
+
...initialPreferences
|
|
1337
|
+
}));
|
|
1338
|
+
const [loading, setLoading] = useState2(true);
|
|
1339
|
+
const [error, setError] = useState2(null);
|
|
1340
|
+
useEffect3(() => {
|
|
1341
|
+
const unsubscribe = subscribePreferences((next) => {
|
|
1342
|
+
setPreferences(next);
|
|
1343
|
+
setLoading(false);
|
|
1344
|
+
setError(null);
|
|
1345
|
+
});
|
|
1346
|
+
const stopSync = startPreferenceSync();
|
|
1347
|
+
getPreferences().catch((nextError) => {
|
|
1348
|
+
setError(nextError);
|
|
1349
|
+
setLoading(false);
|
|
1350
|
+
});
|
|
1351
|
+
return () => {
|
|
1352
|
+
unsubscribe();
|
|
1353
|
+
stopSync();
|
|
1354
|
+
};
|
|
1355
|
+
}, []);
|
|
1356
|
+
useEffect3(() => {
|
|
1357
|
+
if (applyToDocument) applyPreferences(preferences2);
|
|
1358
|
+
}, [applyToDocument, preferences2]);
|
|
1359
|
+
useEffect3(() => {
|
|
1360
|
+
var _a;
|
|
1361
|
+
if (preferences2.theme !== "system" || typeof matchMedia !== "function") return void 0;
|
|
1362
|
+
const media = matchMedia("(prefers-color-scheme: dark)");
|
|
1363
|
+
const applySystemTheme = () => applyPreferences(preferences2);
|
|
1364
|
+
(_a = media.addEventListener) == null ? void 0 : _a.call(media, "change", applySystemTheme);
|
|
1365
|
+
return () => {
|
|
1366
|
+
var _a2;
|
|
1367
|
+
return (_a2 = media.removeEventListener) == null ? void 0 : _a2.call(media, "change", applySystemTheme);
|
|
1368
|
+
};
|
|
1369
|
+
}, [preferences2]);
|
|
1370
|
+
const value = useMemo(() => ({
|
|
1371
|
+
preferences: preferences2,
|
|
1372
|
+
resolvedTheme: resolveTheme(preferences2.theme),
|
|
1373
|
+
loading,
|
|
1374
|
+
error,
|
|
1375
|
+
refresh: () => getPreferences({ force: true }),
|
|
1376
|
+
update: updatePreferences
|
|
1377
|
+
}), [error, loading, preferences2]);
|
|
1378
|
+
return /* @__PURE__ */ React2.createElement(PreferencesContext.Provider, { value }, children);
|
|
1379
|
+
}
|
|
1380
|
+
function usePreferences() {
|
|
1381
|
+
const context = useContext2(PreferencesContext);
|
|
1382
|
+
if (!context) throw new Error("usePreferences must be used within a PreferencesProvider");
|
|
1383
|
+
return context;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1018
1386
|
// index.js
|
|
1019
1387
|
var auth = {
|
|
1020
1388
|
// ๐ง Config
|
|
@@ -1034,7 +1402,7 @@ var auth = {
|
|
|
1034
1402
|
getToken,
|
|
1035
1403
|
setToken,
|
|
1036
1404
|
clearToken,
|
|
1037
|
-
setRefreshToken,
|
|
1405
|
+
setRefreshToken: setRefreshToken2,
|
|
1038
1406
|
// โ
Refresh token for HTTP dev
|
|
1039
1407
|
getRefreshToken,
|
|
1040
1408
|
clearRefreshToken,
|
|
@@ -1045,6 +1413,10 @@ var auth = {
|
|
|
1045
1413
|
// โ
Debug function
|
|
1046
1414
|
// ๐ Authenticated API client
|
|
1047
1415
|
api: api_default,
|
|
1416
|
+
preferences,
|
|
1417
|
+
// ๐ Safe authentication diagnostics
|
|
1418
|
+
getDiagnosticContext,
|
|
1419
|
+
emitAuthDiagnostic,
|
|
1048
1420
|
// ๐งช Utilities
|
|
1049
1421
|
decodeToken,
|
|
1050
1422
|
isTokenExpired,
|
|
@@ -1091,8 +1463,21 @@ var auth = {
|
|
|
1091
1463
|
};
|
|
1092
1464
|
export {
|
|
1093
1465
|
AuthProvider,
|
|
1466
|
+
PREFERENCE_DEFAULTS,
|
|
1467
|
+
PreferencesProvider,
|
|
1468
|
+
applyPreferences,
|
|
1094
1469
|
auth,
|
|
1470
|
+
formatUserDate,
|
|
1471
|
+
formatUserNumber,
|
|
1472
|
+
formatUserRelativeTime,
|
|
1473
|
+
getPreferences,
|
|
1474
|
+
normalizePreferences,
|
|
1475
|
+
resolveTheme,
|
|
1476
|
+
startPreferenceSync,
|
|
1477
|
+
subscribePreferences,
|
|
1478
|
+
updatePreferences,
|
|
1095
1479
|
useAuth,
|
|
1480
|
+
usePreferences,
|
|
1096
1481
|
useSessionMonitor
|
|
1097
1482
|
};
|
|
1098
1483
|
//# sourceMappingURL=index.js.map
|