@spidy092/auth-client 3.1.1 → 3.1.3

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/index.cjs CHANGED
@@ -162,7 +162,7 @@ function shouldUseLocalStorage() {
162
162
  return false;
163
163
  }
164
164
  }
165
- function setRefreshToken2(token) {
165
+ function setRefreshToken(token) {
166
166
  if (!token) {
167
167
  clearRefreshToken();
168
168
  return;
@@ -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");
@@ -374,7 +412,7 @@ async function emitAuthDiagnostic(event, outcome, reasonCode, details = {}) {
374
412
 
375
413
  // core.js
376
414
  var callbackProcessed = false;
377
- function login(clientKeyArg, redirectUriArg) {
415
+ function login(clientKeyArg, redirectUriArg, options = {}) {
378
416
  resetCallbackState();
379
417
  const {
380
418
  clientKey: defaultClientKey,
@@ -397,23 +435,24 @@ function login(clientKeyArg, redirectUriArg) {
397
435
  sessionStorage.setItem("originalApp", clientKey);
398
436
  sessionStorage.setItem("returnUrl", redirectUri);
399
437
  if (isRouterMode()) {
400
- return routerLogin(clientKey, redirectUri);
438
+ return routerLogin(clientKey, redirectUri, options);
401
439
  } else {
402
- return clientLogin(clientKey, redirectUri);
440
+ return clientLogin(clientKey, redirectUri, options);
403
441
  }
404
442
  }
405
- function routerLogin(clientKey, redirectUri) {
443
+ function routerLogin(clientKey, redirectUri, options = {}) {
406
444
  const { authBaseUrl } = getConfig();
407
445
  const params = new URLSearchParams();
408
446
  if (redirectUri) {
409
447
  params.append("redirect_uri", redirectUri);
410
448
  }
449
+ if (options.switchAccount || options.switch_account) params.append("switch_account", "true");
411
450
  params.append("correlation_id", getDiagnosticContext().correlationId);
412
451
  const query = params.toString();
413
452
  const backendLoginUrl = `${authBaseUrl}/login/${clientKey}${query ? `?${query}` : ""}`;
414
453
  window.location.href = backendLoginUrl;
415
454
  }
416
- function clientLogin(clientKey, redirectUri) {
455
+ function clientLogin(clientKey, redirectUri, options = {}) {
417
456
  const { accountUiUrl } = getConfig();
418
457
  const params = new URLSearchParams({
419
458
  client: clientKey
@@ -421,6 +460,7 @@ function clientLogin(clientKey, redirectUri) {
421
460
  if (redirectUri) {
422
461
  params.append("redirect_uri", redirectUri);
423
462
  }
463
+ if (options.switchAccount || options.switch_account) params.append("switch_account", "true");
424
464
  const centralizedLoginUrl = `${accountUiUrl}/login?${params.toString()}`;
425
465
  window.location.href = centralizedLoginUrl;
426
466
  }
@@ -546,6 +586,28 @@ function resetCallbackState() {
546
586
  }
547
587
  var refreshInProgress = false;
548
588
  var refreshPromise = null;
589
+ async function withCrossTabRefreshLock(clientKey, tokenBeforeRefresh, refreshRequest) {
590
+ var _a;
591
+ const lockName = `auth-refresh-${clientKey}`;
592
+ const run = async () => {
593
+ try {
594
+ const persistedToken = localStorage.getItem("authToken");
595
+ if (tokenBeforeRefresh && persistedToken && persistedToken !== tokenBeforeRefresh) {
596
+ setToken(persistedToken);
597
+ emitAuthDiagnostic("TOKEN_REFRESH_REUSED_CROSS_TAB", "SUCCESS", "CROSS_TAB_ROTATION", {
598
+ clientKey
599
+ });
600
+ return persistedToken;
601
+ }
602
+ } catch {
603
+ }
604
+ return refreshRequest();
605
+ };
606
+ if (typeof navigator !== "undefined" && ((_a = navigator.locks) == null ? void 0 : _a.request)) {
607
+ return navigator.locks.request(lockName, run);
608
+ }
609
+ return run();
610
+ }
549
611
  async function refreshToken() {
550
612
  const { clientKey, authBaseUrl } = getConfig();
551
613
  if (refreshInProgress && refreshPromise) {
@@ -554,70 +616,74 @@ async function refreshToken() {
554
616
  }
555
617
  refreshInProgress = true;
556
618
  refreshPromise = (async () => {
557
- var _a, _b, _c, _d, _e, _f;
558
- try {
559
- const storedRefreshToken = getRefreshToken();
560
- console.log("\u{1F504} Refreshing token:", {
561
- clientKey,
562
- mode: isRouterMode() ? "ROUTER" : "CLIENT",
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}`, {
619
+ const tokenBeforeRefresh = getToken();
620
+ const refreshRequest = async () => {
621
+ var _a, _b, _c, _d, _e, _f;
622
+ try {
623
+ const storedRefreshToken = getRefreshToken();
624
+ console.log("\u{1F504} Refreshing token:", {
587
625
  clientKey,
588
- status: response.status
626
+ mode: isRouterMode() ? "ROUTER" : "CLIENT",
627
+ hasStoredRefreshToken: !!storedRefreshToken
589
628
  });
590
- const refreshError = new Error(`Refresh failed: ${response.status}`);
591
- refreshError.code = serverCode || `HTTP_${response.status}`;
592
- refreshError.status = response.status;
593
- throw refreshError;
594
- }
595
- const data = await response.json();
596
- const { access_token, refresh_token: new_refresh_token } = data;
597
- if (!access_token) {
598
- throw new Error("No access token in refresh response");
599
- }
600
- setToken(access_token);
601
- if (new_refresh_token) {
602
- setRefreshToken(new_refresh_token);
603
- console.log("\u{1F504} New refresh token stored from rotation");
604
- }
605
- console.log("\u2705 Token refresh successful, listeners notified");
606
- emitAuthDiagnostic("TOKEN_REFRESH_COMPLETED", "SUCCESS", "NONE", { clientKey });
607
- return access_token;
608
- } catch (err) {
609
- console.error("\u274C Token refresh error:", err);
610
- const isAuthRejection = ((_c = err.message) == null ? void 0 : _c.includes("401")) || ((_d = err.message) == null ? void 0 : _d.includes("403")) || ((_e = err.message) == null ? void 0 : _e.includes("invalid_grant")) || ((_f = err.message) == null ? void 0 : _f.includes("Refresh failed: 4"));
611
- if (isAuthRejection) {
612
- clearToken();
613
- clearRefreshToken();
629
+ const requestOptions = {
630
+ method: "POST",
631
+ credentials: "include",
632
+ // ✅ Include httpOnly cookies (for HTTPS)
633
+ headers: {
634
+ ...diagnosticHeaders(),
635
+ "Content-Type": "application/json"
636
+ }
637
+ };
638
+ if (storedRefreshToken) {
639
+ requestOptions.body = JSON.stringify({ refreshToken: storedRefreshToken });
640
+ console.log("\u{1F4E6} Sending refresh token in body only (Header skipped) v3.0.2");
641
+ }
642
+ const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
643
+ if (!response.ok) {
644
+ const errorText = await response.text();
645
+ let serverCode = null;
646
+ try {
647
+ serverCode = ((_a = JSON.parse(errorText)) == null ? void 0 : _a.error) || ((_b = JSON.parse(errorText)) == null ? void 0 : _b.code);
648
+ } catch {
649
+ }
650
+ emitAuthDiagnostic("TOKEN_REFRESH_REJECTED", "FAILURE", serverCode || `HTTP_${response.status}`, {
651
+ clientKey,
652
+ status: response.status
653
+ });
654
+ const refreshError = new Error(`Refresh failed: ${response.status}`);
655
+ refreshError.code = serverCode || `HTTP_${response.status}`;
656
+ refreshError.status = response.status;
657
+ throw refreshError;
658
+ }
659
+ const data = await response.json();
660
+ const { access_token, refresh_token: new_refresh_token } = data;
661
+ if (!access_token) {
662
+ throw new Error("No access token in refresh response");
663
+ }
664
+ setToken(access_token);
665
+ if (new_refresh_token) {
666
+ setRefreshToken(new_refresh_token);
667
+ console.log("\u{1F504} New refresh token stored from rotation");
668
+ }
669
+ console.log("\u2705 Token refresh successful, listeners notified");
670
+ emitAuthDiagnostic("TOKEN_REFRESH_COMPLETED", "SUCCESS", "NONE", { clientKey });
671
+ return access_token;
672
+ } catch (err) {
673
+ console.error("\u274C Token refresh error:", err);
674
+ 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"));
675
+ if (isAuthRejection) {
676
+ clearToken();
677
+ clearRefreshToken();
678
+ }
679
+ throw err;
614
680
  }
615
- throw err;
616
- } finally {
617
- refreshInProgress = false;
618
- refreshPromise = null;
619
- }
620
- })();
681
+ };
682
+ return withCrossTabRefreshLock(clientKey, tokenBeforeRefresh, refreshRequest);
683
+ })().finally(() => {
684
+ refreshInProgress = false;
685
+ refreshPromise = null;
686
+ });
621
687
  return refreshPromise;
622
688
  }
623
689
  async function validateCurrentSession() {
@@ -1438,6 +1504,8 @@ function usePreferences() {
1438
1504
  var auth = {
1439
1505
  // 🔧 Config
1440
1506
  setConfig,
1507
+ applyRuntimePolicy,
1508
+ loadRuntimePolicy,
1441
1509
  getConfig,
1442
1510
  isRouterMode,
1443
1511
  // 🔐 Core flows
@@ -1453,7 +1521,7 @@ var auth = {
1453
1521
  getToken,
1454
1522
  setToken,
1455
1523
  clearToken,
1456
- setRefreshToken: setRefreshToken2,
1524
+ setRefreshToken,
1457
1525
  // ✅ Refresh token for HTTP dev
1458
1526
  getRefreshToken,
1459
1527
  clearRefreshToken,