-
-
Notifications
You must be signed in to change notification settings - Fork 187
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
improv: account syncing performance #4726
Changes from all commits
7e35ac6
a885311
88fd50e
8c52345
0a08746
09a04b2
f6589ba
c997c7a
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 |
---|---|---|
|
@@ -11,12 +11,13 @@ import type { | |
StateMetadata, | ||
} from '@metamask/base-controller'; | ||
import { BaseController } from '@metamask/base-controller'; | ||
import type { InternalAccount } from '@metamask/keyring-api'; | ||
import type { | ||
KeyringControllerGetStateAction, | ||
KeyringControllerLockEvent, | ||
KeyringControllerUnlockEvent, | ||
KeyringControllerAddNewAccountAction, | ||
import { type InternalAccount, isEvmAccountType } from '@metamask/keyring-api'; | ||
import { | ||
type KeyringControllerGetStateAction, | ||
type KeyringControllerLockEvent, | ||
type KeyringControllerUnlockEvent, | ||
type KeyringControllerAddNewAccountAction, | ||
KeyringTypes, | ||
} from '@metamask/keyring-controller'; | ||
import type { NetworkConfiguration } from '@metamask/network-controller'; | ||
import type { HandleSnapRequest } from '@metamask/snaps-controllers'; | ||
|
@@ -48,6 +49,7 @@ import { | |
getUserStorageAllFeatureEntries, | ||
upsertUserStorage, | ||
} from './services'; | ||
import { waitForExpectedValue } from './utils'; | ||
|
||
// TODO: add external NetworkController event | ||
// Need to listen for when a network gets added | ||
|
@@ -274,6 +276,7 @@ export default class UserStorageController extends BaseController< | |
// We will remove this once the feature will be released | ||
isAccountSyncingEnabled: false, | ||
isAccountSyncingInProgress: false, | ||
addedAccountsCount: 0, | ||
canSync: () => { | ||
try { | ||
this.#assertProfileSyncingEnabled(); | ||
|
@@ -291,8 +294,10 @@ export default class UserStorageController extends BaseController< | |
// eslint-disable-next-line @typescript-eslint/no-misused-promises | ||
async (account) => { | ||
if (this.#accounts.isAccountSyncingInProgress) { | ||
this.#accounts.addedAccountsCount += 1; | ||
return; | ||
} | ||
|
||
await this.saveInternalAccountToUserStorage(account); | ||
}, | ||
); | ||
|
@@ -308,8 +313,20 @@ export default class UserStorageController extends BaseController< | |
}, | ||
); | ||
}, | ||
doesInternalAccountHaveCorrectKeyringType: (account: InternalAccount) => { | ||
return ( | ||
account.metadata.keyring.type === KeyringTypes.hd || | ||
account.metadata.keyring.type === KeyringTypes.simple | ||
); | ||
}, | ||
getInternalAccountsList: async (): Promise<InternalAccount[]> => { | ||
return this.messagingSystem.call('AccountsController:listAccounts'); | ||
const internalAccountsList = await this.messagingSystem.call( | ||
'AccountsController:listAccounts', | ||
); | ||
|
||
return internalAccountsList?.filter( | ||
this.#accounts.doesInternalAccountHaveCorrectKeyringType, | ||
); | ||
}, | ||
getUserStorageAccountsList: async (): Promise< | ||
UserStorageAccount[] | null | ||
|
@@ -643,7 +660,6 @@ export default class UserStorageController extends BaseController< | |
* @param values - data to store, in the form of an array of `[entryKey, entryValue]` pairs | ||
* @returns nothing. NOTE that an error is thrown if fails to store data. | ||
*/ | ||
|
||
public async performBatchSetStorage( | ||
path: UserStoragePathWithFeatureOnly, | ||
values: [UserStoragePathWithKeyOnly, string][], | ||
|
@@ -761,6 +777,7 @@ export default class UserStorageController extends BaseController< | |
|
||
try { | ||
this.#accounts.isAccountSyncingInProgress = true; | ||
this.#accounts.addedAccountsCount = 0; | ||
|
||
const profileId = await this.#auth.getProfileId(); | ||
|
||
|
@@ -793,14 +810,21 @@ export default class UserStorageController extends BaseController< | |
userStorageAccountsList.length - internalAccountsList.length; | ||
|
||
// Create new accounts to match the user storage accounts list | ||
const addNewAccountsPromises = Array.from({ | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
for await (const _ of Array.from({ | ||
length: numberOfAccountsToAdd, | ||
}).map(async () => { | ||
})) { | ||
const expectedAccountsCountAfterAddition = | ||
this.#accounts.addedAccountsCount + 1; | ||
await this.messagingSystem.call('KeyringController:addNewAccount'); | ||
await waitForExpectedValue( | ||
() => this.#accounts.addedAccountsCount, | ||
expectedAccountsCountAfterAddition, | ||
5000, | ||
); | ||
this.#config?.accountSyncing?.onAccountAdded?.(profileId); | ||
}); | ||
|
||
await Promise.all(addNewAccountsPromises); | ||
} | ||
} | ||
|
||
// Second step: compare account names | ||
|
@@ -906,7 +930,11 @@ export default class UserStorageController extends BaseController< | |
async saveInternalAccountToUserStorage( | ||
internalAccount: InternalAccount, | ||
): Promise<void> { | ||
if (!this.#accounts.canSync()) { | ||
if ( | ||
!this.#accounts.canSync() || | ||
!isEvmAccountType(internalAccount.type) || | ||
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. This is okay. I find that once we have more than 1 boolean operand (&&/||) it gets a little confusing. Maybe could split out into a separate func? |
||
!this.#accounts.doesInternalAccountHaveCorrectKeyringType(internalAccount) | ||
) { | ||
return; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,3 +26,34 @@ export function setIntersection<TItem>( | |
a.forEach((e) => b.has(e) && intersection.add(e)); | ||
return intersection; | ||
} | ||
|
||
/** | ||
* | ||
* Waits for a value to be returned from a getter function. | ||
* | ||
* @param getter - Function that returns the value to check | ||
* @param expectedValue - The value to wait for | ||
* @param timeout - The time to wait before timing out | ||
* @returns A promise that resolves when the expected value is returned | ||
* or rejects if the timeout is reached. | ||
*/ | ||
export function waitForExpectedValue<TVariable>( | ||
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. Since we had to resort to this, really nice util. |
||
getter: () => TVariable, | ||
expectedValue: TVariable, | ||
timeout = 1000, | ||
): Promise<TVariable> { | ||
return new Promise((resolve, reject) => { | ||
const interval = setInterval(() => { | ||
const value = getter(); | ||
if (value === expectedValue) { | ||
clearInterval(interval); | ||
resolve(value); | ||
} | ||
}, 100); | ||
|
||
setTimeout(() => { | ||
clearInterval(interval); | ||
reject(new Error('Timed out waiting for expected value')); | ||
}, timeout); | ||
}); | ||
} |
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.
Hehe, nice