@bounded-sh/client 0.0.110 → 0.0.112-ux-audit.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +1 -1
  2. package/dist/auth/index.d.ts +3 -1
  3. package/dist/auth/oidc-auth.d.ts +2 -0
  4. package/dist/auth/providers/email-auth-provider.d.ts +1 -1
  5. package/dist/auth/providers/guest-auth-provider.d.ts +1 -1
  6. package/dist/auth/providers/injected-evm-wallet-provider.d.ts +1 -1
  7. package/dist/auth/providers/injected-wallet-provider.d.ts +1 -1
  8. package/dist/auth/providers/offchain-auth-provider.d.ts +1 -1
  9. package/dist/auth/providers/privy-expo-provider.d.ts +1 -1
  10. package/dist/auth/solana-rpc.d.ts +9 -0
  11. package/dist/{email-auth-provider-D9SQu9c1.mjs → email-auth-provider-BfCH7K6N.mjs} +4 -3
  12. package/dist/email-auth-provider-BfCH7K6N.mjs.map +1 -0
  13. package/dist/{email-auth-provider-T-Go_9h9.js → email-auth-provider-CG5cxOj0.js} +4 -3
  14. package/dist/email-auth-provider-CG5cxOj0.js.map +1 -0
  15. package/dist/{email-auth-provider-Czo-xM6n.js → email-auth-provider-DTrCfOJd.js} +4 -3
  16. package/dist/email-auth-provider-DTrCfOJd.js.map +1 -0
  17. package/dist/{email-auth-provider-BPeQo6xL.mjs → email-auth-provider-eyi3Wpnx.mjs} +4 -3
  18. package/dist/email-auth-provider-eyi3Wpnx.mjs.map +1 -0
  19. package/dist/{guest-auth-provider-BIohNOHi.mjs → guest-auth-provider-BsW56oJg.mjs} +10 -5
  20. package/dist/guest-auth-provider-BsW56oJg.mjs.map +1 -0
  21. package/dist/{guest-auth-provider-DsFTB844.mjs → guest-auth-provider-DjOFjtnk.mjs} +10 -5
  22. package/dist/guest-auth-provider-DjOFjtnk.mjs.map +1 -0
  23. package/dist/{guest-auth-provider-BDhwvzem.js → guest-auth-provider-Dtl1J-2F.js} +10 -5
  24. package/dist/guest-auth-provider-Dtl1J-2F.js.map +1 -0
  25. package/dist/{guest-auth-provider-DJ5nCGjq.js → guest-auth-provider-Tndt6tzJ.js} +10 -5
  26. package/dist/guest-auth-provider-Tndt6tzJ.js.map +1 -0
  27. package/dist/{index-Cs5nPIzA.js → index-Brdwqgqs.js} +256 -67
  28. package/dist/index-Brdwqgqs.js.map +1 -0
  29. package/dist/{index-DbXoOlIa.mjs → index-CRxls8Qc.mjs} +257 -68
  30. package/dist/index-CRxls8Qc.mjs.map +1 -0
  31. package/dist/index.js +1 -1
  32. package/dist/index.mjs +1 -1
  33. package/dist/{index.native-BSOqaKIz.mjs → index.native-CAH_ks0s.mjs} +245 -63
  34. package/dist/index.native-CAH_ks0s.mjs.map +1 -0
  35. package/dist/{index.native-BpcNwWQX.js → index.native-ingNhWPw.js} +244 -62
  36. package/dist/index.native-ingNhWPw.js.map +1 -0
  37. package/dist/index.native.js +1 -1
  38. package/dist/index.native.mjs +1 -1
  39. package/dist/{injected-evm-wallet-provider-BjqqLfPD.mjs → injected-evm-wallet-provider-BjkCeRBL.mjs} +4 -3
  40. package/dist/injected-evm-wallet-provider-BjkCeRBL.mjs.map +1 -0
  41. package/dist/{injected-evm-wallet-provider-_UNpc_hM.js → injected-evm-wallet-provider-Cao1U0NZ.js} +4 -3
  42. package/dist/{injected-evm-wallet-provider-_UNpc_hM.js.map → injected-evm-wallet-provider-Cao1U0NZ.js.map} +1 -1
  43. package/dist/{injected-evm-wallet-provider-CMl0Fyxk.js → injected-evm-wallet-provider-CwOGXCDt.js} +4 -3
  44. package/dist/injected-evm-wallet-provider-CwOGXCDt.js.map +1 -0
  45. package/dist/{injected-evm-wallet-provider-Xb2G-ZGI.mjs → injected-evm-wallet-provider-DXjwPjLW.mjs} +4 -3
  46. package/dist/{injected-evm-wallet-provider-Xb2G-ZGI.mjs.map → injected-evm-wallet-provider-DXjwPjLW.mjs.map} +1 -1
  47. package/dist/{injected-wallet-provider-D-Pq9QVK.mjs → injected-wallet-provider-BG6gGNch.mjs} +4 -3
  48. package/dist/{injected-wallet-provider-D-Pq9QVK.mjs.map → injected-wallet-provider-BG6gGNch.mjs.map} +1 -1
  49. package/dist/{injected-wallet-provider-DZOwvKgd.mjs → injected-wallet-provider-CfYNIIHd.mjs} +4 -3
  50. package/dist/{injected-wallet-provider-DZOwvKgd.mjs.map → injected-wallet-provider-CfYNIIHd.mjs.map} +1 -1
  51. package/dist/{injected-wallet-provider-C7Q0haGY.js → injected-wallet-provider-DSlHfgMU.js} +4 -3
  52. package/dist/{injected-wallet-provider-C7Q0haGY.js.map → injected-wallet-provider-DSlHfgMU.js.map} +1 -1
  53. package/dist/{injected-wallet-provider-CgHOrtmj.js → injected-wallet-provider-LTnqoaYD.js} +4 -3
  54. package/dist/{injected-wallet-provider-CgHOrtmj.js.map → injected-wallet-provider-LTnqoaYD.js.map} +1 -1
  55. package/package.json +2 -2
  56. package/dist/email-auth-provider-BPeQo6xL.mjs.map +0 -1
  57. package/dist/email-auth-provider-Czo-xM6n.js.map +0 -1
  58. package/dist/email-auth-provider-D9SQu9c1.mjs.map +0 -1
  59. package/dist/email-auth-provider-T-Go_9h9.js.map +0 -1
  60. package/dist/guest-auth-provider-BDhwvzem.js.map +0 -1
  61. package/dist/guest-auth-provider-BIohNOHi.mjs.map +0 -1
  62. package/dist/guest-auth-provider-DJ5nCGjq.js.map +0 -1
  63. package/dist/guest-auth-provider-DsFTB844.mjs.map +0 -1
  64. package/dist/index-Cs5nPIzA.js.map +0 -1
  65. package/dist/index-DbXoOlIa.mjs.map +0 -1
  66. package/dist/index.native-BSOqaKIz.mjs.map +0 -1
  67. package/dist/index.native-BpcNwWQX.js.map +0 -1
  68. package/dist/injected-evm-wallet-provider-BjqqLfPD.mjs.map +0 -1
  69. package/dist/injected-evm-wallet-provider-CMl0Fyxk.js.map +0 -1
@@ -277,8 +277,9 @@ class OffchainAuthProvider {
277
277
  async logout() {
278
278
  await this.wrappedProvider.logout();
279
279
  }
280
- async restoreSession() {
281
- const user = await this.wrappedProvider.restoreSession();
280
+ async restoreSession(beforeCommit) {
281
+ const provider = this.wrappedProvider;
282
+ const user = await provider.restoreSession(beforeCommit);
282
283
  if (user) {
283
284
  return Object.assign(Object.assign({}, user), { provider: this });
284
285
  }
@@ -779,15 +780,35 @@ function resolveSolanaWalletStandardChain(network, context) {
779
780
  `Expected one of: solana_devnet, solana_mainnet.`);
780
781
  }
781
782
  }
783
+ /**
784
+ * The platform RPC endpoint for a supported Solana network, or null when the
785
+ * network is missing or unsupported. Used when the app passes no rpcUrl.
786
+ */
787
+ function defaultSolanaRpcUrl(network) {
788
+ switch (network) {
789
+ case "solana_mainnet":
790
+ return SOLANA_MAINNET_RPC_URL;
791
+ case "solana_devnet":
792
+ return SOLANA_DEVNET_RPC_URL;
793
+ default:
794
+ return null;
795
+ }
796
+ }
797
+ /**
798
+ * An explicit rpcUrl always wins. Otherwise the platform RPC for the configured
799
+ * network is used. With neither, fail closed.
800
+ */
782
801
  function resolveSolanaRpcUrl(explicitRpcUrl, network, context) {
802
+ var _a;
783
803
  if (network != null && network !== "" && !isSupportedSolanaRpcNetwork(network)) {
784
804
  throw new Error(`${context} does not support Solana network "${network}". ` +
785
805
  `Expected one of: solana_devnet, solana_mainnet.`);
786
806
  }
787
- const rpcUrl = normalizeSolanaRpcUrl(explicitRpcUrl);
807
+ const rpcUrl = (_a = normalizeSolanaRpcUrl(explicitRpcUrl)) !== null && _a !== void 0 ? _a : defaultSolanaRpcUrl(network);
788
808
  if (rpcUrl)
789
809
  return rpcUrl;
790
- throw new Error(`${context} requires an explicit Solana RPC URL. Pass config.rpcUrl before signing or submitting onchain transactions.`);
810
+ throw new Error(`${context} requires a Solana network. Set init({ chain: "solana_mainnet" | "solana_devnet" }) ` +
811
+ `or pass config.rpcUrl before signing or submitting onchain transactions.`);
791
812
  }
792
813
 
793
814
  var __classPrivateFieldGet = (undefined && undefined.__classPrivateFieldGet) || function (receiver, state, kind, f) {
@@ -5512,7 +5533,8 @@ async function loginWithRedirect(opts) {
5512
5533
  return runNativeLogin(opts);
5513
5534
  if (!hasDOM$2())
5514
5535
  throw new Error('loginWithRedirect requires a browser or a configured React Native platform');
5515
- const { url, pkce } = await buildAuthorize(opts);
5536
+ const { url, pkce } = await untilAborted(buildAuthorize(opts), opts.signal);
5537
+ throwIfCancelled(opts.signal);
5516
5538
  // Stash the verifier + state so the redirect_uri page can finish the exchange. sessionStorage
5517
5539
  // is per-tab and cleared on close — appropriate for a short-lived auth handshake. Keyed by
5518
5540
  // this attempt's own state so a second concurrent login cannot overwrite it.
@@ -5541,9 +5563,29 @@ async function completeLoginFromRedirect() {
5541
5563
  throw error;
5542
5564
  return result.user;
5543
5565
  }
5566
+ function throwIfCancelled(signal) {
5567
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted)
5568
+ throw new Error('cancelled');
5569
+ }
5570
+ async function untilAborted(work, signal) {
5571
+ if (!signal)
5572
+ return work;
5573
+ throwIfCancelled(signal);
5574
+ let onAbort;
5575
+ try {
5576
+ return await Promise.race([work, new Promise((_, reject) => {
5577
+ onAbort = () => reject(new Error('cancelled'));
5578
+ signal.addEventListener('abort', onAbort, { once: true });
5579
+ })]);
5580
+ }
5581
+ finally {
5582
+ signal.removeEventListener('abort', onAbort);
5583
+ }
5584
+ }
5544
5585
  /** Popup variant: open the hosted login in a popup; resolves with the User once the popup's
5545
5586
  * redirect_uri page initializes the SDK, which hands the code back automatically. */
5546
5587
  async function loginWithPopup(opts) {
5588
+ throwIfCancelled(opts.signal);
5547
5589
  if (!hasDOM$2())
5548
5590
  throw new Error('loginWithPopup is web-only — on React Native use loginWithRedirect()');
5549
5591
  // Open synchronously, before PKCE/config awaits consume the click's user-
@@ -5561,7 +5603,7 @@ async function loginWithPopup(opts) {
5561
5603
  throw new Error('Popup blocked — use loginWithRedirect() instead');
5562
5604
  let authorization;
5563
5605
  try {
5564
- authorization = await buildAuthorize(opts, 'popup');
5606
+ authorization = await untilAborted(buildAuthorize(opts, 'popup'), opts.signal);
5565
5607
  }
5566
5608
  catch (error) {
5567
5609
  try {
@@ -5578,6 +5620,7 @@ async function loginWithPopup(opts) {
5578
5620
  // window with no listener or watchdog attached.
5579
5621
  let expectedOrigin;
5580
5622
  try {
5623
+ throwIfCancelled(opts.signal);
5581
5624
  expectedOrigin = new URL(pkce.redirectUri).origin;
5582
5625
  sessionStorage.setItem(pkceKey(pkce.state), JSON.stringify(pkce));
5583
5626
  }
@@ -5609,20 +5652,42 @@ async function loginWithPopup(opts) {
5609
5652
  setAuthLoading(true); // reflect "signing in…" while the popup is open
5610
5653
  try {
5611
5654
  return await new Promise((resolve, reject) => {
5655
+ var _a, _b;
5612
5656
  let settled = false;
5657
+ let handedOff = false;
5658
+ let committing = false;
5613
5659
  let timer;
5614
5660
  let navigationTimer;
5615
5661
  let completionTimer;
5616
5662
  const cleanup = () => {
5617
- settled = true;
5618
5663
  clearInterval(timer);
5619
5664
  clearTimeout(navigationTimer);
5620
5665
  clearTimeout(completionTimer);
5621
5666
  window.removeEventListener('message', onMsg);
5622
5667
  removeAttempt(pkce.state);
5623
5668
  };
5669
+ const finish = (error, user) => {
5670
+ var _a;
5671
+ if (settled)
5672
+ return;
5673
+ settled = true;
5674
+ cleanup();
5675
+ (_a = opts.signal) === null || _a === void 0 ? void 0 : _a.removeEventListener('abort', onAbort);
5676
+ try {
5677
+ popup.close();
5678
+ }
5679
+ catch ( /* ignore */_b) { /* ignore */ }
5680
+ if (error)
5681
+ reject(error);
5682
+ else
5683
+ resolve(user);
5684
+ };
5685
+ // Provider adoption commits synchronously after its final guard. An auth
5686
+ // listener dismissing the card there cannot cancel an already signed-in user.
5687
+ const onAbort = () => { if (!committing)
5688
+ finish(new Error('cancelled')); };
5624
5689
  const onMsg = async (ev) => {
5625
- if (settled || ev.origin !== expectedOrigin || ev.source !== popup)
5690
+ if (settled || handedOff || ev.origin !== expectedOrigin || ev.source !== popup)
5626
5691
  return;
5627
5692
  const d = ev.data;
5628
5693
  if (!d || d.type !== 'bounded-oidc-code' ||
@@ -5636,35 +5701,30 @@ async function loginWithPopup(opts) {
5636
5701
  return;
5637
5702
  if (d.appId !== pkce.appId || d.issuer !== pkce.issuer)
5638
5703
  return;
5704
+ handedOff = true;
5639
5705
  cleanup();
5640
5706
  try {
5707
+ throwIfCancelled(opts.signal);
5641
5708
  await validateAttempt(pkce, new URL(d.callbackUrl));
5709
+ throwIfCancelled(opts.signal);
5642
5710
  popup.postMessage({ type: 'bounded-oidc-accepted', state: pkce.state }, expectedOrigin);
5643
5711
  if (d.error)
5644
5712
  throw new Error(typeof d.errorDescription === 'string' ? d.errorDescription : `Login failed: ${d.error}`);
5645
- const user = await exchangeCode(d.code, pkce);
5646
- try {
5647
- popup.close();
5648
- }
5649
- catch ( /* ignore */_a) { /* ignore */ }
5650
- resolve(user);
5713
+ const user = await exchangeCode(d.code, pkce, opts.signal, () => { committing = true; });
5714
+ finish(undefined, user);
5651
5715
  }
5652
5716
  catch (e) {
5653
- try {
5654
- popup.close();
5655
- }
5656
- catch ( /* ignore */_b) { /* ignore */ }
5657
- reject(e);
5717
+ finish(e);
5658
5718
  }
5659
5719
  };
5720
+ (_a = opts.signal) === null || _a === void 0 ? void 0 : _a.addEventListener('abort', onAbort, { once: true });
5721
+ if ((_b = opts.signal) === null || _b === void 0 ? void 0 : _b.aborted) {
5722
+ onAbort();
5723
+ return;
5724
+ }
5660
5725
  window.addEventListener('message', onMsg);
5661
5726
  completionTimer = setTimeout(() => {
5662
- cleanup();
5663
- try {
5664
- popup.close();
5665
- }
5666
- catch ( /* ignore */_a) { /* ignore */ }
5667
- reject(new Error('Login expired or its popup disconnected. Start sign-in again.'));
5727
+ finish(new Error('Login expired or its popup disconnected. Start sign-in again.'));
5668
5728
  }, LOGIN_TIMEOUT_MS);
5669
5729
  // Some browser/extension combinations can return a real popup handle but
5670
5730
  // strand the window at about:blank instead of navigating to the hosted
@@ -5685,23 +5745,18 @@ async function loginWithPopup(opts) {
5685
5745
  const NAVIGATION_RECHECK_MS = 2000;
5686
5746
  const navigationStartedAt = Date.now();
5687
5747
  const checkNavigation = () => {
5688
- if (settled || popup.closed)
5748
+ if (settled || handedOff || popup.closed)
5689
5749
  return;
5690
5750
  try {
5691
5751
  if (popup.location.href === 'about:blank') {
5692
5752
  if (Date.now() - navigationStartedAt >= NAVIGATION_GRACE_MS) {
5693
- cleanup();
5694
- try {
5695
- popup.close();
5696
- }
5697
- catch ( /* ignore */_a) { /* ignore */ }
5698
- reject(new Error('Popup stalled at about:blank - use loginWithRedirect() instead'));
5753
+ finish(new Error('Popup stalled at about:blank - use loginWithRedirect() instead'));
5699
5754
  return;
5700
5755
  }
5701
5756
  navigationTimer = setTimeout(checkNavigation, NAVIGATION_RECHECK_MS);
5702
5757
  }
5703
5758
  }
5704
- catch ( /* cross-origin hosted auth loaded normally */_b) { /* cross-origin hosted auth loaded normally */ }
5759
+ catch ( /* cross-origin hosted auth loaded normally */_a) { /* cross-origin hosted auth loaded normally */ }
5705
5760
  };
5706
5761
  navigationTimer = setTimeout(checkNavigation, NAVIGATION_RECHECK_MS);
5707
5762
  // The popup posts its code and then closes ITSELF, so a naive "popup.closed -> reject"
@@ -5711,10 +5766,8 @@ async function loginWithPopup(opts) {
5711
5766
  timer = setInterval(() => {
5712
5767
  if (popup.closed) {
5713
5768
  clearInterval(timer);
5714
- setTimeout(() => { if (!settled) {
5715
- cleanup();
5716
- reject(new Error('Login window closed'));
5717
- } }, 500);
5769
+ setTimeout(() => { if (!settled && !handedOff)
5770
+ finish(new Error('Login window closed')); }, 500);
5718
5771
  }
5719
5772
  }, 500);
5720
5773
  });
@@ -5765,9 +5818,10 @@ async function inlineAuthEndpoint() {
5765
5818
  const cfg = await core.getConfig();
5766
5819
  return { base: await issuerBase(), appId: cfg.appId };
5767
5820
  }
5768
- async function exchangeCode(code, pkce) {
5821
+ async function exchangeCode(code, pkce, signal, onCommitting) {
5769
5822
  const generation = getInitializationGeneration();
5770
5823
  const beforeCommit = () => {
5824
+ throwIfCancelled(signal);
5771
5825
  if (generation !== getInitializationGeneration())
5772
5826
  throw new Error('Login was superseded by SDK reinitialization. Start sign-in again.');
5773
5827
  };
@@ -5783,7 +5837,7 @@ async function exchangeCode(code, pkce) {
5783
5837
  let res;
5784
5838
  try {
5785
5839
  res = await fetch(`${base}/api/auth/oauth2/token`, {
5786
- method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' }, body: body.toString(),
5840
+ method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' }, body: body.toString(), signal,
5787
5841
  });
5788
5842
  }
5789
5843
  catch (err) {
@@ -5809,7 +5863,7 @@ async function exchangeCode(code, pkce) {
5809
5863
  // Adopt on EVERY session surface (see establishInlineSession) - hosted
5810
5864
  // popup/redirect completions all land here, so this closes their gap too.
5811
5865
  try {
5812
- await adoptSessionProvider({ provider: oidcProviderSingleton, method: 'email', configKey: 'direct:oidc', user, beforeCommit });
5866
+ await adoptSessionProvider({ provider: oidcProviderSingleton, method: 'email', configKey: 'direct:oidc', user, beforeCommit: () => { beforeCommit(); onCommitting === null || onCommitting === void 0 ? void 0 : onCommitting(); } });
5813
5867
  }
5814
5868
  catch (error) {
5815
5869
  if (typeof rollback === 'function')
@@ -5878,6 +5932,31 @@ function idTokenIsGuest(idToken, expectedAddress) {
5878
5932
  function storedSessionIsGuest() {
5879
5933
  return idTokenIsGuest(getStoredIdToken());
5880
5934
  }
5935
+ /** Internal: select a cross-tab restore from bearer claims, never global hints. */
5936
+ function storedSessionAuthMethod(config) {
5937
+ try {
5938
+ const token = getStoredIdToken();
5939
+ if (!token)
5940
+ return null;
5941
+ let payload = token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
5942
+ payload += '='.repeat((4 - payload.length % 4) % 4);
5943
+ const claims = JSON.parse(getPlatform().atob(payload));
5944
+ const method = claims.is_anonymous === true ? 'guest'
5945
+ : claims['custom:walletProvider'] === 'turnkey' || claims.email ? 'email'
5946
+ : claims['custom:evmWalletAddress'] ? 'evm-wallet'
5947
+ : claims['custom:walletAddress'] ? 'phantom' : 'email';
5948
+ if (config.authMethod != null && canonicalWalletAuthMethod(config.authMethod) !== method)
5949
+ return null;
5950
+ if (method === 'evm-wallet' && config.authMethod !== 'evm-wallet')
5951
+ return null;
5952
+ if (method === 'phantom' && (isWalletLoginDisabled(config) || isEmailRequired(config)))
5953
+ return null;
5954
+ return method;
5955
+ }
5956
+ catch (_a) {
5957
+ return null;
5958
+ }
5959
+ }
5881
5960
  /**
5882
5961
  * Pick the provider used for an ordinary `init({ appId })` restore.
5883
5962
  *
@@ -6139,9 +6218,10 @@ function optionalProviderError(method) {
6139
6218
  `loginWithRedirect(...) for Bounded Auth, signInAnonymously() for guests, ` +
6140
6219
  `or install/import an explicit wallet-provider entry once your app opts into "${method}".`);
6141
6220
  }
6142
- async function getAuthProvider(config) {
6221
+ async function getAuthProvider(config, fromStorage = false) {
6143
6222
  var _a;
6144
- const nextProviderConfigKey = config ? authProviderConfigKey(config) : null;
6223
+ const requestedAuthMethod = config ? (fromStorage ? storedSessionAuthMethod(config) : authMethodForInit(config)) : null;
6224
+ const nextProviderConfigKey = config ? authProviderConfigKey(Object.assign(Object.assign({}, config), { authMethod: requestedAuthMethod })) : null;
6145
6225
  if (currentAuthProvider && (!config || currentAuthProviderConfigKey === nextProviderConfigKey)) {
6146
6226
  return currentAuthProvider;
6147
6227
  }
@@ -6156,7 +6236,8 @@ async function getAuthProvider(config) {
6156
6236
  initConfig = config;
6157
6237
  // Keep what the app actually wrote for error text; everything else runs on
6158
6238
  // the canonical method so exactly one string reaches storage and the issuer.
6159
- const requestedAuthMethod = authMethodForInit(config);
6239
+ if (!requestedAuthMethod)
6240
+ throw new Error('The stored session is incompatible with this app authentication configuration.');
6160
6241
  const authMethod = canonicalWalletAuthMethod(requestedAuthMethod);
6161
6242
  const rpcUrl = normalizeSolanaRpcUrl(config.rpcUrl);
6162
6243
  const configuredNetwork = getConfiguredSolanaNetwork(config);
@@ -6181,7 +6262,7 @@ async function getAuthProvider(config) {
6181
6262
  // manager as legacy email sessions. Human login should be started
6182
6263
  // with loginWithRedirect(); this provider is kept as the restore and
6183
6264
  // logout surface for those sessions.
6184
- currentAuthProvider = new (await Promise.resolve().then(function () { return require('./email-auth-provider-T-Go_9h9.js'); })).EmailAuthProvider();
6265
+ currentAuthProvider = new (await Promise.resolve().then(function () { return require('./email-auth-provider-CG5cxOj0.js'); })).EmailAuthProvider();
6185
6266
  break;
6186
6267
  case "phantom": {
6187
6268
  // First-class Solana WALLET LOGIN (the poof/TaroBase-style "connect
@@ -6223,7 +6304,7 @@ async function getAuthProvider(config) {
6223
6304
  // (bounded-login-widget.ts), so registration never counts as
6224
6305
  // evidence the user has a wallet.
6225
6306
  await ensureSolanaMobileWalletRegistered(config, configuredNetwork);
6226
- const { InjectedWalletProvider } = await Promise.resolve().then(function () { return require('./injected-wallet-provider-CgHOrtmj.js'); });
6307
+ const { InjectedWalletProvider } = await Promise.resolve().then(function () { return require('./injected-wallet-provider-LTnqoaYD.js'); });
6227
6308
  currentAuthProvider = new InjectedWalletProvider(resolveInjectedWalletConfig(config), rpcUrl, configuredNetwork);
6228
6309
  break;
6229
6310
  }
@@ -6236,7 +6317,7 @@ async function getAuthProvider(config) {
6236
6317
  // Selecting authMethod:'evm-wallet' IS the opt-in — pass evmWalletConfig
6237
6318
  // to point at a specific wallet or set chainId/domain.
6238
6319
  const evmWalletConfig = ((_a = config.evmWalletConfig) !== null && _a !== void 0 ? _a : {});
6239
- const { InjectedEvmWalletProvider } = await Promise.resolve().then(function () { return require('./injected-evm-wallet-provider-CMl0Fyxk.js'); });
6320
+ const { InjectedEvmWalletProvider } = await Promise.resolve().then(function () { return require('./injected-evm-wallet-provider-CwOGXCDt.js'); });
6240
6321
  currentAuthProvider = new InjectedEvmWalletProvider(evmWalletConfig);
6241
6322
  break;
6242
6323
  }
@@ -6261,7 +6342,7 @@ async function getAuthProvider(config) {
6261
6342
  break;
6262
6343
  case "guest":
6263
6344
  // Anonymous auth: a device-local ed25519 keypair signs the challenge.
6264
- currentAuthProvider = new (await Promise.resolve().then(function () { return require('./guest-auth-provider-BDhwvzem.js'); })).GuestAuthProvider();
6345
+ currentAuthProvider = new (await Promise.resolve().then(function () { return require('./guest-auth-provider-Dtl1J-2F.js'); })).GuestAuthProvider();
6265
6346
  break;
6266
6347
  }
6267
6348
  if (!currentAuthProvider) {
@@ -6323,7 +6404,7 @@ async function signInAnonymously() {
6323
6404
  // Lazy-load the guest provider so its ed25519/web3 dependencies stay out of
6324
6405
  // the main bundle for data-only apps. Specifier is a static string literal
6325
6406
  // so bundle-splitting can see it.
6326
- const provider = new (await Promise.resolve().then(function () { return require('./guest-auth-provider-BDhwvzem.js'); })).GuestAuthProvider();
6407
+ const provider = new (await Promise.resolve().then(function () { return require('./guest-auth-provider-Dtl1J-2F.js'); })).GuestAuthProvider();
6327
6408
  setAuthLoading(true); // reflect "signing in…" while the guest keypair session is minted
6328
6409
  // adoptSessionProvider awaits only getConfig() (already resolved here) and then
6329
6410
  // runs every write - provider/method/config/marker/user - in one synchronous
@@ -6494,7 +6575,7 @@ function ensureSharedWalletLoginReady() {
6494
6575
  if (!sharedWalletReady) {
6495
6576
  sharedWalletReady = (async () => {
6496
6577
  await core.getConfig();
6497
- await Promise.resolve().then(function () { return require('./injected-wallet-provider-CgHOrtmj.js'); });
6578
+ await Promise.resolve().then(function () { return require('./injected-wallet-provider-LTnqoaYD.js'); });
6498
6579
  })().catch((error) => {
6499
6580
  sharedWalletReady = null;
6500
6581
  throw error;
@@ -6597,7 +6678,7 @@ async function loginWithWallet(options) {
6597
6678
  await ensureSharedWalletLoginReady();
6598
6679
  else
6599
6680
  await ensureWalletLoginReady();
6600
- const { InjectedWalletProvider } = await Promise.resolve().then(function () { return require('./injected-wallet-provider-CgHOrtmj.js'); });
6681
+ const { InjectedWalletProvider } = await Promise.resolve().then(function () { return require('./injected-wallet-provider-LTnqoaYD.js'); });
6601
6682
  // Keep the RAW wallet provider: the offchain wrapper forwards only a fixed
6602
6683
  // set of methods, so cleanup below must speak to the provider that actually
6603
6684
  // holds the config rather than to whatever is wrapping it.
@@ -6869,9 +6950,21 @@ var index = /*#__PURE__*/Object.freeze({
6869
6950
  logout: logout$1,
6870
6951
  preloadWalletLogin: preloadWalletLogin,
6871
6952
  resolveAuthMode: resolveAuthMode,
6872
- signInAnonymously: signInAnonymously
6953
+ signInAnonymously: signInAnonymously,
6954
+ storedSessionAuthMethod: storedSessionAuthMethod
6873
6955
  });
6874
6956
 
6957
+ /**
6958
+ * An explicit rpcUrl wins; otherwise a Solana chain gets the platform RPC so
6959
+ * every onchain path (wallet signing, submission, reconcile) has one.
6960
+ */
6961
+ function withDefaultSolanaRpcUrl(config) {
6962
+ var _a;
6963
+ if (normalizeSolanaRpcUrl(config.rpcUrl))
6964
+ return config;
6965
+ const rpcUrl = defaultSolanaRpcUrl((_a = config.chain) === null || _a === void 0 ? void 0 : _a.trim());
6966
+ return rpcUrl ? Object.assign(Object.assign({}, config), { rpcUrl }) : config;
6967
+ }
6875
6968
  /**
6876
6969
  * Subscribed once per process, not per init(): re-registering on every init would
6877
6970
  * stack duplicate handlers on repeated initialization.
@@ -6936,11 +7029,80 @@ let isAuthLoading = false;
6936
7029
  let authReadyPromise = null;
6937
7030
  let initializingConfig = null;
6938
7031
  let initializationGeneration = 0;
7032
+ let initializedConfig = null;
7033
+ let storageBridgeRegistered = false;
7034
+ let observedIdToken = null;
7035
+ let observedIssuer = null;
7036
+ // Storage events carry no authority. Re-read the active manager's app/issuer
7037
+ // namespace, then use the ordinary initializer/provider restore and listeners.
7038
+ function registerStorageBridge() {
7039
+ if (storageBridgeRegistered || !getPlatform().hasDOM || typeof window === 'undefined')
7040
+ return;
7041
+ storageBridgeRegistered = true;
7042
+ window.addEventListener('storage', (event) => {
7043
+ if (event.storageArea !== window.localStorage || !initializedConfig)
7044
+ return;
7045
+ void syncStoredSession().catch((error) => console.warn('Bounded session synchronization failed:', error));
7046
+ });
7047
+ }
7048
+ async function syncStoredSession() {
7049
+ const config = initializedConfig;
7050
+ if (!config)
7051
+ return;
7052
+ const token = core.WebSessionManager.getIdToken();
7053
+ const issuer = core.WebSessionManager.getIssuer();
7054
+ if (token === observedIdToken && issuer === observedIssuer)
7055
+ return;
7056
+ observedIdToken = token;
7057
+ observedIssuer = issuer;
7058
+ const generation = ++initializationGeneration;
7059
+ // Hide the former principal while the replacement is validated. This also
7060
+ // reconnects live reads through the existing auth-state change path.
7061
+ setCurrentUser(null);
7062
+ if (authReadyPromise)
7063
+ await authReadyPromise.catch(() => { });
7064
+ if (generation !== initializationGeneration || config !== initializedConfig)
7065
+ return;
7066
+ const record = await core.WebSessionManager.readSessionWithGeneration();
7067
+ if (generation !== initializationGeneration || config !== initializedConfig)
7068
+ return;
7069
+ if (!record)
7070
+ return;
7071
+ const normalize = (url) => (url || '').replace(/\/+$/, '');
7072
+ const resolvedConfig = await core.getConfig();
7073
+ if (generation !== initializationGeneration || config !== initializedConfig)
7074
+ return;
7075
+ const allowedIssuers = [normalize(resolvedConfig.humanAuthApiUrl || 'https://auth.bounded.sh'), normalize(resolvedConfig.authApiUrl)].filter(Boolean);
7076
+ // A legacy shared blob can belong to another app. Never restore or clear
7077
+ // that app's credential; the current app remains signed out.
7078
+ if ((record.appId && record.appId !== config.appId)
7079
+ || (record.issuer && !allowedIssuers.includes(normalize(record.issuer))))
7080
+ return;
7081
+ if (!storedSessionAuthMethod(config))
7082
+ return;
7083
+ const restored = startInitialization(config, true);
7084
+ const restoreGeneration = initializationGeneration;
7085
+ try {
7086
+ await restored;
7087
+ }
7088
+ catch (error) {
7089
+ if (restoreGeneration === initializationGeneration && core.WebSessionManager.getIdToken())
7090
+ throw error;
7091
+ }
7092
+ }
6939
7093
  /** Internal guard for a login result arriving after the app reconfigured the SDK. */
6940
7094
  function getInitializationGeneration() { return initializationGeneration; }
6941
7095
  // This file acts as a middleware for the global state of the SDK
6942
7096
  // This mostly involves setting up the AuthProvider and managing the current user
6943
- async function initialize(newConfig) {
7097
+ async function initialize(requestedConfig, fromStorage) {
7098
+ const newConfig = withDefaultSolanaRpcUrl(requestedConfig);
7099
+ const generation = initializationGeneration;
7100
+ const validateStorage = fromStorage ? core.WebSessionManager.captureSessionEstablishment() : undefined;
7101
+ const beforeCommit = () => {
7102
+ if (generation !== initializationGeneration)
7103
+ throw new Error('Session restore was superseded.');
7104
+ validateStorage === null || validateStorage === void 0 ? void 0 : validateStorage();
7105
+ };
6944
7106
  // Mark auth as loading for the ENTIRE init/restore window so consumers render
6945
7107
  // a neutral/loading state instead of the signed-out UI while we rehydrate the
6946
7108
  // session — otherwise getCurrentUser() is null + loading is false during the
@@ -6959,8 +7121,9 @@ async function initialize(newConfig) {
6959
7121
  // onAuthStateChanged never fired, and every consumer UI went on showing a
6960
7122
  // connected wallet for a session the server no longer knew about.
6961
7123
  registerSessionClearedBridge();
7124
+ registerStorageBridge();
6962
7125
  // Get auth provider first
6963
- authProviderInstance = await getAuthProvider(newConfig);
7126
+ authProviderInstance = await getAuthProvider(newConfig, fromStorage);
6964
7127
  // Initialize config with auth provider
6965
7128
  await core.init(Object.assign(Object.assign({}, newConfig), { authProvider: authProviderInstance }));
6966
7129
  // A return establishes its own provider/session, or hands off a popup.
@@ -6985,7 +7148,8 @@ async function initialize(newConfig) {
6985
7148
  // Wait for restoreSession to complete
6986
7149
  // The appId check is done in SessionManager.getSession which is called by restoreSession
6987
7150
  // If there's an appId mismatch, the session will be cleared and restoreSession will return null
6988
- let user = await authProviderInstance.restoreSession();
7151
+ let user = await authProviderInstance.restoreSession(beforeCommit);
7152
+ beforeCommit();
6989
7153
  // If a session exists but was created before auth method tracking (legacy session),
6990
7154
  // clear it to avoid mismatches when the app switches auth methods (e.g. email → phantom)
6991
7155
  //
@@ -6993,9 +7157,10 @@ async function initialize(newConfig) {
6993
7157
  // unawaited call would return a Promise (always truthy) — nulling the user
6994
7158
  // unconditionally AND letting setCurrentUser run while the old credential
6995
7159
  // was still in storage.
6996
- if (user && await clearIncompatibleSession()) {
7160
+ if (user && !fromStorage && await clearIncompatibleSession()) {
6997
7161
  user = null;
6998
7162
  }
7163
+ beforeCommit();
6999
7164
  setCurrentUser(user);
7000
7165
  // Mark init as completed
7001
7166
  initCompleted = true;
@@ -7035,13 +7200,17 @@ function sameConfigValue(a, b, seen = new WeakMap()) {
7035
7200
  && sameConfigValue(a[key], b[key], seen));
7036
7201
  }
7037
7202
  function init$1(newConfig) {
7203
+ return startInitialization(newConfig, false);
7204
+ }
7205
+ function startInitialization(newConfig, fromStorage) {
7038
7206
  if (initializingConfig) {
7039
7207
  if (sameConfigValue(initializingConfig, newConfig)) {
7040
7208
  return authReadyPromise;
7041
7209
  }
7042
7210
  return Promise.reject(new Error('SDK initialization is in progress. Await init() before changing its configuration.'));
7043
7211
  }
7044
- initializingConfig = Object.assign({}, newConfig);
7212
+ initializedConfig = Object.assign({}, newConfig);
7213
+ initializingConfig = initializedConfig;
7045
7214
  initializationGeneration++;
7046
7215
  let resolveReady;
7047
7216
  let rejectReady;
@@ -7053,7 +7222,7 @@ function init$1(newConfig) {
7053
7222
  // auth-loading listener to call waitForAuthReady() synchronously when init()
7054
7223
  // announces that session restoration has begun.
7055
7224
  authReadyPromise = ready;
7056
- void initialize(initializingConfig).then(() => { initializingConfig = null; resolveReady(); }, (error) => { initializingConfig = null; rejectReady(error); });
7225
+ void initialize(initializingConfig, fromStorage).then(() => { initializingConfig = null; resolveReady(); }, (error) => { initializingConfig = null; rejectReady(error); });
7057
7226
  return ready;
7058
7227
  }
7059
7228
  /**
@@ -7126,6 +7295,13 @@ function userPrincipalKey(user) {
7126
7295
  }
7127
7296
  // Update the currentUser and notify listeners
7128
7297
  function setCurrentUser(user) {
7298
+ observedIdToken = getCurrentIdToken();
7299
+ try {
7300
+ observedIssuer = core.getActiveSessionManager().getIssuer();
7301
+ }
7302
+ catch (_a) {
7303
+ observedIssuer = null;
7304
+ }
7129
7305
  // Enrich with the universal identity fields (id/email) derived from the
7130
7306
  // active session's idToken — central chokepoint so every provider's user
7131
7307
  // object gains { id, address, email } without each construction site
@@ -8073,7 +8249,8 @@ async function prepareAndOpen(opts, pending) {
8073
8249
  return authApi.loginWithRedirect(options);
8074
8250
  // Invoke immediately inside the click handler to retain popup activation.
8075
8251
  return authApi.loginWithPopup(options).catch((error) => {
8076
- if (shouldFallbackToRedirect(error))
8252
+ var _a;
8253
+ if (!((_a = options.signal) === null || _a === void 0 ? void 0 : _a.aborted) && shouldFallbackToRedirect(error))
8077
8254
  return authApi.loginWithRedirect(options);
8078
8255
  throw error;
8079
8256
  });
@@ -8211,6 +8388,12 @@ async function prepareAndOpen(opts, pending) {
8211
8388
  let activeAttempt = 0;
8212
8389
  let abortScopedLogin = null;
8213
8390
  let abortGesture = null;
8391
+ const startHostedLogin = (options) => {
8392
+ const controller = new AbortController();
8393
+ const abort = () => controller.abort();
8394
+ abortScopedLogin = abort;
8395
+ return hostedLogin(Object.assign(Object.assign({}, options), { signal: controller.signal }));
8396
+ };
8214
8397
  /** Reject a gesture promise already waiting on a view that is going away. */
8215
8398
  const abortPendingGesture = () => {
8216
8399
  const abort = abortGesture;
@@ -8236,6 +8419,7 @@ async function prepareAndOpen(opts, pending) {
8236
8419
  if (!attemptIsCurrent(id))
8237
8420
  return false;
8238
8421
  activeAttempt = 0;
8422
+ abortScopedLogin = null;
8239
8423
  return true;
8240
8424
  };
8241
8425
  /** Take the card back (Back, a lane switch, settling the modal). Deliberately
@@ -8390,7 +8574,7 @@ async function prepareAndOpen(opts, pending) {
8390
8574
  loginHint: input.value,
8391
8575
  redirectUri: opts.redirectUri,
8392
8576
  };
8393
- hostedLogin(emailOpts)
8577
+ startHostedLogin(emailOpts)
8394
8578
  // A hosted login that RESOLVES minted a real session, so it settles
8395
8579
  // the card even if another lane has since taken it - dropping it
8396
8580
  // would leave the app signed in behind a widget that says it is not.
@@ -8440,7 +8624,7 @@ async function prepareAndOpen(opts, pending) {
8440
8624
  }
8441
8625
  setBusy(b, "Opening secure sign-in\u2026");
8442
8626
  const socialOpts = { provider: method, redirectUri: opts.redirectUri };
8443
- hostedLogin(socialOpts)
8627
+ startHostedLogin(socialOpts)
8444
8628
  .then((u) => {
8445
8629
  if (u) {
8446
8630
  settleAttempt(attempt);
@@ -10452,7 +10636,7 @@ class PrivyExpoProvider {
10452
10636
  if (isLegacyTx) {
10453
10637
  const legacyTx = transaction;
10454
10638
  if (!legacyTx.recentBlockhash) {
10455
- const rpcUrl = this.getRpcUrl();
10639
+ const rpcUrl = await this.getRpcUrl();
10456
10640
  const connection = new Connection(rpcUrl, 'confirmed');
10457
10641
  const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed');
10458
10642
  legacyTx.recentBlockhash = blockhash;
@@ -10465,7 +10649,7 @@ class PrivyExpoProvider {
10465
10649
  else {
10466
10650
  const versionedTx = transaction;
10467
10651
  if (!versionedTx.message.recentBlockhash) {
10468
- const rpcUrl = this.getRpcUrl();
10652
+ const rpcUrl = await this.getRpcUrl();
10469
10653
  const connection = new Connection(rpcUrl, 'confirmed');
10470
10654
  const { blockhash } = await connection.getLatestBlockhash('confirmed');
10471
10655
  versionedTx.message.recentBlockhash = blockhash;
@@ -10485,7 +10669,7 @@ class PrivyExpoProvider {
10485
10669
  await this.ensureReady();
10486
10670
  const { Connection, PublicKey } = await import('@solana/web3.js');
10487
10671
  const wallet = await this.getWalletOrThrow('submitting this transaction');
10488
- const rpcUrl = this.getRpcUrl();
10672
+ const rpcUrl = await this.getRpcUrl();
10489
10673
  const connection = new Connection(rpcUrl, 'confirmed');
10490
10674
  // Ensure blockhash.
10491
10675
  // DS3-0414: a Solana signature covers the compiled message (recentBlockhash
@@ -10512,8 +10696,9 @@ class PrivyExpoProvider {
10512
10696
  const { signature } = await this.signAndSubmitInternal(transaction, wallet, connection);
10513
10697
  return signature;
10514
10698
  }
10515
- async restoreSession() {
10699
+ async restoreSession(beforeCommit) {
10516
10700
  const session = await core.getActiveSessionManager().getSession();
10701
+ beforeCommit === null || beforeCommit === void 0 ? void 0 : beforeCommit();
10517
10702
  if (session) {
10518
10703
  this.authenticatedAddress = session.address;
10519
10704
  return { provider: this, address: session.address };
@@ -10586,8 +10771,12 @@ class PrivyExpoProvider {
10586
10771
  const txInfo = await confirmAndCheckTransaction(connection, signature);
10587
10772
  return { signature, txInfo };
10588
10773
  }
10589
- getRpcUrl(network) {
10590
- return resolveSolanaRpcUrl(this.networkUrl, network, "Privy Expo Solana transaction");
10774
+ // The constructor rpcUrl wins, then init()'s rpcUrl, then the platform RPC
10775
+ // for init()'s chain.
10776
+ async getRpcUrl() {
10777
+ var _a;
10778
+ const config = await core.getConfig();
10779
+ return resolveSolanaRpcUrl((_a = this.networkUrl) !== null && _a !== void 0 ? _a : config.rpcUrl, isSupportedSolanaRpcNetwork(config.chain) ? config.chain : null, "Privy Expo Solana transaction");
10591
10780
  }
10592
10781
  async ensureReady(timeoutMs = 15000) {
10593
10782
  if (!this.privyMethods) {
@@ -11094,4 +11283,4 @@ exports.useAuth = useAuth;
11094
11283
  exports.useBuildRun = useBuildRun;
11095
11284
  exports.useQuery = useQuery;
11096
11285
  exports.waitForAuthReady = waitForAuthReady;
11097
- //# sourceMappingURL=index-Cs5nPIzA.js.map
11286
+ //# sourceMappingURL=index-Brdwqgqs.js.map