Skip to content

Commit

Permalink
[NO CHANGELOG][Checkout Widget] Validate checkout widget inputs (#2209)
Browse files Browse the repository at this point in the history
  • Loading branch information
jhesgodi authored Sep 24, 2024
1 parent 2a3a8e5 commit 4f05d8b
Show file tree
Hide file tree
Showing 6 changed files with 275 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export type CheckouWidgetSaleFlowParams = {
flow: CheckoutFlowType.SALE;
} & SaleWidgetParams;

export type CheckouWidgetAddFundsFlowParams = {
export type CheckoutWidgetAddFundsFlowParams = {
flow: CheckoutFlowType.ADD_FUNDS;
} & AddFundsWidgetParams;

Expand All @@ -53,7 +53,7 @@ export type CheckoutWidgetFlowParams =
| CheckouWidgetBridgeFlowParams
| CheckouWidgetOnRampFlowParams
| CheckouWidgetSaleFlowParams
| CheckouWidgetAddFundsFlowParams;
| CheckoutWidgetAddFundsFlowParams;

export type CheckoutWidgetParams = {
/** The language to use for the checkout widget */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import AddFundsWidget from '../add-funds/AddFundsWidget';
import { getViewShouldConnect } from './functions/getViewShouldConnect';
import { useWidgetEvents } from './hooks/useWidgetEvents';
import { getConnectLoaderParams } from './functions/getConnectLoaderParams';
import { checkoutFlows } from './functions/isValidCheckoutFlow';

export type CheckoutWidgetInputs = {
checkout: Checkout;
Expand Down Expand Up @@ -82,21 +83,47 @@ export default function CheckoutWidget(props: CheckoutWidgetInputs) {
});
}, [flowParams]);

const showBackButton = !!view.data?.showBackButton;
/**
* If invalid flow set error view
*/
useEffect(() => {
if (checkoutFlows.includes(flowParams.flow)) return;

viewDispatch({
payload: {
type: ViewActions.UPDATE_VIEW,
view: {
type: SharedViews.ERROR_VIEW,
error: {
name: 'InvalidViewType',
message: `Invalid view type "${flowParams}"`,
},
},
},
});
}, [flowParams.flow]);

/**
* Validate if the view requires connect loader
*/
const shouldConnectView = useMemo(
() => getViewShouldConnect(view.type),
[view.type],
);

/*
* Show back button
*/
const showBackButton = !!view.data?.showBackButton;

return (
<ViewContextProvider>
<CheckoutWidgetContextProvicer>
{/* --- Status Views --- */}
{view.type === SharedViews.LOADING_VIEW && (
<LoadingView loadingText={t('views.LOADING_VIEW.text')} />
)}
{view.type === SharedViews.SERVICE_UNAVAILABLE_ERROR_VIEW && (
{view.type === SharedViews.ERROR_VIEW && (
<ErrorView
onCloseClick={() => {
sendCheckoutEvent(eventTarget, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ import {
WidgetProperties,
WidgetTheme,
WidgetType,
CheckoutWidgetConnectFlowParams,
CheckoutWidgetWalletFlowParams,
CheckoutWidgetAddFundsFlowParams,
CheckouWidgetSwapFlowParams,
CheckouWidgetBridgeFlowParams,
CheckouWidgetOnRampFlowParams,
CheckouWidgetSaleFlowParams,
CheckoutFlowType,
} from '@imtbl/checkout-sdk';
import React, { Suspense } from 'react';
import { ThemeProvider } from '../../components/ThemeProvider/ThemeProvider';
Expand All @@ -13,6 +21,13 @@ import { HandoverProvider } from '../../context/handover-context/HandoverProvide
import { LoadingView } from '../../views/loading/LoadingView';
import { Base } from '../BaseWidgetRoot';
import i18n from '../../i18n';
import {
isValidAddress,
isValidAmount,
isValidWalletProvider,
} from '../../lib/validations/widgetValidators';
import { deduplicateSaleItemsArray } from './functions/deduplicateSaleItemsArray';
import { checkoutFlows } from './functions/isValidCheckoutFlow';

const CheckoutWidget = React.lazy(() => import('./CheckoutWidget'));

Expand All @@ -37,11 +52,193 @@ export class CheckoutWidgetRoot extends Base<WidgetType.CHECKOUT> {
};
}

protected getValidConnectFlowParams(params: CheckoutWidgetConnectFlowParams) {
const validatedParams = { ...params };

if (!Array.isArray(validatedParams.blocklistWalletRdns)) {
// eslint-disable-next-line no-console
console.warn('[IMTBL]: invalid "blocklistWalletRdns" widget input');
validatedParams.blocklistWalletRdns = [];
}

return validatedParams;
}

protected getValidWalletFlowParams(params: CheckoutWidgetWalletFlowParams) {
return params;
}

protected getValidSaleFlowParams(params: CheckouWidgetSaleFlowParams) {
const validatedParams = { ...params };

if (!isValidWalletProvider(params.walletProviderName)) {
// eslint-disable-next-line no-console
console.warn('[IMTBL]: invalid "walletProviderName" widget input');
validatedParams.walletProviderName = undefined;
}

if (!Array.isArray(validatedParams.items)) {
// eslint-disable-next-line no-console
console.warn('[IMTBL]: invalid "items" widget input.');
validatedParams.items = [];
}

if (!params.environmentId) {
// eslint-disable-next-line no-console
console.warn('[IMTBL]: invalid "environmentId" widget input');
validatedParams.environmentId = '';
}

if (!params.collectionName) {
// eslint-disable-next-line no-console
console.warn('[IMTBL]: invalid "collectionName" widget input');
validatedParams.collectionName = '';
}

if (
params.excludePaymentTypes !== undefined
&& !Array.isArray(params.excludePaymentTypes)
) {
// eslint-disable-next-line no-console
console.warn('[IMTBL]: invalid "excludePaymentTypes" widget input');
validatedParams.excludePaymentTypes = [];
}

return {
...validatedParams,
items: deduplicateSaleItemsArray(params.items),
};
}

protected getValidAddFundsFlowParams(
params: CheckoutWidgetAddFundsFlowParams,
) {
const validatedParams = { ...params };

if (validatedParams.showBridgeOption) {
validatedParams.showBridgeOption = true;
}

if (validatedParams.showOnrampOption) {
validatedParams.showOnrampOption = true;
}

if (validatedParams.showSwapOption) {
validatedParams.showSwapOption = true;
}

if (!isValidAmount(validatedParams.toAmount)) {
// eslint-disable-next-line no-console
console.warn('[IMTBL]: invalid "toAmount" widget input');
validatedParams.toAmount = '';
}

if (!isValidAddress(params.toTokenAddress)) {
// eslint-disable-next-line no-console
console.warn('[IMTBL]: invalid "toTokenAddress" widget input');
validatedParams.toTokenAddress = '';
}

return validatedParams;
}

protected getValidSwapFlowParams(params: CheckouWidgetSwapFlowParams) {
const validatedParams = { ...params };

if (!isValidAmount(params.amount)) {
// eslint-disable-next-line no-console
console.warn('[IMTBL]: invalid "amount" widget input');
validatedParams.amount = '';
}

if (!isValidAddress(params.fromTokenAddress)) {
// eslint-disable-next-line no-console
console.warn('[IMTBL]: invalid "fromTokenAddress" widget input');
validatedParams.fromTokenAddress = '';
}

if (!isValidAddress(params.toTokenAddress)) {
// eslint-disable-next-line no-console
console.warn('[IMTBL]: invalid "toTokenAddress" widget input');
validatedParams.toTokenAddress = '';
}

if (params.autoProceed) {
validatedParams.autoProceed = true;
}

return validatedParams;
}

protected getValidBridgeFlowParams(params: CheckouWidgetBridgeFlowParams) {
const validatedParams = { ...params };

if (!isValidAmount(params.amount)) {
// eslint-disable-next-line no-console
console.warn('[IMTBL]: invalid "amount" widget input');
validatedParams.amount = '';
}

if (!isValidAddress(params.tokenAddress)) {
// eslint-disable-next-line no-console
console.warn('[IMTBL]: invalid "tokenAddress" widget input');
validatedParams.tokenAddress = '';
}

return validatedParams;
}

protected getValidOnRampFlowParams(params: CheckouWidgetOnRampFlowParams) {
const validatedParams = { ...params };

if (!isValidAmount(params.amount)) {
// eslint-disable-next-line no-console
console.warn('[IMTBL]: invalid "amount" widget input');
validatedParams.amount = '';
}

if (!isValidAddress(params.tokenAddress)) {
// eslint-disable-next-line no-console
console.warn('[IMTBL]: invalid "tokenAddress" widget input');
validatedParams.tokenAddress = '';
}

return validatedParams;
}

protected getValidatedParameters(
params: CheckoutWidgetParams,
): CheckoutWidgetParams {
// TODO: Validate params for each widget
return params;
// if empty do nothing
if (Object.keys(params).length === 0) {
return params;
}

const flowType = params.flow;
const supportedFlows = checkoutFlows.join(', ');

switch (flowType) {
case CheckoutFlowType.CONNECT:
return this.getValidConnectFlowParams(params);
case CheckoutFlowType.WALLET:
return this.getValidWalletFlowParams(params);
case CheckoutFlowType.SALE:
return this.getValidSaleFlowParams(params);
case CheckoutFlowType.SWAP:
return this.getValidSwapFlowParams(params);
case CheckoutFlowType.BRIDGE:
return this.getValidBridgeFlowParams(params);
case CheckoutFlowType.ONRAMP:
return this.getValidOnRampFlowParams(params);
case CheckoutFlowType.ADD_FUNDS:
return this.getValidAddFundsFlowParams(params);
default:
// eslint-disable-next-line no-console
console.warn(
`[IMTBL]: invalid "flow: ${flowType}" widget input, must be one of the following: ${supportedFlows}`,
);
return params;
}
}

protected render() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { SaleItem } from '@imtbl/checkout-sdk';

export function deduplicateSaleItemsArray(items: SaleItem[] | undefined): SaleItem[] {
if (!items || !Array.isArray(items)) return [];

const uniqueItems = items.reduce((acc, item) => {
const itemIndex = acc.findIndex(
({ productId }) => productId === item.productId,
);

if (itemIndex !== -1) {
acc[itemIndex] = { ...item, qty: acc[itemIndex].qty + item.qty };
return acc;
}

return [...acc, { ...item }];
}, [] as SaleItem[]);

return uniqueItems;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { CheckoutFlowType } from '@imtbl/checkout-sdk';

/** Orchestration Events List */
export const checkoutFlows = [
CheckoutFlowType.CONNECT,
CheckoutFlowType.WALLET,
CheckoutFlowType.SALE,
CheckoutFlowType.SWAP,
CheckoutFlowType.BRIDGE,
CheckoutFlowType.ONRAMP,
CheckoutFlowType.ADD_FUNDS,
];

/**
* Check if event is orchestration event
*/
export function isValidCheckoutFlow(flow: string): boolean {
return checkoutFlows.includes(flow as CheckoutFlowType);
}
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ function CheckoutUI() {

// mount & re-render widget everytime params change
useEffect(() => {
if (params == undefined) return;
if (params?.flow === undefined) return;
if (renderAfterConnect && !web3Provider) return;

mount();
Expand Down Expand Up @@ -659,6 +659,11 @@ function CheckoutUI() {
<Select.Option.Label>{flow}</Select.Option.Label>
</Select.Option>
))}
<Select.Option key={"INVALID"} optionKey={"INVALID"}>
<Select.Option.Label>
{"INVALID FLOW TYPE"}
</Select.Option.Label>
</Select.Option>
</Select>
</>
)}
Expand Down

0 comments on commit 4f05d8b

Please sign in to comment.