|
| 1 | +import { Center, chakra } from '@chakra-ui/react'; |
| 2 | +import { DappscoutIframeProvider, useDappscoutIframe } from 'dappscout-iframe'; |
| 3 | +import React, { useCallback, useEffect, useState, useMemo } from 'react'; |
| 4 | + |
| 5 | +import config from 'configs/app'; |
| 6 | +import essentialDappsChainsConfig from 'configs/essential-dapps-chains'; |
| 7 | +import useWeb3Wallet from 'lib/web3/useWallet'; |
| 8 | +import { ContentLoader } from 'toolkit/components/loaders/ContentLoader'; |
| 9 | + |
| 10 | +import useMarketplaceWallet from '../marketplace/useMarketplaceWallet'; |
| 11 | + |
| 12 | +const IFRAME_SANDBOX_ATTRIBUTE = 'allow-forms allow-orientation-lock ' + |
| 13 | +'allow-pointer-lock allow-popups-to-escape-sandbox ' + |
| 14 | +'allow-same-origin allow-scripts ' + |
| 15 | +'allow-top-navigation-by-user-activation allow-popups'; |
| 16 | + |
| 17 | +const IFRAME_ALLOW_ATTRIBUTE = 'clipboard-read; clipboard-write;'; |
| 18 | + |
| 19 | +type ContentProps = { |
| 20 | + appUrl?: string; |
| 21 | + address?: string; |
| 22 | + message?: Record<string, unknown>; |
| 23 | + isEssentialDapp?: boolean; |
| 24 | + className?: string; |
| 25 | +}; |
| 26 | + |
| 27 | +const Content = chakra(({ appUrl, address, message, isEssentialDapp, className }: ContentProps) => { |
| 28 | + const { iframeRef, isReady } = useDappscoutIframe(); |
| 29 | + const web3Wallet = useWeb3Wallet({ source: 'Essential dapps' }); |
| 30 | + |
| 31 | + const [ iframeKey, setIframeKey ] = useState(0); |
| 32 | + const [ isFrameLoading, setIsFrameLoading ] = useState(true); |
| 33 | + const [ iframeHeight, setIframeHeight ] = useState(0); |
| 34 | + |
| 35 | + useEffect(() => { |
| 36 | + setIsFrameLoading(true); |
| 37 | + setIframeKey((key) => key + 1); |
| 38 | + }, [ address ]); |
| 39 | + |
| 40 | + const handleIframeLoad = useCallback(() => { |
| 41 | + setIsFrameLoading(false); |
| 42 | + }, []); |
| 43 | + |
| 44 | + useEffect(() => { |
| 45 | + if (!isFrameLoading && message && appUrl) { |
| 46 | + iframeRef?.current?.contentWindow?.postMessage(message, appUrl); |
| 47 | + } |
| 48 | + }, [ isFrameLoading, appUrl, iframeRef, message ]); |
| 49 | + |
| 50 | + useEffect(() => { |
| 51 | + const handleMessage = (event: MessageEvent) => { |
| 52 | + try { |
| 53 | + if (event.origin !== new URL(appUrl ?? '').origin || !isEssentialDapp) { |
| 54 | + return; |
| 55 | + } |
| 56 | + switch (event.data?.type) { |
| 57 | + case 'window-height': |
| 58 | + setIframeHeight(Number(event.data.height)); |
| 59 | + break; |
| 60 | + case 'connect-wallet': |
| 61 | + web3Wallet.connect(); |
| 62 | + break; |
| 63 | + } |
| 64 | + } catch {} |
| 65 | + }; |
| 66 | + |
| 67 | + window.addEventListener('message', handleMessage); |
| 68 | + return () => window.removeEventListener('message', handleMessage); |
| 69 | + }, [ appUrl, isEssentialDapp, web3Wallet ]); |
| 70 | + |
| 71 | + return ( |
| 72 | + <Center |
| 73 | + flexGrow={ 1 } |
| 74 | + minH={ isEssentialDapp ? `${ iframeHeight }px` : undefined } |
| 75 | + minW="100%" |
| 76 | + className={ className } |
| 77 | + > |
| 78 | + { (isFrameLoading) && ( |
| 79 | + <ContentLoader/> |
| 80 | + ) } |
| 81 | + |
| 82 | + { isReady && ( |
| 83 | + <chakra.iframe |
| 84 | + key={ iframeKey } |
| 85 | + allow={ IFRAME_ALLOW_ATTRIBUTE } |
| 86 | + ref={ iframeRef } |
| 87 | + sandbox={ IFRAME_SANDBOX_ATTRIBUTE } |
| 88 | + h="100%" |
| 89 | + w="100%" |
| 90 | + display={ isFrameLoading ? 'none' : 'block' } |
| 91 | + src={ appUrl } |
| 92 | + title="Marketplace dapp" |
| 93 | + onLoad={ handleIframeLoad } |
| 94 | + background="transparent" |
| 95 | + allowTransparency={ true } |
| 96 | + /> |
| 97 | + ) } |
| 98 | + </Center> |
| 99 | + ); |
| 100 | +}); |
| 101 | + |
| 102 | +type Props = { |
| 103 | + appId: string; |
| 104 | + appUrl?: string; |
| 105 | + message?: Record<string, unknown>; |
| 106 | + isEssentialDapp?: boolean; |
| 107 | + className?: string; |
| 108 | +}; |
| 109 | + |
| 110 | +const MarketplaceAppIframe = ({ |
| 111 | + appId, appUrl, message, isEssentialDapp, className, |
| 112 | +}: Props) => { |
| 113 | + const { |
| 114 | + address, |
| 115 | + chainId: connectedChainId, |
| 116 | + sendTransaction, |
| 117 | + signMessage, |
| 118 | + signTypedData, |
| 119 | + switchChain, |
| 120 | + } = useMarketplaceWallet(appId, isEssentialDapp); |
| 121 | + |
| 122 | + const [ chainId, rpcUrl ] = useMemo(() => { |
| 123 | + let data: [ number?, string? ] = [ Number(config.chain.id), config.chain.rpcUrls[0] ]; |
| 124 | + |
| 125 | + if (isEssentialDapp) { |
| 126 | + const chainConfig = essentialDappsChainsConfig()?.chains.find( |
| 127 | + (chain) => chain.id === String(connectedChainId), |
| 128 | + ); |
| 129 | + if (chainConfig?.app_config?.chain?.rpcUrls[0]) { |
| 130 | + data = [ connectedChainId, chainConfig.app_config.chain.rpcUrls[0] ]; |
| 131 | + } |
| 132 | + } |
| 133 | + |
| 134 | + return data; |
| 135 | + }, [ isEssentialDapp, connectedChainId ]); |
| 136 | + |
| 137 | + return ( |
| 138 | + <DappscoutIframeProvider |
| 139 | + address={ address } |
| 140 | + appUrl={ appUrl } |
| 141 | + chainId={ chainId } |
| 142 | + rpcUrl={ rpcUrl } |
| 143 | + sendTransaction={ sendTransaction } |
| 144 | + signMessage={ signMessage } |
| 145 | + signTypedData={ signTypedData } |
| 146 | + switchChain={ switchChain } |
| 147 | + > |
| 148 | + <Content |
| 149 | + appUrl={ appUrl } |
| 150 | + address={ address } |
| 151 | + message={ message } |
| 152 | + isEssentialDapp={ isEssentialDapp } |
| 153 | + className={ className } |
| 154 | + /> |
| 155 | + </DappscoutIframeProvider> |
| 156 | + ); |
| 157 | +}; |
| 158 | + |
| 159 | +export default chakra(MarketplaceAppIframe); |
0 commit comments