diff --git a/packages/checkout/sdk/src/widgets/definitions/events/checkout.ts b/packages/checkout/sdk/src/widgets/definitions/events/checkout.ts index d22666b0a3..22ff048ee9 100644 --- a/packages/checkout/sdk/src/widgets/definitions/events/checkout.ts +++ b/packages/checkout/sdk/src/widgets/definitions/events/checkout.ts @@ -1,3 +1,11 @@ export enum CheckoutEventType { - + CHECKOUT_APP_READY = 'CHECKOUT_APP_READY', + CHECKOUT_APP_EVENT = 'CHECKOUT_APP_EVENT', } +/** + * Represents a successful connection. + * @property {Web3Provider} provider + * @property {WalletProviderName | undefined} walletProviderName + */ +export type CheckoutEventReady = { +}; diff --git a/packages/checkout/sdk/src/widgets/definitions/types.ts b/packages/checkout/sdk/src/widgets/definitions/types.ts index 544185cdc3..ff59e5fdf7 100644 --- a/packages/checkout/sdk/src/widgets/definitions/types.ts +++ b/packages/checkout/sdk/src/widgets/definitions/types.ts @@ -131,54 +131,62 @@ type ProviderEventMapping = { */ export type WidgetEventData = { [WidgetType.CONNECT]: { - [ConnectEventType.SUCCESS]: ConnectionSuccess, - [ConnectEventType.FAILURE]: ConnectionFailed, - [ConnectEventType.CLOSE_WIDGET]: {}, - [ConnectEventType.WALLETCONNECT_PROVIDER_UPDATED]: WalletConnectProviderChanged, - } & OrchestrationMapping & ProviderEventMapping, + [ConnectEventType.SUCCESS]: ConnectionSuccess; + [ConnectEventType.FAILURE]: ConnectionFailed; + [ConnectEventType.CLOSE_WIDGET]: {}; + [ConnectEventType.WALLETCONNECT_PROVIDER_UPDATED]: WalletConnectProviderChanged; + } & OrchestrationMapping & + ProviderEventMapping; [WidgetType.WALLET]: { - [WalletEventType.NETWORK_SWITCH]: WalletNetworkSwitch - [WalletEventType.DISCONNECT_WALLET]: WalletDisconnect - [WalletEventType.CLOSE_WIDGET]: {} - } & OrchestrationMapping & ProviderEventMapping, + [WalletEventType.NETWORK_SWITCH]: WalletNetworkSwitch; + [WalletEventType.DISCONNECT_WALLET]: WalletDisconnect; + [WalletEventType.CLOSE_WIDGET]: {}; + } & OrchestrationMapping & + ProviderEventMapping; [WidgetType.SWAP]: { - [SwapEventType.SUCCESS]: SwapSuccess, - [SwapEventType.FAILURE]: SwapFailed, - [SwapEventType.REJECTED]: SwapRejected, - [SwapEventType.CLOSE_WIDGET]: {}, - } & OrchestrationMapping & ProviderEventMapping + [SwapEventType.SUCCESS]: SwapSuccess; + [SwapEventType.FAILURE]: SwapFailed; + [SwapEventType.REJECTED]: SwapRejected; + [SwapEventType.CLOSE_WIDGET]: {}; + } & OrchestrationMapping & + ProviderEventMapping; [WidgetType.BRIDGE]: { - [BridgeEventType.TRANSACTION_SENT]: BridgeTransactionSent, - [BridgeEventType.FAILURE]: BridgeFailed, - [BridgeEventType.CLOSE_WIDGET]: {} - [BridgeEventType.CLAIM_WITHDRAWAL_SUCCESS]: BridgeClaimWithdrawalSuccess - [BridgeEventType.CLAIM_WITHDRAWAL_FAILURE]: BridgeClaimWithdrawalFailed - } & OrchestrationMapping & ProviderEventMapping, + [BridgeEventType.TRANSACTION_SENT]: BridgeTransactionSent; + [BridgeEventType.FAILURE]: BridgeFailed; + [BridgeEventType.CLOSE_WIDGET]: {}; + [BridgeEventType.CLAIM_WITHDRAWAL_SUCCESS]: BridgeClaimWithdrawalSuccess; + [BridgeEventType.CLAIM_WITHDRAWAL_FAILURE]: BridgeClaimWithdrawalFailed; + } & OrchestrationMapping & + ProviderEventMapping; [WidgetType.ONRAMP]: { - [OnRampEventType.SUCCESS]: OnRampSuccess, - [OnRampEventType.FAILURE]: OnRampFailed, - [OnRampEventType.CLOSE_WIDGET]: {}, - } & OrchestrationMapping & ProviderEventMapping, + [OnRampEventType.SUCCESS]: OnRampSuccess; + [OnRampEventType.FAILURE]: OnRampFailed; + [OnRampEventType.CLOSE_WIDGET]: {}; + } & OrchestrationMapping & + ProviderEventMapping; [WidgetType.SALE]: { - [SaleEventType.SUCCESS]: SaleSuccess, - [SaleEventType.FAILURE]: SaleFailed, - [SaleEventType.REJECTED]: any, - [SaleEventType.CLOSE_WIDGET]: {}, - [SaleEventType.TRANSACTION_SUCCESS]: SaleTransactionSuccess, - [SaleEventType.PAYMENT_METHOD]: SalePaymentMethod, - [SaleEventType.PAYMENT_TOKEN]: SalePaymentToken, - [SaleEventType.REQUEST_BRIDGE]: {}, - [SaleEventType.REQUEST_SWAP]: {}, - [SaleEventType.REQUEST_ONRAMP]: {}, - } & OrchestrationMapping & ProviderEventMapping, - + [SaleEventType.SUCCESS]: SaleSuccess; + [SaleEventType.FAILURE]: SaleFailed; + [SaleEventType.REJECTED]: any; + [SaleEventType.CLOSE_WIDGET]: {}; + [SaleEventType.TRANSACTION_SUCCESS]: SaleTransactionSuccess; + [SaleEventType.PAYMENT_METHOD]: SalePaymentMethod; + [SaleEventType.PAYMENT_TOKEN]: SalePaymentToken; + [SaleEventType.REQUEST_BRIDGE]: {}; + [SaleEventType.REQUEST_SWAP]: {}; + [SaleEventType.REQUEST_ONRAMP]: {}; + } & OrchestrationMapping & + ProviderEventMapping; [WidgetType.CHECKOUT]: { - } & OrchestrationMapping & ProviderEventMapping, + [CheckoutEventType.CHECKOUT_APP_READY]: {}; + [CheckoutEventType.CHECKOUT_APP_EVENT]: Record; + } & OrchestrationMapping & + ProviderEventMapping; }; /** diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidget.tsx b/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidget.tsx index 9ea9367200..554859e3a1 100644 --- a/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidget.tsx +++ b/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidget.tsx @@ -1,14 +1,14 @@ +import { + useContext, useEffect, useMemo, useReducer, +} from 'react'; import { Checkout, + CheckoutEventType, CheckoutWidgetConfiguration, CheckoutWidgetParams, + IMTBLWidgetEvents, WalletProviderName, } from '@imtbl/checkout-sdk'; -import { - useEffect, - useMemo, - useReducer, -} from 'react'; import { CheckoutActions, checkoutReducer, @@ -16,9 +16,23 @@ import { } from './context/CheckoutContext'; import { CheckoutContextProvider } from './context/CheckoutContextProvider'; import { CheckoutAppIframe } from './views/CheckoutAppIframe'; -// import { CHECKOUT_APP_URL } from '../../lib/constants'; - import { getIframeURL } from './functions/iframeParams'; +import { + sendCheckoutEvent, + sendCheckoutReadyEvent, +} from './CheckoutWidgetEvents'; +import { EventTargetContext } from '../../context/event-target-context/EventTargetContext'; + +const widgetEventsList = [ + IMTBLWidgetEvents.IMTBL_WIDGETS_PROVIDER, + IMTBLWidgetEvents.IMTBL_CONNECT_WIDGET_EVENT, + IMTBLWidgetEvents.IMTBL_WALLET_WIDGET_EVENT, + IMTBLWidgetEvents.IMTBL_SWAP_WIDGET_EVENT, + IMTBLWidgetEvents.IMTBL_BRIDGE_WIDGET_EVENT, + IMTBLWidgetEvents.IMTBL_ONRAMP_WIDGET_EVENT, + IMTBLWidgetEvents.IMTBL_SALE_WIDGET_EVENT, + IMTBLWidgetEvents.IMTBL_CHECKOUT_WIDGET_EVENT, +]; export type CheckoutWidgetInputs = { checkout: Checkout; @@ -29,17 +43,65 @@ export type CheckoutWidgetInputs = { export default function CheckoutWidget(props: CheckoutWidgetInputs) { const { config, checkout, params } = props; const { environment, publishableKey } = checkout.config; + const { + eventTargetState: { eventTarget }, + } = useContext(EventTargetContext); - const [checkoutState, checkoutDispatch] = useReducer(checkoutReducer, initialCheckoutState); + const [targetOrigin, iframeUrl] = useMemo(() => { + if (!publishableKey) return ['', '']; + return getIframeURL(params, config, environment, publishableKey); + }, [params, config, environment, publishableKey]); + + const [checkoutState, checkoutDispatch] = useReducer( + checkoutReducer, + initialCheckoutState, + ); const checkoutReducerValues = useMemo( () => ({ checkoutState, checkoutDispatch }), [checkoutState, checkoutDispatch], ); + const handleIframeEvents = ( + event: MessageEvent<{ + type: IMTBLWidgetEvents; + detail: { + type: string; + data: Record; + }; + }>, + ) => { + const { type } = event.data; + if (event.origin !== targetOrigin) return; + if (!widgetEventsList.includes(type)) return; + + console.log('🍎 Ack 🍎', event.data); // eslint-disable-line + + const { detail } = event.data; + + switch (type) { + case IMTBLWidgetEvents.IMTBL_CHECKOUT_WIDGET_EVENT: + switch (detail.type) { + case CheckoutEventType.CHECKOUT_APP_READY: + sendCheckoutReadyEvent(eventTarget); + break; + default: + break; + } + + break; + default: + sendCheckoutEvent(eventTarget, event.data); + break; + } + }; + useEffect(() => { - if (!publishableKey) return; + window.addEventListener('message', handleIframeEvents); + return () => window.removeEventListener('message', handleIframeEvents); + }, []); - const iframeUrl = getIframeURL(params, config, environment, publishableKey); + useEffect(() => { + if (iframeUrl === undefined) return; checkoutDispatch({ payload: { @@ -47,7 +109,7 @@ export default function CheckoutWidget(props: CheckoutWidgetInputs) { iframeUrl, }, }); - }, [params, config, environment, publishableKey]); + }, [iframeUrl]); useEffect(() => { checkoutDispatch({ @@ -58,9 +120,13 @@ export default function CheckoutWidget(props: CheckoutWidgetInputs) { }); const connectProvider = async () => { - const createProviderResult = await checkout.createProvider({ walletProviderName: WalletProviderName.METAMASK }); + const createProviderResult = await checkout.createProvider({ + walletProviderName: WalletProviderName.METAMASK, + }); - const connectResult = await checkout.connect({ provider: createProviderResult.provider }); + const connectResult = await checkout.connect({ + provider: createProviderResult.provider, + }); checkoutDispatch({ payload: { @@ -75,7 +141,6 @@ export default function CheckoutWidget(props: CheckoutWidgetInputs) { return ( - CheckoutWidgetComponent ); diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidgetEvents.ts b/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidgetEvents.ts new file mode 100644 index 0000000000..580affecc4 --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidgetEvents.ts @@ -0,0 +1,37 @@ +import { + WidgetEvent, + CheckoutEventType, + IMTBLWidgetEvents, + WidgetType, +} from '@imtbl/checkout-sdk'; + +export const sendCheckoutReadyEvent = (eventTarget: Window | EventTarget) => { + const event = new CustomEvent< + WidgetEvent + >(IMTBLWidgetEvents.IMTBL_CHECKOUT_WIDGET_EVENT, { + detail: { + type: CheckoutEventType.CHECKOUT_APP_READY, + data: {}, + }, + }); + // eslint-disable-next-line no-console + console.log('checkout app ready ', eventTarget, event); + if (eventTarget !== undefined) eventTarget.dispatchEvent(event); +}; + +export const sendCheckoutEvent = ( + eventTarget: Window | EventTarget, + data: Record, +) => { + const event = new CustomEvent< + WidgetEvent + >(IMTBLWidgetEvents.IMTBL_CHECKOUT_WIDGET_EVENT, { + detail: { + type: CheckoutEventType.CHECKOUT_APP_EVENT, + data, + }, + }); + // eslint-disable-next-line no-console + console.log('checkout app event ', eventTarget, event); + if (eventTarget !== undefined) eventTarget.dispatchEvent(event); +}; diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/functions/iframeParams.ts b/packages/checkout/widgets-lib/src/widgets/checkout/functions/iframeParams.ts index f421e4ab3e..86c3339c4d 100644 --- a/packages/checkout/widgets-lib/src/widgets/checkout/functions/iframeParams.ts +++ b/packages/checkout/widgets-lib/src/widgets/checkout/functions/iframeParams.ts @@ -10,6 +10,13 @@ import { CHECKOUT_APP_URL } from '../../../lib/constants'; const toQueryString = (params: Record): string => { const sanitizedParams = Object.entries(params) .filter(([, value]) => value !== undefined) + .map(([key, value]) => { + const safeValue = Array.isArray(value) || typeof value === 'object' + ? JSON.stringify(value) + : value; + + return [key, safeValue]; + }) .map(([key, value]) => [key, value] as [string, string]); return new URLSearchParams(sanitizedParams).toString(); @@ -121,14 +128,14 @@ export const getIframeURL = ( config: CheckoutWidgetConfiguration, environment: Environment, publishableKey: string, -): string => { +) => { const { flow } = params; - const language = params.language || config.language; - // const baseUrl = 'http://localhost:3001'; - const baseUrl = CHECKOUT_APP_URL[environment]; + const baseURL = CHECKOUT_APP_URL[environment]; const queryParams = getIframeParams(params, config); - return `${baseUrl}/${publishableKey}/${language}/${flow}?${queryParams}`; + const iframeURL = `${baseURL}/${publishableKey}/${language}/${flow}?${queryParams}`; + + return [baseURL, iframeURL]; }; diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/views/CheckoutAppIframe.tsx b/packages/checkout/widgets-lib/src/widgets/checkout/views/CheckoutAppIframe.tsx index 116cf9864c..e335ed1250 100644 --- a/packages/checkout/widgets-lib/src/widgets/checkout/views/CheckoutAppIframe.tsx +++ b/packages/checkout/widgets-lib/src/widgets/checkout/views/CheckoutAppIframe.tsx @@ -1,7 +1,5 @@ import { Box } from '@biom3/react'; -import { - useRef, -} from 'react'; +import { useRef } from 'react'; import { CheckoutActions } from '../context/CheckoutContext'; import { useCheckoutContext } from '../context/CheckoutContextProvider'; @@ -13,9 +11,7 @@ export interface LoadingHandoverProps { } export function CheckoutAppIframe() { const [checkoutState, checkoutDispatch] = useCheckoutContext(); - const { - iframeUrl, - } = checkoutState; + const { iframeUrl } = checkoutState; const iframeRef = useRef(null); const onIframeLoad = () => { @@ -39,10 +35,11 @@ export function CheckoutAppIframe() { )} sx={{ diff --git a/packages/checkout/widgets-sample-app/src/components/ui/checkout/checkout.tsx b/packages/checkout/widgets-sample-app/src/components/ui/checkout/checkout.tsx index ffdfe5b644..aec3fb9ab7 100644 --- a/packages/checkout/widgets-sample-app/src/components/ui/checkout/checkout.tsx +++ b/packages/checkout/widgets-sample-app/src/components/ui/checkout/checkout.tsx @@ -7,6 +7,7 @@ import { WidgetType, WalletProviderName, SalePaymentTypes, + CheckoutEventType } from "@imtbl/checkout-sdk"; import { Environment } from "@imtbl/config"; import { WidgetsFactory } from "@imtbl/checkout-widgets"; @@ -47,6 +48,19 @@ function CheckoutUI() { useEffect(() => { passport.connectEvm(); }, []); + + useEffect(() => { + if (!checkoutWidget) return; + + checkoutWidget.addListener(CheckoutEventType.CHECKOUT_APP_READY, (data) => { + console.log('----------> CHECKOUT_APP_READY', data); + }); + + checkoutWidget.addListener(CheckoutEventType.CHECKOUT_APP_EVENT, (data) => { + console.log('----------> CHECKOUT_APP_EVENT', data); + }); + + }, [checkoutWidget ]); return (
@@ -62,7 +76,7 @@ function CheckoutUI() { onClick={() => { checkoutWidget.mount("checkout", { flow: CheckoutFlowType.CONNECT, - blocklistWalletRdns: [], + // blocklistWalletRdns: ["io.metamask"], }); }} > @@ -127,7 +141,7 @@ function CheckoutUI() { }, ], environmentId: "4dfc4bec-1867-49aa-ad35-d8a13b206c94", - collectionName: "Metalcoree", + collectionName: "Lootboxes", excludePaymentTypes: [SalePaymentTypes.CREDIT], preferredCurrency: "USDC", });