@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.js
CHANGED
|
@@ -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 = {}) {
|
|
@@ -324,6 +393,7 @@ async function logout(options = {}) {
|
|
|
324
393
|
method: "POST",
|
|
325
394
|
credentials: "include",
|
|
326
395
|
headers: {
|
|
396
|
+
...diagnosticHeaders(),
|
|
327
397
|
"Authorization": token ? `Bearer ${token}` : "",
|
|
328
398
|
"Content-Type": "application/json"
|
|
329
399
|
},
|
|
@@ -369,11 +439,19 @@ function handleCallback() {
|
|
|
369
439
|
callbackProcessed = false;
|
|
370
440
|
}
|
|
371
441
|
callbackProcessed = true;
|
|
442
|
+
clearLoginLock();
|
|
372
443
|
sessionStorage.removeItem("originalApp");
|
|
373
444
|
sessionStorage.removeItem("returnUrl");
|
|
374
445
|
if (error) {
|
|
375
446
|
const errorDescription = params.get("error_description") || error;
|
|
376
|
-
|
|
447
|
+
const authError = new Error(`Authentication failed: ${errorDescription}`);
|
|
448
|
+
authError.code = error;
|
|
449
|
+
authError.correlationId = getDiagnosticContext().correlationId;
|
|
450
|
+
emitAuthDiagnostic("CALLBACK_REJECTED", "FAILURE", error.toUpperCase(), {
|
|
451
|
+
clientKey: getConfig().clientKey,
|
|
452
|
+
state: params.get("state")
|
|
453
|
+
});
|
|
454
|
+
throw authError;
|
|
377
455
|
}
|
|
378
456
|
if (accessToken2) {
|
|
379
457
|
setToken(accessToken2);
|
|
@@ -396,9 +474,19 @@ function handleCallback() {
|
|
|
396
474
|
url.searchParams.delete("error_description");
|
|
397
475
|
window.history.replaceState({}, "", url);
|
|
398
476
|
console.log("\u2705 Callback processed successfully, token stored");
|
|
477
|
+
emitAuthDiagnostic("CALLBACK_COMPLETED", "SUCCESS", "NONE", {
|
|
478
|
+
clientKey: getConfig().clientKey,
|
|
479
|
+
state: params.get("state")
|
|
480
|
+
});
|
|
399
481
|
return accessToken2;
|
|
400
482
|
}
|
|
401
|
-
|
|
483
|
+
emitAuthDiagnostic("CALLBACK_REJECTED", "FAILURE", "ACCESS_TOKEN_MISSING", {
|
|
484
|
+
clientKey: getConfig().clientKey,
|
|
485
|
+
state: params.get("state")
|
|
486
|
+
});
|
|
487
|
+
const missingTokenError = new Error("No access token found in callback URL");
|
|
488
|
+
missingTokenError.code = "ACCESS_TOKEN_MISSING";
|
|
489
|
+
throw missingTokenError;
|
|
402
490
|
}
|
|
403
491
|
function resetCallbackState() {
|
|
404
492
|
callbackProcessed = false;
|
|
@@ -413,7 +501,7 @@ async function refreshToken() {
|
|
|
413
501
|
}
|
|
414
502
|
refreshInProgress = true;
|
|
415
503
|
refreshPromise = (async () => {
|
|
416
|
-
var _a, _b, _c, _d;
|
|
504
|
+
var _a, _b, _c, _d, _e, _f;
|
|
417
505
|
try {
|
|
418
506
|
const storedRefreshToken = getRefreshToken();
|
|
419
507
|
console.log("\u{1F504} Refreshing token:", {
|
|
@@ -426,6 +514,7 @@ async function refreshToken() {
|
|
|
426
514
|
credentials: "include",
|
|
427
515
|
// โ
Include httpOnly cookies (for HTTPS)
|
|
428
516
|
headers: {
|
|
517
|
+
...diagnosticHeaders(),
|
|
429
518
|
"Content-Type": "application/json"
|
|
430
519
|
}
|
|
431
520
|
};
|
|
@@ -436,8 +525,19 @@ async function refreshToken() {
|
|
|
436
525
|
const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
|
|
437
526
|
if (!response.ok) {
|
|
438
527
|
const errorText = await response.text();
|
|
439
|
-
|
|
440
|
-
|
|
528
|
+
let serverCode = null;
|
|
529
|
+
try {
|
|
530
|
+
serverCode = ((_a = JSON.parse(errorText)) == null ? void 0 : _a.error) || ((_b = JSON.parse(errorText)) == null ? void 0 : _b.code);
|
|
531
|
+
} catch {
|
|
532
|
+
}
|
|
533
|
+
emitAuthDiagnostic("TOKEN_REFRESH_REJECTED", "FAILURE", serverCode || `HTTP_${response.status}`, {
|
|
534
|
+
clientKey,
|
|
535
|
+
status: response.status
|
|
536
|
+
});
|
|
537
|
+
const refreshError = new Error(`Refresh failed: ${response.status}`);
|
|
538
|
+
refreshError.code = serverCode || `HTTP_${response.status}`;
|
|
539
|
+
refreshError.status = response.status;
|
|
540
|
+
throw refreshError;
|
|
441
541
|
}
|
|
442
542
|
const data = await response.json();
|
|
443
543
|
const { access_token, refresh_token: new_refresh_token } = data;
|
|
@@ -450,10 +550,11 @@ async function refreshToken() {
|
|
|
450
550
|
console.log("\u{1F504} New refresh token stored from rotation");
|
|
451
551
|
}
|
|
452
552
|
console.log("\u2705 Token refresh successful, listeners notified");
|
|
553
|
+
emitAuthDiagnostic("TOKEN_REFRESH_COMPLETED", "SUCCESS", "NONE", { clientKey });
|
|
453
554
|
return access_token;
|
|
454
555
|
} catch (err) {
|
|
455
556
|
console.error("\u274C Token refresh error:", err);
|
|
456
|
-
const isAuthRejection = ((
|
|
557
|
+
const isAuthRejection = ((_c = err.message) == null ? void 0 : _c.includes("401")) || ((_d = err.message) == null ? void 0 : _d.includes("403")) || ((_e = err.message) == null ? void 0 : _e.includes("invalid_grant")) || ((_f = err.message) == null ? void 0 : _f.includes("Refresh failed: 4"));
|
|
457
558
|
if (isAuthRejection) {
|
|
458
559
|
clearToken();
|
|
459
560
|
clearRefreshToken();
|
|
@@ -476,6 +577,7 @@ async function validateCurrentSession() {
|
|
|
476
577
|
const response = await fetch(`${authBaseUrl}/account/validate-session`, {
|
|
477
578
|
method: "GET",
|
|
478
579
|
headers: {
|
|
580
|
+
...diagnosticHeaders(),
|
|
479
581
|
"Authorization": `Bearer ${token}`,
|
|
480
582
|
"Content-Type": "application/json"
|
|
481
583
|
},
|
|
@@ -728,6 +830,7 @@ api.interceptors.request.use((config2) => {
|
|
|
728
830
|
if ((runtimeConfig == null ? void 0 : runtimeConfig.clientKey) && !config2.headers["X-Client-Key"]) {
|
|
729
831
|
config2.headers["X-Client-Key"] = runtimeConfig.clientKey;
|
|
730
832
|
}
|
|
833
|
+
Object.assign(config2.headers, diagnosticHeaders());
|
|
731
834
|
const token = getToken();
|
|
732
835
|
if (token) {
|
|
733
836
|
config2.headers.Authorization = `Bearer ${token}`;
|
|
@@ -746,6 +849,10 @@ api.interceptors.response.use(
|
|
|
746
849
|
return Promise.reject(error);
|
|
747
850
|
}
|
|
748
851
|
config2._retry = true;
|
|
852
|
+
emitAuthDiagnostic("API_401_REFRESH_STARTED", "PENDING", "HTTP_401", {
|
|
853
|
+
clientKey: getConfig().clientKey,
|
|
854
|
+
status: 401
|
|
855
|
+
});
|
|
749
856
|
if (!refreshPromise2) {
|
|
750
857
|
refreshPromise2 = refreshToken().then((newToken) => {
|
|
751
858
|
refreshPromise2 = null;
|
|
@@ -805,6 +912,208 @@ function isAuthenticated() {
|
|
|
805
912
|
return !!token && !isTokenExpired(token, 10);
|
|
806
913
|
}
|
|
807
914
|
|
|
915
|
+
// preferences.js
|
|
916
|
+
var PREFERENCE_DEFAULTS = Object.freeze({
|
|
917
|
+
theme: "system",
|
|
918
|
+
colorScheme: "blue",
|
|
919
|
+
density: "comfortable",
|
|
920
|
+
reducedMotion: false,
|
|
921
|
+
language: "en",
|
|
922
|
+
timezone: "Etc/UTC",
|
|
923
|
+
dateFormat: "MM/DD/YYYY",
|
|
924
|
+
timeFormat: "12h",
|
|
925
|
+
highContrast: false,
|
|
926
|
+
largeText: false
|
|
927
|
+
});
|
|
928
|
+
var ALLOWED = {
|
|
929
|
+
theme: /* @__PURE__ */ new Set(["light", "dark", "system"]),
|
|
930
|
+
density: /* @__PURE__ */ new Set(["compact", "comfortable", "spacious"]),
|
|
931
|
+
timeFormat: /* @__PURE__ */ new Set(["12h", "24h"])
|
|
932
|
+
};
|
|
933
|
+
var listeners2 = /* @__PURE__ */ new Set();
|
|
934
|
+
var cachedPreferences = null;
|
|
935
|
+
var pendingRequest = null;
|
|
936
|
+
var syncCleanup = null;
|
|
937
|
+
function extractResponseData(response) {
|
|
938
|
+
var _a;
|
|
939
|
+
return ((_a = response == null ? void 0 : response.data) == null ? void 0 : _a.data) ?? (response == null ? void 0 : response.data) ?? response ?? {};
|
|
940
|
+
}
|
|
941
|
+
function normalizePreferences(value = {}) {
|
|
942
|
+
const next = { ...PREFERENCE_DEFAULTS };
|
|
943
|
+
for (const key of Object.keys(PREFERENCE_DEFAULTS)) {
|
|
944
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
945
|
+
const candidate = value[key];
|
|
946
|
+
if (ALLOWED[key] && !ALLOWED[key].has(candidate)) continue;
|
|
947
|
+
if (typeof PREFERENCE_DEFAULTS[key] !== typeof candidate) continue;
|
|
948
|
+
next[key] = candidate;
|
|
949
|
+
}
|
|
950
|
+
return next;
|
|
951
|
+
}
|
|
952
|
+
function resolveTheme(theme, matchMedia2 = globalThis.matchMedia) {
|
|
953
|
+
if (theme === "dark" || theme === "light") return theme;
|
|
954
|
+
return typeof matchMedia2 === "function" && matchMedia2("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
955
|
+
}
|
|
956
|
+
function announce(preferences2) {
|
|
957
|
+
cachedPreferences = normalizePreferences(preferences2);
|
|
958
|
+
listeners2.forEach((listener) => listener(cachedPreferences));
|
|
959
|
+
return cachedPreferences;
|
|
960
|
+
}
|
|
961
|
+
async function getPreferences({ force = false } = {}) {
|
|
962
|
+
if (cachedPreferences && !force) return cachedPreferences;
|
|
963
|
+
if (pendingRequest) return pendingRequest;
|
|
964
|
+
pendingRequest = api_default.get("/account/preferences").then((response) => announce(extractResponseData(response))).finally(() => {
|
|
965
|
+
pendingRequest = null;
|
|
966
|
+
});
|
|
967
|
+
return pendingRequest;
|
|
968
|
+
}
|
|
969
|
+
async function updatePreferences(patch) {
|
|
970
|
+
const allowedPatch = {};
|
|
971
|
+
for (const key of Object.keys(PREFERENCE_DEFAULTS)) {
|
|
972
|
+
if (Object.hasOwn(patch, key)) allowedPatch[key] = patch[key];
|
|
973
|
+
}
|
|
974
|
+
const candidate = normalizePreferences({ ...cachedPreferences || PREFERENCE_DEFAULTS, ...allowedPatch });
|
|
975
|
+
const response = await api_default.put("/account/preferences", allowedPatch);
|
|
976
|
+
const saved = extractResponseData(response);
|
|
977
|
+
return announce(Object.keys(saved).length ? saved : candidate);
|
|
978
|
+
}
|
|
979
|
+
function subscribePreferences(listener) {
|
|
980
|
+
listeners2.add(listener);
|
|
981
|
+
if (cachedPreferences) listener(cachedPreferences);
|
|
982
|
+
return () => listeners2.delete(listener);
|
|
983
|
+
}
|
|
984
|
+
function clearPreferenceCache() {
|
|
985
|
+
cachedPreferences = null;
|
|
986
|
+
pendingRequest = null;
|
|
987
|
+
}
|
|
988
|
+
function applyPreferences(preferences2, documentRef = globalThis.document) {
|
|
989
|
+
var _a, _b, _c;
|
|
990
|
+
if (!(documentRef == null ? void 0 : documentRef.documentElement)) return;
|
|
991
|
+
const value = normalizePreferences(preferences2);
|
|
992
|
+
const root = documentRef.documentElement;
|
|
993
|
+
const resolvedTheme = resolveTheme(value.theme, (_b = (_a = documentRef.defaultView) == null ? void 0 : _a.matchMedia) == null ? void 0 : _b.bind(documentRef.defaultView));
|
|
994
|
+
root.dataset.theme = resolvedTheme;
|
|
995
|
+
root.dataset.themePreference = value.theme;
|
|
996
|
+
root.dataset.colorScheme = value.colorScheme;
|
|
997
|
+
root.dataset.density = value.density;
|
|
998
|
+
root.lang = value.language;
|
|
999
|
+
root.style.colorScheme = resolvedTheme;
|
|
1000
|
+
root.style.setProperty("--user-accent-color", {
|
|
1001
|
+
blue: "#1976d2",
|
|
1002
|
+
green: "#2e7d32",
|
|
1003
|
+
purple: "#7b1fa2",
|
|
1004
|
+
orange: "#c65d00",
|
|
1005
|
+
red: "#c62828"
|
|
1006
|
+
}[value.colorScheme] || "#1976d2");
|
|
1007
|
+
root.classList.toggle("user-reduced-motion", value.reducedMotion);
|
|
1008
|
+
root.classList.toggle("user-high-contrast", value.highContrast);
|
|
1009
|
+
root.classList.toggle("user-large-text", value.largeText);
|
|
1010
|
+
const styleId = "sso-user-preference-styles";
|
|
1011
|
+
if (!documentRef.getElementById(styleId)) {
|
|
1012
|
+
const style = documentRef.createElement("style");
|
|
1013
|
+
style.id = styleId;
|
|
1014
|
+
style.textContent = `
|
|
1015
|
+
html.user-large-text { font-size: 112.5%; }
|
|
1016
|
+
html.user-high-contrast { filter: contrast(1.15); }
|
|
1017
|
+
html[data-density="compact"] { --user-density-padding: 6px; }
|
|
1018
|
+
html[data-density="comfortable"] { --user-density-padding: 10px; }
|
|
1019
|
+
html[data-density="spacious"] { --user-density-padding: 14px; }
|
|
1020
|
+
html[data-density] .MuiListItem-root { padding-top: var(--user-density-padding); padding-bottom: var(--user-density-padding); }
|
|
1021
|
+
html.user-reduced-motion *, html.user-reduced-motion *::before, html.user-reduced-motion *::after {
|
|
1022
|
+
animation-duration: 0.01ms !important;
|
|
1023
|
+
animation-iteration-count: 1 !important;
|
|
1024
|
+
scroll-behavior: auto !important;
|
|
1025
|
+
transition-duration: 0.01ms !important;
|
|
1026
|
+
}
|
|
1027
|
+
`;
|
|
1028
|
+
(_c = documentRef.head) == null ? void 0 : _c.appendChild(style);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
function startPreferenceSync({ refreshOnFocus = true } = {}) {
|
|
1032
|
+
var _a, _b;
|
|
1033
|
+
if (syncCleanup || typeof globalThis.addEventListener !== "function") return syncCleanup || (() => {
|
|
1034
|
+
});
|
|
1035
|
+
const refresh = () => {
|
|
1036
|
+
if (!globalThis.document || globalThis.document.visibilityState === "visible") {
|
|
1037
|
+
getPreferences({ force: true }).catch(() => {
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
const onVisibility = () => refresh();
|
|
1042
|
+
(_b = (_a = globalThis.document) == null ? void 0 : _a.addEventListener) == null ? void 0 : _b.call(_a, "visibilitychange", onVisibility);
|
|
1043
|
+
if (refreshOnFocus) globalThis.addEventListener("focus", refresh);
|
|
1044
|
+
syncCleanup = () => {
|
|
1045
|
+
var _a2, _b2;
|
|
1046
|
+
(_b2 = (_a2 = globalThis.document) == null ? void 0 : _a2.removeEventListener) == null ? void 0 : _b2.call(_a2, "visibilitychange", onVisibility);
|
|
1047
|
+
if (refreshOnFocus) globalThis.removeEventListener("focus", refresh);
|
|
1048
|
+
syncCleanup = null;
|
|
1049
|
+
};
|
|
1050
|
+
return syncCleanup;
|
|
1051
|
+
}
|
|
1052
|
+
function localeFor(language) {
|
|
1053
|
+
const supported = Intl.DateTimeFormat.supportedLocalesOf([language]);
|
|
1054
|
+
return supported[0] || "en";
|
|
1055
|
+
}
|
|
1056
|
+
function fixedOffset(value) {
|
|
1057
|
+
const match = /^UTC([+-])(\d{2}):(\d{2})$/.exec(value || "");
|
|
1058
|
+
if (!match) return null;
|
|
1059
|
+
const minutes = Number(match[2]) * 60 + Number(match[3]);
|
|
1060
|
+
return (match[1] === "-" ? -1 : 1) * minutes;
|
|
1061
|
+
}
|
|
1062
|
+
function formatUserDate(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, options = {}) {
|
|
1063
|
+
const value = normalizePreferences(preferences2);
|
|
1064
|
+
const date = input instanceof Date ? input : new Date(input);
|
|
1065
|
+
const offset = fixedOffset(value.timezone);
|
|
1066
|
+
const adjusted = offset === null ? date : new Date(date.getTime() + offset * 6e4);
|
|
1067
|
+
const timeZone = offset === null ? value.timezone : "UTC";
|
|
1068
|
+
const hasExplicitParts = ["year", "month", "day", "weekday", "hour", "minute", "second", "dateStyle", "timeStyle"].some((key) => options[key] !== void 0);
|
|
1069
|
+
const dateOptions = hasExplicitParts ? options : { year: "numeric", month: "2-digit", day: "2-digit" };
|
|
1070
|
+
const formatter = new Intl.DateTimeFormat(localeFor(value.language), {
|
|
1071
|
+
...dateOptions,
|
|
1072
|
+
timeZone,
|
|
1073
|
+
...dateOptions.hour || dateOptions.timeStyle ? { hour12: value.timeFormat === "12h" } : {}
|
|
1074
|
+
});
|
|
1075
|
+
if (!hasExplicitParts) {
|
|
1076
|
+
const parts = Object.fromEntries(formatter.formatToParts(adjusted).map((part) => [part.type, part.value]));
|
|
1077
|
+
if (value.dateFormat === "DD/MM/YYYY") return `${parts.day}/${parts.month}/${parts.year}`;
|
|
1078
|
+
if (value.dateFormat === "YYYY-MM-DD") return `${parts.year}-${parts.month}-${parts.day}`;
|
|
1079
|
+
return `${parts.month}/${parts.day}/${parts.year}`;
|
|
1080
|
+
}
|
|
1081
|
+
return formatter.format(adjusted);
|
|
1082
|
+
}
|
|
1083
|
+
function formatUserNumber(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, options = {}) {
|
|
1084
|
+
const value = normalizePreferences(preferences2);
|
|
1085
|
+
return new Intl.NumberFormat(localeFor(value.language), options).format(input);
|
|
1086
|
+
}
|
|
1087
|
+
function formatUserRelativeTime(input, preferences2 = cachedPreferences || PREFERENCE_DEFAULTS, now = /* @__PURE__ */ new Date()) {
|
|
1088
|
+
const value = normalizePreferences(preferences2);
|
|
1089
|
+
const deltaSeconds = (new Date(input).getTime() - now.getTime()) / 1e3;
|
|
1090
|
+
const units = [
|
|
1091
|
+
["year", 31536e3],
|
|
1092
|
+
["month", 2592e3],
|
|
1093
|
+
["week", 604800],
|
|
1094
|
+
["day", 86400],
|
|
1095
|
+
["hour", 3600],
|
|
1096
|
+
["minute", 60],
|
|
1097
|
+
["second", 1]
|
|
1098
|
+
];
|
|
1099
|
+
const [unit, seconds] = units.find(([, size]) => Math.abs(deltaSeconds) >= size) || units.at(-1);
|
|
1100
|
+
return new Intl.RelativeTimeFormat(localeFor(value.language), { numeric: "auto" }).format(Math.round(deltaSeconds / seconds), unit);
|
|
1101
|
+
}
|
|
1102
|
+
var preferences = {
|
|
1103
|
+
defaults: PREFERENCE_DEFAULTS,
|
|
1104
|
+
get: getPreferences,
|
|
1105
|
+
update: updatePreferences,
|
|
1106
|
+
subscribe: subscribePreferences,
|
|
1107
|
+
clearCache: clearPreferenceCache,
|
|
1108
|
+
apply: applyPreferences,
|
|
1109
|
+
startSync: startPreferenceSync,
|
|
1110
|
+
formatDate: formatUserDate,
|
|
1111
|
+
formatNumber: formatUserNumber,
|
|
1112
|
+
formatRelativeTime: formatUserRelativeTime,
|
|
1113
|
+
normalize: normalizePreferences,
|
|
1114
|
+
resolveTheme
|
|
1115
|
+
};
|
|
1116
|
+
|
|
808
1117
|
// react/AuthProvider.jsx
|
|
809
1118
|
import React, { createContext, useState, useEffect, useRef } from "react";
|
|
810
1119
|
var AuthContext = createContext();
|
|
@@ -868,7 +1177,8 @@ function AuthProvider({ children, onSessionExpired }) {
|
|
|
868
1177
|
console.log("\u{1F4E5} Profile response status:", res.status);
|
|
869
1178
|
if (!res.ok) throw new Error("Failed to fetch user");
|
|
870
1179
|
return res.json();
|
|
871
|
-
}).then((
|
|
1180
|
+
}).then((responseBody) => {
|
|
1181
|
+
const userData = (responseBody == null ? void 0 : responseBody.data) ?? responseBody;
|
|
872
1182
|
console.log("\u2705 Profile fetched successfully:", userData.email);
|
|
873
1183
|
setUser(userData);
|
|
874
1184
|
setSessionValid(true);
|
|
@@ -1015,6 +1325,62 @@ var useSessionMonitor = (options = {}) => {
|
|
|
1015
1325
|
};
|
|
1016
1326
|
};
|
|
1017
1327
|
|
|
1328
|
+
// react/PreferencesProvider.jsx
|
|
1329
|
+
import React2, { createContext as createContext2, useContext as useContext2, useEffect as useEffect3, useMemo, useState as useState2 } from "react";
|
|
1330
|
+
var PreferencesContext = createContext2(null);
|
|
1331
|
+
function PreferencesProvider({ children, initialPreferences, applyToDocument = true }) {
|
|
1332
|
+
const [preferences2, setPreferences] = useState2(() => ({
|
|
1333
|
+
...PREFERENCE_DEFAULTS,
|
|
1334
|
+
...initialPreferences
|
|
1335
|
+
}));
|
|
1336
|
+
const [loading, setLoading] = useState2(true);
|
|
1337
|
+
const [error, setError] = useState2(null);
|
|
1338
|
+
useEffect3(() => {
|
|
1339
|
+
const unsubscribe = subscribePreferences((next) => {
|
|
1340
|
+
setPreferences(next);
|
|
1341
|
+
setLoading(false);
|
|
1342
|
+
setError(null);
|
|
1343
|
+
});
|
|
1344
|
+
const stopSync = startPreferenceSync();
|
|
1345
|
+
getPreferences().catch((nextError) => {
|
|
1346
|
+
setError(nextError);
|
|
1347
|
+
setLoading(false);
|
|
1348
|
+
});
|
|
1349
|
+
return () => {
|
|
1350
|
+
unsubscribe();
|
|
1351
|
+
stopSync();
|
|
1352
|
+
};
|
|
1353
|
+
}, []);
|
|
1354
|
+
useEffect3(() => {
|
|
1355
|
+
if (applyToDocument) applyPreferences(preferences2);
|
|
1356
|
+
}, [applyToDocument, preferences2]);
|
|
1357
|
+
useEffect3(() => {
|
|
1358
|
+
var _a;
|
|
1359
|
+
if (preferences2.theme !== "system" || typeof matchMedia !== "function") return void 0;
|
|
1360
|
+
const media = matchMedia("(prefers-color-scheme: dark)");
|
|
1361
|
+
const applySystemTheme = () => applyPreferences(preferences2);
|
|
1362
|
+
(_a = media.addEventListener) == null ? void 0 : _a.call(media, "change", applySystemTheme);
|
|
1363
|
+
return () => {
|
|
1364
|
+
var _a2;
|
|
1365
|
+
return (_a2 = media.removeEventListener) == null ? void 0 : _a2.call(media, "change", applySystemTheme);
|
|
1366
|
+
};
|
|
1367
|
+
}, [preferences2]);
|
|
1368
|
+
const value = useMemo(() => ({
|
|
1369
|
+
preferences: preferences2,
|
|
1370
|
+
resolvedTheme: resolveTheme(preferences2.theme),
|
|
1371
|
+
loading,
|
|
1372
|
+
error,
|
|
1373
|
+
refresh: () => getPreferences({ force: true }),
|
|
1374
|
+
update: updatePreferences
|
|
1375
|
+
}), [error, loading, preferences2]);
|
|
1376
|
+
return /* @__PURE__ */ React2.createElement(PreferencesContext.Provider, { value }, children);
|
|
1377
|
+
}
|
|
1378
|
+
function usePreferences() {
|
|
1379
|
+
const context = useContext2(PreferencesContext);
|
|
1380
|
+
if (!context) throw new Error("usePreferences must be used within a PreferencesProvider");
|
|
1381
|
+
return context;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1018
1384
|
// index.js
|
|
1019
1385
|
var auth = {
|
|
1020
1386
|
// ๐ง Config
|
|
@@ -1045,6 +1411,10 @@ var auth = {
|
|
|
1045
1411
|
// โ
Debug function
|
|
1046
1412
|
// ๐ Authenticated API client
|
|
1047
1413
|
api: api_default,
|
|
1414
|
+
preferences,
|
|
1415
|
+
// ๐ Safe authentication diagnostics
|
|
1416
|
+
getDiagnosticContext,
|
|
1417
|
+
emitAuthDiagnostic,
|
|
1048
1418
|
// ๐งช Utilities
|
|
1049
1419
|
decodeToken,
|
|
1050
1420
|
isTokenExpired,
|
|
@@ -1091,8 +1461,21 @@ var auth = {
|
|
|
1091
1461
|
};
|
|
1092
1462
|
export {
|
|
1093
1463
|
AuthProvider,
|
|
1464
|
+
PREFERENCE_DEFAULTS,
|
|
1465
|
+
PreferencesProvider,
|
|
1466
|
+
applyPreferences,
|
|
1094
1467
|
auth,
|
|
1468
|
+
formatUserDate,
|
|
1469
|
+
formatUserNumber,
|
|
1470
|
+
formatUserRelativeTime,
|
|
1471
|
+
getPreferences,
|
|
1472
|
+
normalizePreferences,
|
|
1473
|
+
resolveTheme,
|
|
1474
|
+
startPreferenceSync,
|
|
1475
|
+
subscribePreferences,
|
|
1476
|
+
updatePreferences,
|
|
1095
1477
|
useAuth,
|
|
1478
|
+
usePreferences,
|
|
1096
1479
|
useSessionMonitor
|
|
1097
1480
|
};
|
|
1098
1481
|
//# sourceMappingURL=index.js.map
|