{"version":3,"file":"controller-integration.cjs","sourceRoot":"","sources":["../../../../src/controllers/user-storage/contact-syncing/controller-integration.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AddressBookEntry } from '@metamask/address-book-controller';\n\nimport { canPerformContactSyncing } from './sync-utils';\nimport type { ContactSyncingOptions } from './types';\nimport type { UserStorageContactEntry } from './types';\nimport type { SyncAddressBookEntry } from './utils';\nimport {\n  mapAddressBookEntryToUserStorageEntry,\n  mapUserStorageEntryToAddressBookEntry,\n} from './utils';\nimport { isContactBridgedFromAccounts } from './utils';\nimport { USER_STORAGE_FEATURE_NAMES } from '../../../shared/storage-schema';\nimport { TraceName } from '../constants';\n\nexport type SyncContactsWithUserStorageConfig = {\n  onContactSyncErroneousSituation?: (\n    errorMessage: string,\n    sentryContext?: Record<string, unknown>,\n  ) => void;\n  onContactUpdated?: () => void;\n  onContactDeleted?: () => void;\n};\n\n/**\n * Creates a unique key for a contact based on chainId and address\n *\n * @param contact - The contact to create a key for\n * @returns A unique string key\n */\nfunction createContactKey(contact: AddressBookEntry): string {\n  if (!contact.address) {\n    throw new Error('Contact address is required to create storage key');\n  }\n  return `${contact.chainId}_${contact.address.toLowerCase()}`;\n}\n\n/**\n * Syncs contacts between local storage and user storage (remote).\n *\n * Handles the following syncing scenarios:\n * 1. First Sync: When local contacts exist but there are no remote contacts, uploads all local contacts.\n * 2. New Device Sync: Downloads remote contacts that don't exist locally (empty local address book).\n * 3. Simple Merge: Ensures both sides (local & remote) have all contacts.\n * 4. Contact Naming Conflicts: When same contact has different names, uses most recent by timestamp.\n * 5. Local Updates: When a contact was updated locally, syncs changes to remote if local is newer.\n * 6. Remote Updates: When a contact was updated remotely, applies changes locally if remote is newer.\n * 7. Local Deletions: Handled by real-time event handlers (deleteContactInRemoteStorage) to prevent false positives.\n * 8. Remote Deletions: When a contact was deleted remotely, applies deletion locally.\n * 9. Concurrent Updates: Resolves conflicts using timestamps to determine the winner.\n * 10. Restore After Delete: If a contact is modified after being deleted, restores it.\n * 11. ChainId Differences: Treats same address on different chains as separate contacts.\n *\n * @param config - Parameters used for syncing callbacks\n * @param options - Parameters used for syncing operations\n * @returns Promise that resolves when contact synchronization is complete\n */\nexport async function syncContactsWithUserStorage(\n  config: SyncContactsWithUserStorageConfig,\n  options: ContactSyncingOptions,\n): Promise<void> {\n  const { getMessenger, getUserStorageControllerInstance, trace } = options;\n  const {\n    onContactSyncErroneousSituation,\n    onContactUpdated,\n    onContactDeleted,\n  } = config;\n\n  // Cannot perform sync, conditions not met\n  if (!canPerformContactSyncing(options)) {\n    return;\n  }\n\n  // NOTE: Pre-sync operations (canPerformContactSyncing, AddressBookController:list, getRemoteContacts)\n  // are intentionally outside try-catch to let errors bubble up to Sentry for better debugging.\n  // Only \"erroneous situation\" errors during sync logic itself should be caught.\n\n  // Get all local contacts from AddressBookController (exclude chain \"*\" contacts)\n  const localVisibleContacts =\n    getMessenger()\n      .call('AddressBookController:list')\n      .filter((contact) => !isContactBridgedFromAccounts(contact))\n      .filter(\n        (contact) => contact.address && contact.chainId && contact.name?.trim(),\n      ) || [];\n\n  // Get remote contacts from user storage API\n  const remoteContacts = await getRemoteContacts(options);\n\n  // Filter remote contacts to exclude invalid ones (or empty array if no remote contacts)\n  const validRemoteContacts =\n    remoteContacts?.filter(\n      (contact) => contact.address && contact.chainId && contact.name?.trim(),\n    ) || [];\n\n  const performSync = async () => {\n    try {\n      // Activate sync semaphore to prevent event loops\n      await getUserStorageControllerInstance().setIsContactSyncingInProgress(\n        true,\n      );\n\n      // Prepare maps for efficient lookup\n      const localContactsMap = new Map<string, AddressBookEntry>();\n      const remoteContactsMap = new Map<string, SyncAddressBookEntry>();\n\n      localVisibleContacts.forEach((contact) => {\n        const key = createContactKey(contact);\n        localContactsMap.set(key, contact);\n      });\n\n      validRemoteContacts.forEach((contact) => {\n        const key = createContactKey(contact);\n        remoteContactsMap.set(key, contact);\n      });\n\n      // Lists to track contacts that need to be synced\n      const contactsToAddOrUpdateLocally: SyncAddressBookEntry[] = [];\n      const contactsToDeleteLocally: SyncAddressBookEntry[] = [];\n      const contactsToUpdateRemotely: AddressBookEntry[] = [];\n\n      // SCENARIO 2 & 6: Process remote contacts - handle new device sync and remote updates\n      for (const remoteContact of validRemoteContacts) {\n        const key = createContactKey(remoteContact);\n        const localContact = localContactsMap.get(key);\n\n        // Handle remote contact based on its status and local existence\n        if (remoteContact.deletedAt) {\n          // SCENARIO 8: Remote deletion - should be applied locally if contact exists locally\n          if (localContact) {\n            contactsToDeleteLocally.push(remoteContact);\n          }\n        } else if (!localContact) {\n          // SCENARIO 2: New contact from remote - import to local\n          contactsToAddOrUpdateLocally.push(remoteContact);\n        } else {\n          // SCENARIO 4 & 6: Contact exists on both sides - check for conflicts\n          const hasContentDifference =\n            localContact.name !== remoteContact.name ||\n            localContact.memo !== remoteContact.memo;\n\n          if (hasContentDifference) {\n            // Check timestamps to determine which version to keep\n            const localTimestamp = localContact.lastUpdatedAt || 0;\n            const remoteTimestamp = remoteContact.lastUpdatedAt || 0;\n\n            if (localTimestamp >= remoteTimestamp) {\n              // Local is newer (or same age) - use local version\n              contactsToUpdateRemotely.push(localContact);\n            } else {\n              // Remote is newer - use remote version\n              contactsToAddOrUpdateLocally.push(remoteContact);\n            }\n          }\n\n          // Else: content is identical, no action needed\n        }\n      }\n\n      // SCENARIO 1, 3 & 5: Process local contacts not in remote - handles first sync and new local contacts\n      for (const localContact of localVisibleContacts) {\n        const key = createContactKey(localContact);\n        const remoteContact = remoteContactsMap.get(key);\n\n        if (!remoteContact) {\n          // New local contact or first sync - add to remote\n          contactsToUpdateRemotely.push(localContact);\n        }\n      }\n\n      // Apply local deletions\n      // Note: Individual errors are intentionally NOT caught here to ensure they reach Sentry\n      // for debugging. Previous versions silently suppressed these errors which made\n      // troubleshooting contact sync issues difficult.\n      for (const contact of contactsToDeleteLocally) {\n        getMessenger().call(\n          'AddressBookController:delete',\n          contact.chainId,\n          contact.address,\n        );\n\n        if (onContactDeleted) {\n          onContactDeleted();\n        }\n      }\n\n      // Apply local additions/updates\n      // Note: Individual errors are intentionally NOT caught here to ensure they reach Sentry\n      // for debugging. Previous versions silently suppressed these errors which made\n      // troubleshooting contact sync issues difficult.\n      for (const contact of contactsToAddOrUpdateLocally) {\n        if (!contact.deletedAt) {\n          getMessenger().call(\n            'AddressBookController:set',\n            contact.address,\n            contact.name || '',\n            contact.chainId,\n            contact.memo || '',\n            contact.addressType,\n          );\n\n          if (onContactUpdated) {\n            onContactUpdated();\n          }\n        }\n      }\n\n      // Apply changes to remote storage\n      if (contactsToUpdateRemotely.length > 0) {\n        const updatedRemoteContacts: Record<string, SyncAddressBookEntry> = {};\n        for (const localContact of contactsToUpdateRemotely) {\n          const key = createContactKey(localContact);\n          updatedRemoteContacts[key] = {\n            ...remoteContactsMap.get(key), // Start with an existing remote contact if it exists\n            ...localContact, // override with local changes\n            lastUpdatedAt: Date.now(), // mark as updated\n          };\n        }\n        // Save updated contacts to remote storage\n        await saveContactsToUserStorage(\n          Object.values(updatedRemoteContacts),\n          options,\n        );\n      }\n    } catch (error) {\n      if (onContactSyncErroneousSituation) {\n        onContactSyncErroneousSituation('Error synchronizing contacts', {\n          error,\n        });\n\n        // Re-throw the error to be handled by the caller\n        throw error;\n      }\n    } finally {\n      await getUserStorageControllerInstance().setIsContactSyncingInProgress(\n        false,\n      );\n    }\n  };\n\n  if (trace) {\n    // Gather pre-sync metrics for performance analysis\n    const initialLocalContacts = localVisibleContacts;\n    const initialValidRemoteContacts = validRemoteContacts;\n\n    await trace(\n      {\n        name: TraceName.ContactSyncFull,\n        data: {\n          localContactCount: initialLocalContacts.length,\n          remoteContactCount: initialValidRemoteContacts.length,\n          isFirstSync:\n            initialValidRemoteContacts.length === 0 &&\n            initialLocalContacts.length > 0,\n          isNewDeviceSync:\n            initialLocalContacts.length === 0 &&\n            initialValidRemoteContacts.length > 0,\n          isRegularSync:\n            initialLocalContacts.length > 0 &&\n            initialValidRemoteContacts.length > 0,\n          hasDataToSync:\n            initialLocalContacts.length > 0 ||\n            initialValidRemoteContacts.length > 0,\n          expectedWorkload:\n            initialLocalContacts.length + initialValidRemoteContacts.length,\n        },\n      },\n      performSync,\n    );\n\n    return;\n  }\n\n  await performSync();\n}\n\n/**\n * Retrieves remote contacts from user storage API\n *\n * @param options - Parameters used for retrieving remote contacts\n * @returns Array of contacts from remote storage, or null if none found\n */\nasync function getRemoteContacts(\n  options: ContactSyncingOptions,\n): Promise<SyncAddressBookEntry[] | null> {\n  const { getUserStorageControllerInstance } = options;\n\n  try {\n    const remoteContactsJsonArray =\n      await getUserStorageControllerInstance().performGetStorageAllFeatureEntries(\n        USER_STORAGE_FEATURE_NAMES.addressBook,\n      );\n\n    if (!remoteContactsJsonArray || remoteContactsJsonArray.length === 0) {\n      return null;\n    }\n\n    // Parse each JSON entry and convert from UserStorageContactEntry to AddressBookEntry\n    const remoteStorageEntries = remoteContactsJsonArray.map((contactJson) => {\n      const entry = JSON.parse(contactJson) as UserStorageContactEntry;\n      return mapUserStorageEntryToAddressBookEntry(entry);\n    });\n\n    return remoteStorageEntries;\n  } catch {\n    return null;\n  }\n}\n\n/**\n * Saves local contacts to user storage\n *\n * @param contacts - The contacts to save to user storage\n * @param options - Parameters used for saving contacts\n * @returns Promise that resolves when contacts are saved\n */\nasync function saveContactsToUserStorage(\n  contacts: AddressBookEntry[],\n  options: ContactSyncingOptions,\n): Promise<void> {\n  const { getUserStorageControllerInstance, trace } = options;\n\n  const saveContacts = async () => {\n    if (!contacts || contacts.length === 0) {\n      return;\n    }\n\n    // Convert each AddressBookEntry to UserStorageContactEntry format and create key-value pairs\n    const storageEntries: [string, string][] = contacts.map((contact) => {\n      const key = createContactKey(contact);\n      const storageEntry = mapAddressBookEntryToUserStorageEntry(contact);\n      return [key, JSON.stringify(storageEntry)];\n    });\n\n    await getUserStorageControllerInstance().performBatchSetStorage(\n      USER_STORAGE_FEATURE_NAMES.addressBook,\n      storageEntries,\n    );\n  };\n\n  return trace\n    ? await trace(\n        {\n          name: TraceName.ContactSyncSaveBatch,\n          data: {\n            contactCount: contacts.length,\n            // Performance scaling indicators\n            hasBatchOperations: contacts.length > 1,\n            chainCount: new Set(contacts.map((c) => c.chainId)).size,\n            hasMemosCount: contacts.filter((c) => c.memo?.length).length,\n          },\n        },\n        saveContacts,\n      )\n    : await saveContacts();\n}\n\n/**\n * Updates a single contact in remote storage without performing a full sync\n * This is used when a contact is updated locally to efficiently push changes to remote\n *\n * @param contact - The contact that was updated locally\n * @param options - Parameters used for syncing operations\n * @returns Promise that resolves when the contact is updated\n */\nexport async function updateContactInRemoteStorage(\n  contact: AddressBookEntry,\n  options: ContactSyncingOptions,\n): Promise<void> {\n  const { trace } = options;\n\n  const updateContact = async () => {\n    if (\n      !canPerformContactSyncing(options) ||\n      !contact.address ||\n      !contact.chainId ||\n      !contact.name?.trim()\n    ) {\n      return;\n    }\n\n    const { getUserStorageControllerInstance } = options;\n\n    // Create an updated entry with timestamp\n    const updatedEntry = {\n      ...contact,\n      lastUpdatedAt: contact.lastUpdatedAt || Date.now(),\n    } as SyncAddressBookEntry;\n\n    const key = createContactKey(contact);\n    const storageEntry = mapAddressBookEntryToUserStorageEntry(updatedEntry);\n\n    // Save individual contact to remote storage\n    await getUserStorageControllerInstance().performSetStorage(\n      `${USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`,\n      JSON.stringify(storageEntry),\n    );\n  };\n\n  if (trace) {\n    return await trace(\n      {\n        name: TraceName.ContactSyncUpdateRemote,\n        data: {\n          chainId: contact.chainId,\n          // Performance indicators\n          hasTimestamp: Boolean(contact.lastUpdatedAt),\n          hasMemo: Boolean(contact.memo?.length),\n          isUpdate: Boolean(contact.lastUpdatedAt), // vs new contact\n        },\n      },\n      updateContact,\n    );\n  }\n\n  return await updateContact();\n}\n\n/**\n * Marks a single contact as deleted in remote storage without performing a full sync\n * This is used when a contact is deleted locally to efficiently push the deletion to remote\n *\n * @param contact - The contact that was deleted locally (contains at least address and chainId)\n * @param options - Parameters used for syncing operations\n * @returns Promise that resolves when the contact is marked as deleted\n */\nexport async function deleteContactInRemoteStorage(\n  contact: AddressBookEntry,\n  options: ContactSyncingOptions,\n): Promise<void> {\n  const { trace } = options;\n  const deleteContact = async () => {\n    if (\n      !canPerformContactSyncing(options) ||\n      !contact.address ||\n      !contact.chainId ||\n      !contact.name?.trim()\n    ) {\n      return;\n    }\n\n    const { getUserStorageControllerInstance } = options;\n    const key = createContactKey(contact);\n\n    try {\n      // Try to get the existing contact first\n      const existingContactJson =\n        await getUserStorageControllerInstance().performGetStorage(\n          `${USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`,\n        );\n\n      if (existingContactJson) {\n        // Mark the existing contact as deleted\n        const existingStorageEntry = JSON.parse(\n          existingContactJson,\n        ) as UserStorageContactEntry;\n        const existingContact =\n          mapUserStorageEntryToAddressBookEntry(existingStorageEntry);\n\n        const now = Date.now();\n        const deletedContact = {\n          ...existingContact,\n          deletedAt: now,\n          lastUpdatedAt: now,\n        } as SyncAddressBookEntry;\n\n        const deletedStorageEntry =\n          mapAddressBookEntryToUserStorageEntry(deletedContact);\n\n        // Save the deleted contact back to storage\n        await getUserStorageControllerInstance().performSetStorage(\n          `${USER_STORAGE_FEATURE_NAMES.addressBook}.${key}`,\n          JSON.stringify(deletedStorageEntry),\n        );\n      }\n    } catch {\n      // If contact doesn't exist in remote storage, no need to mark as deleted\n      console.warn('Contact not found in remote storage for deletion:', key);\n    }\n  };\n\n  return trace\n    ? await trace({ name: TraceName.ContactSyncDeleteRemote }, deleteContact)\n    : await deleteContact();\n}\n"]}