diff --git a/apps/cowswap-frontend/src/common/hooks/useAnalyticsReporter.ts b/apps/cowswap-frontend/src/common/hooks/useAnalyticsReporter.ts index 6a657b002c..d869f2ab4f 100644 --- a/apps/cowswap-frontend/src/common/hooks/useAnalyticsReporter.ts +++ b/apps/cowswap-frontend/src/common/hooks/useAnalyticsReporter.ts @@ -1,4 +1,4 @@ -import { useEffect, useMemo } from 'react' +import { useEffect } from 'react' import { Dimensions, @@ -76,10 +76,7 @@ export function useAnalyticsReporter() { const walletName = _walletName || getConnectionName(connection.type, isMetaMask) - const injectedWidgetAppId = useMemo( - () => (injectedWidgetMetaData ? `${injectedWidgetMetaData.appKey}:${injectedWidgetMetaData.url}` : ''), - [injectedWidgetMetaData] - ) + const injectedWidgetAppId = injectedWidgetMetaData.appKey useEffect(() => { // Custom dimension 2 - walletname diff --git a/apps/cowswap-frontend/src/modules/appData/hooks.ts b/apps/cowswap-frontend/src/modules/appData/hooks.ts index 560a13dbc9..c4fecb66cd 100644 --- a/apps/cowswap-frontend/src/modules/appData/hooks.ts +++ b/apps/cowswap-frontend/src/modules/appData/hooks.ts @@ -22,7 +22,7 @@ export function useAppCode(): string | null { return useMemo(() => { if (isInjectedWidget()) { - return injectedWidgetMetaData?.appKey || null + return injectedWidgetMetaData.appKey } if (APP_CODE) { diff --git a/apps/cowswap-frontend/src/modules/injectedWidget/hooks/useInjectedWidgetMetaData.ts b/apps/cowswap-frontend/src/modules/injectedWidget/hooks/useInjectedWidgetMetaData.ts index 216eb2d8c3..d17e901912 100644 --- a/apps/cowswap-frontend/src/modules/injectedWidget/hooks/useInjectedWidgetMetaData.ts +++ b/apps/cowswap-frontend/src/modules/injectedWidget/hooks/useInjectedWidgetMetaData.ts @@ -1,7 +1,7 @@ import { useAtomValue } from 'jotai' -import { InjectedWidgetMetaData, injectedWidgetMetaDataAtom } from '../state/injectedWidgetMetaDataAtom' +import { CowSwapWidgetMetaData, injectedWidgetMetaDataAtom } from '../state/injectedWidgetMetaDataAtom' -export function useInjectedWidgetMetaData(): InjectedWidgetMetaData | null { +export function useInjectedWidgetMetaData(): CowSwapWidgetMetaData { return useAtomValue(injectedWidgetMetaDataAtom) } diff --git a/apps/cowswap-frontend/src/modules/injectedWidget/hooks/useInjectedWidgetParams.ts b/apps/cowswap-frontend/src/modules/injectedWidget/hooks/useInjectedWidgetParams.ts index 589c43e5b0..1fb13cb514 100644 --- a/apps/cowswap-frontend/src/modules/injectedWidget/hooks/useInjectedWidgetParams.ts +++ b/apps/cowswap-frontend/src/modules/injectedWidget/hooks/useInjectedWidgetParams.ts @@ -1,9 +1,9 @@ import { useAtomValue } from 'jotai' -import type { CowSwapWidgetAppParams } from '@cowprotocol/widget-lib' +import type { CowSwapWidgetParams } from '@cowprotocol/widget-lib' import { injectedWidgetParamsAtom } from '../state/injectedWidgetParamsAtom' -export function useInjectedWidgetParams(): CowSwapWidgetAppParams { +export function useInjectedWidgetParams(): CowSwapWidgetParams { return useAtomValue(injectedWidgetParamsAtom) } diff --git a/apps/cowswap-frontend/src/modules/injectedWidget/state/injectedWidgetMetaDataAtom.ts b/apps/cowswap-frontend/src/modules/injectedWidget/state/injectedWidgetMetaDataAtom.ts index a03015f207..710ba8b1fd 100644 --- a/apps/cowswap-frontend/src/modules/injectedWidget/state/injectedWidgetMetaDataAtom.ts +++ b/apps/cowswap-frontend/src/modules/injectedWidget/state/injectedWidgetMetaDataAtom.ts @@ -1,8 +1,11 @@ import { atom } from 'jotai' -export interface InjectedWidgetMetaData { +export interface CowSwapWidgetMetaData { appKey: string - url: string } -export const injectedWidgetMetaDataAtom = atom(null) +const DEFAULT_INJECTED_WIDGET_META_DATA: CowSwapWidgetMetaData = { + appKey: 'DEFAULT_INJECTED_WIDGET', +} + +export const injectedWidgetMetaDataAtom = atom(DEFAULT_INJECTED_WIDGET_META_DATA) diff --git a/apps/cowswap-frontend/src/modules/injectedWidget/state/injectedWidgetParamsAtom.ts b/apps/cowswap-frontend/src/modules/injectedWidget/state/injectedWidgetParamsAtom.ts index 444e9e07df..666179a84b 100644 --- a/apps/cowswap-frontend/src/modules/injectedWidget/state/injectedWidgetParamsAtom.ts +++ b/apps/cowswap-frontend/src/modules/injectedWidget/state/injectedWidgetParamsAtom.ts @@ -1,5 +1,5 @@ import { atom } from 'jotai' -import type { CowSwapWidgetAppParams } from '@cowprotocol/widget-lib' +import type { CowSwapWidgetParams } from '@cowprotocol/widget-lib' -export const injectedWidgetParamsAtom = atom({}) +export const injectedWidgetParamsAtom = atom({}) diff --git a/apps/cowswap-frontend/src/modules/injectedWidget/updaters/IframeResizer.ts b/apps/cowswap-frontend/src/modules/injectedWidget/updaters/IframeResizer.ts index e01c11951d..30671d93b2 100644 --- a/apps/cowswap-frontend/src/modules/injectedWidget/updaters/IframeResizer.ts +++ b/apps/cowswap-frontend/src/modules/injectedWidget/updaters/IframeResizer.ts @@ -1,17 +1,13 @@ import { useLayoutEffect, useRef } from 'react' 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) useLayoutEffect(() => { - if (!dynamicHeightEnabled) return - // Initial height calculation and message const sendHeightUpdate = () => { const contentHeight = document.body.scrollHeight @@ -37,7 +33,7 @@ export function IframeResizer() { return () => { observer.disconnect() } - }, [dynamicHeightEnabled]) + }, []) return null } diff --git a/apps/cowswap-frontend/src/modules/injectedWidget/updaters/InjectedWidgetUpdater.tsx b/apps/cowswap-frontend/src/modules/injectedWidget/updaters/InjectedWidgetUpdater.tsx index 5b6dd2d81d..ccc59303f5 100644 --- a/apps/cowswap-frontend/src/modules/injectedWidget/updaters/InjectedWidgetUpdater.tsx +++ b/apps/cowswap-frontend/src/modules/injectedWidget/updaters/InjectedWidgetUpdater.tsx @@ -59,7 +59,7 @@ export function InjectedWidgetUpdater() { navigate(data.urlParams) } - if (method === 'metaData') { + if (method === 'metaData' && data.metaData) { updateMetaData(data.metaData) } }, diff --git a/apps/widget-configurator/src/app/configurator/embedDialog.tsx b/apps/widget-configurator/src/app/configurator/embedDialog.tsx index 2f07ff7b60..20f0d2d486 100644 --- a/apps/widget-configurator/src/app/configurator/embedDialog.tsx +++ b/apps/widget-configurator/src/app/configurator/embedDialog.tsx @@ -13,10 +13,9 @@ import { nightOwl } from 'react-syntax-highlighter/dist/esm/styles/hljs' export interface EmbedDialogProps { params: CowSwapWidgetProps['params'] - settings: CowSwapWidgetProps['settings'] } -export function EmbedDialog({ params, settings }: EmbedDialogProps) { +export function EmbedDialog({ params }: EmbedDialogProps) { const [open, setOpen] = useState(false) const [scroll, setScroll] = useState('paper') @@ -42,18 +41,17 @@ export function EmbedDialog({ params, settings }: EmbedDialogProps) { const paramsSanitized = { ...params, - container: ``, provider: ``, } const code = ` -import { CowSwapWidgetParams, CowSwapWidgetSettings, cowSwapWidget } from '@cowprotocol/widget-lib' +import { CowSwapWidgetParams, cowSwapWidget } from '@cowprotocol/widget-lib' -const params: CowSwapWidgetParams = ${JSON.stringify(paramsSanitized, null, 4)} +const container = document.getElementById('') -const settings: CowSwapWidgetSettings = ${JSON.stringify(settings, null, 4)} +const params: CowSwapWidgetParams = ${JSON.stringify(paramsSanitized, null, 4)} -const updateWidget = cowSwapWidget(params, settings) +const updateWidget = cowSwapWidget(container, params) ` const handleCopy = () => { diff --git a/apps/widget-configurator/src/app/configurator/hooks/useWidgetParamsAndSettings.ts b/apps/widget-configurator/src/app/configurator/hooks/useWidgetParamsAndSettings.ts index 4c7e3ebe41..2a29c785b6 100644 --- a/apps/widget-configurator/src/app/configurator/hooks/useWidgetParamsAndSettings.ts +++ b/apps/widget-configurator/src/app/configurator/hooks/useWidgetParamsAndSettings.ts @@ -28,17 +28,13 @@ export function useWidgetParamsAndSettings( sellTokenAmount, buyToken, buyTokenAmount, - dynamicHeightEnabled, } = configuratorState const params: CowSwapWidgetProps['params'] = { - metaData: { appKey: '', url: '' }, - width: 400, - height: 640, + appKey: '', + width: '400px', + height: '640px', provider, - } - - const settings: CowSwapWidgetProps['settings'] = { theme, chainId, env: getEnv(), @@ -47,16 +43,15 @@ export function useWidgetParamsAndSettings( sell: { asset: sellToken, amount: sellTokenAmount ? sellTokenAmount.toString() : undefined }, buy: { asset: buyToken, amount: buyTokenAmount?.toString() }, }, - dynamicHeightEnabled, enabledTradeTypes, // palette: { // primaryColor: '#d9258e', - // screenBackground: '#c7860f', - // widgetBackground: '#eed4a7', - // textColor: '#413931', + // screenBackground: '#ee00cd', + // widgetBackground: '#b900ff', + // textColor: '#b348cc', // }, } - return { params, settings } + return params }, [provider, configuratorState]) } diff --git a/apps/widget-configurator/src/app/configurator/index.tsx b/apps/widget-configurator/src/app/configurator/index.tsx index 615b6c12a1..00eeadf297 100644 --- a/apps/widget-configurator/src/app/configurator/index.tsx +++ b/apps/widget-configurator/src/app/configurator/index.tsx @@ -66,11 +66,9 @@ export function Configurator({ title }: { title: string }) { sellTokenAmount, buyToken, buyTokenAmount, - dynamicHeightEnabled: true, } - const paramsAndSettings = useWidgetParamsAndSettings(provider, state) - const { params, settings } = paramsAndSettings || {} + const params = useWidgetParamsAndSettings(provider, state) useEffect(() => { web3Modal.setThemeMode(mode) @@ -117,11 +115,11 @@ export function Configurator({ title }: { title: string }) { - {params && settings && ( + {params && ( <> - +
- + )}
diff --git a/apps/widget-configurator/src/app/configurator/types.ts b/apps/widget-configurator/src/app/configurator/types.ts index c4838961c3..d925c18e15 100644 --- a/apps/widget-configurator/src/app/configurator/types.ts +++ b/apps/widget-configurator/src/app/configurator/types.ts @@ -12,5 +12,4 @@ export interface ConfiguratorState { sellTokenAmount: number | undefined buyToken: string buyTokenAmount: number | undefined - dynamicHeightEnabled: boolean } diff --git a/libs/widget-lib/README.md b/libs/widget-lib/README.md index f7b12d0337..5d6f517294 100644 --- a/libs/widget-lib/README.md +++ b/libs/widget-lib/README.md @@ -31,29 +31,27 @@ Create a container somewhere in your website, the widget will be rendered inside Import the widget and initialise it: ```js -import { cowSwapWidget } from '@cowprotocol/widget-lib' +import { cowSwapWidget, CowSwapWidgetParams } from '@cowprotocol/widget-lib' // Initialise the widget const widgetContainer = document.getElementById('cowswap-widget') -const updateWidget = cowSwapWidget({ - container: widgetContainer, - width: 600, - height: 640, - metaData: { - appKey: '', // Just an unique identifier for your app, - appUrl: '' - }, -// Optionally, you can provide some additional params to customise your widget -}, { +const params: CowSwapWidgetParams = { + appKey: '', // Just an unique identifier for your app sell: { asset: 'DAI' }, buy: { asset: 'USDC', amount: '0.1' }, // instantiate your own web3 provider provider: window.ethereum -}) +} + +const updateWidget = cowSwapWidget( + widgetContainer, + // Optionally, you can provide some additional params to customise your widget + params +) // You also can change widget configuration on the fly -updateWidget({ tradeType: 'limit' }) +updateWidget({ ...params, tradeType: 'limit' }) ``` ## Developers diff --git a/libs/widget-lib/demo/vanilla.html b/libs/widget-lib/demo/vanilla.html index 2ef73239f9..0136ed05bb 100644 --- a/libs/widget-lib/demo/vanilla.html +++ b/libs/widget-lib/demo/vanilla.html @@ -3,22 +3,12 @@ CoWSwap Widget demo - +
diff --git a/libs/widget-lib/docs/README.md b/libs/widget-lib/docs/README.md index 668d8f67e6..6e4cf11322 100644 --- a/libs/widget-lib/docs/README.md +++ b/libs/widget-lib/docs/README.md @@ -21,30 +21,29 @@ npm install @cowprotocol/widget-lib ## Quick start ```typescript -import {cowSwapWidget, CowSwapWidgetParams, CowSwapWidgetSettings} from '@cowprotocol/widget-lib' +import {cowSwapWidget, CowSwapWidgetParams} from '@cowprotocol/widget-lib' -// Initialise the widget +// HTML element where the widget will be rendered const widgetContainer = document.getElementById('cowswap-widget') const params: CowSwapWidgetParams = { - container: widgetContainer, - metaData: {appKey: 'YOUR_APP_ID', url: 'https://YOUR_APP_URL'}, + appKey: 'YOUR_APP_ID', width: 600, height: 640, -} - -const settings: CowSwapWidgetSettings = { sell: {asset: 'DAI'}, buy: {asset: 'USDC', amount: '0.1'} } -cowSwapWidget(params, settings) +cowSwapWidget(widgetContainer, params) ``` ## App key -You must specify the `appKey` parameter when initializing the widget. This parameter is used to identify the source of orders. + +You must specify the `appKey` parameter when initializing the widget. This parameter is used to identify the source of +orders. The key must be a UTF8 string of up to 50 chars. -It will be a part of orders meta-data, see more in the [CoW Protocol Docs](https://docs.cow.fi/front-end/creating-app-ids/create-the-order-meta-data-file/appcode). +It will be a part of orders meta-data, see more in +the [CoW Protocol Docs](https://docs.cow.fi/front-end/creating-app-ids/create-the-order-meta-data-file/appcode). ## Wallet provider @@ -70,104 +69,61 @@ interface JsonRpcRequest { } ``` -An example of connecting a widget to Metamask: +An example of connecting a widget to Rabby Wallet or Metamask: ```typescript import {cowSwapWidget, CowSwapWidgetParams} from '@cowprotocol/widget-lib' -const params: CowSwapWidgetParams = { - container: document.getElementById('cowswap-widget'), - metaData: {appKey: 'YOUR_APP_ID', url: 'https://YOUR_APP_URL'}, - width: 600, - height: 640, - provider: window.ethereum // <------- -} - -cowSwapWidget(params, {}) +cowSwapWidget( + document.getElementById('cowswap-widget'), + { + provider: window.ethereum // <------- + } +) ``` ## Configuration ### `CowSwapWidgetParams` -| Parameter | Type | Description | -|-------------|-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------| -| `width` | `number` | The width of the widget in pixels. | -| `height` | `number` | The height of the widget in pixels. | -| `container` | `HTMLElement` | The container in which the widget will be displayed. | -| `metaData` | `CowSwapWidgetMetaData` | Information about the application in which the widget is embedded. This information will help identify the source of orders and requests from users. | -| `provider` | `EthereumProvider` | (Optional) The Ethereum provider to be used for interacting with a wallet. | - -### `CowSwapWidgetSettings` - -| Parameter | Type | Description | -|------------------------|------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `chainId` | `number` | The blockchain ID on which the trade will take place. | -| `tradeType` | `string` | The type of trade. Can be `swap` or `limit-orders`. | -| `env` | `CowSwapWidgetEnv` | The environment of the widget (`'local'` or `'prod'`). | -| `tradeAssets` | `TradeAssets` | (Optional) An object containing information about the selling and buying assets. | -| `theme` | `CowSwapTheme` | (Optional) The theme of the widget (`'dark'` for dark theme or `'light'` for light theme). | -| `logoUrl` | `boolean` | (Optional) The width of the widget in pixels. | -| `hideLogo` | `boolean` | (Optional) The height of the widget in pixels. | -| `hideNetworkSelector` | `boolean` | (Optional) Disables an opportunity to change the network from the widget UI. | -| `dynamicHeightEnabled` | `boolean` | (Optional) Dynamically changes the height of the iframe depending on the content. | -| `enabledTradeTypes` | `Array` | (Optional) CowSwap provides three trading widgets: swap, limit and twap orders. Using this option you can narrow down the list of available trading widgets. | -| `palette` | `CowSwapWidgetPalette` | (Optional) Using the palette you can customize the appearance of the widget. For example, you can change the main color of the background and text. | - -```typescript -export interface CowSwapWidgetMetaData { - appKey: string - url: string -} - -interface TradeAsset { - asset: string - amount?: string -} - -export interface TradeAssets { - sell: TradeAsset - buy: TradeAsset -} - -export enum TradeType { - SWAP = 'swap', - LIMIT = 'limit', - ADVANCED = 'advanced', -} - -export interface CowSwapWidgetPalette { - primaryColor: string - screenBackground: string - widgetBackground: string - textColor: string -} -``` +> All params are optional + +| Parameter | Type | Default | Description | +|-----------------------|------------------------|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `width` | `string` | 400px | The width of the widget in css values (px, vh, etc.). | +| `height` | `string` | 600px | The height of the widget in css values (px, vh, etc.). | +| `appKey` | `string` | 'DEFAULT_INJECTED_WIDGET' | The unique identifier of the widget consumer. Please fill the for to let us know a little about you: | +| `provider` | `EthereumProvider` | --- | The Ethereum provider to be used for interacting with a wallet. To connect, for example, to Rabby Wallet or Metamask, just set `window.ethereum`. You also might like to use https://web3modal.com | +| `chainId` | `number` | 1 | The blockchain ID on which the trade will take place. Currently supported: 1 (Mainnet), 5 (Goerli), 100 (Gnosis chain) | +| `tradeType` | `TradeType` | 'swap' | The type of trade. Can be `swap` or `limit` or `advanced`. | +| `env` | `CowSwapWidgetEnv` | 'prod' | The environment of the widget (`local` , `prod` , `dev` , `pr`). See [`COWSWAP_URLS`](../src/consts.ts) const value for urls. | +| `tradeAssets` | `TradeAssets` | Same as in swap.cow.fi | An object containing information about the selling and buying assets. Example: `{ asset: 'WBTC', amount: 12 }` or `{ asset: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' }` | +| `theme` | `CowSwapTheme` | 'light' | The theme of the widget (`'dark'` for dark theme or `'light'` for light theme). | +| `logoUrl` | `string` | --- | Allows to set a custom logo for the widget. | +| `hideLogo` | `boolean` | false | Option to hide the logo in the widget. | +| `hideNetworkSelector` | `boolean` | false | Disables an opportunity to change the network from the widget UI. | +| `enabledTradeTypes` | `Array` | All are enabled | CowSwap provides three trading widgets: `swap`, `limit` and `advanced` orders. Using this option you can narrow down the list of available trading widgets. | +| `palette` | `CowSwapWidgetPalette` | --- | Using the palette you can customize the appearance of the widget. For example, you can change the main color of the background and text. | ## Widget updating You can change all possible widget options on the fly: ```typescript -import { cowSwapWidget, CowSwapWidgetParams, CowSwapWidgetSettings } from '@cowprotocol/widget-lib' - -const params: CowSwapWidgetParams = { - container: document.getElementById('cowswap-widget'), - metaData: { appKey: 'YOUR_APP_ID', url: 'https://YOUR_APP_URL' }, - width: 600, - height: 640, -} +import {cowSwapWidget, CowSwapWidgetParams} from '@cowprotocol/widget-lib' +const container = document.getElementById('cowswap-widget') -const settings: CowSwapWidgetSettings = { +const params: CowSwapWidgetParams = { + appKey: 'YOUR_APP_ID', logoUrl: 'YOUR_LOGO_URL' } -const updateWidget = cowSwapWidget(params, settings) +const updateWidget = cowSwapWidget(container, params) // Update the widget updateWidget({ - ...settings, + ...params, theme: 'dark', // <- Change theme to dark hideNetworkSelector: true // <- Hide the network selector }) diff --git a/libs/widget-lib/src/JsonRpcManager.ts b/libs/widget-lib/src/JsonRpcManager.ts index 79d846979a..86371a851e 100644 --- a/libs/widget-lib/src/JsonRpcManager.ts +++ b/libs/widget-lib/src/JsonRpcManager.ts @@ -4,20 +4,36 @@ const JSON_PRC_V = '2.0' const TARGET_ORIGIN = '*' const EVENTS = ['connect', 'disconnect', 'close', 'chainChanged', 'accountsChanged'] +/** + * Manages JSON-RPC requests and interactions with an Ethereum provider. + */ export class JsonRpcManager { - ethereumProvider: EthereumProvider | null = null + /** The Ethereum provider instance. */ + private ethereumProvider: EthereumProvider | null = null - requests: { [key: string]: JsonRpcRequest } = {} + /** Stored JSON-RPC requests. */ + private requests: { [key: string]: JsonRpcRequest } = {} + /** + * Creates an instance of JsonRpcManager. + * @param contentWindow - The window for handling events. + */ constructor(private contentWindow: Window) { window.addEventListener('message', this.processEvent) } + /** + * Disconnects the JSON-RPC manager from the Ethereum provider. + */ disconnect() { this.ethereumProvider = null window.removeEventListener('message', this.processEvent) } + /** + * Handles the 'connect' event and sets up event listeners for Ethereum provider events. + * @param ethereumProvider - The Ethereum provider to connect. + */ onConnect(ethereumProvider: EthereumProvider) { this.ethereumProvider = ethereumProvider @@ -34,6 +50,10 @@ export class JsonRpcManager { }) } + /** + * Processes a JSON-RPC request and sends appropriate response or error via the content window. + * @param request - The JSON-RPC request to be processed. + */ processRequest(request: JsonRpcRequest) { if (!this.ethereumProvider) return diff --git a/libs/widget-lib/src/consts.ts b/libs/widget-lib/src/consts.ts index 49269e600c..9eb91d3a91 100644 --- a/libs/widget-lib/src/consts.ts +++ b/libs/widget-lib/src/consts.ts @@ -1,8 +1,14 @@ import { CowSwapWidgetEnv } from './types' +function getPrHostName(): string { + const prKey = location.hostname.replace('widget-configurator-git-', '').replace('-cowswap.vercel.app', '') + + return `https://swap-dev-git-${prKey}-cowswap.vercel.app` +} + export const COWSWAP_URLS: Record = { local: 'http://localhost:3000', prod: 'https://swap.cow.fi', dev: 'https://dev.swap.cow.fi', - pr: window.location.origin, + pr: getPrHostName(), } diff --git a/libs/widget-lib/src/cowSwapWidget.ts b/libs/widget-lib/src/cowSwapWidget.ts index ed3b8e8754..6ebe9a4be9 100644 --- a/libs/widget-lib/src/cowSwapWidget.ts +++ b/libs/widget-lib/src/cowSwapWidget.ts @@ -1,14 +1,29 @@ import { JsonRpcManager } from './JsonRpcManager' -import { CowSwapWidgetMetaData, CowSwapWidgetParams, CowSwapWidgetSettings } from './types' +import { CowSwapWidgetParams } from './types' import { buildTradeAmountsQuery, buildWidgetPath, buildWidgetUrl } from './urlUtils' +/** + * Key for identifying the event associated with the CoW Swap Widget. + */ const COW_SWAP_WIDGET_EVENT_KEY = 'cowSwapWidget' -export type UpdateWidgetCallback = (params: CowSwapWidgetSettings) => void +const DEFAULT_HEIGHT = '600px' +const DEFAULT_WIDTH = '400px' -export function cowSwapWidget(params: CowSwapWidgetParams, settings: CowSwapWidgetSettings): UpdateWidgetCallback { - const { container, provider } = params - const iframe = createIframe(params, settings) +/** + * Callback function signature for updating the CoW Swap Widget. + */ +export type UpdateWidgetCallback = (params: CowSwapWidgetParams) => void + +/** + * Generates and injects a CoW Swap Widget into the provided container. + * @param container - The HTML element to inject the widget into. + * @param params - Parameters for configuring the widget. + * @returns A callback function to update the widget with new settings. + */ +export function cowSwapWidget(container: HTMLElement, params: CowSwapWidgetParams = {}): UpdateWidgetCallback { + const { provider } = params + const iframe = createIframe(params) container.innerHTML = '' container.appendChild(iframe) @@ -17,7 +32,7 @@ export function cowSwapWidget(params: CowSwapWidgetParams, settings: CowSwapWidg if (!contentWindow) throw new Error('Iframe does not contain a window!') - sendMetaData(contentWindow, params.metaData) + sendAppKey(contentWindow, params.appKey) applyDynamicHeight(iframe, params.height) @@ -28,33 +43,38 @@ export function cowSwapWidget(params: CowSwapWidgetParams, settings: CowSwapWidg } iframe.addEventListener('load', () => { - updateWidget(settings, contentWindow, iframe) + updateWidget(params, contentWindow) }) - return (newSettings: CowSwapWidgetSettings) => updateWidget(newSettings, contentWindow, iframe) + return (newParams: CowSwapWidgetParams) => updateWidget(newParams, contentWindow) } -function createIframe(params: CowSwapWidgetParams, settings: CowSwapWidgetSettings): HTMLIFrameElement { - const { width, height } = params +/** + * Creates an iframe element for the CoW Swap Widget based on provided parameters and settings. + * @param params - Parameters for the widget. + * @returns The generated HTMLIFrameElement. + */ +function createIframe(params: CowSwapWidgetParams): HTMLIFrameElement { + const { width = DEFAULT_WIDTH, height = DEFAULT_HEIGHT } = params const iframe = document.createElement('iframe') - iframe.src = buildWidgetUrl(settings) - iframe.width = `${width}px` - iframe.height = `${height}px` + iframe.src = buildWidgetUrl(params) + iframe.width = width + iframe.height = height iframe.style.border = '0' return iframe } -function updateWidget(settings: CowSwapWidgetSettings, contentWindow: Window, iframe: HTMLIFrameElement) { - const pathname = buildWidgetPath(settings) - const search = buildTradeAmountsQuery(settings).toString() - - // Reset iframe height to default - if (!settings.dynamicHeightEnabled) { - iframe.style.height = '' - } +/** + * Updates the CoW Swap Widget based on the new settings provided. + * @param params - New params for the widget. + * @param contentWindow - Window object of the widget's iframe. + */ +function updateWidget(params: CowSwapWidgetParams, contentWindow: Window) { + const pathname = buildWidgetPath(params) + const search = buildTradeAmountsQuery(params).toString() contentWindow.postMessage( { @@ -64,13 +84,21 @@ function updateWidget(settings: CowSwapWidgetSettings, contentWindow: Window, if pathname, search, }, - appParams: settings, + appParams: { + ...params, + provider: undefined, + }, }, '*' ) } -function sendMetaData(contentWindow: Window, metaData: CowSwapWidgetMetaData) { +/** + * Sends appKey to the contentWindow of the widget. + * @param contentWindow - Window object of the widget's iframe. + * @param appKey - A unique identifier for the app. + */ +function sendAppKey(contentWindow: Window, appKey: string | undefined) { window.addEventListener('message', (event) => { if (event.data.key !== COW_SWAP_WIDGET_EVENT_KEY || event.data.method !== 'activate') { return @@ -80,21 +108,24 @@ function sendMetaData(contentWindow: Window, metaData: CowSwapWidgetMetaData) { { key: COW_SWAP_WIDGET_EVENT_KEY, method: 'metaData', - metaData, + metaData: appKey ? { appKey } : undefined, }, '*' ) }) } -function applyDynamicHeight(iframe: HTMLIFrameElement, defaultHeight: number) { +/** + * Applies dynamic height adjustments to the widget's iframe. + * @param iframe - The HTMLIFrameElement of the widget. + * @param defaultHeight - Default height for the widget. + */ +function applyDynamicHeight(iframe: HTMLIFrameElement, defaultHeight = DEFAULT_HEIGHT) { 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` + iframe.style.height = event.data.height ? `${event.data.height}px` : defaultHeight }) } diff --git a/libs/widget-lib/src/types.ts b/libs/widget-lib/src/types.ts index 3ff6b4e030..8dced6fd38 100644 --- a/libs/widget-lib/src/types.ts +++ b/libs/widget-lib/src/types.ts @@ -1,3 +1,5 @@ +import type { SupportedChainId } from '@cowprotocol/cow-sdk' + export interface JsonRpcRequest { id: number method: string @@ -6,8 +8,24 @@ export interface JsonRpcRequest { // https://eips.ethereum.org/EIPS/eip-1193 export interface EthereumProvider { + /** + * Subscribes to Ethereum-related events. + * @param event - The event to subscribe to. + * @param args - Arguments for the event. + */ on(event: string, args: unknown): void + + /** + * Sends a JSON-RPC request to the Ethereum provider and returns the response. + * @param params - JSON-RPC request parameters. + * @returns A promise that resolves with the response. + */ request(params: JsonRpcRequest): Promise + + /** + * Requests permission to connect to the Ethereum provider. + * @returns A promise that resolves once permission is granted. + */ enable(): Promise } @@ -15,11 +33,25 @@ export type CowSwapWidgetEnv = 'local' | 'prod' | 'dev' | 'pr' export type CowSwapTheme = 'dark' | 'light' +/** + *Trade asset parameters, for example: + * { asset: 'WBTC', amount: 12 } + * or + * { asset: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' } // USDC + */ interface TradeAsset { + /** The asset symbol or identifier. */ asset: string + /** + * The amount of the asset (optional). + * If specified, represents the quantity or value of the asset. + */ amount?: string } +/** + * A pair of assets to trade. + */ export interface TradeAssets { sell: TradeAsset buy: TradeAsset @@ -28,17 +60,13 @@ export interface TradeAssets { export enum TradeType { SWAP = 'swap', LIMIT = 'limit', + /** + * Currently it means only TWAP orders. + * But in the future it can be extended to support other order types. + */ ADVANCED = 'advanced', } -export interface CowSwapWidgetUrlParams { - chainId?: number - tradeType?: TradeType - env?: CowSwapWidgetEnv - tradeAssets?: TradeAssets - theme?: CowSwapTheme -} - export interface CowSwapWidgetPalette { primaryColor: string screenBackground: string @@ -46,26 +74,66 @@ export interface CowSwapWidgetPalette { textColor: string } -export interface CowSwapWidgetAppParams { - logoUrl?: string - hideLogo?: boolean - hideNetworkSelector?: boolean - dynamicHeightEnabled?: boolean - enabledTradeTypes?: TradeType[] - palette?: CowSwapWidgetPalette -} +interface CowSwapWidgetConfig { + /** + * The width of the widget in pixels. Default: 400px + */ + width: string + /** + * The height of the widget in pixels. Default: 600px + */ + height: string + /** + * The unique identifier of the widget consumer. + * Please fill the for to let us know a little about you: + */ + appKey: string + /** + * The widget might be connected to a custom Ethereum provider. + */ + provider: EthereumProvider -export type CowSwapWidgetSettings = CowSwapWidgetUrlParams & CowSwapWidgetAppParams + /** + * Network ID. + */ + chainId: SupportedChainId + /** + * Swap, Limit or Advanced (Twap). + */ + tradeType: TradeType + /** + * The environment of the widget. Default: prod + */ + env: CowSwapWidgetEnv + /** + * The assets to trade. + */ + tradeAssets: TradeAssets + /** + * The theme of the widget UI. + */ + theme: CowSwapTheme -export interface CowSwapWidgetMetaData { - appKey: string - url: string + /** + * Allows to set a custom logo for the widget. + */ + logoUrl: string + /** + * Option to hide the logo in the widget. + */ + hideLogo: boolean + /** + * Option to hide the network selector in the widget. + */ + hideNetworkSelector: boolean + /** + * Enables the ability to switch between trade types in the widget. + */ + enabledTradeTypes: TradeType[] + /** + * Colors palette to customize the widget UI. + */ + palette: CowSwapWidgetPalette } -export interface CowSwapWidgetParams { - width: number - height: number - container: HTMLElement - metaData: CowSwapWidgetMetaData - provider?: EthereumProvider -} +export type CowSwapWidgetParams = Partial diff --git a/libs/widget-lib/src/urlUtils.ts b/libs/widget-lib/src/urlUtils.ts index 2277544262..9669910eac 100644 --- a/libs/widget-lib/src/urlUtils.ts +++ b/libs/widget-lib/src/urlUtils.ts @@ -1,7 +1,7 @@ import { COWSWAP_URLS } from './consts' -import { CowSwapWidgetUrlParams, TradeType } from './types' +import { CowSwapWidgetParams, TradeType } from './types' -export function buildWidgetUrl(params: CowSwapWidgetUrlParams): string { +export function buildWidgetUrl(params: CowSwapWidgetParams): string { const host = COWSWAP_URLS[params.env || 'prod'] const path = buildWidgetPath(params) const query = buildTradeAmountsQuery(params) @@ -9,7 +9,7 @@ export function buildWidgetUrl(params: CowSwapWidgetUrlParams): string { return host + '/#' + path + '?' + query } -export function buildWidgetPath(params: CowSwapWidgetUrlParams): string { +export function buildWidgetPath(params: CowSwapWidgetParams): string { const { chainId = 1, tradeAssets, tradeType = TradeType.SWAP } = params const assetsPath = tradeAssets @@ -19,7 +19,7 @@ export function buildWidgetPath(params: CowSwapWidgetUrlParams): string { return `/${chainId}/widget/${tradeType}/${assetsPath}` } -export function buildTradeAmountsQuery(params: CowSwapWidgetUrlParams): URLSearchParams { +export function buildTradeAmountsQuery(params: CowSwapWidgetParams): URLSearchParams { const { tradeAssets, theme } = params const query = new URLSearchParams() diff --git a/libs/widget-react/README.md b/libs/widget-react/README.md index d5b32025bb..0913499646 100644 --- a/libs/widget-react/README.md +++ b/libs/widget-react/README.md @@ -17,23 +17,16 @@ yarn add @cowprotocol/widget-react Import component and some convenient types ```ts -import { CowSwapWidget, CowSwapWidgetParams, CowSwapWidgetSettings } from '@cowprotocol/widget-react' +import { CowSwapWidget, CowSwapWidgetParams } from '@cowprotocol/widget-react' ``` Prepare the config for the widget: ```ts const cowSwapWidgetParams: CowSwapWidgetParams = { - container: document.getElementById('cow-swap-widget'), - metaData: { - appKey: '', - appUrl: '', - }, - width: 600, - height: 700, -} - -const cowSwapWidgetSettings: CowSwapWidgetSettings = { + appKey: '', + width: '600px', + height: '700px', tradeType: 'swap', } ``` @@ -41,7 +34,7 @@ const cowSwapWidgetSettings: CowSwapWidgetSettings = { Render the component: ```jsx - + ``` ## Developers diff --git a/libs/widget-react/src/lib/CowSwapWidget.tsx b/libs/widget-react/src/lib/CowSwapWidget.tsx index 4c747ffe01..1d17345976 100644 --- a/libs/widget-react/src/lib/CowSwapWidget.tsx +++ b/libs/widget-react/src/lib/CowSwapWidget.tsx @@ -1,20 +1,13 @@ import { useEffect, useRef } from 'react' -import { - cowSwapWidget, - CowSwapWidgetParams, - CowSwapWidgetSettings, - EthereumProvider, - UpdateWidgetCallback, -} from '@cowprotocol/widget-lib' +import { cowSwapWidget, CowSwapWidgetParams, EthereumProvider, UpdateWidgetCallback } from '@cowprotocol/widget-lib' export interface CowSwapWidgetProps { - params: Omit - settings: CowSwapWidgetSettings + params: CowSwapWidgetParams provider?: EthereumProvider } -export function CowSwapWidget({ params, settings, provider }: CowSwapWidgetProps) { +export function CowSwapWidget({ params, provider }: CowSwapWidgetProps) { const providerRef = useRef() const iframeContainerRef = useRef(null) const updateWidgetRef = useRef(null) @@ -28,11 +21,11 @@ export function CowSwapWidget({ params, settings, provider }: CowSwapWidgetProps } if (updateWidgetRef.current) { - updateWidgetRef.current(settings) + updateWidgetRef.current(params) } else { - updateWidgetRef.current = cowSwapWidget({ ...params, container: iframeContainerRef.current }, settings) + updateWidgetRef.current = cowSwapWidget(iframeContainerRef.current, params) } - }, [provider, params, settings]) + }, [provider, params]) useEffect(() => { providerRef.current = provider