When a web3 app is inside an iframe, changing the active wallet doesn't trigger a new "Connect" request inside Metamask.
Sample connect request:

Simple reproduction:
No connect requests will be triggered. Uniswap will show you the last wallet that you approved a connection for, but not the one that you changed to.
You can only change between wallets that are _already connected_ to the web3 app. Then the addresses update correctly inside the iframe. There's no other way to connect the wallet than to open the iframe in a new tab and approving the connection request after switching.
Thanks for the report! This case was not considered - currently only the parent frame is considered by our popup UI as the "active dapp".
We might be able to fall back to looking at a child frame if the child frame is connected but the parent isn't 馃. I'll have to check and see whether the web extension API provides information about child frames in the active tab. Handling _nested_ connected dapps would require a more substantial design change though.
When you encountered this issue, was the dapp in the iframe the only connected dapp (as in the example you linked)? Or did you also have nested dapps in mind?
There's no other way to connect the wallet than to open the iframe in a new tab and approving the connection request after switching.
This part is concerning on its own. Even if we decide not to consider nested dapps in our UI, we should at least provide a way to manually connect new accounts to them.
This part is concerning on its own. Even if we decide not to consider nested dapps in our UI, we should at least provide a way to manually connect new accounts to them.
@Gudahtt I wrote another issue which could be a relevant fix for "same origin url" apps: https://github.com/MetaMask/metamask-extension/issues/9190. If apps are identified only by their origin domain and _not port_ (so myapp.host.org:8000 is identified as myapp.host.org), then apps inside iframes that are on the same domain but a different port will immediately be connected once the parent app is connected.
For now, I built a (imo elegant) solution. I think it's the best possible workaround while there isn't yet a native fix:
The user experience is smooth (only 2 extra clicks):
Here's how it works:
openConnect message event back to the parent.closeMe event is sent by the "pop-up" to window.opener (the "parent" app), and the parent app closes the pop-up.Here's the code:
Parent app events (Svelte) (the iframe html object is bound to let frame :
<script>
// Sends new address to iframe
let frame: any;
const eth: any = (window as any).ethereum;
const updateWeb3Api = async () => {
frame.contentWindow.postMessage(
{ type: "ethAddress", address: $ethAddress },
config.iframeUrl
);
};
// This triggers every time there's a new address. With vanilla js you should listen for `eth_accountsChanged` metamask event here.
$: if (loaded && $ethAddress) {
updateWeb3Api();
}
// Listen for events from iframe (to open Connect page in new tab or close it)
let registered: boolean = false;
let eventer: any;
let manageWindow: any;
const eventHandler = (e: any) => {
if (e.data === "goToManage") {
manageWindow = window.open(config.iframeUrl + "/manage", "_blank");
}
if (e.data === "closeManage") {
manageWindow.close();
}
};
// Mount eventListener
onMount(() => {
if (!registered) {
registered = true;
// Listen to message from child window
window.addEventListener("message", eventHandler);
}
});
// Unmount eventListener
onDestroy(() => {
loaded = false;
if (registered) {
window.removeEventListener("message", eventHandler);
}
registered = false;
});
</script>
<iframe
on:load={() => (loaded = true)}
id="iframe"
src="{config.iframeUrl}/{urlExt}"
bind:this={frame}
{style} />
iframe app index.tsx event listener/pusher (listens for address change events sent by parent):
if ('ethereum' in window) {
;(window.ethereum as any).autoRefreshOnNetworkChange = false
const isMetamask = window.ethereum.isMetaMask
// Handle new Ethereum account from iframe
if (!listenerAttached) {
listenerAttached = true
window.addEventListener('message', async event => {
if (event.data.type === 'ethAddress' && event.origin.startsWith(process.env.REACT_APP_PARENT)) {
const acc = await (window.ethereum as any).request({ method: 'eth_requestAccounts' })
if (isMetamask && String(acc[0]).toLowerCase() !== String(event.data?.address).toLowerCase()) {
window.parent.postMessage('goToManage', process.env.REACT_APP_PARENT)
}
}
})
}
}
iframe app "Connect" tab (sends "closeMe" event to the parent app on connect so it can close this tab) (note you also need to query for accounts so the Connect tab is open as soon as you click Metamask):
export default function Manage() {
;(window.ethereum as any).on('accountsChanged', () => {
// @ts-ignore
if (window.opener) {
window.opener.postMessage('closeManage', process.env.REACT_APP_WALLET)
}
})
return (
<p>
Open MetaMask and connect your new wallet, then close this window.
</p>
)
}
When you encountered this issue, was the dapp in the iframe the only connected dapp (as in the example you linked)? Or did you also have nested dapps in mind?
@Gudahtt In my use case I have a parent web3 app, and inside it an iframe with another web3 app. So the parent gets "connect" requests for Metamask for wallet changes, but not the iframe. So for my use case the fallback should be a check "if the parent is connected but not the iframe".