-
Notifications
You must be signed in to change notification settings - Fork 5.2k
fix: timeout chrome runtime messages for Ledger #31766
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@@ -9,12 +9,22 @@ import { | |||
OffscreenCommunicationTarget, | |||
} from '../../../../shared/constants/offscreen-communication'; | |||
|
|||
const DEFAULT_MESSAGE_TIMEOUT = 4000; | |||
|
|||
const SIGNING_TIMEOUT = 20_000; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a large value, or the user won't have time to grab the device and accept the signature request
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Though also not too large, or we'd wait forever for the user signature, never releasing KeyringController's mutex.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. Tricky problem. I can see it taking more than 20 seconds if someone's closely analyzing the signature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps we should leave the confirmation screen open while the approval on the device is pending? And have no timeout, so users have as much time as they need. But if they want to cancel it, they can close the screen and we can cancel the operation and release the mutex then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As there is a risk of UX degradation, I assigned a timeout only for messages that don't require a user action (i.e. attemptMakeApp
and updateTransportMethod
) in 92cfd72
message: IFrameMessage<TAction>, | ||
timeout = DEFAULT_MESSAGE_TIMEOUT, | ||
): Promise<ResponsePayload> { | ||
if (!this.isDeviceConnected) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, this.isDeviceConnected
seems to always be false
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm removing this check for now.
Builds ready [92cfd72]
UI Startup Metrics (1218 ± 64 ms)
|
Builds ready [ecfcd13]
UI Startup Metrics (1209 ± 59 ms)
|
Builds ready [8d59d2f]
UI Startup Metrics (1209 ± 59 ms)
Bundle size diffs [🚀 Bundle size reduced!]
|
Builds ready [a439859]
UI Startup Metrics (1217 ± 65 ms)
Bundle size diffs [🚀 Bundle size reduced!]
|
let hasResponse = false; | ||
|
||
if (timeout) { | ||
setTimeout(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
timer should be cleaned if resolve
/reject
happens elsewhere (in the chrome.runtime.sendMessage
callback). You also won't need the hasResponse
variable at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point! I changed the way this it's done in e402758
resolve(response.payload); | ||
} else { | ||
reject(response.payload.error); | ||
reject(response?.payload?.error || 'Unknown Ledger error'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reject
value is sometimes an Error
and sometimes a string. It should always be an Error
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as response
is any
, we don't know what response?.payload?.error
really is. I wrapped it in an Error
instance in 1508807
so we are sure that we always reject with an Error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense to me.
^ Well, other than the CI failures 😞 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
Description
This PR fixes the following deadlock issue when the Ledger iframe endpoint is unavailable (timeout or error) while using a chrome MV3: #31436
Iframe messages that require user actions to be resolved (e.g., signing) are intentionally left with no timeout for now.
The equivalent issue for MV2/firefox has been fixed separately
Related issues
Fixes: #31436
Manual testing steps
To reproduce the issue
/etc/hosts
) to force the iframe to fail:127.0.0.1 metamask.github.io
main
branch)yarn start
, to run an MV3 buildTo test the fix
/etc/hosts
) to force the iframe to fail:127.0.0.1 metamask.github.io
yarn start
and reload the extensionScreenshots/Recordings
Before
After
Pre-merge author checklist
Pre-merge reviewer checklist