Skip to content

Commit

Permalink
Sign message (#24)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
arhtudormorar and razvantomegea authored Jun 20, 2024
1 parent 5445cae commit a40095a
Show file tree
Hide file tree
Showing 34 changed files with 562 additions and 32 deletions.
8 changes: 3 additions & 5 deletions jest-puppeteer.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ const config = {
roots: ['<rootDir>/src'],
modulePaths: ['<rootDir>/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',
Expand All @@ -67,9 +67,7 @@ const config = {
server: {
command: 'vite preview'
},
browserContext: 'incognito',
browserPerWorker: true,
runBeforeUnload: true
browserContext: 'incognito'
};

if (!isHeadless) {
Expand Down
4 changes: 2 additions & 2 deletions src/__mocks__/data/constants.ts
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import {
getLoginHookData,
getSignHookData,
getSignMessageHookData,
Transaction,
useGetLoginInfo
} from 'lib';
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/hooks/useRedirectPathname/useRedirectPathname.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/lib/sdkJsWebWalletIo.ts
Original file line number Diff line number Diff line change
@@ -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';
5 changes: 4 additions & 1 deletion src/localConstants/dataTestIds.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export enum DataTestIdsEnum {
receiverError = 'receiverError',
receiverInput = 'receiverInput',
sendBtn = 'sendBtn',
signMessageBtn = 'signMessageBtn',
cancelSignMessageBtn = 'cancelSignMessageBtn',
sendEsdtTypeInput = 'sendEsdtTypeInput',
sendNFtTypeInput = 'sendNFtTypeInput',
signBtn = 'signBtn',
Expand All @@ -29,5 +31,6 @@ export enum DataTestIdsEnum {
transactionToastTitle = 'transactionToastTitle',
unlockPage = 'unlockPage',
userAddress = 'userAddress',
walletFile = 'walletFile'
walletFile = 'walletFile',
signMessagePage = 'signMessagePage'
}
13 changes: 8 additions & 5 deletions src/localConstants/routes/routeNames.enums.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
export enum HooksEnum {
login = 'login',
logout = 'logout',
sign = 'sign'
sign = 'sign',
signMessage = 'sign-message'
}

export enum RouteNamesEnum {
Expand All @@ -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}`
}
1 change: 1 addition & 0 deletions src/localConstants/sdkDapp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
7 changes: 7 additions & 0 deletions src/pages/Dashboard/widgets/Account/Account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ export const Account = () => {
>
Send
</MxLink>
<MxLink
className='inline-block rounded-lg bg-blue-500 px-4 py-2 text-sm text-white'
data-testid={DataTestIdsEnum.sendBtn}
to={routeNames.signMessage}
>
Sign Message
</MxLink>
</div>
</div>
<div className='mb-2 hidden justify-center sm:block'>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <Navigate to={routeNames.unlock} replace />;
}

Expand All @@ -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: {
Expand Down
6 changes: 4 additions & 2 deletions src/pages/Hook/LoginHook/LoginHook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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<HookStateEnum>(
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Hook/LogoutHook/LogoutHook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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]);
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Hook/SignHook/SignHook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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<HookStateEnum>(
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Hook/SignHook/tests/InvalidSignHook.e2e.ts
Original file line number Diff line number Diff line change
@@ -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 () => {
Expand Down
47 changes: 47 additions & 0 deletions src/pages/Hook/SignMessageHook/SignMessageHook.tsx
Original file line number Diff line number Diff line change
@@ -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>(
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 (
<HookValidationOutcome
hook={HooksEnum.signMessage}
callbackUrl={data?.callbackUrl}
validUrl={validUrl}
/>
);
};
1 change: 1 addition & 0 deletions src/pages/Hook/SignMessageHook/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './SignMessageHook';
26 changes: 26 additions & 0 deletions src/pages/Hook/SignMessageHook/tests/CancelSignMessage.e2e.ts
Original file line number Diff line number Diff line change
@@ -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`
);
});
});
28 changes: 28 additions & 0 deletions src/pages/Hook/SignMessageHook/tests/InvalidSignMessage.e2e.ts
Original file line number Diff line number Diff line change
@@ -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));
});
});
26 changes: 26 additions & 0 deletions src/pages/Hook/SignMessageHook/tests/SignMessage.e2e.ts
Original file line number Diff line number Diff line change
@@ -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'
);
});
});
1 change: 1 addition & 0 deletions src/pages/Hook/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './LoginHook';
export * from './LogoutHook';
export * from './SignHook';
export * from './SignMessageHook';
2 changes: 1 addition & 1 deletion src/pages/Send/Send.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export const Send = () => {

return (
<div className='flex flex-col p-6 max-w-2xl w-full bg-white shadow-md rounded h-full'>
<h2 className='text-3xl font-bold p-2 mb-2 text-center'>Send</h2>
<h2 className='text-2xl font-bold p-2 mb-2 text-center'>Send</h2>
<form onSubmit={formik.handleSubmit}>
<div className='flex flex-col gap-4 h-full'>
<div className='flex flex-col'>
Expand Down
Loading

0 comments on commit a40095a

Please sign in to comment.