{"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 { 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  toErrorMessage,\n} from '../utils';\nimport type { StateSnapshot } from '../utils';\nimport { AtomicSyncQueue } from './atomic-sync-queue';\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 (\n      !this.isBackupAndSyncEnabled ||\n      !this.hasSyncedAtLeastOnce ||\n      // This prevents rate limiting scenarios where full syncs trigger group creations\n      // that in turn enqueue the same single group syncs that the full sync just did.\n      // This can very rarely lead to inconsistencies, but will be fixed on the next full sync.\n      // TODO: let's improve this in the future by tracking the updates done in the full sync and\n      // comparing against that.\n      this.isInProgress\n    ) {\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 = toErrorMessage(error);\n\n            backupAndSyncLogger(\n              `Legacy syncing failed for wallet ${wallet.id}: ${errorMessage}`,\n            );\n            throw new Error(\n              `Legacy syncing failed for wallet: ${errorMessage}`,\n            );\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 = toErrorMessage(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"]}