@metamask-previews/account-tree-controller 0.15.1-preview-c896a02b → 0.15.1-preview-c8856e2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -0
- package/dist/backup-and-sync/service/index.cjs +4 -14
- package/dist/backup-and-sync/service/index.cjs.map +1 -1
- package/dist/backup-and-sync/service/index.d.cts +2 -0
- package/dist/backup-and-sync/service/index.d.cts.map +1 -1
- package/dist/backup-and-sync/service/index.d.mts +2 -0
- package/dist/backup-and-sync/service/index.d.mts.map +1 -1
- package/dist/backup-and-sync/service/index.mjs +4 -14
- package/dist/backup-and-sync/service/index.mjs.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -12,9 +12,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
12
12
|
- **BREAKING:** Use `:getSelectedMultichainAccount` instead of `:getSelectedAccount` to compute currently selected account group ([#6608](https://github.com/MetaMask/core/pull/6608))
|
|
13
13
|
- Coming from the old account model, a non-EVM account could have been selected and the lastly selected EVM account might not be using the same group index.
|
|
14
14
|
- Bump `@metamask/utils` from `^11.4.2` to `^11.8.0` ([#6588](https://github.com/MetaMask/core/pull/6588))
|
|
15
|
+
- Bump `@metamask/base-controller` from `^8.3.0` to `^8.4.0` ([#6632](https://github.com/MetaMask/core/pull/6632))
|
|
15
16
|
|
|
16
17
|
### Removed
|
|
17
18
|
|
|
19
|
+
- Remove full sync triggers when single sync operations are enqueued and `hasSyncedAtLeastOnce` is `false` ([#6634](https://github.com/MetaMask/core/pull/6634))
|
|
18
20
|
- Remove use of `:getSelectedAccount` action ([#6608](https://github.com/MetaMask/core/pull/6608))
|
|
19
21
|
|
|
20
22
|
## [0.15.1]
|
|
@@ -100,17 +100,12 @@ class BackupAndSyncService {
|
|
|
100
100
|
}
|
|
101
101
|
/**
|
|
102
102
|
* Enqueues a single wallet sync operation (fire-and-forget).
|
|
103
|
+
* If the first full sync has not yet occurred, it does nothing.
|
|
103
104
|
*
|
|
104
105
|
* @param walletId - The wallet ID to sync.
|
|
105
106
|
*/
|
|
106
107
|
enqueueSingleWalletSync(walletId) {
|
|
107
|
-
if (!this.isBackupAndSyncEnabled) {
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
if (!this.hasSyncedAtLeastOnce) {
|
|
111
|
-
// Run big sync
|
|
112
|
-
// eslint-disable-next-line no-void
|
|
113
|
-
void this.performFullSync();
|
|
108
|
+
if (!this.isBackupAndSyncEnabled || !this.hasSyncedAtLeastOnce) {
|
|
114
109
|
return;
|
|
115
110
|
}
|
|
116
111
|
// eslint-disable-next-line no-void
|
|
@@ -118,17 +113,12 @@ class BackupAndSyncService {
|
|
|
118
113
|
}
|
|
119
114
|
/**
|
|
120
115
|
* Enqueues a single group sync operation (fire-and-forget).
|
|
116
|
+
* If the first full sync has not yet occurred, it does nothing.
|
|
121
117
|
*
|
|
122
118
|
* @param groupId - The group ID to sync.
|
|
123
119
|
*/
|
|
124
120
|
enqueueSingleGroupSync(groupId) {
|
|
125
|
-
if (!this.isBackupAndSyncEnabled) {
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
if (!this.hasSyncedAtLeastOnce) {
|
|
129
|
-
// Run big sync
|
|
130
|
-
// eslint-disable-next-line no-void
|
|
131
|
-
void this.performFullSync();
|
|
121
|
+
if (!this.isBackupAndSyncEnabled || !this.hasSyncedAtLeastOnce) {
|
|
132
122
|
return;
|
|
133
123
|
}
|
|
134
124
|
// eslint-disable-next-line no-void
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sourceRoot":"","sources":["../../../src/backup-and-sync/service/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AccountGroupId, AccountWalletId } from '@metamask/account-api';\nimport { AccountWalletType } from '@metamask/account-api';\nimport type { UserStorageController } from '@metamask/profile-sync-controller';\n\nimport { AtomicSyncQueue } from './atomic-sync-queue';\nimport { backupAndSyncLogger } from '../../logger';\nimport type { AccountTreeControllerState } from '../../types';\nimport type { AccountWalletEntropyObject } from '../../wallet';\nimport { TraceName } from '../analytics';\nimport type { ProfileId } from '../authentication';\nimport { getProfileId } from '../authentication';\nimport {\n createLocalGroupsFromUserStorage,\n performLegacyAccountSyncing,\n syncGroupsMetadata,\n syncGroupMetadata,\n syncWalletMetadata,\n} from '../syncing';\nimport type {\n BackupAndSyncContext,\n UserStorageSyncedWallet,\n UserStorageSyncedWalletGroup,\n} from '../types';\nimport {\n getAllGroupsFromUserStorage,\n getGroupFromUserStorage,\n getWalletFromUserStorage,\n pushGroupToUserStorageBatch,\n} from '../user-storage';\nimport {\n createStateSnapshot,\n restoreStateFromSnapshot,\n getLocalEntropyWallets,\n getLocalGroupsForEntropyWallet,\n} from '../utils';\nimport type { StateSnapshot } from '../utils';\n\n/**\n * Service responsible for managing all backup and sync operations.\n *\n * This service handles:\n * - Full sync operations\n * - Single item sync operations\n * - Sync queue management\n * - Sync state management\n */\nexport class BackupAndSyncService {\n readonly #context: BackupAndSyncContext;\n\n /**\n * Queue manager for atomic sync operations.\n */\n readonly #atomicSyncQueue: AtomicSyncQueue;\n\n /**\n * Cached promise for ongoing full sync operations.\n * Ensures multiple callers await the same sync operation.\n */\n #ongoingFullSyncPromise: Promise<void> | null = null;\n\n /**\n * Cached promise for the first ongoing full sync operation.\n * Ensures multiple callers await the same sync operation.\n */\n #firstOngoingFullSyncPromise: Promise<void> | null = null;\n\n constructor(context: BackupAndSyncContext) {\n this.#context = context;\n this.#atomicSyncQueue = new AtomicSyncQueue();\n }\n\n /**\n * Checks if syncing is currently in progress.\n *\n * @returns True if syncing is in progress.\n */\n get isInProgress(): boolean {\n return this.#context.controller.state.isAccountTreeSyncingInProgress;\n }\n\n /**\n * Checks if the account tree has been synced at least once.\n *\n * @returns True if the account tree has been synced at least once.\n */\n get hasSyncedAtLeastOnce(): boolean {\n return this.#context.controller.state\n .hasAccountTreeSyncingSyncedAtLeastOnce;\n }\n\n /**\n * Checks if backup and sync is enabled by checking UserStorageController state.\n *\n * @returns True if backup and sync + account syncing is enabled.\n */\n get isBackupAndSyncEnabled(): boolean {\n const userStorageControllerState = this.#context.messenger.call(\n 'UserStorageController:getState',\n );\n const { isAccountSyncingEnabled, isBackupAndSyncEnabled } =\n userStorageControllerState;\n\n return isBackupAndSyncEnabled && isAccountSyncingEnabled;\n }\n\n /**\n * Clears the atomic queue and resets ongoing operations.\n */\n clearState(): void {\n this.#atomicSyncQueue.clear();\n this.#ongoingFullSyncPromise = null;\n this.#firstOngoingFullSyncPromise = null;\n }\n\n /**\n * Handles changes to the user storage state.\n * Used to clear the backup and sync service state.\n *\n * @param state - The new user storage state.\n */\n handleUserStorageStateChange(\n state: UserStorageController.UserStorageControllerState,\n ): void {\n if (!state.isAccountSyncingEnabled || !state.isBackupAndSyncEnabled) {\n // If either syncing is disabled, clear the account tree state\n this.clearState();\n }\n }\n\n /**\n * Gets the entropy wallet associated with the given wallet ID.\n *\n * @param walletId - The wallet ID to look up.\n * @returns The associated entropy wallet, or undefined if not found.\n */\n #getEntropyWallet(\n walletId: AccountWalletId,\n ): AccountWalletEntropyObject | undefined {\n const wallet = this.#context.controller.state.accountTree.wallets[walletId];\n return wallet?.type === AccountWalletType.Entropy ? wallet : undefined;\n }\n\n /**\n * Sets up cleanup for ongoing sync promise tracking without affecting error propagation.\n *\n * @param promise - The promise to track and clean up\n * @returns The same promise (for chaining)\n */\n #setupOngoingPromiseCleanup(promise: Promise<void>): Promise<void> {\n this.#ongoingFullSyncPromise = promise;\n // Set up cleanup without affecting the returned promise\n promise\n .finally(() => {\n this.#ongoingFullSyncPromise = null;\n })\n .catch(() => {\n // Only ignore errors from the cleanup operation itself\n // The original promise errors are still propagated to callers\n });\n return promise;\n }\n\n /**\n * Enqueues a single wallet sync operation (fire-and-forget).\n *\n * @param walletId - The wallet ID to sync.\n */\n enqueueSingleWalletSync(walletId: AccountWalletId): void {\n if (!this.isBackupAndSyncEnabled) {\n return;\n }\n\n if (!this.hasSyncedAtLeastOnce) {\n // Run big sync\n // eslint-disable-next-line no-void\n void this.performFullSync();\n return;\n }\n // eslint-disable-next-line no-void\n void this.#atomicSyncQueue.enqueue(() =>\n this.#performSingleWalletSyncInner(walletId),\n );\n }\n\n /**\n * Enqueues a single group sync operation (fire-and-forget).\n *\n * @param groupId - The group ID to sync.\n */\n enqueueSingleGroupSync(groupId: AccountGroupId): void {\n if (!this.isBackupAndSyncEnabled) {\n return;\n }\n\n if (!this.hasSyncedAtLeastOnce) {\n // Run big sync\n // eslint-disable-next-line no-void\n void this.performFullSync();\n return;\n }\n\n // eslint-disable-next-line no-void\n void this.#atomicSyncQueue.enqueue(() =>\n this.#performSingleGroupSyncInner(groupId),\n );\n }\n\n /**\n * Performs a full synchronization of the local account tree with user storage, ensuring consistency\n * between local state and cloud-stored account data.\n * If a full sync is already in progress, it will return the ongoing promise.\n * This clears the atomic sync queue before starting the full sync.\n *\n * NOTE: in some very edge cases, this can be ran concurrently if triggered quickly after\n * toggling back and forth the backup and sync feature from the UI.\n *\n * @returns A promise that resolves when the sync is complete.\n */\n async performFullSync(): Promise<void> {\n if (!this.isBackupAndSyncEnabled) {\n return undefined;\n }\n\n // If there's an ongoing sync (including first sync), return it\n if (this.#ongoingFullSyncPromise) {\n return this.#ongoingFullSyncPromise;\n }\n\n // Create a new ongoing sync (sequential calls after previous completed)\n const newSyncPromise = this.#atomicSyncQueue.clearAndEnqueue(() =>\n this.#performFullSyncInner(),\n );\n\n // First sync setup - create and cache the first sync promise\n if (!this.#firstOngoingFullSyncPromise) {\n this.#firstOngoingFullSyncPromise = newSyncPromise;\n }\n\n return this.#setupOngoingPromiseCleanup(newSyncPromise);\n }\n\n /**\n * Performs a full synchronization of the local account tree with user storage, ensuring consistency\n * between local state and cloud-stored account data.\n *\n * If the first ever full sync is already in progress, it will return the ongoing promise.\n * If the first ever full sync has already completed, it will resolve and NOT start a new sync.\n *\n * This clears the atomic sync queue before starting the full sync.\n *\n * @returns A promise that resolves when the sync is complete.\n */\n async performFullSyncAtLeastOnce(): Promise<void> {\n if (!this.isBackupAndSyncEnabled) {\n return undefined;\n }\n\n if (!this.#firstOngoingFullSyncPromise) {\n this.#firstOngoingFullSyncPromise = this.#atomicSyncQueue.clearAndEnqueue(\n () => this.#performFullSyncInner(),\n );\n // eslint-disable-next-line no-void\n void this.#setupOngoingPromiseCleanup(this.#firstOngoingFullSyncPromise);\n }\n\n return this.#firstOngoingFullSyncPromise;\n }\n\n /**\n * Performs a full synchronization of the local account tree with user storage, ensuring consistency\n * between local state and cloud-stored account data.\n *\n * This method performs a comprehensive sync operation that:\n * 1. Identifies all local entropy wallets that can be synchronized\n * 2. Performs legacy account syncing if needed (for backwards compatibility)\n * - Disables subsequent legacy syncing by setting a flag in user storage\n * - Exits early if multichain account syncing is disabled after legacy sync\n * 3. Executes multichain account syncing for each wallet:\n * - Syncs wallet metadata bidirectionally\n * - Creates missing local groups from user storage data (or pushes local groups if none exist remotely)\n * - Refreshes local state to reflect newly created groups\n * - Syncs group metadata bidirectionally\n *\n * The sync is atomic per wallet with rollback on errors, but continues processing other wallets\n * if individual wallet sync fails. A global lock prevents concurrent sync operations.\n *\n * During this process, all other atomic multichain related user storage updates are blocked.\n *\n * @throws Will throw if the sync operation encounters unrecoverable errors\n */\n async #performFullSyncInner(): Promise<void> {\n // Prevent multiple syncs from running at the same time.\n // Also prevents atomic updates from being applied while syncing is in progress.\n if (this.isInProgress) {\n return;\n }\n\n // Set isAccountTreeSyncingInProgress immediately to prevent race conditions\n this.#context.controllerStateUpdateFn(\n (state: AccountTreeControllerState) => {\n state.isAccountTreeSyncingInProgress = true;\n },\n );\n\n // Encapsulate the sync logic in a function to allow tracing\n const bigSyncFn = async () => {\n try {\n // 1. Identifies all local entropy wallets that can be synchronized\n const localSyncableWallets = getLocalEntropyWallets(this.#context);\n\n if (!localSyncableWallets.length) {\n // No wallets to sync, just return. This shouldn't happen.\n return;\n }\n\n // 2. Iterate over each local wallet\n for (const wallet of localSyncableWallets) {\n const entropySourceId = wallet.metadata.entropy.id;\n\n let walletProfileId: ProfileId;\n let walletFromUserStorage: UserStorageSyncedWallet | null;\n let groupsFromUserStorage: UserStorageSyncedWalletGroup[];\n\n try {\n walletProfileId = await getProfileId(\n this.#context,\n entropySourceId,\n );\n\n [walletFromUserStorage, groupsFromUserStorage] = await Promise.all([\n getWalletFromUserStorage(this.#context, entropySourceId),\n getAllGroupsFromUserStorage(this.#context, entropySourceId),\n ]);\n\n // 2.1 Decide if we need to perform legacy account syncing\n if (\n !walletFromUserStorage ||\n !walletFromUserStorage.isLegacyAccountSyncingDisabled\n ) {\n // 2.2 Perform legacy account syncing\n // This will migrate legacy account data to the new structure.\n // This operation will only be performed once.\n await performLegacyAccountSyncing(\n this.#context,\n entropySourceId,\n walletProfileId,\n );\n }\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n const errorString = `Legacy syncing failed for wallet ${wallet.id}: ${errorMessage}`;\n\n backupAndSyncLogger(errorString);\n throw new Error(errorString);\n }\n\n // 3. Execute multichain account syncing\n let stateSnapshot: StateSnapshot | undefined;\n\n try {\n // 3.1 Wallet syncing\n // Create a state snapshot before processing each wallet for potential rollback\n stateSnapshot = createStateSnapshot(this.#context);\n\n // Sync wallet metadata bidirectionally\n await syncWalletMetadata(\n this.#context,\n wallet,\n walletFromUserStorage,\n walletProfileId,\n );\n\n // 3.2 Groups syncing\n // If groups data does not exist in user storage yet, create it\n if (!groupsFromUserStorage.length) {\n // If no groups exist in user storage, we can push all groups from the wallet to the user storage and exit\n await pushGroupToUserStorageBatch(\n this.#context,\n getLocalGroupsForEntropyWallet(this.#context, wallet.id),\n entropySourceId,\n );\n\n continue; // No need to proceed with metadata comparison if groups are new\n }\n\n // Create local groups for each group from user storage if they do not exist\n // This will ensure that we have all groups available locally before syncing metadata\n await createLocalGroupsFromUserStorage(\n this.#context,\n groupsFromUserStorage,\n entropySourceId,\n walletProfileId,\n );\n\n // Sync group metadata bidirectionally\n await syncGroupsMetadata(\n this.#context,\n wallet,\n groupsFromUserStorage,\n entropySourceId,\n walletProfileId,\n );\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n const errorString = `Error during multichain account syncing for wallet ${wallet.id}: ${errorMessage}`;\n\n backupAndSyncLogger(errorString);\n\n // Attempt to rollback state changes for this wallet\n try {\n if (!stateSnapshot) {\n throw new Error(\n `State snapshot is missing for wallet ${wallet.id}`,\n );\n }\n restoreStateFromSnapshot(this.#context, stateSnapshot);\n backupAndSyncLogger(\n `Rolled back state changes for wallet ${wallet.id}`,\n );\n } catch (rollbackError) {\n backupAndSyncLogger(\n `Failed to rollback state for wallet ${wallet.id}:`,\n rollbackError instanceof Error\n ? rollbackError.message\n : String(rollbackError),\n );\n }\n\n // Continue with next wallet instead of failing the entire sync\n continue;\n }\n }\n } catch (error) {\n backupAndSyncLogger('Error during multichain account syncing:', error);\n throw error;\n }\n\n this.#context.controllerStateUpdateFn((state) => {\n state.hasAccountTreeSyncingSyncedAtLeastOnce = true;\n });\n };\n\n // Execute the big sync function with tracing and ensure state cleanup\n try {\n await this.#context.traceFn(\n {\n name: TraceName.AccountSyncFull,\n },\n bigSyncFn,\n );\n } finally {\n // Always reset state, regardless of success or failure\n this.#context.controllerStateUpdateFn(\n (state: AccountTreeControllerState) => {\n state.isAccountTreeSyncingInProgress = false;\n },\n );\n }\n }\n\n /**\n * Performs a single wallet's bidirectional metadata sync with user storage.\n *\n * @param walletId - The wallet ID to sync.\n */\n async #performSingleWalletSyncInner(\n walletId: AccountWalletId,\n ): Promise<void> {\n try {\n const wallet = this.#getEntropyWallet(walletId);\n if (!wallet) {\n return; // Only sync entropy wallets\n }\n\n const entropySourceId = wallet.metadata.entropy.id;\n const walletProfileId = await getProfileId(\n this.#context,\n entropySourceId,\n );\n const walletFromUserStorage = await getWalletFromUserStorage(\n this.#context,\n entropySourceId,\n );\n\n await syncWalletMetadata(\n this.#context,\n wallet,\n walletFromUserStorage,\n walletProfileId,\n );\n } catch (error) {\n backupAndSyncLogger(\n `Error in single wallet sync for ${walletId}:`,\n error,\n );\n throw error;\n }\n }\n\n /**\n * Performs a single group's bidirectional metadata sync with user storage.\n *\n * @param groupId - The group ID to sync.\n */\n async #performSingleGroupSyncInner(groupId: AccountGroupId): Promise<void> {\n try {\n const walletId = this.#context.groupIdToWalletId.get(groupId);\n if (!walletId) {\n return;\n }\n\n const wallet = this.#getEntropyWallet(walletId);\n if (!wallet) {\n return; // Only sync entropy wallets\n }\n\n const group = wallet.groups[groupId];\n if (!group) {\n return;\n }\n\n const entropySourceId = wallet.metadata.entropy.id;\n const walletProfileId = await getProfileId(\n this.#context,\n entropySourceId,\n );\n\n // Get the specific group from user storage\n const groupFromUserStorage = await getGroupFromUserStorage(\n this.#context,\n entropySourceId,\n group.metadata.entropy.groupIndex,\n );\n\n await syncGroupMetadata(\n this.#context,\n group,\n groupFromUserStorage,\n entropySourceId,\n walletProfileId,\n );\n } catch (error) {\n backupAndSyncLogger(`Error in single group sync for ${groupId}:`, error);\n throw error;\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.cjs","sourceRoot":"","sources":["../../../src/backup-and-sync/service/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AccountGroupId, AccountWalletId } from '@metamask/account-api';\nimport { AccountWalletType } from '@metamask/account-api';\nimport type { UserStorageController } from '@metamask/profile-sync-controller';\n\nimport { AtomicSyncQueue } from './atomic-sync-queue';\nimport { backupAndSyncLogger } from '../../logger';\nimport type { AccountTreeControllerState } from '../../types';\nimport type { AccountWalletEntropyObject } from '../../wallet';\nimport { TraceName } from '../analytics';\nimport type { ProfileId } from '../authentication';\nimport { getProfileId } from '../authentication';\nimport {\n createLocalGroupsFromUserStorage,\n performLegacyAccountSyncing,\n syncGroupsMetadata,\n syncGroupMetadata,\n syncWalletMetadata,\n} from '../syncing';\nimport type {\n BackupAndSyncContext,\n UserStorageSyncedWallet,\n UserStorageSyncedWalletGroup,\n} from '../types';\nimport {\n getAllGroupsFromUserStorage,\n getGroupFromUserStorage,\n getWalletFromUserStorage,\n pushGroupToUserStorageBatch,\n} from '../user-storage';\nimport {\n createStateSnapshot,\n restoreStateFromSnapshot,\n getLocalEntropyWallets,\n getLocalGroupsForEntropyWallet,\n} from '../utils';\nimport type { StateSnapshot } from '../utils';\n\n/**\n * Service responsible for managing all backup and sync operations.\n *\n * This service handles:\n * - Full sync operations\n * - Single item sync operations\n * - Sync queue management\n * - Sync state management\n */\nexport class BackupAndSyncService {\n readonly #context: BackupAndSyncContext;\n\n /**\n * Queue manager for atomic sync operations.\n */\n readonly #atomicSyncQueue: AtomicSyncQueue;\n\n /**\n * Cached promise for ongoing full sync operations.\n * Ensures multiple callers await the same sync operation.\n */\n #ongoingFullSyncPromise: Promise<void> | null = null;\n\n /**\n * Cached promise for the first ongoing full sync operation.\n * Ensures multiple callers await the same sync operation.\n */\n #firstOngoingFullSyncPromise: Promise<void> | null = null;\n\n constructor(context: BackupAndSyncContext) {\n this.#context = context;\n this.#atomicSyncQueue = new AtomicSyncQueue();\n }\n\n /**\n * Checks if syncing is currently in progress.\n *\n * @returns True if syncing is in progress.\n */\n get isInProgress(): boolean {\n return this.#context.controller.state.isAccountTreeSyncingInProgress;\n }\n\n /**\n * Checks if the account tree has been synced at least once.\n *\n * @returns True if the account tree has been synced at least once.\n */\n get hasSyncedAtLeastOnce(): boolean {\n return this.#context.controller.state\n .hasAccountTreeSyncingSyncedAtLeastOnce;\n }\n\n /**\n * Checks if backup and sync is enabled by checking UserStorageController state.\n *\n * @returns True if backup and sync + account syncing is enabled.\n */\n get isBackupAndSyncEnabled(): boolean {\n const userStorageControllerState = this.#context.messenger.call(\n 'UserStorageController:getState',\n );\n const { isAccountSyncingEnabled, isBackupAndSyncEnabled } =\n userStorageControllerState;\n\n return isBackupAndSyncEnabled && isAccountSyncingEnabled;\n }\n\n /**\n * Clears the atomic queue and resets ongoing operations.\n */\n clearState(): void {\n this.#atomicSyncQueue.clear();\n this.#ongoingFullSyncPromise = null;\n this.#firstOngoingFullSyncPromise = null;\n }\n\n /**\n * Handles changes to the user storage state.\n * Used to clear the backup and sync service state.\n *\n * @param state - The new user storage state.\n */\n handleUserStorageStateChange(\n state: UserStorageController.UserStorageControllerState,\n ): void {\n if (!state.isAccountSyncingEnabled || !state.isBackupAndSyncEnabled) {\n // If either syncing is disabled, clear the account tree state\n this.clearState();\n }\n }\n\n /**\n * Gets the entropy wallet associated with the given wallet ID.\n *\n * @param walletId - The wallet ID to look up.\n * @returns The associated entropy wallet, or undefined if not found.\n */\n #getEntropyWallet(\n walletId: AccountWalletId,\n ): AccountWalletEntropyObject | undefined {\n const wallet = this.#context.controller.state.accountTree.wallets[walletId];\n return wallet?.type === AccountWalletType.Entropy ? wallet : undefined;\n }\n\n /**\n * Sets up cleanup for ongoing sync promise tracking without affecting error propagation.\n *\n * @param promise - The promise to track and clean up\n * @returns The same promise (for chaining)\n */\n #setupOngoingPromiseCleanup(promise: Promise<void>): Promise<void> {\n this.#ongoingFullSyncPromise = promise;\n // Set up cleanup without affecting the returned promise\n promise\n .finally(() => {\n this.#ongoingFullSyncPromise = null;\n })\n .catch(() => {\n // Only ignore errors from the cleanup operation itself\n // The original promise errors are still propagated to callers\n });\n return promise;\n }\n\n /**\n * Enqueues a single wallet sync operation (fire-and-forget).\n * If the first full sync has not yet occurred, it does nothing.\n *\n * @param walletId - The wallet ID to sync.\n */\n enqueueSingleWalletSync(walletId: AccountWalletId): void {\n if (!this.isBackupAndSyncEnabled || !this.hasSyncedAtLeastOnce) {\n return;\n }\n\n // eslint-disable-next-line no-void\n void this.#atomicSyncQueue.enqueue(() =>\n this.#performSingleWalletSyncInner(walletId),\n );\n }\n\n /**\n * Enqueues a single group sync operation (fire-and-forget).\n * If the first full sync has not yet occurred, it does nothing.\n *\n * @param groupId - The group ID to sync.\n */\n enqueueSingleGroupSync(groupId: AccountGroupId): void {\n if (!this.isBackupAndSyncEnabled || !this.hasSyncedAtLeastOnce) {\n return;\n }\n\n // eslint-disable-next-line no-void\n void this.#atomicSyncQueue.enqueue(() =>\n this.#performSingleGroupSyncInner(groupId),\n );\n }\n\n /**\n * Performs a full synchronization of the local account tree with user storage, ensuring consistency\n * between local state and cloud-stored account data.\n * If a full sync is already in progress, it will return the ongoing promise.\n * This clears the atomic sync queue before starting the full sync.\n *\n * NOTE: in some very edge cases, this can be ran concurrently if triggered quickly after\n * toggling back and forth the backup and sync feature from the UI.\n *\n * @returns A promise that resolves when the sync is complete.\n */\n async performFullSync(): Promise<void> {\n if (!this.isBackupAndSyncEnabled) {\n return undefined;\n }\n\n // If there's an ongoing sync (including first sync), return it\n if (this.#ongoingFullSyncPromise) {\n return this.#ongoingFullSyncPromise;\n }\n\n // Create a new ongoing sync (sequential calls after previous completed)\n const newSyncPromise = this.#atomicSyncQueue.clearAndEnqueue(() =>\n this.#performFullSyncInner(),\n );\n\n // First sync setup - create and cache the first sync promise\n if (!this.#firstOngoingFullSyncPromise) {\n this.#firstOngoingFullSyncPromise = newSyncPromise;\n }\n\n return this.#setupOngoingPromiseCleanup(newSyncPromise);\n }\n\n /**\n * Performs a full synchronization of the local account tree with user storage, ensuring consistency\n * between local state and cloud-stored account data.\n *\n * If the first ever full sync is already in progress, it will return the ongoing promise.\n * If the first ever full sync has already completed, it will resolve and NOT start a new sync.\n *\n * This clears the atomic sync queue before starting the full sync.\n *\n * @returns A promise that resolves when the sync is complete.\n */\n async performFullSyncAtLeastOnce(): Promise<void> {\n if (!this.isBackupAndSyncEnabled) {\n return undefined;\n }\n\n if (!this.#firstOngoingFullSyncPromise) {\n this.#firstOngoingFullSyncPromise = this.#atomicSyncQueue.clearAndEnqueue(\n () => this.#performFullSyncInner(),\n );\n // eslint-disable-next-line no-void\n void this.#setupOngoingPromiseCleanup(this.#firstOngoingFullSyncPromise);\n }\n\n return this.#firstOngoingFullSyncPromise;\n }\n\n /**\n * Performs a full synchronization of the local account tree with user storage, ensuring consistency\n * between local state and cloud-stored account data.\n *\n * This method performs a comprehensive sync operation that:\n * 1. Identifies all local entropy wallets that can be synchronized\n * 2. Performs legacy account syncing if needed (for backwards compatibility)\n * - Disables subsequent legacy syncing by setting a flag in user storage\n * - Exits early if multichain account syncing is disabled after legacy sync\n * 3. Executes multichain account syncing for each wallet:\n * - Syncs wallet metadata bidirectionally\n * - Creates missing local groups from user storage data (or pushes local groups if none exist remotely)\n * - Refreshes local state to reflect newly created groups\n * - Syncs group metadata bidirectionally\n *\n * The sync is atomic per wallet with rollback on errors, but continues processing other wallets\n * if individual wallet sync fails. A global lock prevents concurrent sync operations.\n *\n * During this process, all other atomic multichain related user storage updates are blocked.\n *\n * @throws Will throw if the sync operation encounters unrecoverable errors\n */\n async #performFullSyncInner(): Promise<void> {\n // Prevent multiple syncs from running at the same time.\n // Also prevents atomic updates from being applied while syncing is in progress.\n if (this.isInProgress) {\n return;\n }\n\n // Set isAccountTreeSyncingInProgress immediately to prevent race conditions\n this.#context.controllerStateUpdateFn(\n (state: AccountTreeControllerState) => {\n state.isAccountTreeSyncingInProgress = true;\n },\n );\n\n // Encapsulate the sync logic in a function to allow tracing\n const bigSyncFn = async () => {\n try {\n // 1. Identifies all local entropy wallets that can be synchronized\n const localSyncableWallets = getLocalEntropyWallets(this.#context);\n\n if (!localSyncableWallets.length) {\n // No wallets to sync, just return. This shouldn't happen.\n return;\n }\n\n // 2. Iterate over each local wallet\n for (const wallet of localSyncableWallets) {\n const entropySourceId = wallet.metadata.entropy.id;\n\n let walletProfileId: ProfileId;\n let walletFromUserStorage: UserStorageSyncedWallet | null;\n let groupsFromUserStorage: UserStorageSyncedWalletGroup[];\n\n try {\n walletProfileId = await getProfileId(\n this.#context,\n entropySourceId,\n );\n\n [walletFromUserStorage, groupsFromUserStorage] = await Promise.all([\n getWalletFromUserStorage(this.#context, entropySourceId),\n getAllGroupsFromUserStorage(this.#context, entropySourceId),\n ]);\n\n // 2.1 Decide if we need to perform legacy account syncing\n if (\n !walletFromUserStorage ||\n !walletFromUserStorage.isLegacyAccountSyncingDisabled\n ) {\n // 2.2 Perform legacy account syncing\n // This will migrate legacy account data to the new structure.\n // This operation will only be performed once.\n await performLegacyAccountSyncing(\n this.#context,\n entropySourceId,\n walletProfileId,\n );\n }\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n const errorString = `Legacy syncing failed for wallet ${wallet.id}: ${errorMessage}`;\n\n backupAndSyncLogger(errorString);\n throw new Error(errorString);\n }\n\n // 3. Execute multichain account syncing\n let stateSnapshot: StateSnapshot | undefined;\n\n try {\n // 3.1 Wallet syncing\n // Create a state snapshot before processing each wallet for potential rollback\n stateSnapshot = createStateSnapshot(this.#context);\n\n // Sync wallet metadata bidirectionally\n await syncWalletMetadata(\n this.#context,\n wallet,\n walletFromUserStorage,\n walletProfileId,\n );\n\n // 3.2 Groups syncing\n // If groups data does not exist in user storage yet, create it\n if (!groupsFromUserStorage.length) {\n // If no groups exist in user storage, we can push all groups from the wallet to the user storage and exit\n await pushGroupToUserStorageBatch(\n this.#context,\n getLocalGroupsForEntropyWallet(this.#context, wallet.id),\n entropySourceId,\n );\n\n continue; // No need to proceed with metadata comparison if groups are new\n }\n\n // Create local groups for each group from user storage if they do not exist\n // This will ensure that we have all groups available locally before syncing metadata\n await createLocalGroupsFromUserStorage(\n this.#context,\n groupsFromUserStorage,\n entropySourceId,\n walletProfileId,\n );\n\n // Sync group metadata bidirectionally\n await syncGroupsMetadata(\n this.#context,\n wallet,\n groupsFromUserStorage,\n entropySourceId,\n walletProfileId,\n );\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n const errorString = `Error during multichain account syncing for wallet ${wallet.id}: ${errorMessage}`;\n\n backupAndSyncLogger(errorString);\n\n // Attempt to rollback state changes for this wallet\n try {\n if (!stateSnapshot) {\n throw new Error(\n `State snapshot is missing for wallet ${wallet.id}`,\n );\n }\n restoreStateFromSnapshot(this.#context, stateSnapshot);\n backupAndSyncLogger(\n `Rolled back state changes for wallet ${wallet.id}`,\n );\n } catch (rollbackError) {\n backupAndSyncLogger(\n `Failed to rollback state for wallet ${wallet.id}:`,\n rollbackError instanceof Error\n ? rollbackError.message\n : String(rollbackError),\n );\n }\n\n // Continue with next wallet instead of failing the entire sync\n continue;\n }\n }\n } catch (error) {\n backupAndSyncLogger('Error during multichain account syncing:', error);\n throw error;\n }\n\n this.#context.controllerStateUpdateFn((state) => {\n state.hasAccountTreeSyncingSyncedAtLeastOnce = true;\n });\n };\n\n // Execute the big sync function with tracing and ensure state cleanup\n try {\n await this.#context.traceFn(\n {\n name: TraceName.AccountSyncFull,\n },\n bigSyncFn,\n );\n } finally {\n // Always reset state, regardless of success or failure\n this.#context.controllerStateUpdateFn(\n (state: AccountTreeControllerState) => {\n state.isAccountTreeSyncingInProgress = false;\n },\n );\n }\n }\n\n /**\n * Performs a single wallet's bidirectional metadata sync with user storage.\n *\n * @param walletId - The wallet ID to sync.\n */\n async #performSingleWalletSyncInner(\n walletId: AccountWalletId,\n ): Promise<void> {\n try {\n const wallet = this.#getEntropyWallet(walletId);\n if (!wallet) {\n return; // Only sync entropy wallets\n }\n\n const entropySourceId = wallet.metadata.entropy.id;\n const walletProfileId = await getProfileId(\n this.#context,\n entropySourceId,\n );\n const walletFromUserStorage = await getWalletFromUserStorage(\n this.#context,\n entropySourceId,\n );\n\n await syncWalletMetadata(\n this.#context,\n wallet,\n walletFromUserStorage,\n walletProfileId,\n );\n } catch (error) {\n backupAndSyncLogger(\n `Error in single wallet sync for ${walletId}:`,\n error,\n );\n throw error;\n }\n }\n\n /**\n * Performs a single group's bidirectional metadata sync with user storage.\n *\n * @param groupId - The group ID to sync.\n */\n async #performSingleGroupSyncInner(groupId: AccountGroupId): Promise<void> {\n try {\n const walletId = this.#context.groupIdToWalletId.get(groupId);\n if (!walletId) {\n return;\n }\n\n const wallet = this.#getEntropyWallet(walletId);\n if (!wallet) {\n return; // Only sync entropy wallets\n }\n\n const group = wallet.groups[groupId];\n if (!group) {\n return;\n }\n\n const entropySourceId = wallet.metadata.entropy.id;\n const walletProfileId = await getProfileId(\n this.#context,\n entropySourceId,\n );\n\n // Get the specific group from user storage\n const groupFromUserStorage = await getGroupFromUserStorage(\n this.#context,\n entropySourceId,\n group.metadata.entropy.groupIndex,\n );\n\n await syncGroupMetadata(\n this.#context,\n group,\n groupFromUserStorage,\n entropySourceId,\n walletProfileId,\n );\n } catch (error) {\n backupAndSyncLogger(`Error in single group sync for ${groupId}:`, error);\n throw error;\n }\n }\n}\n"]}
|
|
@@ -44,12 +44,14 @@ export declare class BackupAndSyncService {
|
|
|
44
44
|
handleUserStorageStateChange(state: UserStorageController.UserStorageControllerState): void;
|
|
45
45
|
/**
|
|
46
46
|
* Enqueues a single wallet sync operation (fire-and-forget).
|
|
47
|
+
* If the first full sync has not yet occurred, it does nothing.
|
|
47
48
|
*
|
|
48
49
|
* @param walletId - The wallet ID to sync.
|
|
49
50
|
*/
|
|
50
51
|
enqueueSingleWalletSync(walletId: AccountWalletId): void;
|
|
51
52
|
/**
|
|
52
53
|
* Enqueues a single group sync operation (fire-and-forget).
|
|
54
|
+
* If the first full sync has not yet occurred, it does nothing.
|
|
53
55
|
*
|
|
54
56
|
* @param groupId - The group ID to sync.
|
|
55
57
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../../../src/backup-and-sync/service/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,8BAA8B;AAE7E,OAAO,KAAK,EAAE,qBAAqB,EAAE,0CAA0C;AAgB/E,OAAO,KAAK,EACV,oBAAoB,EAGrB,qBAAiB;AAelB;;;;;;;;GAQG;AACH,qBAAa,oBAAoB;;gBAoBnB,OAAO,EAAE,oBAAoB;IAKzC;;;;OAIG;IACH,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED;;;;OAIG;IACH,IAAI,oBAAoB,IAAI,OAAO,CAGlC;IAED;;;;OAIG;IACH,IAAI,sBAAsB,IAAI,OAAO,CAQpC;IAED;;OAEG;IACH,UAAU,IAAI,IAAI;IAMlB;;;;;OAKG;IACH,4BAA4B,CAC1B,KAAK,EAAE,qBAAqB,CAAC,0BAA0B,GACtD,IAAI;IAwCP
|
|
1
|
+
{"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../../../src/backup-and-sync/service/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,8BAA8B;AAE7E,OAAO,KAAK,EAAE,qBAAqB,EAAE,0CAA0C;AAgB/E,OAAO,KAAK,EACV,oBAAoB,EAGrB,qBAAiB;AAelB;;;;;;;;GAQG;AACH,qBAAa,oBAAoB;;gBAoBnB,OAAO,EAAE,oBAAoB;IAKzC;;;;OAIG;IACH,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED;;;;OAIG;IACH,IAAI,oBAAoB,IAAI,OAAO,CAGlC;IAED;;;;OAIG;IACH,IAAI,sBAAsB,IAAI,OAAO,CAQpC;IAED;;OAEG;IACH,UAAU,IAAI,IAAI;IAMlB;;;;;OAKG;IACH,4BAA4B,CAC1B,KAAK,EAAE,qBAAqB,CAAC,0BAA0B,GACtD,IAAI;IAwCP;;;;;OAKG;IACH,uBAAuB,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI;IAWxD;;;;;OAKG;IACH,sBAAsB,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAWrD;;;;;;;;;;OAUG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAuBtC;;;;;;;;;;OAUG;IACG,0BAA0B,IAAI,OAAO,CAAC,IAAI,CAAC;CAwSlD"}
|
|
@@ -44,12 +44,14 @@ export declare class BackupAndSyncService {
|
|
|
44
44
|
handleUserStorageStateChange(state: UserStorageController.UserStorageControllerState): void;
|
|
45
45
|
/**
|
|
46
46
|
* Enqueues a single wallet sync operation (fire-and-forget).
|
|
47
|
+
* If the first full sync has not yet occurred, it does nothing.
|
|
47
48
|
*
|
|
48
49
|
* @param walletId - The wallet ID to sync.
|
|
49
50
|
*/
|
|
50
51
|
enqueueSingleWalletSync(walletId: AccountWalletId): void;
|
|
51
52
|
/**
|
|
52
53
|
* Enqueues a single group sync operation (fire-and-forget).
|
|
54
|
+
* If the first full sync has not yet occurred, it does nothing.
|
|
53
55
|
*
|
|
54
56
|
* @param groupId - The group ID to sync.
|
|
55
57
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../../src/backup-and-sync/service/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,8BAA8B;AAE7E,OAAO,KAAK,EAAE,qBAAqB,EAAE,0CAA0C;AAgB/E,OAAO,KAAK,EACV,oBAAoB,EAGrB,qBAAiB;AAelB;;;;;;;;GAQG;AACH,qBAAa,oBAAoB;;gBAoBnB,OAAO,EAAE,oBAAoB;IAKzC;;;;OAIG;IACH,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED;;;;OAIG;IACH,IAAI,oBAAoB,IAAI,OAAO,CAGlC;IAED;;;;OAIG;IACH,IAAI,sBAAsB,IAAI,OAAO,CAQpC;IAED;;OAEG;IACH,UAAU,IAAI,IAAI;IAMlB;;;;;OAKG;IACH,4BAA4B,CAC1B,KAAK,EAAE,qBAAqB,CAAC,0BAA0B,GACtD,IAAI;IAwCP
|
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../../src/backup-and-sync/service/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,8BAA8B;AAE7E,OAAO,KAAK,EAAE,qBAAqB,EAAE,0CAA0C;AAgB/E,OAAO,KAAK,EACV,oBAAoB,EAGrB,qBAAiB;AAelB;;;;;;;;GAQG;AACH,qBAAa,oBAAoB;;gBAoBnB,OAAO,EAAE,oBAAoB;IAKzC;;;;OAIG;IACH,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED;;;;OAIG;IACH,IAAI,oBAAoB,IAAI,OAAO,CAGlC;IAED;;;;OAIG;IACH,IAAI,sBAAsB,IAAI,OAAO,CAQpC;IAED;;OAEG;IACH,UAAU,IAAI,IAAI;IAMlB;;;;;OAKG;IACH,4BAA4B,CAC1B,KAAK,EAAE,qBAAqB,CAAC,0BAA0B,GACtD,IAAI;IAwCP;;;;;OAKG;IACH,uBAAuB,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI;IAWxD;;;;;OAKG;IACH,sBAAsB,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAWrD;;;;;;;;;;OAUG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAuBtC;;;;;;;;;;OAUG;IACG,0BAA0B,IAAI,OAAO,CAAC,IAAI,CAAC;CAwSlD"}
|
|
@@ -97,17 +97,12 @@ export class BackupAndSyncService {
|
|
|
97
97
|
}
|
|
98
98
|
/**
|
|
99
99
|
* Enqueues a single wallet sync operation (fire-and-forget).
|
|
100
|
+
* If the first full sync has not yet occurred, it does nothing.
|
|
100
101
|
*
|
|
101
102
|
* @param walletId - The wallet ID to sync.
|
|
102
103
|
*/
|
|
103
104
|
enqueueSingleWalletSync(walletId) {
|
|
104
|
-
if (!this.isBackupAndSyncEnabled) {
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
if (!this.hasSyncedAtLeastOnce) {
|
|
108
|
-
// Run big sync
|
|
109
|
-
// eslint-disable-next-line no-void
|
|
110
|
-
void this.performFullSync();
|
|
105
|
+
if (!this.isBackupAndSyncEnabled || !this.hasSyncedAtLeastOnce) {
|
|
111
106
|
return;
|
|
112
107
|
}
|
|
113
108
|
// eslint-disable-next-line no-void
|
|
@@ -115,17 +110,12 @@ export class BackupAndSyncService {
|
|
|
115
110
|
}
|
|
116
111
|
/**
|
|
117
112
|
* Enqueues a single group sync operation (fire-and-forget).
|
|
113
|
+
* If the first full sync has not yet occurred, it does nothing.
|
|
118
114
|
*
|
|
119
115
|
* @param groupId - The group ID to sync.
|
|
120
116
|
*/
|
|
121
117
|
enqueueSingleGroupSync(groupId) {
|
|
122
|
-
if (!this.isBackupAndSyncEnabled) {
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
if (!this.hasSyncedAtLeastOnce) {
|
|
126
|
-
// Run big sync
|
|
127
|
-
// eslint-disable-next-line no-void
|
|
128
|
-
void this.performFullSync();
|
|
118
|
+
if (!this.isBackupAndSyncEnabled || !this.hasSyncedAtLeastOnce) {
|
|
129
119
|
return;
|
|
130
120
|
}
|
|
131
121
|
// eslint-disable-next-line no-void
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../../src/backup-and-sync/service/index.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,OAAO,EAAE,iBAAiB,EAAE,8BAA8B;AAG1D,OAAO,EAAE,eAAe,EAAE,gCAA4B;AACtD,OAAO,EAAE,mBAAmB,EAAE,yBAAqB;AAGnD,OAAO,EAAE,SAAS,EAAE,+BAAqB;AAEzC,OAAO,EAAE,YAAY,EAAE,oCAA0B;AACjD,OAAO,EACL,gCAAgC,EAChC,2BAA2B,EAC3B,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EACnB,6BAAmB;AAMpB,OAAO,EACL,2BAA2B,EAC3B,uBAAuB,EACvB,wBAAwB,EACxB,2BAA2B,EAC5B,kCAAwB;AACzB,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,sBAAsB,EACtB,8BAA8B,EAC/B,2BAAiB;AAGlB;;;;;;;;GAQG;AACH,MAAM,OAAO,oBAAoB;IAoB/B,YAAY,OAA6B;;QAnBhC,gDAA+B;QAExC;;WAEG;QACM,wDAAkC;QAE3C;;;WAGG;QACH,uDAAgD,IAAI,EAAC;QAErD;;;WAGG;QACH,4DAAqD,IAAI,EAAC;QAGxD,uBAAA,IAAI,iCAAY,OAAO,MAAA,CAAC;QACxB,uBAAA,IAAI,yCAAoB,IAAI,eAAe,EAAE,MAAA,CAAC;IAChD,CAAC;IAED;;;;OAIG;IACH,IAAI,YAAY;QACd,OAAO,uBAAA,IAAI,qCAAS,CAAC,UAAU,CAAC,KAAK,CAAC,8BAA8B,CAAC;IACvE,CAAC;IAED;;;;OAIG;IACH,IAAI,oBAAoB;QACtB,OAAO,uBAAA,IAAI,qCAAS,CAAC,UAAU,CAAC,KAAK;aAClC,sCAAsC,CAAC;IAC5C,CAAC;IAED;;;;OAIG;IACH,IAAI,sBAAsB;QACxB,MAAM,0BAA0B,GAAG,uBAAA,IAAI,qCAAS,CAAC,SAAS,CAAC,IAAI,CAC7D,gCAAgC,CACjC,CAAC;QACF,MAAM,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,GACvD,0BAA0B,CAAC;QAE7B,OAAO,sBAAsB,IAAI,uBAAuB,CAAC;IAC3D,CAAC;IAED;;OAEG;IACH,UAAU;QACR,uBAAA,IAAI,6CAAiB,CAAC,KAAK,EAAE,CAAC;QAC9B,uBAAA,IAAI,gDAA2B,IAAI,MAAA,CAAC;QACpC,uBAAA,IAAI,qDAAgC,IAAI,MAAA,CAAC;IAC3C,CAAC;IAED;;;;;OAKG;IACH,4BAA4B,CAC1B,KAAuD;QAEvD,IAAI,CAAC,KAAK,CAAC,uBAAuB,IAAI,CAAC,KAAK,CAAC,sBAAsB,EAAE;YACnE,8DAA8D;YAC9D,IAAI,CAAC,UAAU,EAAE,CAAC;SACnB;IACH,CAAC;IAmCD;;;;OAIG;IACH,uBAAuB,CAAC,QAAyB;QAC/C,IAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE;YAChC,OAAO;SACR;QAED,IAAI,CAAC,IAAI,CAAC,oBAAoB,EAAE;YAC9B,eAAe;YACf,mCAAmC;YACnC,KAAK,IAAI,CAAC,eAAe,EAAE,CAAC;YAC5B,OAAO;SACR;QACD,mCAAmC;QACnC,KAAK,uBAAA,IAAI,6CAAiB,CAAC,OAAO,CAAC,GAAG,EAAE,CACtC,uBAAA,IAAI,2FAA8B,MAAlC,IAAI,EAA+B,QAAQ,CAAC,CAC7C,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,sBAAsB,CAAC,OAAuB;QAC5C,IAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE;YAChC,OAAO;SACR;QAED,IAAI,CAAC,IAAI,CAAC,oBAAoB,EAAE;YAC9B,eAAe;YACf,mCAAmC;YACnC,KAAK,IAAI,CAAC,eAAe,EAAE,CAAC;YAC5B,OAAO;SACR;QAED,mCAAmC;QACnC,KAAK,uBAAA,IAAI,6CAAiB,CAAC,OAAO,CAAC,GAAG,EAAE,CACtC,uBAAA,IAAI,0FAA6B,MAAjC,IAAI,EAA8B,OAAO,CAAC,CAC3C,CAAC;IACJ,CAAC;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,eAAe;QACnB,IAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE;YAChC,OAAO,SAAS,CAAC;SAClB;QAED,+DAA+D;QAC/D,IAAI,uBAAA,IAAI,oDAAwB,EAAE;YAChC,OAAO,uBAAA,IAAI,oDAAwB,CAAC;SACrC;QAED,wEAAwE;QACxE,MAAM,cAAc,GAAG,uBAAA,IAAI,6CAAiB,CAAC,eAAe,CAAC,GAAG,EAAE,CAChE,uBAAA,IAAI,mFAAsB,MAA1B,IAAI,CAAwB,CAC7B,CAAC;QAEF,6DAA6D;QAC7D,IAAI,CAAC,uBAAA,IAAI,yDAA6B,EAAE;YACtC,uBAAA,IAAI,qDAAgC,cAAc,MAAA,CAAC;SACpD;QAED,OAAO,uBAAA,IAAI,yFAA4B,MAAhC,IAAI,EAA6B,cAAc,CAAC,CAAC;IAC1D,CAAC;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,0BAA0B;QAC9B,IAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE;YAChC,OAAO,SAAS,CAAC;SAClB;QAED,IAAI,CAAC,uBAAA,IAAI,yDAA6B,EAAE;YACtC,uBAAA,IAAI,qDAAgC,uBAAA,IAAI,6CAAiB,CAAC,eAAe,CACvE,GAAG,EAAE,CAAC,uBAAA,IAAI,mFAAsB,MAA1B,IAAI,CAAwB,CACnC,MAAA,CAAC;YACF,mCAAmC;YACnC,KAAK,uBAAA,IAAI,yFAA4B,MAAhC,IAAI,EAA6B,uBAAA,IAAI,yDAA6B,CAAC,CAAC;SAC1E;QAED,OAAO,uBAAA,IAAI,yDAA6B,CAAC;IAC3C,CAAC;CA0RF;iXA5ZG,QAAyB;IAEzB,MAAM,MAAM,GAAG,uBAAA,IAAI,qCAAS,CAAC,UAAU,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC5E,OAAO,MAAM,EAAE,IAAI,KAAK,iBAAiB,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AACzE,CAAC,+GAQ2B,OAAsB;IAChD,uBAAA,IAAI,gDAA2B,OAAO,MAAA,CAAC;IACvC,wDAAwD;IACxD,OAAO;SACJ,OAAO,CAAC,GAAG,EAAE;QACZ,uBAAA,IAAI,gDAA2B,IAAI,MAAA,CAAC;IACtC,CAAC,CAAC;SACD,KAAK,CAAC,GAAG,EAAE;QACV,uDAAuD;QACvD,8DAA8D;IAChE,CAAC,CAAC,CAAC;IACL,OAAO,OAAO,CAAC;AACjB,CAAC;AA4GD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,KAAK;IACH,wDAAwD;IACxD,gFAAgF;IAChF,IAAI,IAAI,CAAC,YAAY,EAAE;QACrB,OAAO;KACR;IAED,4EAA4E;IAC5E,uBAAA,IAAI,qCAAS,CAAC,uBAAuB,CACnC,CAAC,KAAiC,EAAE,EAAE;QACpC,KAAK,CAAC,8BAA8B,GAAG,IAAI,CAAC;IAC9C,CAAC,CACF,CAAC;IAEF,4DAA4D;IAC5D,MAAM,SAAS,GAAG,KAAK,IAAI,EAAE;QAC3B,IAAI;YACF,mEAAmE;YACnE,MAAM,oBAAoB,GAAG,sBAAsB,CAAC,uBAAA,IAAI,qCAAS,CAAC,CAAC;YAEnE,IAAI,CAAC,oBAAoB,CAAC,MAAM,EAAE;gBAChC,0DAA0D;gBAC1D,OAAO;aACR;YAED,oCAAoC;YACpC,KAAK,MAAM,MAAM,IAAI,oBAAoB,EAAE;gBACzC,MAAM,eAAe,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;gBAEnD,IAAI,eAA0B,CAAC;gBAC/B,IAAI,qBAAqD,CAAC;gBAC1D,IAAI,qBAAqD,CAAC;gBAE1D,IAAI;oBACF,eAAe,GAAG,MAAM,YAAY,CAClC,uBAAA,IAAI,qCAAS,EACb,eAAe,CAChB,CAAC;oBAEF,CAAC,qBAAqB,EAAE,qBAAqB,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;wBACjE,wBAAwB,CAAC,uBAAA,IAAI,qCAAS,EAAE,eAAe,CAAC;wBACxD,2BAA2B,CAAC,uBAAA,IAAI,qCAAS,EAAE,eAAe,CAAC;qBAC5D,CAAC,CAAC;oBAEH,0DAA0D;oBAC1D,IACE,CAAC,qBAAqB;wBACtB,CAAC,qBAAqB,CAAC,8BAA8B,EACrD;wBACA,qCAAqC;wBACrC,8DAA8D;wBAC9D,8CAA8C;wBAC9C,MAAM,2BAA2B,CAC/B,uBAAA,IAAI,qCAAS,EACb,eAAe,EACf,eAAe,CAChB,CAAC;qBACH;iBACF;gBAAC,OAAO,KAAK,EAAE;oBACd,MAAM,YAAY,GAChB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;oBACzD,MAAM,WAAW,GAAG,oCAAoC,MAAM,CAAC,EAAE,KAAK,YAAY,EAAE,CAAC;oBAErF,mBAAmB,CAAC,WAAW,CAAC,CAAC;oBACjC,MAAM,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC;iBAC9B;gBAED,wCAAwC;gBACxC,IAAI,aAAwC,CAAC;gBAE7C,IAAI;oBACF,qBAAqB;oBACrB,+EAA+E;oBAC/E,aAAa,GAAG,mBAAmB,CAAC,uBAAA,IAAI,qCAAS,CAAC,CAAC;oBAEnD,uCAAuC;oBACvC,MAAM,kBAAkB,CACtB,uBAAA,IAAI,qCAAS,EACb,MAAM,EACN,qBAAqB,EACrB,eAAe,CAChB,CAAC;oBAEF,qBAAqB;oBACrB,+DAA+D;oBAC/D,IAAI,CAAC,qBAAqB,CAAC,MAAM,EAAE;wBACjC,0GAA0G;wBAC1G,MAAM,2BAA2B,CAC/B,uBAAA,IAAI,qCAAS,EACb,8BAA8B,CAAC,uBAAA,IAAI,qCAAS,EAAE,MAAM,CAAC,EAAE,CAAC,EACxD,eAAe,CAChB,CAAC;wBAEF,SAAS,CAAC,gEAAgE;qBAC3E;oBAED,4EAA4E;oBAC5E,qFAAqF;oBACrF,MAAM,gCAAgC,CACpC,uBAAA,IAAI,qCAAS,EACb,qBAAqB,EACrB,eAAe,EACf,eAAe,CAChB,CAAC;oBAEF,sCAAsC;oBACtC,MAAM,kBAAkB,CACtB,uBAAA,IAAI,qCAAS,EACb,MAAM,EACN,qBAAqB,EACrB,eAAe,EACf,eAAe,CAChB,CAAC;iBACH;gBAAC,OAAO,KAAK,EAAE;oBACd,MAAM,YAAY,GAChB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;oBACzD,MAAM,WAAW,GAAG,sDAAsD,MAAM,CAAC,EAAE,KAAK,YAAY,EAAE,CAAC;oBAEvG,mBAAmB,CAAC,WAAW,CAAC,CAAC;oBAEjC,oDAAoD;oBACpD,IAAI;wBACF,IAAI,CAAC,aAAa,EAAE;4BAClB,MAAM,IAAI,KAAK,CACb,wCAAwC,MAAM,CAAC,EAAE,EAAE,CACpD,CAAC;yBACH;wBACD,wBAAwB,CAAC,uBAAA,IAAI,qCAAS,EAAE,aAAa,CAAC,CAAC;wBACvD,mBAAmB,CACjB,wCAAwC,MAAM,CAAC,EAAE,EAAE,CACpD,CAAC;qBACH;oBAAC,OAAO,aAAa,EAAE;wBACtB,mBAAmB,CACjB,uCAAuC,MAAM,CAAC,EAAE,GAAG,EACnD,aAAa,YAAY,KAAK;4BAC5B,CAAC,CAAC,aAAa,CAAC,OAAO;4BACvB,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAC1B,CAAC;qBACH;oBAED,+DAA+D;oBAC/D,SAAS;iBACV;aACF;SACF;QAAC,OAAO,KAAK,EAAE;YACd,mBAAmB,CAAC,0CAA0C,EAAE,KAAK,CAAC,CAAC;YACvE,MAAM,KAAK,CAAC;SACb;QAED,uBAAA,IAAI,qCAAS,CAAC,uBAAuB,CAAC,CAAC,KAAK,EAAE,EAAE;YAC9C,KAAK,CAAC,sCAAsC,GAAG,IAAI,CAAC;QACtD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,sEAAsE;IACtE,IAAI;QACF,MAAM,uBAAA,IAAI,qCAAS,CAAC,OAAO,CACzB;YACE,IAAI,EAAE,SAAS,CAAC,eAAe;SAChC,EACD,SAAS,CACV,CAAC;KACH;YAAS;QACR,uDAAuD;QACvD,uBAAA,IAAI,qCAAS,CAAC,uBAAuB,CACnC,CAAC,KAAiC,EAAE,EAAE;YACpC,KAAK,CAAC,8BAA8B,GAAG,KAAK,CAAC;QAC/C,CAAC,CACF,CAAC;KACH;AACH,CAAC;AAED;;;;GAIG;AACH,KAAK,6DACH,QAAyB;IAEzB,IAAI;QACF,MAAM,MAAM,GAAG,uBAAA,IAAI,+EAAkB,MAAtB,IAAI,EAAmB,QAAQ,CAAC,CAAC;QAChD,IAAI,CAAC,MAAM,EAAE;YACX,OAAO,CAAC,4BAA4B;SACrC;QAED,MAAM,eAAe,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACnD,MAAM,eAAe,GAAG,MAAM,YAAY,CACxC,uBAAA,IAAI,qCAAS,EACb,eAAe,CAChB,CAAC;QACF,MAAM,qBAAqB,GAAG,MAAM,wBAAwB,CAC1D,uBAAA,IAAI,qCAAS,EACb,eAAe,CAChB,CAAC;QAEF,MAAM,kBAAkB,CACtB,uBAAA,IAAI,qCAAS,EACb,MAAM,EACN,qBAAqB,EACrB,eAAe,CAChB,CAAC;KACH;IAAC,OAAO,KAAK,EAAE;QACd,mBAAmB,CACjB,mCAAmC,QAAQ,GAAG,EAC9C,KAAK,CACN,CAAC;QACF,MAAM,KAAK,CAAC;KACb;AACH,CAAC;AAED;;;;GAIG;AACH,KAAK,4DAA8B,OAAuB;IACxD,IAAI;QACF,MAAM,QAAQ,GAAG,uBAAA,IAAI,qCAAS,CAAC,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC9D,IAAI,CAAC,QAAQ,EAAE;YACb,OAAO;SACR;QAED,MAAM,MAAM,GAAG,uBAAA,IAAI,+EAAkB,MAAtB,IAAI,EAAmB,QAAQ,CAAC,CAAC;QAChD,IAAI,CAAC,MAAM,EAAE;YACX,OAAO,CAAC,4BAA4B;SACrC;QAED,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,CAAC,KAAK,EAAE;YACV,OAAO;SACR;QAED,MAAM,eAAe,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACnD,MAAM,eAAe,GAAG,MAAM,YAAY,CACxC,uBAAA,IAAI,qCAAS,EACb,eAAe,CAChB,CAAC;QAEF,2CAA2C;QAC3C,MAAM,oBAAoB,GAAG,MAAM,uBAAuB,CACxD,uBAAA,IAAI,qCAAS,EACb,eAAe,EACf,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,CAClC,CAAC;QAEF,MAAM,iBAAiB,CACrB,uBAAA,IAAI,qCAAS,EACb,KAAK,EACL,oBAAoB,EACpB,eAAe,EACf,eAAe,CAChB,CAAC;KACH;IAAC,OAAO,KAAK,EAAE;QACd,mBAAmB,CAAC,kCAAkC,OAAO,GAAG,EAAE,KAAK,CAAC,CAAC;QACzE,MAAM,KAAK,CAAC;KACb;AACH,CAAC","sourcesContent":["import type { AccountGroupId, AccountWalletId } from '@metamask/account-api';\nimport { AccountWalletType } from '@metamask/account-api';\nimport type { UserStorageController } from '@metamask/profile-sync-controller';\n\nimport { AtomicSyncQueue } from './atomic-sync-queue';\nimport { backupAndSyncLogger } from '../../logger';\nimport type { AccountTreeControllerState } from '../../types';\nimport type { AccountWalletEntropyObject } from '../../wallet';\nimport { TraceName } from '../analytics';\nimport type { ProfileId } from '../authentication';\nimport { getProfileId } from '../authentication';\nimport {\n createLocalGroupsFromUserStorage,\n performLegacyAccountSyncing,\n syncGroupsMetadata,\n syncGroupMetadata,\n syncWalletMetadata,\n} from '../syncing';\nimport type {\n BackupAndSyncContext,\n UserStorageSyncedWallet,\n UserStorageSyncedWalletGroup,\n} from '../types';\nimport {\n getAllGroupsFromUserStorage,\n getGroupFromUserStorage,\n getWalletFromUserStorage,\n pushGroupToUserStorageBatch,\n} from '../user-storage';\nimport {\n createStateSnapshot,\n restoreStateFromSnapshot,\n getLocalEntropyWallets,\n getLocalGroupsForEntropyWallet,\n} from '../utils';\nimport type { StateSnapshot } from '../utils';\n\n/**\n * Service responsible for managing all backup and sync operations.\n *\n * This service handles:\n * - Full sync operations\n * - Single item sync operations\n * - Sync queue management\n * - Sync state management\n */\nexport class BackupAndSyncService {\n readonly #context: BackupAndSyncContext;\n\n /**\n * Queue manager for atomic sync operations.\n */\n readonly #atomicSyncQueue: AtomicSyncQueue;\n\n /**\n * Cached promise for ongoing full sync operations.\n * Ensures multiple callers await the same sync operation.\n */\n #ongoingFullSyncPromise: Promise<void> | null = null;\n\n /**\n * Cached promise for the first ongoing full sync operation.\n * Ensures multiple callers await the same sync operation.\n */\n #firstOngoingFullSyncPromise: Promise<void> | null = null;\n\n constructor(context: BackupAndSyncContext) {\n this.#context = context;\n this.#atomicSyncQueue = new AtomicSyncQueue();\n }\n\n /**\n * Checks if syncing is currently in progress.\n *\n * @returns True if syncing is in progress.\n */\n get isInProgress(): boolean {\n return this.#context.controller.state.isAccountTreeSyncingInProgress;\n }\n\n /**\n * Checks if the account tree has been synced at least once.\n *\n * @returns True if the account tree has been synced at least once.\n */\n get hasSyncedAtLeastOnce(): boolean {\n return this.#context.controller.state\n .hasAccountTreeSyncingSyncedAtLeastOnce;\n }\n\n /**\n * Checks if backup and sync is enabled by checking UserStorageController state.\n *\n * @returns True if backup and sync + account syncing is enabled.\n */\n get isBackupAndSyncEnabled(): boolean {\n const userStorageControllerState = this.#context.messenger.call(\n 'UserStorageController:getState',\n );\n const { isAccountSyncingEnabled, isBackupAndSyncEnabled } =\n userStorageControllerState;\n\n return isBackupAndSyncEnabled && isAccountSyncingEnabled;\n }\n\n /**\n * Clears the atomic queue and resets ongoing operations.\n */\n clearState(): void {\n this.#atomicSyncQueue.clear();\n this.#ongoingFullSyncPromise = null;\n this.#firstOngoingFullSyncPromise = null;\n }\n\n /**\n * Handles changes to the user storage state.\n * Used to clear the backup and sync service state.\n *\n * @param state - The new user storage state.\n */\n handleUserStorageStateChange(\n state: UserStorageController.UserStorageControllerState,\n ): void {\n if (!state.isAccountSyncingEnabled || !state.isBackupAndSyncEnabled) {\n // If either syncing is disabled, clear the account tree state\n this.clearState();\n }\n }\n\n /**\n * Gets the entropy wallet associated with the given wallet ID.\n *\n * @param walletId - The wallet ID to look up.\n * @returns The associated entropy wallet, or undefined if not found.\n */\n #getEntropyWallet(\n walletId: AccountWalletId,\n ): AccountWalletEntropyObject | undefined {\n const wallet = this.#context.controller.state.accountTree.wallets[walletId];\n return wallet?.type === AccountWalletType.Entropy ? wallet : undefined;\n }\n\n /**\n * Sets up cleanup for ongoing sync promise tracking without affecting error propagation.\n *\n * @param promise - The promise to track and clean up\n * @returns The same promise (for chaining)\n */\n #setupOngoingPromiseCleanup(promise: Promise<void>): Promise<void> {\n this.#ongoingFullSyncPromise = promise;\n // Set up cleanup without affecting the returned promise\n promise\n .finally(() => {\n this.#ongoingFullSyncPromise = null;\n })\n .catch(() => {\n // Only ignore errors from the cleanup operation itself\n // The original promise errors are still propagated to callers\n });\n return promise;\n }\n\n /**\n * Enqueues a single wallet sync operation (fire-and-forget).\n *\n * @param walletId - The wallet ID to sync.\n */\n enqueueSingleWalletSync(walletId: AccountWalletId): void {\n if (!this.isBackupAndSyncEnabled) {\n return;\n }\n\n if (!this.hasSyncedAtLeastOnce) {\n // Run big sync\n // eslint-disable-next-line no-void\n void this.performFullSync();\n return;\n }\n // eslint-disable-next-line no-void\n void this.#atomicSyncQueue.enqueue(() =>\n this.#performSingleWalletSyncInner(walletId),\n );\n }\n\n /**\n * Enqueues a single group sync operation (fire-and-forget).\n *\n * @param groupId - The group ID to sync.\n */\n enqueueSingleGroupSync(groupId: AccountGroupId): void {\n if (!this.isBackupAndSyncEnabled) {\n return;\n }\n\n if (!this.hasSyncedAtLeastOnce) {\n // Run big sync\n // eslint-disable-next-line no-void\n void this.performFullSync();\n return;\n }\n\n // eslint-disable-next-line no-void\n void this.#atomicSyncQueue.enqueue(() =>\n this.#performSingleGroupSyncInner(groupId),\n );\n }\n\n /**\n * Performs a full synchronization of the local account tree with user storage, ensuring consistency\n * between local state and cloud-stored account data.\n * If a full sync is already in progress, it will return the ongoing promise.\n * This clears the atomic sync queue before starting the full sync.\n *\n * NOTE: in some very edge cases, this can be ran concurrently if triggered quickly after\n * toggling back and forth the backup and sync feature from the UI.\n *\n * @returns A promise that resolves when the sync is complete.\n */\n async performFullSync(): Promise<void> {\n if (!this.isBackupAndSyncEnabled) {\n return undefined;\n }\n\n // If there's an ongoing sync (including first sync), return it\n if (this.#ongoingFullSyncPromise) {\n return this.#ongoingFullSyncPromise;\n }\n\n // Create a new ongoing sync (sequential calls after previous completed)\n const newSyncPromise = this.#atomicSyncQueue.clearAndEnqueue(() =>\n this.#performFullSyncInner(),\n );\n\n // First sync setup - create and cache the first sync promise\n if (!this.#firstOngoingFullSyncPromise) {\n this.#firstOngoingFullSyncPromise = newSyncPromise;\n }\n\n return this.#setupOngoingPromiseCleanup(newSyncPromise);\n }\n\n /**\n * Performs a full synchronization of the local account tree with user storage, ensuring consistency\n * between local state and cloud-stored account data.\n *\n * If the first ever full sync is already in progress, it will return the ongoing promise.\n * If the first ever full sync has already completed, it will resolve and NOT start a new sync.\n *\n * This clears the atomic sync queue before starting the full sync.\n *\n * @returns A promise that resolves when the sync is complete.\n */\n async performFullSyncAtLeastOnce(): Promise<void> {\n if (!this.isBackupAndSyncEnabled) {\n return undefined;\n }\n\n if (!this.#firstOngoingFullSyncPromise) {\n this.#firstOngoingFullSyncPromise = this.#atomicSyncQueue.clearAndEnqueue(\n () => this.#performFullSyncInner(),\n );\n // eslint-disable-next-line no-void\n void this.#setupOngoingPromiseCleanup(this.#firstOngoingFullSyncPromise);\n }\n\n return this.#firstOngoingFullSyncPromise;\n }\n\n /**\n * Performs a full synchronization of the local account tree with user storage, ensuring consistency\n * between local state and cloud-stored account data.\n *\n * This method performs a comprehensive sync operation that:\n * 1. Identifies all local entropy wallets that can be synchronized\n * 2. Performs legacy account syncing if needed (for backwards compatibility)\n * - Disables subsequent legacy syncing by setting a flag in user storage\n * - Exits early if multichain account syncing is disabled after legacy sync\n * 3. Executes multichain account syncing for each wallet:\n * - Syncs wallet metadata bidirectionally\n * - Creates missing local groups from user storage data (or pushes local groups if none exist remotely)\n * - Refreshes local state to reflect newly created groups\n * - Syncs group metadata bidirectionally\n *\n * The sync is atomic per wallet with rollback on errors, but continues processing other wallets\n * if individual wallet sync fails. A global lock prevents concurrent sync operations.\n *\n * During this process, all other atomic multichain related user storage updates are blocked.\n *\n * @throws Will throw if the sync operation encounters unrecoverable errors\n */\n async #performFullSyncInner(): Promise<void> {\n // Prevent multiple syncs from running at the same time.\n // Also prevents atomic updates from being applied while syncing is in progress.\n if (this.isInProgress) {\n return;\n }\n\n // Set isAccountTreeSyncingInProgress immediately to prevent race conditions\n this.#context.controllerStateUpdateFn(\n (state: AccountTreeControllerState) => {\n state.isAccountTreeSyncingInProgress = true;\n },\n );\n\n // Encapsulate the sync logic in a function to allow tracing\n const bigSyncFn = async () => {\n try {\n // 1. Identifies all local entropy wallets that can be synchronized\n const localSyncableWallets = getLocalEntropyWallets(this.#context);\n\n if (!localSyncableWallets.length) {\n // No wallets to sync, just return. This shouldn't happen.\n return;\n }\n\n // 2. Iterate over each local wallet\n for (const wallet of localSyncableWallets) {\n const entropySourceId = wallet.metadata.entropy.id;\n\n let walletProfileId: ProfileId;\n let walletFromUserStorage: UserStorageSyncedWallet | null;\n let groupsFromUserStorage: UserStorageSyncedWalletGroup[];\n\n try {\n walletProfileId = await getProfileId(\n this.#context,\n entropySourceId,\n );\n\n [walletFromUserStorage, groupsFromUserStorage] = await Promise.all([\n getWalletFromUserStorage(this.#context, entropySourceId),\n getAllGroupsFromUserStorage(this.#context, entropySourceId),\n ]);\n\n // 2.1 Decide if we need to perform legacy account syncing\n if (\n !walletFromUserStorage ||\n !walletFromUserStorage.isLegacyAccountSyncingDisabled\n ) {\n // 2.2 Perform legacy account syncing\n // This will migrate legacy account data to the new structure.\n // This operation will only be performed once.\n await performLegacyAccountSyncing(\n this.#context,\n entropySourceId,\n walletProfileId,\n );\n }\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n const errorString = `Legacy syncing failed for wallet ${wallet.id}: ${errorMessage}`;\n\n backupAndSyncLogger(errorString);\n throw new Error(errorString);\n }\n\n // 3. Execute multichain account syncing\n let stateSnapshot: StateSnapshot | undefined;\n\n try {\n // 3.1 Wallet syncing\n // Create a state snapshot before processing each wallet for potential rollback\n stateSnapshot = createStateSnapshot(this.#context);\n\n // Sync wallet metadata bidirectionally\n await syncWalletMetadata(\n this.#context,\n wallet,\n walletFromUserStorage,\n walletProfileId,\n );\n\n // 3.2 Groups syncing\n // If groups data does not exist in user storage yet, create it\n if (!groupsFromUserStorage.length) {\n // If no groups exist in user storage, we can push all groups from the wallet to the user storage and exit\n await pushGroupToUserStorageBatch(\n this.#context,\n getLocalGroupsForEntropyWallet(this.#context, wallet.id),\n entropySourceId,\n );\n\n continue; // No need to proceed with metadata comparison if groups are new\n }\n\n // Create local groups for each group from user storage if they do not exist\n // This will ensure that we have all groups available locally before syncing metadata\n await createLocalGroupsFromUserStorage(\n this.#context,\n groupsFromUserStorage,\n entropySourceId,\n walletProfileId,\n );\n\n // Sync group metadata bidirectionally\n await syncGroupsMetadata(\n this.#context,\n wallet,\n groupsFromUserStorage,\n entropySourceId,\n walletProfileId,\n );\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n const errorString = `Error during multichain account syncing for wallet ${wallet.id}: ${errorMessage}`;\n\n backupAndSyncLogger(errorString);\n\n // Attempt to rollback state changes for this wallet\n try {\n if (!stateSnapshot) {\n throw new Error(\n `State snapshot is missing for wallet ${wallet.id}`,\n );\n }\n restoreStateFromSnapshot(this.#context, stateSnapshot);\n backupAndSyncLogger(\n `Rolled back state changes for wallet ${wallet.id}`,\n );\n } catch (rollbackError) {\n backupAndSyncLogger(\n `Failed to rollback state for wallet ${wallet.id}:`,\n rollbackError instanceof Error\n ? rollbackError.message\n : String(rollbackError),\n );\n }\n\n // Continue with next wallet instead of failing the entire sync\n continue;\n }\n }\n } catch (error) {\n backupAndSyncLogger('Error during multichain account syncing:', error);\n throw error;\n }\n\n this.#context.controllerStateUpdateFn((state) => {\n state.hasAccountTreeSyncingSyncedAtLeastOnce = true;\n });\n };\n\n // Execute the big sync function with tracing and ensure state cleanup\n try {\n await this.#context.traceFn(\n {\n name: TraceName.AccountSyncFull,\n },\n bigSyncFn,\n );\n } finally {\n // Always reset state, regardless of success or failure\n this.#context.controllerStateUpdateFn(\n (state: AccountTreeControllerState) => {\n state.isAccountTreeSyncingInProgress = false;\n },\n );\n }\n }\n\n /**\n * Performs a single wallet's bidirectional metadata sync with user storage.\n *\n * @param walletId - The wallet ID to sync.\n */\n async #performSingleWalletSyncInner(\n walletId: AccountWalletId,\n ): Promise<void> {\n try {\n const wallet = this.#getEntropyWallet(walletId);\n if (!wallet) {\n return; // Only sync entropy wallets\n }\n\n const entropySourceId = wallet.metadata.entropy.id;\n const walletProfileId = await getProfileId(\n this.#context,\n entropySourceId,\n );\n const walletFromUserStorage = await getWalletFromUserStorage(\n this.#context,\n entropySourceId,\n );\n\n await syncWalletMetadata(\n this.#context,\n wallet,\n walletFromUserStorage,\n walletProfileId,\n );\n } catch (error) {\n backupAndSyncLogger(\n `Error in single wallet sync for ${walletId}:`,\n error,\n );\n throw error;\n }\n }\n\n /**\n * Performs a single group's bidirectional metadata sync with user storage.\n *\n * @param groupId - The group ID to sync.\n */\n async #performSingleGroupSyncInner(groupId: AccountGroupId): Promise<void> {\n try {\n const walletId = this.#context.groupIdToWalletId.get(groupId);\n if (!walletId) {\n return;\n }\n\n const wallet = this.#getEntropyWallet(walletId);\n if (!wallet) {\n return; // Only sync entropy wallets\n }\n\n const group = wallet.groups[groupId];\n if (!group) {\n return;\n }\n\n const entropySourceId = wallet.metadata.entropy.id;\n const walletProfileId = await getProfileId(\n this.#context,\n entropySourceId,\n );\n\n // Get the specific group from user storage\n const groupFromUserStorage = await getGroupFromUserStorage(\n this.#context,\n entropySourceId,\n group.metadata.entropy.groupIndex,\n );\n\n await syncGroupMetadata(\n this.#context,\n group,\n groupFromUserStorage,\n entropySourceId,\n walletProfileId,\n );\n } catch (error) {\n backupAndSyncLogger(`Error in single group sync for ${groupId}:`, error);\n throw error;\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../../src/backup-and-sync/service/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AccountGroupId, AccountWalletId } from '@metamask/account-api';\nimport { AccountWalletType } from '@metamask/account-api';\nimport type { UserStorageController } from '@metamask/profile-sync-controller';\n\nimport { AtomicSyncQueue } from './atomic-sync-queue';\nimport { backupAndSyncLogger } from '../../logger';\nimport type { AccountTreeControllerState } from '../../types';\nimport type { AccountWalletEntropyObject } from '../../wallet';\nimport { TraceName } from '../analytics';\nimport type { ProfileId } from '../authentication';\nimport { getProfileId } from '../authentication';\nimport {\n createLocalGroupsFromUserStorage,\n performLegacyAccountSyncing,\n syncGroupsMetadata,\n syncGroupMetadata,\n syncWalletMetadata,\n} from '../syncing';\nimport type {\n BackupAndSyncContext,\n UserStorageSyncedWallet,\n UserStorageSyncedWalletGroup,\n} from '../types';\nimport {\n getAllGroupsFromUserStorage,\n getGroupFromUserStorage,\n getWalletFromUserStorage,\n pushGroupToUserStorageBatch,\n} from '../user-storage';\nimport {\n createStateSnapshot,\n restoreStateFromSnapshot,\n getLocalEntropyWallets,\n getLocalGroupsForEntropyWallet,\n} from '../utils';\nimport type { StateSnapshot } from '../utils';\n\n/**\n * Service responsible for managing all backup and sync operations.\n *\n * This service handles:\n * - Full sync operations\n * - Single item sync operations\n * - Sync queue management\n * - Sync state management\n */\nexport class BackupAndSyncService {\n readonly #context: BackupAndSyncContext;\n\n /**\n * Queue manager for atomic sync operations.\n */\n readonly #atomicSyncQueue: AtomicSyncQueue;\n\n /**\n * Cached promise for ongoing full sync operations.\n * Ensures multiple callers await the same sync operation.\n */\n #ongoingFullSyncPromise: Promise<void> | null = null;\n\n /**\n * Cached promise for the first ongoing full sync operation.\n * Ensures multiple callers await the same sync operation.\n */\n #firstOngoingFullSyncPromise: Promise<void> | null = null;\n\n constructor(context: BackupAndSyncContext) {\n this.#context = context;\n this.#atomicSyncQueue = new AtomicSyncQueue();\n }\n\n /**\n * Checks if syncing is currently in progress.\n *\n * @returns True if syncing is in progress.\n */\n get isInProgress(): boolean {\n return this.#context.controller.state.isAccountTreeSyncingInProgress;\n }\n\n /**\n * Checks if the account tree has been synced at least once.\n *\n * @returns True if the account tree has been synced at least once.\n */\n get hasSyncedAtLeastOnce(): boolean {\n return this.#context.controller.state\n .hasAccountTreeSyncingSyncedAtLeastOnce;\n }\n\n /**\n * Checks if backup and sync is enabled by checking UserStorageController state.\n *\n * @returns True if backup and sync + account syncing is enabled.\n */\n get isBackupAndSyncEnabled(): boolean {\n const userStorageControllerState = this.#context.messenger.call(\n 'UserStorageController:getState',\n );\n const { isAccountSyncingEnabled, isBackupAndSyncEnabled } =\n userStorageControllerState;\n\n return isBackupAndSyncEnabled && isAccountSyncingEnabled;\n }\n\n /**\n * Clears the atomic queue and resets ongoing operations.\n */\n clearState(): void {\n this.#atomicSyncQueue.clear();\n this.#ongoingFullSyncPromise = null;\n this.#firstOngoingFullSyncPromise = null;\n }\n\n /**\n * Handles changes to the user storage state.\n * Used to clear the backup and sync service state.\n *\n * @param state - The new user storage state.\n */\n handleUserStorageStateChange(\n state: UserStorageController.UserStorageControllerState,\n ): void {\n if (!state.isAccountSyncingEnabled || !state.isBackupAndSyncEnabled) {\n // If either syncing is disabled, clear the account tree state\n this.clearState();\n }\n }\n\n /**\n * Gets the entropy wallet associated with the given wallet ID.\n *\n * @param walletId - The wallet ID to look up.\n * @returns The associated entropy wallet, or undefined if not found.\n */\n #getEntropyWallet(\n walletId: AccountWalletId,\n ): AccountWalletEntropyObject | undefined {\n const wallet = this.#context.controller.state.accountTree.wallets[walletId];\n return wallet?.type === AccountWalletType.Entropy ? wallet : undefined;\n }\n\n /**\n * Sets up cleanup for ongoing sync promise tracking without affecting error propagation.\n *\n * @param promise - The promise to track and clean up\n * @returns The same promise (for chaining)\n */\n #setupOngoingPromiseCleanup(promise: Promise<void>): Promise<void> {\n this.#ongoingFullSyncPromise = promise;\n // Set up cleanup without affecting the returned promise\n promise\n .finally(() => {\n this.#ongoingFullSyncPromise = null;\n })\n .catch(() => {\n // Only ignore errors from the cleanup operation itself\n // The original promise errors are still propagated to callers\n });\n return promise;\n }\n\n /**\n * Enqueues a single wallet sync operation (fire-and-forget).\n * If the first full sync has not yet occurred, it does nothing.\n *\n * @param walletId - The wallet ID to sync.\n */\n enqueueSingleWalletSync(walletId: AccountWalletId): void {\n if (!this.isBackupAndSyncEnabled || !this.hasSyncedAtLeastOnce) {\n return;\n }\n\n // eslint-disable-next-line no-void\n void this.#atomicSyncQueue.enqueue(() =>\n this.#performSingleWalletSyncInner(walletId),\n );\n }\n\n /**\n * Enqueues a single group sync operation (fire-and-forget).\n * If the first full sync has not yet occurred, it does nothing.\n *\n * @param groupId - The group ID to sync.\n */\n enqueueSingleGroupSync(groupId: AccountGroupId): void {\n if (!this.isBackupAndSyncEnabled || !this.hasSyncedAtLeastOnce) {\n return;\n }\n\n // eslint-disable-next-line no-void\n void this.#atomicSyncQueue.enqueue(() =>\n this.#performSingleGroupSyncInner(groupId),\n );\n }\n\n /**\n * Performs a full synchronization of the local account tree with user storage, ensuring consistency\n * between local state and cloud-stored account data.\n * If a full sync is already in progress, it will return the ongoing promise.\n * This clears the atomic sync queue before starting the full sync.\n *\n * NOTE: in some very edge cases, this can be ran concurrently if triggered quickly after\n * toggling back and forth the backup and sync feature from the UI.\n *\n * @returns A promise that resolves when the sync is complete.\n */\n async performFullSync(): Promise<void> {\n if (!this.isBackupAndSyncEnabled) {\n return undefined;\n }\n\n // If there's an ongoing sync (including first sync), return it\n if (this.#ongoingFullSyncPromise) {\n return this.#ongoingFullSyncPromise;\n }\n\n // Create a new ongoing sync (sequential calls after previous completed)\n const newSyncPromise = this.#atomicSyncQueue.clearAndEnqueue(() =>\n this.#performFullSyncInner(),\n );\n\n // First sync setup - create and cache the first sync promise\n if (!this.#firstOngoingFullSyncPromise) {\n this.#firstOngoingFullSyncPromise = newSyncPromise;\n }\n\n return this.#setupOngoingPromiseCleanup(newSyncPromise);\n }\n\n /**\n * Performs a full synchronization of the local account tree with user storage, ensuring consistency\n * between local state and cloud-stored account data.\n *\n * If the first ever full sync is already in progress, it will return the ongoing promise.\n * If the first ever full sync has already completed, it will resolve and NOT start a new sync.\n *\n * This clears the atomic sync queue before starting the full sync.\n *\n * @returns A promise that resolves when the sync is complete.\n */\n async performFullSyncAtLeastOnce(): Promise<void> {\n if (!this.isBackupAndSyncEnabled) {\n return undefined;\n }\n\n if (!this.#firstOngoingFullSyncPromise) {\n this.#firstOngoingFullSyncPromise = this.#atomicSyncQueue.clearAndEnqueue(\n () => this.#performFullSyncInner(),\n );\n // eslint-disable-next-line no-void\n void this.#setupOngoingPromiseCleanup(this.#firstOngoingFullSyncPromise);\n }\n\n return this.#firstOngoingFullSyncPromise;\n }\n\n /**\n * Performs a full synchronization of the local account tree with user storage, ensuring consistency\n * between local state and cloud-stored account data.\n *\n * This method performs a comprehensive sync operation that:\n * 1. Identifies all local entropy wallets that can be synchronized\n * 2. Performs legacy account syncing if needed (for backwards compatibility)\n * - Disables subsequent legacy syncing by setting a flag in user storage\n * - Exits early if multichain account syncing is disabled after legacy sync\n * 3. Executes multichain account syncing for each wallet:\n * - Syncs wallet metadata bidirectionally\n * - Creates missing local groups from user storage data (or pushes local groups if none exist remotely)\n * - Refreshes local state to reflect newly created groups\n * - Syncs group metadata bidirectionally\n *\n * The sync is atomic per wallet with rollback on errors, but continues processing other wallets\n * if individual wallet sync fails. A global lock prevents concurrent sync operations.\n *\n * During this process, all other atomic multichain related user storage updates are blocked.\n *\n * @throws Will throw if the sync operation encounters unrecoverable errors\n */\n async #performFullSyncInner(): Promise<void> {\n // Prevent multiple syncs from running at the same time.\n // Also prevents atomic updates from being applied while syncing is in progress.\n if (this.isInProgress) {\n return;\n }\n\n // Set isAccountTreeSyncingInProgress immediately to prevent race conditions\n this.#context.controllerStateUpdateFn(\n (state: AccountTreeControllerState) => {\n state.isAccountTreeSyncingInProgress = true;\n },\n );\n\n // Encapsulate the sync logic in a function to allow tracing\n const bigSyncFn = async () => {\n try {\n // 1. Identifies all local entropy wallets that can be synchronized\n const localSyncableWallets = getLocalEntropyWallets(this.#context);\n\n if (!localSyncableWallets.length) {\n // No wallets to sync, just return. This shouldn't happen.\n return;\n }\n\n // 2. Iterate over each local wallet\n for (const wallet of localSyncableWallets) {\n const entropySourceId = wallet.metadata.entropy.id;\n\n let walletProfileId: ProfileId;\n let walletFromUserStorage: UserStorageSyncedWallet | null;\n let groupsFromUserStorage: UserStorageSyncedWalletGroup[];\n\n try {\n walletProfileId = await getProfileId(\n this.#context,\n entropySourceId,\n );\n\n [walletFromUserStorage, groupsFromUserStorage] = await Promise.all([\n getWalletFromUserStorage(this.#context, entropySourceId),\n getAllGroupsFromUserStorage(this.#context, entropySourceId),\n ]);\n\n // 2.1 Decide if we need to perform legacy account syncing\n if (\n !walletFromUserStorage ||\n !walletFromUserStorage.isLegacyAccountSyncingDisabled\n ) {\n // 2.2 Perform legacy account syncing\n // This will migrate legacy account data to the new structure.\n // This operation will only be performed once.\n await performLegacyAccountSyncing(\n this.#context,\n entropySourceId,\n walletProfileId,\n );\n }\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n const errorString = `Legacy syncing failed for wallet ${wallet.id}: ${errorMessage}`;\n\n backupAndSyncLogger(errorString);\n throw new Error(errorString);\n }\n\n // 3. Execute multichain account syncing\n let stateSnapshot: StateSnapshot | undefined;\n\n try {\n // 3.1 Wallet syncing\n // Create a state snapshot before processing each wallet for potential rollback\n stateSnapshot = createStateSnapshot(this.#context);\n\n // Sync wallet metadata bidirectionally\n await syncWalletMetadata(\n this.#context,\n wallet,\n walletFromUserStorage,\n walletProfileId,\n );\n\n // 3.2 Groups syncing\n // If groups data does not exist in user storage yet, create it\n if (!groupsFromUserStorage.length) {\n // If no groups exist in user storage, we can push all groups from the wallet to the user storage and exit\n await pushGroupToUserStorageBatch(\n this.#context,\n getLocalGroupsForEntropyWallet(this.#context, wallet.id),\n entropySourceId,\n );\n\n continue; // No need to proceed with metadata comparison if groups are new\n }\n\n // Create local groups for each group from user storage if they do not exist\n // This will ensure that we have all groups available locally before syncing metadata\n await createLocalGroupsFromUserStorage(\n this.#context,\n groupsFromUserStorage,\n entropySourceId,\n walletProfileId,\n );\n\n // Sync group metadata bidirectionally\n await syncGroupsMetadata(\n this.#context,\n wallet,\n groupsFromUserStorage,\n entropySourceId,\n walletProfileId,\n );\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n const errorString = `Error during multichain account syncing for wallet ${wallet.id}: ${errorMessage}`;\n\n backupAndSyncLogger(errorString);\n\n // Attempt to rollback state changes for this wallet\n try {\n if (!stateSnapshot) {\n throw new Error(\n `State snapshot is missing for wallet ${wallet.id}`,\n );\n }\n restoreStateFromSnapshot(this.#context, stateSnapshot);\n backupAndSyncLogger(\n `Rolled back state changes for wallet ${wallet.id}`,\n );\n } catch (rollbackError) {\n backupAndSyncLogger(\n `Failed to rollback state for wallet ${wallet.id}:`,\n rollbackError instanceof Error\n ? rollbackError.message\n : String(rollbackError),\n );\n }\n\n // Continue with next wallet instead of failing the entire sync\n continue;\n }\n }\n } catch (error) {\n backupAndSyncLogger('Error during multichain account syncing:', error);\n throw error;\n }\n\n this.#context.controllerStateUpdateFn((state) => {\n state.hasAccountTreeSyncingSyncedAtLeastOnce = true;\n });\n };\n\n // Execute the big sync function with tracing and ensure state cleanup\n try {\n await this.#context.traceFn(\n {\n name: TraceName.AccountSyncFull,\n },\n bigSyncFn,\n );\n } finally {\n // Always reset state, regardless of success or failure\n this.#context.controllerStateUpdateFn(\n (state: AccountTreeControllerState) => {\n state.isAccountTreeSyncingInProgress = false;\n },\n );\n }\n }\n\n /**\n * Performs a single wallet's bidirectional metadata sync with user storage.\n *\n * @param walletId - The wallet ID to sync.\n */\n async #performSingleWalletSyncInner(\n walletId: AccountWalletId,\n ): Promise<void> {\n try {\n const wallet = this.#getEntropyWallet(walletId);\n if (!wallet) {\n return; // Only sync entropy wallets\n }\n\n const entropySourceId = wallet.metadata.entropy.id;\n const walletProfileId = await getProfileId(\n this.#context,\n entropySourceId,\n );\n const walletFromUserStorage = await getWalletFromUserStorage(\n this.#context,\n entropySourceId,\n );\n\n await syncWalletMetadata(\n this.#context,\n wallet,\n walletFromUserStorage,\n walletProfileId,\n );\n } catch (error) {\n backupAndSyncLogger(\n `Error in single wallet sync for ${walletId}:`,\n error,\n );\n throw error;\n }\n }\n\n /**\n * Performs a single group's bidirectional metadata sync with user storage.\n *\n * @param groupId - The group ID to sync.\n */\n async #performSingleGroupSyncInner(groupId: AccountGroupId): Promise<void> {\n try {\n const walletId = this.#context.groupIdToWalletId.get(groupId);\n if (!walletId) {\n return;\n }\n\n const wallet = this.#getEntropyWallet(walletId);\n if (!wallet) {\n return; // Only sync entropy wallets\n }\n\n const group = wallet.groups[groupId];\n if (!group) {\n return;\n }\n\n const entropySourceId = wallet.metadata.entropy.id;\n const walletProfileId = await getProfileId(\n this.#context,\n entropySourceId,\n );\n\n // Get the specific group from user storage\n const groupFromUserStorage = await getGroupFromUserStorage(\n this.#context,\n entropySourceId,\n group.metadata.entropy.groupIndex,\n );\n\n await syncGroupMetadata(\n this.#context,\n group,\n groupFromUserStorage,\n entropySourceId,\n walletProfileId,\n );\n } catch (error) {\n backupAndSyncLogger(`Error in single group sync for ${groupId}:`, error);\n throw error;\n }\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metamask-previews/account-tree-controller",
|
|
3
|
-
"version": "0.15.1-preview-
|
|
3
|
+
"version": "0.15.1-preview-c8856e2",
|
|
4
4
|
"description": "Controller to group account together based on some pre-defined rules",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"MetaMask",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@metamask/base-controller": "^8.
|
|
50
|
+
"@metamask/base-controller": "^8.4.0",
|
|
51
51
|
"@metamask/snaps-sdk": "^9.0.0",
|
|
52
52
|
"@metamask/snaps-utils": "^11.0.0",
|
|
53
53
|
"@metamask/superstruct": "^3.1.0",
|