-
-
Notifications
You must be signed in to change notification settings - Fork 208
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
Subscribe to QRKeyring
store and publish updates
#1702
Conversation
qrKeyrings.forEach((qrKeyring) => { | ||
if (this.#qrKeyringStateListener) { | ||
qrKeyring.getMemStore().unsubscribe(this.#qrKeyringStateListener); | ||
} | ||
}); |
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.
We shouldn't assume that we have just one QRKeyring
as calling twice this expression:
this.addNewKeyring(KeyringTypes.qr)
would end up creating two different QRKeyring
instances
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.
The real problem with having multiple QRKeyring
instances would be that the event listeners would publish through the same messenger event, making impossible from a client to establish which specific QRKeyring
emitted that state update.
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.
A possible solution for this would be to subscribe to QRKeyring
during addNewAccount
only for the first keyring added: this would force a client that wants to use more than one QRKeyring
instance to implement a subscribing solution on its side.
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.
Or, as we don't explicitly support multiple keyrings of the same type (yet), we could simply throw an error when calling addNewKeyring
if there's a keyring of that type already (with the exception of SimpleKeyring
, but that one is created through importAccountWithStrategy
).
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.
Ops, I actually forgot that I added this in addNewKeyring
to prevent exactly this 🤦 :
if (type === KeyringTypes.qr) {
return this.getOrAddQRKeyring();
}
So the behavior in this PR is that if the addNewKeyring
is called with a QR type, then the QRKeyring is retrieved or created
28155e4
to
f5c65e6
Compare
@@ -467,7 +477,11 @@ export class KeyringController extends BaseControllerV2< | |||
async addNewKeyring( | |||
type: KeyringTypes | string, | |||
opts?: unknown, | |||
): Promise<Keyring<Json>> { | |||
): Promise<unknown> { |
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.
Not strictly required by this PR, but makes more sense since the keyring created could be of any type (for example, a QRKeyring
)
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.
Can we have a base class/interface for the Keyring, then having a child classes with specific Keyring types for example QRKering.
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.
We have a type that should be implemented by all Keyrings: https://github.com/MetaMask/utils/blob/4589c755e3005024c9071dd0c23c933335c3efa4/src/keyring.ts#L47
But that's not yet the case, so we still have to handle their API differences. In this specific case, it is more correct to say that we don't know what we are returning, instead of assuming that we are returning a Keyring<Json
- also because we don't restrict the type of keyrings that can be created through this controller
if (type === KeyringTypes.qr) { | ||
return this.getOrAddQRKeyring(); | ||
} |
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.
We do this to avoid this issue: https://github.com/MetaMask/core/pull/1702/files#r1334384936
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!
f5c65e6
to
74f5cbf
Compare
74f5cbf
to
cddbd4e
Compare
cddbd4e
to
7baeab5
Compare
## Explanation Currently, the extension is directly subscribing to the `QRKeyring` store, to determine whether to open the modal for syncing and signing. This raises concerns when the instance of the QRKeyring will be held by `KeyringController` instead of the client because it will be tricky to handle `subscribe` and `unsubscribe` in the keyring's lifecycle, risking to have multiple subscriptions to the same QRKeyring instance or even no subscriptions at all! This PR gives this responsibility to `KeyringController`. `KeyringController` will subscribe to the QRKeyring instance in these methods: - `submitPassword` - because here we unlock the vault and potentially find a QRKeyring (even in case of re-construction from backup) - `submitEncryptionKey` - same as above - `getOrAddQRKeyring` - because here we explicitly create a new QRKeyring if it doesn't exist - `addNewKeyring(type)` - when `type` is of a QRKeyring then it will return `getOrAddQRKeyring`, to avoid creating multiple keyring instances (and multiple subscriptions) `KeyringController` will unsubscribe from the `QRKeyring` instance (if there's one ) when calling `setLocked()`. Moreover, `KeyringController:qrKeyringStateChange` event is now available on the messenger <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? For example: * Fixes #12345 * Related to #67890 --> * Related to MetaMask/metamask-extension#18776 * Related to MetaMask/metamask-extension#20502 ## Changelog <!-- If you're making any consumer-facing changes, list those changes here as if you were updating a changelog, using the template below as a guide. (CATEGORY is one of BREAKING, ADDED, CHANGED, DEPRECATED, REMOVED, or FIXED. For security-related issues, follow the Security Advisory process.) Please take care to name the exact pieces of the API you've added or changed (e.g. types, interfaces, functions, or methods). If there are any breaking changes, make sure to offer a solution for consumers to follow once they upgrade to the changes. Finally, if you're only making changes to development scripts or tests, you may replace the template below with "None". --> ### `@metamask/keyring-controller` - **BREAKING**: `addNewKeyring(type)` return type changed from `Promise<Keyring<Json>>` to `Promise<unknown>` - When calling with QRKeyring `type` the keyring instance is retrieved or created (no multiple QRKeyring instances possible) - **ADDED**: Add `getQRKeyring(): QRKeyring | undefined` method - **ADDED**: Add `KeyringController:qrKeyringStateChange` messenger event The event emits updates from the internal `QRKeyring` instance, if there's one ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've highlighted breaking changes using the "BREAKING" category above as appropriate
## Explanation Currently, the extension is directly subscribing to the `QRKeyring` store, to determine whether to open the modal for syncing and signing. This raises concerns when the instance of the QRKeyring will be held by `KeyringController` instead of the client because it will be tricky to handle `subscribe` and `unsubscribe` in the keyring's lifecycle, risking to have multiple subscriptions to the same QRKeyring instance or even no subscriptions at all! This PR gives this responsibility to `KeyringController`. `KeyringController` will subscribe to the QRKeyring instance in these methods: - `submitPassword` - because here we unlock the vault and potentially find a QRKeyring (even in case of re-construction from backup) - `submitEncryptionKey` - same as above - `getOrAddQRKeyring` - because here we explicitly create a new QRKeyring if it doesn't exist - `addNewKeyring(type)` - when `type` is of a QRKeyring then it will return `getOrAddQRKeyring`, to avoid creating multiple keyring instances (and multiple subscriptions) `KeyringController` will unsubscribe from the `QRKeyring` instance (if there's one ) when calling `setLocked()`. Moreover, `KeyringController:qrKeyringStateChange` event is now available on the messenger <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? For example: * Fixes #12345 * Related to #67890 --> * Related to MetaMask/metamask-extension#18776 * Related to MetaMask/metamask-extension#20502 ## Changelog <!-- If you're making any consumer-facing changes, list those changes here as if you were updating a changelog, using the template below as a guide. (CATEGORY is one of BREAKING, ADDED, CHANGED, DEPRECATED, REMOVED, or FIXED. For security-related issues, follow the Security Advisory process.) Please take care to name the exact pieces of the API you've added or changed (e.g. types, interfaces, functions, or methods). If there are any breaking changes, make sure to offer a solution for consumers to follow once they upgrade to the changes. Finally, if you're only making changes to development scripts or tests, you may replace the template below with "None". --> ### `@metamask/keyring-controller` - **BREAKING**: `addNewKeyring(type)` return type changed from `Promise<Keyring<Json>>` to `Promise<unknown>` - When calling with QRKeyring `type` the keyring instance is retrieved or created (no multiple QRKeyring instances possible) - **ADDED**: Add `getQRKeyring(): QRKeyring | undefined` method - **ADDED**: Add `KeyringController:qrKeyringStateChange` messenger event The event emits updates from the internal `QRKeyring` instance, if there's one ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've highlighted breaking changes using the "BREAKING" category above as appropriate
Explanation
Currently, the extension is directly subscribing to the
QRKeyring
store, to determine whether to open the modal for syncing and signing.This raises concerns when the instance of the QRKeyring will be held by
KeyringController
instead of the client because it will be tricky to handlesubscribe
andunsubscribe
in the keyring's lifecycle, risking to have multiple subscriptions to the same QRKeyring instance or even no subscriptions at all!This PR gives this responsibility to
KeyringController
.KeyringController
will subscribe to the QRKeyring instance in these methods:submitPassword
- because here we unlock the vault and potentially find a QRKeyring (even in case of re-construction from backup)submitEncryptionKey
- same as abovegetOrAddQRKeyring
- because here we explicitly create a new QRKeyring if it doesn't existaddNewKeyring(type)
- whentype
is of a QRKeyring then it will returngetOrAddQRKeyring
, to avoid creating multiple keyring instances (and multiple subscriptions)KeyringController
will unsubscribe from theQRKeyring
instance (if there's one ) when callingsetLocked()
.Moreover,
KeyringController:qrKeyringStateChange
event is now available on the messengerReferences
QRKeyring
fromKeyringController
metamask-extension#20502Changelog
@metamask/keyring-controller
addNewKeyring(type)
return type changed fromPromise<Keyring<Json>>
toPromise<unknown>
type
the keyring instance is retrieved or created (no multiple QRKeyring instances possible)getQRKeyring(): QRKeyring | undefined
methodKeyringController:qrKeyringStateChange
messenger eventThe event emits updates from the internal
QRKeyring
instance, if there's oneChecklist