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