fix: prevent approval popup from being force-closed by dapp polling (ADN-757)#819
Open
fix: prevent approval popup from being force-closed by dapp polling (ADN-757)#819
Conversation
…sful tx A race between the popup's success response and chrome.windows.onRemoved could cause the dapp to receive TRANSACTION_REJECTED even though the transaction was broadcast successfully. Guard sendResponse to fire at most once, clean up listeners after the first response, and await chrome.runtime.sendMessage in the popup before closing the window.
DApps such as Gnoswap poll GET_ACCOUNT/GET_NETWORK every few seconds while an approval popup is open. Each incoming request previously ran removePopups() unconditionally, tearing down the in-flight popup and making the dapp receive TRANSACTION_REJECTED even though the user had done nothing or had actually approved. - message-handler: skip existsPopups/removePopups for read-only request types (GET_ACCOUNT, GET_NETWORK). - common: track window ids closed programmatically and, when onRemoved fires for such a popup, respond with UNEXPECTED_ERROR instead of the default closeMessage. Keeps the user-rejection signal truthful for future forced close paths.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three related fixes for approval-popup teardown that caused dapps to see
TRANSACTION_REJECTEDwhen the user had actually approved (or had not acted at all).message-handler.tsno longer tears down open popups for read-only requests (GET_ACCOUNT,GET_NETWORK). The dapp's background polling was force-closing in-flight approval popups.common.tsnow ensuressendResponsefires at most once and cleans up all popup listeners together, plus the popup awaitschrome.runtime.sendMessagebeforewindow.close(). Prevents the earlier race where a successful popup response could be overridden by theonRemovedfallback.UNEXPECTED_ERRORinstead of the user-rejection message. This keeps the rejection signal truthful for future force-close paths (e.g. consecutiveDO_CONTRACTrequests).Root cause
While a
DO_CONTRACTpopup was open, the dapp (e.g. Gnoswap) kept pollingGET_ACCOUNTevery few seconds. On each poll,MessageHandler.requestHandlerranremovePopups()before dispatching the handler, which closed the approval popup. The popup'sonRemovedlistener incommon.tsthen sent the defaultcloseMessage(TRANSACTION_REJECTED) back to the dapp — producing a "user rejected" result even though the user had done nothing, or had actually approved.Timing evidence from instrumentation:
51fd0e5b…: dappGET_ACCOUNTat t=50997ms → popuponRemovedat t=51017ms (~20ms gap)2c108cd1…: dappGET_ACCOUNTat t=124156ms → popuponRemovedat t=124173ms (~17ms gap)Changes
packages/adena-extension/src/inject/message/message-handler.tsNON_POPUP_REQUEST_TYPES = { GET_ACCOUNT, GET_NETWORK }.existsPopups()/removePopups()for these read-only requests. Popup-opening requests (DO_CONTRACT,SIGN_*,ADD_ESTABLISH, etc.) preserve the existing behavior.packages/adena-extension/src/inject/message/methods/common.tsonRemovedListener/onMessageListener/onTabsUpdatedListenerand add acleanup()that removes all three. Prevents listener leaks across popup sessions.sendResponseOnceguard sosendResponsecan never fire twice for the same request (defends against races between popup success message andonRemovedfallback).programmaticallyClosedPopups: Set<number>.removePopups()records each window id it is about to close. WhenonRemovedListenerfires, if the id is in the set, respond withUNEXPECTED_ERRORinstead of the defaultcloseMessage. This prevents future forced-close paths (e.g. consecutiveDO_CONTRACTrequests) from being reported as user rejections.packages/adena-extension/src/pages/popup/wallet/approve-transaction-main/index.tsxonClickCloseResult:await chrome.runtime.sendMessage(response)beforewindow.close()so the success response actually reaches the background before the popup tears down.Test plan
GET_ACCOUNTpolling fire). Confirm the popup stays open and the transaction succeeds. Dapp should receiveTRANSACTION_SUCCESS.TRANSACTION_SUCCESS.TRANSACTION_REJECTED(unchanged behavior).DO_CONTRACTwhile a first popup is still open — previous request should now receiveUNEXPECTED_ERROR(not a user rejection).SIGN_AMINO/SIGN_TXpopups behave the same way (samecreatePopuppath).GET_ACCOUNT/GET_NETWORKcontinue to respond normally.Closes ADN-757