@spidy092/auth-client 3.1.2 → 3.1.4
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 +117 -61
- package/dist/api.cjs.map +1 -1
- package/dist/api.js +117 -61
- package/dist/api.js.map +1 -1
- package/dist/index.cjs +290 -96
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +290 -96
- package/dist/index.js.map +1 -1
- package/dist/preferences.cjs +117 -61
- package/dist/preferences.cjs.map +1 -1
- package/dist/preferences.js +117 -61
- package/dist/preferences.js.map +1 -1
- package/dist/react/AuthProvider.cjs +266 -96
- package/dist/react/AuthProvider.cjs.map +1 -1
- package/dist/react/AuthProvider.js +266 -96
- package/dist/react/AuthProvider.js.map +1 -1
- package/dist/react/PreferencesProvider.cjs +117 -61
- package/dist/react/PreferencesProvider.cjs.map +1 -1
- package/dist/react/PreferencesProvider.js +117 -61
- package/dist/react/PreferencesProvider.js.map +1 -1
- package/dist/react/useAuth.cjs +7 -0
- package/dist/react/useAuth.cjs.map +1 -1
- package/dist/react/useAuth.js +7 -0
- package/dist/react/useAuth.js.map +1 -1
- package/dist/react/useSessionMonitor.cjs +157 -66
- package/dist/react/useSessionMonitor.cjs.map +1 -1
- package/dist/react/useSessionMonitor.js +157 -66
- package/dist/react/useSessionMonitor.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -268,6 +268,44 @@ var config = {
|
|
|
268
268
|
// ⚠️ In true production, set to false and rely on httpOnly cookies
|
|
269
269
|
persistRefreshToken: false
|
|
270
270
|
};
|
|
271
|
+
var RUNTIME_POLICY_DEFAULTS = {
|
|
272
|
+
tokenRefreshBuffer: 60,
|
|
273
|
+
sessionValidationInterval: 15 * 60 * 1e3,
|
|
274
|
+
enableSessionValidation: true,
|
|
275
|
+
enableProactiveRefresh: true,
|
|
276
|
+
validateOnVisibility: true
|
|
277
|
+
};
|
|
278
|
+
function validateRuntimePolicy(policy = {}) {
|
|
279
|
+
const numberInRange = (value, min, max) => Number.isInteger(value) && value >= min && value <= max;
|
|
280
|
+
const result = { ...RUNTIME_POLICY_DEFAULTS };
|
|
281
|
+
if (numberInRange(policy.tokenRefreshBuffer, 10, 300)) result.tokenRefreshBuffer = policy.tokenRefreshBuffer;
|
|
282
|
+
if (numberInRange(policy.sessionValidationInterval, 6e4, 864e5)) result.sessionValidationInterval = policy.sessionValidationInterval;
|
|
283
|
+
for (const key of ["enableSessionValidation", "enableProactiveRefresh", "validateOnVisibility"]) {
|
|
284
|
+
if (typeof policy[key] === "boolean") result[key] = policy[key];
|
|
285
|
+
}
|
|
286
|
+
return result;
|
|
287
|
+
}
|
|
288
|
+
function applyRuntimePolicy(policy = {}) {
|
|
289
|
+
config = { ...config, ...validateRuntimePolicy(policy) };
|
|
290
|
+
return getConfig();
|
|
291
|
+
}
|
|
292
|
+
async function loadRuntimePolicy() {
|
|
293
|
+
const { authBaseUrl, clientKey } = config;
|
|
294
|
+
if (!authBaseUrl || !clientKey || typeof fetch !== "function") return getConfig();
|
|
295
|
+
try {
|
|
296
|
+
const response = await fetch(`${authBaseUrl.replace(/\/+$/, "")}/clients/${encodeURIComponent(clientKey)}/config`, {
|
|
297
|
+
method: "GET",
|
|
298
|
+
credentials: "include",
|
|
299
|
+
headers: { Accept: "application/json" }
|
|
300
|
+
});
|
|
301
|
+
if (!response.ok) throw new Error(`runtime policy HTTP ${response.status}`);
|
|
302
|
+
const payload = await response.json();
|
|
303
|
+
return applyRuntimePolicy(payload.authentication_policy || {});
|
|
304
|
+
} catch (error) {
|
|
305
|
+
console.warn("[auth-client] Runtime policy unavailable; using safe defaults", error.message);
|
|
306
|
+
return applyRuntimePolicy({});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
271
309
|
function setConfig(customConfig = {}) {
|
|
272
310
|
if (!customConfig.clientKey || !customConfig.authBaseUrl) {
|
|
273
311
|
throw new Error("Missing required config: clientKey and authBaseUrl are required");
|
|
@@ -353,6 +391,28 @@ function clearLoginLock() {
|
|
|
353
391
|
} catch {
|
|
354
392
|
}
|
|
355
393
|
}
|
|
394
|
+
async function reportClientDiagnostic(safe) {
|
|
395
|
+
if (typeof fetch !== "function" || typeof getToken !== "function") return;
|
|
396
|
+
if (safe.outcome === "SUCCESS" && !safe.event.startsWith("PROFILE_") && !safe.event.startsWith("SESSION_")) return;
|
|
397
|
+
const { authBaseUrl } = getConfig();
|
|
398
|
+
const token = getToken();
|
|
399
|
+
if (!authBaseUrl || !token) return;
|
|
400
|
+
try {
|
|
401
|
+
await fetch(`${authBaseUrl.replace(/\/+$/, "")}/client-telemetry`, {
|
|
402
|
+
method: "POST",
|
|
403
|
+
credentials: "include",
|
|
404
|
+
keepalive: true,
|
|
405
|
+
headers: {
|
|
406
|
+
"Content-Type": "application/json",
|
|
407
|
+
Authorization: `Bearer ${token}`,
|
|
408
|
+
"X-Correlation-ID": safe.correlationId,
|
|
409
|
+
"X-Request-ID": safe.correlationId
|
|
410
|
+
},
|
|
411
|
+
body: JSON.stringify(safe)
|
|
412
|
+
});
|
|
413
|
+
} catch {
|
|
414
|
+
}
|
|
415
|
+
}
|
|
356
416
|
async function emitAuthDiagnostic(event, outcome, reasonCode, details = {}) {
|
|
357
417
|
const context = getDiagnosticContext();
|
|
358
418
|
const safe = {
|
|
@@ -369,12 +429,13 @@ async function emitAuthDiagnostic(event, outcome, reasonCode, details = {}) {
|
|
|
369
429
|
};
|
|
370
430
|
const method = outcome === "FAILURE" ? "error" : outcome === "WARNING" ? "warn" : "info";
|
|
371
431
|
console[method]("[auth-client]", safe);
|
|
432
|
+
void reportClientDiagnostic(safe);
|
|
372
433
|
return safe;
|
|
373
434
|
}
|
|
374
435
|
|
|
375
436
|
// core.js
|
|
376
437
|
var callbackProcessed = false;
|
|
377
|
-
function login(clientKeyArg, redirectUriArg) {
|
|
438
|
+
function login(clientKeyArg, redirectUriArg, options = {}) {
|
|
378
439
|
resetCallbackState();
|
|
379
440
|
const {
|
|
380
441
|
clientKey: defaultClientKey,
|
|
@@ -397,23 +458,24 @@ function login(clientKeyArg, redirectUriArg) {
|
|
|
397
458
|
sessionStorage.setItem("originalApp", clientKey);
|
|
398
459
|
sessionStorage.setItem("returnUrl", redirectUri);
|
|
399
460
|
if (isRouterMode()) {
|
|
400
|
-
return routerLogin(clientKey, redirectUri);
|
|
461
|
+
return routerLogin(clientKey, redirectUri, options);
|
|
401
462
|
} else {
|
|
402
|
-
return clientLogin(clientKey, redirectUri);
|
|
463
|
+
return clientLogin(clientKey, redirectUri, options);
|
|
403
464
|
}
|
|
404
465
|
}
|
|
405
|
-
function routerLogin(clientKey, redirectUri) {
|
|
466
|
+
function routerLogin(clientKey, redirectUri, options = {}) {
|
|
406
467
|
const { authBaseUrl } = getConfig();
|
|
407
468
|
const params = new URLSearchParams();
|
|
408
469
|
if (redirectUri) {
|
|
409
470
|
params.append("redirect_uri", redirectUri);
|
|
410
471
|
}
|
|
472
|
+
if (options.switchAccount || options.switch_account) params.append("switch_account", "true");
|
|
411
473
|
params.append("correlation_id", getDiagnosticContext().correlationId);
|
|
412
474
|
const query = params.toString();
|
|
413
475
|
const backendLoginUrl = `${authBaseUrl}/login/${clientKey}${query ? `?${query}` : ""}`;
|
|
414
476
|
window.location.href = backendLoginUrl;
|
|
415
477
|
}
|
|
416
|
-
function clientLogin(clientKey, redirectUri) {
|
|
478
|
+
function clientLogin(clientKey, redirectUri, options = {}) {
|
|
417
479
|
const { accountUiUrl } = getConfig();
|
|
418
480
|
const params = new URLSearchParams({
|
|
419
481
|
client: clientKey
|
|
@@ -421,6 +483,7 @@ function clientLogin(clientKey, redirectUri) {
|
|
|
421
483
|
if (redirectUri) {
|
|
422
484
|
params.append("redirect_uri", redirectUri);
|
|
423
485
|
}
|
|
486
|
+
if (options.switchAccount || options.switch_account) params.append("switch_account", "true");
|
|
424
487
|
const centralizedLoginUrl = `${accountUiUrl}/login?${params.toString()}`;
|
|
425
488
|
window.location.href = centralizedLoginUrl;
|
|
426
489
|
}
|
|
@@ -546,6 +609,28 @@ function resetCallbackState() {
|
|
|
546
609
|
}
|
|
547
610
|
var refreshInProgress = false;
|
|
548
611
|
var refreshPromise = null;
|
|
612
|
+
async function withCrossTabRefreshLock(clientKey, tokenBeforeRefresh, refreshRequest) {
|
|
613
|
+
var _a;
|
|
614
|
+
const lockName = `auth-refresh-${clientKey}`;
|
|
615
|
+
const run = async () => {
|
|
616
|
+
try {
|
|
617
|
+
const persistedToken = localStorage.getItem("authToken");
|
|
618
|
+
if (tokenBeforeRefresh && persistedToken && persistedToken !== tokenBeforeRefresh) {
|
|
619
|
+
setToken(persistedToken);
|
|
620
|
+
emitAuthDiagnostic("TOKEN_REFRESH_REUSED_CROSS_TAB", "SUCCESS", "CROSS_TAB_ROTATION", {
|
|
621
|
+
clientKey
|
|
622
|
+
});
|
|
623
|
+
return persistedToken;
|
|
624
|
+
}
|
|
625
|
+
} catch {
|
|
626
|
+
}
|
|
627
|
+
return refreshRequest();
|
|
628
|
+
};
|
|
629
|
+
if (typeof navigator !== "undefined" && ((_a = navigator.locks) == null ? void 0 : _a.request)) {
|
|
630
|
+
return navigator.locks.request(lockName, run);
|
|
631
|
+
}
|
|
632
|
+
return run();
|
|
633
|
+
}
|
|
549
634
|
async function refreshToken() {
|
|
550
635
|
const { clientKey, authBaseUrl } = getConfig();
|
|
551
636
|
if (refreshInProgress && refreshPromise) {
|
|
@@ -554,70 +639,74 @@ async function refreshToken() {
|
|
|
554
639
|
}
|
|
555
640
|
refreshInProgress = true;
|
|
556
641
|
refreshPromise = (async () => {
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
hasStoredRefreshToken: !!storedRefreshToken
|
|
564
|
-
});
|
|
565
|
-
const requestOptions = {
|
|
566
|
-
method: "POST",
|
|
567
|
-
credentials: "include",
|
|
568
|
-
// ✅ Include httpOnly cookies (for HTTPS)
|
|
569
|
-
headers: {
|
|
570
|
-
...diagnosticHeaders(),
|
|
571
|
-
"Content-Type": "application/json"
|
|
572
|
-
}
|
|
573
|
-
};
|
|
574
|
-
if (storedRefreshToken) {
|
|
575
|
-
requestOptions.body = JSON.stringify({ refreshToken: storedRefreshToken });
|
|
576
|
-
console.log("\u{1F4E6} Sending refresh token in body only (Header skipped) v3.0.2");
|
|
577
|
-
}
|
|
578
|
-
const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
|
|
579
|
-
if (!response.ok) {
|
|
580
|
-
const errorText = await response.text();
|
|
581
|
-
let serverCode = null;
|
|
582
|
-
try {
|
|
583
|
-
serverCode = ((_a = JSON.parse(errorText)) == null ? void 0 : _a.error) || ((_b = JSON.parse(errorText)) == null ? void 0 : _b.code);
|
|
584
|
-
} catch {
|
|
585
|
-
}
|
|
586
|
-
emitAuthDiagnostic("TOKEN_REFRESH_REJECTED", "FAILURE", serverCode || `HTTP_${response.status}`, {
|
|
642
|
+
const tokenBeforeRefresh = getToken();
|
|
643
|
+
const refreshRequest = async () => {
|
|
644
|
+
var _a, _b, _c, _d, _e, _f;
|
|
645
|
+
try {
|
|
646
|
+
const storedRefreshToken = getRefreshToken();
|
|
647
|
+
console.log("\u{1F504} Refreshing token:", {
|
|
587
648
|
clientKey,
|
|
588
|
-
|
|
649
|
+
mode: isRouterMode() ? "ROUTER" : "CLIENT",
|
|
650
|
+
hasStoredRefreshToken: !!storedRefreshToken
|
|
589
651
|
});
|
|
590
|
-
const
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
652
|
+
const requestOptions = {
|
|
653
|
+
method: "POST",
|
|
654
|
+
credentials: "include",
|
|
655
|
+
// ✅ Include httpOnly cookies (for HTTPS)
|
|
656
|
+
headers: {
|
|
657
|
+
...diagnosticHeaders(),
|
|
658
|
+
"Content-Type": "application/json"
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
if (storedRefreshToken) {
|
|
662
|
+
requestOptions.body = JSON.stringify({ refreshToken: storedRefreshToken });
|
|
663
|
+
console.log("\u{1F4E6} Sending refresh token in body only (Header skipped) v3.0.2");
|
|
664
|
+
}
|
|
665
|
+
const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
|
|
666
|
+
if (!response.ok) {
|
|
667
|
+
const errorText = await response.text();
|
|
668
|
+
let serverCode = null;
|
|
669
|
+
try {
|
|
670
|
+
serverCode = ((_a = JSON.parse(errorText)) == null ? void 0 : _a.error) || ((_b = JSON.parse(errorText)) == null ? void 0 : _b.code);
|
|
671
|
+
} catch {
|
|
672
|
+
}
|
|
673
|
+
emitAuthDiagnostic("TOKEN_REFRESH_REJECTED", "FAILURE", serverCode || `HTTP_${response.status}`, {
|
|
674
|
+
clientKey,
|
|
675
|
+
status: response.status
|
|
676
|
+
});
|
|
677
|
+
const refreshError = new Error(`Refresh failed: ${response.status}`);
|
|
678
|
+
refreshError.code = serverCode || `HTTP_${response.status}`;
|
|
679
|
+
refreshError.status = response.status;
|
|
680
|
+
throw refreshError;
|
|
681
|
+
}
|
|
682
|
+
const data = await response.json();
|
|
683
|
+
const { access_token, refresh_token: new_refresh_token } = data;
|
|
684
|
+
if (!access_token) {
|
|
685
|
+
throw new Error("No access token in refresh response");
|
|
686
|
+
}
|
|
687
|
+
setToken(access_token);
|
|
688
|
+
if (new_refresh_token) {
|
|
689
|
+
setRefreshToken(new_refresh_token);
|
|
690
|
+
console.log("\u{1F504} New refresh token stored from rotation");
|
|
691
|
+
}
|
|
692
|
+
console.log("\u2705 Token refresh successful, listeners notified");
|
|
693
|
+
emitAuthDiagnostic("TOKEN_REFRESH_COMPLETED", "SUCCESS", "NONE", { clientKey });
|
|
694
|
+
return access_token;
|
|
695
|
+
} catch (err) {
|
|
696
|
+
console.error("\u274C Token refresh error:", err);
|
|
697
|
+
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"));
|
|
698
|
+
if (isAuthRejection) {
|
|
699
|
+
clearToken();
|
|
700
|
+
clearRefreshToken();
|
|
701
|
+
}
|
|
702
|
+
throw err;
|
|
614
703
|
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
})
|
|
704
|
+
};
|
|
705
|
+
return withCrossTabRefreshLock(clientKey, tokenBeforeRefresh, refreshRequest);
|
|
706
|
+
})().finally(() => {
|
|
707
|
+
refreshInProgress = false;
|
|
708
|
+
refreshPromise = null;
|
|
709
|
+
});
|
|
621
710
|
return refreshPromise;
|
|
622
711
|
}
|
|
623
712
|
async function validateCurrentSession() {
|
|
@@ -1170,23 +1259,72 @@ var preferences = {
|
|
|
1170
1259
|
// react/AuthProvider.jsx
|
|
1171
1260
|
var import_react = __toESM(require("react"), 1);
|
|
1172
1261
|
var AuthContext = (0, import_react.createContext)();
|
|
1173
|
-
function AuthProvider({ children, onSessionExpired }) {
|
|
1262
|
+
function AuthProvider({ children, onSessionExpired, manageSessionSecurity = true }) {
|
|
1174
1263
|
const [token, setTokenState] = (0, import_react.useState)(getToken());
|
|
1175
1264
|
const [user, setUser] = (0, import_react.useState)(null);
|
|
1176
1265
|
const [loading, setLoading] = (0, import_react.useState)(!!token);
|
|
1177
1266
|
const [sessionValid, setSessionValid] = (0, import_react.useState)(true);
|
|
1178
1267
|
const sessionSecurityRef = (0, import_react.useRef)(null);
|
|
1268
|
+
const onSessionExpiredRef = (0, import_react.useRef)(onSessionExpired);
|
|
1269
|
+
const recoveryInFlightRef = (0, import_react.useRef)(null);
|
|
1270
|
+
const profileRecoveryTokensRef = (0, import_react.useRef)(/* @__PURE__ */ new Set());
|
|
1271
|
+
(0, import_react.useEffect)(() => {
|
|
1272
|
+
onSessionExpiredRef.current = onSessionExpired;
|
|
1273
|
+
}, [onSessionExpired]);
|
|
1274
|
+
(0, import_react.useEffect)(() => {
|
|
1275
|
+
const unsubscribe = addTokenListener((nextToken, previousToken) => {
|
|
1276
|
+
if (nextToken === previousToken) return;
|
|
1277
|
+
setTokenState(nextToken);
|
|
1278
|
+
if (nextToken) {
|
|
1279
|
+
setSessionValid(true);
|
|
1280
|
+
setLoading(true);
|
|
1281
|
+
} else {
|
|
1282
|
+
setSessionValid(false);
|
|
1283
|
+
setUser(null);
|
|
1284
|
+
setLoading(false);
|
|
1285
|
+
}
|
|
1286
|
+
});
|
|
1287
|
+
return unsubscribe;
|
|
1288
|
+
}, []);
|
|
1289
|
+
const invalidateLocalSession = () => {
|
|
1290
|
+
clearToken();
|
|
1291
|
+
setTokenState(null);
|
|
1292
|
+
setUser(null);
|
|
1293
|
+
setSessionValid(false);
|
|
1294
|
+
setLoading(false);
|
|
1295
|
+
};
|
|
1179
1296
|
const handleSessionInvalid = (reason) => {
|
|
1180
1297
|
console.log("\u{1F6A8} AuthProvider: Session invalidated -", reason);
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1298
|
+
if (recoveryInFlightRef.current) return recoveryInFlightRef.current;
|
|
1299
|
+
const recover = async () => {
|
|
1300
|
+
const callback = onSessionExpiredRef.current;
|
|
1301
|
+
if (typeof callback === "function") {
|
|
1302
|
+
try {
|
|
1303
|
+
await callback(reason);
|
|
1304
|
+
const recoveredToken = getToken();
|
|
1305
|
+
if (recoveredToken) {
|
|
1306
|
+
setTokenState(recoveredToken);
|
|
1307
|
+
setSessionValid(true);
|
|
1308
|
+
setLoading(true);
|
|
1309
|
+
return true;
|
|
1310
|
+
}
|
|
1311
|
+
} catch (error) {
|
|
1312
|
+
await emitAuthDiagnostic("SESSION_RECOVERY_FAILED", "FAILURE", reason, {
|
|
1313
|
+
status: (error == null ? void 0 : error.status) || null,
|
|
1314
|
+
errorCode: (error == null ? void 0 : error.code) || null
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
invalidateLocalSession();
|
|
1319
|
+
return false;
|
|
1320
|
+
};
|
|
1321
|
+
recoveryInFlightRef.current = recover().finally(() => {
|
|
1322
|
+
recoveryInFlightRef.current = null;
|
|
1323
|
+
});
|
|
1324
|
+
return recoveryInFlightRef.current;
|
|
1187
1325
|
};
|
|
1188
1326
|
(0, import_react.useEffect)(() => {
|
|
1189
|
-
if (token && !sessionSecurityRef.current) {
|
|
1327
|
+
if (manageSessionSecurity && token && !sessionSecurityRef.current) {
|
|
1190
1328
|
console.log("\u{1F510} AuthProvider: Starting session security");
|
|
1191
1329
|
const unsubscribe = onSessionInvalid(handleSessionInvalid);
|
|
1192
1330
|
sessionSecurityRef.current = startSessionSecurity(handleSessionInvalid);
|
|
@@ -1198,11 +1336,11 @@ function AuthProvider({ children, onSessionExpired }) {
|
|
|
1198
1336
|
}
|
|
1199
1337
|
};
|
|
1200
1338
|
}
|
|
1201
|
-
if (!token && sessionSecurityRef.current) {
|
|
1339
|
+
if ((!manageSessionSecurity || !token) && sessionSecurityRef.current) {
|
|
1202
1340
|
sessionSecurityRef.current.stopAll();
|
|
1203
1341
|
sessionSecurityRef.current = null;
|
|
1204
1342
|
}
|
|
1205
|
-
}, [token]);
|
|
1343
|
+
}, [manageSessionSecurity, token]);
|
|
1206
1344
|
(0, import_react.useEffect)(() => {
|
|
1207
1345
|
console.log("\u{1F50D} AuthProvider useEffect triggered:", {
|
|
1208
1346
|
hasToken: !!token,
|
|
@@ -1223,26 +1361,79 @@ function AuthProvider({ children, onSessionExpired }) {
|
|
|
1223
1361
|
authBaseUrl,
|
|
1224
1362
|
tokenPreview: token.slice(0, 50) + "..."
|
|
1225
1363
|
});
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1364
|
+
let cancelled = false;
|
|
1365
|
+
const fetchProfile = async (accessToken2) => {
|
|
1366
|
+
const response = await fetch(`${authBaseUrl}/account/profile`, {
|
|
1367
|
+
headers: { Authorization: `Bearer ${accessToken2}` },
|
|
1368
|
+
credentials: "include"
|
|
1369
|
+
});
|
|
1370
|
+
console.log("\u{1F4E5} Profile response status:", response.status);
|
|
1371
|
+
if (!response.ok) {
|
|
1372
|
+
const error = new Error(`Profile request failed: ${response.status}`);
|
|
1373
|
+
error.status = response.status;
|
|
1374
|
+
throw error;
|
|
1375
|
+
}
|
|
1376
|
+
return response.json();
|
|
1377
|
+
};
|
|
1378
|
+
const loadProfile = async () => {
|
|
1379
|
+
try {
|
|
1380
|
+
const responseBody = await fetchProfile(token);
|
|
1381
|
+
if (cancelled) return;
|
|
1382
|
+
const userData = (responseBody == null ? void 0 : responseBody.data) ?? responseBody;
|
|
1383
|
+
console.log("\u2705 Profile fetched successfully:", userData.email);
|
|
1384
|
+
setUser(userData);
|
|
1385
|
+
setSessionValid(true);
|
|
1386
|
+
setLoading(false);
|
|
1387
|
+
} catch (error) {
|
|
1388
|
+
if (cancelled) return;
|
|
1389
|
+
const status = Number((error == null ? void 0 : error.status) || 0);
|
|
1390
|
+
const isUnauthorized = status === 401;
|
|
1391
|
+
const callback = onSessionExpiredRef.current;
|
|
1392
|
+
await emitAuthDiagnostic(
|
|
1393
|
+
"PROFILE_REQUEST_FAILED",
|
|
1394
|
+
isUnauthorized ? "FAILURE" : "WARNING",
|
|
1395
|
+
isUnauthorized ? "PROFILE_UNAUTHORIZED" : status ? `PROFILE_HTTP_${status}` : "PROFILE_NETWORK_ERROR",
|
|
1396
|
+
{ status, errorName: error == null ? void 0 : error.name, errorCode: error == null ? void 0 : error.code }
|
|
1397
|
+
);
|
|
1398
|
+
if (!isUnauthorized) {
|
|
1399
|
+
console.warn("\u26A0\uFE0F Profile unavailable; retaining the current auth session", error);
|
|
1400
|
+
setLoading(false);
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1403
|
+
if (typeof callback === "function" && !profileRecoveryTokensRef.current.has(token)) {
|
|
1404
|
+
profileRecoveryTokensRef.current.add(token);
|
|
1405
|
+
try {
|
|
1406
|
+
await callback("profile_unauthorized");
|
|
1407
|
+
const recoveredToken = getToken();
|
|
1408
|
+
if (recoveredToken && recoveredToken !== token) {
|
|
1409
|
+
setTokenState(recoveredToken);
|
|
1410
|
+
setSessionValid(true);
|
|
1411
|
+
setLoading(true);
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1414
|
+
if (recoveredToken) {
|
|
1415
|
+
const responseBody = await fetchProfile(recoveredToken);
|
|
1416
|
+
if (cancelled) return;
|
|
1417
|
+
const userData = (responseBody == null ? void 0 : responseBody.data) ?? responseBody;
|
|
1418
|
+
setUser(userData);
|
|
1419
|
+
setSessionValid(true);
|
|
1420
|
+
setLoading(false);
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
} catch (recoveryError) {
|
|
1424
|
+
await emitAuthDiagnostic("PROFILE_RECOVERY_FAILED", "FAILURE", "PROFILE_REFRESH_FAILED", {
|
|
1425
|
+
status: (recoveryError == null ? void 0 : recoveryError.status) || null,
|
|
1426
|
+
errorCode: (recoveryError == null ? void 0 : recoveryError.code) || null
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
invalidateLocalSession();
|
|
1431
|
+
}
|
|
1432
|
+
};
|
|
1433
|
+
void loadProfile();
|
|
1434
|
+
return () => {
|
|
1435
|
+
cancelled = true;
|
|
1436
|
+
};
|
|
1246
1437
|
}, [token]);
|
|
1247
1438
|
const login2 = (clientKey, redirectUri, state) => {
|
|
1248
1439
|
login(clientKey, redirectUri, state);
|
|
@@ -1271,6 +1462,7 @@ function AuthProvider({ children, onSessionExpired }) {
|
|
|
1271
1462
|
setToken(newToken);
|
|
1272
1463
|
setTokenState(newToken);
|
|
1273
1464
|
setSessionValid(true);
|
|
1465
|
+
if (newToken) setLoading(true);
|
|
1274
1466
|
},
|
|
1275
1467
|
clearToken: () => {
|
|
1276
1468
|
stopSessionSecurity();
|
|
@@ -1438,6 +1630,8 @@ function usePreferences() {
|
|
|
1438
1630
|
var auth = {
|
|
1439
1631
|
// 🔧 Config
|
|
1440
1632
|
setConfig,
|
|
1633
|
+
applyRuntimePolicy,
|
|
1634
|
+
loadRuntimePolicy,
|
|
1441
1635
|
getConfig,
|
|
1442
1636
|
isRouterMode,
|
|
1443
1637
|
// 🔐 Core flows
|