@openfort/react 1.0.6 → 1.0.8

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 (53) hide show
  1. package/build/assets/icons.js +1 -1
  2. package/build/components/Common/WalletRecoveryIcon/index.d.ts +1 -0
  3. package/build/components/Common/WalletRecoveryIcon/index.js +6 -1
  4. package/build/components/Common/WalletRecoveryIcon/index.js.map +1 -1
  5. package/build/components/ConnectModal/ConnectWithInjector/index.js +4 -4
  6. package/build/components/ConnectModal/ConnectWithMobile.js +10 -8
  7. package/build/components/ConnectModal/ConnectWithMobile.js.map +1 -1
  8. package/build/components/ConnectModal/ConnectWithOAuth.js +5 -1
  9. package/build/components/ConnectModal/ConnectWithOAuth.js.map +1 -1
  10. package/build/components/ConnectModal/index.js +3 -3
  11. package/build/components/Openfort/OpenfortProvider.js +4 -0
  12. package/build/components/Openfort/OpenfortProvider.js.map +1 -1
  13. package/build/components/Pages/Connected/EthereumConnected.js +7 -4
  14. package/build/components/Pages/Connected/EthereumConnected.js.map +1 -1
  15. package/build/components/Pages/Connected/SolanaConnected.js +1 -1
  16. package/build/components/Pages/Connectors/index.js +1 -55
  17. package/build/components/Pages/Connectors/index.js.map +1 -1
  18. package/build/components/Pages/LinkedProvider/index.js +7 -2
  19. package/build/components/Pages/LinkedProvider/index.js.map +1 -1
  20. package/build/components/Pages/LoadWallets/index.js +35 -28
  21. package/build/components/Pages/LoadWallets/index.js.map +1 -1
  22. package/build/components/Pages/RemoveLinkedProvider/index.js +5 -1
  23. package/build/components/Pages/RemoveLinkedProvider/index.js.map +1 -1
  24. package/build/components/Pages/SelectWalletToRecover/index.js +41 -3
  25. package/build/components/Pages/SelectWalletToRecover/index.js.map +1 -1
  26. package/build/ethereum/hooks/useEthereumEmbeddedWallet.js +5 -1
  27. package/build/ethereum/hooks/useEthereumEmbeddedWallet.js.map +1 -1
  28. package/build/hooks/openfort/auth/useAuthCallback.d.ts +1 -1
  29. package/build/hooks/openfort/auth/useAuthCallback.js +24 -8
  30. package/build/hooks/openfort/auth/useAuthCallback.js.map +1 -1
  31. package/build/hooks/useResolvedIdentity.js +4 -1
  32. package/build/hooks/useResolvedIdentity.js.map +1 -1
  33. package/build/openfort/CoreOpenfortProvider.js +1 -0
  34. package/build/openfort/CoreOpenfortProvider.js.map +1 -1
  35. package/build/openfort/hooks/useActiveAddressSync.d.ts +7 -3
  36. package/build/openfort/hooks/useActiveAddressSync.js +37 -7
  37. package/build/openfort/hooks/useActiveAddressSync.js.map +1 -1
  38. package/build/solana/hooks/useSolanaEmbeddedWallet.js +1 -1
  39. package/build/utils/rpc.d.ts +0 -4
  40. package/build/utils/rpc.js +5 -1
  41. package/build/utils/rpc.js.map +1 -1
  42. package/build/utils/urlSecurity.d.ts +28 -0
  43. package/build/utils/urlSecurity.js +56 -0
  44. package/build/utils/urlSecurity.js.map +1 -0
  45. package/build/version.d.ts +1 -1
  46. package/build/version.js +1 -1
  47. package/build/wagmi/useConnectWithSiwe.js +35 -29
  48. package/build/wagmi/useConnectWithSiwe.js.map +1 -1
  49. package/build/wagmi/useEmbeddedWalletWagmiSync.js +21 -1
  50. package/build/wagmi/useEmbeddedWalletWagmiSync.js.map +1 -1
  51. package/build/wagmi/useWalletAuth.js +7 -0
  52. package/build/wagmi/useWalletAuth.js.map +1 -1
  53. package/package.json +2 -3
@@ -6,19 +6,50 @@ import { toSolanaUserWallet } from '../../../hooks/openfort/walletTypes.js';
6
6
  import { useResolvedIdentity } from '../../../hooks/useResolvedIdentity.js';
7
7
  import { useOpenfortCore } from '../../../openfort/useOpenfort.js';
8
8
  import { useSolanaEmbeddedWallet } from '../../../solana/hooks/useSolanaEmbeddedWallet.js';
9
+ import styled from '../../../styles/styled/index.js';
9
10
  import 'detect-browser';
10
11
  import 'react';
11
12
  import { truncateEthAddress } from '../../../utils/format.js';
12
13
  import { walletConfigs } from '../../../wallets/walletConfigs.js';
13
14
  import Button from '../../Common/Button/index.js';
14
15
  import { ModalHeading } from '../../Common/Modal/styles.js';
15
- import { WalletRecoveryIcon } from '../../Common/WalletRecoveryIcon/index.js';
16
+ import { RECOVERY_METHOD_LABEL, WalletRecoveryIcon } from '../../Common/WalletRecoveryIcon/index.js';
16
17
  import { recoverRoute } from '../../Openfort/routeHelpers.js';
17
18
  import { routes } from '../../Openfort/types.js';
18
19
  import { useOpenfort } from '../../Openfort/useOpenfort.js';
19
20
  import { PageContent } from '../../PageContent/index.js';
20
21
  import { ProvidersButton, ProviderLabel, ProviderIcon } from '../Providers/styles.js';
21
22
 
23
+ const RecoveryTag = styled.span `
24
+ display: inline-flex;
25
+ align-items: center;
26
+ padding: 2px 8px;
27
+ border-radius: 6px;
28
+ font-size: 11px;
29
+ font-weight: 600;
30
+ line-height: 16px;
31
+ white-space: nowrap;
32
+ background: var(--ck-body-background-secondary, #f0f0f0);
33
+ color: var(--ck-body-color-muted, #999);
34
+ `;
35
+ const WalletListScroll = styled.div `
36
+ max-height: min(400px, 50vh);
37
+ overflow-x: hidden;
38
+ overflow-y: auto;
39
+
40
+ &::-webkit-scrollbar {
41
+ width: 4px;
42
+ }
43
+ &::-webkit-scrollbar-track {
44
+ background: transparent;
45
+ }
46
+ &::-webkit-scrollbar-thumb {
47
+ background: var(--ck-body-color-muted, #c47a2a);
48
+ border-radius: 4px;
49
+ }
50
+ scrollbar-width: thin;
51
+ scrollbar-color: var(--ck-body-color-muted, #c47a2a) transparent;
52
+ `;
22
53
  function WalletRow({ chainType, wallet, }) {
23
54
  var _a;
24
55
  const { setRoute } = useOpenfort();
@@ -54,16 +85,23 @@ function WalletRow({ chainType, wallet, }) {
54
85
  }
55
86
  setRoute(recoverRoute(chainType, wallet));
56
87
  };
57
- return (jsx(ProvidersButton, { children: jsxs(Button, { onClick: handleClick, children: [jsx(ProviderLabel, { children: display }), jsx(ProviderIcon, { children: walletIcon() })] }) }));
88
+ const tag = wallet.recoveryMethod != null ? RECOVERY_METHOD_LABEL[wallet.recoveryMethod] : undefined;
89
+ return (jsx(ProvidersButton, { children: jsxs(Button, { onClick: handleClick, children: [jsxs(ProviderLabel, { children: [display, tag && jsx(RecoveryTag, { children: tag })] }), jsx(ProviderIcon, { children: walletIcon() })] }) }));
58
90
  }
91
+ /** Connected-page routes that indicate the user navigated here from "Manage wallets" */
92
+ const CONNECTED_ROUTES = new Set([routes.CONNECTED, routes.ETH_CONNECTED, routes.SOL_CONNECTED]);
59
93
  function SelectWalletToRecover() {
60
94
  const { chainType } = useOpenfortCore();
95
+ const { previousRoute } = useOpenfort();
61
96
  const ethereumWallet = useEthereumEmbeddedWallet();
62
97
  const solanaWallet = useSolanaEmbeddedWallet();
63
98
  const embeddedWallet = chainType === ChainTypeEnum.EVM ? ethereumWallet : solanaWallet;
64
99
  const wallets = embeddedWallet.wallets;
65
100
  const list = wallets.map((wallet) => jsx(WalletRow, { chainType: chainType, wallet: wallet }, wallet.id));
66
- return (jsxs(PageContent, { onBack: routes.PROVIDERS, logoutOnBack: true, children: [jsx(ModalHeading, { children: "Select a wallet to recover" }), list] }));
101
+ // When arriving from a connected page (Manage wallets), go back there without logging out.
102
+ // When arriving from the login flow, go back to providers and log out.
103
+ const fromConnected = previousRoute != null && CONNECTED_ROUTES.has(previousRoute.route);
104
+ return (jsxs(PageContent, { onBack: fromConnected ? 'back' : routes.PROVIDERS, logoutOnBack: !fromConnected, children: [jsx(ModalHeading, { children: "Select a wallet" }), jsx(WalletListScroll, { children: list })] }));
67
105
  }
68
106
 
69
107
  export { SelectWalletToRecover as default };
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -139,6 +139,11 @@ function useEthereumEmbeddedWallet(options) {
139
139
  ...(accountType !== DEFAULT_ACCOUNT_TYPE && { chainId: (_d = createOptions === null || createOptions === void 0 ? void 0 : createOptions.chainId) !== null && _d !== void 0 ? _d : creationChainId }),
140
140
  recoveryParams,
141
141
  });
142
+ // Set the address before fetching accounts or setting connected state.
143
+ // This prevents a race where the sync effect sees status='connected' but
144
+ // no activeEmbeddedAddress and disconnects (especially for the first wallet
145
+ // when embeddedAccounts is still empty).
146
+ setActiveEmbeddedAddress(account.address);
142
147
  await updateEmbeddedAccounts({ silent: true });
143
148
  const provider = await getEmbeddedEthereumProvider();
144
149
  const connectedWallet = buildConnectedWallet(account, 0, async () => provider, {
@@ -151,7 +156,6 @@ function useEthereumEmbeddedWallet(options) {
151
156
  provider,
152
157
  error: null,
153
158
  });
154
- setActiveEmbeddedAddress(account.address);
155
159
  (_e = createOptions === null || createOptions === void 0 ? void 0 : createOptions.onSuccess) === null || _e === void 0 ? void 0 : _e.call(createOptions, { account });
156
160
  return account;
157
161
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useEthereumEmbeddedWallet.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"useEthereumEmbeddedWallet.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,4 +1,4 @@
1
- import type { UIAuthProvider } from '../../../components/Openfort/types';
1
+ import { UIAuthProvider } from '../../../components/Openfort/types';
2
2
  import { OpenfortError } from '../../../core/errors';
3
3
  import type { OpenfortHookOptions } from '../../../types';
4
4
  import type { CreateWalletPostAuthOptions } from './useConnectToWalletPostAuth';
@@ -1,6 +1,8 @@
1
1
  import { useState, useRef, useEffect } from 'react';
2
+ import { UIAuthProvider } from '../../../components/Openfort/types.js';
2
3
  import { OpenfortError, OpenfortReactErrorType } from '../../../types.js';
3
4
  import { logger } from '../../../utils/logger.js';
5
+ import { parseCallbackUrl, suppressReferrer } from '../../../utils/urlSecurity.js';
4
6
  import { useEmailAuth } from './useEmailAuth.js';
5
7
  import { useOAuth } from './useOAuth.js';
6
8
 
@@ -79,17 +81,27 @@ const useAuthCallback = ({ enabled = true, // Automatically handle OAuth and ema
79
81
  if (callbackProcessedRef.current)
80
82
  return;
81
83
  callbackProcessedRef.current = true;
84
+ // Parse callback URL (fixes OF-1013 duplicate `?` issue)
85
+ const url = parseCallbackUrl(window.location.href);
86
+ const rawProvider = url.searchParams.get('openfortAuthProvider');
87
+ // Allowlist: UIAuthProvider values + callback-only providers set by buildCallbackUrl
88
+ const validProviders = new Set([
89
+ ...Object.values(UIAuthProvider),
90
+ 'email', // set by buildCallbackUrl for email verification
91
+ 'password', // set by buildCallbackUrl for password reset
92
+ ]);
93
+ if (!rawProvider || !validProviders.has(rawProvider)) {
94
+ return;
95
+ }
96
+ // Validated against the allowlist above
97
+ const openfortAuthProvider = rawProvider;
98
+ // Suppress Referer SYNCHRONOUSLY — before any async work — so that
99
+ // subresource requests cannot leak access_token to third parties.
100
+ const restoreReferrer = suppressReferrer();
82
101
  (async () => {
83
102
  var _a, _b, _c;
84
- // redirectUrl is not working with query params OF-1013
85
- const fixedUrl = window.location.href.replace('?state=', '&state=');
86
- const url = new URL(fixedUrl);
87
- const openfortAuthProvider = url.searchParams.get('openfortAuthProvider');
88
- if (!openfortAuthProvider) {
89
- return;
90
- }
91
103
  setProvider(openfortAuthProvider);
92
- if (openfortAuthProvider === 'email') {
104
+ if (openfortAuthProvider === 'email' || openfortAuthProvider === 'password') {
93
105
  // Email verification flow
94
106
  // The backend verifies the email server-side via /auth/verify-email?token=...
95
107
  // and then redirects here. If a `state` token is present we verify client-side
@@ -101,6 +113,7 @@ const useAuthCallback = ({ enabled = true, // Automatically handle OAuth and ema
101
113
  url.searchParams.delete(key);
102
114
  });
103
115
  window.history.replaceState({}, document.title, url.toString());
116
+ restoreReferrer();
104
117
  };
105
118
  if (state && email) {
106
119
  // State present — verify client-side as well
@@ -130,6 +143,7 @@ const useAuthCallback = ({ enabled = true, // Automatically handle OAuth and ema
130
143
  removeParams();
131
144
  }
132
145
  else {
146
+ restoreReferrer();
133
147
  const err = new OpenfortError('No email found in URL', OpenfortReactErrorType.AUTHENTICATION_ERROR);
134
148
  logger.error('No email found in URL');
135
149
  (_b = hookOptions.onError) === null || _b === void 0 ? void 0 : _b.call(hookOptions, err);
@@ -142,6 +156,7 @@ const useAuthCallback = ({ enabled = true, // Automatically handle OAuth and ema
142
156
  const userId = url.searchParams.get('user_id');
143
157
  const token = url.searchParams.get('access_token');
144
158
  if (!userId || !token) {
159
+ restoreReferrer();
145
160
  logger.error(`Missing user id or access token`, {
146
161
  hasUserId: !!userId,
147
162
  hasToken: !!token,
@@ -157,6 +172,7 @@ const useAuthCallback = ({ enabled = true, // Automatically handle OAuth and ema
157
172
  url.searchParams.delete(key);
158
173
  });
159
174
  window.history.replaceState({}, document.title, url.toString());
175
+ restoreReferrer();
160
176
  };
161
177
  logger.log('callback', { userId });
162
178
  const options = {
@@ -1 +1 @@
1
- {"version":3,"file":"useAuthCallback.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"useAuthCallback.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -61,7 +61,10 @@ function useResolvedIdentity(options) {
61
61
  var _a, _b, _c, _d, _e;
62
62
  const { address, chainType = ChainTypeEnum.EVM, ensChainId = 0, enabled = true } = options;
63
63
  const { walletConfig } = useOpenfort();
64
- const rpcUrl = (_c = (_b = (_a = walletConfig === null || walletConfig === void 0 ? void 0 : walletConfig.ethereum) === null || _a === void 0 ? void 0 : _a.rpcUrls) === null || _b === void 0 ? void 0 : _b[ensChainId]) !== null && _c !== void 0 ? _c : getDefaultEthereumRpcUrl(ensChainId);
64
+ // Only resolve RPC URL for mainnet (ensChainId === 1) chainId 0 means "do not resolve"
65
+ const rpcUrl = ensChainId === 1
66
+ ? ((_c = (_b = (_a = walletConfig === null || walletConfig === void 0 ? void 0 : walletConfig.ethereum) === null || _a === void 0 ? void 0 : _a.rpcUrls) === null || _b === void 0 ? void 0 : _b[ensChainId]) !== null && _c !== void 0 ? _c : getDefaultEthereumRpcUrl(ensChainId))
67
+ : undefined;
65
68
  const isEnabled = enabled && !!address && address.length > 0 && ensChainId === 1 && !!rpcUrl;
66
69
  const { data, error, isLoading } = useAsyncData({
67
70
  queryKey: ['identity', chainType, address, ensChainId],
@@ -1 +1 @@
1
- {"version":3,"file":"useResolvedIdentity.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"useResolvedIdentity.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -219,6 +219,7 @@ const CoreOpenfortProvider = ({ children, onConnect, onDisconnect, openfortConfi
219
219
  storeActiveEmbeddedAddress,
220
220
  chainType,
221
221
  store,
222
+ walletConfig,
222
223
  });
223
224
  // Current chain for EVM provider reconfiguration
224
225
  const evmChainId = (strategy === null || strategy === void 0 ? void 0 : strategy.chainType) === ChainTypeEnum.EVM ? (bridge ? bridge.chainId : strategy === null || strategy === void 0 ? void 0 : strategy.getChainId()) : undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"CoreOpenfortProvider.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"CoreOpenfortProvider.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,5 +1,6 @@
1
1
  import { type ChainTypeEnum, type Openfort } from '@openfort/openfort-js';
2
2
  import type { StoreApi } from 'zustand/vanilla';
3
+ import type { OpenfortWalletConfig } from '../../components/Openfort/types';
3
4
  import type { OpenfortStore } from '../store';
4
5
  type Params = {
5
6
  openfort: Openfort;
@@ -8,13 +9,16 @@ type Params = {
8
9
  storeActiveEmbeddedAddress: OpenfortStore['activeEmbeddedAddress'];
9
10
  chainType: ChainTypeEnum;
10
11
  store: StoreApi<OpenfortStore>;
12
+ walletConfig: OpenfortWalletConfig | undefined;
11
13
  };
12
14
  /**
13
15
  * Syncs the active embedded address into the store.
14
16
  *
15
17
  * - Clears address when there are no accounts or user is logged out.
16
- * - When READY: asks the SDK for its active wallet, falls back to the first
17
- * account for the current chain.
18
+ * - When connectOnLogin is false and the session went through
19
+ * EMBEDDED_SIGNER_NOT_CONFIGURED (login flow), skips auto-seeding at READY
20
+ * so the user must pick a wallet explicitly.
21
+ * - Returning sessions (SDK starts directly at READY) always auto-seed.
18
22
  */
19
- export declare function useActiveAddressSync({ openfort, storeEmbeddedAccounts, storeEmbeddedState, storeActiveEmbeddedAddress, chainType, store, }: Params): void;
23
+ export declare function useActiveAddressSync({ openfort, storeEmbeddedAccounts, storeEmbeddedState, storeActiveEmbeddedAddress, chainType, store, walletConfig, }: Params): void;
20
24
  export {};
@@ -1,18 +1,30 @@
1
1
  import { EmbeddedState } from '@openfort/openfort-js';
2
- import { useEffect } from 'react';
2
+ import { useRef, useEffect } from 'react';
3
3
  import { firstEmbeddedAddress } from '../../core/strategyUtils.js';
4
4
 
5
5
  /**
6
6
  * Syncs the active embedded address into the store.
7
7
  *
8
8
  * - Clears address when there are no accounts or user is logged out.
9
- * - When READY: asks the SDK for its active wallet, falls back to the first
10
- * account for the current chain.
9
+ * - When connectOnLogin is false and the session went through
10
+ * EMBEDDED_SIGNER_NOT_CONFIGURED (login flow), skips auto-seeding at READY
11
+ * so the user must pick a wallet explicitly.
12
+ * - Returning sessions (SDK starts directly at READY) always auto-seed.
11
13
  */
12
- function useActiveAddressSync({ openfort, storeEmbeddedAccounts, storeEmbeddedState, storeActiveEmbeddedAddress, chainType, store, }) {
14
+ function useActiveAddressSync({ openfort, storeEmbeddedAccounts, storeEmbeddedState, storeActiveEmbeddedAddress, chainType, store, walletConfig, }) {
15
+ var _a;
16
+ const connectOnLogin = (_a = walletConfig === null || walletConfig === void 0 ? void 0 : walletConfig.connectOnLogin) !== null && _a !== void 0 ? _a : true;
17
+ // Track whether the current session went through EMBEDDED_SIGNER_NOT_CONFIGURED
18
+ // (i.e. a login flow, not a returning session). When connectOnLogin is false, we
19
+ // skip auto-seeding at READY only for login flows — returning sessions (where the
20
+ // SDK starts directly at READY with a configured signer) should always auto-seed.
21
+ const sawSignerNotConfiguredRef = useRef(false);
13
22
  useEffect(() => {
14
23
  if (!openfort || !(storeEmbeddedAccounts === null || storeEmbeddedAccounts === void 0 ? void 0 : storeEmbeddedAccounts.length)) {
15
- if (!(storeEmbeddedAccounts === null || storeEmbeddedAccounts === void 0 ? void 0 : storeEmbeddedAccounts.length)) {
24
+ // Only clear the address on genuine logout (no user). During wallet creation
25
+ // the accounts list is briefly empty while updateEmbeddedAccounts refetches —
26
+ // clearing here would undo the address that create() just set.
27
+ if (!(storeEmbeddedAccounts === null || storeEmbeddedAccounts === void 0 ? void 0 : storeEmbeddedAccounts.length) && !store.getState().user) {
16
28
  store.getState().setActiveEmbeddedAddress(undefined);
17
29
  }
18
30
  return;
@@ -23,13 +35,16 @@ function useActiveAddressSync({ openfort, storeEmbeddedAccounts, storeEmbeddedSt
23
35
  if (storeEmbeddedState === EmbeddedState.UNAUTHENTICATED || storeEmbeddedState === EmbeddedState.NONE) {
24
36
  if (!store.getState().user) {
25
37
  store.getState().setActiveEmbeddedAddress(undefined);
38
+ sawSignerNotConfiguredRef.current = false;
26
39
  }
27
40
  return;
28
41
  }
29
42
  // Bootstrap recovery: when signer is not yet configured and no address is set,
30
43
  // seed the active address so useAutoRecovery can trigger and drive state → READY.
44
+ // Skip when connectOnLogin is false — the user should explicitly choose a wallet.
31
45
  if (storeEmbeddedState === EmbeddedState.EMBEDDED_SIGNER_NOT_CONFIGURED) {
32
- if (storeActiveEmbeddedAddress === undefined) {
46
+ sawSignerNotConfiguredRef.current = true;
47
+ if (connectOnLogin && storeActiveEmbeddedAddress === undefined) {
33
48
  const first = firstEmbeddedAddress(storeEmbeddedAccounts, chainType);
34
49
  if (first)
35
50
  store.getState().setActiveEmbeddedAddress(first);
@@ -42,6 +57,13 @@ function useActiveAddressSync({ openfort, storeEmbeddedAccounts, storeEmbeddedSt
42
57
  // Already have an address — nothing to resolve
43
58
  if (storeActiveEmbeddedAddress !== undefined)
44
59
  return;
60
+ // Login flow with connectOnLogin=false: the session went through
61
+ // EMBEDDED_SIGNER_NOT_CONFIGURED, so don't auto-seed. The user must pick
62
+ // a wallet via the UI. create() and setActive() set the address directly,
63
+ // so they bypass this check.
64
+ if (!connectOnLogin && sawSignerNotConfiguredRef.current) {
65
+ return;
66
+ }
45
67
  // Priority 1: ask the SDK for its active wallet
46
68
  let cancelled = false;
47
69
  openfort.embeddedWallet
@@ -69,7 +91,15 @@ function useActiveAddressSync({ openfort, storeEmbeddedAccounts, storeEmbeddedSt
69
91
  return () => {
70
92
  cancelled = true;
71
93
  };
72
- }, [openfort, storeEmbeddedAccounts, storeEmbeddedState, storeActiveEmbeddedAddress, chainType, store]);
94
+ }, [
95
+ openfort,
96
+ storeEmbeddedAccounts,
97
+ storeEmbeddedState,
98
+ storeActiveEmbeddedAddress,
99
+ chainType,
100
+ store,
101
+ connectOnLogin,
102
+ ]);
73
103
  }
74
104
 
75
105
  export { useActiveAddressSync };
@@ -1 +1 @@
1
- {"version":3,"file":"useActiveAddressSync.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"useActiveAddressSync.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -123,6 +123,7 @@ function useSolanaEmbeddedWallet(options) {
123
123
  accountType: AccountTypeEnum.EOA,
124
124
  recoveryParams,
125
125
  });
126
+ setActiveEmbeddedAddress(account.address);
126
127
  await updateEmbeddedAccounts({ silent: true });
127
128
  const provider = createProviderForAccount(account);
128
129
  const connectedWallet = {
@@ -139,7 +140,6 @@ function useSolanaEmbeddedWallet(options) {
139
140
  provider,
140
141
  error: null,
141
142
  });
142
- setActiveEmbeddedAddress(account.address);
143
143
  (_a = createOptions === null || createOptions === void 0 ? void 0 : createOptions.onSuccess) === null || _a === void 0 ? void 0 : _a.call(createOptions, { account });
144
144
  return account;
145
145
  }
@@ -6,10 +6,6 @@
6
6
  */
7
7
  import type { Chain } from 'viem';
8
8
  import type { SolanaCluster } from '../solana/types';
9
- /**
10
- * Get default Ethereum RPC URL for a chain ID.
11
- * Returns the viem/chains default RPC when known, falls back to Sepolia.
12
- */
13
9
  export declare function getDefaultEthereumRpcUrl(chainId: number): string;
14
10
  /**
15
11
  * Get default Solana RPC URL for a cluster.
@@ -30,11 +30,15 @@ const DEFAULT_SOLANA_RPC_URLS = {
30
30
  * Get default Ethereum RPC URL for a chain ID.
31
31
  * Returns the viem/chains default RPC when known, falls back to Sepolia.
32
32
  */
33
+ const warnedChainIds = new Set();
33
34
  function getDefaultEthereumRpcUrl(chainId) {
34
35
  const chain = KNOWN_CHAINS[chainId];
35
36
  const rpcUrl = chain === null || chain === void 0 ? void 0 : chain.rpcUrls.default.http[0];
36
37
  if (!rpcUrl) {
37
- logger.warn(`No default Ethereum RPC URL found for chain ${chainId}. Configure rpcUrls in OpenfortProvider for better reliability and rate limits.`);
38
+ if (!warnedChainIds.has(chainId)) {
39
+ warnedChainIds.add(chainId);
40
+ logger.warn(`No default Ethereum RPC URL found for chain ${chainId}. Configure rpcUrls in OpenfortProvider for better reliability and rate limits.`);
41
+ }
38
42
  return sepolia.rpcUrls.default.http[0];
39
43
  }
40
44
  return rpcUrl;
@@ -1 +1 @@
1
- {"version":3,"file":"rpc.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"rpc.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Utilities for secure handling of OAuth callback URLs containing tokens.
3
+ *
4
+ * Prevents Referer-header leakage of access tokens and provides robust
5
+ * URL parsing for the OF-1013 workaround (duplicate `?` in redirect URLs).
6
+ */
7
+ /**
8
+ * Injects a `<meta name="referrer" content="no-referrer">` tag so that
9
+ * any subresource request fired before `history.replaceState` strips the
10
+ * tokens will NOT leak the full URL (including access_token) via the
11
+ * Referer header.
12
+ *
13
+ * Call this **synchronously** — before any `await` — when the URL
14
+ * contains sensitive query parameters.
15
+ *
16
+ * @returns A cleanup function that removes the meta tag.
17
+ */
18
+ export declare function suppressReferrer(): () => void;
19
+ /**
20
+ * Parses the current `window.location.href`, fixing the OF-1013 issue
21
+ * where the server redirect produces a URL with a duplicate `?`, e.g.
22
+ * `https://example.com/callback?existing=1?access_token=xxx&user_id=yyy`.
23
+ *
24
+ * Instead of a fragile `.replace('?access_token=', '&access_token=')`
25
+ * that can mangle values containing the same substring, this finds the
26
+ * *second* `?` (if any) and replaces it with `&`.
27
+ */
28
+ export declare function parseCallbackUrl(href: string): URL;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Utilities for secure handling of OAuth callback URLs containing tokens.
3
+ *
4
+ * Prevents Referer-header leakage of access tokens and provides robust
5
+ * URL parsing for the OF-1013 workaround (duplicate `?` in redirect URLs).
6
+ */
7
+ const REFERRER_META_ID = '__openfort_no_referrer';
8
+ /**
9
+ * Injects a `<meta name="referrer" content="no-referrer">` tag so that
10
+ * any subresource request fired before `history.replaceState` strips the
11
+ * tokens will NOT leak the full URL (including access_token) via the
12
+ * Referer header.
13
+ *
14
+ * Call this **synchronously** — before any `await` — when the URL
15
+ * contains sensitive query parameters.
16
+ *
17
+ * @returns A cleanup function that removes the meta tag.
18
+ */
19
+ function suppressReferrer() {
20
+ if (typeof document === 'undefined')
21
+ return () => { };
22
+ // Avoid duplicates if called more than once
23
+ if (document.getElementById(REFERRER_META_ID))
24
+ return () => { };
25
+ const meta = document.createElement('meta');
26
+ meta.id = REFERRER_META_ID;
27
+ meta.name = 'referrer';
28
+ meta.content = 'no-referrer';
29
+ document.head.appendChild(meta);
30
+ return () => {
31
+ meta.remove();
32
+ };
33
+ }
34
+ /**
35
+ * Parses the current `window.location.href`, fixing the OF-1013 issue
36
+ * where the server redirect produces a URL with a duplicate `?`, e.g.
37
+ * `https://example.com/callback?existing=1?access_token=xxx&user_id=yyy`.
38
+ *
39
+ * Instead of a fragile `.replace('?access_token=', '&access_token=')`
40
+ * that can mangle values containing the same substring, this finds the
41
+ * *second* `?` (if any) and replaces it with `&`.
42
+ */
43
+ function parseCallbackUrl(href) {
44
+ const firstQ = href.indexOf('?');
45
+ if (firstQ === -1)
46
+ return new URL(href);
47
+ const secondQ = href.indexOf('?', firstQ + 1);
48
+ if (secondQ === -1)
49
+ return new URL(href);
50
+ // Replace only the second `?` with `&`
51
+ const fixed = `${href.slice(0, secondQ)}&${href.slice(secondQ + 1)}`;
52
+ return new URL(fixed);
53
+ }
54
+
55
+ export { parseCallbackUrl, suppressReferrer };
56
+ //# sourceMappingURL=urlSecurity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"urlSecurity.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1 +1 @@
1
- export declare const OPENFORT_VERSION = "1.0.6";
1
+ export declare const OPENFORT_VERSION = "1.0.8";
package/build/version.js CHANGED
@@ -1,4 +1,4 @@
1
- const OPENFORT_VERSION = '1.0.6';
1
+ const OPENFORT_VERSION = '1.0.8';
2
2
 
3
3
  export { OPENFORT_VERSION };
4
4
  //# sourceMappingURL=version.js.map
@@ -1,5 +1,5 @@
1
1
  import { OpenfortError } from '@openfort/openfort-js';
2
- import { useCallback } from 'react';
2
+ import { useRef, useCallback } from 'react';
3
3
  import { useEthereumBridge } from '../ethereum/OpenfortEthereumBridgeContext.js';
4
4
  import { useOpenfortCore } from '../openfort/useOpenfort.js';
5
5
  import { createSIWEMessage } from '../siwe/create-siwe-message.js';
@@ -17,27 +17,35 @@ import { logger } from '../utils/logger.js';
17
17
  * ```
18
18
  */
19
19
  function useConnectWithSiwe() {
20
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
21
20
  const { client, user, updateUser } = useOpenfortCore();
22
21
  const bridge = useEthereumBridge();
23
- const address = (_a = bridge === null || bridge === void 0 ? void 0 : bridge.account) === null || _a === void 0 ? void 0 : _a.address;
24
- const connector = (_b = bridge === null || bridge === void 0 ? void 0 : bridge.account) === null || _b === void 0 ? void 0 : _b.connector;
25
- const chainId = (_c = bridge === null || bridge === void 0 ? void 0 : bridge.chainId) !== null && _c !== void 0 ? _c : 0;
26
- const accountChainId = (_f = (_e = (_d = bridge === null || bridge === void 0 ? void 0 : bridge.account) === null || _d === void 0 ? void 0 : _d.chain) === null || _e === void 0 ? void 0 : _e.id) !== null && _f !== void 0 ? _f : bridge === null || bridge === void 0 ? void 0 : bridge.chainId;
27
- const chainName = (_h = (_g = bridge === null || bridge === void 0 ? void 0 : bridge.account) === null || _g === void 0 ? void 0 : _g.chain) === null || _h === void 0 ? void 0 : _h.name;
28
- const switchChainAsync = (_j = bridge === null || bridge === void 0 ? void 0 : bridge.switchChain) === null || _j === void 0 ? void 0 : _j.switchChainAsync;
29
- const signMessage = bridge === null || bridge === void 0 ? void 0 : bridge.signMessage;
30
- const connectWithSiwe = useCallback(async ({ onError, onConnect, address: propsAddress, connectorType: propsConnectorType, walletClientType: propsWalletClientType, link = !!user, } = {}) => {
31
- var _a, _b;
32
- const addressToUse = propsAddress !== null && propsAddress !== void 0 ? propsAddress : address;
33
- const connectorType = propsConnectorType !== null && propsConnectorType !== void 0 ? propsConnectorType : connector === null || connector === void 0 ? void 0 : connector.type;
34
- const walletClientType = propsWalletClientType !== null && propsWalletClientType !== void 0 ? propsWalletClientType : connector === null || connector === void 0 ? void 0 : connector.id;
35
- if (!addressToUse || !connectorType || !walletClientType) {
36
- logger.log('No address found', { address: addressToUse, connectorType, walletClientType });
22
+ // Use a ref so the callback always reads the latest bridge state,
23
+ // not a stale closure from the last render (critical after connectAsync changes the active connector).
24
+ const bridgeRef = useRef(bridge);
25
+ bridgeRef.current = bridge;
26
+ const userRef = useRef(user);
27
+ userRef.current = user;
28
+ const connectWithSiwe = useCallback(async ({ onError, onConnect, address: propsAddress, connectorType: propsConnectorType, walletClientType: propsWalletClientType, link, } = {}) => {
29
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
30
+ // Read fresh values from the bridge ref — NOT from the stale render closure
31
+ const b = bridgeRef.current;
32
+ const currentUser = userRef.current;
33
+ const shouldLink = link !== null && link !== void 0 ? link : !!currentUser;
34
+ const address = propsAddress !== null && propsAddress !== void 0 ? propsAddress : (_a = b === null || b === void 0 ? void 0 : b.account) === null || _a === void 0 ? void 0 : _a.address;
35
+ const connectorType = propsConnectorType !== null && propsConnectorType !== void 0 ? propsConnectorType : (_c = (_b = b === null || b === void 0 ? void 0 : b.account) === null || _b === void 0 ? void 0 : _b.connector) === null || _c === void 0 ? void 0 : _c.type;
36
+ const walletClientType = propsWalletClientType !== null && propsWalletClientType !== void 0 ? propsWalletClientType : (_e = (_d = b === null || b === void 0 ? void 0 : b.account) === null || _d === void 0 ? void 0 : _d.connector) === null || _e === void 0 ? void 0 : _e.id;
37
+ const chainId = (_f = b === null || b === void 0 ? void 0 : b.chainId) !== null && _f !== void 0 ? _f : 0;
38
+ const accountChainId = (_j = (_h = (_g = b === null || b === void 0 ? void 0 : b.account) === null || _g === void 0 ? void 0 : _g.chain) === null || _h === void 0 ? void 0 : _h.id) !== null && _j !== void 0 ? _j : b === null || b === void 0 ? void 0 : b.chainId;
39
+ const chainName = (_l = (_k = b === null || b === void 0 ? void 0 : b.account) === null || _k === void 0 ? void 0 : _k.chain) === null || _l === void 0 ? void 0 : _l.name;
40
+ const switchChainAsync = (_m = b === null || b === void 0 ? void 0 : b.switchChain) === null || _m === void 0 ? void 0 : _m.switchChainAsync;
41
+ const signMessage = b === null || b === void 0 ? void 0 : b.signMessage;
42
+ if (!address || !connectorType || !walletClientType) {
43
+ logger.warn('[useConnectWithSiwe] Missing params', { address, connectorType, walletClientType });
37
44
  onError === null || onError === void 0 ? void 0 : onError('No address found');
38
45
  return;
39
46
  }
40
47
  if (!signMessage) {
48
+ logger.warn('[useConnectWithSiwe] No signMessage on bridge');
41
49
  onError === null || onError === void 0 ? void 0 : onError('EVM bridge not available (signMessage)');
42
50
  return;
43
51
  }
@@ -46,46 +54,44 @@ function useConnectWithSiwe() {
46
54
  await switchChainAsync({ chainId });
47
55
  }
48
56
  let nonce;
49
- if (link) {
50
- const resp = await client.auth.initLinkSiwe({ address: addressToUse });
57
+ if (shouldLink) {
58
+ const resp = await client.auth.initLinkSiwe({ address });
51
59
  nonce = resp.nonce;
52
60
  }
53
61
  else {
54
- const resp = await client.auth.initSiwe({ address: addressToUse });
62
+ const resp = await client.auth.initSiwe({ address });
55
63
  nonce = resp.nonce;
56
64
  }
57
- const SIWEMessage = createSIWEMessage(addressToUse, nonce, chainId);
65
+ const SIWEMessage = createSIWEMessage(address, nonce, chainId);
58
66
  if (!SIWEMessage)
59
67
  throw new Error('SIWE message creation failed (window not available)');
60
68
  const signature = await signMessage({ message: SIWEMessage });
61
- if (link) {
62
- logger.log('Linking wallet to user');
69
+ if (shouldLink) {
63
70
  await client.auth.linkWithSiwe({
64
71
  signature,
65
72
  message: SIWEMessage,
66
73
  connectorType,
67
74
  walletClientType,
68
- address: addressToUse,
75
+ address,
69
76
  chainId,
70
77
  });
71
78
  }
72
79
  else {
73
- logger.log('Authenticating with SIWE');
74
80
  await client.auth.loginWithSiwe({
75
81
  signature,
76
82
  message: SIWEMessage,
77
83
  connectorType,
78
84
  walletClientType,
79
- address: addressToUse,
85
+ address,
80
86
  });
81
87
  }
82
88
  await updateUser();
83
89
  await Promise.resolve(onConnect === null || onConnect === void 0 ? void 0 : onConnect());
84
90
  }
85
91
  catch (err) {
86
- logger.log('Failed to connect with SIWE', {
87
- error: err,
88
- status: (_b = (_a = err === null || err === void 0 ? void 0 : err.response) === null || _a === void 0 ? void 0 : _a.status) !== null && _b !== void 0 ? _b : 'unknown',
92
+ logger.error('[useConnectWithSiwe] SIWE failed', {
93
+ message: err instanceof Error ? err.message : String(err),
94
+ status: (_o = err === null || err === void 0 ? void 0 : err.response) === null || _o === void 0 ? void 0 : _o.status,
89
95
  });
90
96
  if (!onError)
91
97
  return;
@@ -107,7 +113,7 @@ function useConnectWithSiwe() {
107
113
  }
108
114
  onError(message, err instanceof OpenfortError ? err : undefined);
109
115
  }
110
- }, [client, user, updateUser, address, chainId, connector, accountChainId, chainName, switchChainAsync, signMessage]);
116
+ }, [client, updateUser]);
111
117
  return { connectWithSiwe };
112
118
  }
113
119