Skip to content

Commit

Permalink
[+] Added getDefaultCallbackUrl helper (#936)
Browse files Browse the repository at this point in the history
* [+] Added `getDefaultCallbackUrl` helper

* Updated CHANGELOG.md
  • Loading branch information
razvantomegea authored Sep 29, 2023
1 parent ecfc39e commit 54929e7
Show file tree
Hide file tree
Showing 19 changed files with 478 additions and 298 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- [Added `getDefaultCallbackUrl` helper](https://github.com/multiversx/mx-sdk-dapp/pull/936)

## [[v2.21.0]](https://github.com/multiversx/mx-sdk-dapp/pull/933)] - 2023-09-21

- [Prevent duplicate custom toasts with the same ID](https://github.com/multiversx/mx-sdk-dapp/pull/932)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
useSignTransactionsCommonData
} from 'hooks';
import { SignPropsType } from 'UI/SignTransactionsModals/types/signTransactionsModals.types';
import { getWindowLocation } from 'utils/window/getWindowLocation';
import { getDefaultCallbackUrl } from 'utils/window';
import { ExtraConfirmationScreenPropsType } from './confirmationScreen.types';
import {
TransactionStatusToast,
Expand Down Expand Up @@ -49,8 +49,7 @@ export const DeviceConfirmationScreen = ({
sessionId: transactionsToSign?.sessionId,
transactions: transactionsToSign?.transactions ?? [],
providerType,
callbackRoute:
transactionsToSign?.callbackRoute || getWindowLocation().pathname,
callbackRoute: transactionsToSign?.callbackRoute || getDefaultCallbackUrl(),
className,
verifyReceiverScam
};
Expand Down
7 changes: 4 additions & 3 deletions src/hooks/login/useExtensionLogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
import { LoginMethodsEnum } from 'types/enums.types';
import { getIsLoggedIn } from 'utils/getIsLoggedIn';
import { optionalRedirect } from 'utils/internal';
import { addOriginToLocationPath, getWindowLocation } from 'utils/window';
import { addOriginToLocationPath } from 'utils/window';
import { getDefaultCallbackUrl } from 'utils/window';
import { useLoginService } from './useLoginService';

export type UseExtensionLoginReturnType = [
Expand Down Expand Up @@ -54,9 +55,9 @@ export const useExtensionLogin = ({
return;
}

const { pathname } = getWindowLocation();
const defaultCallbackUrl = getDefaultCallbackUrl();
const callbackUrl: string = encodeURIComponent(
addOriginToLocationPath(callbackRoute ?? pathname)
addOriginToLocationPath(callbackRoute ?? defaultCallbackUrl)
);

if (hasNativeAuth && !token) {
Expand Down
6 changes: 4 additions & 2 deletions src/hooks/login/useOperaLogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { LoginMethodsEnum } from 'types/enums.types';
import { getIsLoggedIn } from 'utils/getIsLoggedIn';
import { optionalRedirect } from 'utils/internal';
import { getDefaultCallbackUrl } from 'utils/window';
import { getWindowLocation } from 'utils/window/getWindowLocation';
import { useLoginService } from './useLoginService';

Expand Down Expand Up @@ -54,9 +55,10 @@ export const useOperaLogin = ({
return;
}

const { origin, pathname } = getWindowLocation();
const { origin } = getWindowLocation();
const defaulCallbackUrl = getDefaultCallbackUrl();
const callbackUrl: string = encodeURIComponent(
`${origin}${callbackRoute ?? pathname}`
`${origin}${callbackRoute ?? defaulCallbackUrl}`
);

if (hasNativeAuth && !token) {
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/signMessage/useSignMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export const useSignMessage = () => {

return `${isWalletLogin ? origin : ''}${callbackUrl.pathname}${
callbackUrl.search
}`;
}${callbackUrl.hash}`;
};

const checkProviderIsInitialized = async () => {
Expand Down
7 changes: 4 additions & 3 deletions src/hooks/transactions/useSignTransactions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {

import { builtCallbackUrl } from 'utils/transactions/builtCallbackUrl';
import { parseTransactionAfterSigning } from 'utils/transactions/parseTransactionAfterSigning';
import { getDefaultCallbackUrl } from 'utils/window';
import { getWindowLocation } from 'utils/window/getWindowLocation';

import {
Expand Down Expand Up @@ -150,9 +151,9 @@ export const useSignTransactions = () => {
customTransactionInformation
} = transactionsToSign;
const { redirectAfterSign } = customTransactionInformation;
const { pathname } = getWindowLocation();
const redirectRoute = callbackRoute || pathname;
const isCurrentRoute = pathname.includes(redirectRoute);
const defaultCallbackUrl = getDefaultCallbackUrl();
const redirectRoute = callbackRoute || defaultCallbackUrl;
const isCurrentRoute = defaultCallbackUrl.includes(redirectRoute);
const shouldRedirectAfterSign = redirectAfterSign && !isCurrentRoute;

try {
Expand Down
4 changes: 2 additions & 2 deletions src/services/transactions/sendBatchTransactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import {
SimpleTransactionType
} from 'types';
import { generateBatchTransactionsGrouping } from 'utils/transactions/batch/generateBatchTransactionsGrouping';
import { getWindowLocation } from 'utils/window/getWindowLocation';
import { getDefaultCallbackUrl } from 'utils/window';
import { signTransactions } from './signTransactions';
import { transformTransactionsToSign } from './utils/transformTransactionsToSign';

export async function sendBatchTransactions({
transactions,
transactionsDisplayInfo,
redirectAfterSign = true,
callbackRoute = getWindowLocation().pathname,
callbackRoute = getDefaultCallbackUrl(),
signWithoutSending = false,
completedTransactionsDelay,
sessionInformation,
Expand Down
4 changes: 2 additions & 2 deletions src/services/transactions/sendTransactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import {
SendTransactionsPropsType,
SimpleTransactionType
} from 'types';
import { getWindowLocation } from 'utils/window/getWindowLocation';
import { getDefaultCallbackUrl } from 'utils/window';
import { signTransactions } from './signTransactions';
import { transformTransactionsToSign } from './utils/transformTransactionsToSign';

export async function sendTransactions({
transactions,
transactionsDisplayInfo,
redirectAfterSign = true,
callbackRoute = getWindowLocation().pathname,
callbackRoute = getDefaultCallbackUrl(),
signWithoutSending = false,
completedTransactionsDelay,
sessionInformation,
Expand Down
6 changes: 3 additions & 3 deletions src/utils/clearNavigationHistory.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { getWindowLocation } from './window/getWindowLocation';
import { getWindowLocation } from './window';

export const clearNavigationHistory = (remainingParams: any) => {
const newUrlParams = new URLSearchParams(remainingParams).toString();
const { pathname } = getWindowLocation();
const { pathname, hash } = getWindowLocation();
const newSearch = newUrlParams ? `?${newUrlParams}` : '';
const fullPath = pathname ? `${pathname}${newSearch}` : './';
const fullPath = pathname ? `${pathname}${newSearch}${hash}` : './';

setTimeout(() => {
window?.history.replaceState({}, document?.title, fullPath);
Expand Down
5 changes: 3 additions & 2 deletions src/utils/sanitizeCallbackUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ export const sanitizeCallbackUrl = (
vulnerableItems: string[] = ['address']
) => {
const url = new URL(targetURL);

const params = new URLSearchParams(url.search);

vulnerableItems.forEach((vulnerableItem) => params.delete(vulnerableItem));
Expand All @@ -17,5 +16,7 @@ export const sanitizeCallbackUrl = (
return targetURL;
}

return `${url.origin}${pathname}${questionMark}${params.toString()}`;
return `${url.origin}${pathname}${questionMark}${params.toString()}${
url.hash
}`;
};
6 changes: 6 additions & 0 deletions src/utils/tests/sanitizeCallbackUrl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ describe('sanitizeLoginCallbackUrl tests', () => {
const result = sanitizeCallbackUrl('https://wallet.multiversx.com');
expect(result).toEqual('https://wallet.multiversx.com');
});
test('Keeps the path and hash of the URL', () => {
const result = sanitizeCallbackUrl(
'https://wallet.multiversx.com/path#hash'
);
expect(result).toEqual('https://wallet.multiversx.com/path#hash');
});
test('remove "address" item from query params when this is the only query item', () => {
const result = sanitizeCallbackUrl(
'https://localhost:3000/feed?address=erd1-address'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ export const generateBatchTransactionsGrouping = (
) => {
let indexInFlatArray = 0;
return transactions.map((group) => {
return group.map((_tx) => indexInFlatArray++);
return group.map(() => indexInFlatArray++);
});
};
11 changes: 8 additions & 3 deletions src/utils/transactions/builtCallbackUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,14 @@ export function builtCallbackUrl({
let url = callbackUrl;

if (Object.entries(urlParams).length > 0) {
const { search, origin, pathname } = new URL(callbackUrl);
const { nextUrlParams } = buildUrlParams(search, urlParams);
url = `${origin}${pathname}?${nextUrlParams}`;
try {
const { search, origin, pathname, hash } = new URL(callbackUrl);
const { nextUrlParams } = buildUrlParams(search, urlParams);
url = `${origin}${pathname}?${nextUrlParams}${hash}`;
} catch (err) {
console.error('Unable to construct URL from: ', callbackUrl, err);
return url;
}
}

return url;
Expand Down
54 changes: 54 additions & 0 deletions src/utils/transactions/tests/builtCallbackUrl.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { builtCallbackUrl } from '../builtCallbackUrl';

describe('builtCallbackUrl tests', () => {
const url = 'https://wallet.multiversx.com';

test('returns callbackUrl unmodified if urlParams is empty', () => {
expect(builtCallbackUrl({ callbackUrl: url })).toBe(url);
});

test('adds urlParams', () => {
expect(
builtCallbackUrl({
callbackUrl: url,
urlParams: { status: 'success' }
})
).toBe('https://wallet.multiversx.com/?status=success');
});

test('adds urlParams and keeps existing hash', () => {
expect(
builtCallbackUrl({
callbackUrl: url + '#test',
urlParams: { status: 'success' }
})
).toBe('https://wallet.multiversx.com/?status=success#test');
});

test('keeps existing urlParams', () => {
expect(
builtCallbackUrl({
callbackUrl: url + '?page=1',
urlParams: { status: 'success' }
})
).toBe('https://wallet.multiversx.com/?page=1&status=success');
});

test('keeps existing hash', () => {
expect(
builtCallbackUrl({
callbackUrl: url + '?page=1#logs',
urlParams: { status: 'success' }
})
).toBe('https://wallet.multiversx.com/?page=1&status=success#logs');
});

test('throws error if callbackUrl is invalid and urlParams are defined', () => {
expect(
builtCallbackUrl({
callbackUrl: '',
urlParams: { status: 'success' }
})
).toBe('');
});
});
7 changes: 7 additions & 0 deletions src/utils/window/getDefaultCallbackUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { getWindowLocation } from './index';

export const getDefaultCallbackUrl = () => {
const { pathname, search, hash } = getWindowLocation();

return `${pathname ?? ''}${search ?? ''}${hash ?? ''}`;
};
1 change: 1 addition & 0 deletions src/utils/window/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './addOriginToLocationPath';
export * from './getDefaultCallbackUrl';
export * from './getWindowLocation';
96 changes: 96 additions & 0 deletions src/utils/window/tests/getDefaultCallbackUrl.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { getDefaultCallbackUrl } from '../getDefaultCallbackUrl';

const searchMock = '?search=mock';
const pathnameMock = '/unlock';
const originMock = 'https://multiversx.com';
const hashMock = '#main';
const hrefMock = 'https://multiversx.com/technology';
const urlMock = '/unlock?search=mock#main';

let windowSpy: jest.SpyInstance;

beforeEach(() => {
windowSpy = jest.spyOn(window, 'window', 'get');
});
afterEach(() => {
windowSpy.mockRestore();
});

describe('Get window location', () => {
it('getDefaultCallbackUrl should be undefined', () => {
windowSpy.mockImplementation(() => undefined);

const callbackUrl = getDefaultCallbackUrl();
expect(callbackUrl).toStrictEqual('');
});

it('window should return search', () => {
windowSpy.mockImplementation(() => ({
location: {
search: searchMock
}
}));

const callbackUrl = getDefaultCallbackUrl();
expect(callbackUrl).toStrictEqual(searchMock);
});

it('getDefaultCallbackUrl should return pathname', () => {
windowSpy.mockImplementation(() => ({
location: {
pathname: pathnameMock
}
}));

const callbackUrl = getDefaultCallbackUrl();
expect(callbackUrl).toStrictEqual(pathnameMock);
});

it('getDefaultCallbackUrl should return empty string when only origin is specified', () => {
windowSpy.mockImplementation(() => ({
location: {
origin: originMock
}
}));

const callbackUrl = getDefaultCallbackUrl();
expect(callbackUrl).toStrictEqual('');
});

it('getDefaultCallbackUrl should return hash', () => {
windowSpy.mockImplementation(() => ({
location: {
hash: hashMock
}
}));

const callbackUrl = getDefaultCallbackUrl();
expect(callbackUrl).toStrictEqual(hashMock);
});

it('getDefaultCallbackUrl should return empty', () => {
windowSpy.mockImplementation(() => ({
location: {
href: hrefMock
}
}));

const callbackUrl = getDefaultCallbackUrl();
expect(callbackUrl).toStrictEqual('');
});

it('getDefaultCallbackUrl should return complete URL without origin', () => {
windowSpy.mockImplementation(() => ({
location: {
pathname: pathnameMock,
origin: originMock,
hash: hashMock,
href: hrefMock,
search: searchMock
}
}));

const callbackUrl = getDefaultCallbackUrl();
expect(callbackUrl).toStrictEqual(urlMock);
});
});
2 changes: 1 addition & 1 deletion src/web/hooks/useIdleTimer.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useIdleTimer as useReactIdleTimer } from 'react-idle-timer';
import { useClosureRef } from 'hooks/useClosureRef';
import { useGetIsLoggedIn } from 'hooks/account/useGetIsLoggedIn';
import { useClosureRef } from 'hooks/useClosureRef';
import { useSelector } from 'reduxStore/DappProviderContext';
import { logoutRouteSelector } from 'reduxStore/selectors';
import { logout as dappLogout } from 'utils/logout';
Expand Down
Loading

0 comments on commit 54929e7

Please sign in to comment.