-
-
Notifications
You must be signed in to change notification settings - Fork 126
Refactor to handle new keyrings with bridge dependencies #163
Changes from all commits
6ef7f15
f1c9718
6593deb
78a3376
500e1a3
a486f5b
53d8263
eeeee36
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,7 +8,10 @@ const { normalize: normalizeAddress } = require('eth-sig-util'); | |
| const SimpleKeyring = require('@metamask/eth-simple-keyring'); | ||
| const HdKeyring = require('@metamask/eth-hd-keyring'); | ||
|
|
||
| const keyringTypes = [SimpleKeyring, HdKeyring]; | ||
| const defaultKeyringBuilders = [ | ||
| keyringBuilderFactory(SimpleKeyring), | ||
| keyringBuilderFactory(HdKeyring), | ||
| ]; | ||
|
|
||
| const KEYRINGS_TYPE_MAP = { | ||
| HD_KEYRING: 'HD Key Tree', | ||
|
|
@@ -35,13 +38,15 @@ class KeyringController extends EventEmitter { | |
| constructor(opts) { | ||
| super(); | ||
| const initState = opts.initState || {}; | ||
| this.keyringTypes = opts.keyringTypes | ||
| ? keyringTypes.concat(opts.keyringTypes) | ||
| : keyringTypes; | ||
| this.keyringBuilders = opts.keyringBuilders | ||
| ? defaultKeyringBuilders.concat(opts.keyringBuilders) | ||
| : defaultKeyringBuilders; | ||
| this.store = new ObservableStore(initState); | ||
| this.memStore = new ObservableStore({ | ||
| isUnlocked: false, | ||
| keyringTypes: this.keyringTypes.map((keyringType) => keyringType.type), | ||
| keyringTypes: this.keyringBuilders.map( | ||
| (keyringBuilder) => keyringBuilder.type, | ||
| ), | ||
| keyrings: [], | ||
| encryptionKey: null, | ||
| }); | ||
|
|
@@ -230,15 +235,15 @@ class KeyringController extends EventEmitter { | |
| * and the current decrypted Keyrings array. | ||
| * | ||
| * All Keyring classes implement a unique `type` string, | ||
| * and this is used to retrieve them from the keyringTypes array. | ||
| * and this is used to retrieve them from the keyringBuilders array. | ||
| * | ||
| * @param {string} type - The type of keyring to add. | ||
| * @param {Object} opts - The constructor options for the keyring. | ||
| * @returns {Promise<Keyring>} The new keyring. | ||
| */ | ||
| async addNewKeyring(type, opts) { | ||
| const Keyring = this.getKeyringClassForType(type); | ||
| const keyring = new Keyring(opts); | ||
| const keyring = await this._newKeyring(type, opts); | ||
|
|
||
| if ((!opts || !opts.mnemonic) && type === KEYRINGS_TYPE_MAP.HD_KEYRING) { | ||
| keyring.generateRandomMnemonic(); | ||
| keyring.addAccounts(); | ||
|
|
@@ -700,13 +705,12 @@ class KeyringController extends EventEmitter { | |
| async _restoreKeyring(serialized) { | ||
| const { type, data } = serialized; | ||
|
|
||
| const Keyring = this.getKeyringClassForType(type); | ||
| if (!Keyring) { | ||
| const keyring = await this._newKeyring(type, data); | ||
| if (!keyring) { | ||
| this._unsupportedKeyrings.push(serialized); | ||
| return undefined; | ||
| } | ||
| const keyring = new Keyring(); | ||
| await keyring.deserialize(data); | ||
|
|
||
| // getAccounts also validates the accounts for some keyrings | ||
| await keyring.getAccounts(); | ||
| this.keyrings.push(keyring); | ||
|
|
@@ -716,16 +720,18 @@ class KeyringController extends EventEmitter { | |
| /** | ||
| * Get Keyring Class For Type | ||
| * | ||
| * Searches the current `keyringTypes` array | ||
| * for a Keyring class whose unique `type` property | ||
| * Searches the current `keyringBuilders` array | ||
| * for a Keyring builder whose unique `type` property | ||
| * matches the provided `type`, | ||
| * returning it if it exists. | ||
| * | ||
| * @param {string} type - The type whose class to get. | ||
| * @returns {Keyring|undefined} The class, if it exists. | ||
| */ | ||
| getKeyringClassForType(type) { | ||
| return this.keyringTypes.find((keyring) => keyring.type === type); | ||
| getKeyringBuilderForType(type) { | ||
| return this.keyringBuilders.find( | ||
| (keyringBuilder) => keyringBuilder.type === type, | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -873,6 +879,52 @@ class KeyringController extends EventEmitter { | |
| ); | ||
| } | ||
| } | ||
|
|
||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are two methods in which a new keyring is instantiated: I've extracted the logic into a On the topic of deserialization, after looking at every single keyring I'm convinced that we don't have to pass the data at construction time as long as we call |
||
| /** | ||
| * Instantiate, initialize and return a new keyring | ||
| * | ||
| * The keyring instantiated is of the given `type`. | ||
| * | ||
| * @param {string} type - The type of keyring to add. | ||
| * @param {Object} data - The data to restore a previously serialized keyring. | ||
| * @returns {Promise<Keyring>} The new keyring. | ||
| */ | ||
| async _newKeyring(type, data) { | ||
Gudahtt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| const keyringBuilder = this.getKeyringBuilderForType(type); | ||
|
|
||
| if (!keyringBuilder) { | ||
| return undefined; | ||
| } | ||
|
|
||
| const keyring = keyringBuilder(); | ||
|
|
||
| await keyring.deserialize(data); | ||
|
|
||
| if (keyring.init) { | ||
| await keyring.init(); | ||
| } | ||
|
|
||
| return keyring; | ||
| } | ||
| } | ||
|
|
||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are many instances in which we try to access the By exporting this helper method we can make sure that whenever we create a keyring builder function, the type is added as well.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, since we are always calling
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we're changing the "Keyring" API in a non-backwards-compatible way, I'd prefer that we start using an object for constructor options rather than parameters (i.e. the "options bag" pattern, passing in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair enough. I've made the change to the options bag pattern directly in The only argument I have in favour of passing dependencies without wrapping them in an object is that, if this ever moves to TypeScript, we could benefit from the syntactic sugar to declare them as class properties in the constructor, but that's a bit far-fetched. |
||
| module.exports = KeyringController; | ||
| /** | ||
| * Get builder function for `Keyring` | ||
| * | ||
| * Returns a builder function for `Keyring` with a `type` property. | ||
| * | ||
| * @param {typeof Keyring} Keyring - The Keyring class for the builder. | ||
| * @returns {function: Keyring} A builder function for the given Keyring. | ||
| */ | ||
| function keyringBuilderFactory(Keyring) { | ||
| const builder = () => new Keyring(); | ||
|
|
||
| builder.type = Keyring.type; | ||
|
|
||
| return builder; | ||
| } | ||
|
|
||
| module.exports = { | ||
| KeyringController, | ||
| keyringBuilderFactory, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| class KeyringMockWithInit { | ||
| init() { | ||
| return Promise.resolve(); | ||
| } | ||
|
|
||
| getAccounts() { | ||
| return []; | ||
| } | ||
|
|
||
| serialize() { | ||
| return Promise.resolve({}); | ||
| } | ||
|
|
||
| deserialize(_) { | ||
| return Promise.resolve(); | ||
| } | ||
| } | ||
|
|
||
| KeyringMockWithInit.type = 'Keyring Mock With Init'; | ||
|
|
||
| module.exports = { | ||
| KeyringMockWithInit, | ||
| }; |
Uh oh!
There was an error while loading. Please reload this page.
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 thought it would be wise to rename this property as it will no longer hold class types, but builder functions.
I have checked within the extension code and it does not appear to be used, but worth thinking about any possible side effects as this can be a breaking change if external code tries to access this property.
keyringTypesin the store is left unchanged and it still stores the types as strings.