diff --git a/apps/cowswap-frontend/src/cow-react/index.tsx b/apps/cowswap-frontend/src/cow-react/index.tsx index 89d4a1da26..eb78e960c7 100644 --- a/apps/cowswap-frontend/src/cow-react/index.tsx +++ b/apps/cowswap-frontend/src/cow-react/index.tsx @@ -9,7 +9,6 @@ import { StrictMode } from 'react' import { BlockNumberProvider } from '@cowprotocol/common-hooks' import { isInjectedWidget } from '@cowprotocol/common-utils' import { nodeRemoveChildFix } from '@cowprotocol/common-utils' -import { IframeResizer } from '@cowprotocol/common-utils' import { jotaiStore } from '@cowprotocol/core' import { SnackbarsWidget } from '@cowprotocol/snackbars' @@ -80,8 +79,6 @@ root.render( - - {isInjectedWidgetMode && } ) diff --git a/apps/cowswap-frontend/src/modules/injectedWidget/consts.ts b/apps/cowswap-frontend/src/modules/injectedWidget/consts.ts new file mode 100644 index 0000000000..798cb1bb2a --- /dev/null +++ b/apps/cowswap-frontend/src/modules/injectedWidget/consts.ts @@ -0,0 +1 @@ +export const COW_SWAP_WIDGET_EVENT_KEY = 'cowSwapWidget' diff --git a/libs/common-utils/src/iframeResizer.ts b/apps/cowswap-frontend/src/modules/injectedWidget/updaters/IframeResizer.ts similarity index 69% rename from libs/common-utils/src/iframeResizer.ts rename to apps/cowswap-frontend/src/modules/injectedWidget/updaters/IframeResizer.ts index f75abca72d..f9d70003f4 100644 --- a/libs/common-utils/src/iframeResizer.ts +++ b/apps/cowswap-frontend/src/modules/injectedWidget/updaters/IframeResizer.ts @@ -1,20 +1,25 @@ import { useEffect, useRef } from 'react' -const MessageType = { - IFRAME_HEIGHT: 'iframeHeight', -} +import { COW_SWAP_WIDGET_EVENT_KEY } from '../consts' +import { useInjectedWidgetParams } from '../hooks/useInjectedWidgetParams' const TARGET_ORIGIN = '*' // Change to CoW specific origin in production export function IframeResizer() { + const { dynamicHeightEnabled } = useInjectedWidgetParams() const previousHeightRef = useRef(0) useEffect(() => { + if (!dynamicHeightEnabled) return + // Initial height calculation and message const sendHeightUpdate = () => { const contentHeight = document.body.scrollHeight if (contentHeight !== previousHeightRef.current) { - window.parent.postMessage({ type: MessageType.IFRAME_HEIGHT, height: contentHeight }, TARGET_ORIGIN) + window.parent.postMessage( + { key: COW_SWAP_WIDGET_EVENT_KEY, method: 'iframeHeight', height: contentHeight }, + TARGET_ORIGIN + ) previousHeightRef.current = contentHeight } } @@ -32,7 +37,7 @@ export function IframeResizer() { return () => { observer.disconnect() } - }, []) + }, [dynamicHeightEnabled]) return null } diff --git a/apps/cowswap-frontend/src/modules/injectedWidget/updaters/InjectedWidgetUpdater.ts b/apps/cowswap-frontend/src/modules/injectedWidget/updaters/InjectedWidgetUpdater.tsx similarity index 94% rename from apps/cowswap-frontend/src/modules/injectedWidget/updaters/InjectedWidgetUpdater.ts rename to apps/cowswap-frontend/src/modules/injectedWidget/updaters/InjectedWidgetUpdater.tsx index 54c63dd2a7..5b6dd2d81d 100644 --- a/apps/cowswap-frontend/src/modules/injectedWidget/updaters/InjectedWidgetUpdater.ts +++ b/apps/cowswap-frontend/src/modules/injectedWidget/updaters/InjectedWidgetUpdater.tsx @@ -5,11 +5,12 @@ import { deepEqual } from '@cowprotocol/common-utils' import { useNavigate } from 'react-router-dom' +import { IframeResizer } from './IframeResizer' + +import { COW_SWAP_WIDGET_EVENT_KEY } from '../consts' import { injectedWidgetMetaDataAtom } from '../state/injectedWidgetMetaDataAtom' import { injectedWidgetParamsAtom } from '../state/injectedWidgetParamsAtom' -const COW_SWAP_WIDGET_EVENT_KEY = 'cowSwapWidget' - const messagesCache: { [method: string]: unknown } = {} const getEventMethod = (event: MessageEvent) => @@ -88,5 +89,5 @@ export function InjectedWidgetUpdater() { }) }, [processEvent]) - return null + return } diff --git a/apps/widget-configurator/src/app/configurator/attachIframeResizer.ts b/apps/widget-configurator/src/app/configurator/attachIframeResizer.ts deleted file mode 100644 index a0b1ce9d14..0000000000 --- a/apps/widget-configurator/src/app/configurator/attachIframeResizer.ts +++ /dev/null @@ -1,48 +0,0 @@ -// TODO: Move this to libs/common-utils/src/iframeResizer.ts as a helper function -// Adding the file here to avoid TS errors around environment variables - -import { useEffect } from 'react' - -interface IframeResizerProps { - iframeId: string - originCheck?: string -} - -// A utility function to adjust iframe height based on postMessage from iframe content. -export function AttachIframeResizer({ iframeId, originCheck }: IframeResizerProps) { - useEffect(() => { - const iframeElement = document.getElementById(iframeId) - - if (!iframeElement) { - console.error(`No iframe found with ID: ${iframeId}`) - return - } - - const handleMessage = (event: MessageEvent) => { - // If an originCheck is provided, validate against it - if (originCheck && event.origin !== originCheck) return - - const data = event.data - - // Validate the data format and type before processing it - if ( - typeof data === 'object' && - data.type === 'iframeHeight' && - Object.prototype.hasOwnProperty.call(data, 'height') - ) { - console.log('got iframeHeight ====>', data.height) - iframeElement.style.height = 'auto' // Reset the iframe's height - iframeElement.style.height = `${data.height}px` // Adjust the height based on the content - } - } - - window.addEventListener('message', handleMessage) - - // Cleanup: Remove the event listener when the component is unmounted - return () => { - window.removeEventListener('message', handleMessage) - } - }, [iframeId, originCheck]) - - return null -} diff --git a/apps/widget-configurator/src/app/configurator/index.tsx b/apps/widget-configurator/src/app/configurator/index.tsx index 153b3024d2..b5a65f0946 100644 --- a/apps/widget-configurator/src/app/configurator/index.tsx +++ b/apps/widget-configurator/src/app/configurator/index.tsx @@ -11,8 +11,10 @@ import { import WalletIcon from '@mui/icons-material/Wallet' import LoadingButton from '@mui/lab/LoadingButton' import Box from '@mui/material/Box' +import Checkbox from '@mui/material/Checkbox' import Divider from '@mui/material/Divider' import Drawer from '@mui/material/Drawer' +import FormControlLabel from '@mui/material/FormControlLabel' import Link from '@mui/material/Link' import Typography from '@mui/material/Typography' @@ -60,6 +62,8 @@ export function Configurator({ title }: { title: string }) { const iframeContainerRef = useRef(null) const updateWidgetRef = useRef(null) + const [isDynamicHeightEnabled, setDynamicHeightEnabled] = useState(false) + useEffect(() => { const widgetContainer = iframeContainerRef.current @@ -84,6 +88,7 @@ export function Configurator({ title }: { title: string }) { }, }, appParams: { + dynamicHeightEnabled: isDynamicHeightEnabled, enabledTradeTypes, }, } @@ -93,7 +98,17 @@ export function Configurator({ title }: { title: string }) { } else { updateWidgetRef.current = cowSwapWidget(params, settings) } - }, [chainId, enabledTradeTypes, sellToken, sellTokenAmount, buyToken, buyTokenAmount, mode, currentTradeType]) + }, [ + chainId, + enabledTradeTypes, + sellToken, + sellTokenAmount, + buyToken, + buyTokenAmount, + mode, + currentTradeType, + isDynamicHeightEnabled, + ]) const handleWidgetRefreshClick = () => { setMode('light') @@ -143,6 +158,15 @@ export function Configurator({ title }: { title: string }) { + Advanced + + setDynamicHeightEnabled(e.target.checked)} /> + } + label="Dynamic widget height" + /> + {/* } variant="contained" onClick={handleWidgetRefreshClick}> diff --git a/libs/common-utils/src/index.ts b/libs/common-utils/src/index.ts index 4a93f2bdbf..ef8fd97326 100644 --- a/libs/common-utils/src/index.ts +++ b/libs/common-utils/src/index.ts @@ -52,4 +52,3 @@ export * from './tryParseFractionalAmount' export * from './maxAmountSpend' export * from './capitalizeFirstLetter' export * from './jotai/atomWithPartialUpdate' -export * from './iframeResizer' diff --git a/libs/widget-lib/src/cowSwapWidget.ts b/libs/widget-lib/src/cowSwapWidget.ts index 66e6ad9f04..7f6140bb67 100644 --- a/libs/widget-lib/src/cowSwapWidget.ts +++ b/libs/widget-lib/src/cowSwapWidget.ts @@ -19,13 +19,15 @@ export function cowSwapWidget(params: CowSwapWidgetParams, settings: CowSwapWidg sendMetaData(contentWindow, params.metaData) + applyDynamicHeight(iframe, params.height) + if (provider) { const jsonRpcManager = new JsonRpcManager(contentWindow) jsonRpcManager.onConnect(provider) } - return (params: CowSwapWidgetSettings) => updateWidget(params, contentWindow) + return (newSettings: CowSwapWidgetSettings) => updateWidget(newSettings, contentWindow, iframe) } function createIframe(params: CowSwapWidgetParams, settings: CowSwapWidgetSettings): HTMLIFrameElement { @@ -41,9 +43,14 @@ function createIframe(params: CowSwapWidgetParams, settings: CowSwapWidgetSettin return iframe } -function updateWidget(params: CowSwapWidgetSettings, contentWindow: Window) { - const pathname = buildWidgetPath(params.urlParams) - const search = buildTradeAmountsQuery(params.urlParams).toString() +function updateWidget(settings: CowSwapWidgetSettings, contentWindow: Window, iframe: HTMLIFrameElement) { + const pathname = buildWidgetPath(settings.urlParams) + const search = buildTradeAmountsQuery(settings.urlParams).toString() + + // Reset iframe height to default + if (!settings.appParams.dynamicHeightEnabled) { + iframe.style.height = '' + } contentWindow.postMessage( { @@ -53,7 +60,7 @@ function updateWidget(params: CowSwapWidgetSettings, contentWindow: Window) { pathname, search, }, - appParams: params.appParams, + appParams: settings.appParams, }, '*' ) @@ -75,3 +82,15 @@ function sendMetaData(contentWindow: Window, metaData: CowSwapWidgetMetaData) { ) }) } + +function applyDynamicHeight(iframe: HTMLIFrameElement, defaultHeight: number) { + window.addEventListener('message', (event) => { + if (event.data.key !== COW_SWAP_WIDGET_EVENT_KEY || event.data.method !== 'iframeHeight') { + return + } + + const height = event.data.height || defaultHeight + + iframe.style.height = `${height}px` + }) +} diff --git a/libs/widget-lib/src/types.ts b/libs/widget-lib/src/types.ts index 760dd8e7ba..9dfac7a56a 100644 --- a/libs/widget-lib/src/types.ts +++ b/libs/widget-lib/src/types.ts @@ -43,6 +43,7 @@ export interface CowSwapWidgetAppParams { logoUrl?: string hideLogo?: boolean hideNetworkSelector?: boolean + dynamicHeightEnabled?: boolean enabledTradeTypes?: TradeType[] }