From a40095a75d8d5b9bdfaf981cfe7ef7816cab0430 Mon Sep 17 00:00:00 2001 From: Tudor Morar Date: Thu, 20 Jun 2024 18:14:02 +0300 Subject: [PATCH] Sign message (#24) * Sign message working * Sign message layout * Layout * Sign message hook working * Sign message hook working * Added sign message tests and fixed bugs * Updated constants.ts * Updated jest-puppeteer.config.js --------- Co-authored-by: razvan.tomegea --- jest-puppeteer.config.js | 8 +- src/__mocks__/data/constants.ts | 4 +- .../PostMessageListener.tsx | 26 +++ .../useRedirectPathname.ts | 2 + src/lib/sdkJsWebWalletIo.ts | 2 + src/localConstants/dataTestIds.enum.ts | 5 +- src/localConstants/routes/routeNames.enums.ts | 13 +- src/localConstants/sdkDapp.ts | 1 + .../Dashboard/widgets/Account/Account.tsx | 7 + .../HookValidationOutcome.tsx | 8 +- src/pages/Hook/LoginHook/LoginHook.tsx | 6 +- src/pages/Hook/LogoutHook/LogoutHook.tsx | 4 +- src/pages/Hook/SignHook/SignHook.tsx | 4 +- .../SignHook/tests/InvalidSignHook.e2e.ts | 4 +- .../Hook/SignMessageHook/SignMessageHook.tsx | 47 ++++++ src/pages/Hook/SignMessageHook/index.tsx | 1 + .../tests/CancelSignMessage.e2e.ts | 26 +++ .../tests/InvalidSignMessage.e2e.ts | 28 ++++ .../SignMessageHook/tests/SignMessage.e2e.ts | 26 +++ src/pages/Hook/index.tsx | 1 + src/pages/Send/Send.tsx | 2 +- src/pages/SignMessage/SignMessage.tsx | 155 ++++++++++++++++++ .../SignMessage/components/SignFailure.tsx | 14 ++ .../SignMessage/components/SignSuccess.tsx | 56 +++++++ src/pages/SignMessage/components/index.ts | 2 + .../SignMessage/helpers/decodeMessage.ts | 32 ++++ src/pages/SignMessage/helpers/index.ts | 1 + .../helpers/tests/decodeMessage.test.ts | 22 +++ src/pages/SignMessage/hooks/index.ts | 1 + .../hooks/useSignMessageCompleted.ts | 52 ++++++ src/pages/SignMessage/index.tsx | 1 + src/pages/Unlock/Unlock.tsx | 2 +- src/pages/index.tsx | 1 + src/routes/routes.tsx | 30 +++- 34 files changed, 562 insertions(+), 32 deletions(-) create mode 100644 src/pages/Hook/SignMessageHook/SignMessageHook.tsx create mode 100644 src/pages/Hook/SignMessageHook/index.tsx create mode 100644 src/pages/Hook/SignMessageHook/tests/CancelSignMessage.e2e.ts create mode 100644 src/pages/Hook/SignMessageHook/tests/InvalidSignMessage.e2e.ts create mode 100644 src/pages/Hook/SignMessageHook/tests/SignMessage.e2e.ts create mode 100644 src/pages/SignMessage/SignMessage.tsx create mode 100644 src/pages/SignMessage/components/SignFailure.tsx create mode 100644 src/pages/SignMessage/components/SignSuccess.tsx create mode 100644 src/pages/SignMessage/components/index.ts create mode 100644 src/pages/SignMessage/helpers/decodeMessage.ts create mode 100644 src/pages/SignMessage/helpers/index.ts create mode 100644 src/pages/SignMessage/helpers/tests/decodeMessage.test.ts create mode 100644 src/pages/SignMessage/hooks/index.ts create mode 100644 src/pages/SignMessage/hooks/useSignMessageCompleted.ts create mode 100644 src/pages/SignMessage/index.tsx diff --git a/jest-puppeteer.config.js b/jest-puppeteer.config.js index 79a13887..b3774a1b 100644 --- a/jest-puppeteer.config.js +++ b/jest-puppeteer.config.js @@ -50,8 +50,8 @@ const config = { roots: ['/src'], modulePaths: ['/src'], bail: 1, - // workerIdleMemoryLimit: '512MB', // Memory used per worker. Required to prevent memory leaks - // maxWorkers: '50%', // Maximum tests ran in parallel. Required to prevent CPU usage at 100% + workerIdleMemoryLimit: '512MB', // Memory used per worker. Required to prevent memory leaks + maxWorkers: '65%', // Maximum tests ran in parallel. Required to prevent CPU usage at 100% launch: { headless: isHeadless, product: 'chrome', @@ -67,9 +67,7 @@ const config = { server: { command: 'vite preview' }, - browserContext: 'incognito', - browserPerWorker: true, - runBeforeUnload: true + browserContext: 'incognito' }; if (!isHeadless) { diff --git a/src/__mocks__/data/constants.ts b/src/__mocks__/data/constants.ts index 12b330ec..4c8c5152 100644 --- a/src/__mocks__/data/constants.ts +++ b/src/__mocks__/data/constants.ts @@ -1,6 +1,6 @@ export const WALLET_TARGET_ORIGIN = 'http://localhost'; export const WALLET_SOURCE_ORIGIN = 'https://localhost:3002'; export const MAIN_REFERRER = 'https://localhost:3001/'; -export const DEFAULT_PAGE_LOAD_DELAY_MS = 2500; -export const DEFAULT_DELAY_MS = 250; +export const DEFAULT_PAGE_LOAD_DELAY_MS = 1000; +export const DEFAULT_DELAY_MS = 100; export const DEFAULT_PASSWORD = 'P@ssw0rd123'; diff --git a/src/components/Utilities/components/PostMessageListener/PostMessageListener.tsx b/src/components/Utilities/components/PostMessageListener/PostMessageListener.tsx index f35f3c86..4fe6370b 100644 --- a/src/components/Utilities/components/PostMessageListener/PostMessageListener.tsx +++ b/src/components/Utilities/components/PostMessageListener/PostMessageListener.tsx @@ -10,6 +10,7 @@ import { import { getLoginHookData, getSignHookData, + getSignMessageHookData, Transaction, useGetLoginInfo } from 'lib'; @@ -130,6 +131,31 @@ export const PostMessageListener = () => { break; } + case CrossWindowProviderRequestEnums.signMessageRequest: { + const payloadString = buildWalletQueryString({ + params: { ...payload, callbackUrl } + }); + + const serializedPayload = `?${payloadString}`; + + const data = getSignMessageHookData(serializedPayload); + + if (data == null) { + return; + } + + dispatch( + setHook({ + type: HooksEnum.signMessage, + hookUrl: data.hookUrl, + callbackUrl + }) + ); + + navigate(routeNames.signMessage); + break; + } + case CrossWindowProviderRequestEnums.finalizeHandshakeRequest: { handshakeEstablished = true; break; diff --git a/src/hooks/useRedirectPathname/useRedirectPathname.ts b/src/hooks/useRedirectPathname/useRedirectPathname.ts index 8506d052..79dedc9c 100644 --- a/src/hooks/useRedirectPathname/useRedirectPathname.ts +++ b/src/hooks/useRedirectPathname/useRedirectPathname.ts @@ -20,6 +20,8 @@ export const useRedirectPathname = () => { switch (hook) { case HooksEnum.sign: return routeNames.sign; + case HooksEnum.signMessage: + return routeNames.signMessage; case HooksEnum.login: { if (!isLoggedIn) { return routeNames.unlock; diff --git a/src/lib/sdkJsWebWalletIo.ts b/src/lib/sdkJsWebWalletIo.ts index 94b4a487..89baf0ba 100644 --- a/src/lib/sdkJsWebWalletIo.ts +++ b/src/lib/sdkJsWebWalletIo.ts @@ -1,7 +1,9 @@ export { getSignHookData } from '@multiversx/sdk-js-web-wallet-io/out/url/signHook/getSignHookData'; export { getLoginHookData } from '@multiversx/sdk-js-web-wallet-io/out/hooks/loginHook/getLoginHookData'; +export { getSignMessageHookData } from '@multiversx/sdk-js-web-wallet-io/out/hooks/signMessageHook/getSignMessageHookData'; export { processBase64Fields } from '@multiversx/sdk-js-web-wallet-io/out/helpers'; export { replyToDapp } from '@multiversx/sdk-js-web-wallet-io/out/replyToDapp/replyToDapp'; export { signTxSchema } from '@multiversx/sdk-js-web-wallet-io/out/url/helpers/sign'; export { parseSignUrl } from '@multiversx/sdk-js-web-wallet-io/out/hooks/helpers/sign'; export { getLogoutHookData } from '@multiversx/sdk-js-web-wallet-io/out/hooks/logoutHook/getLogoutHookData'; +export { parseQueryParams } from '@multiversx/sdk-js-web-wallet-io/out/helpers/navigation/parseQueryParams'; diff --git a/src/localConstants/dataTestIds.enum.ts b/src/localConstants/dataTestIds.enum.ts index 907d36d0..13791554 100644 --- a/src/localConstants/dataTestIds.enum.ts +++ b/src/localConstants/dataTestIds.enum.ts @@ -20,6 +20,8 @@ export enum DataTestIdsEnum { receiverError = 'receiverError', receiverInput = 'receiverInput', sendBtn = 'sendBtn', + signMessageBtn = 'signMessageBtn', + cancelSignMessageBtn = 'cancelSignMessageBtn', sendEsdtTypeInput = 'sendEsdtTypeInput', sendNFtTypeInput = 'sendNFtTypeInput', signBtn = 'signBtn', @@ -29,5 +31,6 @@ export enum DataTestIdsEnum { transactionToastTitle = 'transactionToastTitle', unlockPage = 'unlockPage', userAddress = 'userAddress', - walletFile = 'walletFile' + walletFile = 'walletFile', + signMessagePage = 'signMessagePage' } diff --git a/src/localConstants/routes/routeNames.enums.ts b/src/localConstants/routes/routeNames.enums.ts index 454dd726..4144ee0e 100644 --- a/src/localConstants/routes/routeNames.enums.ts +++ b/src/localConstants/routes/routeNames.enums.ts @@ -1,7 +1,8 @@ export enum HooksEnum { login = 'login', logout = 'logout', - sign = 'sign' + sign = 'sign', + signMessage = 'sign-message' } export enum RouteNamesEnum { @@ -11,11 +12,13 @@ export enum RouteNamesEnum { disclaimer = '/disclaimer', send = '/send', logout = '/logout', - sign = '/sign' + sign = '/sign', + signMessage = '/sign-message' } export enum HooksPageEnum { - signHook = `/hook/${HooksEnum.sign}`, - loginHook = `/hook/${HooksEnum.login}`, - logoutHook = `/hook/${HooksEnum.logout}` + sign = `/hook/${HooksEnum.sign}`, + login = `/hook/${HooksEnum.login}`, + logout = `/hook/${HooksEnum.logout}`, + signMessage = `/hook/${HooksEnum.signMessage}` } diff --git a/src/localConstants/sdkDapp.ts b/src/localConstants/sdkDapp.ts index c385b2b8..5bb68aaa 100644 --- a/src/localConstants/sdkDapp.ts +++ b/src/localConstants/sdkDapp.ts @@ -12,4 +12,5 @@ export { TOKENS_ENDPOINT, NFTS_ENDPOINT } from '@multiversx/sdk-dapp/apiCalls/endpoints'; +export { CANCELLED } from '@multiversx/sdk-dapp/constants/errorsMessages'; export { TransactionBatchStatusesEnum } from '@multiversx/sdk-dapp/types/enums.types'; diff --git a/src/pages/Dashboard/widgets/Account/Account.tsx b/src/pages/Dashboard/widgets/Account/Account.tsx index 059f2c4d..3afbad3e 100644 --- a/src/pages/Dashboard/widgets/Account/Account.tsx +++ b/src/pages/Dashboard/widgets/Account/Account.tsx @@ -68,6 +68,13 @@ export const Account = () => { > Send + + Sign Message +
diff --git a/src/pages/Hook/HookValidationOutcome/HookValidationOutcome.tsx b/src/pages/Hook/HookValidationOutcome/HookValidationOutcome.tsx index ef095a68..7a599d55 100644 --- a/src/pages/Hook/HookValidationOutcome/HookValidationOutcome.tsx +++ b/src/pages/Hook/HookValidationOutcome/HookValidationOutcome.tsx @@ -47,7 +47,9 @@ export const HookValidationOutcome = ({ if (isInvalid) { // login hook is invalid, meaning user cannot see dashboard - if ([HooksEnum.login, HooksEnum.sign].includes(hook)) { + if ( + [HooksEnum.login, HooksEnum.sign, HooksEnum.signMessage].includes(hook) + ) { return ; } @@ -59,7 +61,9 @@ export const HookValidationOutcome = ({ if ( isValid && registeredHook && - [HooksEnum.sign, HooksEnum.login].includes(registeredHook) + [HooksEnum.login, HooksEnum.sign, HooksEnum.signMessage].includes( + registeredHook + ) ) { switch (loginMethod) { case LoginMethodsEnum.none: { diff --git a/src/pages/Hook/LoginHook/LoginHook.tsx b/src/pages/Hook/LoginHook/LoginHook.tsx index a92ee615..9764b1ea 100644 --- a/src/pages/Hook/LoginHook/LoginHook.tsx +++ b/src/pages/Hook/LoginHook/LoginHook.tsx @@ -3,7 +3,7 @@ import { useDispatch } from 'react-redux'; import { useLocation } from 'react-router-dom'; import { useLogout } from 'hooks'; import { getLoginHookData, useGetAccount } from 'lib'; -import { HooksEnum } from 'localConstants'; +import { HooksEnum, HooksPageEnum } from 'localConstants'; import { setHook } from 'redux/slices'; import { HookValidationOutcome } from '../HookValidationOutcome'; import { HookStateEnum } from '../types'; @@ -15,7 +15,9 @@ export const LoginHook = () => { const logout = useLogout(); const data = useMemo(() => { - return pathname.includes(HooksEnum.login) ? getLoginHookData(search) : null; + return pathname.includes(HooksPageEnum.login) + ? getLoginHookData(search) + : null; }, [pathname]); const [validUrl, setValidUrl] = useState( diff --git a/src/pages/Hook/LogoutHook/LogoutHook.tsx b/src/pages/Hook/LogoutHook/LogoutHook.tsx index 4e4b31a7..a790830e 100644 --- a/src/pages/Hook/LogoutHook/LogoutHook.tsx +++ b/src/pages/Hook/LogoutHook/LogoutHook.tsx @@ -3,7 +3,7 @@ import { useDispatch } from 'react-redux'; import { useLocation, useNavigate } from 'react-router-dom'; import { useLogout } from 'hooks'; import { getLogoutHookData, replyToDapp } from 'lib'; -import { HooksEnum } from 'localConstants'; +import { HooksEnum, HooksPageEnum } from 'localConstants'; import { setHook } from 'redux/slices'; import { routeNames } from 'routes'; import { CrossWindowProviderResponseEnums } from 'types'; @@ -18,7 +18,7 @@ export const LogoutHook = () => { const logout = useLogout(); const data = useMemo(() => { - return pathname.includes(HooksEnum.logout) + return pathname.includes(HooksPageEnum.logout) ? getLogoutHookData(search) : null; }, [pathname]); diff --git a/src/pages/Hook/SignHook/SignHook.tsx b/src/pages/Hook/SignHook/SignHook.tsx index 3cdabf09..2983c523 100644 --- a/src/pages/Hook/SignHook/SignHook.tsx +++ b/src/pages/Hook/SignHook/SignHook.tsx @@ -3,7 +3,7 @@ import { useDispatch } from 'react-redux'; import { useLocation } from 'react-router-dom'; import { useSignTxSchema } from 'hooks/useSignTxSchema'; import { getSignHookData } from 'lib'; -import { HooksEnum } from 'localConstants'; +import { HooksEnum, HooksPageEnum } from 'localConstants'; import { setHook } from 'redux/slices'; import { HookValidationOutcome } from '../HookValidationOutcome'; import { HookStateEnum } from '../types'; @@ -15,7 +15,7 @@ export const SignHook = () => { const { pathname, search } = useLocation(); const data = useMemo(() => { - return pathname.includes(HooksEnum.sign) ? getData(search) : null; + return pathname.includes(HooksPageEnum.sign) ? getData(search) : null; }, [pathname]); const [validUrl, setValidUrl] = useState( diff --git a/src/pages/Hook/SignHook/tests/InvalidSignHook.e2e.ts b/src/pages/Hook/SignHook/tests/InvalidSignHook.e2e.ts index 20d997db..8df9eecc 100644 --- a/src/pages/Hook/SignHook/tests/InvalidSignHook.e2e.ts +++ b/src/pages/Hook/SignHook/tests/InvalidSignHook.e2e.ts @@ -1,7 +1,7 @@ import { WALLET_SOURCE_ORIGIN, keystoreAccount, pemAccount } from '__mocks__'; -import { pingSC } from './data/pingSC'; -import { expectElementToContainText } from 'utils/testUtils/puppeteer'; import { DataTestIdsEnum } from 'localConstants/dataTestIds.enum'; +import { expectElementToContainText } from 'utils/testUtils/puppeteer'; +import { pingSC } from './data/pingSC'; describe('Invalid sign hook tests', () => { it('should navigate to /unlock page without signing when callbackUrl is missing', async () => { diff --git a/src/pages/Hook/SignMessageHook/SignMessageHook.tsx b/src/pages/Hook/SignMessageHook/SignMessageHook.tsx new file mode 100644 index 00000000..78670243 --- /dev/null +++ b/src/pages/Hook/SignMessageHook/SignMessageHook.tsx @@ -0,0 +1,47 @@ +import { useEffect, useMemo, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { useLocation } from 'react-router-dom'; +import { getSignMessageHookData } from 'lib'; +import { HooksEnum, HooksPageEnum } from 'localConstants'; +import { setHook } from 'redux/slices'; +import { HookValidationOutcome } from '../HookValidationOutcome'; +import { HookStateEnum } from '../types'; + +export const SignMessageHook = () => { + const dispatch = useDispatch(); + const { pathname, search } = useLocation(); + + const data = useMemo(() => { + return pathname.includes(HooksPageEnum.signMessage) + ? getSignMessageHookData(search) + : null; + }, [pathname]); + + const [validUrl, setValidUrl] = useState( + HookStateEnum.pending + ); + + useEffect(() => { + if (data == null) { + return setValidUrl(HookStateEnum.invalid); + } + + dispatch( + setHook({ + type: HooksEnum.signMessage, + hookUrl: data.hookUrl, + callbackUrl: data.callbackUrl ?? '' + }) + ); + + setValidUrl(HookStateEnum.valid); + }, []); + + return ( + + ); +}; diff --git a/src/pages/Hook/SignMessageHook/index.tsx b/src/pages/Hook/SignMessageHook/index.tsx new file mode 100644 index 00000000..5c6d8aef --- /dev/null +++ b/src/pages/Hook/SignMessageHook/index.tsx @@ -0,0 +1 @@ +export * from './SignMessageHook'; diff --git a/src/pages/Hook/SignMessageHook/tests/CancelSignMessage.e2e.ts b/src/pages/Hook/SignMessageHook/tests/CancelSignMessage.e2e.ts new file mode 100644 index 00000000..5f1f1650 --- /dev/null +++ b/src/pages/Hook/SignMessageHook/tests/CancelSignMessage.e2e.ts @@ -0,0 +1,26 @@ +import { keystoreAccount, WALLET_SOURCE_ORIGIN } from '__mocks__'; +import { DataTestIdsEnum } from 'localConstants/dataTestIds.enum'; +import { getByDataTestId, loginWithKeystore } from 'utils/testUtils/puppeteer'; + +describe('Cancel sign message tests', () => { + it('should cancel sign message and redirect to callbackUrl with status cancelled', async () => { + await page.goto( + `${WALLET_SOURCE_ORIGIN}/hook/sign-message?message=test&callbackUrl=https://devnet.xexchange.com`, + { + waitUntil: 'domcontentloaded' + } + ); + + await loginWithKeystore({ skipLoggedInCheck: true }); + await page.waitForSelector( + getByDataTestId(DataTestIdsEnum.signMessagePage) + ); + + expect(page.url()).toMatch(`${WALLET_SOURCE_ORIGIN}/sign-message`); + await page.click(getByDataTestId(DataTestIdsEnum.cancelSignMessageBtn)); + + expect(page.url()).toMatch( + `https://devnet.xexchange.com/?address=${keystoreAccount.address}&status=cancelled` + ); + }); +}); diff --git a/src/pages/Hook/SignMessageHook/tests/InvalidSignMessage.e2e.ts b/src/pages/Hook/SignMessageHook/tests/InvalidSignMessage.e2e.ts new file mode 100644 index 00000000..38dff1ed --- /dev/null +++ b/src/pages/Hook/SignMessageHook/tests/InvalidSignMessage.e2e.ts @@ -0,0 +1,28 @@ +import { WALLET_SOURCE_ORIGIN } from '__mocks__/data'; +import { DataTestIdsEnum } from 'localConstants/dataTestIds.enum'; +import { getByDataTestId, loginWithKeystore } from 'utils/testUtils/puppeteer'; + +describe('Invalid sign message tests', () => { + it('should navigate to /dashboard route without signing when message is missing', async () => { + await page.goto( + `${WALLET_SOURCE_ORIGIN}/hook/sign-message?callbackUrl=https://devnet.xexchange.com`, + { + waitUntil: 'domcontentloaded' + } + ); + + await loginWithKeystore(); + expect(page.url()).toMatch(`${WALLET_SOURCE_ORIGIN}/dashboard`); + await page.click(getByDataTestId(DataTestIdsEnum.logoutBtn)); + }); + + it('should navigate to /dashboard route without signing when callbackUrl is missing', async () => { + await page.goto(`${WALLET_SOURCE_ORIGIN}/hook/sign-message?message=test`, { + waitUntil: 'domcontentloaded' + }); + + await loginWithKeystore(); + expect(page.url()).toMatch(`${WALLET_SOURCE_ORIGIN}/dashboard`); + await page.click(getByDataTestId(DataTestIdsEnum.logoutBtn)); + }); +}); diff --git a/src/pages/Hook/SignMessageHook/tests/SignMessage.e2e.ts b/src/pages/Hook/SignMessageHook/tests/SignMessage.e2e.ts new file mode 100644 index 00000000..afa94c81 --- /dev/null +++ b/src/pages/Hook/SignMessageHook/tests/SignMessage.e2e.ts @@ -0,0 +1,26 @@ +import { WALLET_SOURCE_ORIGIN } from '__mocks__/data'; +import { DataTestIdsEnum } from 'localConstants/dataTestIds.enum'; +import { getByDataTestId, loginWithKeystore } from 'utils/testUtils/puppeteer'; + +describe('Cancel sign message tests', () => { + it('should cancel sign message and redirect to callbackUrl with status cancelled', async () => { + await page.goto( + `${WALLET_SOURCE_ORIGIN}/hook/sign-message?message=test&callbackUrl=https://devnet.xexchange.com`, + { + waitUntil: 'domcontentloaded' + } + ); + + await loginWithKeystore({ skipLoggedInCheck: true }); + await page.waitForSelector( + getByDataTestId(DataTestIdsEnum.signMessagePage) + ); + + expect(page.url()).toMatch(`${WALLET_SOURCE_ORIGIN}/sign-message`); + await page.click(getByDataTestId(DataTestIdsEnum.signMessageBtn)); + + expect(page.url()).toMatch( + 'https://devnet.xexchange.com/?status=signed&signature=1f0e1b40b1d83dcde5a62271c676f0d541adf4fbfa55daac4885911ae59c435d313179fab5ba5dc50918dae31909f1d4bf28bd2b68f47a1301c0745c6e039506' + ); + }); +}); diff --git a/src/pages/Hook/index.tsx b/src/pages/Hook/index.tsx index 30312e3d..13b87b50 100644 --- a/src/pages/Hook/index.tsx +++ b/src/pages/Hook/index.tsx @@ -1,3 +1,4 @@ export * from './LoginHook'; export * from './LogoutHook'; export * from './SignHook'; +export * from './SignMessageHook'; diff --git a/src/pages/Send/Send.tsx b/src/pages/Send/Send.tsx index 429d4dc4..0ba02c61 100644 --- a/src/pages/Send/Send.tsx +++ b/src/pages/Send/Send.tsx @@ -143,7 +143,7 @@ export const Send = () => { return (
-

Send

+

Send

diff --git a/src/pages/SignMessage/SignMessage.tsx b/src/pages/SignMessage/SignMessage.tsx new file mode 100644 index 00000000..4ae193ff --- /dev/null +++ b/src/pages/SignMessage/SignMessage.tsx @@ -0,0 +1,155 @@ +import { useEffect, useState } from 'react'; +import type { MouseEvent } from 'react'; +import { faBroom, faArrowsRotate } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { useGetSignMessageSession } from '@multiversx/sdk-dapp/hooks/signMessage/useGetSignMessageSession'; +import { useSelector } from 'react-redux'; +import { useNavigate } from 'react-router-dom'; +import { Button } from 'components/Button'; +import { OutputContainer } from 'components/OutputContainer'; +import { useReplyWithCancelled } from 'hooks'; +import { parseQueryParams, useSignMessage } from 'lib'; +import { CANCELLED, DataTestIdsEnum, HooksEnum } from 'localConstants'; +import { hookSelector } from 'redux/selectors'; +import { routeNames } from 'routes'; +import { SignedMessageStatusesEnum } from 'types'; +import { SignFailure, SignSuccess } from './components'; +import { useSignMessageCompleted } from './hooks'; + +export const SignMessage = () => { + const { sessionId, signMessage, onAbort, onCancel } = useSignMessage(); + const messageSession = useGetSignMessageSession(sessionId); + const { type: hook, callbackUrl, hookUrl } = useSelector(hookSelector); + const navigate = useNavigate(); + const replyWithCancelled = useReplyWithCancelled({ + caller: 'SignModals' + }); + const signMessageCompleted = useSignMessageCompleted(); + + const isSignMessageHook = hook === HooksEnum.signMessage; + + const [message, setMessage] = useState( + isSignMessageHook ? String(parseQueryParams(hookUrl).message) : '' + ); + + const handleSubmit = (e: MouseEvent) => { + e.preventDefault(); + + if (messageSession) { + onAbort(); + } + + if (!message.trim()) { + return; + } + + signMessage({ + message, + callbackRoute: window.location.href + }); + }; + + const isSuccess = + messageSession?.message && + messageSession?.status === SignedMessageStatusesEnum.signed; + + useEffect(() => { + if (isSuccess && isSignMessageHook) { + signMessageCompleted({ isSuccess, signedMessageInfo: messageSession }); + } + }, [isSuccess]); + + // Clear state on destroy + useEffect( + () => () => { + onAbort(); + setMessage(''); + }, + [] + ); + + const handleSignMessageCloseFlow = () => { + if (!isSignMessageHook) { + onAbort(); + navigate(routeNames.dashboard); + return; + } + + onCancel({ + errorMessage: CANCELLED, + callbackRoute: callbackUrl ?? window.location.href + }); + + replyWithCancelled(); + navigate(routeNames.dashboard); + }; + + const isError = messageSession + ? [ + (SignedMessageStatusesEnum.cancelled, SignedMessageStatusesEnum.failed) + ].includes(messageSession.status) && messageSession?.message + : false; + + return ( +
+
+

+ Sign Message +

+ + {!isSuccess && !isError && ( +