@openfort/react 1.1.1 → 1.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.
Files changed (36) hide show
  1. package/build/components/Common/ErrorFallbackPage/index.d.ts +7 -0
  2. package/build/components/Common/ErrorFallbackPage/index.js +17 -0
  3. package/build/components/Common/ErrorFallbackPage/index.js.map +1 -0
  4. package/build/components/Common/NotFoundFallback/index.d.ts +6 -0
  5. package/build/components/Common/NotFoundFallback/index.js +11 -0
  6. package/build/components/Common/NotFoundFallback/index.js.map +1 -0
  7. package/build/components/Common/TickList/styles.js +7 -6
  8. package/build/components/Common/TickList/styles.js.map +1 -1
  9. package/build/components/Common/WalletConnectNotConfigured/index.d.ts +9 -0
  10. package/build/components/Common/WalletConnectNotConfigured/index.js +26 -0
  11. package/build/components/Common/WalletConnectNotConfigured/index.js.map +1 -0
  12. package/build/components/ConnectModal/ConnectWithMobile.js +5 -1
  13. package/build/components/ConnectModal/ConnectWithMobile.js.map +1 -1
  14. package/build/components/ConnectModal/ConnectWithQRCode.js +7 -1
  15. package/build/components/ConnectModal/ConnectWithQRCode.js.map +1 -1
  16. package/build/components/Pages/CreateWallet/SolanaCreateWallet.js +2 -2
  17. package/build/components/Pages/CreateWallet/index.js +2 -2
  18. package/build/components/Pages/LoadWallets/index.js +9 -0
  19. package/build/components/Pages/LoadWallets/index.js.map +1 -1
  20. package/build/components/Pages/Loading/index.js +8 -0
  21. package/build/components/Pages/Loading/index.js.map +1 -1
  22. package/build/components/Pages/MobileConnectors/index.js +5 -0
  23. package/build/components/Pages/MobileConnectors/index.js.map +1 -1
  24. package/build/components/Pages/SendConfirmation/index.js +16 -4
  25. package/build/components/Pages/SendConfirmation/index.js.map +1 -1
  26. package/build/components/PasswordStrength/PasswordStrengthIndicator.d.ts +1 -1
  27. package/build/components/PasswordStrength/PasswordStrengthIndicator.js +10 -1
  28. package/build/components/PasswordStrength/PasswordStrengthIndicator.js.map +1 -1
  29. package/build/hooks/openfort/useUI.js +16 -20
  30. package/build/hooks/openfort/useUI.js.map +1 -1
  31. package/build/hooks/useTimedOut.d.ts +2 -0
  32. package/build/hooks/useTimedOut.js +14 -0
  33. package/build/hooks/useTimedOut.js.map +1 -0
  34. package/build/version.d.ts +1 -1
  35. package/build/version.js +1 -1
  36. package/package.json +2 -2
@@ -0,0 +1,7 @@
1
+ type ErrorFallbackPageProps = {
2
+ header: string;
3
+ description: string;
4
+ };
5
+ /** Shared error page with a single way out: back to the providers (sign-in) screen. */
6
+ declare const ErrorFallbackPage: ({ header, description }: ErrorFallbackPageProps) => import("react/jsx-runtime").JSX.Element;
7
+ export default ErrorFallbackPage;
@@ -0,0 +1,17 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { useOpenfortCore } from '../../../openfort/useOpenfort.js';
3
+ import { routes } from '../../Openfort/types.js';
4
+ import { useOpenfort } from '../../Openfort/useOpenfort.js';
5
+ import { PageContent } from '../../PageContent/index.js';
6
+ import Button from '../Button/index.js';
7
+ import Loader from '../Loading/index.js';
8
+
9
+ /** Shared error page with a single way out: back to the providers (sign-in) screen. */
10
+ const ErrorFallbackPage = ({ header, description }) => {
11
+ const { setRoute } = useOpenfort();
12
+ const { user } = useOpenfortCore();
13
+ return (jsxs(PageContent, { onBack: routes.PROVIDERS, children: [jsx(Loader, { header: header, isError: true, description: description }), jsx(Button, { onClick: () => setRoute(routes.PROVIDERS), children: user ? 'Go back' : 'Back to sign in' })] }));
14
+ };
15
+
16
+ export { ErrorFallbackPage as default };
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Catch-all page for flows that would otherwise spin forever
3
+ * (loading watchdog timeouts, unreachable states).
4
+ */
5
+ declare const NotFoundFallback: () => import("react/jsx-runtime").JSX.Element;
6
+ export default NotFoundFallback;
@@ -0,0 +1,11 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import ErrorFallbackPage from '../ErrorFallbackPage/index.js';
3
+
4
+ /**
5
+ * Catch-all page for flows that would otherwise spin forever
6
+ * (loading watchdog timeouts, unreachable states).
7
+ */
8
+ const NotFoundFallback = () => (jsx(ErrorFallbackPage, { header: "This is taking longer than expected", description: "We couldn't load this screen. Go back and try again." }));
9
+
10
+ export { NotFoundFallback as default };
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;"}
@@ -3,7 +3,7 @@ import styled from '../../../styles/styled/index.js';
3
3
  const TickListContainer = styled.ul `
4
4
  display: flex;
5
5
  flex-direction: column;
6
- gap: 8px;
6
+ gap: 6px;
7
7
  padding-top: 8px;
8
8
  padding-bottom: 8px;
9
9
  `;
@@ -11,16 +11,17 @@ const TickItem = styled.li `
11
11
  display: flex;
12
12
  align-items: center;
13
13
  text-align: left;
14
- gap: 8px;
15
- font-size: 16px;
16
- line-height: 24px;
14
+ gap: 6px;
15
+ font-size: 13px;
16
+ line-height: 18px;
17
+ color: var(--ck-body-color-muted);
17
18
  `;
18
19
  const TickIconWrapper = styled.span `
19
20
  display: flex;
20
21
  align-items: center;
21
22
  justify-content: center;
22
- width: 16px;
23
- height: 16px;
23
+ width: 14px;
24
+ height: 14px;
24
25
  flex-shrink: 0;
25
26
  `;
26
27
 
@@ -1 +1 @@
1
- {"version":3,"file":"styles.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"styles.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,9 @@
1
+ /** True when a WalletConnect connector is configured (i.e. a projectId was provided). */
2
+ export declare function useHasWalletConnect(): boolean;
3
+ /**
4
+ * Shown when a WalletConnect-dependent flow is opened but no WalletConnect
5
+ * projectId was configured. End users get neutral copy; the actionable
6
+ * config hint goes to the developer console.
7
+ */
8
+ declare const WalletConnectNotConfigured: () => import("react/jsx-runtime").JSX.Element;
9
+ export default WalletConnectNotConfigured;
@@ -0,0 +1,26 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { useEffect } from 'react';
3
+ import { useEthereumBridge } from '../../../ethereum/OpenfortEthereumBridgeContext.js';
4
+ import { isWalletConnectConnector } from '../../../utils/index.js';
5
+ import ErrorFallbackPage from '../ErrorFallbackPage/index.js';
6
+
7
+ /** True when a WalletConnect connector is configured (i.e. a projectId was provided). */
8
+ function useHasWalletConnect() {
9
+ const bridge = useEthereumBridge();
10
+ return !!(bridge === null || bridge === void 0 ? void 0 : bridge.connectors.some((c) => isWalletConnectConnector(c.id)));
11
+ }
12
+ /**
13
+ * Shown when a WalletConnect-dependent flow is opened but no WalletConnect
14
+ * projectId was configured. End users get neutral copy; the actionable
15
+ * config hint goes to the developer console.
16
+ */
17
+ const WalletConnectNotConfigured = () => {
18
+ useEffect(() => {
19
+ // biome-ignore lint/suspicious/noConsole: config error must reach developers without debug mode
20
+ console.warn('[Openfort-React] WalletConnect is not configured: pass walletConnectProjectId to getDefaultConnectors (e.g. via your WalletConnect env variable) to enable external wallet connections.');
21
+ }, []);
22
+ return (jsx(ErrorFallbackPage, { header: "Wallet connections unavailable", description: "External wallet connections aren't available right now. Please use another sign-in method." }));
23
+ };
24
+
25
+ export { WalletConnectNotConfigured as default, useHasWalletConnect };
26
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,4 +1,4 @@
1
- import { jsxs, jsx } from 'react/jsx-runtime';
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
2
  import { useState, useEffect } from 'react';
3
3
  import { embeddedWalletId } from '../../constants/openfort.js';
4
4
  import { useEthereumBridge } from '../../ethereum/OpenfortEthereumBridgeContext.js';
@@ -11,6 +11,7 @@ import { walletConfigs } from '../../wallets/walletConfigs.js';
11
11
  import Button from '../Common/Button/index.js';
12
12
  import FitText from '../Common/FitText/index.js';
13
13
  import Loader from '../Common/Loading/index.js';
14
+ import WalletConnectNotConfigured, { useHasWalletConnect } from '../Common/WalletConnectNotConfigured/index.js';
14
15
  import { routes } from '../Openfort/types.js';
15
16
  import { useOpenfort } from '../Openfort/useOpenfort.js';
16
17
  import { PageContent } from '../PageContent/index.js';
@@ -36,6 +37,7 @@ const ConnectWithMobile = () => {
36
37
  .indexOf(connector.id) !== -1);
37
38
  const wallet = useExternalConnector(connector.id) || (walletId && walletConfigs[walletId]) || {};
38
39
  const bridge = useEthereumBridge();
40
+ const hasWalletConnect = useHasWalletConnect();
39
41
  // Only consider external wallets as "connected" — ignore the embedded wallet connector
40
42
  const isExternalConnected = (_d = (((_a = bridge === null || bridge === void 0 ? void 0 : bridge.account) === null || _a === void 0 ? void 0 : _a.isConnected) && ((_c = (_b = bridge === null || bridge === void 0 ? void 0 : bridge.account) === null || _b === void 0 ? void 0 : _b.connector) === null || _c === void 0 ? void 0 : _c.id) !== embeddedWalletId)) !== null && _d !== void 0 ? _d : false;
41
43
  const [status, setStatus] = useState(isExternalConnected ? states.CONNECTING : states.INIT);
@@ -87,6 +89,8 @@ const ConnectWithMobile = () => {
87
89
  break;
88
90
  }
89
91
  }, [status]);
92
+ if (!hasWalletConnect)
93
+ return jsx(WalletConnectNotConfigured, {});
90
94
  return (jsxs(PageContent, { children: [jsx(Loader, { header: `Connecting with ${connector.id.split(',')[0]}`, icon: wallet === null || wallet === void 0 ? void 0 : wallet.icon, isError: status === states.ERROR, description: description, onRetry: () => {
91
95
  setStatus(isExternalConnected ? states.CONNECTING : states.INIT);
92
96
  setDescription('');
@@ -1 +1 @@
1
- {"version":3,"file":"ConnectWithMobile.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"ConnectWithMobile.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -9,6 +9,7 @@ import { useExternalConnector } from '../../wallets/useExternalConnectors.js';
9
9
  import { CopyText } from '../Common/CopyToClipboard/CopyText.js';
10
10
  import Loader from '../Common/Loading/index.js';
11
11
  import { ModalBody } from '../Common/Modal/styles.js';
12
+ import WalletConnectNotConfigured, { useHasWalletConnect } from '../Common/WalletConnectNotConfigured/index.js';
12
13
  import { routes } from '../Openfort/types.js';
13
14
  import { useOpenfort } from '../Openfort/useOpenfort.js';
14
15
  import { PageContent } from '../PageContent/index.js';
@@ -45,6 +46,7 @@ const ConnectWithWalletConnect = () => {
45
46
  const { connector } = useOpenfort();
46
47
  const wallet = useExternalConnector(connector.id);
47
48
  const { open: openWalletConnectModal } = useWalletConnectModal();
49
+ const hasWalletConnect = useHasWalletConnect();
48
50
  const [error, setError] = useState(undefined);
49
51
  const hasOpenedRef = useRef(false);
50
52
  const openModal = useCallback(async () => {
@@ -54,11 +56,15 @@ const ConnectWithWalletConnect = () => {
54
56
  setError(error);
55
57
  }, [openWalletConnectModal]);
56
58
  useEffect(() => {
59
+ if (!hasWalletConnect)
60
+ return;
57
61
  if (hasOpenedRef.current)
58
62
  return;
59
63
  hasOpenedRef.current = true;
60
64
  openModal();
61
- }, [openModal]);
65
+ }, [openModal, hasWalletConnect]);
66
+ if (!hasWalletConnect)
67
+ return jsx(WalletConnectNotConfigured, {});
62
68
  return (jsx(PageContent, { children: jsx(Loader, { header: error ? 'Error connecting wallet.' : `Connecting...`, icon: wallet === null || wallet === void 0 ? void 0 : wallet.icon, isError: !!error, description: error, onRetry: openModal }) }));
63
69
  };
64
70
  const ConnectWithQRCode = () => {
@@ -1 +1 @@
1
- {"version":3,"file":"ConnectWithQRCode.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"ConnectWithQRCode.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -203,14 +203,14 @@ const SolanaCreatePassword = ({ onChangeMethod, onBack, logoutOnBack, }) => {
203
203
  if (recoveryError)
204
204
  triggerResize();
205
205
  }, [recoveryError]);
206
- return (jsxs(PageContent, { onBack: onBack, logoutOnBack: logoutOnBack, children: [jsx(FloatingGraphic, { height: "80px", logoCenter: { logo: jsx(KeyIcon, {}), size: '1.2' }, logoTopLeft: { logo: jsx(LockIcon, {}), size: '0.75' }, logoBottomRight: { logo: jsx(LockIcon, {}), size: '0.5' } }), jsx(ModalHeading, { children: "Secure your wallet" }), jsxs(ModalBody, { style: { textAlign: 'center' }, children: [jsx(FitText, { children: "Set a password for your wallet." }), jsxs("form", { onSubmit: (e) => {
206
+ return (jsxs(PageContent, { onBack: onBack, logoutOnBack: logoutOnBack, children: [jsx(FloatingGraphic, { height: "80px", logoCenter: { logo: jsx(KeyIcon, {}), size: '1.2' }, logoTopLeft: { logo: jsx(LockIcon, {}), size: '0.75' }, logoBottomRight: { logo: jsx(LockIcon, {}), size: '0.5' } }), jsx(ModalHeading, { children: "Secure your wallet" }), jsxs(ModalBody, { style: { textAlign: 'center' }, children: [jsx("span", { style: { display: 'block', marginBottom: 16 }, children: "You will use this password to access your wallet, so keep it safe." }), jsxs("form", { onSubmit: (e) => {
207
207
  e.preventDefault();
208
208
  handleSubmit();
209
209
  }, children: [jsx(Input, { value: recoveryPhrase, onChange: (e) => {
210
210
  if (showPasswordIsTooWeakError)
211
211
  setShowPasswordIsTooWeakError(false);
212
212
  setRecoveryPhrase(e.target.value);
213
- }, type: "password", placeholder: "Enter your password", autoComplete: "off" }), jsx(PasswordStrengthIndicator, { password: recoveryPhrase, showPasswordIsTooWeakError: showPasswordIsTooWeakError }), jsx(TickList, { items: ['You will use this password to access your wallet', "Make sure it's strong and memorable"] }), recoveryError && (jsx(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, children: jsx(ModalBody, { style: { height: 24, marginTop: 12 }, "$error": true, children: jsx(FitText, { children: recoveryError }) }) }, recoveryError)), jsx(Button, { onClick: handleSubmit, waiting: loading, disabled: loading, children: "Create wallet" })] }), jsx(OtherMethod, { currentMethod: RecoveryMethod.PASSWORD, onChangeMethod: onChangeMethod })] })] }));
213
+ }, type: "password", placeholder: "Enter your password", autoComplete: "off" }), jsx(PasswordStrengthIndicator, { password: recoveryPhrase, showPasswordIsTooWeakError: showPasswordIsTooWeakError }), recoveryPhrase && (jsx(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, children: jsx(TickList, { items: ["Make sure it's strong and memorable", 'If you lose it, no one can recover it for you'] }) })), recoveryError && (jsx(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, children: jsx(ModalBody, { style: { height: 24, marginTop: 12 }, "$error": true, children: jsx(FitText, { children: recoveryError }) }) }, recoveryError)), jsx(Button, { onClick: handleSubmit, waiting: loading, disabled: loading, children: "Create wallet" })] }), jsx(OtherMethod, { currentMethod: RecoveryMethod.PASSWORD, onChangeMethod: onChangeMethod })] })] }));
214
214
  };
215
215
  const ChooseRecoveryMethod = ({ onChangeMethod, onBack, logoutOnBack, }) => {
216
216
  return (jsxs(PageContent, { onBack: onBack, logoutOnBack: logoutOnBack, children: [jsx(ModalHeading, { children: "Choose a recovery method" }), jsx(ProvidersButton, { children: jsxs(Button, { onClick: () => onChangeMethod(RecoveryMethod.PASSKEY), children: [jsx(ProviderLabel, { children: "Passkey" }), jsx(ProviderIcon, { children: jsx(FingerPrintIcon, {}) })] }) }), jsx(ProvidersButton, { children: jsxs(Button, { onClick: () => onChangeMethod(RecoveryMethod.PASSWORD), children: [jsx(ProviderLabel, { children: "Password" }), jsx(ProviderIcon, { children: jsx(KeyIcon, {}) })] }) }), jsx(ProvidersButton, { children: jsxs(Button, { onClick: () => onChangeMethod(RecoveryMethod.AUTOMATIC), children: [jsx(ProviderLabel, { children: "Automatic" }), jsx(ProviderIcon, { children: jsx(LockIcon, {}) })] }) })] }));
@@ -273,14 +273,14 @@ const CreateWalletPasswordRecovery = ({ onChangeMethod, onBack, logoutOnBack, })
273
273
  }, logoBottomRight: {
274
274
  logo: jsx(LockIcon, {}),
275
275
  size: '0.5',
276
- } }), jsx(ModalHeading, { children: "Secure your wallet" }), jsxs(ModalBody, { style: { textAlign: 'center' }, children: [jsx(FitText, { children: "Set a password for your wallet." }), jsxs("form", { onSubmit: (e) => {
276
+ } }), jsx(ModalHeading, { children: "Secure your wallet" }), jsxs(ModalBody, { style: { textAlign: 'center' }, children: [jsx("span", { style: { display: 'block', marginBottom: 16 }, children: "You will use this password to access your wallet, so keep it safe." }), jsxs("form", { onSubmit: (e) => {
277
277
  e.preventDefault();
278
278
  handleSubmit();
279
279
  }, children: [jsx(Input, { value: recoveryPhrase, onChange: (e) => {
280
280
  if (showPasswordIsTooWeakError)
281
281
  setShowPasswordIsTooWeakError(false);
282
282
  setRecoveryPhrase(e.target.value);
283
- }, type: "password", placeholder: "Enter your password", autoComplete: "off" }), jsx(PasswordStrengthIndicator, { password: recoveryPhrase, showPasswordIsTooWeakError: showPasswordIsTooWeakError }), jsx(TickList, { items: ['You will use this password to access your wallet', "Make sure it's strong and memorable"] }), recoveryError && (jsx(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, children: jsx(ModalBody, { style: { height: 24, marginTop: 12 }, "$error": true, children: jsx(FitText, { children: recoveryError }) }) }, recoveryError)), jsx(Button, { onClick: handleSubmit, waiting: loading, disabled: loading, children: "Create wallet" })] }), jsx(OtherMethod, { currentMethod: RecoveryMethod.PASSWORD, onChangeMethod: onChangeMethod })] })] }));
283
+ }, type: "password", placeholder: "Enter your password", autoComplete: "off" }), jsx(PasswordStrengthIndicator, { password: recoveryPhrase, showPasswordIsTooWeakError: showPasswordIsTooWeakError }), recoveryPhrase && (jsx(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, children: jsx(TickList, { items: ["Make sure it's strong and memorable", 'If you lose it, no one can recover it for you'] }) })), recoveryError && (jsx(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, children: jsx(ModalBody, { style: { height: 24, marginTop: 12 }, "$error": true, children: jsx(FitText, { children: recoveryError }) }) }, recoveryError)), jsx(Button, { onClick: handleSubmit, waiting: loading, disabled: loading, children: "Create wallet" })] }), jsx(OtherMethod, { currentMethod: RecoveryMethod.PASSWORD, onChangeMethod: onChangeMethod })] })] }));
284
284
  };
285
285
  const ChooseRecoveryMethod = ({ onChangeMethod, onBack, logoutOnBack, }) => {
286
286
  return (jsxs(PageContent, { onBack: onBack, logoutOnBack: logoutOnBack, children: [jsx(ModalHeading, { children: "Choose a recovery method" }), jsx(ProvidersButton, { children: jsxs(Button, { onClick: () => onChangeMethod(RecoveryMethod.PASSKEY), children: [jsx(ProviderLabel, { children: "Passkey" }), jsx(ProviderIcon, { children: jsx(FingerPrintIcon, {}) })] }) }), jsx(ProvidersButton, { children: jsxs(Button, { onClick: () => onChangeMethod(RecoveryMethod.PASSWORD), children: [jsx(ProviderLabel, { children: "Password" }), jsx(ProviderIcon, { children: jsx(KeyIcon, {}) })] }) }), jsx(ProvidersButton, { children: jsxs(Button, { onClick: () => onChangeMethod(RecoveryMethod.AUTOMATIC), children: [jsx(ProviderLabel, { children: "Automatic" }), jsx(ProviderIcon, { children: jsx(LockIcon, {}) })] }) })] }));
@@ -3,10 +3,12 @@ import { ChainTypeEnum, RecoveryMethod } from '@openfort/openfort-js';
3
3
  import { useState, useEffect } from 'react';
4
4
  import { useEthereumEmbeddedWallet } from '../../../ethereum/hooks/useEthereumEmbeddedWallet.js';
5
5
  import { toSolanaUserWallet } from '../../../hooks/openfort/walletTypes.js';
6
+ import { useTimedOut } from '../../../hooks/useTimedOut.js';
6
7
  import { useOpenfortCore } from '../../../openfort/useOpenfort.js';
7
8
  import { useSolanaEmbeddedWallet } from '../../../solana/hooks/useSolanaEmbeddedWallet.js';
8
9
  import { logger } from '../../../utils/logger.js';
9
10
  import Loader from '../../Common/Loading/index.js';
11
+ import NotFoundFallback from '../../Common/NotFoundFallback/index.js';
10
12
  import { createRoute, recoverRoute } from '../../Openfort/routeHelpers.js';
11
13
  import { routes } from '../../Openfort/types.js';
12
14
  import { useOpenfort } from '../../Openfort/useOpenfort.js';
@@ -32,6 +34,9 @@ const errorForChainRegistry = {
32
34
  message: (errorWallets === null || errorWallets === void 0 ? void 0 : errorWallets.message) || 'There was an error loading wallets',
33
35
  }),
34
36
  };
37
+ // Watchdog: if wallet loading never settles (hung fetch, unreachable state),
38
+ // bail out to the not-found fallback instead of spinning forever.
39
+ const LOADING_TIMEOUT_MS = 10000;
35
40
  const LoadWallets = () => {
36
41
  var _a;
37
42
  const { chainType, user, isLoadingAccounts } = useOpenfortCore();
@@ -41,6 +46,7 @@ const LoadWallets = () => {
41
46
  const embeddedWallet = chainType === ChainTypeEnum.EVM ? ethereumWallet : solanaWallet;
42
47
  const connectOnLogin = (_a = walletConfig === null || walletConfig === void 0 ? void 0 : walletConfig.connectOnLogin) !== null && _a !== void 0 ? _a : true;
43
48
  const [loadingUX, setLoadingUX] = useState(true);
49
+ const timedOut = useTimedOut(LOADING_TIMEOUT_MS);
44
50
  const wallets = embeddedWallet.wallets;
45
51
  const isLoadingWallets = embeddedWallet.status === 'fetching-wallets' ||
46
52
  embeddedWallet.status === 'connecting' ||
@@ -98,6 +104,9 @@ const LoadWallets = () => {
98
104
  const { isError: isErrorFromChain, message: errorMessageFromChain } = errorForChainRegistry[chainType](errorWallets);
99
105
  const isError = !user || isErrorFromChain;
100
106
  const errorMessage = !user ? undefined : errorMessageFromChain;
107
+ // Only fall back while still spinning — real errors keep their own message
108
+ if (timedOut && !isError)
109
+ return jsx(NotFoundFallback, {});
101
110
  return (jsx(PageContent, { onBack: !user ? 'back' : null, children: jsx(Loader, { header: "Setting up wallet", isError: isError, description: isError ? errorMessage : 'Setting up wallets' }) }));
102
111
  };
103
112
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -3,13 +3,18 @@ import { ChainTypeEnum, EmbeddedState } from '@openfort/openfort-js';
3
3
  import React, { useEffect } from 'react';
4
4
  import { useEthereumEmbeddedWallet } from '../../../ethereum/hooks/useEthereumEmbeddedWallet.js';
5
5
  import { useEthereumBridge } from '../../../ethereum/OpenfortEthereumBridgeContext.js';
6
+ import { useTimedOut } from '../../../hooks/useTimedOut.js';
6
7
  import { useOpenfortCore } from '../../../openfort/useOpenfort.js';
7
8
  import { useSolanaEmbeddedWallet } from '../../../solana/hooks/useSolanaEmbeddedWallet.js';
8
9
  import Loader from '../../Common/Loading/index.js';
10
+ import NotFoundFallback from '../../Common/NotFoundFallback/index.js';
9
11
  import { routes } from '../../Openfort/types.js';
10
12
  import { useOpenfort } from '../../Openfort/useOpenfort.js';
11
13
  import { PageContent } from '../../PageContent/index.js';
12
14
 
15
+ // Watchdog: if no state transition routes us away within this window, the modal
16
+ // would otherwise spin forever (e.g. opened while signed out, or a misconfigured SDK).
17
+ const LOADING_TIMEOUT_MS = 10000;
13
18
  const Loading = () => {
14
19
  const { setRoute, walletConfig } = useOpenfort();
15
20
  const { user, isLoadingAccounts, isLoading, needsRecovery, embeddedState } = useOpenfortCore();
@@ -25,6 +30,7 @@ const Loading = () => {
25
30
  const address = embeddedConnected ? wallet.address : bridgeConnected ? bridge === null || bridge === void 0 ? void 0 : bridge.account.address : undefined;
26
31
  const [isFirstFrame, setIsFirstFrame] = React.useState(true);
27
32
  const [retryCount, setRetryCount] = React.useState(0);
33
+ const timedOut = useTimedOut(LOADING_TIMEOUT_MS);
28
34
  useEffect(() => {
29
35
  if (isFirstFrame)
30
36
  return;
@@ -65,6 +71,8 @@ const Loading = () => {
65
71
  // UX: Wait a bit before showing the next page
66
72
  setTimeout(() => setIsFirstFrame(false), 400);
67
73
  }, []);
74
+ if (timedOut)
75
+ return jsx(NotFoundFallback, {});
68
76
  return (jsx(PageContent, { children: jsx(Loader, { header: "Redirecting" }) }));
69
77
  };
70
78
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -7,6 +7,7 @@ import { CopyButton } from '../../Common/CopyToClipboard/CopyButton.js';
7
7
  import { ModalContent } from '../../Common/Modal/styles.js';
8
8
  import { ScrollArea } from '../../Common/ScrollArea/index.js';
9
9
  import { Spinner } from '../../Common/Spinner/index.js';
10
+ import WalletConnectNotConfigured, { useHasWalletConnect } from '../../Common/WalletConnectNotConfigured/index.js';
10
11
  import { routes } from '../../Openfort/types.js';
11
12
  import { useOpenfort } from '../../Openfort/useOpenfort.js';
12
13
  import { PageContent } from '../../PageContent/index.js';
@@ -19,6 +20,7 @@ const MobileConnectors = () => {
19
20
  const locales = useLocales();
20
21
  const { open: openW3M, isOpen: isOpenW3M } = useWalletConnectModal();
21
22
  const wallets = useExternalConnectors();
23
+ const hasWalletConnect = useHasWalletConnect();
22
24
  // filter out installed wallets
23
25
  const walletsIdsToDisplay = (_a = Object.keys(walletConfigs).filter((walletId) => {
24
26
  const wallet = walletConfigs[walletId];
@@ -32,6 +34,9 @@ const MobileConnectors = () => {
32
34
  context.setRoute(routes.CONNECT_WITH_MOBILE);
33
35
  context.setConnector({ id: walletId });
34
36
  };
37
+ // Every wallet on this page connects through WalletConnect deeplinks
38
+ if (!hasWalletConnect)
39
+ return jsx(WalletConnectNotConfigured, {});
35
40
  return (jsx(PageContent, { width: 312, onBack: routes.PROVIDERS, children: jsxs(Container, { children: [jsx(ModalContent, { style: { paddingBottom: 0 }, children: jsx(ScrollArea, { height: 340, children: jsxs(WalletList, { children: [walletsIdsToDisplay
36
41
  .sort(
37
42
  // sort by name
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -256,7 +256,10 @@ const SendConfirmation = () => {
256
256
  }
257
257
  }, [isPollingBalance, currentBalance]);
258
258
  const handleConfirm = async () => {
259
- if (submittingRef.current)
259
+ // Block re-entry while submitting, and never submit when a tx already
260
+ // exists for this send — a second `eth_sendTransaction` is the duplicate
261
+ // transaction the customer hit after the wallet was slow to respond.
262
+ if (submittingRef.current || transactionHash)
260
263
  return;
261
264
  if (!recipientAddress || !parsedAmount || parsedAmount <= BigInt(0) || insufficientBalance)
262
265
  return;
@@ -279,8 +282,10 @@ const SendConfirmation = () => {
279
282
  });
280
283
  }
281
284
  }
282
- catch (_error) {
283
- // Errors are surfaced through mutation hooks
285
+ catch {
286
+ // The error is already recorded in nativeError/erc20Error (which drive
287
+ // firstError and the error UI) before being re-thrown — we only catch
288
+ // here to stop it becoming an unhandled rejection.
284
289
  }
285
290
  finally {
286
291
  submittingRef.current = false;
@@ -331,7 +336,14 @@ const SendConfirmation = () => {
331
336
  width: '100%',
332
337
  color: 'var(--ck-body-color-valid)',
333
338
  fontSize: '12px',
334
- }, children: "Sponsored transaction" }))] })] }), insufficientBalance && !isSuccess && (jsx(StatusMessage, { "$status": "error", children: "Insufficient balance for this transfer." })), errorDetails && (jsxs(ErrorContainer, { children: [jsx(ErrorTitle, { children: errorDetails.title }), jsx(ErrorMessage, { children: errorDetails.message }), errorDetails.action && jsx(ErrorAction, { children: errorDetails.action })] })), jsxs(ButtonRow, { children: [jsx(Button, { variant: "primary", onClick: isSuccess ? handleOpenBlockExplorer : handleConfirm, disabled: isSuccess ? false : !recipientAddress || !parsedAmount || parsedAmount <= BigInt(0) || insufficientBalance, waiting: isLoading, icon: isSuccess ? jsx(TickIcon, { style: { width: 18, height: 18 } }) : undefined, children: isSuccess ? 'Confirmed' : isLoading ? 'Confirming...' : 'Confirm' }), isSuccess ? (jsx(Button, { variant: "secondary", onClick: handleFinish, children: "Back to profile" })) : (jsx(Button, { variant: "secondary", onClick: handleCancel, disabled: isLoading, children: "Cancel" }))] })] }));
339
+ }, children: "Sponsored transaction" }))] })] }), insufficientBalance && !isSuccess && (jsx(StatusMessage, { "$status": "error", children: "Insufficient balance for this transfer." })), errorDetails && (jsxs(ErrorContainer, { children: [jsx(ErrorTitle, { children: errorDetails.title }), jsx(ErrorMessage, { children: errorDetails.message }), errorDetails.action && jsx(ErrorAction, { children: errorDetails.action })] })), jsxs(ButtonRow, { children: [jsx(Button, { variant: "primary", onClick: isSuccess ? handleOpenBlockExplorer : handleConfirm, disabled: isSuccess
340
+ ? false
341
+ : isLoading ||
342
+ Boolean(transactionHash) ||
343
+ !recipientAddress ||
344
+ !parsedAmount ||
345
+ parsedAmount <= BigInt(0) ||
346
+ insufficientBalance, waiting: isLoading, icon: isSuccess ? jsx(TickIcon, { style: { width: 18, height: 18 } }) : undefined, children: isSuccess ? 'Confirmed' : isLoading ? 'Confirming...' : 'Confirm' }), isSuccess ? (jsx(Button, { variant: "secondary", onClick: handleFinish, children: "Back to profile" })) : (jsx(Button, { variant: "secondary", onClick: handleCancel, disabled: isLoading, children: "Cancel" }))] })] }));
335
347
  };
336
348
 
337
349
  export { SendConfirmation as default };
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,4 +1,4 @@
1
1
  export declare const PasswordStrengthIndicator: ({ password, showPasswordIsTooWeakError, }: {
2
2
  password: string;
3
3
  showPasswordIsTooWeakError: boolean;
4
- }) => import("react/jsx-runtime").JSX.Element;
4
+ }) => import("react/jsx-runtime").JSX.Element | null;
@@ -1,7 +1,8 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { motion } from 'framer-motion';
3
- import { useMemo } from 'react';
3
+ import { useMemo, useEffect } from 'react';
4
4
  import styled from '../../styles/styled/index.js';
5
+ import { useOpenfort } from '../Openfort/useOpenfort.js';
5
6
  import { getPasswordStrength, getPasswordStrengthLabel } from './password-utility.js';
6
7
 
7
8
  const Container = styled.div `
@@ -49,6 +50,14 @@ const PasswordStrengthIndicator = ({ password, showPasswordIsTooWeakError, }) =>
49
50
  return '#d1d5db'; // gray-300
50
51
  }
51
52
  }, [label]);
53
+ const { triggerResize } = useOpenfort();
54
+ // Grow/shrink the modal as the meter appears/disappears so it isn't clipped.
55
+ useEffect(() => {
56
+ triggerResize();
57
+ }, [!!password, showPasswordIsTooWeakError, triggerResize]);
58
+ // Only surface strength once the user starts typing (or on the too-weak error).
59
+ if (!password && !showPasswordIsTooWeakError)
60
+ return null;
52
61
  return (jsxs(Container, { children: [jsx(BarWrapper, { children: jsx(Progress, { color: color, initial: { width: 0 }, animate: { width: `${passwordStrength * 100}%` }, transition: { ease: 'easeOut', duration: 0.5 } }) }), jsxs("div", { style: { position: 'relative' }, children: [jsx(motion.div, { initial: { opacity: 1 }, animate: {
53
62
  opacity: showPasswordIsTooWeakError ? 0 : 1,
54
63
  y: showPasswordIsTooWeakError ? 5 : 0,
@@ -1 +1 @@
1
- {"version":3,"file":"PasswordStrengthIndicator.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"PasswordStrengthIndicator.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -22,7 +22,12 @@ const safeRoutes = {
22
22
  routes.PROVIDERS,
23
23
  ],
24
24
  };
25
- const allRoutes = [...safeRoutes.connected, ...safeRoutes.disconnected];
25
+ /** Route can be selected by string (route name) or by object with `route` property */
26
+ function routeMatches(a, b) {
27
+ const aRoute = typeof a === 'object' && a !== null && 'route' in a ? a.route : a;
28
+ const bRoute = typeof b === 'object' && b !== null && 'route' in b ? b.route : b;
29
+ return aRoute === bRoute;
30
+ }
26
31
  /** Connector id must be a connector (e.g. injected, walletConnect), not an Openfort account id. */
27
32
  function isAccountId(id) {
28
33
  return id.startsWith('acc_');
@@ -84,27 +89,18 @@ function useUI() {
84
89
  setRoute(routes.CONNECTED);
85
90
  }
86
91
  const gotoAndOpen = (route) => {
87
- let validRoute = route;
88
- if (!allRoutes.includes(route)) {
89
- validRoute = isConnected ? routes.CONNECTED : routes.PROVIDERS;
90
- logger.log(`Route ${route} is not a valid route, navigating to ${validRoute} instead.`);
91
- }
92
- else {
93
- if (isConnected) {
94
- if (!safeRoutes.connected.includes(route)) {
95
- validRoute = routes.CONNECTED;
96
- logger.log(`Route ${route} is not a valid route when connected, navigating to ${validRoute} instead.`);
97
- }
98
- }
99
- else {
100
- if (!safeRoutes.disconnected.includes(route)) {
101
- validRoute = routes.PROVIDERS;
102
- logger.log(`Route ${route} is not a valid route when disconnected, navigating to ${validRoute} instead.`);
103
- }
104
- }
92
+ const safeList = isConnected ? safeRoutes.connected : safeRoutes.disconnected;
93
+ const fallback = isConnected ? routes.CONNECTED : routes.PROVIDERS;
94
+ // Navigate using the allowlisted spec so vetted options (e.g. connectType) are enforced,
95
+ // not whatever the caller passed alongside a matching route name.
96
+ const match = safeList.find((r) => routeMatches(r, route));
97
+ if (!match) {
98
+ logger.log(`Route ${JSON.stringify(route)} is not valid when ${isConnected ? 'connected' : 'disconnected'}, navigating to ${fallback} instead.`);
105
99
  }
106
- setRoute(validRoute);
100
+ // setOpen(true) resets route/history/connector for a clean session, so it MUST run
101
+ // before setRoute — otherwise it clobbers the requested route back to LOADING.
107
102
  setOpen(true);
103
+ setRoute(match !== null && match !== void 0 ? match : fallback);
108
104
  };
109
105
  return {
110
106
  isOpen: open,
@@ -1 +1 @@
1
- {"version":3,"file":"useUI.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"useUI.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,2 @@
1
+ /** Returns true once `ms` milliseconds have elapsed since mount. */
2
+ export declare function useTimedOut(ms: number): boolean;
@@ -0,0 +1,14 @@
1
+ import { useState, useEffect } from 'react';
2
+
3
+ /** Returns true once `ms` milliseconds have elapsed since mount. */
4
+ function useTimedOut(ms) {
5
+ const [timedOut, setTimedOut] = useState(false);
6
+ useEffect(() => {
7
+ const timeout = setTimeout(() => setTimedOut(true), ms);
8
+ return () => clearTimeout(timeout);
9
+ }, [ms]);
10
+ return timedOut;
11
+ }
12
+
13
+ export { useTimedOut };
14
+ //# sourceMappingURL=useTimedOut.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTimedOut.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;"}
@@ -1 +1 @@
1
- export declare const OPENFORT_VERSION = "1.1.1";
1
+ export declare const OPENFORT_VERSION = "1.1.3";
package/build/version.js CHANGED
@@ -1,4 +1,4 @@
1
- const OPENFORT_VERSION = '1.1.1';
1
+ const OPENFORT_VERSION = '1.1.3';
2
2
 
3
3
  export { OPENFORT_VERSION };
4
4
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfort/react",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "author": "Openfort (https://www.openfort.io)",
5
5
  "license": "BSD-2-Clause license",
6
6
  "description": "The easiest way to integrate Openfort to your project.",
@@ -65,7 +65,7 @@
65
65
  "react"
66
66
  ],
67
67
  "dependencies": {
68
- "@openfort/openfort-js": "^1.3.5",
68
+ "@openfort/openfort-js": "^1.3.6",
69
69
  "buffer": "^6.0.3",
70
70
  "detect-browser": "^5.3.0",
71
71
  "fast-password-entropy": "^1.1.1",